← Blog

sigilOS Retro: Sega Saturn — System #43, SH-2, and VDP2

June 22, 2026 · sigil-retropie · Sigil-Docs
retropie saturn sh-2 vdp2 0.6.0

The Sega Saturn is system #43 in the sigilOS RetroPie launcher. It's the most architecturally significant addition yet: the Saturn required a new CPU core from scratch. Every other system added so far has reused an existing CPU — the 6502, the Z80, the 68000, the MIPS. The Saturn's Hitachi SH-2 is none of those. It's a 32-bit RISC with fixed-width 16-bit instructions, a big-endian register file, and a delay-slot branching model that took careful implementation to get right. uart=50 PASS.

CPUHitachi SH-2 @ ~28 MHz (×2 in real hardware) ROM512KB boot ROM ($00000000) Work RAM2MB ($06000000) VDP2 shadow2KB register block ($05E00000) Output320×224 ARGB32 Frame budget47,633 SH-2 steps EL0 blob25,880 B ROM cap512KB (SECURE)

The SH-2 core

cores/sh2.sg implements the Hitachi SH-2 instruction set in the same pattern as the other CPU cores in the launcher — a step function (sh2_step) that reads one instruction from the program counter, decodes it, and updates the register file and memory. The SH-2's fixed 16-bit instruction width makes the fetch loop clean: every instruction is exactly two bytes, there are no variable-length prefixes.

Register file

The SH-2 has 16 general-purpose registers (R0–R15) plus a set of control registers:

Delay slots

The SH-2's branch instructions — BRA, BSR, BT-S, BF-S, JMP, JSR, RTS — all have a delay slot: the instruction immediately following the branch executes before the branch takes effect. This is the SH-2's most implementation-sensitive feature. Get it wrong and the branch target is right but the post-branch state is corrupted.

sh2.sg implements delay slots with two state fields: dly_active (bool) and dly_tgt (u32 — the branch target). At the top of sh2_step, before instruction fetch, the delay slot is checked:

if dly_active:
    dly_active = 0
    pc = dly_tgt
    return        # branch takes effect after the delay instruction executed

A delay-slot branch sets dly_active = 1 and dly_tgt = target but does not modify PC. The current instruction (the one in the delay slot) then executes at the current PC. On the next sh2_step call, the check at the top fires, PC becomes dly_tgt, and the branch has taken effect. The instruction in the delay slot ran once between those two steps — exactly the SH-2's specified behaviour.

Instruction coverage

The first increment covers the subset needed to execute a meaningful boot sequence and frame render loop:


The Saturn bus and VDP2

cores/saturn.sg is linked before sh2.sg, which means its definitions of sh2_mem_r32, sh2_mem_w32 (and the 8/16-bit variants) take precedence via first-def-wins. The Saturn's memory map:

AddressRegionSize
$00000000Boot ROM512KB
$05E00000VDP2 register shadow2KB
$06000000Work RAM-L2MB

Reads and writes outside these ranges return 0 / discard silently — a safe default for a first increment where most of the Saturn's peripherals (VDP1, SCSP audio, SH-2 DMA, CD block) are not yet modelled.

VDP2 backdrop

The Saturn's VDP2 is the background processor — scroll planes, rotation backgrounds, colour arithmetic. For a first-increment core, the backdrop colour is the right starting point: it's always present, it requires no tilemap data, and it exercises the VDP2 register shadow correctly.

The backdrop colour register lives at VDP2 offset $E0 (absolute $05E000E0). It stores an RGB555 value: 5 bits each of red, green, and blue packed into 16 bits. sat_backdrop_argb unpacks this to ARGB32 with the standard 5-bit scale (each channel × 8 + channel >> 2 for rounding). sat_render fills the 320×224 framebuffer with this colour for every pixel.

Boot sequence

core_load copies the 512KB ROM image into sat_brom, clears work RAM and the VDP2 shadow to zero, then calls sh2_reset. The SH-2's reset vector is two 32-bit words at the start of the ROM: $00000000 holds the initial stack pointer (loaded into R15), and $00000004 holds the initial PC. sh2_reset reads these two values and sets the register file accordingly — the same pattern as every other console that stores its reset vector in ROM.


Test coverage

tests/saturn_core.sg (uart=50) verifies: