SPC700

Strictly for discussing ZSNES development and for submitting code. You can also join us on IRC at irc.libera.chat in #zsnes.
Please, no requests here.

Moderator: ZSNES Mods

Overload
Hazed
Posts: 70
Joined: Sat Sep 18, 2004 12:47 am
Location: Australia
Contact:

SPC700

Post by Overload »

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.
TRAC
SNEeSe Developer
SNEeSe Developer
Posts: 25
Joined: Sun Nov 14, 2004 12:46 pm
Contact:

Post by TRAC »

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...
Overload
Hazed
Posts: 70
Joined: Sat Sep 18, 2004 12:47 am
Location: Australia
Contact:

Post by Overload »

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.

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
This is what i have in Sleuth for DAS
Last edited by Overload on Sun Jun 26, 2005 5:30 am, edited 1 time in total.
caitsith2
New Member
Posts: 8
Joined: Tue Aug 03, 2004 9:27 am

Post by caitsith2 »

IIRC, the BRK opcode is basically a soft interrupt. The Vector for it, is located in the same place as one of the TCall vectors, (can't remember which one though). Returning, requires an RTI, since it pushes not only the Return address, but the PSW as well.
anomie
Lurker
Posts: 151
Joined: Tue Dec 07, 2004 1:40 am

Post by anomie »

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).
Overload
Hazed
Posts: 70
Joined: Sat Sep 18, 2004 12:47 am
Location: Australia
Contact:

Post by Overload »

Thanks for the the info anomie.
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.
My thoughts are that BRK is the only command that sets the B flag.

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.
The Dumper
Rookie
Posts: 11
Joined: Wed Jul 28, 2004 5:03 pm

Post by The Dumper »

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
anomie
Lurker
Posts: 151
Joined: Tue Dec 07, 2004 1:40 am

Post by anomie »

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

I wouldn't be surprised if the IRQ functionality is not accessable, though.
TRAC
SNEeSe Developer
SNEeSe Developer
Posts: 25
Joined: Sun Nov 14, 2004 12:46 pm
Contact:

Post by TRAC »

Overload wrote:How does the break flag affect xcn? I don't quite follow.
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:[Edit]
TRAC, SNEeSe doesn't seem to like daa or das.
I found the problem in SNEeSe DAA, but it probably won't get committed until I find the specific problem in DAS.
TRAC
SNEeSe Developer
SNEeSe Developer
Posts: 25
Joined: Sun Nov 14, 2004 12:46 pm
Contact:

Post by TRAC »

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.
blargg
Regular
Posts: 327
Joined: Thu Jun 30, 2005 1:54 pm
Location: USA
Contact:

Post by blargg »

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.
Overload
Hazed
Posts: 70
Joined: Sat Sep 18, 2004 12:47 am
Location: Australia
Contact:

Post by Overload »

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

Code: Select all

if ((carry) || (a > 0x99))
{
	carry = true;
	a += 0x60;
}

if ((half_carry) || ((a & 0x0f) > 0x09))
	a += 0x06;

setflags_nz(a);
DAS

Code: Select all

if ((!carry) || (a > 0x99))
{
	carry = false;
	a -= 0x60;
}

if ((!half_carry) || ((a & 0x0f) > 0x09))
	a -= 0x06;

setflags_nz(a);
John, I checked those links. They are very informative.

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.
Overload
Hazed
Posts: 70
Joined: Sat Sep 18, 2004 12:47 am
Location: Australia
Contact:

Post by Overload »

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
Sounds good.
anomie
Lurker
Posts: 151
Joined: Tue Dec 07, 2004 1:40 am

Post by anomie »

Overload wrote:TRAC, Thanks for the info on DAA and DAS. This is what i come up with.
That's what I came up with too, BTW.
blargg wrote:Of interest might be the DIV raw data and the algorithm I came up with.
At some point I came up with an algorithm that seems to give correct results for A, Y, and the V flag:

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;
It's more or less a fairly standard division algorithm. The interesting parts are the XOR (i'd have expected OR) and the V flag being between Y and A in the result.
blargg
Regular
Posts: 327
Joined: Thu Jun 30, 2005 1:54 pm
Location: USA
Contact:

DIV Instruction Validator

Post by blargg »

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

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;
byuu

Post by byuu »

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.

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);
}
blargg
Regular
Posts: 327
Joined: Thu Jun 30, 2005 1:54 pm
Location: USA
Contact:

Post by blargg »

byuusan wrote:Would you mind explaining what's with the black magic when y >= x * 2?
It's the simplest algorithm that matches the results I observed for DIV :). I don't know why (I haven't studied hardware division much). The result follows a fairly simple pattern for AY going from $0000 to $ffff for different values of x.

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
anomie
Lurker
Posts: 151
Joined: Tue Dec 07, 2004 1:40 am

Post by anomie »

byuusan wrote:Would you mind explaining what's with the black magic when y >= x * 2?
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.
blargg
Regular
Posts: 327
Joined: Thu Jun 30, 2005 1:54 pm
Location: USA
Contact:

Post by blargg »

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.
anomie
Lurker
Posts: 151
Joined: Tue Dec 07, 2004 1:40 am

Post by anomie »

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.
Very nice.

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" ;)
byuu

Post by byuu »

Hm, I can't quite tell from your list what happens when x = 0...

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
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?
blargg
Regular
Posts: 327
Joined: Thu Jun 30, 2005 1:54 pm
Location: USA
Contact:

Post by blargg »

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.
byuu

Post by byuu »

Bah, you're right, sorry about that. y would never be less than x * 2 if x were zero.
Ok, so then we have the half-carry as ((y & 15) >= (x & 15)) before calculation, negative as !!(a & 0x80) after, and zero as (a == 0) after.

So I guess that opcode is taken care of.
byuu

Post by byuu »

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:

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-
Hmm... there we go. Ok, now let's see what's going on with my core.

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
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.
Overload
Hazed
Posts: 70
Joined: Sat Sep 18, 2004 12:47 am
Location: Australia
Contact:

Post by Overload »

LEDI made quite a few mistakes in his document.

I don't have spc700.txt but one consistant mistake i see in the doc i have from 1997 is the use of 'labs' instead of '!abs'.
Post Reply