RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression (272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps, and all dump-derived textures/traces) is excluded via .gitignore and stays local. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Simulation Area
Testbenches, traces, vectors, and golden-reference comparison artifacts.
This directory exists early on purpose — retroDE_ps2 should grow with
validation infrastructure from the beginning rather than bolting it on
later.
Running
Driver is a plain Makefile. From this directory:
make lint # verilator --lint-only over all RTL (strict)
make tb_ee_fetch # build + run Milestone B chain via iverilog
make tb_gs # build + run gs_stub TB
make tb_intc # build + run intc_stub TB (incl. ack+inject collision guard)
make tb_platform_video # build + run Milestone A integration (gs_stub → platform_video)
make tb_bgcolor_via_dma # build + run Milestone A+ (DMAC → GIF → GS → platform)
make tb_sif_mailbox # build + run sif_mailbox_stub unit TB
make tb_sif_command_echo # build + run EE↔peer command-echo integration TB
make tb_sif_command_echo_rearm # build + run re-armable two-command TB
make tb_sif_negative_path # build + run stall/malformed-lifecycle TB
make tb_sif_dma_smoke # build + run first SIF DMA receive test (ch5)
make tb_sif_dma_mid_stall # build + run mid-transfer backpressure test
make tb_sif_dma_overflow # build + run full-stop capacity backpressure test
make tb_sif_combined_ctrl_data # build + run first combined ctrl+data SIF handshake
make tb_iop_ram # build + run first IOP-side primitive (iop_ram_stub)
make tb_iop_memory_map # build + run IOP memory map decode + routing
make tb_iop_fetch_through_map # build + run IOP fetch → map → RAM integration
make tb_sif_iop_bridge_smoke # build + run SIF-to-IOP-RAM bridge write test
make tb_sif_iop_bridge_exec # build + run execution-visible bridge test
make tb_iop_to_sif_via_map # build + run IOP-reaches-SIF-via-real-map
make tb_iop_dmac_via_map # build + run IOP DMAC ch9 through the real map
make tb_sif_ee_landing_via_dmac # build + run first reverse-direction SIF DMA
make tb_sif_iop_driven_combined # build + run IOP-driven combined ctrl+data handshake
make tb_ee_dmac_intc # build + run first EE DMAC completion → INTC flow
make tb_iop_dmac_intc # build + run IOP DMAC ch9 → IOP INTC through real map
make tb_iop_self_driven # build + run first self-driven IOP transaction (RAM-backed)
make tb_iop_autonomous_two_xfers # build + run first autonomous two-transaction script (BNE loop)
make tb_iop_core_basic # build + run first real MIPS-decode IOP core program
make tb_iop_core_interrupts # build + run first exception-driven IOP program (COP0 + RAM ISR)
make tb_iop_core_bootstrap # build + run first BIOS-sourced bootstrap (Phase 1, no IRQs)
make tb_iop_core_bootstrap_intc # build + run BIOS-sourced interrupt flow (Phase 2)
make tb_iop_core_bios_smoke # build + run strict-mode BIOS smoke (halts on first unsupported opcode)
make tb_iop_core_bios_smoke BIOS=/path/to/bios.hex # same TB, real BIOS image
make tb_ee_core_basic # build + run first EE-side real-decode program
make tb_ee_core_memops # build + run EE CPU→RAM SW/LW round-trip
make tb_ee_core_dmac # build + run EE core programming DMAC ch2 via SW
make tb_ee_core_dmac_poll # build + run EE core polling CHCR + DONE_COUNT
make tb_ee_core_dmac_intc # build + run EE DMAC → INTC → exception → ISR → RFE
make tb_ee_core_bios_smoke # build + run EE strict-mode BIOS smoke (halts on first unsupported opcode)
make tb_ee_core_bios_smoke BIOS=/path/to/bios.hex # same TB, real BIOS image
make tb_ee_core_slti # build + run SLTI/SLTIU coverage (first real-BIOS-driven growth)
make tb_ee_core_addi # build + run ADDI coverage
make tb_ee_core_andi # build + run ANDI coverage
make tb_ee_core_rtype_logic # build + run R-type AND/OR/XOR/NOR coverage
make tb_ee_core_sb # build + run SB byte-store coverage
make tb_ee_core_lb # build + run LB sign-extended byte-load coverage
make tb_ee_core_jal # build + run JAL jump-and-link coverage
make tb_ee_core_rtype_addu # build + run R-type ADDU/SUBU coverage
make tb_ee_core_slt # build + run R-type SLT/SLTU coverage
make tb_ee_core_lh # build + run LH sign-extended halfword-load coverage
make tb_ee_core_lhu # build + run LHU zero-extended halfword-load coverage
make tb_ee_core_shift # build + run SLL/SRL/SRA shift-family coverage
make tb_ee_core_sh # build + run SH halfword-store coverage
make tb_ee_core_jalr # build + run JALR register-indirect call coverage
make tb_ee_core_add_sub # build + run R-type ADD/SUB coverage (overflow-trap deferred)
make tb_ee_core_cop0_count # build + run COP0 Count (reg 9) coverage
make run # all fifty-three TBs in sequence
make all_checks # lint + run (RTL + benches; the default gate)
make full_checks # all_checks + test_compare (widest regression)
# Golden-reference harness (Milestone B track) — see sim/golden/
make golden_nop # generate the NOP-sled golden trace
make compare_ee_fetch # diff RTL ee_fetch.trace against NOP-sled golden
make test_compare # exercise all three documented exit-code paths
make traces # list trace files under sim/traces/rtl/
make clean # wipe build/ and traces/
make all_checks is the quality gate. make lint alone does not compile
the testbenches — it only runs Verilator over RTL. Treat "lint green" as
necessary-but-not-sufficient.
Toolchain used locally:
iverilog(Icarus Verilog 12+) for running testbenches. Event-driven, handles@(posedge)/@(negedge)/forcecleanly.verilator(5.x, built with--timingsupport) for strict lint.
The split is deliberate: Verilator's lint is strict and fast; iverilog's event-driven scheduler is the right fit for stimulus-heavy testbenches with timing-control tasks.
Expected output
[tb_ee_fetch_stub] ifetch_count=32 unmapped_count=1 errors=0
[tb_ee_fetch_stub] PASS
[tb_gs_stub] bgcolor_events=2 mode_events=1 errors=0
[tb_gs_stub] PASS
[tb_intc_stub] source_assertion=3 register_write=2 ack=4 errors=0
[tb_intc_stub] PASS
[tb_platform_video_stub] frames=4 active_pixels=150 hsync_pulses=34 vsync_pulses=4 errors=0
[tb_platform_video_stub] PASS
[tb_bgcolor_via_dma] mem_writes=2 mem_reads_dmac=2 map_reads_dmac=2 dma_cfg=3 dma_start=1 dma_beat=2 dma_done=1 giftag=2 gs_bgcolor=2 frames=3 post_frames=2 last0=0 last1=1 bg=(ff,00,00) errors=0
[tb_bgcolor_via_dma] PASS
[tb_sif_mailbox_stub] ee_writes=4 iop_writes=3 ee_reads=3 iop_reads=4 errors=0
[tb_sif_mailbox_stub] PASS
[tb_sif_command_echo] peer_done=1 wait_cycles=8 ee_side_writes=2 iop_side_writes=2 errors=0
[tb_sif_command_echo] PASS
[tb_sif_command_echo_rearm] ack_count=2 ee_writes=6 iop_writes=4 captured={...} errors=0
[tb_sif_command_echo_rearm] PASS
[tb_sif_negative_path] phase_a_ack=0 phase_b_ack=1 phase_c_ack=2 errors=0
[tb_sif_negative_path] PASS
[tb_sif_dma_smoke] stall_rx=0 stall_done=0 rx_count=4 dma_beats=4 dma_done=1 sif_writes=4 last_seen=1 errors=0
[tb_sif_dma_smoke] PASS
[tb_sif_dma_mid_stall] stalled_beats=2 stalled_done=0 final_beats=4 rx=4 beat2_src=0x320 last=1 errors=0
[tb_sif_dma_mid_stall] PASS
[tb_sif_dma_overflow] settle_beats=2 settle_rx=2 full=1 done=0 beat_idx=2 errors=0
[tb_sif_dma_overflow] PASS
[tb_sif_combined_ctrl_data] pre_dma_acks=0 ack_count=1 dma_done=1 peer_writes=2 last_seen_cy=74 smcom_wr_cy=79 errors=0
[tb_sif_combined_ctrl_data] PASS
[tb_iop_ram_stub] iop_reads=5 iop_writes=3 iop_region_hits=8 errors=0
[tb_iop_ram_stub] PASS
[tb_iop_memory_map_stub] map_reads_ram=4 map_writes_ram=2 map_unmapped=2 master_mismatches=0 errors=0
[tb_iop_memory_map_stub] PASS
[tb_iop_fetch_through_map] ifetches=22 map_routed=22 map_iop_cpu_tagged=22 errors=0
[tb_iop_fetch_through_map] PASS
[tb_sif_iop_bridge_smoke] dma_done=1 bridge_writes_map=8 bridge_writes_ram=8 errors=0
[tb_sif_iop_bridge_smoke] PASS
[tb_sif_iop_bridge_exec] dma_done=1 bridge_writes=8 ifetches=16 iop_cpu_reads=16 mismatches=0 errors=0 last_bw_cy=27 first_fetch_cy=48
[tb_sif_iop_bridge_exec] PASS
[tb_iop_to_sif_via_map] map_sif=4 map_ram=2 map_unmapped=1 mbx_iop_side=4 errors=0
[tb_iop_to_sif_via_map] PASS
[tb_iop_dmac_via_map] map_dmac=9 map_ram_dma=8 cfg=6 start=2 beat=8 done=2 stall_cy=9 errors=0
[tb_iop_dmac_via_map] PASS
[tb_sif_ee_landing_via_dmac] start=1 beat=16 done=1 ee_map_writes=4 iop_map_dma_reads=16 stall_cy=20 stall_beats=6 errors=0
[tb_sif_ee_landing_via_dmac] PASS
[tb_sif_iop_driven_combined] start=1 beat=16 done=1 ee_map_writes=4 pre_dma_ack=0 ack=1 errors=0
[tb_sif_iop_driven_combined] PASS
[tb_ee_dmac_intc] done=2 asserts=2 acks=2 errors=0
[tb_ee_dmac_intc] PASS
[tb_iop_dmac_intc] done=2 asserts=2 acks=2 map_intc_events=9 errors=0
[tb_iop_dmac_intc] PASS
[tb_iop_self_driven] stall_pc=4 ops=10 wait_irq_exit=1 halt=1 done=1 asserts=1 acks=1 errors=0
[tb_iop_self_driven] PASS
[tb_iop_autonomous_two_xfers] ops=16 wait_irq_exit=2 bne_taken=1 halt=1 done=2 asserts=2 acks=2 errors=0
[tb_iop_autonomous_two_xfers] PASS
[tb_iop_core_basic] retired=38 branches_taken=3 delay_slots=3 halt=1 done=2 asserts=2 acks=2 errors=0
[tb_iop_core_basic] PASS
[tb_iop_core_interrupts] retired=49 branches_taken=4 delay_slots=4 except=2 rfe=2 halt=1 done=2 asserts=2 acks=2 intc_stat_polls=0 errors=0
[tb_iop_core_interrupts] PASS
[tb_iop_core_bootstrap] retired=19 taken=2 delay=2 halt=1 done=1 bios_reads=19 reset_reads=1 errors=0
[tb_iop_core_bootstrap] PASS
[tb_iop_core_bootstrap_intc] retired=51 taken=5 delay=5 except=2 rfe=2 halt=1 done=2 acks=2 bios_reads=35 reset_reads=1 intc_stat_polls=0 errors=0
[tb_iop_core_bootstrap_intc] PASS
[tb_iop_core_bios_smoke] retired=11 trap=1 trap_pc=0xbfc00120 trap_instr=0x00432824 halt=0 except=0 done=0 bios_reads=11 hist(lui,ori,addiu,sw,lw,j,other)=(1,4,1,2,0,1,1) errors=0
[tb_iop_core_bios_smoke] PASS
[tb_ee_core_basic] retired=9 taken=2 delay=2 halt=1 except=0 trap=0 bios_reads=9 errors=0
[tb_ee_core_basic] PASS
Trace files land in sim/traces/rtl/:
ee_fetch.trace— EE fetch-side events (one RESET + one IFETCH per fetch)ee_mem.trace— memory-map request-time events (READ / UNMAPPED)ee_bios.trace— BIOS ROM completion-time eventsgs.trace— BGCOLOR / MODE register writes (tb_gs_stub)intc.trace— IRQ state transitions (includes the collision regression trace)milestone_a_gs.trace— GS events during Milestone A integration TBmilestone_a_plat.trace— platform frame-complete events during Milestone A TBbgcolor_via_dma_ram.trace— EE RAM write + delivery-time reads (Wave 2.5+)bgcolor_via_dma_map.trace— map-layer request-routing events (Wave 2.7)bgcolor_via_dma_dmac.trace— DMAC CFG/START/BEAT/DONE eventsbgcolor_via_dma_gif.trace— GIF tag eventsbgcolor_via_dma_gs.trace— GS BGCOLOR write eventsbgcolor_via_dma_plat.trace— platform frames during transfersif_mailbox.trace— SIF mailbox/flag reads and writes with side_id (EE vs. IOP)sif_command_echo.trace— EE↔peer command-echo exchange on the mailboxsif_command_echo_rearm.trace— two-command re-armable exchange with explicit clearsif_negative_path.trace— stall / malformed-lifecycle / recovery sequencesif_dma_ram.trace— RAM traffic during SIF DMA smoke testsif_dma_map.trace— memory-map routing for DMAC ch5 fetchessif_dma_dmac.trace— DMAC ch5 CFG/START/BEAT/DONEsif_dma_sif.trace— SIF-side DMA receive eventssif_dma_mid_stall_*.trace— mid-transfer stall variant traces (ram/map/dmac/sif)iop_dmac_via_map.trace— IOP map routing for DMAC ch9 accesses (region=IOP_DMAC)iop_dmac_reg.trace— IOP DMAC ch9 CFG/START/BEAT/DONE eventsee_landing_iop_map.trace— IOP map routing during reverse-direction transfer (CPU programming + DMA source reads)ee_landing_ee_map.trace— EE map routing for bridge writes into EE RAM (master_id=5)ee_landing_dmac.trace— IOP DMAC ch9 CFG/START/BEAT/DONE during full-chain transferiop_driven_iop_map.trace— IOP map routing during IOP-driven combined handshake (CPU ops + DMA reads)iop_driven_ee_map.trace— EE map bridge-write events during the combined handshakeiop_driven_dmac.trace— IOP DMAC ch9 events during the combined handshakeiop_driven_mailbox.trace— mailbox reads/writes tagged by side (IOP doorbell + EE-side combiner ack)ee_dmac_intc_*.trace— EE DMAC → INTC completion-visibility flow (DMAC + INTC events)iop_intc_*.trace— IOP DMAC ch9 → IOP INTC flow via real map (iop_map/dmac/intc)iop_self_driven_exec.trace— scripted IOP executor op timeline (one event per op)iop_self_driven_map.trace— IOP map routing during the self-driven flowiop_self_driven_dmac.trace— IOP DMAC ch9 events during the self-driven flowiop_self_driven_intc.trace— IOP INTC events (assertion + ack) during the self-driven flowiop_two_xfers_*.trace— RAM-backed BNE-looping two-transaction flow (exec/map/dmac/intc)iop_core_basic_*.trace— first real MIPS-decode run (core/map/dmac/intc); one IFETCH event per retired instruction with delay-slot and branch-taken flagsiop_core_intr_*.trace— exception-driven run with COP0 + RAM-resident ISR; core trace flags include exception-taken (bit 5) and RFE (bit 6) retiresiop_boot_*.trace— first BIOS-sourced IOP bootstrap; map trace shows BIOS region (6) reads starting at 0xBFC0_0000iop_boot_intc_*.trace— BIOS-sourced bootstrap that enables interrupts and vectors to a RAM-resident ISR (full reset-source + exception-entry story in one timeline)iop_bios_smoke_*.trace— strict-mode BIOS smoke; core retire trace flags bit 7 on the halt cycle; replace TB's synthetic BIOS preload with a real dump to drive targeted core growthee_core_basic_*.trace— first EE-side real-decode run; core (SUBSYS_EE) + map streams; mirror of the IOPtb_iop_core_basicstory on the EE execution path
Contents
Makefile— build/run/lint driver.build/— iverilog compile artifacts (gitignore candidate).tb/— testbenches, one subdirectory per subsystem.traces/— captured traces.traces/rtl/is simulation output;traces/golden/is reserved for emulator-side traces once the golden-reference harness lands.vectors/— deterministic stimulus inputs.golden/— scripts + notes for emulator comparison, plusbin_to_hex.pyfor the real-BIOS iteration loop (seesim/golden/README.md).
Real-BIOS iteration
The tb_iop_core_bios_smoke TB doubles as the iteration harness for
growing iop_core_stub's instruction surface against a real Sony BIOS
dump. Per third_party/LICENSING.md no BIOS image is shipped with the
repository; the user supplies their own.
The loop is:
-
Convert your
.bindump to the text hex format iverilog's$readmemhexpects (one 32-bit little-endian word per line, no 0x prefix, no commas):python3 sim/golden/bin_to_hex.py path/to/ps2_bios.bin \ -o /tmp/ps2_bios.hexThe utility rejects inputs larger than the stub's 4 MiB window and pads shorter inputs with NOPs.
-
Run the smoke TB with the
BIOS=Makefile variable. The resolved absolute path is forwarded to vvp as+BIOS=…and the TB skips its synthetic bootstrap in favour of$readmemhinto the BIOS memory:make -C sim tb_iop_core_bios_smoke BIOS=/tmp/ps2_bios.hexThe TB runs with
STRICT_UNSUPPORTED=1, halts on the first opcodeiop_core_stubdoesn't implement, and prints a single report line:[tb_iop_core_bios_smoke] retired=<N> trap=1 trap_pc=0x… trap_instr=0x… mnemonic=<op> halt=0 except=0 done=0 bios_reads=<N> hist(lui,ori,addiu,sw,lw,j,other)=(…) errors=0 -
Add the smallest useful opcode family around that
mnemonic(e.g. if it'sand, add AND/OR/XOR/NOR together because they share SPECIAL-R-type decode cheaply). -
Rerun step 2. The next trap is the next opcode to add.
Running the target without BIOS= preserves the CI-deterministic
synthetic path (the committed make run gate). make full_checks
continues to exercise only the synthetic variant.
Notes for later waves
- Warnings about
$error/$fdisplayinalways_ffare expected — iverilog flags them as "cannot be synthesized" which is correct for simulation-only constructs. Ignore. - Trace format and cycle-counting conventions are documented in
docs/decisions/0000-trace-format.mdandrtl/debug/README.md. - Trace files are git-ignored candidates; regenerate via
make run.