Page 1 of 1
SNES Math Routines
Posted: Thu Mar 04, 2010 9:21 pm
by pcmantinker
I've been reading about how to do various math routines with 65816 assembly. I've figured out how to add, subtract, and multiply 8bit integers, but division is still stumping me. I'd also like to learn how to work with 16bit integers, but am not sure the best way to approach that.
For multiplying integers, I read about the multiplicand and multiplier registers in Yoshi's docs. It's pretty simple. I'm using neviksti's printing macros to print to the screen.
Here is my code for multiplication:
Code: Select all
PrintString "\nMultiplication: 50*4 = "
lda #50
sta $4202 ; store the multiplicand
lda #4
sta $4203 ; store the multiplier
PrintNum $4216 ; print result
Here is my code for division:
Code: Select all
PrintString "\nDivision with remainder: 5/4="
lda #5
sta $4204 ; store dividend
lda #4
sta $4205 ; store divisor
PrintNum $4214 ; print result
PrintString "\nRemainder:"
PrintNum $4216 ; print remainder
Lastly, when performing mathematical operations, do you use the accumulator primarily or is it a combination of the accumulator, x, and y?
EDIT: After testing my code for multiplication on the real hardware, it doesn't appear to multiply the numbers correctly. I tested it in ZSNES before testing it on the SNES.
Posted: Thu Mar 04, 2010 11:13 pm
by whicker
dude.
wait for your operation to complete. You've got to burn, don't quote me on this, 8 cpu cycles.
Posted: Thu Mar 04, 2010 11:18 pm
by pcmantinker
I read that it takes 16 cpu cycles to complete a division operation. I also confirmed that it takes 8 cpu cycles to complete a multiplication operation. I'll need to figure out how to print 16bit integers as my current code only prints 8bit integers.
Posted: Fri Mar 05, 2010 1:26 am
by mic_
Shouldn't it be (assuming A is 8-bit):
Code: Select all
lda #<dividend
sta $4204 ; low
lda #>dividend
sta $4205 ; high
lda #divisor
sta $4206
; insert code to wait 16 cycles
lda $4214 ; low part of quotient
; do whatever
lda $4216 ; low part of remainder
; do whatever
Posted: Fri Mar 05, 2010 8:56 am
by blargg
pcmantinker wrote:I read that it takes 16 cpu cycles to complete a division operation. I also confirmed that it takes 8 cpu cycles to complete a multiplication operation.
SNES CPU cycles vary in length depending on the operation.
Posted: Fri Mar 05, 2010 10:22 am
by Near
I tested it in ZSNES before testing it on the SNES.
I really wish I could lecture you on using the least accurate SNES emulator ever made to test programs on, but unfortunately no SNES emulator properly supports the mul/div delays. At best, MESS and bsnes allow for returning 0x00 when reading too early to alert you there's a problem.
More on why here:
http://www.allgoodthings.us/mambo/index ... =3790#3790
Posted: Fri Mar 05, 2010 11:25 am
by pcmantinker
mic_ wrote:Shouldn't it be (assuming A is 8-bit):
Code: Select all
lda #<dividend
sta $4204 ; low
lda #>dividend
sta $4205 ; high
lda #divisor
sta $4206
; insert code to wait 16 cycles
lda $4214 ; low part of quotient
; do whatever
lda $4216 ; low part of remainder
; do whatever
Can you explain what the high and low of the dividend are referring to? I modified my code to this for division:
Code: Select all
PrintString "\nDivision with remainder: 16/4="
lda #16
sta $4204 ; low
lda #16
sta $4205 ; high
lda #4
sta $4206
NOP ; wait 16 cpu cycles
NOP
NOP
NOP
NOP
NOP
NOP
NOP
lda $4214 ; low part of quotient
; do whatever
PrintNum $4214
lda $4216 ; low part of remainder
; do whatever
PrintString "\nRemainder:"
PrintNum $4216
The only problem is that my remainder is the same as the quotient. If I divide something that is equally divisible by another number, the remainder should be zero.
Also byuu, thanks for pointing me in the right direction for using a better emulator. I switched over to bsnes as you suggested. I thought about ZSNES's accuracy and how it wasn't the best for development.
Posted: Fri Mar 05, 2010 5:04 pm
by mic_
Can you explain what the high and low of the dividend are referring to?
The low 8 bits and the high 8 bits of a 16-bit value.
The only problem is that my remainder is the same as the quotient. If I divide something that is equally divisible by another number, the remainder should be zero.
In this case you're setting the dividend to $1010 and the divisor to 4, so I would've expected $4214 to return 4 (low part of $404) and $4216 to return 0. I've never used the hardware divider though, so there might be something I've missed.
Posted: Fri Mar 05, 2010 7:56 pm
by Near
pcmantinker wrote:Also byuu, thanks for pointing me in the right direction for using a better emulator. I switched over to bsnes as you suggested. I thought about ZSNES's accuracy and how it wasn't the best for development.
That's just it, though. bsnes doesn't emulate the mul/div delays, either. The best you can do is edit %APPDATA%/.bsnes/bsnes.cfg and set cpu.aluMul/DivDelay to higher values, say 96 or so.
Really, the big #1 pet peeve I have with ZSNES at the moment is that they've known you're not allowed to write to video RAM during active display for well over 13 years now and still haven't fixed it, despite the dozens of ROM translations this has ruined and the hundreds of hours people have spent fixing this post-release. It's literally a one-line change to add this.
Every other emulator properly blocks these VRAM writes.
PrintString "\nDivision with remainder: 16/4="
lda #16
sta $4204 ; low
lda #16
sta $4205 ; high
lda #4
sta $4206
That is 4112/4, or 1028.
Posted: Sun Mar 07, 2010 6:41 pm
by pcmantinker
Quote:
PrintString "\nDivision with remainder: 16/4="
lda #16
sta $4204 ; low
lda #16
sta $4205 ; high
lda #4
sta $4206
That is 4112/4, or 1028.
Thanks for pointing out the error in my code. I've modified the code so that it divides decimal 128 by decimal 32. It waits 16 cpu cycles before attempting to read the values and write to the screen.
Code: Select all
PrintString "\nDivision: 128/32="
lda #128
sta $4204 ; low
lda #0
sta $4205 ; high
lda #32
sta $4206
NOP ; wait 16 cpu cycles
NOP
NOP
NOP
NOP
NOP
NOP
NOP
lda $4214 ; low part of quotient
; do whatever
PrintNum $4214 ; low
lda $4216 ; low part of remainder
; do whatever
PrintString "\nRemainder="
PrintNum $4216 ; low
The output is as follows:
Division: 128/32=4
Remainder: 4
Not sure why the low byte of the remainder is returning 4 because 128 divides by 32 evenly. Also, when I try dividing 100 by 10, it returns 1 as the remainder. Just curious if I'm not accessing the remainder register properly? Also, how would I test that the correct value is being assigned in bsnes? The debugger is a little different than what I'm used to for a high level language.