Initial commit: retroDE_ps2 — first-of-its-kind PS2 GS FPGA core (DE25-Nano / Agilex 5)
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>
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
# rtl/iop
|
||||
|
||||
IOP subsystem. Matches `docs/contracts/iop.md`.
|
||||
|
||||
## Current contents
|
||||
|
||||
- `iop_ram_stub.sv` — 32-bit IOP RAM primitive. Default 16 KiB,
|
||||
parameterizable. Read + write ports with byte-enable granularity,
|
||||
one-cycle read latency, caller-provided `master_id` for trace
|
||||
attribution. Emits trace events under `SUBSYS_IOP`.
|
||||
- `iop_memory_map_stub.sv` — IOP-side address decode. CPU-side port uses
|
||||
kseg0/kseg1 stripping (`phys = iop_addr[28:0]`). Second write-master
|
||||
port for DMA bridges (`bridge_wr_*`), physical addressing. Regions now
|
||||
decoded:
|
||||
- IOP RAM (phys 0x00000000-0x001FFFFF) → `iop_ram_stub`
|
||||
- SIF registers (phys 0x1D000000 block) → SIF register shell
|
||||
(`sif_mailbox_stub` IOP side) via `sif_rd_*` / `sif_wr_*` ports
|
||||
- IOP DMAC channel 9 (phys 0x1F801520-0x1F80152F) → IOP DMAC
|
||||
register shell via `iop_dmac_rd_*` / `iop_dmac_wr_*` ports
|
||||
- IOP INTC (phys 0x1F801070-0x1F80107F) → `intc_stub` (IOP-side
|
||||
instance) via `iop_intc_rd_*` / `iop_intc_wr_*` ports
|
||||
- Shared BIOS ROM (phys 0x1FC00000-0x1FFFFFFF, 4 MiB) →
|
||||
`bios_rom_stub` via `bios_rd_*` port. kseg1 aliasing makes
|
||||
`0xBFC0_0000` reset fetches land here transparently. BIOS is
|
||||
read-only; writes to this window trace as UNMAPPED.
|
||||
- everything else → UNMAPPED with deterministic 0xDEADBEEF
|
||||
Future regions (other DMAC channels, IOP timers, SPU2) reserved in
|
||||
comments. Arbitration between CPU and bridge writes on RAM path:
|
||||
CPU wins on same-cycle collision. SIF, DMAC, INTC, and BIOS are
|
||||
separate ports and don't contend with RAM.
|
||||
- `iop_fetch_stub.sv` — minimal sequential 32-bit fetcher. Mirrors
|
||||
`ee_fetch_stub` in shape: PC-incrementing, no decode, no branches, no
|
||||
exceptions. Default `RESET_VECTOR` is in IOP RAM (0x00000000), NOT in
|
||||
BIOS space — explicitly non-BIOS boot. Emits `IOP RESET` once and
|
||||
`IOP IFETCH` per response. First execution-visible IOP traffic in the
|
||||
project; fetches route through `iop_memory_map_stub`.
|
||||
- `iop_core_stub.sv` — **real instruction-decoding IOP core with
|
||||
minimal COP0, asynchronous interrupt exception entry, and the
|
||||
architectural MIPS reset vector.** Tiny MIPS R3000 subset,
|
||||
multi-cycle FSM, speaks the same map / DMAC / INTC protocol as every
|
||||
previous engine. Default `PC_RESET = 0xBFC0_0000` (kseg1 into the
|
||||
shared BIOS window; override with a parameter for RAM-only tests).
|
||||
Supported opcodes:
|
||||
LUI, ORI, ADDIU, LW, SW, BEQ, BNE, J, JR (SPECIAL func 0x08),
|
||||
NOP (any other SPECIAL func / unknown opcode), SYSCALL
|
||||
(SPECIAL func 0x0C, halts), **MFC0 / MTC0 / RFE** (COP0 opcode 0x10).
|
||||
32-entry register file with `$0` hardwired.
|
||||
**COP0 subset:** Status (IE/KU triple stack + IM), Cause (ExcCode +
|
||||
IP reflecting cpu_irq), EPC. Exception entry is sampled at clean
|
||||
instruction-retire boundaries: if a delay slot is outstanding, the
|
||||
exception defers until the delay slot resolves. On entry: push IE/KU
|
||||
stack, ExcCode=0, save EPC=next_pc, PC←EXC_VECTOR (parameter).
|
||||
**Branch delay slot** honoured from day one; taken-branch and
|
||||
delay-slot retires are both flagged in the trace.
|
||||
**Strict mode:** `STRICT_UNSUPPORTED` parameter (default 0). When
|
||||
set, unsupported opcodes halt the core and latch the offending
|
||||
pc/instr word into `trap_o` / `trap_pc_o` / `trap_instr_o` instead
|
||||
of silently retiring as NOPs. The canonical NOP (`instr == 32'h0`,
|
||||
SLL $0,$0,0) is always treated as a real NOP. Retire trace flag
|
||||
bit 7 marks strict-trap retires. Used by the BIOS smoke TB; other
|
||||
benches leave it off for backwards compatibility.
|
||||
Deferred: BD bit in Cause, nested interrupts, syscall/break
|
||||
exception dispatch, R-type ALU/shifts/HI-LO.
|
||||
- `iop_exec_stub.sv` — **RAM-backed IOP execution primitive (bridge
|
||||
module).**
|
||||
Not a MIPS core, not an ISA decoder. A tiny FSM sequencer that fetches
|
||||
its micro-ops from IOP RAM through the real `iop_memory_map_stub`
|
||||
CPU-side port — the same way a future instruction-fetching CPU will.
|
||||
The control program is no longer RTL-resident; it lives as data in
|
||||
RAM that someone (a TB, eventually a BIOS loader) preloads before
|
||||
pulsing `go_i`.
|
||||
**Five opcodes**: `HALT`, `WRITE(addr, data)`, `READ(addr)`,
|
||||
`WAIT_IRQ`, `BNE(target_pc, expected)` — branch if the last READ's
|
||||
result does not equal `expected`, enabling real loops.
|
||||
Op layout in RAM: 16 bytes per op (`pc<<4` addressing). Word 0 is
|
||||
the opcode (low 4 bits), word 1 is addr or branch target, word 2 is
|
||||
data or expected value, word 3 is reserved. `SCRIPT_BASE` is a
|
||||
parameter (default 0x0000_0400).
|
||||
Takes `cpu_irq` from the IOP INTC; `WAIT_IRQ` genuinely blocks until
|
||||
a real interrupt asserts. One trace event per op completion with
|
||||
flag bits marking WAIT_IRQ exit (bit 1), HALT entry (bit 2), and
|
||||
BNE taken (bit 3). When a real MIPS decode primitive eventually
|
||||
arrives, it replaces this module while keeping the same map / DMA /
|
||||
INTC hookup verbatim.
|
||||
- `iop_dmac_reg_stub.sv` — IOP DMAC for one SIF-facing channel
|
||||
(CHANNEL=9, PATH_ID=9, MASTER_ID=4). Register surface (low-byte
|
||||
offsets): MADR @ 0x00, BCR @ 0x04, CHCR @ 0x08, DONE_COUNT @ 0x0C
|
||||
(read-only monotonic counter); start bit is CHCR[0].
|
||||
Real data path: on start, DMAC latches MADR/BCR, then steps through
|
||||
IDLE → FETCH_WAIT → ACTIVE_SEND → DONE per beat, sourcing 32-bit words
|
||||
from IOP RAM through the map's `dma_rd_*` port (src_addr stepping by
|
||||
4 per beat). Endpoint is a word-granularity ready/valid/last stream
|
||||
with `ep_ready` back-pressure — no false completion under stall.
|
||||
Emits DMA_CFG on register writes, DMA_START on arm, DMA_BEAT per
|
||||
accepted beat (with src_addr + remaining count), DMA_DONE on the
|
||||
final beat. `done_count_o` is a monotonic visible counter.
|
||||
`irq_completion_o` is a one-cycle pulse on S_DONE — wired into the
|
||||
IOP INTC as source bit 0 so software can observe channel completion.
|
||||
Only reachable through the real IOP map at 0x1F80_1520.
|
||||
|
||||
## Explicit non-goals (current step)
|
||||
|
||||
- Full MIPS R3000 ISA coverage (the core is still a narrow subset;
|
||||
strict-mode halts on the first unsupported opcode so the BIOS tells
|
||||
us what to grow next)
|
||||
- Full 2 MiB RAM sizing (stub defaults stay small for sim speed; the map
|
||||
window is 2 MiB and truncates at the connection to the smaller stub)
|
||||
- IOP I/O beyond the currently decoded regions (DMAC ch9 / INTC / BIOS);
|
||||
SPU2, timers, and other peripherals are not wired yet
|
||||
- IOP DMAC channels other than ch9 (SIF0 IOP→EE)
|
||||
- Real Sony BIOS execution (the smoke TB's synthetic bootstrap is the
|
||||
current committed content; swapping in a user-supplied dump is a
|
||||
drop-in exercise that will reveal the next missing opcode)
|
||||
|
||||
## Scope boundary
|
||||
|
||||
This directory owns IOP CPU execution, IOP-local RAM/I/O decode, IOP
|
||||
interrupt intake, IOP DMAC channels, and BIOS-side IOP boot sequencing
|
||||
behavior (per `docs/contracts/iop.md`).
|
||||
|
||||
The IOP side now runs a MIPS R3000 subset from an architecturally
|
||||
correct BIOS reset vector, with precise interrupt exception entry and
|
||||
a RAM-resident ISR. The project has crossed five architectural seams:
|
||||
1. TB-orchestrated → fabric-orchestrated (scripted exec stub)
|
||||
2. RTL-resident → RAM-resident control (exec stub reads ops from RAM)
|
||||
3. Micro-op bridge → real ISA decode (iop_core_stub)
|
||||
4. Polled completion → asynchronous exception-driven control flow
|
||||
(COP0 + cpu_irq)
|
||||
5. TB-preloaded RAM as reset source → BIOS ROM at 0xBFC0_0000
|
||||
(shared BIOS wired through the IOP map; hand-assembled bootstraps
|
||||
prove the seam before any real Sony BIOS is attempted)
|
||||
|
||||
Each seam preserved every prior module — only where code comes from
|
||||
evolved.
|
||||
|
||||
## Planned next increments
|
||||
|
||||
These are possibilities, not commitments — order will be decided per the
|
||||
next architectural question:
|
||||
|
||||
- **BIOS-driven core growth:** point `tb_iop_core_bios_smoke` at a
|
||||
user-supplied BIOS dump (swap the TB's synthetic preload for
|
||||
`$readmemh` into `u_bios.mem`), observe the first unsupported
|
||||
opcode, add it to `iop_core_stub`, repeat. Expected near-term
|
||||
additions: ANDI, ADDU/SUBU, SLL/SRL/SRA, JAL, SLT(U). Do not add
|
||||
speculatively; let the BIOS trace drive the order.
|
||||
- Core exception growth as the BIOS path demands it: BD bit in
|
||||
Cause, nested interrupts, syscall/break exception dispatch.
|
||||
- Other IOP DMAC channels (CDVD / SPU2 / DEV9 / SIF1-2 / SIO2).
|
||||
- IOP map expansion: remaining IOP I/O (0x1F800000), SPU2
|
||||
(0x1F900000).
|
||||
Reference in New Issue
Block a user