Midway Zeus 2

From MAMEDEV Wiki
Revision as of 23:13, 28 December 2007 by Aaron (talk | contribs)

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

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

Address R/W Purpose
$000 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).
$004 W This is a bitmask controlling some texture behaviors:
$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 W Low 16 bits specify texture mode
$008 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.
$00A 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.
$00C 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.
$00E 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.
$04E 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?

Also note that when rendering directly via a write to $060, this is set to $00808080.

$058 W Usually written after $05A; after writing, waits for read from $0F6 to return with bit 4 cleared
$05A W Usually written immediately before writing $058
$060 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.
$068 W MK4 written at startup: $00030000 = toggled on then off at startup

invasn writes via FIFO command $17 (both high and low) and via model command $19. Values seen = $40C41370

$070 W Seems to specify the X coordinate of a model (written as 16-bit from FIFO command $17)
$072 W Seems to specify the Y coordinate of a model (written as 16-bit from FIFO command $17)
$074 W Seems to specify the Z coordinate of a model (written as 16-bit from FIFO command $17)
$076 W invasn writes to this both directly and via FIFO command $18. Values seen = $F8800000
$078 W invasn writes to this via FIFO command $18. Values seen = $000000C7. Could be the screen X origin in the low 16 bits.
$07A W invasn writes to this via FIFO command $18. Values seen = $00800000. Could be the screen Y origin in the high 16 bits.
$07C W invasn writes to this via FIFO command $18. Values seen = $FFFFFFE0
$07E 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.
$080 W Master control; configures some core chip behavior:
$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 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)
$0B0 R/W Data port for direct RAM access
$0B2 R/W Data port for direct RAM access
$0B4 W Address for direct RAM access in expanded form
$0B6 W Direct RAM access control
$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 W Written at startup = $801F2500
$0C2 W Written at startup = $0015E591
$0C4 W Written at startup = $000C0042 (HSYNC start/HSYNC end?)
$0C6 W Written at startup = $0211007F (HTOTAL/HBLANK end?)
$0C8 W Written at startup = $010300FF (VSYNC start/VBLANK start?)
$0CA W Written at startup = $01160107 (VTOTAL/VSYNC end?)
$0CC W Written at startup = $00000000 (display framebuffer start address? toggles between $00000000 and $00800000)
$0CE W Written at startup = $00C87620
$0E0 W Data FIFO (see FIFO commands, below)
$0F0 R Current horizontal video beam position; used by invasn for reading the lightgun (low 11 bits)
$0F2 R Current vertical video beam position; used by invasn for reading the lightgun (low 10 bits)
$0F4 R Status register
$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 R Status register 2
$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 W Unknown, but written in a loop with master control bit $00000008 cleared

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

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

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:

WHICHPTR Purpose
$008000 pointer to model data to render via FIFO command $13
$018000 pointer to model data to render via FIFO command $13 (does the top byte matter?)
$00C040 pointer to palette to use for texture lookups
$004040 set via FIFO command in mk4 (len=02)
$02C0F0 set in model data in mk4 (len=0F)
$03C0F0 set via FIFO command in mk4 (len=00)
$02C0E7 set via FIFO command in mk4 (len=08)
$04C09C set via FIFO command in mk4 (len=08)
$05C0A5 set via FIFO command in mk4 (len=21)
$80C0A5 set via FIFO command in mk4 (len=3F)
$81C0A5 set via FIFO command in mk4 (len=35)
$82C0A5 set via FIFO command in mk4 (len=41)
$00C0F0 set via FIFO command in invasn (len=0F)
$00C0B0 set via FIFO command in invasn (len=3F) -- seems to be the same as C0A5
$05C0B0 set via FIFO command in invasn (len=21)
$00C09C set via FIFO command in invasn (len=06)
$00C0A3 set via FIFO command in invasn (len=0A)

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