The Dreamcast's architecture pairs a 200 MHz Hitachi SH-4 superscalar CPU with a NEC/VideoLogic PowerVR CLX2 tile-based GPU (embedded in the Holly ASIC), a Yamaha AICA sound processor running an independent ARM7DI core, and a proprietary GD-ROM drive—all interconnected through Holly's system bus. This reference documents every subsystem at the register and instruction level needed to build a static recompilation engine: hex addresses, binary encodings, bit layouts, and data formats.
The Dreamcast uses a 29-bit physical address space divided into eight 64MB areas, with the upper 3 bits of the 32-bit logical address controlling cache behavior and privilege. The MMU is disabled in normal operation, so logical-to-physical mapping is fixed. Every hardware register documented below uses P2 uncached addresses (prefix 0xA0000000) unless noted otherwise.
CHAPTER 01SH-4 CPU: instruction set, registers, and pipeline
The SH-4 (SH7091 variant at 200 MHz) is a 32-bit RISC processor with fixed 16-bit instruction encoding, a hardware FPU with vector extensions, and 2-way superscalar issue. It is the sole processor responsible for all game logic and 3D math—the PowerVR GPU performs no vertex transformation.
Register file
General-purpose registers (32-bit): R0–R15, where R0 has special roles in indexed addressing and some immediate-mode instructions. R15 is conventionally the stack pointer. R0–R7 are banked: two physical sets (BANK0 and BANK1) are selected by SR.RB, enabling fast exception entry without register saves.
Floating-point registers (32 physical banks of 16): FR0–FR15 (single-precision, active bank), XF0–XF15 (extended bank, the other bank). The FPSCR.FR bit swaps which physical bank is "front" (FR) vs. "back" (XF). Double-precision pairs: DR0 = {FR0,FR1}, DR2 = {FR2,FR3}, … DR14 = {FR14,FR15}. Four-component vectors: FV0 = {FR0–FR3}, FV4 = {FR4–FR7}, FV8 = {FR8–FR11}, FV12 = {FR12–FR15}. The back-bank matrix register XMTRX is a 4×4 matrix laid out column-major: column 0 = {XF0–XF3}, column 1 = {XF4–XF7}, column 2 = {XF8–XF11}, column 3 = {XF12–XF15}.
Control/system registers:
| Register | Purpose |
|---|---|
| SR | Status register (T, S, IMASK, Q, M, FD, BL, RB, MD bits) |
| GBR | Global base register for GBR-based addressing |
| VBR | Vector base register for exception handler addresses |
| MACH/MACL | Multiply-accumulate high/low (64-bit MAC result) |
| PR | Procedure register (return address for BSR/JSR/BSRF) |
| PC | Program counter |
| SSR/SPC | Saved SR and saved PC on exception entry |
| SGR | Saved R15 on exception entry |
| DBR | Debug base register |
| FPSCR | FPU status/control (FR, SZ, PR, DN, cause/enable/flag, RM) |
| FPUL | FPU communication register (integer↔float transfers) |
SR bit layout (reset value 0x700000F0)bit 0 = T (true/false condition), bit 1 = S (MAC saturation), bits 4–7 = IMASK (interrupt mask level 0–15), bit 8 = Q, bit 9 = M (divide step state), bit 12 = FD (FPU disable), bit 28 = BL (exception block), bit 29 = RB (register bank select), bit 30 = MD (0=user, 1=privileged).
FPSCR bit layout (reset value 0x00040001)bits 0–1 = RM (00=round-nearest, 01=round-zero), bits 2–6 = Flag (I,U,O,Z,V sticky), bits 7–11 = Enable (I,U,O,Z,V), bits 12–17 = Cause (I,U,O,Z,V,E—cleared before each FPU op), bit 18 = DN (denorm-as-zero), bit 19 = PR (0=single, 1=double), bit 20 = SZ (FMOV transfer size, 0=32-bit, 1=64-bit pair), bit 21 = FR (FP bank select). The combination SZ=1 + PR=1 is reserved and must never be set.
Instruction set architecture
All instructions are 16 bits wide. Fields commonly encode nnnn (destination register 0–15), mmmm (source register), dddddddd (displacement), iiiiiiii (immediate). The instruction set spans these categories:
Data transfer: MOV Rm,Rn (0110nnnnmmmm0011), MOV #imm,Rn (1110nnnniiiiiiii), MOV.B/W/L with all addressing modes (register indirect, pre-decrement @-Rn, post-increment @Rn+, indexed @(R0,Rn), displacement @(disp,Rn), GBR-based @(disp,GBR), PC-relative @(disp,PC)), MOVA (11000111dddddddd), MOVT, SWAP.B/W, XTRCT, LDS/STS (to/from FPUL, FPSCR, MACH, MACL, PR), LDC/STC (to/from SR, GBR, VBR, SSR, SPC, DBR, SGR, Rn_BANK).
Arithmetic: ADD, ADDC, ADDV, SUB, SUBC, SUBV, NEG, NEGC, MUL.L, MULS.W, MULU.W, DMULS.L, DMULU.L, MAC.L, MAC.W, DT (decrement-and-test), DIV0S, DIV0U, DIV1, CMP/EQ, CMP/GE, CMP/GT, CMP/HI, CMP/HS, CMP/PL, CMP/PZ, CMP/STR, CMP/EQ #imm, EXTS.B/W, EXTU.B/W.
Logic: AND, OR, XOR, TST, NOT—plus @(R0,GBR) variants for AND.B, OR.B, TST.B, XOR.B.
Shift: ROTL, ROTR, ROTCL, ROTCR, SHAL, SHAR, SHLL/SHLR (1/2/8/16), SHAD Rm,Rn (0100nnnnmmmm1100—arithmetic dynamic shift, new in SH-3/4), SHLD Rm,Rn (0100nnnnmmmm1101—logical dynamic shift, new in SH-3/4).
Branch: BF (10001011dddddddd), BT (10001001dddddddd), BF/S and BT/S (with delay slot), BRA (1010dddddddddddd), BSR, BRAF Rm (0000mmmm00100011—PC-relative far branch via register, new vs SH-1), BSRF Rm (0000mmmm00000011), JMP @Rm, JSR @Rm, RTS, RTE (0000000000101011). All unconditional branches and JMP/JSR/RTS/RTE have delay slots.
System: NOP, SLEEP, CLRT, SETT, CLRS, SETS, CLRMAC, TRAPA #imm (11000011iiiiiiii), LDTLB (0000000000111000—load TLB entry from PTEH/PTEL, new in SH-3/4).
Floating-point instructions (all new vs SH-2)
The SH-2 has no FPU. Every FP instruction below is an SH-4 addition. All FP opcodes begin with 1111 in bits [15:12].
| Instruction | Encoding | Operation | Latency | ||
|---|---|---|---|---|---|
| FADD FRm,FRn | 1111nnnnmmmm0000 | FRn + FRm → FRn | 3 (SP) / 8 (DP) | ||
| FSUB FRm,FRn | 1111nnnnmmmm0001 | FRn − FRm → FRn | 3 / 8 | ||
| FMUL FRm,FRn | 1111nnnnmmmm0010 | FRn × FRm → FRn | 3 / 8 | ||
| FDIV FRm,FRn | 1111nnnnmmmm0011 | FRn ÷ FRm → FRn | 10 / 23 | ||
| FMAC FR0,FRm,FRn | 1111nnnnmmmm1110 | FR0×FRm + FRn → FRn | 3 (SP only) | ||
| FCMP/EQ FRm,FRn | 1111nnnnmmmm0100 | (FRn==FRm) → T | 2 | ||
| FCMP/GT FRm,FRn | 1111nnnnmmmm0101 | (FRn>FRm) → T | 2 | ||
| FABS FRn | 1111nnnn01011101 | FRn | → FRn | 0 | |
| FNEG FRn | 1111nnnn01001101 | −FRn → FRn | 0 | ||
| FSQRT FRn | 1111nnnn01101101 | √FRn → FRn | 10 / 23 | ||
| FLOAT FPUL,FRn | 1111nnnn00101101 | int(FPUL) → float(FRn) | 3 | ||
| FTRC FRn,FPUL | 1111nnnn00111101 | trunc(FRn) → int(FPUL) | 3 | ||
| FCNVDS DRm,FPUL | 1111mmm010111101 | double→single (PR=1) | 3 | ||
| FCNVSD FPUL,DRn | 1111nnn010101101 | single→double (PR=1) | 3 | ||
| FLDS FRm,FPUL | 1111mmmm00011101 | FRm → FPUL | 0 | ||
| FSTS FPUL,FRn | 1111nnnn00001101 | FPUL → FRn | 0 | ||
| FLDI0 FRn | 1111nnnn10001101 | 0.0f → FRn | 0 | ||
| FLDI1 FRn | 1111nnnn10011101 | 1.0f → FRn | 0 | ||
| FIPR FVm,FVn | 1111nnmm11101101 | 4D dot product → FR[n+3] | 4 (pitch 1) | ||
| FTRV XMTRX,FVn | 1111nn0111111101 | 4×4 matrix × vector → FVn | 7 (pitch 4) | ||
| FRCHG | 1111101111111101 | Toggle FPSCR.FR (bank swap) | 1 | ||
| FSCHG | 1111001111111101 | Toggle FPSCR.SZ (transfer size) | 1 | ||
| FSRRA FRn | 1111nnnn01111101 | 1/√FRn → FRn (approx) | 1 | ||
| FSCA FPUL,DRn | 1111nnn011111101 | sin/cos(FPUL) → DR (16-bit fixed angle) | 3 |
FMAC is the cornerstone single-precision multiply-accumulate: FR0 × FRm + FRn → FRn with full 28-bit internal precision. FIPR computes a 4-component inner product in 4 cycles. FTRV multiplies XMTRX (the back-bank 4×4 matrix) by FVn in 7 cycles—this is the critical instruction for vertex transformation. FSRRA and FSCA are technically SH-4A instructions but are present on the Dreamcast's SH7091 silicon and widely used by games.
Cache control and other SH-4 additions over SH-2
Beyond the entire FPU, these instructions are new to SH-4
- PREF @Rn (
0000nnnn10000011) — Prefetch 32-byte cache line to operand cache - OCBI @Rn (
0000nnnn10010011) — Operand cache block invalidate - OCBP @Rn (
0000nnnn10100011) — OC block purge (writeback + invalidate) - OCBWB @Rn (
0000nnnn10110011) — OC block writeback (keep valid) - MOVCA.L R0,@Rn (
0000nnnn11000011) — Move with cache block allocate (no read-fill) - SHAD/SHLD — Dynamic shift by register amount (SH-3/4)
- BRAF/BSRF — PC-relative far branches (added SH-2, absent in SH-1)
- LDTLB — Load TLB from PTEH/PTEL/PTEA
Addressing modes
| Mode | Syntax | Access sizes | Notes |
|---|---|---|---|
| Register direct | Rn | — | Source/dest is register value |
| Register indirect | @Rn | B/W/L | EA = Rn |
| Post-increment | @Rn+ | B/W/L | EA = Rn, then Rn += size |
| Pre-decrement | @-Rn | B/W/L | Rn -= size, then EA = Rn |
| Displacement | @(disp,Rn) | B(4-bit)/W(4-bit×2)/L(4-bit×4) | R0-only for B/W |
| Indexed | @(R0,Rn) | B/W/L | EA = R0 + Rn |
| GBR+disp | @(disp,GBR) | B(8-bit)/W(×2)/L(×4) | R0-only dest for loads |
| PC-relative | @(disp,PC) | W(8-bit×2)/L(8-bit×4) | For loading constants |
| Immediate | #imm | 8-bit signed (MOV), 8-bit (logic) | Zero-extended for logic ops |
Superscalar pipeline
The SH-4 has a 5-stage pipeline (IF → ID → EX → MA → WB) and can dual-issue by classifying instructions into groups: MT (move/trivial—can pair with anything), EX (execute-stage ALU), LS (load/store), FE (FP execute), BR (branch), CO (co-issue prohibited—always single-issue). Two instructions from different groups can issue together. CO-group instructions (LDC, STC, LDS, STS, TRAPA, RTE, SLEEP, MAC.L, MAC.W) always execute alone.
Memory-mapped control registers (P4 area, 0xFFxxxxxx)
| Address | Register | Description |
|---|---|---|
0xFF000000 | PTEH | Page table entry high (TLB) |
0xFF000004 | PTEL | Page table entry low |
0xFF000008 | TTB | Translation table base |
0xFF00000C | TEA | TLB exception address |
0xFF000010 | MMUCR | MMU control register |
0xFF000014 | BASRA | Break ASID A (UBC) |
0xFF000018 | BASRB | Break ASID B |
0xFF00001C | CCR | Cache control register |
0xFF000020 | TRA | TRAPA exception register (bits [9:2] = imm8) |
0xFF000024 | EXPEVT | Exception event code (12-bit) |
0xFF000028 | INTEVT | Interrupt event code (12-bit) |
0xFF000030 | PTEA | Page table entry assistance |
0xFF000034 | QACR0 | Store queue address control 0 |
0xFF000038 | QACR0 | (correction: QACR0 at 0x38) |
0xFF00003C | QACR1 | Store queue address control 1 |
CCR (0xFF00001C) bit layoutbit 0 = OCE (OC enable), bit 1 = WT (write-through), bit 2 = CB (copy-back P1), bit 3 = OCI (OC invalidate), bit 5 = ORA (OC-as-RAM enable for 8KB+8KB split), bit 7 = OIX (OC index mode), bit 8 = ICE (IC enable), bit 11 = ICI (IC invalidate), bit 15 = IIX (IC index mode).
Store queues at 0xE0000000–0xE3FFFFFFtwo 32-byte buffers (SQ0 at bit[5]=0, SQ1 at bit[5]=1). Filled with MOV.L writes, flushed via PREF @Rn. Upper physical address bits come from QACR0/QACR1. Critical for burst-writing display lists to the TA FIFO.
Exception handling
Exception entrySR→SSR, PC→SPC, R15→SGR, set SR.MD=1, SR.RB=1, SR.BL=1, write exception code to EXPEVT or INTEVT, branch to VBR + offset. Offsets: +0x100 for general exceptions, +0x400 for TLB miss, +0x600 for interrupts. Reset vectors are fixed at 0xA0000000 (power-on) and 0xA0000020 (manual). Key EXPEVT codes: 0x040/0x060 = TLB miss read/write, 0x0E0/0x100 = address error read/write, 0x120 = FPU exception, 0x160 = TRAPA, 0x180 = illegal instruction, 0x800 = FPU disable.
CHAPTER 02PowerVR CLX2 (Holly): tile-based deferred rendering
How TBDR works
Unlike immediate-mode renderers that process triangles in submission order and rely on a Z-buffer to resolve visibility (causing massive overdraw), the PowerVR divides the screen into 32×32 pixel tiles and performs per-tile hidden surface removal (HSR) before texturing. This means the texture shading processor (TSP) only processes the visible fragment per pixel—zero overdraw. The process has two asynchronous phases that can overlap across frames:
Binning phase (Tile Accelerator): The CPU submits geometry as global parameters + vertex parameters to the TA FIFO at physical address 0x10000000. The TA sorts each primitive into the tiles it overlaps, building per-tile Object Pointer Buffers (OPBs) and an Object Buffer (OB) in VRAM.
Rendering phase (ISP → TSP): Triggered by writing to STARTRENDER (0xA05F8014). For each tile: the ISP (Image Synthesis Processor) fetches the tile's primitives, performs depth testing and HSR to find the visible surface at each pixel, and the TSP (Texture Shading Processor) applies texturing, shading, and blending only to visible fragments. The completed tile is written to the framebuffer in VRAM. This per-tile architecture means the working set fits in on-chip SRAM, delivering extremely high effective fill rates.
Three rendering lists
- Opaque (PVR_LIST_OP_POLY): Front-to-back HSR, only the nearest fragment is textured. No alpha blending. Most efficient list.
- Translucent (PVR_LIST_TR_POLY): Supports alpha blending. The hardware performs order-independent transparency—automatically sorting translucent fragments per-pixel and blending back-to-front. Slower than opaque but avoids manual sorting.
- Punch-through (PVR_LIST_PT_POLY): 1-bit alpha test against PT_ALPHA_REF. Pixels either fully opaque or fully discarded. Used for cutout textures (foliage, chain-link fences).
Each list also has optional modifier volume variants (opaque modifier, translucent modifier) for stencil-like per-pixel shadow effects using inclusion/exclusion volumes.
Tile Accelerator parameter format
Parameters are submitted as 32-byte aligned blocks to the TA FIFO at 0x10000000 (or 0xB0000000 via P2). Two main types:
Global parameters (32 bytes): Set polygon state for subsequent vertices. Word 0 is a parameter control word encoding: bits [31:29] = parameter type (0=end-of-list, 4=polygon/modifier, 7=vertex), bits [26:24] = list type (0=opaque, 1=opaque modifier, 2=translucent, 3=translucent modifier, 4=punch-through), bits [22:21] = group/strip control. Subsequent words contain: ISP/TSP instruction word (depth compare mode, culling, Z-write), TSP instruction word (texture filtering, UV clamp/flip, fog, alpha), texture control word (address in VRAM, format, mip, VQ, twiddled, size).
Vertex parameters (32 bytes): Position as 3 × 32-bit IEEE floats (X, Y, Z where Z is 1/W for perspective correction), packed ARGB color (32-bit), base/offset colors, UV texture coordinates (32-bit float or 16-bit packed). Bit 28 in the parameter control word signals end-of-strip (last vertex of a triangle strip).
End-of-list: A special 32-byte parameter (type field = 0) marks the end of the current polygon list.
Texture formats
| Format | Code | Description |
|---|---|---|
| ARGB1555 | 0 | 16bpp, 1-bit alpha + RGB555 |
| RGB565 | 1 | 16bpp, no alpha |
| ARGB4444 | 2 | 16bpp, 4-bit alpha + RGB444 |
| YUV422 | 3 | 16bpp, luminance/chroma video format |
| Bump map | 4 | 16bpp, S/R environment-mapped |
| 4BPP palette | 5 | 4bpp, indexed into 1024-entry palette RAM |
| 8BPP palette | 6 | 8bpp, indexed into palette RAM |
Twiddled textures store pixels in Morton order (Z-order curve), interleaving U and V coordinate bits. This dramatically improves 2D spatial locality for texture cache access. All power-of-two textures should be twiddled; paletted textures are always twiddled.
VQ (Vector Quantization) compression stores a 2048-byte codebook of 256 entries, each a 2×2 texel block (4 pixels × 2 bytes at 16bpp). The image data is a twiddled array of 8-bit indices into this codebook, achieving approximately 8:1 compression for 16bpp textures. Decoded in hardware at zero performance cost.
Stride textures support non-power-of-two widths (multiples of 32, up to 992). Width is set globally via TSP_CFG register (0xA05F80E4) bits [4:0].
PVR core registers (base 0xA05F8000)
| Address | Register | Description |
|---|---|---|
0xA05F8000 | PVR_ID | Chip ID (0x17FD11DB) |
0xA05F8004 | PVR_REVISION | Chip revision |
0xA05F8008 | SOFTRESET | Bit 0=TA reset, Bit 1=PVR reset, Bit 2=VRAM bus reset |
0xA05F8014 | STARTRENDER | Write to trigger ISP/TSP rendering |
0xA05F8020 | ISP_VERTBUF_ADDR | Vertex buffer base (OB) in VRAM |
0xA05F802C | ISP_TILEMAT_ADDR | Tile matrix (OPB) base in VRAM |
0xA05F8030 | SPANSORT_CFG | Span sort config (write 0x101) |
0xA05F8040 | BORDER_COL | Border color RGB888 |
0xA05F8044 | FB_DISPLAY_CFG | Display mode, pixel format, clock double, display enable |
0xA05F8048 | FB_RENDER_CFG | Render target format, dithering |
0xA05F804C | FB_RENDER_MODULO | Render stride = (bpp × width) / 8 |
0xA05F8050 | FB_DISPLAY_ADDR1 | Framebuffer read address (odd/non-interlaced) |
0xA05F8054 | FB_DISPLAY_ADDR2 | Framebuffer read address (even field) |
0xA05F805C | FB_DISPLAY_SIZE | [29:20]=modulo, [19:10]=height−1, [9:0]=width in 32-bit words−1 |
0xA05F8060 | FB_RENDER_ADDR1 | Render output address |
0xA05F8064 | FB_RENDER_ADDR2 | Render output address (even field) |
0xA05F8068 | FB_CLIP_X | Horizontal clipping: [26:16]=max, [10:0]=min |
0xA05F806C | FB_CLIP_Y | Vertical clipping |
0xA05F8074 | CHEAP_SHADOW | Bit 8=enable, [7:0]=shadow intensity |
0xA05F8078 | OBJECT_CLIP | Polygon cull distance (IEEE float) |
0xA05F807C | FPU_PARAM_CFG | Object buffer config, autosort mode |
0xA05F8088 | BGPLANE_Z | Background plane depth (IEEE float) |
0xA05F808C | BGPLANE_CFG | BG config: ISP/TSP mode, VRAM address |
0xA05F8098 | ISP_FEED_CFG | Sort mode (0=autosort, 1=presorted) |
0xA05F80B0 | FOG_TABLE_COL | Table fog color |
0xA05F80B4 | FOG_VERTEX_COL | Vertex fog color |
0xA05F80B8 | FOG_DENSITY | Fog density coefficient |
0xA05F80BC | COLOR_CLAMP_MAX | Color clamp ceiling |
0xA05F80C0 | COLOR_CLAMP_MIN | Color clamp floor |
0xA05F80CC | VPOS_IRQ | Raster position interrupt (top/bottom) |
0xA05F80D0 | SYNC_CFG | Video output enable, interlace, broadcast std |
0xA05F80D4 | HBORDER | Horizontal border range |
0xA05F80D8 | SYNC_LOAD | Refresh/scanline counts |
0xA05F80DC | VBORDER | Vertical border range |
0xA05F80E4 | TSP_CFG | Texture modulo width, VQ endian |
0xA05F80F4 | SCALER_CFG | Horizontal/vertical scaling |
0xA05F8108 | PALETTE_CFG | Palette format (0=ARGB1555, 1=RGB565, 2=ARGB4444, 3=ARGB8888) |
0xA05F810C | SYNC_STAT | VBlank/HBlank status, current scanline |
0xA05F8124 | TA_OPB_START | TA Object Pointer Buffer start address |
0xA05F8128 | TA_OB_START | TA Object Buffer start address |
0xA05F812C | TA_OPB_END | TA OPB end limit |
0xA05F8130 | TA_OB_END | TA OB end limit |
0xA05F8134 | TA_OPB_POS | Current OPB write position (read-only) |
0xA05F8138 | TA_OB_POS | Current OB write position (read-only) |
0xA05F813C | TILEBUF_SIZE | Tile matrix dimensions (in 32px tiles) |
0xA05F8140 | TA_OPB_CFG | Per-list OPB allocation sizes, enables |
0xA05F8144 | TA_INIT | Bit 31: initialize TA vertex registration |
0xA05F8148 | YUV_ADDR | YUV converter destination in VRAM |
0xA05F814C | YUV_CFG | YUV format and dimensions |
0xA05F8164 | TA_OPL_INIT | OPL start address (reinit) |
0xA05F8200–83FF | FOG_TABLE | 128-entry fog density LUT |
0xA05F9000–9FFF | PALETTE_TABLE | 1024 palette entries |
8MB VRAM layout
VRAM occupies physical 0x04000000–0x047FFFFF (8MB) in two 4MB banks. The 32-bit area (0xA5000000–0xA57FFFFF via P2) provides sequential access—used for framebuffers. The 64-bit area (0xA4000000–0xA47FFFFF) interleaves banks every 4 bytes for double bandwidth—used for textures. Typical allocation: two framebuffers (~614KB each at 640×480×16bpp), two tile-buffer sets (OPB+OB), texture memory occupying the remainder. Total usable texture space depends on resolution and buffering strategy.
CHAPTER 03Yamaha AICA: 64-channel sound with ARM7DI core
Architecture overview
The AICA integrates a 64-channel PCM/ADPCM synthesizer, a 128-step programmable DSP, and an ARM7DI processor (no Thumb, no hardware multiplier) running at approximately 45 MHz with 2MB dedicated sound RAM. The ARM7 executes audio driver code independently from the SH-4—it has no built-in firmware and must be loaded by the main CPU.
AICA registers from SH-4 (physical addresses in Area 0)
| Address Range | Content |
|---|---|
0x00700000–0x00707FFF | AICA control registers (32KB) |
0x00710000–0x0071000B | AICA RTC registers |
0x00800000–0x009FFFFF | Sound RAM (2MB) |
Via P20xA0700000 for registers, 0xA0800000 for sound RAM.
Channel registers (64 channels × 0x80 bytes = 0x2000 bytes at offset 0x0000)
Each channel occupies 0x80 bytes starting at base + (channel × 0x80)
| Offset | Register | Description |
|---|---|---|
0x00 | PlayControl | Key on/off (bits 15–14), loop (bit 9), format (bits 8–7: 0=16-bit PCM, 1=8-bit PCM, 2=4-bit ADPCM), sample addr high |
0x04 | SampleAddrLow | Lower 16 bits of sample start address in sound RAM |
0x08 | LoopStart | Loop start position (samples) |
0x0C | LoopEnd | Loop end / total sample length |
0x10 | AmpEnv1 | Attack rate, Decay1 rate, Decay2 rate |
0x14 | AmpEnv2 | Release rate, decay level, key rate scaling |
0x18 | SampleRatePitch | OCT (bits 15–11, signed −8 to +7), FNS (bits 10–0, frequency number) |
0x1C | LFOControl | LFO frequency, pitch/amplitude modulation waveform and depth |
0x20 | DSPChannelSend | DSP channel routing and send level |
0x24 | DirectPanVolSend | Direct volume (4-bit), pan (5-bit) |
0x28 | LPF1Volume | Main volume (8-bit, 0xFF=silent), filter enable and Q |
0x2C–0x44 | LPF2–LPF8 | Per-envelope-stage filter frequency and rates |
Key system registers
| Offset | Register | Description |
|---|---|---|
0x2800 | MasterVolume | Master volume (bits 3–0), mono/stereo mode |
0x2890 | TimerA | Timer A start value |
0x28A8 | ARMClock | ARM7 clock speed setting |
0x28B4 | INTEnable | Write 0x20 to enable SH-4 interrupts |
0x28B8 | INTSend | Write 0x20 to raise SH-4 interrupt |
0x2C00 | ARMReset | Bit 0: 1=hold ARM7 in reset, 0=release |
0x2D00 | INTRequest | Current interrupt number |
0x3000–0x31FC | DSP program (128 steps) | DSP microprogram memory |
0x3200–0x32FC | DSP coefficients | 64 coefficient registers |
0x3400–0x43FF | DSP data/temp RAM | Working memory for DSP effects |
SH-4 ↔ ARM7 communication
The SH-4 loads the ARM7 audio driver into sound RAM (0x00800000), then clears bit 0 of ARMReset (0x2C00) to release the ARM7. The ARM7 boots from address 0x00000000 in its local space (= start of sound RAM). Communication uses shared mailbox regions in sound RAM—the SH-4 writes commands and audio data; the ARM7 reads them and controls the 64 channels. Interrupts can be sent bidirectionally via the INTSend/INTRequest registers.
CHAPTER 04Memory architecture and address map
Physical address space (29-bit, 8 × 64MB areas)
| Physical Range | Content | Size |
|---|---|---|
0x00000000–0x001FFFFF | Boot ROM (mask ROM) | 2MB |
0x00200000–0x0023FFFF | Flash ROM (settings/saves) | 256KB |
0x005F6800–0x005F69FF | Holly system bus (SB) registers | 512B |
0x005F6C00–0x005F6CFF | Maple interface registers | 256B |
0x005F7000–0x005F70FF | GD-ROM registers (G1 bus) | 256B |
0x005F7400–0x005F74FF | G1 interface / GD-ROM DMA registers | 256B |
0x005F7800–0x005F78FF | G2 interface registers | 256B |
0x005F7C00–0x005F7CFF | PVR interface registers | 256B |
0x005F8000–0x005F9FFF | PVR core / TA registers | 8KB |
0x00700000–0x00707FFF | AICA control registers | 32KB |
0x00710000–0x0071000B | AICA RTC registers | 12B |
0x00800000–0x009FFFFF | Sound RAM | 2MB |
0x04000000–0x047FFFFF | VRAM (64-bit interleaved access) | 8MB |
0x05000000–0x057FFFFF | VRAM (32-bit sequential access) | 8MB |
0x0C000000–0x0CFFFFFF | Main system RAM | 16MB |
0x10000000–0x107FFFFF | TA polygon converter FIFO | — |
0x10800000–0x10FFFFFF | TA YUV converter FIFO | — |
0x11000000–0x11FFFFFF | Texture memory direct access | — |
0x14000000–0x17FFFFFF | Expansion (modem) port | 64MB area |
Logical address regions (32-bit, SH-4 with MMU off)
| Range | Name | P | ALT | NC | Properties |
|---|---|---|---|---|---|
0x00000000–0x7FFFFFFF | U0/P0 | 0 | × | × | User/priv, cached, MMU if enabled |
0x80000000–0x9FFFFFFF | P1 | 1 | 0 | 0 | Priv only, cached (copy-back/through) |
0xA0000000–0xBFFFFFFF | P2 | 1 | 0 | 1 | Priv only, uncached (for HW regs) |
0xC0000000–0xDFFFFFFF | P3 | 1 | 1 | 0 | Priv only, cached, MMU if enabled |
0xE0000000–0xFFFFFFFF | P4 | 1 | 1 | 1 | SH-4 on-chip I/O, store queues |
Games typically access main RAM at 0x8C000000 (P1, cached) or 0x0C000000 (P0), and hardware registers at 0xA05Fxxxx (P2, uncached).
Bus architecture
The SH-4 connects to main RAM directly via a 100 MHz, 64-bit SDRAM bus (two 32-bit banks). The SH-4 connects to Holly via a 100 MHz multiplexed address/data bus (MPX mode). Holly's internal root bus connects to: PVR core (with 64-bit 100 MHz VRAM bus), G1 bus (GD-ROM, Boot ROM, Flash—16-bit), G2 bus (AICA, modem, expansion—16-bit), and Maple interface (serial). All DMA is arbitrated by Holly's bus arbiter.
Key DMA channels: SH-4 DMAC has 4 channels (SAR/DAR/DMATCR/CHCR registers at 0xFFA00000–0xFFA0003C per channel, DMAOR at 0xFFA00040). Holly provides additional DMA: Ch2-DMA for PVR (0xA05F6800), Sort-DMA (0xA05F6810), GD-ROM DMA (0xA05F7404), AICA DMA (via G2), Maple DMA (0xA05F6C04).
CHAPTER 05GD-ROM: proprietary disc format and controller
The GD-ROM (Gigadisc) is a dual-density proprietary disc format. The low-density area (inner tracks, ~35MB) is standard CD-readable and contains a warning audio track and small data track. A security ring separates it from the high-density area (outer tracks, ~984MB at double density), which contains the game data and is unreadable by standard CD drives. The drive reads using CAV (Constant Angular Velocity) at 4× in the low-density area and 6–12× in the high-density area, with a 128KB buffer. The file system is ISO 9660 with Joliet extensions.
GD-ROM registers (G1 bus, ATAPI-like)
| Address | Register | Description |
|---|---|---|
0xA05F7018 | Alt Status | Alternate status (no interrupt clear) |
0xA05F7080 | Data | 16-bit PIO data transfer |
0xA05F7084 | Error/Features | Error (read) / Features (write) |
0xA05F7088 | IntReason/SectorCount | Interrupt reason / sector count |
0xA05F708C | SectorNumber | Sector number |
0xA05F7090 | ByteCountLow | Byte count low |
0xA05F7094 | ByteCountHigh | Byte count high |
0xA05F7098 | DriveSelect | Drive select |
0xA05F709C | Status/Command | Status (read) / Command (write) |
ATAPI commands0xA0 (PACKET—sends 12-byte command packet), 0x08 (soft reset), 0x90 (diagnostic), 0xA1 (identify). GD-ROM DMA registers at 0xA05F7404 (start address), 0xA05F7408 (length), 0xA05F7414 (enable), 0xA05F7418 (status).
Boot sequence
Boot ROM at 0xA0000000 → authenticates disc → loads IP.BIN (32KB from high-density area FAD ~45150) to 0x8C008000 → IP.BIN contains meta info (hardware ID "SEGA SEGAKATANA", product number, boot filename), SEGA license screen code, and two customizable bootstrap stages → reads 1ST_READ.BIN (filename from IP.BIN offset 0x60) from ISO 9660 filesystem, descrambles it, loads to 0x8C010000 → IP.BIN displays SEGA logo → bootstrap sets up stack, VBR, disables cache → jumps to 0x8C010000.
CHAPTER 06Maple bus: controller protocol and peripherals
Protocol fundamentals
Maple is a proprietary 2-wire serial bus at ~2 Mbps connecting 4 controller ports, each supporting 1 main peripheral + up to 5 sub-peripherals (expansion slots). All Maple transactions are initiated by the SH-4 through Holly's DMA controller—the CPU writes a linked list of transfer descriptors to main RAM, points the Maple DMA register at it, and triggers the transfer.
Maple DMA registers
| Address | Register | Description |
|---|---|---|
0xA05F6C04 | SB_MDSTAR | DMA command table pointer (physical addr, 32-byte aligned) |
0xA05F6C10 | SB_MDTSEL | DMA trigger select |
0xA05F6C14 | SB_MDEN | DMA enable |
0xA05F6C18 | SB_MDST | DMA start (bit 0, auto-clears on completion) |
0xA05F6C80 | SB_MSYS | System control (timing, speed) |
Frame format
Each Maple frame consists of a 32-bit header word followed by 0–255 data words
| Byte (in word) | Field |
|---|---|
| Byte 0 (bits 7–0) | Data word count (additional payload words) |
| Byte 1 (bits 15–8) | Sender address |
| Byte 2 (bits 23–16) | Recipient address |
| Byte 3 (bits 31–24) | Command code |
Address encoding: Bits [7:6] = port (0=A, 1=B, 2=C, 3=D). Bit 5 = main peripheral. Bits [4:0] = sub-peripheral select (one-hot). Host addresses: 0x00, 0x40, 0x80, 0xC0. Main peripheral addresses: 0x20, 0x60, 0xA0, 0xE0. Sub-peripherals: 0x01/0x02/0x04 (port A), etc.
Command codes
| Code | Command | Direction |
|---|---|---|
0x01 | Device Info Request | Host → Device |
0x02 | Extended Device Info Request | Host → Device |
0x03 | Reset Device | Host → Device |
0x04 | Shutdown Device | Host → Device |
0x05 | Device Info (response) | Device → Host |
0x06 | Extended Device Info (response) | Device → Host |
0x07 | Acknowledge | Device → Host |
0x08 | Data Transfer (response) | Device → Host |
0x09 | Get Condition | Host → Device |
0x0A | Get Memory Information | Host → Device |
0x0B | Block Read | Host → Device |
0x0C | Block Write | Host → Device |
0x0E | Set Condition | Host → Device |
0xFC | Request Resend | Any |
0xFD | Unknown Command (error) | Device → Host |
0xFE | Function Not Supported | Device → Host |
0xFF | No Response | — |
Function codes (device capabilities)
| Code | Function | Used by |
|---|---|---|
0x00000001 | Controller | Standard pad |
0x00000002 | Storage | VMU memory card |
0x00000004 | Screen (LCD) | VMU display |
0x00000008 | Timer | VMU clock |
0x00000010 | Audio Input | Microphone |
0x00000020 | AR Gun | — |
0x00000040 | Keyboard | DC keyboard |
0x00000080 | Light Gun | Light gun |
0x00000100 | Vibration | Purupuru (rumble) pack |
0x00000200 | Mouse | DC mouse |
Controller data format (Get Condition response)
The standard controller returns condition data with inverted button logic (0 = pressed, 1 = released)
| Bit | Button | Bit | Button | |
|---|---|---|---|---|
| 0 | C | 8 | Z | |
| 1 | B | 9 | Y | |
| 2 | A | 10 | X | |
| 3 | Start | 11 | D | |
| 4 | D-pad Up | 12 | Up2 | |
| 5 | D-pad Down | 13 | Down2 | |
| 6 | D-pad Left | 14 | Left2 | |
| 7 | D-pad Right | 15 | Right2 |
Following the 16-bit button wordR trigger (8-bit, 0–255), L trigger (8-bit, 0–255), analog stick X (8-bit, 0=left, 128=center, 255=right), analog stick Y (8-bit, 0=up, 128=center, 255=down), then second stick X/Y for dual-stick controllers.
VMU (Visual Memory Unit)
The VMU contains a Sanyo LC86K87 8-bit CPU, 128KB flash (200 usable blocks × 512 bytes), a 48×32 monochrome LCD (viewed as 48 wide × 32 tall), 4-direction D-pad + A/B/MODE/SLEEP buttons, and a CR2032 battery. It exposes three Maple functions: Storage (0x00000002) for block read/write of save data, Screen (0x00000004) for LCD bitmap writes (192 bytes = 48×32 pixels at 1bpp), and Timer (0x00000008) for the real-time clock. Block reads/writes use commands 0x0B/0x0C with a location word encoding partition, phase, and block number.
The Purupuru (Rumble) Pack exposes function 0x00000100 and is controlled via Set Condition (0x0E) with vibration intensity, duration, and frequency parameters.
CHAPTER 07Conclusion
The Dreamcast's architecture presents several unique challenges for static recompilation. The SH-4's 16-bit fixed-width instructions simplify decoding but its delay slots on all unconditional branches, FP register banking (FPSCR.FR toggling two 16-register banks), and dual-issue superscalar scheduling require careful modeling. The vector instructions FIPR and FTRV operate across register banks—FTRV reads the back bank as a matrix—making bank-swap tracking essential.
The PowerVR's tile-based architecture means the GPU has no equivalent to a traditional draw call pipeline—all geometry flows through the TA FIFO as parameter blocks, and rendering is deferred per-tile. Emulating this requires either full TBDR simulation or converting the TA parameter stream into modern GPU API calls with manual tile management. The store queue mechanism (0xE0000000 + PREF) is the primary fast path for TA submission and must be correctly emulated.
The AICA's independent ARM7 core means a recompiler must run two instruction-set decoders and manage cross-CPU communication through shared sound RAM. The Maple bus adds a third protocol layer with its own DMA descriptor format. All three subsystems operate asynchronously—timing-accurate synchronization between the SH-4, ARM7, PVR rendering, and Maple DMA polling is the central challenge of any Dreamcast recompilation engine.
CHAPTER 08I/O Dispatch Reference (Final Synthesis)
AICA Audio Registers (G2-bus, $00700000 base)
The AICA is the Dreamcast's descendant of the Saturn SCSP — same Yamaha lineage but with 64 channels (vs 32), an ARM7DI coprocessor (vs 68EC000), and 2 MB sound RAM (vs 512 KB). Registers are accessed via G2-bus from the SH-4 at physical $00700000 (P2: $A0700000).
Per-Channel Registers (64 channels × $80 bytes at $00701000 + slot×$80):
| Offset | Name | Bits | Description |
|---|---|---|---|
| +$00 | KYONB/PlayCtrl | bit0=KEY_ON trigger, [8:7]=PCMS (0=PCM16, 1=PCM8, 2=ADPCM4), [22:0]=SA high | Key control + sample format + address high |
| +$04 | SA_LO | [15:0]=SA low | Sample address low (address = SA >> 1 for byte offset) |
| +$08 | LSA | [15:0]=loop start | Loop start position (samples) |
| +$0C | LEA | [15:0]=loop end | Loop end / total sample length |
| +$10 | AmpEnv1 | AR(4:0), D1R(4:0), D2R(4:0) | EG attack/decay1/decay2 rates (5-bit each) |
| +$14 | AmpEnv2 | RR(4:0), DL(4:0), KRS(3:0), LPSLNK | Release rate, decay level, key rate scaling |
| +$18 | Pitch | OCT[15:11] (signed −8..+7), FNS[10:0] | Octave + frequency number |
| +$1C | LFO | LFOF(4:0), pitch/amp waveform & depth | LFO control |
| +$20 | DSPSend | DSP channel routing + send level | Effect bus routing |
| +$24 | DirectPan | DISDL(3:0), DIPAN(4:0) | Direct volume (4-bit) + pan (5-bit) |
| +$28 | EffectPan | EFSDL(3:0), EFPAN(4:0) | Effect send (4-bit) + effect pan (5-bit) |
| +$2C | TL | TL[7:0] | Total level (8-bit attenuation, 0=max, 0xFF=silent) |
Read EG[12:0]/SGC at channel offset for current envelope state.
AICA Channel Setup Pseudocode:
void aica_channel_setup(uint8 slot, uint32 sa, uint16 lsa, uint16 lea,
uint8 pcms, uint16 fns, int8 oct,
uint8 tl, uint8 disdl, uint8 dipan) {
uint32 base = 0x00701000 + slot * 0x80;
write32(base + KYONB_OFF, 0); // clear key-on
write32(base + SA_OFF, sa >> 1); // sample address (byte→word)
write16(base + LSA_OFF, lsa);
write16(base + LEA_OFF, lea);
write8(base + PCMS_OFF, pcms); // 0=PCM16, 1=PCM8, 2=ADPCM4
write16(base + FNS_OFF, fns);
write8(base + OCT_OFF, oct & 0xF);
write8(base + AR_OFF, 0x1F); // max attack rate
write8(base + TL_OFF, tl);
write8(base + DISDL_OFF, disdl);
write8(base + DIPAN_OFF, dipan);
write8(base + EFSDL_OFF, 0xF); // max effect send
write32(base + KYONEX_OFF, 1); // trigger key-on
}Global Registers ($00702800+):
| Address | Name | R/W | Bits | Description |
|---|---|---|---|---|
| $00702800 | MVOL | RW | [15:12]=MN (mono), [11]=M8, [5:0]=MVOL (0=−∞..15=0 dB). Read returns VER[3:0] | Master volume. Read returns hardware version ID |
| $00702804 | Ringbuf | RW | [14:13]=RBL (0=8K..3=64K words), [12:3]=RBP | DSP ring buffer config. Write starts effect chain |
| $00702890 | TIMA | RW | Timer A start value (8-bit + prescaler) | Timer A reload value |
| $0070289C | SCIEB | RW | [10:0] IRQ enable bits (DMA=4, TimerA/B/C=6–8) | Sound CPU interrupt enable |
| $007028A0 | SCIPD | R | [10:0] interrupt pending bits | Sound CPU interrupt pending |
| $007028A4 | SCIRE | WO | [10:0] | Sound CPU interrupt reset — write-1-to-clear |
| $007028B4 | MCIEB | RW | [10:0] IRQ enable bits | Main CPU (SH-4) interrupt enable |
| $007028B8 | MCIPD | R | [10:0] interrupt pending bits | Main CPU interrupt pending |
| $007028BC | MCIRE | WO | [10:0] | Main CPU interrupt reset — write-1-to-clear |
| $00702C00 | ARMRST | RW | bit 0: 1=hold ARM7 in reset, 0=release | ARM7 reset control. Set after DMA-loading driver to wave RAM ($00800000), then clear to boot |
Timer Overflow → IRQ Chain: Timer A/B/C overflow → sets SCIPD bit → if SCIEB enabled → ARM7 interrupt. If MCIEB enabled → MCIPD set → Holly ISTNRM sound request → SH-4 interrupt at VBR+$600.
DSP Effect Chain (128-step MPRO at $00703400):
64-bit instruction encodingMASA[5:0], IRA/TWA/TRA/EWA, MRD/MWT, YSEL/XSEL, SHFT, NEGB. Ring buffer formula: (MADRS + ADRS_REG + MDEC_CT + NXADR) mod RBL_size. Coefficients at $00703200 (64 entries). Data/temp RAM at $00703400–$000043FF.
Standard effectsHall reverb = 34 steps, Chorus = 22 steps (LFO on delay line). Representative COEF values (no commercial dumps exist publicly): COEF[0]=0x7FFF (gain),
0x4000- (damp), etc.
Dynarec C++ Driver Pattern (from Flycast — x64/ARM64 with dirty-flag + fallback):
class AICA_DSP {
bool dirty = true;
void* dynarec_code = nullptr;
public:
void Run(u32 samples) {
if (dirty || MPRO_changed) { dsp_recompile(); dirty = false; }
for (u32 i = 0; i < samples; i++) {
if (dynarec_enabled) ((void(*)())dynarec_code)();
else runStep(); // interpreter fallback
}
}
void dsp_recompile() { /* emit x64/ARM64 for 128-step MPRO */ }
};GD-ROM Controller Registers (G1-bus, $A05F7000 base)
ATAPI Registers (directly mapped):
| Address | Name | R/W | Description |
|---|---|---|---|
| $A05F7018 | Alt Status | R | Alternate status (read does NOT clear interrupt) |
| $A05F7080 | Data | RW | 16-bit PIO data transfer |
| $A05F7084 | Error/Features | RW | Error (R) / Features (W): bit 0=DMA mode |
| $A05F7088 | IntReason | RW | Interrupt reason / sector count |
| $A05F708C | SectorNumber | RW | Sector number |
| $A05F7090 | ByteCountLow | RW | Byte count low |
| $A05F7094 | ByteCountHigh | RW | Byte count high |
| $A05F7098 | DriveSelect | RW | Drive select |
| $A05F709C | Status/Command | RW | Status (R): BSY/DRDY/DRQ/ERR bits. Command (W): $A0=PACKET, $08=soft reset |
GD-ROM DMA Registers:
| Address | Name | R/W | Description |
|---|---|---|---|
| $A05F7404 | GDST_ADDR | RW | DMA destination address in main RAM |
| $A05F7408 | GDST_LEN | RW | DMA transfer length (in 32-byte units) |
| $A05F740C | GDST_DIR | RW | DMA direction |
| $A05F7414 | GDST_EN | W | DMA enable — writing 1 starts transfer |
| $A05F7418 | GDST_STAT | R | DMA status: bit 0 = active. Auto-clears on completion. Triggers ISTNRM[14] |
GD-ROM DMA Read Protocol (high-density sector read):
void gdrom_dma_read(uint32 fad, uint32 sectors, void* buf) {
write32(0xA05F7404, 0x01); // FEATURES=DMA mode
write32(0xA05F7408, sectors);
write32(0xA05F709C, 0xA0); // ATAPI PACKET command
// Send 12-byte CDB: cmd $30 (READ_CD), format, FAD, count
uint8 cdb[12] = {0x30, 0x0C,
(fad>>24), (fad>>16), (fad>>8), fad,
0x00, (sectors>>8), sectors, 0x00, 0x00, 0x00};
for (int i = 0; i < 12; i += 2)
write16(0xA05F7018, (cdb[i]<<8) | cdb[i+1]);
// Configure DMA
write32(0xA05F7404, (uint32)buf);
write32(0xA05F7408, sectors * 2048 / 32);
write32(0xA05F740C, 1); // direction
write32(0xA05F7414, 1); // enable → starts transfer
write32(0xA05F7418, 1); // initiate
while (read32(0xA05F7418) & 1); // poll or use ISTNRM[14]
}High-Density Access: GET_TOC command ($14, byte1=1) to switch to GD area.
Sense Key Table + Recovery:
- $05/$21 (illegal FAD) → re-GET_TOC
- $03/$11 (medium error) → CD_OPEN retry
- $0B/$4E (aborted) → SOFT_RESET
KOS-style IRQ Driver:
static void gdrom_irq_callback(uint32 status) {
if (status & (1<<14)) gdrom_dma_done = true;
else if (status & ISTERR_MASK)
gdrom_kos_timed_recovery(cdrom_get_sense());
}Maple Bus DMA Registers ($A05F6C00 base)
| Address | Name | R/W | Description |
|---|---|---|---|
| $A05F6C04 | SB_MDSTAR | RW | DMA command table pointer (physical, 32-byte aligned) |
| $A05F6C10 | SB_MDTSEL | RW | DMA trigger select |
| $A05F6C14 | SB_MDEN | RW | DMA enable |
| $A05F6C18 | SB_MDST | RW | DMA start: bit 0 write=start, auto-clears on completion. Read bit 0 = busy |
| $A05F6C80 | SB_MSYS | RW | System control (timing, speed) |
DMA Descriptor Format (32-byte aligned in main RAM):
| Word | Bits | Description |
|---|---|---|
| 0 | [31]=LAST, [30:27]=PORT, [26]=GUN, [25:0]=LENGTH | Control + transfer length |
| 1 | [31:0] | Receive buffer address |
Lightgun Support: Set GUN=1 + LAST=1 in descriptor word 0 → 8 SDCKB clock pulses →
hardware latches H/V position to PVR register $A05F80C4 (H[31:16], V[15:0]). CRC variants
0xFF- or 0x00 initial XOR.
VMU Operations (controller + 5 sub-peripherals per port):
typedef struct {
uint32 cmd; // $0B=block_read, $0C=block_write
uint32 recipient; // $21 | (sub << 4)
uint32 sender;
uint32 length;
uint8 data[256];
} maple_frame;
int maple_vmu_op(uint8 port, uint8 pt, uint8 phase,
uint8 block, void* buf, bool write, uint8 sub) {
maple_frame f = {
.cmd = write ? 0x0C : 0x0B,
.recipient = 0x21 | (sub << 4),
// ...
};
maple_send_frame(&f);
for (uint8 s = 0; s < 5; s++) maple_queue_frame(&f);
while (!maple_done());
maple_response r; maple_parse_response(&r);
if (r.error & 0x20) { /* phase retry or bad-block remap */ }
uint8 crc = 0xFF;
for (each byte in data) crc ^= byte;
return crc;
}SH-4 DMAC + Store Queue
DMAC (4 channels at $FFA00000):
| Offset | Name | R/W | Description |
|---|---|---|---|
| +$00/+$10/+$20/+$30 | SAR0–3 | RW | Source address |
| +$04/+$14/+$24/+$34 | DAR0–3 | RW | Destination address |
| +$08/+$18/+$28/+$38 | DMATCR0–3 | RW | Transfer count |
| +$0C/+$1C/+$2C/+$3C | CHCR0–3 | RW | Channel control: DE (enable), TE (end), IE (IRQ enable), TS (transfer size), AR (auto-request), DM/SM (dest/src increment mode) |
| +$40 | DMAOR | RW | DMA operation register: DDM (round-robin), PR (priority), NMIF, AE (address error), DME (master enable — must be set before any transfer) |
Store Queue (TA Fast Path):
Two 32-byte buffers at $E0000000 (SQ0 at bit[5]=0, SQ1 at bit[5]=1). Fill with MOV.L writes, flush via PREF @Rn instruction. Upper physical address bits from QACR0/QACR1 ($FF000034/$FF00003C).
Primary useburst-write display lists to TA FIFO at physical $10000000 (64-bit transfer size). This is the critical fast path for geometry submission — all high-performance Dreamcast games use Store Queue → TA FIFO for polygon submission.
PVR Tile Accelerator (TA) Parameter Format
Parameter Control Word (PCW — Word 0):
| Bits | Name | Description |
|---|---|---|
| [31:29] | cmd | 4=POLY, 5=SPRITE, 7=VERTEX, 0=END_OF_LIST |
| [28] | end_of_strip | 1=last vertex of triangle strip |
| [26:24] | listtype | 0=opaque, 1=opaque_mod, 2=translucent, 3=trans_mod, 4=punch-through |
| [22:21] | group/strip | Group/strip control |
ISP Word 1: [31:29]=depth compare mode, [28:27]=cull mode (none/small/CW/CCW), [26]=z-write disable.
TSP Word 2/3: Blend mode (src/dst alpha factors), fog control, texture filter (point/bilinear/trilinear), UV clamp/flip, texture address in VRAM, format (ARGB1555/RGB565/ARGB4444/YUV422/bump/4BPP/8BPP), mip-map, VQ compression, twiddled.
17-Vertex Parse: TA processes up to 17 vertices per polygon submission. Region array links Object Pointer Buffers (OPB) per 32×32 pixel tile.
OPB Chaining (all resolutions):
void ta_link_opb(uint32 tx, uint32 ty, uint32 list, uint32 ptr) {
uint32 tiles_x = (FB_W_CTRL & 0x1) ? 20 : 10;
uint32* region = (uint32*)(TA_OL_BASE + (ty * tiles_x + tx) * 24 + list * 4);
*region = ptr;
write32(TA_NEXT_OPB, ptr + 64);
write32(TA_ITP_CURRENT, ptr);
write32(TA_GLOB_TILE_CLIP, (ty << 16) | tx);
write32(TA_OBJECT_CLIP, (ty << 16) | tx);
write32(TA_SQ_WRITE, ptr);
write32(TA_OL_LIMIT, ptr + 0x100000);
}Submission via Store Queue (TA_SQ_WRITE at TA FIFO $10000000).
Additional Holly Registers ($A05F8000+)
| Address | Name | R/W | Description |
|---|---|---|---|
| $A05F8008 | SOFTRESET | W | bit 0=TA reset, bit 1=PVR reset, bit 2=VRAM bus reset. Writes trigger immediate re-sync |
| $A05F8050 | FB_R_SOF1 | RW | Framebuffer scanout address (odd field / non-interlaced) |
| $A05F8054 | FB_R_SOF2 | RW | Framebuffer scanout address (even field) |
| $A05F80C4 | GUN_POS | R | Lightgun H/V position: H[31:16], V[15:0]. Latched by SDCKB after GUN maple descriptor |
| $A05F80CC | SPG_VPOS | RW | VBlank interrupt position (configurable top/bottom scanline) |
| $A05F80D0 | SPG_HBLANK | RW | Horizontal sync/blank timing |
| $A05F80D4 | SPG_LOAD | RW | Sync load (scanline/refresh counts) |
| $A05F8144 | TA_INIT | W | bit 31: initialize TA vertex registration. Must write before first polygon submission per frame |
Complete I/O Register Dispatch Table
| Address | Name | R/W | Side Effects |
|---|---|---|---|
$005F6900 | Holly ISTNRM | R/W | R: normal interrupt status (32 flag bits). W: write-1-to-clear (AND NOT) |
$005F6904 | Holly ISTEXT | R/W | R: external interrupt status. W: write-1-to-clear |
$005F6C14 | Maple MDEN | W | Maple DMA enable |
$005F6C18 | Maple MDST | R/W | R: bit 0=DMA busy. W: bit 0=start DMA. Auto-clears on completion → ISTNRM[12] |
$005F709C | GD-ROM Status/Cmd | R/W | R: BSY/DRDY/DRQ/ERR status bits. W: ATAPI command ($A0=PACKET, $08=reset) |
$005F7414 | GD-ROM DMA EN | W | Writing 1 starts GD-ROM DMA transfer |
$005F7418 | GD-ROM DMA STAT | R | bit 0=DMA active. Auto-clears on completion → ISTNRM[14] |
$005F8008 | PVR SOFTRESET | W | bits 0-2: TA/PVR/VRAM reset. Immediate re-sync |
$005F8014 | PVR STARTRENDER | W | Trigger ISP/TSP rendering. Sets ISTNRM[2] on completion |
$005F809C | SPG_STATUS | R | VBlank/HBlank status, current scanline, field parity |
$005F80C4 | GUN_POS | R | Lightgun position: H[31:16], V[15:0]. Latched by SDCKB |
$005F8144 | PVR TA_INIT | W | bit 31: initialize TA vertex registration |
$00702800 | AICA MVOL | R/W | W: master volume [5:0] + mono [15:12]. R: returns VER[3:0] hardware version |
$00702804 | AICA Ringbuf | W | Write starts DSP effect chain. RBL [14:13] (ring buffer length) + RBP [12:3] |
$00702C00 | AICA ARMRST | W | bit 0: 1=hold ARM7 reset, 0=release. Write after loading driver to wave RAM |
$FFA00040 | SH-4 DMAOR | W | DMA master enable (DME). Must be set before any DMAC channel transfer |
Integration with Existing Pipeline
Interrupt routing through Holly ISTNRM/ISTEXT:
- GD-ROM DMA complete → ISTNRM[14]
- Maple DMA complete / lightgun → ISTNRM[12] + HV latch to $005F80C4
- PVR render complete → ISTNRM[2]
- TA object buffer overflow → ISTERR[5]
- AICA → SH-4 interrupt via MCIPD → Holly ISTNRM sound request bit
SH-4 DMAC + Store Queue fast path:
- PREF instruction flushes 32-byte SQ → TA FIFO at $10000000
- DMAOR master enable (DME bit) required before any DMAC channel transfer
- QACR0/QACR1 ($FF000034/$FF00003C) configure upper address bits for SQ target
- 64-bit burst transfer size on SQ flush
Verification Notes
Test approach (Flycast/lxdream trace comparison):
- Holly ISTNRM — verify write-1-to-clear (AND NOT) behavior, all 32 flag bits
- GD-ROM ATAPI — send PACKET command, verify status progression (BSY→DRQ→ready)
- Maple MDST — start DMA, verify auto-clear of bit 0 on completion + ISTNRM[12]
- AICA ARMRST — load driver to $00800000, clear bit 0, verify ARM7 boots
- PVR TA_INIT → STARTRENDER — submit geometry, verify ISTNRM[2] on render done
- Lightgun — set GUN=1 in maple descriptor, verify HV latch at $005F80C4
Commercial game validation:
- Sonic Adventure: Store Queue TA submission + PVR STARTRENDER at 60fps
- Soul Calibur: Vertex-heavy TA submission with OPB chaining across 640×480 tiles
- Shenmue: GD-ROM streaming + AICA DSP ambient reverb (34-step hall)
- House of the Dead 2: Lightgun HV latch + GUN maple descriptor protocol
CHAPTER 09I/O Dispatch Deepening (Maximum Depth Synthesis)
AICA DSP — ARM7 Address Space + Upload Protocol
The AICA DSP registers have dual address mappings — the SH-4 accesses them via
G2-bus at $00700000+, while the ARM7 coprocessor accesses them locally at $00800000+
| SH-4 Address | ARM7 Address | Name | Description |
|---|---|---|---|
| $00703200 | $00803200 | COEF | 128 × 13-bit signed coefficients |
| $00703400–$00703BFC | $00803400–$00803BFC | MPRO | 128 DSP program steps (64-bit each) |
| $00704000–$000043FF | $00804000–$000043FF | TEMP | Temporary ring buffer (auto-decrement) |
| $00704400–$00704500 | $00804400–$00804500 | MEMS | Memory sample buffer |
| $007045C0–$007045C7 | $008045C0–$008045C7 | EFREG | 8 effect output registers (DAC output) |
DSP MPRO Upload Format: Each 64-bit program step is written as 4 × 16-bit words
- STEP[63:48] at base address
- STEP[47:32] at base + 4
- STEP[31:16] at base + 8
- STEP[15:0] at base + 12
COEF Table: 128 entries × 13-bit signed values (vs 64 entries documented in Final Synthesis — the full bank is 128). Used by the DSP for scaling, feedback coefficients, and filter parameters. Loaded via SH-4 G2-bus writes after ARM7 reset.
Additional Per-Channel Register — IMXL/ISEL:
- Offset +$30: FX mixer send level (IMXL) and DSP bus select (ISEL). Controls which
DSP mixer input bus the channel feeds into. This register shares the offset with DISDL/DIPAN but uses different bit fields for the mixer routing.
DSP Enable: MVOL register ($00702800) bit 4 enables DSP program execution. Must be set after loading MPRO and COEF. The DSP runs synchronously at the AICA sample rate.
Effect Presets (SCSP-ported patterns):
- Hall reverb: 34 steps
- Chorus: 22 steps (LFO modulation on delay line)
- Delay: 20 steps (simple echo with feedback)
G2 Bus Access Constraints:
- G2-bus writes from SH-4 stall for ≤16µs while AICA DMA is active
- Reading AICA registers during active DMA transfer blocks until DMA completes
- Writes to channel registers while KYONEX is pending may be ignored
GD-ROM — Full ATAPI Command Table + ASC/ASCQ Error Codes
The GD-ROM controller uses an extended ATAPI command set defined in Sega's SPI Protocol Ver.1.30 specification (Cdif131e.pdf). The controller registers at $A05F7400+ are mirrored at physical $005F74xx.
Extended Register Layout (G1-bus $A05F7400+):
| Address | Name | R/W | Description |
|---|---|---|---|
| $A05F7018 | GD_DATA | RW | 16-bit PIO data transfer |
| $A05F7404 | GD_FEATURES | W | Feature select (bit 0 = DMA mode) |
| $A05F7408 | GD_SECT_CNT | RW | Sector count |
| $A05F740C | GD_SECT_NUM | RW | Sector number |
| $A05F7410 | GD_BYTE_CTL_LO | RW | Byte count low |
| $A05F7414 | GD_BYTE_CTL_HI | RW | Byte count high |
| $A05F7418 | GD_DRV_SEL | RW | Drive select |
| $A05F741C | GD_STATUS/CMD | RW | R: status bits (BSY/DRDY/DRQ/ERR), W: ATAPI command |
ATAPI + Vendor Command Table (SPI Ver.1.30):
| Opcode | Name | Description |
|---|---|---|
| $00 | TEST_UNIT_READY | Check drive status (no data transfer) |
| $12 | SET_MODE | Vendor: laser boost / read-retry configuration |
| $13 | REQ_ERROR | Request error information (sense key + ASC/ASCQ) |
| $14 | GET_TOC | Read Table of Contents (byte1=1 for GD high-density area) |
| $30 | CD_READ | Primary 12-byte read command (FAD-based addressing) |
| $A8 | READ(12) | Standard ATAPI fallback read command (LBA-based) |
ASC/ASCQ Error Code Table (SPI Appendix I, verbatim):
| ASC | ASCQ | Sense Key | Description |
|---|---|---|---|
| $00 | $00 | 0h | No error |
| $09 | $04 | 4h | Laser power control error (hardware option) |
| $09 | $02 | 4h | Focus servo failure |
| $09 | $03 | 4h | Spindle servo failure |
| $3A | $00 | 2h | No media present (disc tray open or empty) |
| $4E | $00 | Bh | New command received during execution (overlapped) |
Sector Read + Laser Recovery Sequence (DMA preferred):
void gdrom_read_lba(u32 lba, void* buf, u32 sectors) {
write_gd(0x741C, 0xA0); // ATAPI PACKET command
send_12byte_packet(0x30, lba, sectors); // CD_READ CDB
if (status & 0x01) { // ERROR bit set
write_gd(0x741C, 0xA0);
send_12byte(0x13); // REQ_ERROR
u8 sense = read_error() >> 4;
u8 asc = read_asc();
u8 ascq = read_ascq();
if (sense == 4 && asc == 0x09 && ascq == 0x04) {
// Laser power control error → SET_MODE laser boost
write_gd(0x741C, 0xA0);
send_12byte(0x12, 0x09, 0x08, 0x00);
delay(50ms); // allow laser recalibration
}
retry();
}
// DMA path: set SB_GDSTAR/LEN/DIR=1/EN=1/ST=1; wait ISTNRM bit14
}Side Effects: ERROR bit assertion → ISTERR interrupt to Holly. Laser servo retry via SET_MODE command ($12) with 50ms recalibration delay.
Maple Bus — Wire Protocol Timing + Response Parsing
The Maple bus operates on 4 independent serial buses (Ports A–D), each with differential clock/data pairs (SDCKA/SDCKB).
Wire Protocol Timing:
| Phase | Signal Sequence | Duration | Description |
|---|---|---|---|
| Start | SDCKA low → SDCKB toggle 4× → SDCKA high | ~640ns host | Bus acquisition |
| Data bit | 2-bit phases, sampled on falling edge | 160ns host / 250ns peripheral | Bidirectional data |
| End | SDCKA high → SDCKB high-low → SDCKA toggle 2× | ~320ns | Bus release |
Edge Spacing: 125ns between different signal lines (SDCKA→SDCKB), 225ns between same signal line edges. These constraints set the maximum bus throughput.
CRC: Byte-wise XOR with init value 0 over frame header + payload bytes. (Note: The Final Synthesis section documents CRC variants with 0xFF or 0x00 init — the Maximum Depth research confirms init=0 as the canonical implementation.)
**Full Binary Transaction Trace (Port 0controller + VMU + lightgun):**
u32 multi_desc[12] = {
0x80000002 | (0x09<<24) | 0x20, rx1, // 0x09 Get Condition (controller)
0x80000002 | (0x0B<<24) | 0x21, vmu_rx, // 0x0B VMU Block Read
0x80000001 | (0x80<<24) | 0x22, gun_rx // 0x80 Light-Gun (LAST=1)
};Response Function Codes:
| Code | Name | Payload | Description |
|---|---|---|---|
| $05 | Device Info | 28 words | Device capabilities, product name, license string |
| $07 | Ack | 0 words | Write acknowledgment (VMU block write success) |
| $08 | Data Transfer | Variable | VMU block data + location word |
| $80 | GUN | 0 words | 8 SDCKB pulses → beam pulls low → HV latch to SPG_TRIGGER_POS |
| $FF | Timeout | — | No response (0xFFFFFFFF in receive buffer) |
Maple Poll Sequence:
void maple_poll(u32 desc_addr) {
*(u32*)0xA05F6C04 = desc_addr; // MDSTAR: set descriptor table pointer
*(u32*)0xA05F6C14 = 0x1; // MDEN: enable Maple DMA
*(u32*)0xA05F6C18 = 0x1; // MDST: start transfer
while (*(u32*)0xA05F6C18 & 0x1); // poll until !busy (auto-clears)
// Parse response buffers with CRC verification (XOR init 0)
}SH-4 DMAC — Channel Control Bits + DMA Contention Matrix
CHCRx (Channel Control Register, offset +$0C/+$1C/+$2C/+$3C) Bit Layout:
| Bits | Name | Description |
|---|---|---|
| [3:0] | TS | Transfer size: 0=64B (Store Queue burst), 1=1B, 2=2B, 3=4B, 4=32B |
| [13:12] | SM | Source address mode: 0=fixed, 1=increment, 2=decrement |
| [15:14] | DM | Destination address mode: 0=fixed, 1=increment, 2=decrement |
| 17 | IE | Interrupt enable on transfer end |
| 24 | DE | DMA enable (per-channel) |
| 25 | TE | Transfer end flag (set by hardware on completion) |
DMAOR ($FFA00040) Bit Layout:
| Bit | Name | Description |
|---|---|---|
| 0 | DME | DMA master enable — must be 1 before any channel transfer |
| 1 | NMIF | NMI flag (set on NMI, must be cleared to re-enable DMA) |
| 2 | AE | Address error flag |
| 5 | MD | Priority mode: 0=CH0 fixed highest priority, 1=round-robin |
DMA Contention Matrix (PVR TA + texture upload + GD-ROM load concurrent):
| Scenario | CH0 Stall | CH2 Texture | SQ (TA) | Handling |
|---|---|---|---|---|
| TA FIFO full | 16 cycles | 32 cycles | 0 (stalled) | TAFOFINT IRQ → reset TA |
| Concurrent DMA | Round-robin 64B | 16–32 cycles | Preempted | SB_TFREM==0 stall ~32 cycles |
TAFOFINT: TA FIFO overflow interrupt. When SB_TFREM (TA FIFO remaining count) reaches 0, Store Queue writes stall for ~32 cycles until space is available. If overflow persists, TAFOFINT fires and the TA must be reset via SOFTRESET bit 0.
Store Queue Recomp Mapping (TA geometry burst):
u32* sq = (u32*)0xE0000000;
for (int i = 0; i < 8; i++) sq[i] = param[i]; // fill 32 bytes
__asm__("pref @%0" : : "r"(sq)); // PREF → 32B burst to $10000000PVR TA — Named Registers + Dual FIFO
TA Configuration Registers ($A05F8000+):
| Address | Name | R/W | Description |
|---|---|---|---|
| $A05F8124 | TA_OL_BASE | RW | Object list base address in VRAM |
| $A05F8128 | TA_ISP_BASE | RW | ISP/TSP parameter base address |
| $A05F812C | TA_OL_LIMIT | RW | Object list end address (overflow → TAFOFINT) |
| $A05F8130 | TA_ISP_LIMIT | RW | ISP/TSP parameter end address |
| $A05F8134 | TA_NEXT_OPB | RW | Next free Object Pointer Block address |
| $A05F8138 | TA_ITP_CURRENT | RW | Current ISP/TSP parameter write pointer |
| $A05F813C | TA_GLOB_TILE_CLIP | RW | Global tile clipping rectangle |
| $A05F8140 | TA_OBJECT_CLIP | RW | Object clipping rectangle |
| $A05F8144 | TA_INIT | W | bit 31: initialize TA vertex registration |
| $A05F8148 | TA_SQ_WRITE | RW | Store Queue write target address |
Dual TA FIFO:
- Primary: $10000000 (Store Queue target for polygon parameter submission)
- Secondary: $10800000 (alternate FIFO for YUV texture conversion data)
END_OF_LIST: Submitting a parameter with cmd[31:29]=0 (PCW command field) triggers an END_OF_LIST interrupt (ISTNRM list-specific bits), signaling that the current display list is complete and rendering can begin via STARTRENDER.
Holly/PVR Video Output — Timing Tables + Quad-Buffer Swap
Video Output Registers ($A05F8000+):
| Address | Name | R/W | Description |
|---|---|---|---|
| $A05F8040 | VO_BORDER_COL | RW | Border color RGB during blanking periods |
| $A05F80C8 | SPG_HBLANK_INT | RW | HBlank interrupt position |
| $A05F80CC | SPG_VBLANK_INT | RW | VBlank interrupt position (configurable top/bottom scanline) |
| $A05F80D0 | SPG_HBLANK | RW | Horizontal sync/blank timing |
| $A05F80D4 | SPG_LOAD | RW | Sync load (scanline/refresh counts). Write restarts sync generator |
| $A05F80D8 | SPG_CONTROL | RW | Video mode (interlace, NTSC/PAL, sync polarity). Write triggers re-sync + TA pipeline flush |
| $A05F80E0 | VO_STARTX | RW | Horizontal display start position (fine-tunes CRT centering) |
| $A05F80E4 | VO_STARTY | RW | Vertical display start position |
| $A05F80E8 | VO_CONTROL | RW | bit 3 = blank_video: 1=show border color, 0=show framebuffer. Immediate display effect |
Video Timing Table (DevBox verbatim defaults):
| Mode | SPG_LOAD | SPG_HBLANK | SPG_VBLANK | SPG_CONTROL | VO_STARTX/Y | Border |
|---|---|---|---|---|---|---|
| NTSC 640×480 prog | $020C0359 | $007E0345 | $00280208 | $00000100 | $00A8/$0028 | $000000 |
| PAL 640×480 prog | $0270035F | $008D034B | $002C026C | $00000180 | $00AE/$002D | $000000 |
| VGA/VESA 640×480 | $020C0359 | $007E0345 | $00280208 | $00000100 | $00A8/$0028 | $000000 |
Quad-Buffer Swap + TA Sync (under TAFOFINT):
void quad_buffer_swap_ta_sync(u32 buf[4], u32 border_rgb) {
if (TAFOFINT()) reset_ta(); // handle TA overflow
*(u32*)0xA05F8050 = buf[idx]; // FB_R_SOF1: swap scanout buffer
*(u32*)0xA05F8040 = border_rgb; // VO_BORDER_COL: set border
*(u32*)0xA05F80E8 |= (1<<3); // VO_CONTROL: blank_video=1
*(u32*)0xA05F8014 = 0x1; // STARTRENDER: begin ISP/TSP
while (!(*(u32*)0xA05F6900 & (1<<12))); // wait VSYNC (ISTNRM bit 12)
idx = (idx + 1) % 4;
*(u32*)0xA05F80D8 = spg_load; // SPG_CONTROL → re-arm TA + VPOS reset
}Side Effects:
- SPG_CONTROL or SPG_LOAD write → restarts sync generator + TA pipeline flush
- blank_video=1 → display shows VO_BORDER_COL instead of framebuffer (prevents tearing
during buffer swap)
- VO_STARTX/Y writes fine-tune CRT horizontal/vertical centering
Expanded I/O Register Dispatch Table
New entries from Maximum Depth Synthesis marked with (MDS)
| Address | Name | R/W | Side Effects |
|---|---|---|---|
$005F8040 | VO_BORDER_COL | W | (MDS) Set border color RGB for blanking periods |
$005F80D4 | SPG_LOAD | W | (MDS) Sync load config. Write restarts sync generator |
$005F80D8 | SPG_CONTROL | W | (MDS) Video mode control. Write triggers re-sync + TA pipeline flush + VPOS reset |
$005F80E8 | VO_CONTROL | W | (MDS) bit 3=blank_video. Immediate display blank/unblank |
Combined with the 16 Final Synthesis entries (8 read + 8 write), Dreamcast now has 20 total dispatch entries (8 read + 12 write) in the Final Synthesis table, plus these 4 additional write entries = 24 total (8 read + 16 write).
Cross-Subsystem DMA Coordination
The Dreamcast's subsystems interact through multiple DMA paths that must be correctly
sequenced for recompilation
- AICA audio loading: G2-bus DMA from main RAM to sound RAM ($00800000), coordinated
with SH-4 DMAC CH0. G2 access stalls ≤16µs during active transfer.
- GD-ROM → main RAM: SPI controller triggers GD-ROM DMA. Completion fires ISTNRM[14],
which typically triggers Maple VBLANK poll in the game's interrupt handler.
- TA geometry: Store Queue bursts (PREF → $10000000) use CHCR TS=0 (64-byte SQ
mode). DMAOR round-robin arbitration with MD bit. SB_TFREM monitoring prevents FIFO overflow; TAFOFINT fires if overflow occurs.
- Framebuffer swap: Quad-buffer rotation syncs SPG timing, TA FIFO state, and VO
blanking across all video modes (NTSC/PAL/VGA). SPG_CONTROL write re-arms the TA and resets VPOS for the next frame.
Additional Verification Notes (Maximum Depth)
Expanded test approach:
- GD-ROM ATAPI command table — send TEST_UNIT_READY ($00), verify no-data status
response; send REQ_ERROR ($13) after forced error, verify ASC/ASCQ matches table
- Laser recovery — inject sense 4 / ASC $09 / ASCQ $04, verify SET_MODE ($12) issued
with laser boost parameters + 50ms delay
- Maple wire timing — verify SDCKA/SDCKB edge spacing matches 125ns/225ns spec;
verify CRC with init=0 over frame+payload
- SH-4 DMAC contention — concurrent CH0 + SQ transfer, verify SB_TFREM stall
behavior (~32 cycles when FIFO full)
- VO_CONTROL blank_video — set bit 3, verify border color displayed instead of
framebuffer content
- SPG_CONTROL write — verify sync generator restart and TA pipeline flush on write
Additional commercial game validation:
- Jet Set Radio: Heavy TA geometry + SQ bursts with TAFOFINT recovery
- Crazy Taxi: Continuous GD-ROM streaming + Maple polling in VBLANK handler
- Ikaruga: Precise SPG timing for 60fps progressive scan (VGA mode)