SNES Math Routines

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
SNES Math Routines
by on (#57532)
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:
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:
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.

by on (#57534)
dude.

wait for your operation to complete. You've got to burn, don't quote me on this, 8 cpu cycles.

by on (#57535)
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.

by on (#57536)
Shouldn't it be (assuming A is 8-bit):

Code:
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

by on (#57551)
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.

by on (#57565)
Quote:
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

by on (#57566)
mic_ wrote:
Shouldn't it be (assuming A is 8-bit):

Code:
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:
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.

by on (#57589)
Quote:
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.

Quote:
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.

by on (#57608)
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.

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.

by on (#57677)
Quote:
Quote:
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:
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.