Some code bits

So you have a need, a need for a particular routine. Well it just may be here.

Square Calculator

This routine calculates the 16-bit unsigned integer square of a signed 16-bit integer in the range +/-255 (decimal).

; by Lee Davison; Calculates the 16 bit unsigned integer square of the signed 
; 16 bit integer in Numberl/Numberh.
; The result is always in the range 0 to 65025 and is held in Squarel/Squareh
;
; The maximum input range is only +/-255 and no checking is done to ensure that
; this is so.
;
; This routine is useful if you are trying to draw circles as for any circle
;
; x^2+y^2=r^2 where x and y are the co-ordinates of any point on the circle and
; r is the circle radius
;
; Destroys all registers

	.ORG	8000		; these must be in RAM

Numberl				; number to square low byte
Numberh	= Numberl+		; number to square high byte
	.word $FFFF

Squarel				; square low byte
Squareh	= Squarel+1		; square high byte
	.word $FFFF

Tempsq				; temp byte for intermediate result
	.byte $00

	.ORG	8192		; any address will do

Square
	LDA	#$00		; clear A
	STA	Squarel		; clear square low byte
				; (the high byte gets shifted out)
	LDA	Numberl		; get number low byte
	LDX	Numberh		; get number high byte
	BPL	NoNneg		; if +ve don't negate it
				; else do a two's complement
	EOR	#$FF		; invert
	SEC			; +1
	ADC	#$00		; and add it

NoNneg:
	STA	Tempsq		; save ABS(number)
	LDX	#$08		; set bit count

Nextr2bit:
	ASL	Squarel		; low byte *2
	ROL	Squareh		; high byte *2+carry from low
	ASL	A		; shift number byte
	BCC	NoSqadd		; don't do add if C = 0
	TAY			; save A
	CLC			; clear carry for add
	LDA	Tempsq		; get number
	ADC	Squarel		; add number^2 low byte
	STA	Squarel		; save number^2 low byte
	LDA	#$00		; clear A
	ADC	Squareh		; add number^2 high byte
	STA	Squareh		; save number^2 high byte
	TYA			; get A back

NoSqadd:
	DEX			; decrement bit count
	BNE	Nextr2bit	; go do next bit
	RTS

Square Root Calculator

This routine calculates the 8-bit integer square root and 9 bit integer remainder of an unsigned 16-bit integer.

; by Lee Davison
; Calculates the 8 bit root and 9 bit remainder of a 16 bit unsigned integer in	
; Numberl/Numberh. The result is always in the range 0 to 255 and is held in
; Root, the remainder is in the range 0 to 511 and is held in Reml/Remh
;
; partial results are held in templ/temph
;
; This routine is the complement to the integer square program.
;
; Destroys A, X registers.

; variables - must be in RAM

Numberl		= $F0		; number to find square root of low byte
Numberh		= Numberl+1	; number to find square root of high byte
Reml		= $F2		; remainder low byte
Remh		= Reml+1	; remainder high byte
templ		= $F4		; temp partial low byte
temph		= templ+1	; temp partial high byte
Root		= $F6		; square root

	*= $8000		; can be anywhere, ROM or RAM

SqRoot
	LDA	#$00		; clear A
	STA	Reml		; clear remainder low byte
	STA	Remh		; clear remainder high byte
	STA	Root		; clear Root
	LDX	#$08		; 8 pairs of bits to do
Loop
	ASL	Root		; Root = Root * 2

	ASL	Numberl		; shift highest bit of number ..
	ROL	Numberh		;
	ROL	Reml		; .. into remainder
	ROL	Remh		;

	ASL	Numberl		; shift highest bit of number ..
	ROL	Numberh		;
	ROL	Reml		; .. into remainder
	ROL	Remh		;

	LDA	Root		; copy Root ..
	STA	templ		; .. to templ
	LDA	#$00		; clear byte
	STA	temph		; clear temp high byte

	SEC			; +1
	ROL	templ		; temp = temp * 2 + 1
	ROL	temph		;

	LDA	Remh		; get remainder high byte
	CMP	temph		; comapre with partial high byte
	BCC	Next		; skip sub if remainder high byte smaller

	BNE	Subtr		; do sub if <> (must be remainder>partial !)

	LDA	Reml		; get remainder low byte
	CMP	templ		; comapre with partial low byte
	BCC	Next		; skip sub if remainder low byte smaller

				; else remainder>=partial so subtract then
				; and add 1 to root. carry is always set here
Subtr
	LDA	Reml		; get remainder low byte
	SBC	templ		; subtract partial low byte
	STA	Reml		; save remainder low byte
	LDA	Remh		; get remainder high byte
	SBC	temph		; subtract partial high byte
	STA	Remh		; save remainder high byte

	INC	Root		; increment Root
Next
	DEX			; decrement bit pair count
	BNE	Loop		; loop if not all done

	RTS

256-Byte Data Buffer

This routine is an interrupt serviced 256 byte data buffer for serial ports and the like.
Very similar code was used to supply the stepper motor driver routine with line co-ordinate pairs.
This is not a finished solution, you will need to add your own code to drive the target of the buffer.

; By Lee Davison
; code for an interrupt serviced data buffer. similar code is used to drive
; the XY stepper motors on a plotter with new position information every 5mS
; and also to allow pen up/down movement time of 70mS

; buffer and variables must be in RAM

Buffer				; 256 byte buffer (must start at page edge)
	.ds	$100

BRindx				; buffer read index
	.byte	$00

BWindx				; buffer write index
	.byte	$00

Sendf				; am sending flag
	.byte	$00

WByte				; temp store for the byte to be sent
	.byte	$00

; write the data to the buffer a byte at a time and increment the pointer.
; the routine is called with the byte to write in A. If the interrupt
; device is idle when this routine is called it will wake it up by doing
; a BRK before it exits

; destroys the Y register

; can be ROM or RAM

Incwritb
	STA	WByte		; save byte to write
	LDA	BRindx		; get read index
	SEC			; set carry for subtract
	SBC	BWindx		; subtract write index
	BEQ	Dowrite		; if equal then buffer empty so do write
	CMP	#$02		; need at least n+1 bytes to avoid rollover
	BCC	Incwritb	; loop if no space


				; construct and write data to buffer
Dowrite
	LDY	BWindx		; get write index
	LDA	WByte		; get byte to write
	STA	Buffer,Y	; save it
	INY			; increment index to next byte
	STY	BWindx		; save new write index byte

; now see if the interrupt service routine is already running or if it's
; idle

	LDA	Sendf		; get the sending flag
	BNE	Doingit		; skip call if running
	BRK			; software call to interrupt routine
	NOP			; need this as return from BRK is +1 byte!
	CLI			; enable the interrupts

Doingit
	RTS				

; this is the interrupt service routine. takes a byte a time from the
; buffer and does some thing with it. also sets up the device(s) for
; the next interrupt

; no registers altered

BuffIRQ
	PHA			; save A
	TXA			; copy X
	PHA			; save X
	TYA			; copy Y
	PHA			; save Y

; insert code here to ensure this is the interrupt you want. if it
; isn't then just exit quietly via ResExit the end of the routine

Getnext
	JSR	Increadb	; increment pointer and read byte from buffer
	BCS	ExitIRQ		; branch if no byte to do

; here would be the guts of the routine such as sending the byte to
; the ACIA or a printer port or some other byte device. it will also
; ensure the device is set to generate an interrupt when it's completed
; it's task

	LDA	#$FF		; set byte
	STA	Sendf		; set sending flag
	JMP	ResExit		; restore the registers & exit

				; all done, clear the flag restore the
				; registers & exit
ExitIRQ
	LDA	#$00		; clear byte
	STA	Sendf		; clear sending flag

ResExit
	PLA			; pull Y
	TAY			; restore it
	PLA			; pull X
	TAX			; restore it
	PLA			; restore A
	RTI			; this was an interrupt service request
				; so exit properly

; get byte from the buffer and increment the pointer. If the buffer is empty
; then exit with the carry flag set else exit with carry clear and the byte
; in A

Increadb
	LDY	BRindx		; get buffer read index
	CPY	BWindx		; compare write index
	BEQ	NOktoread	; branch if empty (= sets carry)
	LDA	Buffer,Y	; get byte from buffer
	INY			; increment pointer
	STY	BRindx		; save buffer read index
	CLC			; clear not ok flag

NOktoread
	RTS

6502 8 bit PRNG

This is a short piece of code that generates a maximal length, 8 bit, pseudo random
number sequence. This routine is no great shakes in the speed stakes, it’s just a demo of a usefull
technique.

; by Lee Davison
; returns pseudo random 8 bit number in A. Affects A, X and Y.
; (r_seed) is the byte from which the number is generated and MUST be
; initialised to a non zero value or this function will always return
; zero. Also r_seed must be in RAM, you can see why......

rand_8
	LDA	r_seed		; get seed
	AND	#$B8		; mask non feedback bits
				; for maximal length run with 8 bits we need
				; taps at b7, b5, b4 and b3
	LDX	#$05		; bit count (shift top 5 bits)
	LDY	#$00		; clear feedback count
F_loop
	ASL	A		; shift bit into carry
	BCC	bit_clr		; branch if bit = 0

	INY			; increment feedback count (b0 is XOR all the	
				; shifted bits from A)
bit_clr
	DEX			; decrement count
	BNE	F_loop		; loop if not all done

no_clr
	TYA			; copy feedback count
	LSR	A		; bit 0 into Cb
	LDA	r_seed		; get seed back
	ROL	A		; rotate carry into byte
	STA	r_seed		; save number as next seed
	RTS			; done

r_seed
	.byte	1		; prng seed byte (must not be zero)

Other number lengths
A maximal length pseudo random number generator is basically just a shif register with feedback from the later stages to generate the next input bit. The form for a generator of length n with feedback from t is shown below.
Pseudo random sequence generator

You don’t have to limit the length to only 8 bits, any number of bits from two upwards will work if you chose the right feedback tap(s). Here is a table of some values.

Bits [n] Tap(s) [t] Length Bits [n] Tap(s) [t] Length
2 1 3 3 2 7
4 3 15 5 3 31
6 5 63 7 6 127
8 6,5,4 255  9 5 511
10 7 1023 11 9 2047
15 14 32767 16 15,13,4 65535 
17 14 131071 18 11 262143
20 17 1048575 21 19 2097151
22 21 4194303 23 18 8388607
24 23,22,17 16777215  25 22 33554431
28 25 268435455 29 27 536870911
31 28 2147483647

In the last case, with 31 bits, if you took a new value once a second the sequence
wouldn’t repeat for over sixty eight years. Even clocked at 1MHz it would still take
over thirty five minutes to cycle through every state.
Faster, shorter method
The above pseudo random number generator is known as a Fibonacci generator and, while it works, it turns out there is a much better generator to implement with a microprocessor.

Galois pseudo random sequence generator.

The Galois generator needs only to test the state of one bit and that can be tested after
the shift has been performed. The state of this bit determines whether the feedback term
is exclusive ORed with the result. This single bit test and multiple bit feedback is
easily done as can be seen from the code below.

; returns pseudo random 8 bit number in A. Affects A. (r_seed) is the
; byte from which the number is generated and MUST be initialised to a
; non zero value or this function will always return zero. Also r_seed		
; must be in RAM, you can see why......

rand_8
	LDA	r_seed		; get seed
	ASL			; shift byte
	BCC	no_eor		; branch if no carry

	EOR	#$CF		; else EOR with $CF
no_eor
	STA	r_seed		; save number as next seed
	RTS			; done

r_seed
	.byte	1		; prng seed byte, must not be zero

You don’t have to limit the length to only 8 bits, any number of bits from three upwards will work if you chose the right feedback value. Here is a table of some values that have been chosen to give a feedback value that fits in a byte, this makes implementation on an 8 bit micro as short as possible. For these values bit n is the least significant bit in the value and the tested bit is the bit shifted out from bit 1.

Bits [n] Feedback Length Bits [n] Feedback Length
3 $03 7 4 $03 15
5 $17 31 6 $27 63
7 $4B 127 8 $CF 255 
9 $B7 511 10 $E7 1023
11 $EB 2047 12 $EB 4095
13 $BB 8191 14 $BB 16383
15 $DD 32767 16 $D7 65535 
17 $AF 131071 18 $E7 262143
19 $AF 524287 20 $F3 1048575
21 $B7 2097151 22 $BB 4194303
23 $F3 8388607 24 $DB 16777215 
25 $93 33554431 26 $B1 67108863
27 $B1 134217727 28 $E1 268435455
29 $C3 536870911 30 $AF 1073741823
31 $8B 2147483647 32 $AF 4294967295 

This is now the form of sequence generator used to generate values for the RND() function in EhBASIC.

“The generation of random numbers is too important to be left to chance.”

Robert R. Coveyou.