The Sega Master System (1985/1986) pairs a Zilog Z80 CPU at 3.58 MHz with a custom Sega 315-5124 VDP, an SN76489-compatible PSG integrated into the VDP, and an optional Yamaha YM2413 FM synthesis chip (Japanese models only). This document covers every hardware register, timing constraint, and behavioral quirk needed for cycle-accurate static recompilation. The SMS sold over 13 million units and its architecture — while Z80-based like the ColecoVision and MSX — introduces significant custom extensions in the VDP, a unique bank-switching scheme, and NMI-wired pause behavior that demand careful emulation.
CHAPTER 011. CPU architecture — Z80 at 3.579545 MHz
Registers and flag layout
The Z80 provides a dual register bank, two index registers, and several special-purpose registers. The main register set comprises eight 8-bit registers: A (accumulator), F (flags), B, C, D, E, H, and L. B/C, D/E, and H/L pair into 16-bit registers BC, DE, and HL respectively, with HL serving as the primary memory pointer. A complete alternate register set (A', F', B', C', D', E', H', L') is swapped in via EX AF,AF' and EXX.
Two 16-bit index registers IX and IY support (IX+d)/(IY+d) addressing with a signed 8-bit displacement. Each has undocumented 8-bit halves: IXH, IXL, IYH, IYL.
The remaining special registers areSP (16-bit stack pointer, grows downward), PC (16-bit program counter), I (8-bit interrupt vector register, unused on SMS since IM 2 is not used), and R (8-bit memory refresh counter — only the lower 7 bits increment, bit 7 is preserved, incremented once per M1 cycle; prefixed instructions increment R by 2).
The flag register F has this layout
| Bit | Flag | Meaning |
|---|---|---|
| 7 | S | Sign — copy of bit 7 of result |
| 6 | Z | Zero — set if result is zero |
| 5 | YF | Undocumented — copy of bit 5 of result |
| 4 | H | Half-carry — carry from bit 3 to bit 4 |
| 3 | XF | Undocumented — copy of bit 3 of result |
| 2 | P/V | Parity (logical ops) or signed overflow (arithmetic) |
| 1 | N | Subtract — set if last op was subtraction (used by DAA) |
| 0 | C | Carry — carry/borrow from bit 7 |
The undocumented flags YF and XF must be emulated correctly — some SMS games depend on them. For most instructions they copy bits 5 and 3 of the result; for BIT b,(IX+d)/BIT b,(IY+d) they copy from the high byte of the computed effective address.
Addressing modes
The Z80 supports ten addressing modesImmediate (LD A,n), Immediate Extended (LD HL,nn), Register (LD A,B), Register Indirect via (HL), (BC), or (DE), Indexed via (IX+d) or (IY+d) with signed 8-bit displacement, Extended/Direct with a full 16-bit address (LD A,(nn)), Relative for JR/DJNZ (signed 8-bit offset, range PC−126 to PC+129), Implied (operand implicit in the opcode), Bit Addressing (BIT b,r), and I/O Port addressing in both direct (IN A,(n) — port = A:n) and indirect (IN r,(C) — port = B:C) forms.
Instruction set with T-state counts
All T-state values below are standard Z80 timings. The SMS applies no additional wait states (unlike MSX systems). Where two values appear (e.g., 17/10), the first is condition-met, the second is condition-not-met. IX/IY indexed variants add +12 T-states over (HL) equivalents for memory operations, and +4 T-states for undocumented half-register operations.
8-bit load group:
| Instruction | T-states | Instruction | T-states |
|---|---|---|---|
LD r,r' | 4 | LD r,n | 7 |
LD r,(HL) | 7 | LD (HL),r | 7 |
LD (HL),n | 10 | LD A,(BC) | 7 |
LD A,(DE) | 7 | LD (BC),A | 7 |
LD (DE),A | 7 | LD A,(nn) | 13 |
LD (nn),A | 13 | LD A,I / LD A,R | 9 |
LD I,A / LD R,A | 9 | LD r,(IX+d) | 19 |
LD (IX+d),r | 19 | LD (IX+d),n | 19 |
16-bit load group:
| Instruction | T-states | Instruction | T-states |
|---|---|---|---|
LD dd,nn | 10 | LD IX,nn | 14 |
LD HL,(nn) | 16 | LD dd,(nn) (ED) | 20 |
LD (nn),HL | 16 | LD (nn),dd (ED) | 20 |
LD SP,HL | 6 | LD SP,IX | 10 |
PUSH qq | 11 | PUSH IX | 15 |
POP qq | 10 | POP IX | 14 |
Exchange, arithmetic, and logic:
| Instruction | T-states | Instruction | T-states |
|---|---|---|---|
EX DE,HL | 4 | EX AF,AF' | 4 |
EXX | 4 | EX (SP),HL | 19 |
ADD/ADC/SUB/SBC/AND/OR/XOR/CP A,r | 4 | Same, A,n | 7 |
Same, A,(HL) | 7 | Same, A,(IX+d) | 19 |
INC r / DEC r | 4 | INC (HL) / DEC (HL) | 11 |
INC (IX+d) / DEC (IX+d) | 23 | ADD HL,ss | 11 |
ADC HL,ss / SBC HL,ss | 15 | ADD IX,pp | 15 |
INC ss / DEC ss | 6 | INC IX / DEC IX | 10 |
DAA / CPL / CCF / SCF | 4 | NEG | 8 |
Rotate, shift, and bit operations:
| Instruction | T-states | Instruction | T-states |
|---|---|---|---|
RLCA/RRCA/RLA/RRA | 4 | RLC/RRC/RL/RR/SLA/SRA/SRL r | 8 |
Same, (HL) | 15 | Same, (IX+d) | 23 |
RLD / RRD | 18 | BIT b,r | 8 |
BIT b,(HL) | 12 | BIT b,(IX+d) | 20 |
SET/RES b,r | 8 | SET/RES b,(HL) | 15 |
SET/RES b,(IX+d) | 23 | SLL r (undoc CB 30-37) | 8 |
Jumps, calls, returns:
| Instruction | T-states | Instruction | T-states |
|---|---|---|---|
JP nn | 10 | JP cc,nn | 10 (always) |
JR e | 12 | JR cc,e | 12/7 |
JP (HL) | 4 | JP (IX) | 8 |
DJNZ e | 13/8 | CALL nn | 17 |
CALL cc,nn | 17/10 | RET | 10 |
RET cc | 11/5 | RETI / RETN | 14 |
RST p | 11 |
Block and I/O instructions:
| Instruction | T-states | Instruction | T-states |
|---|---|---|---|
LDI/LDD/CPI/CPD | 16 | LDIR/LDDR/CPIR/CPDR | 21/16 |
IN A,(n) | 11 | IN r,(C) | 12 |
OUT (n),A | 11 | OUT (C),r | 12 |
INI/IND/OUTI/OUTD | 16 | INIR/INDR/OTIR/OTDR | 21/16 |
NOP | 4 | HALT | 4 (repeats) |
DI / EI | 4 | IM 0/IM 1/IM 2 | 8 |
Undocumented instructions relevant to SMS emulation
SLL (Shift Left Logical) — opcodes CB 30–CB 37. Shifts left, bit 7 → carry, bit 0 set to 1 (unlike SLA which clears bit 0). Computes (value × 2) + 1.
IX/IY half-register ops — a DD/FD prefix redirects H→IXH, L→IXL for register-only instructions (LD A,IXH = DD 7C, ADD A,IXL = DD 85, INC IXH = DD 24). Exception: instructions mixing H/L with (HL) resolve to the real H/L register plus indexed addressing.
DDCB/FDCB double-prefixed instructions — perform a CB-group operation on (IX+d) and also copy the result to a register. Encoding: DD CB d opcode, where bits 2–0 of the final byte select the destination register (110 = documented (IX+d) only, others store to B/C/D/E/H/L/A). BIT variants do not copy. All take 23 T-states (20 for BIT). Some SMS games use these.
Prefix stacking — multiple DD/FD prefixes do not stack; each resets the IX/IY selection. Each stray prefix acts as a 4-T-state NOP that increments R. An ED prefix cancels any preceding DD/FD.
SMS-specific interrupt behavior
The SMS uses IM 1 exclusively. The BIOS sets this at power-on. In IM 1, maskable interrupts push PC and jump to $0038. No vector table is used.
The VDP is the sole source of maskable interrupts, directly wired to /INT. Two interrupt types exist: VBlank (fires on first scanline after active display; sets status register bit 7) and line interrupt (fires when the internal line counter underflows; see VDP section). The /INT line remains asserted as long as the pending condition persists and interrupts are enabled in the relevant VDP register. Reading the VDP status register ($BF) clears the pending interrupt and deasserts /INT. The interrupt handler must perform this read to determine the source and prevent immediate re-entry.
Interrupt acceptance takes 13 T-states (equivalent to RST $38). The EI instruction enables interrupts after the following instruction completes — critical for EI; RET sequences. DI takes effect immediately.
The Pause button is wired directly to the Z80's /NMI pin. NMI is edge-triggered (single pulse on press), pushes PC, and jumps to $0066. It cannot be disabled. The NMI handler must use RETN to return, which copies IFF2→IFF1 to restore the previous interrupt-enable state. The Pause button is not readable via any I/O port — it only generates NMI. On Game Gear, the Start button instead reads at port $00 bit 7 and does not generate NMI in GG mode.
HALT causes the CPU to execute internal NOPs (4 T-states each, R incrementing) with PC frozen. Both NMI and enabled maskable interrupts break HALT. If interrupts are disabled during HALT, only NMI or hardware reset can recover.
Clock timing
| Parameter | NTSC | PAL |
|---|---|---|
| Master crystal | 10.738635 MHz | 10.640684 MHz |
| Z80 clock (÷3) | 3.579545 MHz | 3.546895 MHz |
| T-states/scanline | 228 | 228 |
| Scanlines/frame | 262 | 313 |
| T-states/frame | 59,736 | 71,364 |
| Frame rate | ~59.92 Hz | ~49.70 Hz |
CHAPTER 022. VDP — Sega 315-5124 (Mode 4, sprites, scrolling, line interrupt)
What changed from the TMS9928A
The SMS VDP is derived from the TMS9918/TMS9928A but is a substantially different chip.
The key additions: Mode 4 with 4bpp tiles, a 32-byte Color RAM (CRAM) providing 64 selectable colors (6-bit RGB), horizontal and vertical scroll registers, scroll inhibit regions, left column blanking, a programmable line counter interrupt, 64 sprites with 8-per-scanline limit (up from 32/4), per-tile horizontal/vertical flip and priority bits, a 9-bit tile index addressing 512 patterns, V counter / H counter readable on ports, and CRAM write mode (command code 3).
The SMS2's 315-5246 adds 224-line and 240-line extended display modes.
Retained from TMS9928Alegacy modes 0–3 remain accessible, the two-port interface ($BE data, $BF control) and two-byte command word protocol are preserved, 16KB VRAM limit is unchanged.
Mode 4 tile format and VRAM layout
Each tile is 8×8 pixels at 4 bits per pixel, stored as 4 interleaved bitplanes per row — 32 bytes per tile. For each of the 8 rows, 4 consecutive bytes represent bitplanes 0–3, with bit 7 being the leftmost pixel:
Byte 0: Bitplane 0, row 0 Byte 4: Bitplane 0, row 1 ... Byte 28: Bitplane 0, row 7
Byte 1: Bitplane 1, row 0 Byte 5: Bitplane 1, row 1 ... Byte 29: Bitplane 1, row 7
Byte 2: Bitplane 2, row 0 Byte 6: Bitplane 2, row 1 ... Byte 30: Bitplane 2, row 7
Byte 3: Bitplane 3, row 0 Byte 7: Bitplane 3, row 1 ... Byte 31: Bitplane 3, row 7For pixel column c (7=left, 0=right)palette_index = (bp3_bit_c << 3) | (bp2_bit_c << 2) | (bp1_bit_c << 1) | bp0_bit_c.
CRAM holds 32 bytes — two 16-color palettes. Bytes $00–$0F are the background palette, $10–$1F the sprite palette. Background tiles select either palette via the nametable entry. Sprites always use the sprite palette, with index 0 being transparent. Each CRAM entry is one byte in --BBGGRR format (2 bits per channel, values 0–3 mapping to intensities 0, 85, 170, 255). This yields 64 possible colors.
The nametable is a 32×28 grid (192-line mode) of 16-bit little-endian entries
Bit 12: Priority (1 = drawn over sprites except where bg pixel = index 0)
Bit 11: Palette select (0 = background palette, 1 = sprite palette)
Bit 10: Vertical flip
Bit 9: Horizontal flip
Bits 8-0: Tile pattern index (0–511)Canonical VRAM layout (with R2=$FF, R5=$FF, R6=$FB)
| Address | Content |
|---|---|
| $0000–$1FFF | Tile patterns 0–255 (8192 bytes) |
| $2000–$37FF | Tile patterns 256–447 (6144 bytes) |
| $3800–$3EFF | Nametable (32×28 = 1792 bytes) |
| $3F00–$3FFF | Sprite Attribute Table (256 bytes) |
Scrolling mechanics
Horizontal scroll (R8) is a full 8-bit value. The upper 5 bits define a coarse column offset into the nametable; the lower 3 bits provide fine pixel scroll (0–7). Increasing values scroll the background rightward. The scroll value can be changed mid-frame. R0 bit 6 enables horizontal scroll inhibit — the top 2 rows (16 pixels) ignore the scroll value. R0 bit 5 enables left column blanking, filling the leftmost 8 pixels with the overscan color to mask the scroll seam.
Vertical scroll (R9) is an 8-bit value that wraps at 224 in 192-line mode (28 rows × 8) or 256 in 224/240-line modes. Unlike horizontal scroll, vertical scroll is latched only during VBlank — writes during active display are deferred. R0 bit 7 enables vertical scroll inhibit for the rightmost 8 columns (columns 24–31), which remain fixed at VScroll=0. This is the mechanism games use for fixed status bars on the right side.
Sprite system
The Sprite Attribute Table (SAT) holds 64 entries in VRAM (typically at $3F00 per R5=$FF)
$3F00–$3F3F: Y positions (1 byte each, 64 entries)
$3F40–$3F7F: Unused
$3F80–$3FFF: X position + tile index pairs (2 bytes each, 64 entries)Actual screen Y = stored_value + 1. A Y value of $D0 (208) in 192-line mode terminates sprite processing — all subsequent sprites are skipped. X positions range 0–255; R0 bit 3 shifts all sprites left 8 pixels (allowing exit off the left edge).
Sprite sizes8×8 (R1 bit 1 = 0) or 8×16 (R1 bit 1 = 1). In 8×16 mode, bit 0 of the tile index is forced to 0 — the top half uses the even tile, the bottom uses tile+1. R1 bit 0 enables sprite zoom (pixel doubling), though this is buggy on the 315-5124 (only partially doubles horizontally). R6 bit 2 selects the sprite pattern base: $0000 (bit 2 = 0) or $2000 (bit 2 = 1). Maximum 8 sprites per scanline; lower-numbered sprites have higher priority.
VDP registers R0–R10
Register $00 — Mode Control 1:
Bit 7: Vertical scroll inhibit (right 8 columns fixed)
Bit 6: Horizontal scroll inhibit (top 2 rows fixed)
Bit 5: Left column blank (mask leftmost 8 pixels with overscan color)
Bit 4: Line interrupt enable (IE1)
Bit 3: Sprite shift (all sprites left 8 pixels)
Bit 2: Mode 4 select (M4) — must be 1 for Mode 4
Bit 1: M2 mode bit (used for extended height modes with M4=1)
Bit 0: External sync (no effect on SMS — set to 0)Register $01 — Mode Control 2:
Bit 7: Should be 1 (compatibility)
Bit 6: Display enable (1=on, 0=blanked to overscan color)
Bit 5: Frame interrupt enable (IE0, VBlank IRQ)
Bit 4: M1 mode bit (240-line select with M4=1, SMS2 only)
Bit 3: M3 mode bit (224-line select with M4=1, SMS2 only)
Bit 1: Sprite size (0=8×8, 1=8×16)
Bit 0: Sprite zoom (1=double size, buggy on 315-5124)Register $02 — Nametable base: Bits 3–1 × $0800 give the base address. R2=$0F → $3800. On SMS1, bit 0 acts as AND mask over Y-coordinate bits. Register $03 (color table, legacy): should be $FF in Mode 4. Register $04 (pattern generator, legacy): bits 2–0 should be set in Mode 4. Register $05 — SAT base: (R5 AND $7E) << 7. R5=$FF → $3F00. Register $06 — sprite pattern base: bit 2 selects $0000 or $2000. Register $07 — overscan color: low 4 bits index the sprite palette.
Register $08 — horizontal scroll (0–255). Register $09 — vertical scroll (0–255, wraps at 224 in 192-line mode). Register $0A — line counter reload value.
VDP control port protocol
The control port at $BF uses a two-byte write sequence. An internal toggle tracks first/second writes. The toggle resets on: second byte written, status register read, or data port read/write.
First write: AAAAAAAA (low 8 bits of address)
Second write: CCAAAAAA (CC = command code, A = high 6 bits of 14-bit address)| Code (CC) | Operation |
|---|---|
| 00 | VRAM read — prefetches byte at address into read buffer, increments address |
| 01 | VRAM write — subsequent $BE writes go to VRAM |
| 10 | Register write — first byte = data, second byte bits 3–0 = register number |
| 11 | CRAM write — subsequent $BE writes go to palette RAM (address AND $1F) |
The read buffer prefetches one byte on VRAM-read setup. The first data port read returns this prefetched byte, then the next VRAM byte is loaded. The address register auto-increments after every data port read or write, wrapping from $3FFF to $0000. Writing the first byte immediately updates the low 8 address bits (a side effect exploitable as a fast partial-address trick).
Status register (read $BF)
Bit 7: Frame interrupt pending (set at VBlank)
Bit 6: Sprite overflow (>8 sprites on one scanline)
Bit 5: Sprite collision (any two non-transparent sprite pixels overlap)
Bits 4-0: Fifth sprite number (legacy modes only; undefined in Mode 4)Reading clears bits 7, 6, and 5, deasserts /INT, and resets the control port write toggle. All flags are latched — they persist until read.
Scanline interrupt (line counter)
Register $0A holds a reload value. An internal counter operates as follows: during active display lines (0–192 inclusive), the counter decrements each scanline. When it underflows from $00 to $FF, the counter reloads from R10 and, if R0 bit 4 is set, a line interrupt is asserted. During VBlank lines (193–261 NTSC), the counter is reloaded from R10 every line. This means writing to R10 mid-frame does not affect the counter until the next reload event.
The line interrupt fires at the same point as the V counter increment — the transition to the next scanline. If both VBlank and line interrupt conditions occur simultaneously, both pending bits are set.
Frame timing
NTSC 192-line: 192 active + 24 bottom border + 3 bottom blank + 3 vsync + 13 top blank + 27 top border = 262 scanlines. V counter reads $00–$DA, then jumps to $D5–$FF. PAL 192-line: 192 active + 24 bottom border + 3 blank + 3 vsync + 13 blank + 78 top border = 313 scanlines.
Extended modes (SMS2/315-5246 only): 224-line mode = M4=1, R0.1=1, R1.3=1 (mode bits 1011). 240-line mode = M4=1, R0.1=1, R1.4=1 (mode bits 1110). These are not valid on the 315-5124 (original SMS), which treats them as standard Mode 4.
Legacy TMS9928 modes
With M4=0, mode bits M3/M2/M1 selectMode 0 (Graphics I, 000), Mode 1 (Text, 001), Mode 2 (Graphics II, 010), Mode 3 (Multicolor, 100). The SMS VDP approximates the TMS9918's fixed 15-color palette with hardwired internal RGB equivalents (not from CRAM). Sprites revert to 32 entries, 4-per-scanline, monochrome, with TMS9918-format SAT. SG-1000/SC-3000 software runs through these modes.
CHAPTER 033. Memory map — complete address space and I/O ports
Z80 address space
| Range | Size | Description |
|---|---|---|
| $0000–$03FF | 1 KB | Always bank 0 ROM — protects interrupt vectors at $0000, $0038, $0066 |
| $0000–$3FFF | 16 KB | Slot 0 — ROM bank selected by $FFFD (first 1KB always bank 0) |
| $4000–$7FFF | 16 KB | Slot 1 — ROM bank selected by $FFFE |
| $8000–$BFFF | 16 KB | Slot 2 — ROM bank selected by $FFFF, or cartridge RAM if enabled |
| $C000–$DFFF | 8 KB | System RAM |
| $E000–$FFFF | 8 KB | Mirror of $C000–$DFFF; writes to $FFFC–$FFFF also trigger mapper registers |
Writing to $FFFC–$FFFF updates both RAM and the mapper. Writing to the unmirrored equivalent ($DFFC–$DFFF) updates only RAM. For games ≤48 KB (3 banks), no bank switching is needed.
I/O port map
The SMS decodes only bits A7, A6, and A0 of the I/O address, producing these effective port groups
| Port range | Read | Write |
|---|---|---|
| $00–$3F | Returns $FF (GG: port $00 = Start/region) | Even: Memory control ($3E); Odd: I/O control ($3F) |
| $40–$7F | Even: V counter; Odd: H counter | SN76489 PSG data |
| $80–$BF | Even: VDP data; Odd: VDP status | Even: VDP data; Odd: VDP control |
| $C0–$FF | Even ($DC): I/O Port A; Odd ($DD): I/O Port B | No effect |
Additional: Ports $F0 (write) / $F1 (write) / $F2 (read/write) access the YM2413 FM chip on Japanese SMS. Port $3E bit 2 (I/O chip) must be disabled to read $F2 for FM detection.
Port $3E — Memory control (write only)
All bits are active-high-disable (1 = disabled)
Bit 7: Expansion slot Bit 3: BIOS ROM
Bit 6: Cartridge slot Bit 2: I/O chip
Bit 5: Card slot Bit 1-0: Unused
Bit 4: Work RAMThe BIOS writes $AB to this port when launching a cartridge (disabling expansion, card, and BIOS while keeping cart, RAM, and I/O enabled). The BIOS stores its last $3E write at RAM $C000.
Port $3F — I/O port control (write only)
Controls TH/TR pin direction and output level for both controller ports
Bit 7: Port B TH output level Bit 3: Port B TH direction (1=output)
Bit 6: Port B TR output level Bit 2: Port B TR direction (1=output)
Bit 5: Port A TH output level Bit 1: Port A TH direction (1=output)
Bit 4: Port A TR output level Bit 0: Port A TR direction (1=output)Default $FF (all inputs). Used for region detectionset TH as output, toggle high/low, read back via $DD bits 6–7. Export SMS echoes the written value; Japanese SMS always returns 0.
CHAPTER 044. Audio — SN76489 PSG + YM2413 OPLL
SN76489 PSG — 3 squares + 1 noise
The PSG is integrated into the VDP chip (not a discrete TI part). It provides 3 square-wave tone channels (0–2) and 1 noise channel (3), written via a single port at $7E/$7F. Clock: 3,579,545 Hz (NTSC); internal divider ÷16.
Command format — Latch/Data byte (bit 7 = 1): %1cctdddd where cc = channel (0–3), t = type (1=volume, 0=tone), dddd = data. This sets the latch for subsequent data bytes. Data byte (bit 7 = 0): %0-DDDDDD — 6 bits sent to the currently latched register. For a 10-bit tone register, the latch byte provides bits 3–0 and the data byte provides bits 9–4.
Tone frequency: f = 3,579,545 / (2 × register_value × 16). Register value 0 produces DC (constant +1 output) — used for PCM sample playback. Value $3FF (1023) gives ~109 Hz; value 1 gives ~111 kHz (theoretical).
Noise channel control (3-bit register)Bit 2 = feedback type (0 = periodic, 1 = white noise). Bits 1–0 = shift rate: 00 → divider $10 (~6991 Hz), 01 → $20 (~3496 Hz), 10 → $40 (~1748 Hz), 11 → tracks channel 2's frequency.
SMS-specific LFSR: 16-bit register, white-noise taps at bits 0 and 3 (mask $0009) with XOR feedback into bit 15. This differs from ColecoVision/BBC Micro (15-bit, taps 0+1) and TI's original SN76489A (XNOR feedback). On noise register write, LFSR resets to $8000. Periodic noise uses only bit 0 as feedback, producing a 1/16 duty cycle loop.
Volume: 4-bit attenuation per channel. $0 = full volume, $F = silence. Each step = 2 dB attenuation. Volume table (linear): 32767, 26028, 20675, 16422, 13045, 10362, 8231, 6568, 5193, 4125, 3277, 2603, 2067, 1642, 1304, 0.
Game Gear stereo (port $06)bits 7–4 = left enable for ch3/2/1/0, bits 3–0 = right enable. Default $FF = all channels both sides. Not present on SMS.
YM2413 OPLL — 9-channel FM synthesis
The Yamaha YM2413 is a cost-reduced 2-operator FM chip present in the Japanese SMS (built-in) and available as an add-on for the Mark III. It provides 9 melody channels or 6 melody + 5 rhythm channels, with 15 built-in ROM instrument patches plus one user-definable patch.
I/O ports: $F0 (register address, write), $F1 (register data, write), $F2 (detection/control, read/write). Write timing: 12 master clocks (~3.4 µs) after address write, 84 master clocks (~23.5 µs) after data write.
Register map:
| Register(s) | Function |
|---|---|
| $00–$07 | User instrument definition (modulator/carrier ADSR, waveform, feedback) |
| $0E | Rhythm control (rhythm enable bit 5, BD/SD/TOM/TCY/HH key-on bits 4–0) |
| $10–$18 | F-Number low 8 bits (channels 0–8) |
| $20–$28 | Sustain (bit 5) / Key-on (bit 4) / Block/octave (bits 3–1) / F-Num bit 8 (bit 0) |
| $30–$38 | Instrument select (bits 7–4, 0–15) / Channel volume (bits 3–0, 0=max, 15=off) |
User instrument registers $00–$07 detail:
$00: Modulator — AM | VIB | EGT | KSR | MULTI[3:0]
$01: Carrier — AM | VIB | EGT | KSR | MULTI[3:0]
$02: Modulator — KSL[7:6] | TL[5:0] (total level / modulation depth)
$03: Carrier — KSL[7:6] | DC | DM | FB[2:0] (waveforms + feedback)
$04: Modulator — AR[7:4] | DR[3:0]
$05: Carrier — AR[7:4] | DR[3:0]
$06: Modulator — SL[7:4] | RR[3:0]
$07: Carrier — SL[7:4] | RR[3:0]MULTI values0=×½, 1=×1, 2=×2 … 7=×7, 8=×8, 9=×9, 10=×10, 11=×10, 12=×12, 13=×12, 14=×15, 15=×15. KSL: 00=0 dB/oct, 01=1.5, 10=3.0, 11=6.0. TL: 6-bit modulation depth (0=max, 63=min, 0.75 dB/step). FB: modulator self-feedback (0=off, 1=π/16 … 7=4π). DC/DM: waveform select per operator (0=full sine, 1=half-rectified sine).
Frequency formula: f = (F-Number × 49716) / 2^(19 - Block) where 49716 = 3,579,545/72.
Built-in instrument patches:
| # | Instrument | # | Instrument |
|---|---|---|---|
| 0 | User-defined | 8 | Organ |
| 1 | Violin | 9 | Horn |
| 2 | Guitar | 10 | Synthesizer |
| 3 | Piano | 11 | Harpsichord |
| 4 | Flute | 12 | Vibraphone |
| 5 | Clarinet | 13 | Synthesizer Bass |
| 6 | Oboe | 14 | Acoustic Bass |
| 7 | Trumpet | 15 | Electric Guitar |
Rhythm mode ($0E bit 5 = 1)channels 6–8 are repurposed for Bass Drum (ch6 modulator+carrier), Hi-Hat (ch7 modulator), Snare Drum (ch7 carrier), Tom-Tom (ch8 modulator), Top Cymbal (ch8 carrier). Key-on via $0E bits 4–0. Volume: $36 high nibble = BD, $37 = HH (high) + SD (low), $38 = TOM (high) + TC (low).
FM detection and PSG/FM switching
Games detect the YM2413 via port $F2write values 0–6 to $F2, read back, and check whether the low 2 bits match. If all iterations match, FM hardware is present. Port $3E bit 2 must be set (I/O chip disabled) before reading $F2. Port $F2 bit 0 enables FM audio output; bit 1 mutes PSG (Japanese SMS only). Most games with FM support contain dual music data and switch entirely to FM for music while retaining PSG for sound effects. About 60 SMS titles contain FM support, including Phantasy Star, Wonder Boy III, Out Run, Shinobi, R-Type, and Space Harrier 3-D.
CHAPTER 055. Bank switching — Sega, Codemasters, and Korean mappers
Sega mapper (standard, ~99% of games)
Three 16 KB ROM slots plus one 8 KB RAM/ROM slot, controlled by writes to $FFFC–$FFFF
$FFFC — RAM/ROM control register:
Bit 7: ROM write enable (dev hardware only)
Bit 4: Cart RAM mapped to $C000–$FFFF (disable on-board RAM via $3E first)
Bit 3: Cart RAM enable in Slot 2 ($8000–$BFFF) — overrides $FFFF ROM banking
Bit 2: Cart RAM bank select (0=first 16KB, 1=second 16KB; most carts have 8KB mirrored)
Bits 1-0: Bank shift (added to bank values written to $FFFD–$FFFF; one revision only)$FFFD — Slot 0 bank ($0000–$3FFF, first 1 KB always bank 0). $FFFE — Slot 1 bank ($4000–$7FFF). $FFFF — Slot 2 bank ($8000–$BFFF). Default power-up state (315-5235 mapper): $FFFC=00, $FFFD=00, $FFFE=01, $FFFF=02. Bank number bit width depends on ROM size (3 bits for 128 KB, 5 for 512 KB, 6 for 1 MB); excess bits wrap via mirroring.
Codemasters mapper
Completely different scheme. Control registers at $0000, $4000, $8000 — writing to each address selects the bank for the corresponding 16 KB slot. The first 1 KB is NOT protected (can be paged out — games must handle interrupt vectors carefully). Defaults: slots 0/1/2 = banks 0/1/0. No cart RAM support except Ernie Els Golf ($4000 bit 7 = 1 maps 8 KB RAM into $A000–$BFFF). Games include Micro Machines, Fantastic Dizzy, Cosmic Spacehead, Pete Sampras Tennis, and about a dozen others.
Korean mapper variants
| Variant | Granularity | Control address(es) | Notes |
|---|---|---|---|
| Korea $A000 | 16 KB | $A000 (Slot 2 only) | Simplest; slots 0+1 fixed. Jang Pung II, Dodgeball King |
| MSX 8 KB (Konami-style) | 8 KB | $4000/$6000/$8000/$A000 | $0000–$3FFF fixed. Penguin Adventure, F-1 Spirit, Super Boy 3 |
| MSX 16 KB | 16 KB | MSX-style | $0000–$3FFF fixed. Wonder Kid proto |
| MSX 8 KB Nemesis | 8 KB | Same as MSX 8K | $0000–$1FFF always = final ROM page. Nemesis (KR) only |
| Janggun-ui Adeul | 8 KB | $FFFE/$FFFF | Bit 6 = data bit-reversal. Son of the General only |
Mapper identification
Check for "TMR SEGA" ASCII header at $7FF0 (standard), $3FF0, or $1FF0. The header includes a 16-bit checksum, BCD product code, region nibble (3=JP, 4=Export, 5–7=GG), and ROM size nibble. Export BIOS enforces valid header + checksum + region = $4. Codemasters games lack the TMR SEGA header. Korean and non-standard mappers are identified by CRC32/SHA1 database lookup in emulators — no reliable heuristic exists beyond database matching.
CHAPTER 066. I/O and controllers — D-pad, buttons, Pause NMI, Light Phaser
Standard controller reading
Port $DC (active-low, 0 = pressed)
Bit 0: Controller 1 Up Bit 4: Controller 1 Button 1
Bit 1: Controller 1 Down Bit 5: Controller 1 Button 2
Bit 2: Controller 1 Left Bit 6: Controller 2 Up
Bit 3: Controller 1 Right Bit 7: Controller 2 DownPort $DD (active-low)
Bit 0: Controller 2 Left Bit 4: Reset button
Bit 1: Controller 2 Right Bit 5: CONT (cartridge detect)
Bit 2: Controller 2 Button 1 Bit 6: Port A TH pin state
Bit 3: Controller 2 Button 2 Bit 7: Port B TH pin stateBits 6–7 of $DD serve dual purposesthey reflect the TH pin state (from Light Phaser sensor or region detection feedback). The Reset button (bit 4) is software-polled, not a hardware reset; absent on SMS2 and Game Gear (reads 1).
Pause button
Wired directly to Z80 /NMI → jumps to $0066. Edge-triggered, one interrupt per press. Not readable via any port. Games must implement their own toggle flag in the NMI handler. Every game must have valid code at $0066 since NMI cannot be masked.
Light Phaser protocol
The Light Phaser's photosensor connects to the TH pin (controller port pin 7). When the sensor detects CRT beam brightness, TH goes LOW. The VDP hardware automatically latches the H counter (port $7F) when TH transitions high-to-low. The trigger maps to TL (Button 1, bit 4 of $DC).
The detection algorithm(1) check trigger via $DC bit 4, (2) render a bright detection frame, (3) poll $DD bit 6/7 for TH transition to 0, (4) read H counter ($7F) for X position (hardware-latched), (5) read V counter ($7E) for Y position. Port $3F must configure TH as input ($FF) for the phaser to function. Horizontal accuracy is excellent due to the hardware latch; vertical accuracy depends on software polling speed.
Paddle and Sports Pad
The paddle controller (HPD-200) transmits an 8-bit position as two 4-bit nibbles over the D-pad lines, with TR (bit 5 of $DC) indicating which nibble is active, clocked by an internal 8 kHz crystal. The Sports Pad (trackball) uses TH/TR as strobe/handshake lines, transmitting relative X/Y displacement as nibbles. Both use $3F to configure pin directions.
CHAPTER 077. Recompilation notes — critical quirks for REFORGE
VDP command word timing
Writing the first byte of a VDP command word immediately updates the low 8 bits of the address register — before the command code in the second byte is known. Some games exploit this for fast partial-address updates. A static recompiler must track the write-toggle state and handle the address register update on the first write, not defer it to the second.
Sprite overflow flag and collision
The sprite overflow flag (status bit 6) sets when more than 8 sprites fall on any scanline — even if display is blanked (R1 bit 6 = 0). Some games use this as a raster-position signal or collision proxy. The collision flag (bit 5) sets when any two non-transparent sprite pixels overlap, with no identification of which sprites collided. Both flags persist until the status register is read, so a missed read means the condition is sticky across frames.
Line interrupt internal counter
The line counter's behavior is asymmetricit decrements during active display + 1 line (lines 0–192) and reloads from R10 during all remaining VBlank lines. Critically, the counter fires on underflow from $00→$FF, not when it equals zero. Writing R10 mid-frame does not reset the internal counter — it only affects the reload value used on subsequent reload events. Games that change R10 mid-frame (for split-screen effects) rely on this deferred-reload behavior.
Vertical scroll wrap at 224
In 192-line mode, vertical scroll wraps at 224, not 256. Values 224–255 produce garbage rows because the nametable is only 28 rows tall. A recompiler must apply vscroll % 224 when computing nametable row indices in 192-line mode. In 224/240-line modes the nametable is 32 rows and wraps at 256.
VDP data port read-ahead buffer
The one-byte read-ahead buffer means the first VRAM read after setting the address returns stale data (the prefetched byte from the previous address). This is by design. Games that read VRAM typically discard the first byte or account for the +1 offset. CRAM writes go through the same data port but bypass the buffer. After a CRAM write, the written value replaces the read buffer contents.
The first 1 KB protection and recompilation implications
The Sega mapper's immutable first 1 KB ($0000–$03FF) guarantees that RST vectors ($0000, $0008, $0010, $0018, $0020, $0028, $0030, $0038) and the NMI handler entry ($0066) are always reachable regardless of Slot 0 banking. A static recompiler can treat this region as fixed code that never changes. The Codemasters mapper does not have this protection — Slot 0 banking can page out interrupt vectors, so Codemasters games must ensure bank 0 is mapped during any interrupt-enabled period, or place handlers in RAM.
EI delay and interrupt acceptance
EI does not take effect until after the next instruction completes. The canonical pattern EI; RET ensures interrupts fire only after the return address is popped. A recompiler must not check for pending interrupts between EI and the following instruction. Similarly, EI; HALT guarantees the CPU enters HALT before any interrupt can fire, so the HALT is always executed.
VDP status register read side effects
Reading $BF clears all three flag bits (VBlank, overflow, collision) simultaneously and resets the write toggle. Games that need to preserve flags across multiple status checks cannot re-read the register. This single-read-clears-all behavior means a recompiler must precisely track when the status register is read and correctly clear all flags at that point. A missed or extra status read will desynchronize interrupt delivery.
Scanline-accurate VDP rendering
Many SMS games use mid-frame register changes for effects: horizontal scroll changes per scanline (parallax), palette changes via CRAM writes during HBlank, display enable/disable for overscan tricks, and line counter changes for split-screen. A recompiler targeting accuracy must render on a per-scanline basis (228 T-states per line) and apply register changes at the correct scanline boundary. The vertical scroll register is the exception — it is latched at VBlank only and immune to mid-frame writes.
Bank switch detection in recompiled code
For static recompilation, writes to $FFFC–$FFFF (Sega mapper) or $0000/$4000/$8000 (Codemasters) must be intercepted and cause the corresponding code region to be remapped. Since $FFFC–$FFFF are in the RAM mirror, any store instruction targeting those addresses must trigger mapper logic. The recompiler should generate guard checks around stores to the $E000–$FFFF region (or maintain a write handler for that range) to catch mapper-control writes without penalizing every RAM store.