Midway Zeus 2: Difference between revisions

From MAMEDEV Wiki
(New page: == Midway Zeus 2 Hardware == The "Zeus 2" was the successor to the original Midway Zeus 3D chip which powered games such as '''Mortal Kombat 4''' and '''Invasion'''. This page describ...)
 
 
(8 intermediate revisions by the same user not shown)
Line 1: Line 1:
== Midway Zeus 2 Hardware ==
The "Zeus 2" was the successor to the original [[Midway Zeus]] 3D chip which powered games such as '''Mortal Kombat 4''' and '''Invasion'''. This page describes the current state of understanding of the chip, based on reverse engineering the games.
The "Zeus 2" was the successor to the original [[Midway Zeus]] 3D chip which powered games such as '''Mortal Kombat 4''' and '''Invasion'''. This page describes the current state of understanding of the chip, based on reverse engineering the games.


=== Games ===
== Games ==
 
There are two known games running on the Zeus 2 hardware:
There are two known games running on the Zeus 2 hardware:
* Cruis'n Exotica
* Cruis'n Exotica
* The Grid
* The Grid


=== Main CPU ===
== Main CPU ==
 
The main CPU driving the Zeus 2 is, like the original Zeus, a TMS32C032 DSP. It is assumed to run at 60 MHz like the original, although this has yet to be confirmed.
The main CPU driving the Zeus 2 is, like the original Zeus, a TMS32C032 DSP. It is assumed to run at 60 MHz like the original, although this has yet to be confirmed.


=== Zeus 2 3D Graphics ===
== Zeus 2 3D Graphics ==
 
As with the original Zeus, the Zeus 2 chip is memory mapped into the main CPU's address space at addresses from $880000-$88007F. Keep in mind that the TMS32C032 only accesses 32-bit memory, so each of the 128 addresses is 32 bits wide (i.e., address $880000 references one 32-bit word, and address $880001 references a completely independent 32-bit word).
As with the original Zeus, the Zeus 2 chip is memory mapped into the main CPU's address space at addresses from $880000-$88007F. Keep in mind that the TMS32C032 only accesses 32-bit memory, so each of the 128 addresses is 32 bits wide (i.e., address $880000 references one 32-bit word, and address $880001 references a completely independent 32-bit word).


As with most external chips, the memory map for the Zeus consists of a number of registers, in this case 128 registers. Unlike the original Zeus, the Zeus 2 appears to only operate in full 32-bit mode.
As with most external chips, the memory map for the Zeus consists of a number of registers, in this case 128 registers. Unlike the original Zeus, the Zeus 2 appears to only operate in full 32-bit mode.


==== Wave RAM ====
=== Wave RAM ===
 
The Zeus 2 chip provides access to two banks of local RAM, called "Wave RAM" in the diagnostic tests. Wave RAM is organized into two separate banks, one for 3D rendering data, and one for the framebuffer. Most references to Wave RAM are done in terms of what are suspected to be row/column addresses.
(copy/paste from Zeus; needs to be updated)
 
The Zeus chip provides access to some local RAM called "Wave RAM" in the diagnostic tests. There are two banks of Wave RAM, 8MB each, for a total of 16MB of 3D graphics RAM. Wave RAM holds the frame buffers, textures, model data, palettes, and other tables used by the Zeus.


Most references to Wave RAM are done in terms of what I suspect are row/column addresses. The low 12 bits of an address are the column, and the upper 12 bits of an address are the row (the top 8 bits are used for other purposes). Wave RAM appears to be configured as 512 columns, so really only 9 of the 12 lower bits are used. 11 of the 12 upper bits are used, giving 512 * 2048 (1M) addressable locations.
Bank 0 of Wave RAM is used to store model data, texture data, palettes, and other rendering information. It is organized as 2048 rows by 1024 columns. Each "cell" of bank 0 contains 8 bytes, giving 2k × 1k × 8 = 16MB of RAM. Data is stored sequentially as little-endian 32-bit words (two words are latched and written together into a single cell).


Direct access to Wave RAM from the CPU is done in 8 byte (64-bit) chunks. This is where the 8MB comes from (512 * 2048 * 8).  
Bank 1 of Wave RAM holds the frame buffer (2 pages). It is organized as 1024 rows by 512 columns. Each "cell" of bank 1 contains 12 bytes, giving 1k × 0.5k × 12 = 6MB of RAM. Frame buffer data is stored as 32-bit RGB data, with a 16-bit depth component. The data is packed such that two 32bpp pixels live in their own words, while the two corresponding 16-bit depth values are packed into a 3rd 32-bit word and stored within the same cell.


Wave RAM appears to be addressed in several different ways. The most common format for referencing a Wave RAM address is to use a special length/row/column format as follows:
Wave RAM is generally addressed in row/column format, in the following format:
{| border="1"
{| border="1"
! Bit||31||30||29||28||27||26||25||24||23||22||21||20||19||18||17||16||15||14||13||12||11||10||09||08||07||06||05||04||03||02||01||00
! Bit||31||30||29||28||27||26||25||24||23||22||21||20||19||18||17||16||15||14||13||12||11||10||09||08||07||06||05||04||03||02||01||00
|- align="center"
|- align="center"
|
|
| 0||0
| colspan="6"| ''LENGTH''
| x
| colspan="11"| ''ROW''
| x||x||x
| colspan="9"| ''COLUMN''
|}
I will call this form "block form", since it is used to reference not only an address but a block of data up to 512 bytes long. I also call it "block form" because everything is specified in terms of 8-byte "blocks". The actual byte address into Wave RAM is given by (8 * (''COLUMN'' + (''ROW'' * 512))). The length ''LENGTH'' specifies a length of 1-64 blocks (0 means 1 block, 63 means 64 blocks).
When accessing Wave RAM via the direct RAM access registers, a similar form is used but without the length:
{| border="1"
! Bit||31||30||29||28||27||26||25||24||23||22||21||20||19||18||17||16||15||14||13||12||11||10||09||08||07||06||05||04||03||02||01||00
|- align="center"
|
| x||x||x||x||x
| x||x||x||x||x
| colspan="11"| ''ROW''
| colspan="11"| ''ROW''
| x||x||x||x||x||x||x
| x||x||x||x||x||x
| colspan="9"| ''COLUMN''
| colspan="10"| ''COLUMN''
|}
I will call this form "expanded form", since the ''ROW'' and ''COLUMN'' parts are expanded out to 16 bits each. Again, the ''ROW'' and ''COLUMN'' values reference 8-byte blocks of data, so the byte address is given by (8 * (''COLUMN'' + (''ROW'' * 512))). Data is written/read via two 32-bit registers $0B0 and $0B2. The address of direct accesses is controlled by register $0B4 and is in the expanded form described here. Writing modes are controlled by register $0B6.
 
The frame buffer consists of 15-bit RGB pixels. It appears that the frame buffer and depth buffer are interleaved at two-pixel intervals. If Pn represents a pixel value, and Zn represents a depth value, it is ordered as:
 
{| border="1"
|-
|P0 || P1 || Z0 || Z1 || P2 || P3 || Z2 || Z3 || ...
|}
|}


When initializing buffers, the depth value is often written as $7FFF.
=== Register Map ===
 
==== Register Map ====
 
(copy/paste from Zeus; needs to be updated)


{| border="1"
{| border="1"
Line 74: Line 40:
!Purpose
!Purpose
|-
|-
|$000
|$00
|R
|Status register. Bit $00000020 is looped on until set.
|-
|$01
|R
|Status register. Bits $000C0070 are looped on until clear. Bit $00000004 is looped on as it toggles (VBLANK?)
|-
|$07
|R
|ID register. Read at startup to verify the chip. Needs to return 1 bits in all of the following: $10451998.
|-
|$08
|W
|W
|Controls solid color when a direct command is written to $060. Used in Invasion's test mode for the solid color monitor screens (and for clearing the screen between test mode screens).
|Data FIFO (see FIFO commands, below)
|-
|$10
|W
|Unknown. crusnexo writes $00000601, $00000641, $00000523 at startup, then leaves as $0000062F during gameplay. thegrid writes $00000601, $00000623, then leaves as $0000062F during gameplay.
|-
|$11
|W
|Unknown. crusnexo/thegrid write $000007FF at startup.
|-
|$12
|W
|Unknown. crusnexo/thegrid write $00200100 at startup.
|-
|$13
|W
|Unknown. crusnexo/thegrid write $00020000, then write to registers $2F and $11, before writing a $00000028 during startup.
|-
|$18
|R/W
|Unknown. thegrid writes $00000000 at startup. thegrid also reads this register and stores it in memory, though it does not appear to be used anywhere.
|-
|$19
|W
|Unknown. thegrid writes $00000000 at startup.
|-
|$20
|W
|Write to a 24-bit sub-register. See the Sub-Registers section below for details. The sub-register number is specified in the upper 8 bits, and the data lives in the lower 24 bits.
|-
|-
|$004
|$22
|W
|W
|This is a bitmask controlling some texture behaviors:
|Unknown. crusnexo/thegrid write $00BA9807 at startup.
:$000000C0 = scale factor for V coordinates (V is specified as V / (1 << ((reg[4] >> 6) & 3)))
:$00000030 = scale factor for U coordinates (U is specified as U / (1 << ((reg[4] >> 4) & 3)))
invasn writes via model command $19. Values seen = $00001C00
|-
|-
|$006
|$23
|W
|W
|Low 16 bits specify texture mode
|Unknown. crusnexo/thegrid write $00654321 at startup.
|-
|-
|$008
|$24
|W
|W
|Specifies Y (in the upper 16 bits) and X (in the lower 16 bits) of the vertex 1 coordinate when a direct command is written to $060.
|Unknown. crusnexo writes $00000601, then a bunch of other registers, before writing $0000000F during startup. thegrid writes $00000601, then a bunch of other registers, before writing $00000001 during startup.
|-
|-
|$00A
|$2A
|W
|W
|Specifies Y (in the upper 16 bits) and X (in the lower 16 bits) of the vertex 2 coordinate when a direct command is written to $060.
|Unknown. crusnexo/thegrid write $00000000 at startup.
|-
|-
|$00C
|$2B
|W
|W
|Specifies Y (in the upper 16 bits) and X (in the lower 16 bits) of the vertex 3 coordinate when a direct command is written to $060.
|Unknown. crusnexo/thegrid write $00000000 at startup.
|-
|-
|$00E
|$2C
|W
|W
|Specifies Y (in the upper 16 bits) and X (in the lower 16 bits) of the vertex 4 coordinate when a direct command is written to $060.
|Unknown. crusnexo/thegrid write $00000003 at startup.
|-
|-
|$04E
|$2D
|W
|W
|Seems to affect blending. Is generally set to $00FFFFFF. However, during screen fades, the system sets this to $000080 (indicating no fade) and ramps it down to $000000 (at full blackness) just before rendering a quad that overlays the area to fade out. Is it alpha blending or modulation of existing pixels?
|Unknown. crusnexo/thegrid write $00000070 at startup.
 
|-
Also note that when rendering directly via a write to $060, this is set to $00808080.
|$2F
|W
|Unknown. crusnexo writes $0000008C, then writes a bunch of registers, following by writing $0000004C, $0000004C, $0000001C in sequence during startup. thegrid just writes $0000008C and then $0000001C.
|-
|$30
|W
|Unknown. crusnexo/thegrid write $0000A18F at startup.
|-
|$31
|W
|Unknown. crusnexo writes $000520C4 at startup. thegrid writes $000523C4 at startup.
|-
|$32
|W
|Horizontal sync begin (upper 16 bits?). crusnexo writes $002E0015 at startup. thegrid writes $00420015.
|-
|$33
|W
|Horizontal blank end (upper 16 bits). Horizontal sync end (lower 16 bits?). crusnexo/thegrid write $0090004C at startup.
|-
|-
|$058
|$34
|W
|W
|Usually written after $05A; after writing, waits for read from $0F6 to return with bit 4 cleared
|Horizontal total (upper 16 bits) and horizontal blank begin (lower 16 bits). crusnexo/thegrid write $029A0290 at startup.
|-
|-
|$05A
|$35
|W
|W
|Usually written immediately before writing $058
|Vertical sync start (upper 16 bits?) and vertical blank start (lower 16 bits). crusnexo/thegrid write $0192018F at startup.
|-
|-
|$060
|$36
|W
|W
|A value of $00000001 is written here immediately after filling the lower registers with what look like parameters for a quad. Before writing a 1 here, the low 24 bits of the master control at $080 are set to $22FCFF.
|Vertical unknown (upper 16 bits?) and vertical sync end (lower 16 bits?). crusnexo/thegrid write $01B60195 at startup.
|-
|-
|$068
|$37
|W
|W
|MK4 written at startup: $00030000 = toggled on then off at startup
|Vertical total (lower 16 bits). crusnexo/thegrid write $000001B6 at startup.
invasn writes via FIFO command $17 (both high and low) and via model command $19. Values seen = $40C41370
|-
|-
|$070
|$38
|W
|W
|Seems to specify the X coordinate of a model (written as 16-bit from FIFO command $17)
|Wave RAM address of screen base. crusnexo/thegrid toggle between $00000000 and $01900000.
|-
|-
|$072
|$39
|W
|W
|Seems to specify the Y coordinate of a model (written as 16-bit from FIFO command $17)
|Unknown. crusnexo/thegrid write $C0FF3010 at startup.
|-
|-
|$074
|$3A
|W
|W
|Seems to specify the Z coordinate of a model (written as 16-bit from FIFO command $17)
|Unknown. crusnexo/thegrid write $00000000 at startup.
|-
|-
|$076
|$40
|W
|W
|invasn writes to this both directly and via FIFO command $18. Values seen = $F8800000
|Wave RAM bank 0 access control. crusnexo writes $00800000 while setting up for access. It writes $00890000 before performing a write access. It writes $00820000 before performing a read access via a manual latch. It writes $00A20000 before performing a read that doesn't require manual latching. Values seen:
:$00000000 (crusnexo/thegrid): reset to this
:$00510000 (crusnexo/thegrid)
:$005500FF (thegrid)
:$00800000 (crusnexo/thegrid): set to this while setting up for direct access
:$00820000 (thegrid): set to this before performing direct reads using the latch
:$0084003F (crusnexo)
:$00890000 (crusnexo/thegrid): set to this before performing direct writes
:$005500FF (crusnexo)
:$00A20000 (crusnexo): set to this before performing reads with automatic latching
:$3855006F (crusnexo)
:$38550070 (crusnexo)
:$38550075 (crusnexo)
:$38550083 (crusnexo)
:$38550088 (crusnexo)
:$38850077 (thegrid)
:$C085003F (crusnexo)
|-
|-
|$078
|$41
|W
|W
|invasn writes to this via FIFO command $18. Values seen = $000000C7. Could be the screen X origin in the low 16 bits.
|Wave RAM bank 0 access address and manual latch. Normally, this is the address used for direct accesses to Wave RAM. In some cases (when register $40 is set to $00820000), a write here does not alter the address (the value written is ignored), but rather triggers a read from Wave RAM into the transfer registers at the previously programmed address. When register $40 is set to $00A20000, a write here causes an immediate transfer from Wave RAM into the transfer registers.
|-
|-
|$07A
|$47
|W
|W
|invasn writes to this via FIFO command $18. Values seen = $00800000. Could be the screen Y origin in the high 16 bits.
|Wave RAM bank 0 write control. crusnexo/thegrid write $0000000F at startup.
|-
|$48-49
|R/W
|Wave RAM bank 0 transfer latches. Data that is read from Wave RAM due to an access to register $41 appears here. Data is also written here prior to being pushed out to Wave RAM. Depending on the value of register $4E, writes to this register may actually trigger a flush from the transfer registers to Wave RAM.
|-
|-
|$07C
|$4C
|W
|W
|invasn writes to this via FIFO command $18. Values seen = $FFFFFFE0
|Unknown. crusnexo/thegrid write $00000000, followed by writes to registers $47 and $4F, followed by $3D530000, $3CC90000, $3CC90000, $3C430000 in sequence, followed by a write to register $4D, followed by $000355E at startup.
|-
|-
|$07E
|$4D
|W
|W
|This is almost certainly a Z buffer offset. The computed Z value is added to this 16.16 value before doing Z comparisons and other Z buffering. It is necessary to implement this in order for many objects to show in front of their backgrounds.
|Unknown. crusnexo/thegrid write $60990006, then $E0990006 at startup.
|-
|$4E
|W
|Wave RAM bank 0 mode control.
 
When performing a write with autoincrement, crusnexo writes $F2080020 at the start, then the address to register $41, then the mode to register $40, then $F2080030, then $F2080069, then does the writes to $48,$49. When finished, it resets the state to $F2080020.
 
Prior to performing writes with explicit address sets before each one, this register is set to $F2080028. Writes are done to $49 then $48.
 
When performing a read with autoincrement, it writes $F2080020 at the start, then the address to register $41, then the mode to register $40, then $F2080030, then $F2080061, then does the reads by doing a dummy write to $41 followed by reads from $48,$49. When finished, it resets the state to $F2080020.
 
Prior to performing reads with explicit address sets before each one, this register is set to $F2080021. Reads are done from $48 then $49.
 
Based on these sequences, the assumption is that the bits mean:
:$00000003 = mask to select which transfer register, when accessed, triggers (in the write case) a flush from transfer registers to Wave RAM and also which transfer register triggers then autoincrement behavior
:$00000008 = set for writes, clear for reads
:$00000010 = set briefly after writing the address but before setting the final mode (crusnexo only)
:$00000040 = set for autoincrement
:$FFFF0000 = set to F208 (crusnexo/thegrid) or F20D (thegrid)
Values seen:
:$F2080020 (crusnexo/thegrid): set to this prior to writing address ($41)
:$F2080021 (crusnexo): set to this before performing reads with explicit addresses
:$F2080028 (crusnexo): set to this before performing writes with explicit addresses
:$F2080030 (crusnexo/thegrid): set to this after writing address ($41) and mode ($40) but before autoinc accesses
:$F2080061 (thegrid): set to this before performing reads with an autoincrementing address
:$F2080069 (crusnexo/thegrid): set to this before performing writes with autoincrementing address
|-
|-
|$080
|$4F
|W
|W
|Master control; configures some core chip behavior:
|Unknown. crusnexo/thegrid write $004F4F00 at startup.
:$40000000 = set along with $02000000 when enabling FIFO empty interrupt
:$02000000 = enable internal data FIFO empty interrupt
:$01000000 = cleared along with $02000000 when disabling FIFO empty interrupt
:$00020000 = 16-bit (0) or 32-bit (1) mode
:$00000080 = set just before writing direct command to $60
:$00000040 = set just before writing direct command to $60
:$00000008 = cleared just before writing to offsets $200-$3FF
|-
|-
|$084
|$50
|W
|W
|$00000xxx = select framebuffer base offset for rendering? (generally set to $080 when $CC is set to $000000 and set to $000 when $CC is set to $800000)
|Wave RAM bank 1 access control. Same as register $40, but for Wave RAM bank 1.
Values seen:
:$00000000 (crusnexo)
:$00510000 (crusnexo)
:$00800000 (crusnexo/thegrid)
:$00820000 (thegrid)
:$00890000 (crusnexo/thegrid)
:$00A20000 (crusnexo)
:$007811FF (crusnexo)
:$0078127F (crusnexo)
:$007812FF (crusnexo)
:... (inclusive repeating every $80)
:$007814FF (crusnexo)
:$007818FF (crusnexo)
:$0078197F (crusnexo)
:$007819FF (crusnexo)
:... (inclusive repeating every $80)
:$00782FFF (crusnexo)
:$007831FF (crusnexo/thegrid)
:$0079FFFF (thegrid)
:$008831FF (thegrid)
:$00E90000 (thegrid)
|-
|-
|$0B0
|$51
|R/W
|R/W
|Data port for direct RAM access
|Wave RAM bank 1 access address and manual latch. Same as register $41, but for Wave RAM bank 1. This register is tested by writing a FIFO command that sets its value and then reading it back.
|-
|$54
|R
|Video position. Both upper and lower 16 bits are read and treated like vertical beam position.
|-
|$57
|W
|Wave RAM bank 1 write control. Same as register $47, but for Wave RAM bank 1. crusnexo writes $0000003F here. thegrid writes both $00000004 and $00000001 to control which of two individual pixels to write. Is this a write mask of some sort?
|-
|-
|$0B2
|$58-5A
|R/W
|R/W
|Data port for direct RAM access
|Wave RAM bank 1 transfer latches. Same as registers $48-49, but for Wave RAM bank 1. Note that there are 3 latches instead of 2, to account for the 3 words per cell.
|-
|$5C
|W
|Unknown. crusnexo writes $00000000, followed by a write to register $5F, followed by $FD530000, $FCC90000, $FCC90000, $FC430000, $00620000, $00620000 in sequence, followed by a write to register $5D, followed by $000355E at startup. thegrid does the same thing except the final value written is $0062355E.
|-
|$5D
|W
|Unknown. crusnexo/thegrid write $44180006, then $C4180006 at startup.
|-
|$5E
|W
|Wave RAM bank 1 mode control. Same as register $4E, but for Wave RAM bank 1.
Values seen:
:$F2080000 (crusnexo)
:$F2080010 (thegrid)
:$F2080020 (crusnexo)
:$F2080021 (crusnexo)
:$F2080028 (crusnexo)
:$F2080030 (crusnexo)
:$F2080041 (thegrid)
:$F2080048 (thegrid)
:$F208004A (thegrid)
:$F2080068 (crusnexo)
:$F20D0000 (thegrid)
:$F20D004A (thegrid)
|-
|$5F
|W
|Unknown. crusnexo/thegrid write $004F4F4F at startup.
|-
|$63
|W
|Unknown. crusnexo/thegrid write $C47E0000 at startup.
|-
|$64
|W
|Unknown. crusnexo/thegrid write $00000000 at startup.
|-
|-
|$0B4
|$65
|W
|W
|Address for direct RAM access in expanded form
|Unknown. crusnexo/thegrid write $00000000 at startup.
|-
|-
|$0B6
|$66
|W
|W
|Direct RAM access control
|Unknown. crusnexo writes $0000008E at startup. thegrid writes both $0000008E and $00000085.
:$80000000 = enable writes to wave RAM 0
:$40000000 = enable writes to wave RAM 1
:$02000000 = perform access on write to B0(0) or B2(1) ?????
:$00800000 = enable writes from [$B2] to depth buffer
:$00400000 = enable writes from [$B0] to depth buffer
:$00200000 = enable writes from [$B2] to RAM buffer
:$00100000 = enable writes from [$B0] to RAM buffer
:$00020000 = auto increment the address
:$00010000 = perform a read when the address is written
:$00000001 = enable direct RAM access
 
Examples:
:$80A00000 when writing a single pixel (address written each time)
:$80A20001 when writing the right pixel of a pair (autoinc assumed)
:$80F60001 when writing the middle pairs of pixels (autoinc assumed)
:$80520001 when writing the left pixel of a pair (autoinc assumed)
:$82F00001 when testing wave RAM page 1 (address written each pair)
:$42F00001 when testing wave RAM page 0 (address written each pair)
:$82F60001 when uploading to wave RAM page 1 (autoinc assumed)
:$42F60001 when uploading to wave RAM page 0 (autoinc assumed)
|-
|-
|$0C0
|$67
|W
|W
|Written at startup = $801F2500
|Unknown. crusnexo/thegrid write $00000000 at startup.
|-
|-
|$0C2
|$68
|W
|W
|Written at startup = $0015E591
|Unknown. crusnexo/thegrid write $0000009D at startup.
|-
|-
|$0C4
|$6A
|W
|W
|Written at startup = $000C0042 (HSYNC start/HSYNC end?)
|Unknown. crusnexo writes $43800000 at startup. thegrid writes $43804000 at startup.
|-
|-
|$0C6
|$6B
|W
|W
|Written at startup = $0211007F (HTOTAL/HBLANK end?)
|Unknown. crusnexo writes $43480000 at startup. thegrid writes $43488000 at startup.
|-
|-
|$0C8
|$6C
|W
|W
|Written at startup = $010300FF (VSYNC start/VBLANK start?)
|Unknown. crusnexo writes $00000009 at startup, later changes to $00000001. thegrid writes $00000009 at startup, later changes to $00000000.
|-
|-
|$0CA
|$6F
|W
|W
|Written at startup = $01160107 (VTOTAL/VSYNC end?)
|Unknown. crusnexo/thegrid write $00003FC0 at startup.
|-
|-
|$0CC
|$76
|W
|W
|Written at startup = $00000000 (display framebuffer start address? toggles between $00000000 and $00800000)
|Unknown. crusnexo/thegrid write $44000000 at startup.
|-
|-
|$0CE
|$77
|W
|W
|Written at startup = $00C87620
|Unknown. crusnexo/thegrid write $43C80000 at startup.
|-
|-
|$0E0
|$78
|W
|W
|Data FIFO (see FIFO commands, below)
|Unknown. crusnexo/thegrid write $00000000 at startup.
|-
|-
|$0F0
|$79
|R
|W
|Current horizontal video beam position; used by invasn for reading the lightgun (low 11 bits)
|Unknown. crusnexo writes $C47A0000 at startup. thegrid writes $C7435000 at startup.
|-
|-
|$0F2
|$7A
|R
|W
|Current vertical video beam position; used by invasn for reading the lightgun (low 10 bits)
|Unknown. crusnexo/thegrid write $000080AA at startup.
|-
|-
|$0F4
|$7C
|R
|W
|Status register
|Unknown. crusnexo/thegrid write $003F5F33 at startup.
:$00000800 = VBLANK? code waits for it to clear to 0 and then reset to 1
:$00000140 = tested together in tight loop until both are 0
:$00000008 = internal data FIFO full
:$00000004 = tested in tight loop along with $00000002 until at least one is set
:$00000002 = tested in tight loop after handling FIFO interrupt until set
|-
|-
|$0F6
|$7D
|R
|W
|Status register 2
|Unknown. crusnexo writes $74037808 at startup. thegrid writes $740A7020 at startup.
:$00009600 = these bits must be set on a read in order to pass Zeus system test
:$00000010 = tested in tight loop after writing 5A/59/58 until set to 0
|-
|-
|$200-$3FF
|$7F
|W
|W
|Unknown, but written in a loop with master control bit $00000008 cleared
|Unknown. crusnexo/thegrid write $00000060 at startup.
Possible fast buffer clear? One set of writes stores $7F7F to all entries.
|-
A second set writes $E00,$25,$E01,$25,...,$E7F,$25. A third set writes $52,$A0000000,...
|}
|}


==== Internal Pointer Registers ====
=== Sub-Registers ===
(copy/paste from Zeus; needs to be updated)
In addition to the direct registers, there is a set of 24-bit "sub-registers" which are written to via direct register $20. When writing here, the sub-register number is specified in the top 8 bits, and the data is provided in the lower 24 bits.
 
There appears to be a set of internal registers which hold pointers to data in wave RAM. They don't appear to be directly accessible via the register map, but are written to via special FIFO or model commands. Which pointer register is being accessed is controlled by a parameter ''WHICHPTR''. The value of pointer registers is always a block pointer into wave RAM. Below is a table of the values that have been seen so far, and what they mean:


{| border="1"
{| border="1"
|-
|-
!''WHICHPTR''
!Address
!Purpose
!Purpose
|-
|-
|$008000
|$00
|pointer to model data to render via FIFO command $13
|Unknown. Always written as $000000, frequently during execution.
|-
|$018000
|pointer to model data to render via FIFO command $13 (does the top byte matter?)
|-
|-
|$00C040
|$01
|pointer to palette to use for texture lookups
|Unknown. crusnexo writes $0001FF at startup. X min/max clip (in high 12 bits/low 12 bits)?
|-
|-
|$004040
|$02
|set via FIFO command in mk4 (len=02)
|Unknown. crusnexo writes $00018F at startup. Y min/max clip (in high 12 bits/low 12 bits)?
|-
|-
|$02C0F0
|$03
|set in model data in mk4 (len=0F)
|Unknown. crusnexo writes $000000 at startup.
|-
|-
|$03C0F0
|$04
|set via FIFO command in mk4 (len=00)
|Render base row. crusnexo alternates between $000000 and $000190 each frame. This value is always opposite that written to register $38.
|-
|-
|$02C0E7
|$05
|set via FIFO command in mk4 (len=08)
|Texture base address. This is specified as a packed address, with the low 10 bits specifying the column and the upper 14 bits specifying the row.
|-
|-
|$04C09C
|$06
|set via FIFO command in mk4 (len=08)
|Unknown. crusnexo sometimes writes $000000 and sometimes writes $0000B1.
|-
|-
|$05C0A5
|$07
|set via FIFO command in mk4 (len=21)
|Unknown. crusnexo writes $0000FF at startup.
|-
|-
|$80C0A5
|$0C
|set via FIFO command in mk4 (len=3F)
|Unknown. crusnexo writes here frequently, specifying values ranging from $000001 to $000141. Alpha blending or fade value?
|-
|-
|$81C0A5
|$0D
|set via FIFO command in mk4 (len=35)
|Unknown. crusnexo writes here frequently, specifying values ranging from $000000 to $0000FF. Alpha blending or fade value?
|-
|-
|$82C0A5
|$11
|set via FIFO command in mk4 (len=41)
|Unknown. crusnexo writes $008A53 at startup.
|-
|-
|$00C0F0
|$12
|set via FIFO command in invasn (len=0F)
|Unknown. crusnexo writes $006009 at startup.
|-
|-
|$00C0B0
|$14
|set via FIFO command in invasn (len=3F) -- seems to be the same as C0A5
|Unknown. crusnexo writes here frequently. Values seen:
:$001000 (crusnexo)
:$004000 (crusnexo)
:$004040 (crusnexo)
:$004062 (crusnexo)
:$004CA1 (crusnexo)
|-
|-
|$05C0B0
|$15
|set via FIFO command in invasn (len=21)
|Unknown. crusnexo writes here frequently, specifying either $000000, $0000FF, or $0007FF.
|-
|-
|$00C09C
|$40
|set via FIFO command in invasn (len=06)
|Unknown. crusnexo writes here frequently. Values seen:
:$000000 (crusnexo)
:$004000 (crusnexo)
:$020202 (crusnexo)
:$020204 (crusnexo)
:$021E0E (crusnexo)
:$024004 (crusnexo)
|-
|-
|$00C0A3
|set via FIFO command in invasn (len=0A)
|}
|}


==== FIFO Commands ====
=== FIFO Commands ===
(copy/paste from Zeus; needs to be updated)
(copy/paste from Zeus; needs to be updated)


Line 348: Line 449:
Several FIFO commands appear to be able to write back to registers. Most registers are written either directly (via accessing memory $880000-$8803FF) or indirectly (via FIFO commands), but a few are accessed in both ways, and match up reasonably well. From what I can tell, only the low 64 registers (from $000-$07F) can be written via the FIFO. This makes sense as the registers higher up are less frequently accessed and appear to be primarily for control.
Several FIFO commands appear to be able to write back to registers. Most registers are written either directly (via accessing memory $880000-$8803FF) or indirectly (via FIFO commands), but a few are accessed in both ways, and match up reasonably well. From what I can tell, only the low 64 registers (from $000-$07F) can be written via the FIFO. This makes sense as the registers higher up are less frequently accessed and appear to be primarily for control.


===== FIFO Command $00 =====
==== FIFO Command $00 ====
{| border="1"
{| border="1"
! Bit||31||30||29||28||27||26||25||24||23||22||21||20||19||18||17||16||15||14||13||12||11||10||09||08||07||06||05||04||03||02||01||00
! Bit||31||30||29||28||27||26||25||24||23||22||21||20||19||18||17||16||15||14||13||12||11||10||09||08||07||06||05||04||03||02||01||00
Line 367: Line 468:
Set an internal pointer register. The low part of the first word (''WHICHPTR'') specifies which internal pointer to set and probably some mode bits. It is not known if these pointers are associated with registers or if they are only accessible internally. The second word specifies the actual pointer, in block form, providing an explicit ''LENGTH'' of 1-64 blocks, and separate ''ROW'' and ''COLUMN'' addresses.
Set an internal pointer register. The low part of the first word (''WHICHPTR'') specifies which internal pointer to set and probably some mode bits. It is not known if these pointers are associated with registers or if they are only accessible internally. The second word specifies the actual pointer, in block form, providing an explicit ''LENGTH'' of 1-64 blocks, and separate ''ROW'' and ''COLUMN'' addresses.


===== FIFO Command $13 =====
=== Model Commands ===
{| border="1"
! Bit||31||30||29||28||27||26||25||24||23||22||21||20||19||18||17||16||15||14||13||12||11||10||09||08||07||06||05||04||03||02||01||00
|- align="center"
| '''Word 0'''
| colspan="8"| $13
| colspan="24"| 0
|}
Render a model which has been previously configured by the internal data pointers. Invasion uses this; MK4 does not appear to do it this way. It is unknown if anything other than 0 is valid in the low 24 bits of the command word.
 
===== FIFO Command $17 =====
{| border="1"
! Bit||31||30||29||28||27||26||25||24||23||22||21||20||19||18||17||16||15||14||13||12||11||10||09||08||07||06||05||04||03||02||01||00
|- align="center"
| '''Word 0'''
| colspan="8"| $17
| ?
| colspan="7"| ''REGNUM''
| colspan="16"| ''REGDATA''
|}
Write a 16-bit value to a register. The register index ''REGNUM'' is specified in bits 16-22, allowing for references to registers $000-$07F. Odd register numbers write to the top 16 bits. Even register numbers write to the low 16 bits. (Does this make sense for registers $070,$072,$074 which write X,Y,Z coordinates for the rain effect in MK4?) The data to be written is specified in the low 16 bits ''REGDATA''.
 
===== FIFO Command $18 =====
{| border="1"
! Bit||31||30||29||28||27||26||25||24||23||22||21||20||19||18||17||16||15||14||13||12||11||10||09||08||07||06||05||04||03||02||01||00
|- align="center"
| '''Word 0'''
| colspan="8"| $18
| ?
| colspan="7"| ''REGNUM''
| colspan="16"| 0
|- align="center"
| '''Word 1'''
| colspan="32"| ''REGDATA''
|}
Write a 32-bit value to a register. The register index ''REGNUM'' specified in bits 16-22, allowing for references to registers $000-$07F. It is unknown if anything other than 0 means anything in the low half of the first word. The second word contains the 32-bit ''REGDATA'' value to write.
 
===== FIFO Command $1A =====
{| border="1"
! Bit||31||30||29||28||27||26||25||24||23||22||21||20||19||18||17||16||15||14||13||12||11||10||09||08||07||06||05||04||03||02||01||00
|- align="center"
| '''Word 0'''
| colspan="8"| $1A
| colspan="24"| 0
|}
Synchronize the pipeline, or something. These are scattered throughout the data.
 
===== FIFO Command $1C =====
{| border="1"
! Bit||31||30||29||28||27||26||25||24||23||22||21||20||19||18||17||16||15||14||13||12||11||10||09||08||07||06||05||04||03||02||01||00
|- align="center"
| '''Word 0'''
| colspan="8"| $1C
| colspan="8"| 0
| colspan="16"| ''MATRIX[0,2]''
|- align="center"
| '''Word 1'''
| colspan="16"| ''MATRIX[1,2]''
| colspan="16"| ''MATRIX[2,2]''
|- align="center"
| '''Word 2'''
| colspan="16"| ''MATRIX[0,1]''
| colspan="16"| ''MATRIX[0,0]''
|- align="center"
| '''Word 3'''
| colspan="16"| ''MATRIX[1,1]''
| colspan="16"| ''MATRIX[1,0]''
|- align="center"
| '''Word 4'''
| colspan="16"| ''MATRIX[2,1]''
| colspan="16"| ''MATRIX[2,0]''
|- align="center"
| '''Word 5'''
| colspan="32"| ''XOFFSET''
|- align="center"
| '''Word 6'''
| colspan="32"| ''YOFFSET''
|- align="center"
| '''Word 7'''
| colspan="32"| ''ZOFFSET''
|}
Set rotation matrix and translation vector. This command specifies a rotation matrix to be applied to all models rendered, along with a translation vector. The 3x3 ''MATRIX'' is a set of 16-bit 1.1.14 values packed two to a word. The translation vector (''XOFFSET'',''YOFFSET'',''ZOFFSET'') is specified as a set of 32-bit values in 1.15.16 format.
 
{| border="1"
! Bit||31||30||29||28||27||26||25||24||23||22||21||20||19||18||17||16||15||14||13||12||11||10||09||08||07||06||05||04||03||02||01||00
|- align="center"
| '''Word 0'''
| colspan="8"| $1C
| colspan="8"| 0
| colspan="16"| $7FFF
|- align="center"
| '''Word 1'''
| colspan="16"| ''MATRIXA[1,0]''
| colspan="16"| ''MATRIXA[0,0]''
|- align="center"
| '''Word 2'''
| colspan="16"| ''MATRIXA[1,1]''
| colspan="16"| ''MATRIXA[0,1]''
|- align="center"
| '''Word 3'''
| colspan="16"| ''MATRIXA[1,2]''
| colspan="16"| ''MATRIXA[0,2]''
|- align="center"
| '''Word 4'''
| colspan="16"| ''MATRIXB[0,1]''
| colspan="16"| ''MATRIXB[0,0]''
|- align="center"
| '''Word 5'''
| colspan="16"| ''MATRIXB[0,2]''
| colspan="16"| ''MATRIXA[2,0]''
|- align="center"
| '''Word 6'''
| colspan="16"| ''MATRIXB[1,2]''
| colspan="16"| ''MATRIXA[2,1]''
|- align="center"
| '''Word 7'''
| colspan="16"| ''MATRIXB[2,2]''
| colspan="16"| ''MATRIXA[2,2]''
|- align="center"
| '''Word 8'''
| colspan="16"| ''MATRIXB[1,1]''
| colspan="16"| ''MATRIXB[1,0]''
|- align="center"
| '''Word 9'''
| colspan="16"| ''MATRIXB[2,1]''
| colspan="16"| ''MATRIXB[2,0]''
|- align="center"
| '''Word 10'''
| colspan="32"| ''XOFFSET''
|- align="center"
| '''Word 11'''
| colspan="32"| ''YOFFSET''
|- align="center"
| '''Word 12'''
| colspan="32"| ''ZOFFSET''
|}
This is an extended form of the command which accepts two matrices. The low 16 bits of the command word are set to $7FFF to indicate that we are using the extended form. The two matrices contain 16-bit values in 1.0.15 format, and are multiplied together (''MATRIXB'' x ''MATRIXA''), taking the top 16 bits to give a 1.1.14 value just like the single matrix form. The translation vector is the same format as the regular form.
 
===== FIFO Command $23/$2E =====
{| border="1"
! Bit||31||30||29||28||27||26||25||24||23||22||21||20||19||18||17||16||15||14||13||12||11||10||09||08||07||06||05||04||03||02||01||00
|- align="center"
| '''Word 0'''
| colspan="8"| $23 or $2E
| colspan="8"| 0
| colspan="16"| XVECTOR
|- align="center"
| '''Word 1'''
| colspan="16"| YVECTOR
| colspan="16"| ZVECTOR
|}
Specify a vector. It is unknown what the purpose of this vector is, though it is possible that it is a point light source.
 
===== FIFO Command $25/$30 =====
{| border="1"
! Bit||31||30||29||28||27||26||25||24||23||22||21||20||19||18||17||16||15||14||13||12||11||10||09||08||07||06||05||04||03||02||01||00
|- align="center"
| '''Word 0'''
| colspan="8"| $25 or $30
| colspan="24"| ''VALUE1''
|- align="center"
| '''Word 1'''
| colspan="16"| ''VALUE2''
| colspan="16"| ''VALUE3''
|- align="center"
| '''Word 2'''
| colspan="16"| ''VALUE4''
| colspan="16"| ''VALUE5''
|- align="center"
| '''Word 3'''
| colspan="16"| ''VALUE6''
| colspan="16"| ''VALUE7''
|}
Some sort of control. Bit 7 of ''VALUE1'' is toggled on each frame. Does this select the page? The other data is completely unknown. It is also strange that MK4 uses $25 and Invasion uses $30, but they both appear to be the same commands. With MK4 the first word is usually $25000000, $25000080, $25008000, or $25800000. The latter two cases expect a total of 10 data words, while the former two expect a total of 4 data words.
 
===== FIFO Command $2D/$70 =====
{| border="1"
! Bit||31||30||29||28||27||26||25||24||23||22||21||20||19||18||17||16||15||14||13||12||11||10||09||08||07||06||05||04||03||02||01||00
|- align="center"
| '''Word 0'''
| colspan="8"| $2D or $70
| colspan="24"| ''UNKNOWN''
|}
This command is rarely used and its purpose is unknown.
 
===== FIFO Command $67 =====
{| border="1"
! Bit||31||30||29||28||27||26||25||24||23||22||21||20||19||18||17||16||15||14||13||12||11||10||09||08||07||06||05||04||03||02||01||00
|- align="center"
| '''Word 0'''
| colspan="8"| $25 or $30
| colspan="8" | 0
| colspan="16"| ''UNKNOWN''
|- align="center"
| '''Word 1'''
| x||x
| colspan="6"| ''MLENGTH''
| x
| colspan="11"| ''MROW''
| x||x||x
| colspan="9"| ''MCOLUMN''
|- align="center"
| '''Word 2'''
| colspan="6"| ''TEXOFFSHI''
| colspan="10"| ''TEXMODE''
| colspan="16"| ''TEXOFFSLO''
|}
Render a model stored in Wave RAM. ''UNKNOWN'' may or may not mean anything. The second word specifies a pointer to model data in wave RAM, using the block form. The texture data is specified in the second word. ''TEXMODE'' describes a texture mode, including information on the texture data width and format. ''TEXOFFSHI'' combines with ''TEXOFFSLO'' to produce the texture row for rendering.
 
==== Model Commands ====
(copy/paste from Zeus; needs to be updated)
(copy/paste from Zeus; needs to be updated)


Line 583: Line 475:
It is possible that the commands below are related to the FIFO commands (it would make sense). However, there is not a perfect overlap and at least one conflict.
It is possible that the commands below are related to the FIFO commands (it would make sense). However, there is not a perfect overlap and at least one conflict.


===== Model Command $08 =====
==== Model Command $08 ====
{| border="1"
{| border="1"
! Bit||31||30||29||28||27||26||25||24||23||22||21||20||19||18||17||16||15||14||13||12||11||10||09||08||07||06||05||04||03||02||01||00
! Bit||31||30||29||28||27||26||25||24||23||22||21||20||19||18||17||16||15||14||13||12||11||10||09||08||07||06||05||04||03||02||01||00
Line 596: Line 488:
Indicates end of model data.
Indicates end of model data.


===== Model Command $0C =====
=== Textures ===
{| border="1"
! Bit||31||30||29||28||27||26||25||24||23||22||21||20||19||18||17||16||15||14||13||12||11||10||09||08||07||06||05||04||03||02||01||00
|- align="center"
| '''Word 0'''
| colspan="8"| $0C
| colspan="24"| ''WHICHPTR''
|- align="center"
| '''Word 1'''
| x||x
| colspan="6"| ''LENGTH''
| x
| colspan="11"| ''ROW''
| x||x||x
| colspan="9"| ''COLUMN''
|}
Set an internal pointer register. This is similar to the $00/$01 FIFO command. Note that for models that require more than 64 8-byte chunks of data, this command can be used to chain to another chunk of data by specifying a=$008000 and b=pointer to the next chunk.
 
===== Model Command $17 =====
{| border="1"
! Bit||31||30||29||28||27||26||25||24||23||22||21||20||19||18||17||16||15||14||13||12||11||10||09||08||07||06||05||04||03||02||01||00
|- align="center"
| '''Word 0'''
| colspan="8"| $17
| ?
| colspan="7"| ''REGNUM''
| colspan="16"| ''REGDATA''
|- align="center"
| '''Word 1'''
| colspan="32"| 0
|}
Set a 16-bit register value. This is similar to the $17 FIFO command.
 
===== Model Command $19 =====
{| border="1"
! Bit||31||30||29||28||27||26||25||24||23||22||21||20||19||18||17||16||15||14||13||12||11||10||09||08||07||06||05||04||03||02||01||00
|- align="center"
| '''Word 0'''
| colspan="8"| $19
| ?
| colspan="7"| ''REGNUM''
| colspan="16"| 0
|- align="center"
| '''Word 1'''
| colspan="32"| ''REGDATA''
|}
Set a 32-bit register value. This is similar to the $18 FIFO command.
 
===== Model Command $25/$30 =====
{| border="1"
! Bit||31||30||29||28||27||26||25||24||23||22||21||20||19||18||17||16||15||14||13||12||11||10||09||08||07||06||05||04||03||02||01||00
|- align="center"
| '''Word 0'''
| colspan="8"| $25 or $30
| colspan="8"| ''ZNORMQ''
| colspan="8"| ''YNORMQ''
| colspan="8"| ''XNORMQ''
|- align="center"
| '''Word 1'''
| colspan="16"| ''TEXPARAMS''
| colspan="16"| ''TEXOFFS''
|- align="center"
| '''Word 2'''
| colspan="16"| ''Y1''
| colspan="16"| ''X1''
|- align="center"
| '''Word 3'''
| colspan="8"| ''V1''
| colspan="8"| ''U1''
| colspan="16"| ''Z1''
|- align="center"
| '''Word 4'''
| colspan="16"| ''Y2''
| colspan="16"| ''X2''
|- align="center"
| '''Word 5'''
| colspan="8"| ''V2''
| colspan="8"| ''U2''
| colspan="16"| ''Z2''
|- align="center"
| '''Word 6'''
| colspan="16"| ''Y3''
| colspan="16"| ''X3''
|- align="center"
| '''Word 7'''
| colspan="8"| ''V3''
| colspan="8"| ''U3''
| colspan="16"| ''Z3''
|- align="center"
| '''Word 8'''
| colspan="16"| ''Y4''
| colspan="16"| ''X4''
|- align="center"
| '''Word 9'''
| colspan="8"| ''V4''
| colspan="8"| ''U4''
| colspan="16"| ''Z4''
|- align="center"
| '''Word 10'''
| colspan="2"| 0
| colspan="10"| ''ZNORM1''
| colspan="10"| ''YNORM1''
| colspan="10"| ''XNORM1''
|- align="center"
| '''Word 11'''
| colspan="2"| 0
| colspan="10"| ''ZNORM2''
| colspan="10"| ''YNORM2''
| colspan="10"| ''XNORM2''
|- align="center"
| '''Word 12'''
| colspan="2"| 0
| colspan="10"| ''ZNORM3''
| colspan="10"| ''YNORM3''
| colspan="10"| ''XNORM3''
|- align="center"
| '''Word 13'''
| colspan="2"| 0
| colspan="10"| ''ZNORM4''
| colspan="10"| ''YNORM4''
| colspan="10"| ''XNORM4''
|}
Specify a quad for rendering. ''XNORMQ'', ''YNORMQ'', and ''ZNORMQ'' describe a 1.0.7 unit vector for the polygon normal. Note that in some cases this isn't properly normalized, but I suspect lighting is disabled when this is the case.
 
''TEXPARAMS'' control texturing. The upper byte is usually either $02, $21, or $23; it is unknown what this means. Bit 7 of ''TEXPARAMS'' is sometimes set to one; its purpose is likewise unknown. Bit 0 of ''TEXPARAMS'', when set, means that color 0 in the texture data is transparent. The 16-bit ''TEXOFFS'' is a texture V offset. In most quads, the smallest V is normalized to 0 and this is an offset. MK4 uses it as a true offset, specifying a base texture address in the $67 command. Invasion uses this as the full offset, using the $13 command which does not specify a base texture address. In this case, assuming the base == 0 seems to work.
 
(''X1'', ''Y1'', ''Z1'') describe the coordinates of the first vertex, in 1.13.2 format. (Note that 1.13.2 &times; 1.1.14 (matrix value) gives 1.14.16 which can be directly added to the 1.15.16 translation vector.) The texture coordinates of the first vertex are specified by (''U1'', ''V1''). This pattern is repeated for the remaining 3 vertices.
 
The final four words appear to be packed vertex normals for each vertex. Each 32-bit word contains three 10-bit packed values in 1.1.8 format. The top two bits appear to be unused.
 
An interesting note here is that this command intersects the FIFO command $25/$30 perfectly. That is, MK4 uses $25 in the FIFO and $25 in the model data. Invasion uses $30 in the FIFO and $30 in the model data. This bolsters the case that the commands are related.
 
==== Textures ====
(copy/paste from Zeus; needs to be updated)
(copy/paste from Zeus; needs to be updated)



Latest revision as of 19:30, 29 December 2007

The "Zeus 2" was the successor to the original Midway Zeus 3D chip which powered games such as Mortal Kombat 4 and Invasion. This page describes the current state of understanding of the chip, based on reverse engineering the games.

Games

There are two known games running on the Zeus 2 hardware:

  • Cruis'n Exotica
  • The Grid

Main CPU

The main CPU driving the Zeus 2 is, like the original Zeus, a TMS32C032 DSP. It is assumed to run at 60 MHz like the original, although this has yet to be confirmed.

Zeus 2 3D Graphics

As with the original Zeus, the Zeus 2 chip is memory mapped into the main CPU's address space at addresses from $880000-$88007F. Keep in mind that the TMS32C032 only accesses 32-bit memory, so each of the 128 addresses is 32 bits wide (i.e., address $880000 references one 32-bit word, and address $880001 references a completely independent 32-bit word).

As with most external chips, the memory map for the Zeus consists of a number of registers, in this case 128 registers. Unlike the original Zeus, the Zeus 2 appears to only operate in full 32-bit mode.

Wave RAM

The Zeus 2 chip provides access to two banks of local RAM, called "Wave RAM" in the diagnostic tests. Wave RAM is organized into two separate banks, one for 3D rendering data, and one for the framebuffer. Most references to Wave RAM are done in terms of what are suspected to be row/column addresses.

Bank 0 of Wave RAM is used to store model data, texture data, palettes, and other rendering information. It is organized as 2048 rows by 1024 columns. Each "cell" of bank 0 contains 8 bytes, giving 2k × 1k × 8 = 16MB of RAM. Data is stored sequentially as little-endian 32-bit words (two words are latched and written together into a single cell).

Bank 1 of Wave RAM holds the frame buffer (2 pages). It is organized as 1024 rows by 512 columns. Each "cell" of bank 1 contains 12 bytes, giving 1k × 0.5k × 12 = 6MB of RAM. Frame buffer data is stored as 32-bit RGB data, with a 16-bit depth component. The data is packed such that two 32bpp pixels live in their own words, while the two corresponding 16-bit depth values are packed into a 3rd 32-bit word and stored within the same cell.

Wave RAM is generally addressed in row/column format, in the following format:

Bit 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
x x x x x ROW x x x x x x COLUMN

Register Map

Address R/W Purpose
$00 R Status register. Bit $00000020 is looped on until set.
$01 R Status register. Bits $000C0070 are looped on until clear. Bit $00000004 is looped on as it toggles (VBLANK?)
$07 R ID register. Read at startup to verify the chip. Needs to return 1 bits in all of the following: $10451998.
$08 W Data FIFO (see FIFO commands, below)
$10 W Unknown. crusnexo writes $00000601, $00000641, $00000523 at startup, then leaves as $0000062F during gameplay. thegrid writes $00000601, $00000623, then leaves as $0000062F during gameplay.
$11 W Unknown. crusnexo/thegrid write $000007FF at startup.
$12 W Unknown. crusnexo/thegrid write $00200100 at startup.
$13 W Unknown. crusnexo/thegrid write $00020000, then write to registers $2F and $11, before writing a $00000028 during startup.
$18 R/W Unknown. thegrid writes $00000000 at startup. thegrid also reads this register and stores it in memory, though it does not appear to be used anywhere.
$19 W Unknown. thegrid writes $00000000 at startup.
$20 W Write to a 24-bit sub-register. See the Sub-Registers section below for details. The sub-register number is specified in the upper 8 bits, and the data lives in the lower 24 bits.
$22 W Unknown. crusnexo/thegrid write $00BA9807 at startup.
$23 W Unknown. crusnexo/thegrid write $00654321 at startup.
$24 W Unknown. crusnexo writes $00000601, then a bunch of other registers, before writing $0000000F during startup. thegrid writes $00000601, then a bunch of other registers, before writing $00000001 during startup.
$2A W Unknown. crusnexo/thegrid write $00000000 at startup.
$2B W Unknown. crusnexo/thegrid write $00000000 at startup.
$2C W Unknown. crusnexo/thegrid write $00000003 at startup.
$2D W Unknown. crusnexo/thegrid write $00000070 at startup.
$2F W Unknown. crusnexo writes $0000008C, then writes a bunch of registers, following by writing $0000004C, $0000004C, $0000001C in sequence during startup. thegrid just writes $0000008C and then $0000001C.
$30 W Unknown. crusnexo/thegrid write $0000A18F at startup.
$31 W Unknown. crusnexo writes $000520C4 at startup. thegrid writes $000523C4 at startup.
$32 W Horizontal sync begin (upper 16 bits?). crusnexo writes $002E0015 at startup. thegrid writes $00420015.
$33 W Horizontal blank end (upper 16 bits). Horizontal sync end (lower 16 bits?). crusnexo/thegrid write $0090004C at startup.
$34 W Horizontal total (upper 16 bits) and horizontal blank begin (lower 16 bits). crusnexo/thegrid write $029A0290 at startup.
$35 W Vertical sync start (upper 16 bits?) and vertical blank start (lower 16 bits). crusnexo/thegrid write $0192018F at startup.
$36 W Vertical unknown (upper 16 bits?) and vertical sync end (lower 16 bits?). crusnexo/thegrid write $01B60195 at startup.
$37 W Vertical total (lower 16 bits). crusnexo/thegrid write $000001B6 at startup.
$38 W Wave RAM address of screen base. crusnexo/thegrid toggle between $00000000 and $01900000.
$39 W Unknown. crusnexo/thegrid write $C0FF3010 at startup.
$3A W Unknown. crusnexo/thegrid write $00000000 at startup.
$40 W Wave RAM bank 0 access control. crusnexo writes $00800000 while setting up for access. It writes $00890000 before performing a write access. It writes $00820000 before performing a read access via a manual latch. It writes $00A20000 before performing a read that doesn't require manual latching. Values seen:
$00000000 (crusnexo/thegrid): reset to this
$00510000 (crusnexo/thegrid)
$005500FF (thegrid)
$00800000 (crusnexo/thegrid): set to this while setting up for direct access
$00820000 (thegrid): set to this before performing direct reads using the latch
$0084003F (crusnexo)
$00890000 (crusnexo/thegrid): set to this before performing direct writes
$005500FF (crusnexo)
$00A20000 (crusnexo): set to this before performing reads with automatic latching
$3855006F (crusnexo)
$38550070 (crusnexo)
$38550075 (crusnexo)
$38550083 (crusnexo)
$38550088 (crusnexo)
$38850077 (thegrid)
$C085003F (crusnexo)
$41 W Wave RAM bank 0 access address and manual latch. Normally, this is the address used for direct accesses to Wave RAM. In some cases (when register $40 is set to $00820000), a write here does not alter the address (the value written is ignored), but rather triggers a read from Wave RAM into the transfer registers at the previously programmed address. When register $40 is set to $00A20000, a write here causes an immediate transfer from Wave RAM into the transfer registers.
$47 W Wave RAM bank 0 write control. crusnexo/thegrid write $0000000F at startup.
$48-49 R/W Wave RAM bank 0 transfer latches. Data that is read from Wave RAM due to an access to register $41 appears here. Data is also written here prior to being pushed out to Wave RAM. Depending on the value of register $4E, writes to this register may actually trigger a flush from the transfer registers to Wave RAM.
$4C W Unknown. crusnexo/thegrid write $00000000, followed by writes to registers $47 and $4F, followed by $3D530000, $3CC90000, $3CC90000, $3C430000 in sequence, followed by a write to register $4D, followed by $000355E at startup.
$4D W Unknown. crusnexo/thegrid write $60990006, then $E0990006 at startup.
$4E W Wave RAM bank 0 mode control.

When performing a write with autoincrement, crusnexo writes $F2080020 at the start, then the address to register $41, then the mode to register $40, then $F2080030, then $F2080069, then does the writes to $48,$49. When finished, it resets the state to $F2080020.

Prior to performing writes with explicit address sets before each one, this register is set to $F2080028. Writes are done to $49 then $48.

When performing a read with autoincrement, it writes $F2080020 at the start, then the address to register $41, then the mode to register $40, then $F2080030, then $F2080061, then does the reads by doing a dummy write to $41 followed by reads from $48,$49. When finished, it resets the state to $F2080020.

Prior to performing reads with explicit address sets before each one, this register is set to $F2080021. Reads are done from $48 then $49.

Based on these sequences, the assumption is that the bits mean:

$00000003 = mask to select which transfer register, when accessed, triggers (in the write case) a flush from transfer registers to Wave RAM and also which transfer register triggers then autoincrement behavior
$00000008 = set for writes, clear for reads
$00000010 = set briefly after writing the address but before setting the final mode (crusnexo only)
$00000040 = set for autoincrement
$FFFF0000 = set to F208 (crusnexo/thegrid) or F20D (thegrid)

Values seen:

$F2080020 (crusnexo/thegrid): set to this prior to writing address ($41)
$F2080021 (crusnexo): set to this before performing reads with explicit addresses
$F2080028 (crusnexo): set to this before performing writes with explicit addresses
$F2080030 (crusnexo/thegrid): set to this after writing address ($41) and mode ($40) but before autoinc accesses
$F2080061 (thegrid): set to this before performing reads with an autoincrementing address
$F2080069 (crusnexo/thegrid): set to this before performing writes with autoincrementing address
$4F W Unknown. crusnexo/thegrid write $004F4F00 at startup.
$50 W Wave RAM bank 1 access control. Same as register $40, but for Wave RAM bank 1.

Values seen:

$00000000 (crusnexo)
$00510000 (crusnexo)
$00800000 (crusnexo/thegrid)
$00820000 (thegrid)
$00890000 (crusnexo/thegrid)
$00A20000 (crusnexo)
$007811FF (crusnexo)
$0078127F (crusnexo)
$007812FF (crusnexo)
... (inclusive repeating every $80)
$007814FF (crusnexo)
$007818FF (crusnexo)
$0078197F (crusnexo)
$007819FF (crusnexo)
... (inclusive repeating every $80)
$00782FFF (crusnexo)
$007831FF (crusnexo/thegrid)
$0079FFFF (thegrid)
$008831FF (thegrid)
$00E90000 (thegrid)
$51 R/W Wave RAM bank 1 access address and manual latch. Same as register $41, but for Wave RAM bank 1. This register is tested by writing a FIFO command that sets its value and then reading it back.
$54 R Video position. Both upper and lower 16 bits are read and treated like vertical beam position.
$57 W Wave RAM bank 1 write control. Same as register $47, but for Wave RAM bank 1. crusnexo writes $0000003F here. thegrid writes both $00000004 and $00000001 to control which of two individual pixels to write. Is this a write mask of some sort?
$58-5A R/W Wave RAM bank 1 transfer latches. Same as registers $48-49, but for Wave RAM bank 1. Note that there are 3 latches instead of 2, to account for the 3 words per cell.
$5C W Unknown. crusnexo writes $00000000, followed by a write to register $5F, followed by $FD530000, $FCC90000, $FCC90000, $FC430000, $00620000, $00620000 in sequence, followed by a write to register $5D, followed by $000355E at startup. thegrid does the same thing except the final value written is $0062355E.
$5D W Unknown. crusnexo/thegrid write $44180006, then $C4180006 at startup.
$5E W Wave RAM bank 1 mode control. Same as register $4E, but for Wave RAM bank 1.

Values seen:

$F2080000 (crusnexo)
$F2080010 (thegrid)
$F2080020 (crusnexo)
$F2080021 (crusnexo)
$F2080028 (crusnexo)
$F2080030 (crusnexo)
$F2080041 (thegrid)
$F2080048 (thegrid)
$F208004A (thegrid)
$F2080068 (crusnexo)
$F20D0000 (thegrid)
$F20D004A (thegrid)
$5F W Unknown. crusnexo/thegrid write $004F4F4F at startup.
$63 W Unknown. crusnexo/thegrid write $C47E0000 at startup.
$64 W Unknown. crusnexo/thegrid write $00000000 at startup.
$65 W Unknown. crusnexo/thegrid write $00000000 at startup.
$66 W Unknown. crusnexo writes $0000008E at startup. thegrid writes both $0000008E and $00000085.
$67 W Unknown. crusnexo/thegrid write $00000000 at startup.
$68 W Unknown. crusnexo/thegrid write $0000009D at startup.
$6A W Unknown. crusnexo writes $43800000 at startup. thegrid writes $43804000 at startup.
$6B W Unknown. crusnexo writes $43480000 at startup. thegrid writes $43488000 at startup.
$6C W Unknown. crusnexo writes $00000009 at startup, later changes to $00000001. thegrid writes $00000009 at startup, later changes to $00000000.
$6F W Unknown. crusnexo/thegrid write $00003FC0 at startup.
$76 W Unknown. crusnexo/thegrid write $44000000 at startup.
$77 W Unknown. crusnexo/thegrid write $43C80000 at startup.
$78 W Unknown. crusnexo/thegrid write $00000000 at startup.
$79 W Unknown. crusnexo writes $C47A0000 at startup. thegrid writes $C7435000 at startup.
$7A W Unknown. crusnexo/thegrid write $000080AA at startup.
$7C W Unknown. crusnexo/thegrid write $003F5F33 at startup.
$7D W Unknown. crusnexo writes $74037808 at startup. thegrid writes $740A7020 at startup.
$7F W Unknown. crusnexo/thegrid write $00000060 at startup.

Sub-Registers

In addition to the direct registers, there is a set of 24-bit "sub-registers" which are written to via direct register $20. When writing here, the sub-register number is specified in the top 8 bits, and the data is provided in the lower 24 bits.

Address Purpose
$00 Unknown. Always written as $000000, frequently during execution.
$01 Unknown. crusnexo writes $0001FF at startup. X min/max clip (in high 12 bits/low 12 bits)?
$02 Unknown. crusnexo writes $00018F at startup. Y min/max clip (in high 12 bits/low 12 bits)?
$03 Unknown. crusnexo writes $000000 at startup.
$04 Render base row. crusnexo alternates between $000000 and $000190 each frame. This value is always opposite that written to register $38.
$05 Texture base address. This is specified as a packed address, with the low 10 bits specifying the column and the upper 14 bits specifying the row.
$06 Unknown. crusnexo sometimes writes $000000 and sometimes writes $0000B1.
$07 Unknown. crusnexo writes $0000FF at startup.
$0C Unknown. crusnexo writes here frequently, specifying values ranging from $000001 to $000141. Alpha blending or fade value?
$0D Unknown. crusnexo writes here frequently, specifying values ranging from $000000 to $0000FF. Alpha blending or fade value?
$11 Unknown. crusnexo writes $008A53 at startup.
$12 Unknown. crusnexo writes $006009 at startup.
$14 Unknown. crusnexo writes here frequently. Values seen:
$001000 (crusnexo)
$004000 (crusnexo)
$004040 (crusnexo)
$004062 (crusnexo)
$004CA1 (crusnexo)
$15 Unknown. crusnexo writes here frequently, specifying either $000000, $0000FF, or $0007FF.
$40 Unknown. crusnexo writes here frequently. Values seen:
$000000 (crusnexo)
$004000 (crusnexo)
$020202 (crusnexo)
$020204 (crusnexo)
$021E0E (crusnexo)
$024004 (crusnexo)

FIFO Commands

(copy/paste from Zeus; needs to be updated)

Commands are written through the FIFO register ($0E0). There is a whole mechanism in the chip for overflowing the FIFO, returning status in the status register ($0F2) and signalling an interrupt when there is space freed up to continue pumping data. But so far we don't need to emulate that, since we are being simplistic and pretending the whole chip runs infinitely fast.

FIFO commands are a stream of 32-bit data. The top 8 bits (or is it 7 bits?) of the first 32-bit word indicate the command. Some commands require just a single 32-bit word, while others require multiple words to follow.

Several FIFO commands appear to be able to write back to registers. Most registers are written either directly (via accessing memory $880000-$8803FF) or indirectly (via FIFO commands), but a few are accessed in both ways, and match up reasonably well. From what I can tell, only the low 64 registers (from $000-$07F) can be written via the FIFO. This makes sense as the registers higher up are less frequently accessed and appear to be primarily for control.

FIFO Command $00

Bit 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
Word 0 $00 WHICHPTR
Word 1 x x LENGTH x ROW x x x COLUMN

Set an internal pointer register. The low part of the first word (WHICHPTR) specifies which internal pointer to set and probably some mode bits. It is not known if these pointers are associated with registers or if they are only accessible internally. The second word specifies the actual pointer, in block form, providing an explicit LENGTH of 1-64 blocks, and separate ROW and COLUMN addresses.

Model Commands

(copy/paste from Zeus; needs to be updated)

Model commands are stored as a stream of data in Wave RAM. A pointer to the model is specified either through FIFO command $67 or by writing command $00008000 and specifying the pointer to the model data there. The pointer to model data contains a 6-bit length in bits 24-29 and a row/column pointer in bits 0-23. The 6-bit length is the length of the model data in 8 byte chunks, minus 1. Thus, a length of $3F references 64 8-byte chunks.

It is possible that the commands below are related to the FIFO commands (it would make sense). However, there is not a perfect overlap and at least one conflict.

Model Command $08

Bit 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
Word 0 $08 0
Word 1 0

Indicates end of model data.

Textures

(copy/paste from Zeus; needs to be updated)

Texture references are a little baffling at the moment. It is clear that a texture is specified by the third word in FIFO command $67 (used by MK4), or in part by a write to register $06 (used by Invasion).

The format of the FIFO command word, based on analyzing the code, appears to be something like this:

Bit 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
A A A A A A B W W W D C C C C C A A A A A A A A A A A A A A A A

The various 'A' bits seem to go together to form an address of sorts (more on that in a minute).

The 'B' bit's purpose is unknown at this time.

The three 'W' bits control the width of the texture. The width appears to be 512 >> W, giving potential widths of 512, 256, 128, 64, 32, 16, 8, and 4.

The 'D' bit controls whether the texture data is 8bpp or 4bpp. All textures so far appear to be palettized, so a palette base must be set prior to rendering.

The purpose of the 5 'C' bits is unknown at this time.

So, back to the address. The weird thing about texture addressing is that the width specified by the 'W' bits seems to affect the texture address. In essence, if you treat Wave RAM as an array of bytes arranged with a width of 'W', the address 'A' is simply the row number.

W Width Wave RAM Address
0 512(?) 'A' << 9
1 256 'A' << 8
2 128 'A' << 7
3 64 'A' << 6
4 32 'A' << 5
5 16 'A' << 4
6 8 'A' << 3
7 4 'A' << 2