SPC700
Moderator: ZSNES Mods
SPC700
I'm updating the code in Sleuth and at the same time writing a program to test some of the SPC700 Opcodes.
http://users.tpg.com.au/trauma/temp/SPC_FLAG.zip
(Source included)
Just thought i'd share this as no emulator passes the 5 tests that I have written so far. All tests pass on a real SNES.
AFAIK, SPC700 timing should not affect the results of the tests.
http://users.tpg.com.au/trauma/temp/SPC_FLAG.zip
(Source included)
Just thought i'd share this as no emulator passes the 5 tests that I have written so far. All tests pass on a real SNES.
AFAIK, SPC700 timing should not affect the results of the tests.
Very good work, interesting test and interesting results. Unfortunately, the test has a shortcoming, which can be revealed by adding the bytes $0D $AE $08 $10 $2D $8E (push psw, pop a, or a,#$10, push a, pop psw) before the spc_xcn_test label and extending the test size appropriately ($7D).
Combined with the original test, this seems to reveal that the B flag is likely cleared on RESET or at the very least on power-on, but can be set like any other flag. Unfortunately, this doesn't impart any information as to what in the processor is supposed to set or clear that flag normally...
Combined with the original test, this seems to reveal that the B flag is likely cleared on RESET or at the very least on power-on, but can be set like any other flag. Unfortunately, this doesn't impart any information as to what in the processor is supposed to set or clear that flag normally...
How does the break flag affect xcn? I don't quite follow.
I updated the test again, there were some problems with timing that I was unaware of. Apparently the spc700 port registers expire after a certain length of time. A long interrupt could cause a test to fail.
http://users.tpg.com.au/trauma/temp/SPC_FLAG.zip
Has anyone researched the Brk Opcode yet?
[Edit]
TRAC, SNEeSe doesn't seem to like daa or das.
This is what i have in Sleuth for DAS
I updated the test again, there were some problems with timing that I was unaware of. Apparently the spc700 port registers expire after a certain length of time. A long interrupt could cause a test to fail.
http://users.tpg.com.au/trauma/temp/SPC_FLAG.zip
Has anyone researched the Brk Opcode yet?
[Edit]
TRAC, SNEeSe doesn't seem to like daa or das.
Code: Select all
MOV AL, A
TEST D0, 01H
JZ L1
CMP AL, 99H
JBE L2
MOV D0, 00H
L1: SUB AL, 60H
L2: SUB AL, 06H
SETS D7
SETZ D1
MOV A, AL
Last edited by Overload on Sun Jun 26, 2005 5:30 am, edited 1 time in total.
Hrm, DAS is interesting in some of those 'impossible' corner cases.
B flag is set by BRK, and POP PSW or RET1 can set or clear it just as they can any other flag. Presumably it would also be set by an IRQ or NMI, were either of those to exist.
The BRK vector is the same as TCALL 0, $FFDE. It pushes the PC and flags, then sets B and clears I before jumping to the BRK handler.
More opcodes to check: DIV (check the quotient and remainder too if you can), BRK, CLRV, various MOV instructions (only MOV into A, X, or Y should affect flags).
B flag is set by BRK, and POP PSW or RET1 can set or clear it just as they can any other flag. Presumably it would also be set by an IRQ or NMI, were either of those to exist.
The BRK vector is the same as TCALL 0, $FFDE. It pushes the PC and flags, then sets B and clears I before jumping to the BRK handler.
More opcodes to check: DIV (check the quotient and remainder too if you can), BRK, CLRV, various MOV instructions (only MOV into A, X, or Y should affect flags).
Thanks for the the info anomie.
I suspect a hardware interrupt does exist but is not connected or maybe we haven't figured out how it works. There could be all kinds of possibilities that could be explored. It could be connected to one of the timers. I haven't seen a game that enables interrupts.
I suspect that BRK and IRQ both share a vector. The same as the 6502.
My thoughts are that BRK is the only command that sets the B flag.anomie wrote:B flag is set by BRK, and POP PSW or RET1 can set or clear it just as they can any other flag. Presumably it would also be set by an IRQ or NMI, were either of those to exist.
I suspect a hardware interrupt does exist but is not connected or maybe we haven't figured out how it works. There could be all kinds of possibilities that could be explored. It could be connected to one of the timers. I haven't seen a game that enables interrupts.
I suspect that BRK and IRQ both share a vector. The same as the 6502.
-
- Rookie
- Posts: 11
- Joined: Wed Jul 28, 2004 5:03 pm
Hey Overload, have you checked out http://www.hynixmcu.com/download/manual/gms81524b.pdf ? I think you'll find some good information and clues there. Don't take it as Gospel, check out your theories, but this family of chips seems to be very similar to the SPC700 (many of the same operations).
Oh heck, might as well link the App note too, http://www.hynixmcu.com/download/app_no ... onNote.pdf
Oh heck, might as well link the App note too, http://www.hynixmcu.com/download/app_no ... onNote.pdf
Well, i haven't seen any sign of an interrupt based on any bits of $f1, $f8, or $f9. $f0 doesn't want to do anything sane for me, but i suppose it could be hidden in there. Or it could be triggered somehow by the DSP, we have a number of seemingly-unused registers in there. $1D, $xA, $xB, or $xE all are possible there.Overload wrote:I suspect a hardware interrupt does exist but is not connected or maybe we haven't figured out how it works. There could be all kinds of possibilities that could be explored. It could be connected to one of the timers. I haven't seen a game that enables interrupts.
I wouldn't be surprised if the IRQ functionality is not accessable, though.
The 'effect' was that if break flag is set before the test, then the test would fail, even if the test would pass if break flag was clear prior. Nothing in the XCN test (or any other) affects the break flag (on the stack or otherwise). That was the small point, that the break flag could be changed, and its initial state was clear. That small point was what broke SNEeSe on XCN test, nothing having to do with XCN itself.Overload wrote:How does the break flag affect xcn? I don't quite follow.
I found the problem in SNEeSe DAA, but it probably won't get committed until I find the specific problem in DAS.Overload wrote:[Edit]
TRAC, SNEeSe doesn't seem to like daa or das.
Just committed fixes to DAA and DAS opcodes to SNEeSe CVS, as well as BRK and RETI support. (which I suspect may be broken...) Also committed changes to DIV to use anomie's algorithm.
DAA and DAS can both alter their behavior based on the H flag. I used a modified version of anomie's APU test program (changing the ADC and SBC opcodes in the pertinent tests to NOP's) to research those two.
DAA and DAS can both alter their behavior based on the H flag. I used a modified version of anomie's APU test program (changing the ADC and SBC opcodes in the pertinent tests to NOP's) to research those two.
I did some SPC CPU reverse-engineering last year and have some of the results. Of interest might be the DIV raw data and the algorithm I came up with.
http://www.io.com/~greens/temp/blargg-s ... 004.10.zip
I also did some testing of the test register ($F1), finding that some settings slow down the CPU, but I haven't written it up.
I plan on doing more reverse-engineering of the CPU and some minor DSP stuff in the future. My goal is to do things in a clear, documented, repeatable way (somewhat like science):
1) Experiment with hardware to come up with hypothetical model
2) Write validation ROMs that test model, particularly edge cases
3) Write emulator based on this model
4) Be sure validation ROMs pass when run under emulator
Anything less (like my results above) should be questioned.
http://www.io.com/~greens/temp/blargg-s ... 004.10.zip
I also did some testing of the test register ($F1), finding that some settings slow down the CPU, but I haven't written it up.
I plan on doing more reverse-engineering of the CPU and some minor DSP stuff in the future. My goal is to do things in a clear, documented, repeatable way (somewhat like science):
1) Experiment with hardware to come up with hypothetical model
2) Write validation ROMs that test model, particularly edge cases
3) Write emulator based on this model
4) Be sure validation ROMs pass when run under emulator
Anything less (like my results above) should be questioned.
Another update. I didn't get a chance to test on the hardware though.
http://users.tpg.com.au/trauma/temp/SPC_FLAG.zip
TRAC, Thanks for the info on DAA and DAS. This is what i come up with.
DAA
DAS
John, I checked those links. They are very informative.
I guess in this case the b flag only determines brk or a tcall.
http://users.tpg.com.au/trauma/temp/SPC_FLAG.zip
TRAC, Thanks for the info on DAA and DAS. This is what i come up with.
DAA
Code: Select all
if ((carry) || (a > 0x99))
{
carry = true;
a += 0x60;
}
if ((half_carry) || ((a & 0x0f) > 0x09))
a += 0x06;
setflags_nz(a);
Code: Select all
if ((!carry) || (a > 0x99))
{
carry = false;
a -= 0x60;
}
if ((!half_carry) || ((a & 0x0f) > 0x09))
a -= 0x06;
setflags_nz(a);
I guess in this case the b flag only determines brk or a tcall.
Last edited by Overload on Thu Jun 30, 2005 2:26 pm, edited 1 time in total.
Sounds good.blargg wrote:I plan on doing more reverse-engineering of the CPU and some minor DSP stuff in the future. My goal is to do things in a clear, documented, repeatable way (somewhat like science):
1) Experiment with hardware to come up with hypothetical model
2) Write validation ROMs that test model, particularly edge cases
3) Write emulator based on this model
4) Be sure validation ROMs pass when run under emulator
That's what I came up with too, BTW.Overload wrote:TRAC, Thanks for the info on DAA and DAS. This is what i come up with.
At some point I came up with an algorithm that seems to give correct results for A, Y, and the V flag:blargg wrote:Of interest might be the DIV raw data and the algorithm I came up with.
Code: Select all
uint32 yva, x, i;
yva = SPC700.YA.W;
x = SPC700.X << 9;
for(i=0; i<9; i++){
yva<<=1; if(yva&0x20000) yva=(yva&0x1ffff)|1;
if(yva>=x) yva^=1;
if(yva&1) yva=(yva-x)&0x1ffff;
}
if(yva&0x100){
SetOverflow();
} else {
ClearOverflow();
}
SPC700.YA.B.Y = (yva>>9)&0xff;
SPC700.YA.B.A = yva&0xff;
DIV Instruction Validator
I finally wrote a fairly thorough validation of the DIV instruction and tested it on my SNES and an emulator using the code below. The test is built as an SPC file. It executes DIV YA,X for all 16 million values of Y, A, and X and checks the result in A and Y, and the overflow flag. It takes about 13 minutes to complete
. It doesn't yet check the negative, zero, and half-carry flags. I used wla to assemble the included source.
http://www.slack.net/~ant/misc/snes_spc_div_test.zip

http://www.slack.net/~ant/misc/snes_spc_div_test.zip
Code: Select all
overflow = (y >= x);
unsigned ya = y * 0x100 + a;
if ( y < x * 2 )
{
a = ya / x;
y = ya % x;
}
else
{
a = 255 - (ya - x * 0x200) / (256 - x);
y = x + (ya - x * 0x200) % (256 - x);
}
a &= 0xff;
y &= 0xff;
Would you mind explaining what's with the black magic when y >= x * 2?
I just use a fairly vanilla, straight-forward approach, which I'm sure is totally and completely wrong, but eh...
It ignores the V/H flags because they're scary.
I just use a fairly vanilla, straight-forward approach, which I'm sure is totally and completely wrong, but eh...
It ignores the V/H flags because they're scary.
Code: Select all
void op_div_ya_x() {
uint16 ya = regs.ya;
if(regs.x == 0) {
//cannot divide by zero
regs.a = 0x00;
regs.y = 0x00;
} else {
regs.a = ya / regs.x;
regs.y = ya % regs.x;
}
regs.p.n = !!(regs.ya & 0x8000);
regs.p.z = (regs.ya == 0);
add_cycles(12);
}
It's the simplest algorithm that matches the results I observed for DIVbyuusan wrote:Would you mind explaining what's with the black magic when y >= x * 2?

Code: Select all
Quotient (A):
x = 0:
$ff x 256, $fe x 256 ... $01 x 256, $00 x 256
x = 1:
$00, $01 ... $fe, $ff
$00, $01 ... $fe, $ff
$ff x 255, $fe x 255 ... $02 x 255, $01 x 254
x = 2:
$00, $00, $01, $01 ... $fe, $fe, $ff, $ff
$00, $00, $01, $01 ... $fe, $fe, $ff, $ff
$ff x 254, $fe x 254 ... $03 x 254, $02 x 250
Remainder (Y):
x = 0:
( $00, $01 ... $fe, $ff ) x 256
x = 1:
( $00 ) x 512
$01, $02 ... $fe, $ff
$01, $02 ... $fe, $ff
...
$01, $02 ... $fd, $fe
x = 2:
( $00, $01 ) x 256
$02, $03 ... $fe, $ff
$02, $03 ... $fe, $ff
...
$02, $03 ... $fa, $fb
x = 255:
$00, $01 ... $fd, $fe
$00, $01 ... $fd, $fe
...
$00, $01 ... $fe, $00
Y>=X*2 is the same as result quotient >= 0x200. Anything 0x00-0xff can fit easily in the 8-bit output register, while 0x100-0x1ff fits in 8-bit output register plus the V flag. Once you hit 0x200, it starts overflowing and bad things happen.byuusan wrote:Would you mind explaining what's with the black magic when y >= x * 2?
By the way, I ran the iterative division algorithm posted by anomie side-by-side with the one I validated and in all cases it generated the same result. It's more like what is probably done in hardware, with the 9 calculation iterations and perhaps a few more for setup corresponding to the 12 (?) processor cycles it takes to execute.
Are there any other significant things to be checked (or double-checked)? My setup allows a very short edit-run cycle. I can type SPC-700 assembly in a window then press F4 to have it run on hardware immediately, with the ability to print bytes from the running code to a log window on the PC.
Are there any other significant things to be checked (or double-checked)? My setup allows a very short edit-run cycle. I can type SPC-700 assembly in a window then press F4 to have it run on hardware immediately, with the ability to print bytes from the running code to a log window on the PC.
Very nice.blargg wrote:My setup allows a very short edit-run cycle. I can type SPC-700 assembly in a window then press F4 to have it run on hardware immediately, with the ability to print bytes from the running code to a log window on the PC.
As for other stuff to check... I can email you what information i have to the address on your homepage, verifying anything would be helpful. Or figuring out anything that sounds vague or "unknown"

Hm, I can't quite tell from your list what happens when x = 0...
That's what I get when I try it... obviously a/y could be anything though.
I was always under the impression that dividing by zero caused an exception to occur on x86 processors... shouldn't this be special cased or something? I'm also not sure I understand why a/y ! = 0x00, since it's impossible to divide by zero anyway. Just weird problems with the processor, I guess?
Code: Select all
--0800 mov a,#$00 A:00 X:00 Y:00 SP:ef YA:0000 nvpbhiZC
--0802 mov y,#$00 A:00 X:00 Y:00 SP:ef YA:0000 nvpbhiZC
--0804 mov x,#$00 A:00 X:00 Y:00 SP:ef YA:0000 nvpbhiZC
--0806 div ya,x A:00 X:00 Y:00 SP:ef YA:0000 nvpbhiZC
--0807 bra $0807 A:ff X:00 Y:00 SP:ef YA:00ff NVpbHizC
I was always under the impression that dividing by zero caused an exception to occur on x86 processors... shouldn't this be special cased or something? I'm also not sure I understand why a/y ! = 0x00, since it's impossible to divide by zero anyway. Just weird problems with the processor, I guess?
Use the code posted by anomie or me to find out the result of DIV for particular values. Neither implementation invokes undefined behavior (no division by zero), even when x = 0.
As far as the behavior of DIV for cases where the result isn't mathematically defined or won't fit within the 9 bit quotient and 8 bit remainder, it's probably just whatever the hardware happens to give rather than specifically crafted. For a small processor like this the slight benefit of giving a special value for undefined cases (or raising an exception) isn't worth the extra transistors.
Considering that anomie's and my tests (both informal and formal) have yielded consistent results, even in mathematically undefined cases, I think it's safe to say that the result of DIV is well-defined for all input values, and only depends on A, X, and Y.
As far as the behavior of DIV for cases where the result isn't mathematically defined or won't fit within the 9 bit quotient and 8 bit remainder, it's probably just whatever the hardware happens to give rather than specifically crafted. For a small processor like this the slight benefit of giving a special value for undefined cases (or raising an exception) isn't worth the extra transistors.
Considering that anomie's and my tests (both informal and formal) have yielded consistent results, even in mathematically undefined cases, I think it's safe to say that the result of DIV is well-defined for all input values, and only depends on A, X, and Y.
Hmm... this seems like a good topic to post this in...
sfsound.txt sucks
Ahem. Now then, anomie: you also copied one of Ledi's mistakes into spc700.txt.
I was tracking down why Illusion of Gaia got stuck in a neverending loop, and I find that it's writing to $007a+y at $0fa3 (y = $77, a = $fe). Aha, it's clearing ports 0 and 1 by writing to $f1! So why is it writing to $007a? Self modifying code. Great, so I trace back and find this with ZSNES:
Hmm... there we go. Ok, now let's see what's going on with my core.
What the hell? asl $0fa5? They both have the opcode 0xcc. So I look in sfsound.txt, and spc700.txt, both say 0x0c = mov addr,y and 0xcc = asl addr. But that doesn't make logical sense. All the mov's are 0x80+ but that one, and all the asl's are < 0x80.
In summary, ZSNES is correct. Both docs have opcodes $0c and $cc backwards.
sfsound.txt sucks
Ahem. Now then, anomie: you also copied one of Ledi's mistakes into spc700.txt.
I was tracking down why Illusion of Gaia got stuck in a neverending loop, and I find that it's writing to $007a+y at $0fa3 (y = $77, a = $fe). Aha, it's clearing ports 0 and 1 by writing to $f1! So why is it writing to $007a? Self modifying code. Great, so I trace back and find this with ZSNES:
Code: Select all
0FB7/C5 MOV $0FA4,A A:00 X:6B Y:13 S:CB N-O-D-?-H-I-Z-C-
0FBA/CC MOV $0FA5,Y A:00 X:6B Y:13 S:CB N-O-D-?-H-I-Z-C-
Code: Select all
--0fb7 mov $0fa4,a A:54 X:6b Y:12 SP:01cb YA:1254 nvpbhizC
--0fba asl $0fa5 A:54 X:6b Y:12 SP:01cb YA:1254 nvpbhizC
In summary, ZSNES is correct. Both docs have opcodes $0c and $cc backwards.