Skip to content

Virtua Racing test code

Javier de Silóniz Sandino edited this page Apr 1, 2021 · 3 revisions

Introduction

Virtua Racing contains a test mode that can be run by booting-up the game while pressing all buttons (including 4 directional buttons at the same time) of a game pad connected to PAD A while booting the game up. SEGA probably had dongles made up to achieve this (it's pretty easy to build this at home too, just connecting the required signals to ground). I've always been curious about what was being tested here, so I've spent some time taking a look at the code.

What follows is an analysis of the test code in both sides, also on how the tests are run and structured, to the best of my abilities. There might be some mistakes, please let me know if I got something wrong.

Test structure

This section will be focused on how these tests are laid out, more than what's being tested.

Mega Drive side

Test initialization

Test code entry point is 0x18000 in MD ROM space. After cleaning registers, stack, the whole DRAM and initializing other hardware stuff (VDP, pads for some reason, even if they aren't used...); the code does a first communication attempt with the SVP side (any error in the checks results in the tests ending with a "COMMUNICATION ERROR"):

  • MD sends 0xFFFF to SVP
  • expects a 0x0000 response from it.
  • MD sends 0x5356 ("SV") to SVP side.
  • expects a 0x4F4B ("OK") response from it.

Given that the tests have passed correctly, some stuff is done with the Z80 (it's initialized with some code that can be found in address 0x1B428). I don't know what this it about, if it's meant to avoid the Z80 get in the way or something. Maybe at some point in the future I'll try to disassemble that part too. There's also a delay set into place (a loop with a counter of 200) before the test continues.

After that, the same "conversation" above between MD and SVP sides happens before the tests are allowed, which leads me to believe that this is intended to be a handshake at some points of these tests (funnily, this is copy-pasted instead of using a subroutine or a branch :shrugs:).

Test indices table and test execution process

At address 0x1B324 there's a table containing the index of each test to be run (as a long word), and the address containing the code of the test itself (as another long word). These are the contents of the table:

Index Entry point Title
0000 0x1BC62 0 :ROM CHECKSUM
0001 0x1BC9A 1 :DRAM R/W
0002 0x1BF4C 2 :DSP ROM RD
0003 0x1BF32 3 :DSP DRAM R/W
0004 0x1BF38 4 :DSP IRAM R/W
0005 0x1BFAC 5 :DSP DRAM OVER WRITE
0006 0x1BF3E 6 :DSP POINTER

Before all tests are run, initialization data is put into DRAM:

; The following subroutine copies the following values from DRAM 0x180002/0x300004:
; 000F00FF
; 00F00FF0
; 00F00F0F
; continously for 0x1550 times
sub_1B2F4:
                movem.l d0-d4/a0,-(sp) 	; backs up d0-d4 and a0
                move.l  #off_F00FF,d0
                move.l  #$F00FF0,d1
                move.l  #$F000F0F,d2
                move.w  #$1550,d3
                lea     ($300004).l,a0 	; pointer to DRAM 180002/300004
loc_1B314:                             
                move.l  d0,(a0)+
                move.l  d1,(a0)+
                move.l  d2,(a0)+
                dbf     d3,loc_1B314 	; keep copying weird values to DRAM
                movem.l (sp)+,d0-d4/a0 	; restore d0-d4, a0
                rts 					; go back

The test execution process happens at offset 0x1B256. For each test the entry point is read (and verified to be sure we don't jump to an even address). After backing up the current state of the registers, the test code jump to that address. The result of each test is stored subsequently in RAM (from address FF311A) and if everything went OK (i.e.: the test code didn't call the subroutine to cause a test error), the hub writes the title of the executed test (and possibly the OK text - I didn't bother much into understanding how it's written to screen). After that, it loops back to the initial subroutine to keep iterating over the table.

loc_1B256:
                bsr.w   sub_1B000  		    ; copies routine for VDP initialization in 18018 to RAM FF1000 for some reason, some tests use it
                move.w  #1,($FF3116).l
                bsr.w   sub_1B2F4 			; copy test data to DRAM 0x180002/0x300004 onwards
                move.l  (a3)+,d0 			; copy test entry point into d0
                btst    #0,d0 				; is last bit 0?
                bne.w   loc_1B2E2 			; if not we consider that we've gone over the whole contents of the test table, write TEST END in screen
                movea.l d0,a0 				; move entry point to a0
                movem.l d1-a6,-(sp)  		; back up registers in stack
                jsr     (a0) 				; execute test
                movem.l (sp)+,d1-a6 		; restore registers from stack
                move.w  #0,($FF3116).l
                move.l  (a3)+,d1 			; get test index from table
                add.l   d1,d1  		        ; increment it
                lea     ($FF311A).l,a0
                move.w  d0,(a0,d1.w) 		; store test result in RAM (usually 1 is sucess, 2 is error), with subsequent offsets (FF311A, FF311C...)
                tst.l   d0
                bmi.w   loc_1B1B8 			; show comm error if test result was negative
                moveq   #1,d4
                lea     (testTitleTable).l,a4 	; a4 points to the table of test titles
                lea     ($FF311A).l,a2 		    ; a2 points to FF311A (current test index)
                lea     (off_1B3D6).l,a6 		; don't know if this is used at all
				
				; This next area seens mainly concerned about writing the test titles into screen
loc_1B2AC:
                moveq   #1,d0
                move.l  d4,d1
                bsr.w   sub_1BC32 		; write d0/d1 into FF3108 and FF310A
                tst.b   (a4)
                beq.w   loc_1B2DE 		; keep writing title until we've found a string separator, the following code is related to that
                exg     a1,a4
                bsr.w   sub_1BABA  	 	; subroutine to write text in screen
                exg     a1,a4
                move.l  d4,d1
                moveq   #$19,d0
                bsr.w   sub_1BC32 		; write d0/d1 into FF3108 and FF310A, not sure what's this about
                move.w  (a2)+,d0
                andi.w  #7,d0
                lsl.w   #2,d0
                movea.l (a6,d0.w),a1
                bsr.w   sub_1BABA 		; subroutine to write text in screen
                addq.l  #1,d4 			
                bra.s   loc_1B2AC 		; keep writing stuff
; ---------------------------------------------------------------------------
loc_1B2DE:
                bra.w   loc_1B256 		; go back to next test

Tests

0 - ROM Checksum

It's just a calculation of the ROM checksum made by the MD side. I won't bother with this one.

1 - DRAM R/W

Mega Drive side

This test is composed of multiple sub-tests:

  • Copying data from ROM to DRAM.
  • Testing Cell Arrange #2 (from 0x3A0000 onwards).
  • Testing Cell Arrange #1 (from 0x390000 onwards).
  • Testing writing data from VRAM to DRAM.

The main code that distinguishes each case is as follows:

loc_1BC9A:
                move.w  #0,($FF3116).l 		    ; Set whatever is in FF3116 to 0
                bsr.w   sub_1BCCC 				; TEST 1a: copy data from ROM to DRAM
                cmpi.w  #1,d0 					; test success marker should be 1 (success)
                bne.w   locret_1BCCA 			; returns if it's not the case
                bsr.w   sub_1BD70 				; TEST 1b: cell arrange #2 (3A0000)
                cmpi.w  #1,d0
                bne.w   locret_1BCCA 			; same thing, if it fails go back
                bsr.w   sub_1BDC6 			    ; TEST 1c: cell arrange #1 (3A0000)
                cmpi.w  #1,d0
                bne.w   locret_1BCCA 			; returns if it failed
                bsr.w   sub_1BE1C 				; TEST 1d: cell arrange effect on VDP (and VDP clear - my guess)
                rts
ROM - DRAM data fill

This test is pretty straight-forward, it copies the first 0x8000 words of ROM code into DRAM, and later checks that reading from it we get the same values. As you'll see in the following tests, usually the result is stored in d0 as a numeric value (1 = success, 2 = failure).

sub_1BCCC:
                lea     ($320000).l,a6
                move.w  #$3FF,d0
                moveq   #0,d1
                move.l  d1,d2
                move.l  d1,d3
                move.l  d1,d4
                move.l  d1,d5
                move.l  d1,d6
                move.l  d1,d7
                movea.l d1,a0
loc_1BCE6:
                movem.l d1-a0,-(a6)
                movem.l d1-a0,-(a6)
                movem.l d1-a0,-(a6)
                movem.l d1-a0,-(a6)
                dbf     d0,loc_1BCE6 		; clearing DRAM from 0x320000
                lea     ($300000).l,a0 	    ; pointing now to DRAM start
                move.l  #$8000,d0 			; loop counter
                moveq   #0,d1
loc_1BD08:
                cmp.w   (a0)+,d1 		 ; comparing with value #0
                bne.w   loc_1BD6C 		 ; we expect every value to be 0
                subq.l  #1,d0
                bne.s   loc_1BD08 		 ; otherwise we return with (possibly) an error
                lea     ($300000).l,a6   ; another pointer to DRAM beginning
                lea     (0).w,a5 		 ; pointer to ROM header
                move.w  #$3FF,d0 		 ; loop counter
                moveq   #$20,d1 		 ; offset for future copies
loc_1BD22:
                movem.l (a5)+,d2-a1 	 
                movem.l d2-a1,(a6) 	     ; copying from a a5 to a6 (9 long words?)
                adda.l  d1,a6 			 ; jump a6 ahead 20 positions
                movem.l (a5)+,d2-a1
                movem.l d2-a1,(a6)
                adda.l  d1,a6 			 ; same copy and jump for 20 positions
                movem.l (a5)+,d2-a1
                movem.l d2-a1,(a6)
                adda.l  d1,a6  		     ; and again
                movem.l (a5)+,d2-a1
                movem.l d2-a1,(a6)
                adda.l  d1,a6  		     ; and again
                dbf     d0,loc_1BD22 	 ; and repeat for 3FF times, supposedly copying 8000 positions of ROM header to DRAM
                lea     ($300000).l,a0
                lea     (0).w,a1
                move.l  #$8000,d0
loc_1BD5E:
                cmpm.w  (a0)+,(a1)+ 	 ; so we're expecting to find the same data in ROM header as in DRAM
                bne.w   loc_1BD6C 		 ; at the very first error, mark the test as such (d0 = 2) and go home
                subq.l  #1,d0
                bne.s   loc_1BD5E 		 ; otherwise keep comparing
                moveq   #1,d0 			 ; and mark this sub-test as a success (1)
                rts
Cell Arrange tests

These tests fill the DRAM with consecutive values starting with 0, and then read data from the Cell Arrange locations checking that the data is converted as expected.

; Cell Arrange 2 test routine

sub_1BD70:
                lea     ($300000).l,a1 	    ; a1 points to DRAM start
                move.w  #$7FFF,d0 			; loop counter
                moveq   #0,d1 				; first value is going to be 0
loc_1BD7C:
                move.w  d1,(a1)+
                addq.w  #1,d1 			 
                dbf     d0,loc_1BD7C 		; so we write consecutive values into DRAM for 8000 words
                lea     ($3A0000).l,a0 		; pointer to cell arrange #2
                moveq   #0,d0
                move.w  #$7FFF,d0 			; loop counter
loc_1BD90:
                move.w  d0,d1
                move.w  d1,d2 			 ; d1 and d2 also are 7FFF
                andi.w  #$1E,d2
                lsl.w   #6,d2
                move.w  d1,d3
                andi.w  #$7E0,d3
                lsr.w   #4,d3
                andi.w  #$F801,d1
                or.w    d2,d1
                or.w    d3,d1
                move.l  d0,d2
                lsl.w   #1,d2
                add.l   a0,d2
                movea.l d2,a1
                move.w  (a1),d2
                cmp.w   d2,d1 			; I'm not trying to understand the conversion right now but this is testing data generated by cell arrange circuit
                bne.w   loc_1BDC2 		; if it fails, mark it as an error and go home
                dbf     d0,loc_1BD90 	; otherwise keep trying
                moveq   #1,d0 			; and return a success code (1)
                rts
; Cell Arrange 1 test routine

sub_1BDC6: 
                lea     ($300000).l,a1 	; pointing to DRAM start
                move.w  #$7FFF,d0
                moveq   #0,d1
loc_1BDD2:
                move.w  d1,(a1)+
                addq.w  #1,d1
                dbf     d0,loc_1BDD2 	; writing 8000 consecutive values to DRAM
                lea     ($390000).l,a0 	; testing Cell Arrange #1.
                moveq   #0,d0
                move.w  #$7FFF,d0
loc_1BDE6: 
                move.w  d0,d1
                move.w  d1,d2
                andi.w  #$3E,d2
                lsl.w   #6,d2
                move.w  d1,d3
                andi.w  #$FC0,d3
                lsr.w   #5,d3
                andi.w  #$F001,d1
                or.w    d2,d1
                or.w    d3,d1
                move.l  d0,d2
                lsl.w   #1,d2
                add.l   a0,d2
                movea.l d2,a1
                move.w  (a1),d2
                cmp.w   d2,d1
                bne.w   loc_1BE18
                dbf     d0,loc_1BDE6  	; See above routine, it's doing the same thing with different conversion values.
                moveq   #1,d0
                rts
VRAM - DRAM fill

If I'm not mistaken, this test checks that the data in DRAM can be copied correctly to VRAM (at first I thought it was related to Cell Arrange but it doesn't make any reference to those). For some reason it executes a VDP initialization routine from RAM (FF1000), which refers to DRAM start and seems to adapt the format to something the VDP can take (seriously, can't understand VDP stuff). If you have more clues on what this is exactly doing, please let me know.

sub_1BE1C:
                lea     (sub_1BEA8).l,a0
                lea     ($FF1000).l,a1
                moveq   #$3F,d0
loc_1BE2A:
                move.l  (a0)+,(a1)+ 		; copy first 80 words of routine 1BEA8 to RAM[FF1000]
                dbf     d0,loc_1BE2A 	 	; which seems intended to load stuff into the VDP
                jsr     $FF1000 			; jump to it
                lea     ($300000).l,a0 		; a0 points to DRAM
                lea     ($C00000).l,a1 		; a1 points to VDP data port
                move.w  #$3FFF,d0 			; loop counter?
                move.l  #0,($C00004).l 		; write 0 to VDP control port
loc_1BE50:  
                cmpm.w  (a0)+,(a1)+ 		; it's comparing the data found in VDP data port with what's in all values in DRAM
                bne.w   loc_1BE82 			; fail and return if they don't match
                subq.l  #2,a1 				; decrement the pointer so it's always addressing VDP data port
                dbf     d0,loc_1BE50 		; do whatever this is checking again
                move.w  #$1FFF,d0 			; loop counter
                move.w  ($C00004).l,d1 		; read from VDP control port
                moveq   #0,d1
                move.l  #$40000000,($C00004).l 	; moving data from VRAM to control port
                lea     ($C00000).l,a1 			; pointer to VDP data port 
loc_1BE78: 
                move.l  d1,(a1) 					
                dbf     d0,loc_1BE78 		; writing all zeros there for VDP data port (cleaning gfx junk from C.A.?)
                moveq   #1,d0 				; success
                rts


; VDP routine being copied to FF1000:

sub_1BEA8:
                moveq   #0,d0
                move.w  ($C00004).l,d2
                move.w  #$8114,($C00004).l
                move.w  #$9300,($C00004).l
                move.w  #$9440,($C00004).l
                move.l  #$300000,d1
                movea.l d1,a0
                addq.w  #2,d1
                move.w  #$9500,d2
                lsr.l   #1,d1
                move.b  d1,d2
                move.w  d2,($C00004).l
                lsr.l   #8,d1
                move.b  d1,d2
                addi.w  #$100,d2
                move.w  d2,($C00004).l
                lsr.l   #8,d1
                move.b  d1,d2
                addi.w  #$100,d2
                move.w  d2,($C00004).l
                move.w  d0,d3
                lsl.l   #2,d3
                lsr.w   #2,d3
                andi.l  #$73FFF,d3
                ori.l   #$804000,d3
                swap    d3
                move.l  d3,($C00004).l
                andi.l  #$FFFF0007,d3
                move.l  d3,($C00004).l
                move.l  (a0),($C00000).l
                move.w  #$8164,($C00004).l
                rts

2 - Reading ROM from DSP

Mega Drive side

This test and most of the following ones share the same code for the Mega Drive side. They're run mostly on the SVP side, so please refer to this section in the later cases. The following communication scheme is observed between MD and SVP:

  • MD sends "SV" to the SVP side.
  • SVP should send an "OK" response (handshake complete).
  • MD then sends the letter "T" followed by a number that identifies the test to run (i.e.: in this case "T0"). The number is NOT an ASCII character but the number itself.
  • SVP should send back a "S<test_id>" response.
  • MD sends another "SV" to start the test and waits for a response from SVP.
  • SVP should send "OK" to signal that the test has passed (or anything else if it didn't).
  • The previous response from SVP (i.e.: "S<test_id>") is then returned to the caller.

In all cases the MD limits itself to check whatever the SVP is answering back to consider tests as passed or failed. The whole routine is as follows:

; SVP tests "communication center" for tests run on the DSP side: tests 2, 3, 4, 6
                 moveq   #0,d1 			 ; test identifier for test 2 (0), others have 1, 2, and 4
loc_1BF4E:
                                        
                 move.w  #$5356,d0
                 bsr.w   sub_1C068 		 ; send "SV" to SVP side
                 tst.l   d0
                 bmi.s   loc_1BF44
                 bsr.w   sub_1C072 		 ; listen to response from SVP
                 tst.l   d0
                 bmi.s   loc_1BF44 		 ; mark test as an error if we got a negative number from SVP
                 cmpi.w  #$4F4B,d0 		 ; did we get "OK"?
                 bne.s   loc_1BF44 		 ; nope? go to "TEST FAILED" routine
                 move.w  #$5400,d0
                 move.b  d1,d0
                 bsr.w   sub_1C068 		 ; send 5400 (T<test_id>) to SVP side, the second byte is the test identifier
                 tst.l   d0
                 bmi.s   loc_1BF44
 loc_1BF76:
                 bsr.w   sub_1C072 		 ; listen to response from SVP
                 tst.l   d0
                 bmi.s   loc_1BF76 		 ; keep waiting for a response from SVP side if we get a negative number
                 move.w  d0,d1
                 andi.w  #$FF00,d1
                 cmpi.w  #$5300,d1 		 ; did we get "T<test_id>"?
                 bne.s   loc_1BF44 		 ; mark test as an error if that's not the case
                 moveq   #0,d1
                 move.b  d0,d1 			 ; move SVP's response to d1
                 move.w  #$5356,d0
                 bsr.w   sub_1C068 		 ; send "SV" to SVP side
                 tst.l   d0
                 bmi.s   loc_1BF44
                 bsr.w   sub_1C072 		 ; listen to response from SVP
                 tst.l   d0
                 bmi.s   loc_1BF44
                 cmpi.w  #$4F4B,d0 		 ; did we get "OK"?
                 bne.s   loc_1BF44 		 ; mark test as an error if not
                 move.l  d1,d0  	     ; remember the original "T<test_id>" we got from SVP? we put it back into d0 (which will be our response of this test)
                 rts

SVP side

What follows is the main test hub for the SVP side. It matches more or less the MD side in the sense that it waits for the Mega Drive to perform a handshake and answers appropiately. It also manages the execution of each of the tests depending on the test identifier sent by the MD side.

c233 -- 030A     		ld    -, (r6--)
c234 -- 0D02 C238		ld    (r6), 0xc238 			# point of return for next subroutine is 0xC238
c236 -- 0860 C289		bra   0xc289 				# wait for MD signal, response from MD is stored in A

													# depending on the MD message we go one way or the other:
c238 -- 0013     		ld    x, a
c239 -- 0830 FFFF		ld    a, 0xffff 			
c23b -- E001     		eor   a, x  				# did MD say 0xFFFF?
c23c -- 4D50 C265		bra   z, 0xc265				# Tests in both sides consider this situation like a comm error.
 													# - Returns the same result (to show an error) and go back.

c23e -- 0830 474F		ld    a, 0x474f 			
c240 -- E001     		eor   a, x 					# did MD say "GO"?
c241 -- 4D50 C26E		bra   z, 0xc26e				# in that case, we respond "OK" and end in an infinite loop
 													# (intended to skipping tests onward - never used by MD side - meant for debug code)
c243 -- 0830 5356		ld    a, 0x5356 			
c245 -- E001     		eor   a, x
c246 -- 4D50 C251		bra   z, 0xc251				# if MD said "SV" this is handshake - respond "OK"

c248 -- 0031     		ld    a, x
c249 -- A800 FF00		and   a, 0xff00
c24b -- E800 5400		eor   a, 0x5400		 	    # is this a test identifier?  		
c24d -- 4D50 C255		bra   z, 0xc255			    # if whatever was in X is a test identifier jump to 0xC255

c24f -- 0860 C233		bra   0xc233				# otherwise jump to 0xC233 - keep waiting for a valid signal

c251 -- 0830 4F4B		ld    a, 0x4f4b 			# MD said "SV" - return "OK" to complete it
c253 -- 0860 C267		bra   0xc267				# send response to MD side

c255 -- 0031     		ld    a, x  				# We got a test identifier here (i.e.: "T" + 0002)
c256 -- A800 00FF		and   a, 0x00ff 			# We drop the "T" part
c258 -- 8003     		add   a, a 					# and duplicate the test identifier to create an offset for the test to be run
c259 -- 8800 C27D		add   a, 0xc27d 			# to be added to the initial test position
c25b -- 030A     		ld    -, (r6--)
c25c -- 0D02 C25F		ld    (r6), 0xc25f 			# return address from this test
c25e -- 0063     		bra   a 					# !!! execute test !!!

c25f -- A800 00FF		and   a, 0x00ff 			# Creating test response by getting the last byte of the test result
c261 -- C800 5300		or    a, 0x5300 			# And appending an "S"
c263 -- 0860 C267		bra   0xc267				# We send this result to the MD side 

c265 -- 0830 0000		ld    a, 0x0000				# if coming through here from a "FFFF" signal by the MD side, we'll send back the same to cause an error
c267 -- 030A     		ld    -, (r6--)
c268 -- 0D02 C26C		ld    (r6), 0xc26c
c26a -- 0860 C299		bra   0xc299				# returns the value we obtained back to the MD side
c26c -- 0860 C233		bra   0xc233 				# keep executing tests

c26e -- 0830 4F4B		ld    a, 0x4f4b 			# "OK" - default value to return if the MD side said "GO" to skip further tests
c270 -- 030A     		ld    -, (r6--)
c271 -- 0D02 C275		ld    (r6), 0xc275
c273 -- 0860 C299		bra   0xc299				# send result to MD side
c275 -- 0860 C275		bra   0xc275				# and get into an infinite loop if required (if MD said "GO")!

c277 -- 0830 0000		ld    a, 0x0000
c279 -- 036E     		bra   (r6++) 	 			# jump back to offset with A = 0000

c27a -- 0830 FFFF		ld    a, 0xffff
c27c -- 036E     		bra   (r6++)				# jump back to offset with A = FFFF - meant for returning errors, seemingly never executed

													# The following routines are the tests themselves, are usually copied to IRAM and executed from there.
c27d -- 0860 C5A3		bra   0xc5a3				# Test id 0 
c27f -- 0860 C5F2		bra   0xc5f2 				# Test id 1
c281 -- 0860 C739		bra   0xc739  				# Test id 2 
c283 -- 0860 C2A3		bra   0xc2a3 				# Test id 3
c285 -- 0860 C7BF		bra   0xc7bf 				# Test id 4
c287 -- 0860 C277		bra   0xc277 			    # Test id 5 - just jumps to callback in (r6) with a result in A of 0x0000

You'll notice that from 0xC27D there's a bunch of jumps to the different tests. Each of these addresses share more or less the same structure. What follows is the one for test ID 0:

c5a3 -- 0830 C5A7    	ld    a, 0xc5a7
c5a5 -- 0860 C8DA    	bra   0xc8da			# set parameters for IRAM write:
c5a7 -- C5AA 									# origin C5AA 
c5a8 -- 0048 									# size of data to copy
c5a9 -- 0000 									# entry point

This is really similar on how Virtua Racing loads its routines to IRAM, what follows is the routine in 0xC8DA that loads the data to IRAM. Notice above that each routine to be loaded needs some parameters:

  • Address pointing to the origin of the code to copy in A.
  • Address of code to copy.
  • Size of routine to copy.
  • Entry point after copying to IRAM.
c8da -- 1800                        ld    r0, 0x00 			
c8db -- 0430                        ld    (r0), a 			# caller should place into A the ROM address to read from
c8dc -- 08E0 8000                   ld    ext6, 0x8000
c8de -- 08E0 081C                   ld    ext6, 0x081c
c8e0 -- 00C0                        ld    ext4, - 			# Writing to IRAM with autoincrement
c8e1 -- 08F0 0000                   ld    ext7, 0x0000
c8e3 -- 0810 01FF                   ld    x, 0x01ff 		# loop size by default
c8e5 -- 0A30                        ld    a, ((r0)+!) 		
c8e6 -- 0023                        ld    y, a 				# read address to copy from ROM into Y
c8e7 -- 0A30                        ld    a, ((r0)+!)
c8e8 -- 0013                        ld    x, a 				# read routine size to copy
c8e9 -- 0A30                        ld    a, ((r0)+!)		# read routine entry point
c8ea -- 0420                        ld    (r0), y 			# writing ROM address to read from into RAM bank
c8eb -- 0023                        ld    y, a  			# entry point is stored in y
c8ec -- 0A30                        ld    a, ((r0)+!) 		# reading from PRAM area
c8ed -- 00C3                        ld    ext4, a 			# into EXT4
c8ee -- 0031                        ld    a, x
c8ef -- 2800 0001                   sub   a, 0x0001
c8f1 -- 0013                        ld    x, a
c8f2 -- 4C50 C8EC                   bra   nz, 0xc8ec 		# loop back
c8f4 -- 1800                        ld    r0, 0x00
c8f5 -- 0062                        bra   y  				# jump to test entry point!

This scheme of having two parameters to specify the origin of the code and the jump address is shared with the Virtua Racing IRAM copy routine, but funnily it doesn't use theirs.

Finally, the individual test for this section is as follows. Notice that the SVP tests follow similar conventions as the ones run in MD side (i.e.: tests marked as successful with 0001, error with 0002). Notice also that the address in the left contains the ROM address followed by the IRAM address when they're run from there.

c5aa -- 0000 -- 030A      		ld    -, (r6--)
c5ab -- 0001 -- 0512      		ld    (r6), x
c5ac -- 0002 -- 030A      		ld    -, (r6--)
c5ad -- 0003 -- 0522      		ld    (r6), y
c5ae -- 0004 -- 030A      		ld    -, (r6--)
c5af -- 0005 -- 0542      		ld    (r6), st 			# Store ST, Y, and X
c5b0 -- 0006 -- 0840 0060 		ld    st, 0x0060
c5b2 -- 0008 -- 0810 000A 		ld    x, 0x000a
	
c5b4 -- 000A -- 030A      		ld    -, (r6--)
c5b5 -- 000B -- 0512      		ld    (r6), x 			# Also store X = 000A
c5b6 -- 000C -- 030A      		ld    -, (r6--)
c5b7 -- 000D -- 0D02 0011 		ld    (r6), 0x0011 		# return address
c5b9 -- 000F -- 0860 0028 		bra   0x0028 			# Go to ROM checker routine
	
c5bb -- 0011 -- E800 0001 		eor   a, 0x0001
c5bd -- 0013 -- 4C50 0021 		bra   nz, 0x0021 		# check result, if it wasn't a success (a=1), go to error handling
	
c5bf -- 0015 -- 033E      		ld    a, (r6++) 		# A contains a loop counter
c5c0 -- 0016 -- 2800 0001 		sub   a, 0x0001
c5c2 -- 0018 -- 0013      		ld    x, a
c5c3 -- 0019 -- 4C50 000A 		bra   nz, 0x000a 		# We repeat the ROM checker routine until we've executed this 10 times
c5c5 -- 001B -- 034E      		ld    st, (r6++)
c5c6 -- 001C -- 032E      		ld    y, (r6++)
c5c7 -- 001D -- 031E      		ld    x, (r6++)
c5c8 -- 001E -- 0830 0001 		ld    a, 0x0001
c5ca -- 0020 -- 036E      		bra   (r6++) 			# If all test runs were successful, restore regs and return with success code (a=1).
 	
c5cb -- 0021 -- 031E      		ld    x, (r6++) 		# Error handling: we restore everything
c5cc -- 0022 -- 034E      		ld    st, (r6++)
c5cd -- 0023 -- 032E      		ld    y, (r6++)
c5ce -- 0024 -- 031E      		ld    x, (r6++)
c5cf -- 0025 -- 0830 0002 		ld    a, 0x0002 		# set error result = 0002
c5d1 -- 0027 -- 036E      		bra   (r6++)			# return to test hub
	
c5d2 -- 0028 -- 1800      		ld    r0, 0x00 			# ROM checker routine, checking that values read from PMs
c5d3 -- 0029 -- 0C00 0400 		ld    (r0), 0x0400 		# and the internal DSP mem reader
c5d5 -- 002B -- 08E0 0400 		ld    ext6, 0x0400
c5d7 -- 002D -- 08E0 0800 		ld    ext6, 0x0800 		
c5d9 -- 002F -- 0009      		ld    -, ext1 			# reading from address 0x400 (initial game code) through EXT1
c5da -- 0030 -- 0810 7C00 		ld    x, 0x7c00 		# number of loops (64Kwords - each loop is two reads from ROM)
c5dc -- 0032 -- 08F0 0000 		ld    ext7, 0x0000		# I don't know if this is restoring EXT6
c5de -- 0034 -- 0039      		ld    a, ext1
c5df -- 0035 -- EA00      		eor   a, ((r0)+!) 		# comparing with internal reads through the DSP mem reader
c5e0 -- 0036 -- 4C50 0045 		bra   nz, 0x0045 		# if they don't match, jump to 0045
c5e2 -- 0038 -- 0039      		ld    a, ext1
c5e3 -- 0039 -- EA00      		eor   a, ((r0)+!)
c5e4 -- 003A -- 4C50 0045 		bra   nz, 0x0045 		# same thing
c5e6 -- 003C -- 0031      		ld    a, x
c5e7 -- 003D -- 2800 0001 		sub   a, 0x0001 		# loop dec
c5e9 -- 003F -- 0013      		ld    x, a
c5ea -- 0040 -- 4C50 0034 		bra   nz, 0x0034		# loop back
c5ec -- 0042 -- 0830 0001 		ld    a, 0x0001
c5ee -- 0044 -- 036E      		bra   (r6++)			# return with success code (a = 1)

3 - DSP DRAM R/W

Mega Drive side

Refer to test 2 for information on the communication between the SVP side and MD side for these tests.

SVP side

This test follows the same communication scheme between SVP and MD sides, please refer to the section for test 2 for more details on it. What follows are details on the actual test being run at this point.

The IRAM routine is used for this test also, here are the parameters:

c5f2 -- 0830 C5F6              ld    a, 0xc5f6
c5f4 -- 0860 C8DA              bra   0xc8da 		# jump to IRAM write routine, with params:
c5f6 -- C5F9 										# routine to write in C5F9
c5f7 -- 0140 										# routine size: 0x140
c5f8 -- 0000 										# entry point

This test routine is a bit long and sometimes convoluted. The basic flow goes as follows:

  • Registers are backed up and DRAM is cleaned up.
  • First test involves copying 3FFF words from ROM address 0x0002 to DRAM address 0x0002.
  • This copy is checked. Any mistake results in test finishing with an error result (a = 0002).
  • The next test involves copying actual data from the DSP (numbers 0x000F, 0x00FF, 0x00F0, 0x0FF0, 0x0F00 and 0x0F0F - funnily the most significant nibble isn't checked for some reason) to DRAM.
  • These values are checked again, and the test results in an error code at the first mistake it founds.
  • Finally the same values are written again but setting the overwrite flag in EXT6 when programming the external register to write to DRAM.
  • Values are checked again.
  • If we got to this point without an error, the test ends with a success code (a = 0001).
c5f9 -- 0000 -- 030A      		ld    -, (r6--)
c5fa -- 0001 -- 0512      		ld    (r6), x
c5fb -- 0002 -- 030A      		ld    -, (r6--)
c5fc -- 0003 -- 0522      		ld    (r6), y
c5fd -- 0004 -- 030A      		ld    -, (r6--)
c5fe -- 0005 -- 0542      		ld    (r6), st 			# Backup registers
c5ff -- 0006 -- 0840 0060 		ld    st, 0x0060
c601 -- 0008 -- 0810 0004 		ld    x, 0x0004 		# number of test runs to attempt
c603 -- 000A -- 030A      		ld    -, (r6--)
c604 -- 000B -- 0512      		ld    (r6), x
c605 -- 000C -- 030A      		ld    -, (r6--)
c606 -- 000D -- 0D02 0011 		ld    (r6), 0x0011 		# set return point
c608 -- 000F -- 0860 0032 		bra   0x0032			# clean DRAM and execute tests

c60a -- 0011 -- E800 0001 		eor   a, 0x0001 		# did we have a success with the test we just run?
c60c -- 0013 -- 4C50 0026 		bra   nz, 0x0026 		# if not, clean DRAM and return with error code (a = 2)
c60e -- 0015 -- 033E      		ld    a, (r6++)
c60f -- 0016 -- 2800 0001 		sub   a, 0x0001
c611 -- 0018 -- 0013      		ld    x, a 				# we restore the counter for test runs, and update it
c612 -- 0019 -- 4C50 000A 		bra   nz, 0x000a 		# if we need to perform more tests (5 in total), keep going
c614 -- 001B -- 030A      		ld    -, (r6--)
c615 -- 001C -- 0D02 0020 		ld    (r6), 0x0020
c617 -- 001E -- 0860 0127 		bra   0x0127 			# clear DRAM
c619 -- 0020 -- 034E      		ld    st, (r6++)
c61a -- 0021 -- 032E      		ld    y, (r6++)
c61b -- 0022 -- 031E      		ld    x, (r6++)
c61c -- 0023 -- 0830 0001 		ld    a, 0x0001
c61e -- 0025 -- 036E      		bra   (r6++) 			# restore registers and go back to caller with success code (a=1)

c61f -- 0026 -- 031E      		ld    x, (r6++)
c620 -- 0027 -- 030A      		ld    -, (r6--)
c621 -- 0028 -- 0D02 002C 		ld    (r6), 0x002c
c623 -- 002A -- 0860 0127 		bra   0x0127 			# clean DRAM
c625 -- 002C -- 034E      		ld    st, (r6++)
c626 -- 002D -- 032E      		ld    y, (r6++)
c627 -- 002E -- 031E      		ld    x, (r6++)
c628 -- 002F -- 0830 0002 		ld    a, 0x0002
c62a -- 0031 -- 036E      		bra   (r6++) 			# restore registers and return to caller with error result (a = 2)

c62b -- 0032 -- 030A      		ld    -, (r6--)
c62c -- 0033 -- 0D02 0037 		ld    (r6), 0x0037
c62e -- 0035 -- 0860 0127 		bra   0x0127 			# Before doing anything, clear DRAM
c630 -- 0037 -- 08E0 0000 		ld    ext6, 0x0000
c632 -- 0039 -- 08E0 0818 		ld    ext6, 0x0818
c634 -- 003B -- 0009      		ld    -, ext1 			# reading from DRAM with EXT1, auto-increment
c635 -- 003C -- 0810 4000 		ld    x, 0x4000 		# outer loop size
c637 -- 003E -- 08F0 0000 		ld    ext7, 0x0000
 														# outer loop starts here
c639 -- 0040 -- 0820 0004 		ld    y, 0x0004			# inner loop size, 4 reads
  														# inner loop starts here
c63b -- 0042 -- 0039      		ld    a, ext1 			# read
c63c -- 0043 -- E800 0000 		eor   a, 0x0000 		# is it 0?
c63e -- 0045 -- 4C50 0124 		bra   nz, 0x0124 		# if not, return with error code (a=2)
c640 -- 0047 -- 0032      		ld    a, y
c641 -- 0048 -- 2800 0001 		sub   a, 0x0001
c643 -- 004A -- 0023      		ld    y, a
c644 -- 004B -- 4C50 0042 		bra   nz, 0x0042		# loop back - inner loop
c646 -- 004D -- 0031      		ld    a, x
c647 -- 004E -- 2800 0001 		sub   a, 0x0001
c649 -- 0050 -- 0013      		ld    x, a
c64a -- 0051 -- 4C50 0040 		bra   nz, 0x0040 		# loop back outer loop

c64c -- 0053 -- 08E0 0002 		ld    ext6, 0x0002		# this routine copies ROM data to DRAM
c64e -- 0055 -- 08E0 0800 		ld    ext6, 0x0800		
c650 -- 0057 -- 0008      		ld    -, ext0 			# reading from ROM[0002] through EXT0, autoincrement
c651 -- 0058 -- 08E0 0002 		ld    ext6, 0x0002
c653 -- 005A -- 08E0 0818 		ld    ext6, 0x0818
c655 -- 005C -- 0080      		ld    ext0, - 			# writing to DRAM[0002] through EXT0, autoincrement
c656 -- 005D -- 0810 3FFE 		ld    x, 0x3ffe 		# outer loop size
c658 -- 005F -- 08F0 0000 		ld    ext7, 0x0000
c65a -- 0061 -- 0820 0004 		ld    y, 0x0004 		# inner loop size
c65c -- 0063 -- 0038      		ld    a, ext0
c65d -- 0064 -- 0083      		ld    ext0, a 			# copy from ROM to DRAM
c65e -- 0065 -- 0032      		ld    a, y
c65f -- 0066 -- 2800 0001 		sub   a, 0x0001
c661 -- 0068 -- 0023      		ld    y, a
c662 -- 0069 -- 4C50 0063 		bra   nz, 0x0063 		# inner jump loop
c664 -- 006B -- 0031      		ld    a, x
c665 -- 006C -- 2800 0001 		sub   a, 0x0001
c667 -- 006E -- 0013      		ld    x, a
c668 -- 006F -- 4C50 0061 		bra   nz, 0x0061 		# outer jump loop

c66a -- 0071 -- 08E0 0002 		ld    ext6, 0x0002		# this routine checks ROM-DRAM copy
c66c -- 0073 -- 08E0 0818 		ld    ext6, 0x0818
c66e -- 0075 -- 0008      		ld    -, ext0 			# reading from DRAM[0002] through EXT0 with auto-increment
c66f -- 0076 -- 08E0 0002 		ld    ext6, 0x0002
c671 -- 0078 -- 08E0 0800 		ld    ext6, 0x0800
c673 -- 007A -- 0009      		ld    -, ext1 			# reading from ROM[0002] through EXT1 with auto-increment		
c674 -- 007B -- 0810 3FFE 		ld    x, 0x3ffe			# outer loop size
c676 -- 007D -- 08F0 0000 		ld    ext7, 0x0000
 														# outer loop starts here
c678 -- 007F -- 0820 0004 		ld    y, 0x0004 		# inner loop size
  														# inner loop starts here
c67a -- 0081 -- 0039      		ld    a, ext1
c67b -- 0082 -- E008      		eor   a, ext0
c67c -- 0083 -- 4C50 0124 		bra   nz, 0x0124		# is data from DRAM correct? if not, return with error code (a=2)
c67e -- 0085 -- 0032      		ld    a, y
c67f -- 0086 -- 2800 0001 		sub   a, 0x0001
c681 -- 0088 -- 0023      		ld    y, a
c682 -- 0089 -- 4C50 0081 		bra   nz, 0x0081 		# inner loop
c684 -- 008B -- 0031      		ld    a, x
c685 -- 008C -- 2800 0001 		sub   a, 0x0001
c687 -- 008E -- 0013      		ld    x, a
c688 -- 008F -- 4C50 007F 		bra   nz, 0x007f 		# outer loop

c68a -- 0091 -- 08E0 0002 		ld    ext6, 0x0002 		# This new test writes with specific data to DRAM
c68c -- 0093 -- 08E0 0818 		ld    ext6, 0x0818
c68e -- 0095 -- 0080      		ld    ext0, - 			# writes to DRAM[0002] with autoincrement through EXT0
c68f -- 0096 -- 0810 2AA8 		ld    x, 0x2aa8 		# loop size
c691 -- 0098 -- 08F0 0000 		ld    ext7, 0x0000
 														# loop
c693 -- 009A -- 0880 000F 		ld    ext0, 0x000f 		# do these writes 2AA9 times
c695 -- 009C -- 0880 00FF 		ld    ext0, 0x00ff
c697 -- 009E -- 0880 00F0 		ld    ext0, 0x00f0
c699 -- 00A0 -- 0880 0FF0 		ld    ext0, 0x0ff0
c69b -- 00A2 -- 0880 0F00 		ld    ext0, 0x0f00
c69d -- 00A4 -- 0880 0F0F 		ld    ext0, 0x0f0f 		# weird values but whatever
c69f -- 00A6 -- 0031      		ld    a, x
c6a0 -- 00A7 -- 2800 0001 		sub   a, 0x0001
c6a2 -- 00A9 -- 0013      		ld    x, a
c6a3 -- 00AA -- 4C50 009A 		bra   nz, 0x009a 		# loop!

c6a5 -- 00AC -- 08E0 0002 		ld    ext6, 0x0002 		# this routine checks the previous writes
c6a7 -- 00AE -- 08E0 0818 		ld    ext6, 0x0818
c6a9 -- 00B0 -- 0008      		ld    -, ext0 			# writes to DRAM[0002] with autoincrement through EXT0
c6aa -- 00B1 -- 0810 2AA8 		ld    x, 0x2aa8 		# loop size
c6ac -- 00B3 -- 08F0 0000 		ld    ext7, 0x0000
c6ae -- 00B5 -- 0038      		ld    a, ext0
c6af -- 00B6 -- E800 000F 		eor   a, 0x000f 		
c6b1 -- 00B8 -- 4C50 0124 		bra   nz, 0x0124		# first check
c6b3 -- 00BA -- 0038      		ld    a, ext0
c6b4 -- 00BB -- E800 00FF 		eor   a, 0x00ff
c6b6 -- 00BD -- 4C50 0124 		bra   nz, 0x0124 		# second check
c6b8 -- 00BF -- 0038      		ld    a, ext0
c6b9 -- 00C0 -- E800 00F0 		eor   a, 0x00f0
c6bb -- 00C2 -- 4C50 0124 		bra   nz, 0x0124 		# third check
c6bd -- 00C4 -- 0038      		ld    a, ext0
c6be -- 00C5 -- E800 0FF0 		eor   a, 0x0ff0 
c6c0 -- 00C7 -- 4C50 0124 		bra   nz, 0x0124 		# fourth check
c6c2 -- 00C9 -- 0038      		ld    a, ext0
c6c3 -- 00CA -- E800 0F00 		eor   a, 0x0f00
c6c5 -- 00CC -- 4C50 0124 		bra   nz, 0x0124 		# fifth check
c6c7 -- 00CE -- 0038      		ld    a, ext0
c6c8 -- 00CF -- E800 0F0F 		eor   a, 0x0f0f
c6ca -- 00D1 -- 4C50 0124 		bra   nz, 0x0124 		# sixth check
c6cc -- 00D3 -- 0031      		ld    a, x
c6cd -- 00D4 -- 2800 0001 		sub   a, 0x0001
c6cf -- 00D6 -- 0013      		ld    x, a
c6d0 -- 00D7 -- 4C50 00B5 		bra   nz, 0x00b5 		# loop!

c6d2 -- 00D9 -- 08E0 0002 		ld    ext6, 0x0002
c6d4 -- 00DB -- 08E0 0C18 		ld    ext6, 0x0c18
c6d6 -- 00DD -- 0080      		ld    ext0, - 			# writes to DRAM[0002] with autoincrement and overwrite bit set
c6d7 -- 00DE -- 0810 2AA8 		ld    x, 0x2aa8			# loop size
c6d9 -- 00E0 -- 08F0 0000 		ld    ext7, 0x0000
c6db -- 00E2 -- 0880 000F 		ld    ext0, 0x000f
c6dd -- 00E4 -- 0880 00FF 		ld    ext0, 0x00ff
c6df -- 00E6 -- 0880 00F0 		ld    ext0, 0x00f0
c6e1 -- 00E8 -- 0880 0FF0 		ld    ext0, 0x0ff0
c6e3 -- 00EA -- 0880 0F00 		ld    ext0, 0x0f00
c6e5 -- 00EC -- 0880 0F0F 		ld    ext0, 0x0f0f		# now this is using the overwrite mode, 0 nibbles won't be written
c6e7 -- 00EE -- 0031      		ld    a, x
c6e8 -- 00EF -- 2800 0001 		sub   a, 0x0001		
c6ea -- 00F1 -- 0013      		ld    x, a
c6eb -- 00F2 -- 4C50 00E2 		bra   nz, 0x00e2 		# loop back!

c6ed -- 00F4 -- 08E0 0002 		ld    ext6, 0x0002		# checking the previous writes
c6ef -- 00F6 -- 08E0 0818 		ld    ext6, 0x0818
c6f1 -- 00F8 -- 0008      		ld    -, ext0
c6f2 -- 00F9 -- 0810 2AA8 		ld    x, 0x2aa8 		# loop size
c6f4 -- 00FB -- 08F0 0000 		ld    ext7, 0x0000
c6f6 -- 00FD -- 0038      		ld    a, ext0
c6f7 -- 00FE -- E800 000F 		eor   a, 0x000f			# check each individual write
c6f9 -- 0100 -- 4C50 0124 		bra   nz, 0x0124 		# and fail if it didn't match expected value
c6fb -- 0102 -- 0038      		ld    a, ext0
c6fc -- 0103 -- E800 00FF 		eor   a, 0x00ff
c6fe -- 0105 -- 4C50 0124 		bra   nz, 0x0124
c700 -- 0107 -- 0038      		ld    a, ext0
c701 -- 0108 -- E800 00F0 		eor   a, 0x00f0
c703 -- 010A -- 4C50 0124 		bra   nz, 0x0124
c705 -- 010C -- 0038      		ld    a, ext0
c706 -- 010D -- E800 0FF0 		eor   a, 0x0ff0
c708 -- 010F -- 4C50 0124 		bra   nz, 0x0124
c70a -- 0111 -- 0038      		ld    a, ext0
c70b -- 0112 -- E800 0F00 		eor   a, 0x0f00
c70d -- 0114 -- 4C50 0124 		bra   nz, 0x0124
c70f -- 0116 -- 0038      		ld    a, ext0
c710 -- 0117 -- E800 0F0F 		eor   a, 0x0f0f
c712 -- 0119 -- 4C50 0124 		bra   nz, 0x0124
c714 -- 011B -- 0031      		ld    a, x
c715 -- 011C -- 2800 0001 		sub   a, 0x0001			
c717 -- 011E -- 0013      		ld    x, a
c718 -- 011F -- 4C50 00FD 		bra   nz, 0x00fd		# loop back
c71a -- 0121 -- 0830 0001 		ld    a, 0x0001
c71c -- 0123 -- 036E      		bra   (r6++) 			# if we're done jump back to caller address, with A = 0001

c71d -- 0124 -- 0830 0002 		ld    a, 0x0002
c71f -- 0126 -- 036E      		bra   (r6++) 			# Return with error code A=2

c720 -- 0127 -- 030A      		ld    -, (r6--)			# this is a routine to clear the whole DRAM
c721 -- 0128 -- 0512      		ld    (r6), x
c722 -- 0129 -- 08E0 0000 		ld    ext6, 0x0000
c724 -- 012B -- 08E0 0818 		ld    ext6, 0x0818
c726 -- 012D -- 00C0      		ld    ext4, - 			# read from DRAM autoincrement
c727 -- 012E -- 0830 4000 		ld    a, 0x4000 		# loop size
c729 -- 0130 -- 08F0 0000 		ld    ext7, 0x0000
c72b -- 0132 -- 0810 0000 		ld    x, 0x0000
c72d -- 0134 -- 00C1      		ld    ext4, x
c72e -- 0135 -- 00C1      		ld    ext4, x
c72f -- 0136 -- 00C1      		ld    ext4, x
c730 -- 0137 -- 00C1      		ld    ext4, x 			# clearing stuff
c731 -- 0138 -- 2800 0001 		sub   a, 0x0001
c733 -- 013A -- 4C50 0134 		bra   nz, 0x0134 		# loop
c735 -- 013C -- 0830 0000 		ld    a, 0x0000
c737 -- 013E -- 031E      		ld    x, (r6++)
c738 -- 0140 -- 036E      		bra   (r6++) 			# go back with A = 0000

4 - DSP IRAM R/W

Mega Drive side

Refer to test 2 for information on the communication between the SVP side and MD side for these tests.

SVP side

This test follows the same communication scheme between SVP and MD sides, please refer to the section for test 2 for more details on it. What follows are details on the actual test being run at this point.

The purpose of this specific test is verifying that writes and reads to IRAM are working OK. While the test is run as part of the same "communications hub" as the other tests, it has its own different touches:

  • It's got its own IRAM routine to copy code there. For some reason it doesn't use the specific routine used by the other tests.
  • It also uses different codes for success / error scenarios (success = 0000, error = FFFF). This makes me think that these tests were written before the rest, or that a different developer worked on these. These test codes are finally converted to the ones understood by the "test hub" (success = 0001, error = 0002).

The flow followed by the test is simple:

  • Copy ROM header to IRAM (almost around the area where SVP code starts).
  • Check these first writes. Any error results in a return with failure (a = FFFF).
  • Test then writes a small routine to IRAM (from address 0x300) and jumps to it.
  • The routine (ran from IRAM) does a bit of the same - copying the first 0x300 words of ROM data to IRAM.
  • It checks the writes and returns the appropiate error code.
  • This whole flow is called 0x100 times! After that, if we still are successful we return with a success code (0x0001), otherwise with an error (0x0002).

What follows is the test routine itself:

c739 -- 0840 0060  		ld    st, 0x0060
c73b -- 0820 0100  		ld    y, 0x0100 		# outer loop size
c73d -- 08E0 0000  		ld    ext6, 0x0000
c73f -- 08E0 0800  		ld    ext6, 0x0800
c741 -- 000C       		ld    -, ext4 			# reading from ROM autoincrement
c742 -- 08E0 8000  		ld    ext6, 0x8000
c744 -- 08E0 081C  		ld    ext6, 0x081c
c746 -- 00C0       		ld    ext4, - 			# writes to IRAM with autoincrement
c747 -- 0830 03F8  		ld    a, 0x03f8 		# loop size
c749 -- 08F0 0000  		ld    ext7, 0x0000
c74b -- 001C       		ld    x, ext4
c74c -- 00C1       		ld    ext4, x 			# copying ROM initial area to IRAM
c74d -- 2800 0001  		sub   a, 0x0001
c74f -- 4C50 C74B  		bra   nz, 0xc74b 		# loop back

c751 -- 1800       		ld    r0, 0x00
c752 -- 0C00 0000  		ld    (r0), 0x0000 		# pointing to IRAM area (from the DSP internal view)
c754 -- 0810 03F8  		ld    x, 0x03f8
c756 -- 08E0 0000  		ld    ext6, 0x0000
c758 -- 08E0 0800  		ld    ext6, 0x0800
c75a -- 000C       		ld    -, ext4			# reading from ROM area with autoincrement
c75b -- 003C       		ld    a, ext4			# read loop
c75c -- EA00       		eor   a, ((r0)+!) 		# compares with data gotten directly from DSP
c75d -- 4C50 C78C  		bra   nz, 0xc78c 		# if there's an error, return to caller with an error code
c75f -- 0031       		ld    a, x
c760 -- 2800 0001  		sub   a, 0x0001
c762 -- 0013       		ld    x, a
c763 -- 4C50 C75B  		bra   nz, 0xc75b		# keep reading

c765 -- 1800       		ld    r0, 0x00 			# IRAM copy routine
c766 -- 0C00 C78F  		ld    (r0), 0xc78f 		# copying code from C78F
c768 -- 08E0 8300  		ld    ext6, 0x8300
c76a -- 08E0 081C  		ld    ext6, 0x081c
c76c -- 00C0       		ld    ext4, - 			# writing to IRAM[300] with autoincrement
c76d -- 08F0 0000  		ld    ext7, 0x0000
c76f -- 0810 0080  		ld    x, 0x0080 		# loop size
c771 -- 0A30       		ld    a, ((r0)+!) 		# read from ROM
c772 -- 00C3       		ld    ext4, a 			# copy to IRAM
c773 -- 0031       		ld    a, x
c774 -- 2800 0001  		sub   a, 0x0001 
c776 -- 0013       		ld    x, a
c777 -- 4C50 C771  		bra   nz, 0xc771 		# keep reading

c779 -- 1800       		ld    r0, 0x00
c77a -- 030A       		ld    -, (r6--)
c77b -- 0D02 C77F  		ld    (r6), 0xc77f 		# return point from IRAM subroutine
c77d -- 0860 0300  		bra   0x0300  			# execute the routine in IRAM
c77f -- 6800 0000  		cmp   a, 0x0000
c781 -- 4C50 C78C  		bra   nz, 0xc78c		# was the routine successful (a=0)? if not, return with an error code

c783 -- 0032       		ld    a, y
c784 -- 2800 0001  		sub   a, 0x0001
c786 -- 0023       		ld    y, a
c787 -- 4C50 C73D  		bra   nz, 0xc73d 		# outer loop (this whole test is run 0x100 times!)

c789 -- 0830 0001  		ld    a, 0x0001
c78b -- 036E       		bra   (r6++)			# if we got through here, go back with A = 0001 (success)

c78c -- 0830 0002  		ld    a, 0x0002
c78e -- 036E       		bra   (r6++)			# return to caller with an error code (a = 2)

And next is the routine copied to IRAM to test writes from ROM to IRAM:

c78f -- 0300 -- 08E0 0000  		ld    ext6, 0x0000
c791 -- 0302 -- 08E0 0800  		ld    ext6, 0x0800
c793 -- 0304 -- 000C       		ld    -, ext4
c794 -- 0305 -- 08E0 8000  		ld    ext6, 0x8000
c796 -- 0307 -- 08E0 081C  		ld    ext6, 0x081c
c798 -- 0309 -- 00C0       		ld    ext4, - 			# copying ROM area[0000] to IRAM[0000]
c799 -- 030A -- 0830 0300  		ld    a, 0x0300			# 300 words
c79b -- 030C -- 08F0 0000  		ld    ext7, 0x0000
c79d -- 030E -- 001C       		ld    x, ext4
c79e -- 030F -- 00C1       		ld    ext4, x
c79f -- 0310 -- 2800 0001  		sub   a, 0x0001
c7a1 -- 0312 -- 4C50 030E  		bra   nz, 0x030e 		# loop back

c7a3 -- 0314 -- 1800       		ld    r0, 0x00 			# this is checking the previous write to IRAM
c7a4 -- 0315 -- 0C00 0000  		ld    (r0), 0x0000
c7a6 -- 0317 -- 0810 0300  		ld    x, 0x0300 		# loop size
c7a8 -- 0319 -- 08E0 0000  		ld    ext6, 0x0000
c7aa -- 031B -- 08E0 0800  		ld    ext6, 0x0800
c7ac -- 031D -- 000C       		ld    -, ext4
c7ad -- 031E -- 003C       		ld    a, ext4 			# reads from ROM area
c7ae -- 031F -- EA00       		eor   a, ((r0)+!)
c7af -- 0320 -- 4C50 032C  		bra   nz, 0x032c 		# if there was an error, return with error code (a = FFFF)
c7b1 -- 0322 -- 0031       		ld    a, x
c7b2 -- 0323 -- 2800 0001  		sub   a, 0x0001
c7b4 -- 0325 -- 0013       		ld    x, a
c7b5 -- 0326 -- 4C50 031E  		bra   nz, 0x031e		# loop back

c7b7 -- 0328 -- 0830 0000  		ld    a, 0x0000 		# If we got to this point, the test was a success
c7b9 -- 032A -- 036E       		bra   (r6++)			# return with a success code (a = 0000)

c7ba -- 032B -- 036E       		bra   (r6++)			# this jump is never accessed :shrugs:

c7bb -- 032C -- 0830 FFFF  		ld    a, 0xffff 		# return with an error (a = FFFF)
c7bd -- 032E -- 036E       		bra   (r6++) 			
c7be -- 032F -- 036E       		bra   (r6++)			# this jump is never accessed :shrugs

5 - DSP DRAM OVER WRITE

Mega Drive side

This test differs a bit from the previous tests even though is also based on the SVP side. Once the handshake with SVP is done (in the same way as previous tests), the Mega Drive side waits for SVP to write 0xABCD at the start of DRAM. Once that happens, Mega Drive side writes two values into DRAM (0x0000 at start and 0xFFFF at end) and starts monitoring it while the SVP is testing writes using the overwrite bit in the external registers. It does that for 0xFFFF times to make sure those two values stay the same.

After that another test happens. The Mega Drive side writes 0xEEEE into DRAM address 0x8000 (value checked by SVP) and starts listening for responses from the SVP side:

  • MD writes 0xEEEE and listens for a notice from SVP side (a negative number in EXT0 is considered a non-response).
  • SVP must write a test id ("T" followed by a number), otherwise we consider this a failed test.
  • MD writes "SV" to the SVP side asking for the test to begin. Waits for a response.
  • SVP responds. If it's "GO" the test is a success, otherwise it's a failure.
                lea     ($310000).l,a3 	    ; pointing to DRAM[8000]
                move.w  #3,d1 				; test identifier (3)
                move.w  #0,(a3) 			; write 0 in DRAM pointer start
                move.w  #$5356,d0
                bsr.w   sub_1C068 			; send "SV" to SVP side to start handshake
                tst.l   d0
                bmi.w   loc_1BF44
                bsr.w   sub_1C072 			; listen to response from SVP
                tst.l   d0 			
                bmi.w   loc_1BF44  		 	; mark test as an error if this is a negative response
                cmpi.w  #$4F4B,d0 			; did we get "GO"?
                bne.w   loc_1BF44 			; any other response is a failure
                move.w  #$5400,d0
                move.b  d1,d0
                bsr.w   sub_1C068			; send "T 0003" (T + test id) to SVP side
                tst.l   d0
                bmi.w   loc_1BF44
loc_1BFEA:
                move.w  ($300000).l,d7 		; pointing to DRAM start
                cmpi.w  #$ABCD,d7
                bne.s   loc_1BFEA 			; wait until SVP has written 0xABCD in DRAM[0000]
                move.w  #$FFFF,d3  			; loop counter for write-read loop
                lea     ($300000).l,a4
                lea     ($31FFFE).l,a5
                move.w  #0,d5
                move.w  #$FFFF,d4
                move.w  d5,(a5) 			; write 0x0000 into DRAM[FFFF] (DRAM end)
                move.w  d4,(a4) 			; write 0xFFFF into DRAM[0000] (DRAM start)
loc_1C012: 
                cmp.w   (a4),d4 	 		; let's read now from DRAM, is the first value the same we wrote?		
                bne.w   loc_1C028 		 	; otherwise fail
                cmp.w   (a5),d5 			; same thing at the other side of DRAM
                bne.w   loc_1C028 			; otherwise fail
                dbf     d3,loc_1C012 		; keep checking - this is probably checking that writes with overwrite bit in SVP aren't messing with our data
                moveq   #1,d2  			    ; if we got it this far, mark test as succesful
                bra.w   loc_1C02A 			; and continue
; ---------------------------------------------------------------------------
loc_1C028:
                moveq   #2,d2
loc_1C02A:
                move.w  #$EEEE,(a3) 	 ; write 0xEEEE into DRAM[8000] (value to be checked by SVP side of things)
loc_1C02E:
                bsr.w   sub_1C072 		 ; listen to SVP response
                tst.l   d0
                bmi.s   loc_1C02E 		 ; keep waiting until we get some positive result
                move.w  d0,d1
                andi.w  #$FF00,d1
                cmpi.w  #$5300,d1 		 ; did we get a S<test_id> result? - this is probably waiting for SVP to be over with other stuff to be able to ask for a new test
                bne.w   loc_1BF44 		 ; if not, fail
                move.w  #$5356,d0
                bsr.w   sub_1C068 		 ; send "SV" to SVP
                tst.l   d0
                bmi.w   loc_1BF44
                bsr.w   sub_1C072 		 ; listen to SVP response
                tst.l   d0
                bmi.w   loc_1BF44 		 ; if we got a negative response, this is a failure
                cmpi.w  #$4F4B,d0
                bne.w   loc_1BF44 		 ; same thing if it isn't "GO"
                move.w  d2,d0 			 ; otherwise we get the value for test result we've kept and return it
                rts

SVP side

As with the Mega Drive side, the SVP part of this test does things way different than the other tests. While the Mega Drive is checking for the top and bottom addresses in DRAM for changes in the data it's written, the SVP side is writing on them with the overwrite bit set (and the value to write is 0000 - which means that we shouldn't be overwriting anything at all). The test also uses EXT0-EXT4 registers for this, to verify that there are no errors in their behavior. What's funny to me is that instead of relying on XST register for comms with Mega Drive, the whole communication between both sides seems to rely on a flag written by the Mega Drive side on DRAM address 0x8000 (value 0xEEEE).

There are also some weird writes to XST register (twice), which makes me think this test was probably written when the SVP was still in development and the whole XST/XST_Status scheme was still in the air. I need to check what writing to EXT0 in XST mode does in real hardware.

c2a3 -- 030A      		ld    -, (r6--)				# This whole area seems a sync routine between MD side and SVP
c2a4 -- 0512      		ld    (r6), x
c2a5 -- 030A      		ld    -, (r6--)
c2a6 -- 0522      		ld    (r6), y
c2a7 -- 030A      		ld    -, (r6--)
c2a8 -- 0542      		ld    (r6), st 				# backing up registers
c2a9 -- 0840 0060 		ld    st, 0x0060
c2ab -- 0830 0000 		ld    a, 0x0000
c2ad -- 0810 0000 		ld    x, 0x0000
c2af -- 0820 0000 		ld    y, 0x0000
c2b1 -- 08F0 0000 		ld    ext7, 0x0000
c2b3 -- 08E0 8000 		ld    ext6, 0x8000
c2b5 -- 08E0 0018 		ld    ext6, 0x0018			# Reads from DRAM[8000] - here we'll check for MD flag 0xEEEE
c2b7 -- 0008      		ld    -, ext0
c2b8 -- 08E0 0000 		ld    ext6, 0x0000
c2ba -- 08E0 0418 		ld    ext6, 0x0418
c2bc -- 0080      		ld    ext0, - 				# Writes to EXT0 in DRAM, overwrite - no increment
c2bd -- 08E0 FFFF 		ld    ext6, 0xffff
c2bf -- 08E0 0418 		ld    ext6, 0x0418
c2c1 -- 0090      		ld    ext1, -				# Writes to EXT1 to DRAM in bottom, overwrite - no increment
c2c2 -- 08E0 0000 		ld    ext6, 0x0000
c2c4 -- 08E0 0418 		ld    ext6, 0x0418
c2c6 -- 00A0      		ld    ext2, -				# Writes to EXT2 to DRAM start, overwrite - no increment
c2c7 -- 08E0 FFFF 		ld    ext6, 0xffff
c2c9 -- 08E0 0418 		ld    ext6, 0x0418
c2cb -- 00B0      		ld    ext3, - 				# EXT3 writes to DRAM bottom with overwrite
c2cc -- 0880 ABCD 		ld    ext0, 0xabcd 			# Writes to EXT0 abcd

c2ce -- 0840 0000 		ld    st, 0x0000			
c2d0 -- 0880 0000 		ld    ext0, 0x0000 			# Writing to XST State register - what does this do?
c2d2 -- 0880 0000 		ld    ext0, 0x0000 			# twice, for good measure and confusion
c2d4 -- 0840 0060 		ld    st, 0x0060
c2d6 -- 0082      		ld    ext0, y 				# Writes to ext0-ext3 with overwrite, being Y = 0 we shouldn't be writing anything here
c2d7 -- 0092      		ld    ext1, y
c2d8 -- 00A2      		ld    ext2, y
c2d9 -- 00B2      		ld    ext3, y
c2da -- 0082      		ld    ext0, y
c2db -- 0092      		ld    ext1, y
c2dc -- 00A2      		ld    ext2, y
c2dd -- 00B2      		ld    ext3, y
c2de -- 0082      		ld    ext0, y
c2df -- 0092      		ld    ext1, y
c2e0 -- 00A2      		ld    ext2, y
c2e1 -- 00B2      		ld    ext3, y
c2e2 -- 0082      		ld    ext0, y
c2e3 -- 0092      		ld    ext1, y
c2e4 -- 00A2      		ld    ext2, y
c2e5 -- 00B2      		ld    ext3, y
c2e6 -- 0082      		ld    ext0, y
c2e7 -- 0092      		ld    ext1, y
c2e8 -- 00A2      		ld    ext2, y
c2e9 -- 00B2      		ld    ext3, y
c2ea -- 0082      		ld    ext0, y
c2eb -- 0092      		ld    ext1, y
c2ec -- 00A2      		ld    ext2, y
c2ed -- 00B2      		ld    ext3, y
c2ee -- 0082      		ld    ext0, y
c2ef -- 0092      		ld    ext1, y
c2f0 -- 00A2      		ld    ext2, y
c2f1 -- 00B2      		ld    ext3, y
c2f2 -- 0082      		ld    ext0, y
c2f3 -- 0092      		ld    ext1, y
c2f4 -- 00A2      		ld    ext2, y
c2f5 -- 00B2      		ld    ext3, y
c2f6 -- 0000      		nop
c2f7 -- 0000      		nop
c2f8 -- 0082      		ld    ext0, y
c2f9 -- 0000      		nop
c2fa -- 0000      		nop
c2fb -- 0092      		ld    ext1, y
c2fc -- 0000      		nop
c2fd -- 0000      		nop
c2fe -- 00A2      		ld    ext2, y
c2ff -- 0000      		nop
c300 -- 0000      		nop
c301 -- 00B2      		ld    ext3, y
c302 -- 0000      		nop
c303 -- 0000      		nop

## Here's a part where we introduce NOPs progressively between each PM write for quite some time, I'm gonna skip most of those:

c57a -- 0082                        ld    ext0, y
c57b -- 0000                        nop
c57c -- 0000                        nop
c57d -- 0000                        nop
c57e -- 0000                        nop
c57f -- 0000                        nop
c580 -- 0000                        nop
c581 -- 0000                        nop
c582 -- 0000                        nop
c583 -- 0092                        ld    ext1, y
c584 -- 0000                        nop
c585 -- 0000                        nop
c586 -- 0000                        nop
c587 -- 0000                        nop
c588 -- 0000                        nop
c589 -- 0000                        nop
c58a -- 0000                        nop
c58b -- 0000                        nop
c58c -- 00A2                        ld    ext2, y
c58d -- 0000                        nop
c58e -- 0000                        nop
c58f -- 0000                        nop
c590 -- 0000                        nop
c591 -- 0000                        nop
c592 -- 0000                        nop
c593 -- 0000                        nop
c594 -- 0000                        nop
c595 -- 00B2                        ld    ext3, y

c596 -- 0038                        ld    a, ext0  			# Read from DRAM start again.
c597 -- E800 EEEE                   eor   a, 0xeeee 		# Has the Mega Drive side written 0xEEEE (and we haven't modified it)?
c599 -- 4D50 C59D                   bra   z, 0xc59d			# if sync is ok, return to caller with success code (a = 0001)
c59b -- 4C00 C2CE                   bra   0xc2ce			# otherwise we run these writes to the ext registers again

c59d -- 034E                        ld    st, (r6++)
c59e -- 032E                        ld    y, (r6++)
c59f -- 031E                        ld    x, (r6++)
c5a0 -- 0830 0001                   ld    a, 0x0001
c5a2 -- 036E                        bra   (r6++) 			# bring back ST, Y, X, return with success code (A = 0001)

6 - Pointer registers in DSP

Mega Drive side

Refer to test 2 for information on the communication between the SVP side and MD side for these tests.

SVP side

This test follows the same communication scheme between SVP and MD sides, please refer to the section for test 2 for more details on it. What follows are details on the actual test being run at this point.

As with most of the SVP-side tests, the test routine is copied to IRAM:

c7bf -- 0830 C7C3 					ld    a, 0xc7c3
c7c1 -- 0860 C8DA 					bra   0xc8da 			# jump to IRAM write routine, with params:
c7c3 -- C7C6 	 											# origin = C7C6
c7c4 -- 0114	 											# code size = 0x114
c7c5 -- 0000 	 											# entry point

This test is mostly focused on testing pointer registers of the DSP, both to access ROM contents (with double addressing) and writing data to RAM Banks. These tests are repeated for 0x1A times. The flow goes as follows:

  • The whole contents of SVP game code (except the SVP internal ROM - 0x400 - 0xFBFF) is read using pointer registers r0, r1, r2, r4 and r5, using double addressing (thus reading with the DSP internal view) and compared to what the PM can read. If there's an error, the test end with an error code.
  • After that, the same test is run but for pointer register r6. This is done separately as r6 is used for register backup and also to hold the return address from subroutines.
  • Then the whole contents of RAM Bank B are backed up in DRAM as RAM Bank B holds data used during all tests.
  • Once that's done, ROM contents are written to fill both RAM Banks and then checked. For these tests a pointer register for each RAM Bank are used: r0/r4, r1/r5 and r2/r6.
  • We restore RAM Bank B back and return a successful result A = 0001 (if something failed during the way, we stop the test and make sure to restore RAM Bank B before returning an error - A = 0002).

The whole test routine (quite long) is as follows:

c7c6 -- 0000 -- 030A     		ld    -, (r6--)
c7c7 -- 0001 -- 0512     		ld    (r6), x
c7c8 -- 0002 -- 030A     		ld    -, (r6--)
c7c9 -- 0003 -- 0522     		ld    (r6), y
c7ca -- 0004 -- 030A     		ld    -, (r6--)
c7cb -- 0005 -- 0542     		ld    (r6), st 			# backup ST, Y and X
c7cc -- 0006 -- 0840 0060		ld    st, 0x0060
c7ce -- 0008 -- 0810 000A		ld    x, 0x000a 		# number of test runs to execute
c7d0 -- 000A -- 030A     		ld    -, (r6--)
c7d1 -- 000B -- 0512     		ld    (r6), x
c7d2 -- 000C -- 030A     		ld    -, (r6--)
c7d3 -- 000D -- 0D02 0011		ld    (r6), 0x0011 		# return point from test subroutine
c7d5 -- 000F -- 0860 0028		bra   0x0028 			# execute test
c7d7 -- 0011 -- E800 0001		eor   a, 0x0001
c7d9 -- 0013 -- 4C50 0021		bra   nz, 0x0021 		# did we get a return value of 0001 (success)? if not return error
c7db -- 0015 -- 033E     		ld    a, (r6++)
c7dc -- 0016 -- 2800 0001		sub   a, 0x0001
c7de -- 0018 -- 0013     		ld    x, a
c7df -- 0019 -- 4C50 000A		bra   nz, 0x000a 		# keep testing

c7e1 -- 001B -- 034E     		ld    st, (r6++)
c7e2 -- 001C -- 032E     		ld    y, (r6++)
c7e3 -- 001D -- 031E     		ld    x, (r6++)
c7e4 -- 001E -- 0830 0001		ld    a, 0x0001
c7e6 -- 0020 -- 036E     		bra   (r6++) 			# restore registers and return with a success code (A = 0001)

c7e7 -- 0021 -- 031E     		ld    x, (r6++)
c7e8 -- 0022 -- 034E     		ld    st, (r6++)
c7e9 -- 0023 -- 032E     		ld    y, (r6++)
c7ea -- 0024 -- 031E     		ld    x, (r6++)
c7eb -- 0025 -- 0830 0002		ld    a, 0x0002
c7ed -- 0027 -- 036E     		bra   (r6++) 			# restore registers and return with an error code (A = 0002)

c7ee -- 0028 -- 1800     		ld    r0, 0x00 			# Checking reads from ROM in SVP area
c7ef -- 0029 -- 1910     		ld    r1, 0x10
c7f0 -- 002A -- 1A20     		ld    r2, 0x20
c7f1 -- 002B -- 1C00     		ld    r4, 0x00
c7f2 -- 002C -- 1D10     		ld    r5, 0x10
c7f3 -- 002D -- 0C00 0400		ld    (r0), 0x0400
c7f5 -- 002F -- 0C01 0400		ld    (r1), 0x0400
c7f7 -- 0031 -- 0C02 0400		ld    (r2), 0x0400
c7f9 -- 0033 -- 0D00 0400		ld    (r4), 0x0400
c7fb -- 0035 -- 0D01 0400		ld    (r5), 0x0400 		# we're going to be checking most pointer registers (except soft stack and r6 as it's used for return addresses)
c7fd -- 0037 -- 08E0 0400		ld    ext6, 0x0400
c7ff -- 0039 -- 08E0 0800		ld    ext6, 0x0800
c801 -- 003B -- 0009     		ld    -, ext1 			# reading from SVP area of the ROM with autoincrement through EXT1
c802 -- 003C -- 0810 F7FF		ld    x, 0xf7ff 		# loop size
c804 -- 003E -- 08F0 0000		ld    ext7, 0x0000

c806 -- 0040 -- 0029     		ld    y, ext1
c807 -- 0041 -- 0032     		ld    a, y
c808 -- 0042 -- EA00     		eor   a, ((r0)+!) 		# check ROM data
c809 -- 0043 -- 4C50 0111		bra   nz, 0x0111 		# if data is not good, return with error code

c80b -- 0045 -- 0032     		ld    a, y
c80c -- 0046 -- EA01     		eor   a, ((r1)+!) 		# again
c80d -- 0047 -- 4C50 0111		bra   nz, 0x0111
c80f -- 0049 -- 0032     		ld    a, y
c810 -- 004A -- EA02     		eor   a, ((r2)+!) 		# again
c811 -- 004B -- 4C50 0111		bra   nz, 0x0111
c813 -- 004D -- 0032     		ld    a, y
c814 -- 004E -- EB00     		eor   a, ((r4)+!) 		# again 
c815 -- 004F -- 4C50 0111		bra   nz, 0x0111
c817 -- 0051 -- 0032     		ld    a, y
c818 -- 0052 -- EB01     		eor   a, ((r5)+!) 		# and another one
c819 -- 0053 -- 4C50 0111		bra   nz, 0x0111
c81b -- 0055 -- 0031     		ld    a, x
c81c -- 0056 -- 2800 0001		sub   a, 0x0001
c81e -- 0058 -- 0013     		ld    x, a
c81f -- 0059 -- 4C50 0040		bra   nz, 0x0040 		# loop back (to cover all game code - except internal ROM)

c821 -- 005B -- 1800     		ld    r0, 0x00
c822 -- 005C -- 1332     		ld    a, r6
c823 -- 005D -- 0430     		ld    (r0), a
c824 -- 005E -- 1E20     		ld    r6, 0x20 			# backup r6 to be able to test it too
c825 -- 005F -- 0D02 0400		ld    (r6), 0x0400
c827 -- 0061 -- 08E0 0400		ld    ext6, 0x0400
c829 -- 0063 -- 08E0 0800		ld    ext6, 0x0800
c82b -- 0065 -- 0009     		ld    -, ext1 			# reading from SVP area in the ROM
c82c -- 0066 -- 0810 F7FF		ld    x, 0xf7ff 		# cover all game code
c82e -- 0068 -- 08F0 0000		ld    ext7, 0x0000
c830 -- 006A -- 0039     		ld    a, ext1
c831 -- 006B -- EB02     		eor   a, ((r6)+!)
c832 -- 006C -- 4D50 0072		bra   z, 0x0072 		# check, if data is correct keep reading
c834 -- 006E -- 0230     		ld    a, (r0)			
c835 -- 006F -- 1532     		ld    r6, a
c836 -- 0070 -- 4C00 0111		bra   0x0111 			# if there was an error, we restore the return point and jump to the error return case
c838 -- 0072 -- 0031     		ld    a, x
c839 -- 0073 -- 2800 0001		sub   a, 0x0001
c83b -- 0075 -- 0013     		ld    x, a
c83c -- 0076 -- 4C50 006A		bra   nz, 0x006a 		# keep reading

c83e -- 0078 -- 0230     		ld    a, (r0)
c83f -- 0079 -- 1532     		ld    r6, a 			# restore contents of r6

c840 -- 007A -- 08E0 8000		ld    ext6, 0x8000
c842 -- 007C -- 08E0 0818		ld    ext6, 0x0818
c844 -- 007E -- 0090     		ld    ext1, - 			# Writes to DRAM with autoincrement
c845 -- 007F -- 08E0 8000		ld    ext6, 0x8000
c847 -- 0081 -- 08E0 0818		ld    ext6, 0x0818
c849 -- 0083 -- 0009     		ld    -, ext1 			# Reads from DRAM with autoincrement
c84a -- 0084 -- 1C00     		ld    r4, 0x00
c84b -- 0085 -- 0810 0100		ld    x, 0x0100 		# loop size
c84d -- 0087 -- 08F0 0000		ld    ext7, 0x0000
c84f -- 0089 -- 033C     		ld    a, (r4++)
c850 -- 008A -- 0093     		ld    ext1, a 			# we're backing up the contents of RAM Bank B to DRAM (it's used in the test code)
c851 -- 008B -- 0031     		ld    a, x
c852 -- 008C -- 2800 0001		sub   a, 0x0001
c854 -- 008E -- 0013     		ld    x, a
c855 -- 008F -- 4C50 0089		bra   nz, 0x0089 		# loop back

c857 -- 0091 -- 1392     		ld    ext1, r6 			# backing up return address in DRAM too
c858 -- 0092 -- 08E0 0000		ld    ext6, 0x0000
c85a -- 0094 -- 08E0 0800		ld    ext6, 0x0800
c85c -- 0096 -- 0008     		ld    -, ext0 			# reading ROM through EXT0 in autoincrement 
c85d -- 0097 -- 1800     		ld    r0, 0x00
c85e -- 0098 -- 1C00     		ld    r4, 0x00
c85f -- 0099 -- 0810 0100		ld    x, 0x0100 		# loop size
c861 -- 009B -- 08F0 0000		ld    ext7, 0x0000
c863 -- 009D -- 0038     		ld    a, ext0
c864 -- 009E -- 0434     		ld    (r0+!), a
c865 -- 009F -- 0534     		ld    (r4+!), a 		# copy ROM contents using the DSP internal view to fill both RAM Banks
c866 -- 00A0 -- 0031     		ld    a, x
c867 -- 00A1 -- 2800 0001		sub   a, 0x0001
c869 -- 00A3 -- 0013     		ld    x, a
c86a -- 00A4 -- 4C50 009D		bra   nz, 0x009d 		# loop back

c86c -- 00A6 -- 0810 0100		ld    x, 0x0100
c86e -- 00A8 -- 0234     		ld    a, (r0+!)
c86f -- 00A9 -- E304     		eor   a, (r4+!)			# checking writes to RAM banks,
c870 -- 00AA -- 4C50 0103		bra   nz, 0x0103 		# if there's an error, restore RAM Bank B and return error code
c872 -- 00AC -- 0031     		ld    a, x
c873 -- 00AD -- 2800 0001		sub   a, 0x0001
c875 -- 00AF -- 0013     		ld    x, a
c876 -- 00B0 -- 4C50 00A8		bra   nz, 0x00a8 		# keep checking

c878 -- 00B2 -- 08E0 1000		ld    ext6, 0x1000 		# this part does mostly the same thing but using r1/r5
c87a -- 00B4 -- 08E0 0800		ld    ext6, 0x0800
c87c -- 00B6 -- 0008     		ld    -, ext0 			# reading from ROM[1000] and autoincrement
c87d -- 00B7 -- 1900     		ld    r1, 0x00
c87e -- 00B8 -- 1D00     		ld    r5, 0x00
c87f -- 00B9 -- 0810 0100		ld    x, 0x0100 		# loop size
c881 -- 00BB -- 08F0 0000		ld    ext7, 0x0000
c883 -- 00BD -- 0038     		ld    a, ext0
c884 -- 00BE -- 0435     		ld    (r1+!), a
c885 -- 00BF -- 0535     		ld    (r5+!), a 		# writing ROM data to both RAM banks
c886 -- 00C0 -- 0031     		ld    a, x
c887 -- 00C1 -- 2800 0001		sub   a, 0x0001
c889 -- 00C3 -- 0013     		ld    x, a
c88a -- 00C4 -- 4C50 00BD		bra   nz, 0x00bd 		# keep writing

c88c -- 00C6 -- 0810 0100		ld    x, 0x0100			# loop size
c88e -- 00C8 -- 0235     		ld    a, (r1+!)
c88f -- 00C9 -- E305     		eor   a, (r5+!) 		# checking data we just wrote
c890 -- 00CA -- 4C50 0103		bra   nz, 0x0103 		# if there's an error, back up RAM Bank B and return an error
c892 -- 00CC -- 0031     		ld    a, x
c893 -- 00CD -- 2800 0001		sub   a, 0x0001
c895 -- 00CF -- 0013     		ld    x, a
c896 -- 00D0 -- 4C50 00C8		bra   nz, 0x00c8 		# keep checking

c898 -- 00D2 -- 08E0 2000		ld    ext6, 0x2000 		# same routine but copying from ROM[2000]
c89a -- 00D4 -- 08E0 0800		ld    ext6, 0x0800 		# to check r2 and r6
c89c -- 00D6 -- 0008     		ld    -, ext0
c89d -- 00D7 -- 1A00     		ld    r2, 0x00
c89e -- 00D8 -- 1E00     		ld    r6, 0x00
c89f -- 00D9 -- 0810 0100		ld    x, 0x0100 		# loop size
c8a1 -- 00DB -- 08F0 0000		ld    ext7, 0x0000
c8a3 -- 00DD -- 0038     		ld    a, ext0
c8a4 -- 00DE -- 0436     		ld    (r2+!), a
c8a5 -- 00DF -- 0536     		ld    (r6+!), a 		# copy ROM data to RAM Banks
c8a6 -- 00E0 -- 0031     		ld    a, x
c8a7 -- 00E1 -- 2800 0001		sub   a, 0x0001
c8a9 -- 00E3 -- 0013     		ld    x, a
c8aa -- 00E4 -- 4C50 00DD		bra   nz, 0x00dd 		# keep writing

c8ac -- 00E6 -- 0810 0100		ld    x, 0x0100
c8ae -- 00E8 -- 0236     		ld    a, (r2+!)
c8af -- 00E9 -- E306     		eor   a, (r6+!) 		# checking data we just wrote
c8b0 -- 00EA -- 4C50 0103		bra   nz, 0x0103 		# if there's an error, back up RAM Bank B and return an error
c8b2 -- 00EC -- 0031     		ld    a, x 				
c8b3 -- 00ED -- 2800 0001		sub   a, 0x0001
c8b5 -- 00EF -- 0013     		ld    x, a
c8b6 -- 00F0 -- 4C50 00E8		bra   nz, 0x00e8 		# keep checking

c8b8 -- 00F2 -- 1C00     		ld    r4, 0x00 			# restore RAM Bank B
c8b9 -- 00F3 -- 0810 0100		ld    x, 0x0100			# loop size
c8bb -- 00F5 -- 08F0 0000		ld    ext7, 0x0000
c8bd -- 00F7 -- 0039     		ld    a, ext1
c8be -- 00F8 -- 053C     		ld    (r4++), a 		# copy data from DRAM to RAM Bank B
c8bf -- 00F9 -- 0031     		ld    a, x
c8c0 -- 00FA -- 2800 0001		sub   a, 0x0001
c8c2 -- 00FC -- 0013     		ld    x, a
c8c3 -- 00FD -- 4C50 00F7		bra   nz, 0x00f7 		# keep writing

c8c5 -- 00FF -- 1592     		ld    r6, ext1 			# back up return address
c8c6 -- 0100 -- 0830 0001		ld    a, 0x0001
c8c8 -- 0102 -- 036E     		bra   (r6++) 			# and return with success code (A = 0001)

c8c9 -- 0103 -- 1C00     		ld    r4, 0x00 			# This restores the contents of RAM Bank B and returns an error
c8ca -- 0104 -- 0810 0100		ld    x, 0x0100 		# loop size
c8cc -- 0106 -- 08F0 0000		ld    ext7, 0x0000
c8ce -- 0108 -- 0039     		ld    a, ext1 			# reading from EXT1
c8cf -- 0109 -- 053C     		ld    (r4++), a 		# restoring RAM bank B from DRAM
c8d0 -- 010A -- 0031     		ld    a, x
c8d1 -- 010B -- 2800 0001		sub   a, 0x0001
c8d3 -- 010D -- 0013     		ld    x, a
c8d4 -- 010E -- 4C50 0108		bra   nz, 0x0108 		# loop back
c8d6 -- 0110 -- 1592     		ld    r6, ext1 			# and copying return address

c8d7 -- 0111 -- 0830 0002		ld    a, 0x0002
c8d9 -- 0113 -- 036E     		bra   (r6++) 			# return with error

Leftover stuff

Use of interrupts

There was clearly an attempt to test interrupts in the SVP side of these tests. During the initialization process, the default interrupt handlers set by the Internal ROM are rewritten as follows:

c21b -- 08E0 83FA		ld    ext6, 0x83fa
c21d -- 08E0 081C		ld    ext6, 0x081c 			# rewriting interrupt vectors
c21f -- 00C0     		ld    ext4, -
c220 -- 0830 0860		ld    a, 0x0860 			# A will be "bra always, ..."
c222 -- 00C3     		ld    ext4, a
c223 -- 08C0 C8F6		ld    ext4, 0xc8f6			# IE0 = bra always, 0xC8F6
c225 -- 00C3     		ld    ext4, a
c226 -- 08C0 C906		ld    ext4, 0xc906 			# IE1 = bra always, 0xC906
c228 -- 00C3     		ld    ext4, a
c229 -- 08C0 C912		ld    ext4, 0xc912			# IE2 = bra always, 0xC912
c22b -- 0830 0000		ld    a, 0x0000				# these next values that are cleared are counters for interrupt executions
c22d -- 0FF8     		ld    B[0xf8], a 			# INT0 counter
c22e -- 0FF9     		ld    B[0xf9], a 			# INT1 counter
c22f -- 0FFA     		ld    B[0xfa], a 			# INT2 counter
c230 -- 0FFB     		ld    B[0xfb], a 			# seemingly unused

As you can notice, there are interrupt handlers in C8F6 (for INT0), C906 (INT1 - HBLANK) and C912 (INT2 - possibly M68000 clock). For the former two, simpler test code is used:

c8f6 -- 030A       		ld    -, (r6--) 		
c8f7 -- 0542       		ld    (r6), st 			
c8f8 -- 030A       		ld    -, (r6--)
c8f9 -- 0532       		ld    (r6), a 			# backup ST/A
c8fa -- 07F8       		ld    a, B[0xf8]
c8fb -- 8800 0001  		add   a, 0x0001
c8fd -- 0FF8       		ld    B[0xf8], a 		# increment counter for interrupt runs for INT0
c8fe -- 0830 0000  		ld    a, 0x0000
c900 -- 0FF9       		ld    B[0xf9], a 		# clear counters for INT1 and INT2
c901 -- 0FFA       		ld    B[0xfa], a 		
c902 -- 033E       		ld    a, (r6++)
c903 -- 034E       		ld    st, (r6++)		# restore A and ST
c904 -- 9405       		setie
c905 -- 0065       		ret 					# set interrupts back on, and return
c906 -- 030A      		ld    -, (r6--) 		# INT1 handler (HBLANK in final VR cartridge)
c907 -- 0542      		ld    (r6), st
c908 -- 030A      		ld    -, (r6--)
c909 -- 0532      		ld    (r6), a 			# backup ST and A
c90a -- 07F9      		ld    a, B[0xf9]
c90b -- 8800 0001 		add   a, 0x0001
c90d -- 0FF9      		ld    B[0xf9], a 		# increment counter for interrupt runs for INT1
c90e -- 033E      		ld    a, (r6++)
c90f -- 034E      		ld    st, (r6++)
c910 -- 9405      		setie
c911 -- 0065      		ret 					# set interrupts back on, and return

There are some counters involved (I guess these were checked in the missing test code). But for INT2 the interrupt handler is a bit more convoluted:

c912 -- 030A                        ld    -, (r6--)
c913 -- 0542                        ld    (r6), st
c914 -- 030A                        ld    -, (r6--)
c915 -- 0532                        ld    (r6), a 			# backup A and ST
c916 -- 07CF                        ld    a, B[0xcf] 		# The next checks the contents of a variable that isn't used in the current ROM contents
c917 -- 6800 00FF                   cmp   a, 0x00ff 		# it was probably filled by missing test code.
c919 -- 4D50 C933                   bra   z, 0xc933 		# if 00FF, go to C933 - each of these lead to subtle changes in the code - updating EXT1
c91b -- 6800 FF00                   cmp   a, 0xff00
c91d -- 4D50 C929                   bra   z, 0xc929 		# if FF00, go to C929 - updating EXT0
c91f -- 6800 F00F                   cmp   a, 0xf00f
c921 -- 4D50 C93D                   bra   z, 0xc93d 		# if F00F, go to C93D - updating EXT0 and EXT1
c923 -- 07FA                        ld    a, B[0xfa]
c924 -- 8800 0001                   add   a, 0x0001
c926 -- 0FFA                        ld    B[0xfa], a 		# increment counter for INT2 runs

c927 -- 4C00 C947                   bra   0xc947 			# end interrupt handling code if we didn't get any of those values

c929 -- 0840 0020                   ld    st, 0x0020
c92b -- 0038                        ld    a, ext0
c92c -- 0083                        ld    ext0, a 			# data copy through EXT0 (with ST=0020 it should behave as PM)
c92d -- 07CE                        ld    a, B[0xce] 		# another counter for INT2?
c92e -- 2800 0001                   sub   a, 0x0001
c930 -- 0FCE                        ld    B[0xce], a
c931 -- 4C00 C947                   bra   0xc947 			# Return

c933 -- 0840 0020                   ld    st, 0x0020
c935 -- 0039                        ld    a, ext1
c936 -- 0093                        ld    ext1, a 			# auto-writes to EXT1?
c937 -- 07CE                        ld    a, B[0xce]
c938 -- 2800 0001                   sub   a, 0x0001 		# another counter decrement
c93a -- 0FCE                        ld    B[0xce], a
c93b -- 4C00 C947                   bra   0xc947 			# Return

c93d -- 0840 0020                   ld    st, 0x0020
c93f -- 0038                        ld    a, ext0
c940 -- 0083                        ld    ext0, a 			# data copy through EXT0
c941 -- 0039                        ld    a, ext1
c942 -- 0093                        ld    ext1, a 			# data copy through EXT1
c943 -- 07CE                        ld    a, B[0xce]
c944 -- 2800 0001                   sub   a, 0x0001
c946 -- 0FCE                        ld    B[0xce], a 		# another counter decrement

c947 -- 033E                        ld    a, (r6++)
c948 -- 034E                        ld    st, (r6++)		# bring back A and ST
c949 -- 9405                        setie
c94a -- 0065                        ret 					# set interrupts back on, and return

Besides the use of some counters and stuff, the interrupt handler is using the external registers EXT0 and EXT1 to copy data in memory. ST5 is set (intended to use EXT0 and EXT1 as PMs), but without programming them through EXT6 (my guess is that these are counting with missing test code to do it before activating interrupts). The expected programming probably involved setting different addresses for reads and writes to copy data from those (probably to be checked later by the test routine in charge of this).

In the Mega Drive side there's a small routine that writes 0, 1 and 0 sequentially to external mapped register in address A15008. The purpose if this is unknown, but it's been suggested that this might be causing an interrupt (or activating them from outside the code). I can confirm that:

  • Interrupts are not enabled by this.
  • INT0 isn't triggered by this (INT1 and INT2 are wired to timing signals).

In any case, I'm convinced that INT0 can be triggered from the Mega Drive somehow. Will keep researching on this.

Game pad code in MD test code

During the test code initialization process in the Mega Drive side, right after DRAM is cleaned, control pads are seemingly initialized too:

		         move.b  #$40,($A10003).l
0001B0FE                 move.b  #$40,($A10009).l
0001B106                 move.b  #$40,($A10005).l
0001B10E                 move.b  #$40,($A1000B).l

It seems funny to me as game pads in the final retail version must be initialized at this point (as these tests are triggered by using a control pad combination during boot-up). This makes me think that originally these tests were run in a standalone ROM that booted up directly from here, and game pad was required for some of these tests (and later removed).

Skippable tests

As mentioned in the section for Test 2, the handshake communication scheme between Mega Drive-SVP to run the shared tests allow for the MD side to stop running the tests in some conditions (MD side can send "GO" to the SVP after the first handshake, in that situation all tests are stopped in the SVP side and it enters an infinite loop). The string "SKIP" can also be found in the ROM. These aren't used in the final retail version, so I wonder if this was combined with the use of game pads to be able to skip longer tests during the game development.