Getting a foothold into 6502 machine language, part 5
I’ve rewritten the code I developed earlier and extended it into a full-fledged usr
machine language routine. However, it needed testing, because of all the rewrites. So I grabbed my copy of Working Copy on my iPad and started Git-versioning both the assembly and the Basic code. It was invaluable for getting the code to work correctly.
[ part 1 | part 2 | part 3 | part 4 | bonus ]
Here are some of the major changes I made:
- the input and output of functions now use the .A, .X and .Y registers
- the prime function now picks out of a list of all the 54 prime numbers that smaller than 256
- a main routine was added that tests the primality of the value put into the
usr
function in Basic, and returns either a zero (false) or minus one (true)
After every part was tested, I ended up with a collection of documents, I’ll share next. These are the algorithm, assembly code, and Basic test program (the machine code already loaded in locations $c000, or 49152, and higher). I did away with a machine code loader in data statements, because that became too much of a chore. The point of this whole exercise was to learn something about coding for the Commodore 64, particularly in 6502 assembly language.
And that I have accomplished. It isn’t the most efficient 6502 machine language routine, but it does work. And that always makes me happy 😃
"algorithm.txt"
main routine:
- convert the real number from Basic into a 16-bit unsigned integer, called “testnum”
- set primality flag to “false”
- for each of the 54 prime numbers that are less than 256,
- if (testnum == prime) then set primality flag to “true”, and go to step 6
- set primality flag to “true”
- for every next prime, starting with the 0th,
- if ( (prime * prime) == testnum) then set primality flag to “false”, and go to step 6
- if ( (prime * prime) > testnum ) then go to step 6
- if ( (testnum modulo prime) == 0) then set primality flag to “false”, and go to step 6
- convert the primality flag into a real value and return it to Basic
prime function:
- pick the n-th element out of a list of 54 primes, where n starts from zero, up to 53
square function:
- start with a zero value for the 16-bit square
- shift square 8 times to the left, and each time
- shift the 8-bit number to be squared left into the carry bit
- if there’s a carry, then add the original value of number to square
modulo function:
- shift the 16-bit dividend 16 times into the 16-bit remainder, and each time
- after each shift, test subtract divisor from remainder, and only store in remainder for positive values
"usrfunction-0-4.asm"
; usrfunction-0-4.asm
getadr = $b7f7
givayf = $b391
*= $c000
jsr getadr ; convert real into unsigned int
; in .A & .Y (hi/lo)
sty testnum ; save for later use
sta testnum+1
ldx #$00
stx isprime ; assume it is not a prime
cpy #$02 ; is testnum less than 2?
lda testnum+1
sbc #$00
bcs okay ; testnum >= 2
jmp foundit ; no, then output value of isprime
okay:
; test if test number is equal to one of the prime numbers
ldx #53
stx order ; start with 53th prime
loop1:
ldx order
bmi loop1x ; if < 0 then exit loop
jsr prime ; get nth prime in .X
cpx testnum ; is testnum == prime?
bne loop1a ; no, then next prime
lda testnum+1
bne loop1a
ldy #$ff ; yes, then
sty isprime ; it is a prime
bne foundit ; skip to the end
loop1a:
dec order ; try previous prime
clc
bcc loop1 ; continue loop
loop1x:
lda #$ff
sta isprime ; assume testnum is prime
lda #$00
sta order ; start with 0th prime
loop2:
ldx order
jsr prime ; find the nth prime
stx temp ; store for later use
txa
jsr square ; square the prime
cmp testnum ; prime * prime == testnum ?
bne loop2a
cpy testnum+1
bne loop2a
ldy #$00
sty isprime ; yes, then it is not a prime
beq foundit
loop2a:
cmp testnum ; is prime * prime > testnum?
tya
sbc testnum+1
bcs foundit ; then exit the loop
ldx temp ; prime is divisor
lda testnum ; testnum is dividend
ldy testnum+1
jsr modulo
cmp #$00 ; is remainder zero?
bne loop2b ; no, then next prime
cpy #$00
bne loop2b
ldy #$00 ; yes, then
sty isprime ; it is not a prime
beq foundit
loop2b:
inc order ; try next prime
clc
bcc loop2 ; continue loop
foundit:
lda isprime ; primality flag is
tay ; either $0000 or $ffff
jmp givayf ; make it real & return to Basic
testnum:
.word $ffff ; container for number to be tested
order:
.byte $ff ; container for n, as in nth prime
isprime:
.byte $ff ; container for flag that signals
; primality
temp
.byte $ff ; temporary storage for prime
; prime
; Finds the nth prime on the number line.
; E.g. prime(0) = 2, prime(1) = 3, etc.
;
; input .X order n
;
; output .X nth prime number
;
; effects on .A, .X, .M, .Z
prime:
lda primes,x ; load from list of primes
tax ; put it in .X
rts
; ordered list of all 54 prime numbers less than 256
primes .byte 2, 3, 5, 7, 11
.byte 13, 17, 19, 23, 29
.byte 31, 37, 41, 43, 47
.byte 53, 59, 61, 67, 71
.byte 73, 79, 83, 89, 97
.byte 101, 103, 107, 109, 113
.byte 127, 131, 137, 139, 149
.byte 151, 157, 163, 167, 173
.byte 179, 181, 191, 193, 197
.byte 199, 211, 223, 227, 229
.byte 233, 239, 241, 251
; square
; Calculates the 16-bit unsigned square of an 8-bit unsigned integer.
;
; input .A number to be squared
; output .A, .Y squared number (lo/hi)
;
; Destroys all registers.
number = $61 ; 8-bit value to be squared
nsquare = $62 ; 16-bit value of squared number
tnumber = $64 ; temporary storage for number
square:
sta number ; put .A in number
lda #$00 ; clear A
sta nsquare ; clear square low byte
; (no need to clear the high byte,
; it gets shifted out)
lda number ; get number in .A
sta tnumber ; save original for later use
ldx #$08 ; 8 bits to process
sqloop:
asl nsquare ; shift square to left
rol nsquare+1 ; (i.e. multiply by 2)
asl a ; get next highest bit of number
bcc sqnoadd ; don't do add if carry is zero
tay ; save .A for later use
clc
lda tnumber ; add original number value
adc nsquare ; to square
sta nsquare
lda #$00 ; add a possible carry
adc nsquare+1 ; to the high byte of square
sta nsquare+1
tya ; get .A back
sqnoadd:
dex ; decrement bit counter
bne sqloop ; go do next bit
lda nsquare ; lo byte of square in .A
ldy nsquare+1 ; hi byte of square in .Y
rts
; modulo
; Calculate the modulo of an 8-bit number with a 16-bit number.
;
; input .A, .Y dividend
; .X divisor
;
; output .A, .Y remainder
;
; Destroys all registers.
num1 = $61 ; 16-bit dividend
num2 = $63 ; 8-bit divisor
rem = $64 ; 16-bit remainder
modulo:
sta num1 ; .A lo byte of dividend
sty num1+1 ; .Y hi byte of dividend
stx num2 ; .X divisor
lda #$00 ; clear remainder
sta rem
sta rem+1
ldx #16 ; do 16 bits
modloop:
asl num1 ; shift num1 to the left
rol num1+1
rol rem ; into remainder
rol rem+1
sec
lda rem ; do trial subtraction
sbc num2 ; with divisor
tay
lda rem+1
sbc #$00 ; divisor is 8-bit value
bcc modnosub ; was there a borrow,
; then skip subtraction
sta rem+1 ; save the result
sty rem
modnosub:
dex ; repeat for 16 bits
bne modloop
lda rem ; lo byte of remainder in .A
ldy rem+1 ; hi byte of remainder in .Y
rts
"testusrf-0-4.bas"
0 rem testusrf-0-4.bas
1 rem version 5 - test completed usr function
10 poke 785,0: poke 786,192:rem usr function at $c000
20 input "number";n
30 m = usr(n)
40 print n;"is ";: if not m then print "not ";
50 print "prime"
60 print "any key to continue"
70 poke 198,0:wait198,1:poke198,0
80 goto 20