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:
2026-06-29 20:10:50 -04:00
commit ec82764bef
2462 changed files with 2174303 additions and 0 deletions
+181
View File
@@ -0,0 +1,181 @@
// retroDE_ps2 — ee_dmac_passive_chan_stub
//
// Ch288 — Lightweight per-channel register surface for the EE DMAC
// channels NOT covered by a dedicated transfer-FSM stub. Hosts the
// four standard per-channel registers (CHCR/MADR/QWC/TADR) for each
// covered channel; reset to zero, writes latch, reads return the
// latched value. NO transfer FSM, NO start-bit side effects, NO
// D_STAT interaction. This is the "init-time channel-clear / quiet
// register surface" Codex framed for Ch288.
//
// Channels covered (4 KiB window each, starting at 0x1000_8000):
// ch0 (VIF0) 0x1000_8000-0x1000_8FFF
// ch1 (VIF1) 0x1000_9000-0x1000_9FFF
// ch3 (IPU_FROM) 0x1000_B000-0x1000_BFFF
// ch4 (IPU_TO) 0x1000_C000-0x1000_CFFF ← qbert's first hit
// ch5 (SIF0) 0x1000_D000-0x1000_DFFF
//
// SKIPPED:
// ch2 (GIF) 0x1000_A000-0x1000_AFFF — already routed
// externally to dmac_reg_stub via the map's
// ee_dmac_ch2_* ports. Do NOT shadow it here.
//
// Channel index extracted from chan_addr[15:12]:
// 0x8 → ch0, 0x9 → ch1, 0xB → ch3, 0xC → ch4, 0xD → ch5
// (0xA / ch2 is filtered by the caller; if chan_addr[15:12]==0xA
// arrives here the module silently drops it.)
//
// Register offsets (chan_addr[11:0], matches dmac_reg_stub layout):
// 0x00 CHCR — control (start bit at [0]); latched, no FSM
// 0x10 MADR — main address
// 0x20 QWC — quadword count
// 0x30 TADR — tag address
// any other offset: read = 0, write dropped + traced
`timescale 1ns/1ps
module ee_dmac_passive_chan_stub
import trace_pkg::*;
(
input logic clk,
input logic rst_n,
// Write port. chan_addr is the 16-bit offset into the entire
// 0x1000_8000-base window: chan_addr[15:12] = channel selector,
// chan_addr[11:0] = register offset within that channel.
input logic reg_wr_en,
input logic [15:0] chan_addr,
input logic [31:0] reg_wr_data,
// Read port (1-cycle latency).
input logic reg_rd_en,
output logic [31:0] reg_rd_data,
output logic reg_rd_valid,
// Trace
output logic ev_valid,
output subsys_e ev_subsys,
output event_e ev_event,
output logic [63:0] ev_arg0,
output logic [63:0] ev_arg1,
output logic [63:0] ev_arg2,
output logic [63:0] ev_arg3,
output logic [31:0] ev_flags
);
localparam logic [11:0] CHCR_OFFSET = 12'h000;
localparam logic [11:0] MADR_OFFSET = 12'h010;
localparam logic [11:0] QWC_OFFSET = 12'h020;
localparam logic [11:0] TADR_OFFSET = 12'h030;
// Channel index from the high nibble of chan_addr. Out-of-range
// nibbles (anything outside 0x8/0x9/0xB/0xC/0xD) get
// chan_valid=0 and the access is dropped.
logic [3:0] chan_nibble;
logic [2:0] chan_idx; // 0..4 packed: 0=ch0, 1=ch1, 2=ch3, 3=ch4, 4=ch5
logic chan_valid;
always_comb begin
chan_nibble = chan_addr[15:12];
chan_idx = 3'd0;
chan_valid = 1'b0;
unique case (chan_nibble)
4'h8: begin chan_idx = 3'd0; chan_valid = 1'b1; end // ch0
4'h9: begin chan_idx = 3'd1; chan_valid = 1'b1; end // ch1
4'hB: begin chan_idx = 3'd2; chan_valid = 1'b1; end // ch3
4'hC: begin chan_idx = 3'd3; chan_valid = 1'b1; end // ch4
4'hD: begin chan_idx = 3'd4; chan_valid = 1'b1; end // ch5
default: ;
endcase
end
logic [11:0] reg_offset;
assign reg_offset = chan_addr[11:0];
// ------------------------------------------------------------------
// Register file: 5 channels × 4 registers
// ------------------------------------------------------------------
logic [31:0] chcr [0:4];
logic [31:0] madr [0:4];
logic [31:0] qwc [0:4];
logic [31:0] tadr [0:4];
always_ff @(posedge clk) begin
if (!rst_n) begin
for (int i = 0; i < 5; i++) begin
chcr[i] <= 32'd0;
madr[i] <= 32'd0;
qwc[i] <= 32'd0;
tadr[i] <= 32'd0;
end
end else if (reg_wr_en && chan_valid) begin
unique case (reg_offset)
CHCR_OFFSET: chcr[chan_idx] <= reg_wr_data;
MADR_OFFSET: madr[chan_idx] <= reg_wr_data;
QWC_OFFSET: qwc[chan_idx] <= reg_wr_data;
TADR_OFFSET: tadr[chan_idx] <= reg_wr_data;
default: ;
endcase
end
end
// Read mux (1-cycle latency). Returns 0 for invalid channel /
// unknown offset.
always_ff @(posedge clk) begin
if (!rst_n) begin
reg_rd_data <= 32'd0;
reg_rd_valid <= 1'b0;
end else begin
reg_rd_valid <= reg_rd_en;
if (reg_rd_en && chan_valid) begin
unique case (reg_offset)
CHCR_OFFSET: reg_rd_data <= chcr[chan_idx];
MADR_OFFSET: reg_rd_data <= madr[chan_idx];
QWC_OFFSET: reg_rd_data <= qwc[chan_idx];
TADR_OFFSET: reg_rd_data <= tadr[chan_idx];
default: reg_rd_data <= 32'd0;
endcase
end else if (reg_rd_en) begin
reg_rd_data <= 32'd0; // invalid channel
end
end
end
// ------------------------------------------------------------------
// Trace — write priority over read; tagged SUBSYS_DMAC with
// arg0 = chan_nibble (0x8/0x9/0xB/0xC/0xD = phys channel), arg1
// = data, arg2 = reg_offset, arg3 = chan_idx (packed 0..4).
// ------------------------------------------------------------------
always_ff @(posedge clk) begin
if (!rst_n) begin
ev_valid <= 1'b0;
ev_subsys <= SUBSYS_DMAC;
ev_event <= EV_WRITE;
ev_arg0 <= 64'd0;
ev_arg1 <= 64'd0;
ev_arg2 <= 64'd0;
ev_arg3 <= 64'd0;
ev_flags <= 32'd0;
end else if (reg_wr_en) begin
ev_valid <= 1'b1;
ev_subsys <= SUBSYS_DMAC;
ev_event <= EV_WRITE;
ev_arg0 <= {60'd0, chan_nibble};
ev_arg1 <= {32'd0, reg_wr_data};
ev_arg2 <= {52'd0, reg_offset};
ev_arg3 <= {61'd0, chan_idx};
ev_flags <= {31'd0, chan_valid};
end else if (reg_rd_en) begin
ev_valid <= 1'b1;
ev_subsys <= SUBSYS_DMAC;
ev_event <= EV_READ;
ev_arg0 <= {60'd0, chan_nibble};
ev_arg1 <= 64'd0;
ev_arg2 <= {52'd0, reg_offset};
ev_arg3 <= {61'd0, chan_idx};
ev_flags <= {31'd0, chan_valid};
end else begin
ev_valid <= 1'b0;
end
end
endmodule : ee_dmac_passive_chan_stub