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,44 @@
|
||||
# rtl/debug
|
||||
|
||||
Trace taps, observability modules, and first-class debug infrastructure.
|
||||
|
||||
This directory is intentionally first-class per the debug/validation strategy
|
||||
in `docs/contracts/validation.md`. Nothing here is ornamental; stubs and real
|
||||
blocks alike depend on it.
|
||||
|
||||
## Wave 1 contents
|
||||
|
||||
- `trace_pkg.sv` — shared types (`subsys_e`, `event_e`) and string renderers
|
||||
used by all trace producers and `trace_sink_stub`.
|
||||
- `trace_sink_stub.sv` — simulation-only text trace writer. One instance per
|
||||
output file; each Wave 1 stub wires its event port to its own sink. See
|
||||
`docs/decisions/0000-trace-format.md` for format rationale.
|
||||
|
||||
## Usage pattern for Wave 1 testbenches
|
||||
|
||||
```systemverilog
|
||||
trace_sink_stub #(
|
||||
.FILENAME ("ee.trace"),
|
||||
.SINK_LABEL("ee_fetch")
|
||||
) u_trace_ee (
|
||||
.clk (clk),
|
||||
.rst_n (rst_n),
|
||||
.ev_valid (dut_ev_valid),
|
||||
.ev_subsys(dut_ev_subsys),
|
||||
.ev_event (dut_ev_event),
|
||||
.ev_arg0 (dut_ev_arg0),
|
||||
.ev_arg1 (dut_ev_arg1),
|
||||
.ev_arg2 (dut_ev_arg2),
|
||||
.ev_arg3 (dut_ev_arg3),
|
||||
.ev_flags (dut_ev_flags)
|
||||
);
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Cycle counter is internal to the sink and advances on `clk` while `rst_n`
|
||||
is high. Cross-clock correlation is a later-wave concern.
|
||||
- `ev_flags == 0` renders as `-` in the trace line; any non-zero value is
|
||||
printed as an 8-hex-digit field.
|
||||
- Event/subsystem codes are globally unique in Wave 1 (not per-subsystem).
|
||||
Revisit if the namespace gets crowded.
|
||||
@@ -0,0 +1,166 @@
|
||||
// retroDE_ps2 — trace package
|
||||
//
|
||||
// Defines the shared trace vocabulary used by Wave 1 stubs. Kept small on
|
||||
// purpose: subsystem IDs, event codes, and string renderers only. See
|
||||
// docs/decisions/0000-trace-format.md and docs/stub_module_plan.md.
|
||||
//
|
||||
// Contract:
|
||||
// trace line = cycle subsystem event arg0 arg1 arg2 arg3 flags
|
||||
//
|
||||
// Event codes are globally unique (not per-subsystem) in Wave 1 to keep the
|
||||
// renderer trivial. Revisit if the namespace gets crowded.
|
||||
|
||||
`ifndef RETRODE_PS2_TRACE_PKG_SV
|
||||
`define RETRODE_PS2_TRACE_PKG_SV
|
||||
|
||||
`timescale 1ns/1ps
|
||||
|
||||
package trace_pkg;
|
||||
|
||||
typedef enum logic [3:0] {
|
||||
SUBSYS_EE = 4'h0,
|
||||
SUBSYS_MEM = 4'h1,
|
||||
SUBSYS_GS = 4'h2,
|
||||
SUBSYS_INTC = 4'h3,
|
||||
SUBSYS_SIF = 4'h4,
|
||||
SUBSYS_DMAC = 4'h5,
|
||||
SUBSYS_IOP = 4'h6,
|
||||
SUBSYS_GIF = 4'h7,
|
||||
SUBSYS_PLAT = 4'h8,
|
||||
SUBSYS_OTHER = 4'hF
|
||||
} subsys_e;
|
||||
|
||||
typedef enum logic [7:0] {
|
||||
EV_RESET = 8'h00,
|
||||
EV_IFETCH = 8'h01,
|
||||
EV_READ = 8'h02,
|
||||
EV_WRITE = 8'h03,
|
||||
EV_UNMAPPED = 8'h04,
|
||||
EV_IRQ = 8'h05,
|
||||
EV_MODE = 8'h06,
|
||||
EV_BGCOLOR = 8'h07,
|
||||
// Wave 2 additions — DMAC / GIF / GS write-path visibility
|
||||
// (see docs/wave2_dma_gif_plan.md).
|
||||
EV_DMA_CFG = 8'h08,
|
||||
EV_DMA_START = 8'h09,
|
||||
EV_DMA_BEAT = 8'h0A,
|
||||
EV_DMA_DONE = 8'h0B,
|
||||
EV_GIFTAG = 8'h0C,
|
||||
EV_GS_WRITE = 8'h0D,
|
||||
// Ch76 — GS primitive-observer "primitive complete" pulse.
|
||||
// Fired by gs_stub when an XYZ2/XYZF2 vertex commit closes a
|
||||
// discrete primitive (POINT / LINE / TRI / SPRITE) per the
|
||||
// currently-latched PRIM[2:0]. arg0=prim_type, arg1=vert
|
||||
// threshold, arg2=cumulative prim count after this draw,
|
||||
// arg3=closing vertex data. No rasterization yet.
|
||||
EV_PRIM_DRAW = 8'h0E,
|
||||
EV_OTHER = 8'hFF
|
||||
} event_e;
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Ch81 — structured GIF/GS field decoders
|
||||
//
|
||||
// The raw 64-bit XYZ2 / XYZF2 / RGBAQ payloads are an awkward
|
||||
// contract for the next layer of the pipeline (rasterizer or
|
||||
// pixel emit). These struct types carry the same data already
|
||||
// unpacked into channel components so a consumer doesn't have
|
||||
// to re-derive the bit slices.
|
||||
//
|
||||
// XYZ2 (PCSX2 GSRegs.h: bits[15:0]=X, [31:16]=Y, [63:32]=Z 32-bit)
|
||||
// XYZF2 (PCSX2 GSRegs.h: bits[15:0]=X, [31:16]=Y, [55:32]=Z 24-bit,
|
||||
// [63:56]=F fog byte)
|
||||
// RGBAQ (bits[7:0]=R, [15:8]=G, [23:16]=B, [31:24]=A, [63:32]=Q float)
|
||||
//
|
||||
// X and Y are PS2 12.4 fixed-point screen coordinates (top 12
|
||||
// bits = integer pixel, low 4 bits = sub-pixel). Z is treated
|
||||
// as opaque in this package — depth interpretation depends on
|
||||
// the GS framebuffer/zbuffer config, which the recognition
|
||||
// layer doesn't model. Q is the texture-coordinate divisor
|
||||
// (IEEE single-precision float); we carry it verbatim.
|
||||
//
|
||||
// is_xyzf2 records the source format so a consumer can
|
||||
// disambiguate the 24-bit-Z + 8-bit-fog packing from the full
|
||||
// 32-bit-Z packing without re-reading the original reg#.
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
typedef struct packed {
|
||||
logic is_xyzf2; // 1 = XYZF2 source, 0 = XYZ2
|
||||
logic [7:0] fog; // valid iff is_xyzf2; else 0
|
||||
logic [31:0] z; // 32-bit (XYZ2) or zero-extended 24-bit (XYZF2)
|
||||
logic [15:0] y; // 12.4 fixed-point screen Y
|
||||
logic [15:0] x; // 12.4 fixed-point screen X
|
||||
} vertex_t;
|
||||
|
||||
typedef struct packed {
|
||||
logic [31:0] q; // texture-coord divisor (IEEE float)
|
||||
logic [7:0] a;
|
||||
logic [7:0] b;
|
||||
logic [7:0] g;
|
||||
logic [7:0] r;
|
||||
} color_t;
|
||||
|
||||
function automatic vertex_t decode_vertex(input logic [63:0] data,
|
||||
input logic is_xyzf2);
|
||||
vertex_t v;
|
||||
v.x = data[15:0];
|
||||
v.y = data[31:16];
|
||||
v.is_xyzf2 = is_xyzf2;
|
||||
if (is_xyzf2) begin
|
||||
v.z = {8'd0, data[55:32]}; // zero-extend 24 bits
|
||||
v.fog = data[63:56];
|
||||
end else begin
|
||||
v.z = data[63:32];
|
||||
v.fog = 8'd0;
|
||||
end
|
||||
return v;
|
||||
endfunction
|
||||
|
||||
function automatic color_t decode_color(input logic [63:0] data);
|
||||
color_t c;
|
||||
c.r = data[7:0];
|
||||
c.g = data[15:8];
|
||||
c.b = data[23:16];
|
||||
c.a = data[31:24];
|
||||
c.q = data[63:32];
|
||||
return c;
|
||||
endfunction
|
||||
|
||||
function automatic string subsys_str(input subsys_e s);
|
||||
case (s)
|
||||
SUBSYS_EE: return "EE";
|
||||
SUBSYS_MEM: return "MEM";
|
||||
SUBSYS_GS: return "GS";
|
||||
SUBSYS_INTC: return "INTC";
|
||||
SUBSYS_SIF: return "SIF";
|
||||
SUBSYS_DMAC: return "DMAC";
|
||||
SUBSYS_IOP: return "IOP";
|
||||
SUBSYS_GIF: return "GIF";
|
||||
SUBSYS_PLAT: return "PLAT";
|
||||
default: return "OTHER";
|
||||
endcase
|
||||
endfunction
|
||||
|
||||
function automatic string event_str(input event_e e);
|
||||
case (e)
|
||||
EV_RESET: return "RESET";
|
||||
EV_IFETCH: return "IFETCH";
|
||||
EV_READ: return "READ";
|
||||
EV_WRITE: return "WRITE";
|
||||
EV_UNMAPPED: return "UNMAPPED";
|
||||
EV_IRQ: return "IRQ";
|
||||
EV_MODE: return "MODE";
|
||||
EV_BGCOLOR: return "BGCOLOR";
|
||||
EV_DMA_CFG: return "DMA_CFG";
|
||||
EV_DMA_START: return "DMA_START";
|
||||
EV_DMA_BEAT: return "DMA_BEAT";
|
||||
EV_DMA_DONE: return "DMA_DONE";
|
||||
EV_GIFTAG: return "GIFTAG";
|
||||
EV_GS_WRITE: return "GS_WRITE";
|
||||
EV_PRIM_DRAW: return "PRIM_DRAW";
|
||||
default: return "OTHER";
|
||||
endcase
|
||||
endfunction
|
||||
|
||||
endpackage : trace_pkg
|
||||
|
||||
`endif // RETRODE_PS2_TRACE_PKG_SV
|
||||
@@ -0,0 +1,88 @@
|
||||
// retroDE_ps2 — trace_sink_stub
|
||||
//
|
||||
// Simulation-only text trace writer for Wave 1 stubs.
|
||||
//
|
||||
// Purpose, owns, success condition, replacement path: see
|
||||
// docs/stub_module_plan.md (Wave 1, item 1)
|
||||
// docs/contracts/validation.md
|
||||
// docs/decisions/0000-trace-format.md
|
||||
//
|
||||
// Interface shape:
|
||||
// - One sink instance per output file. A testbench instantiates multiple
|
||||
// sinks (one per stub under test) and wires each stub's event port to
|
||||
// its own sink. Offline tooling merges files by cycle when needed.
|
||||
// - The cycle counter is internal and advances on clk while rst_n is high.
|
||||
// Multi-clock-domain correlation is a later-wave concern.
|
||||
//
|
||||
// Line format (docs/decisions/0000):
|
||||
// cycle subsys event arg0 arg1 arg2 arg3 flags
|
||||
// where flags is rendered as `-` when zero.
|
||||
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module trace_sink_stub
|
||||
import trace_pkg::*;
|
||||
#(
|
||||
parameter string FILENAME = "trace.txt",
|
||||
parameter int SCHEMA_VERSION = 1,
|
||||
parameter string SINK_LABEL = "trace"
|
||||
) (
|
||||
input logic clk,
|
||||
input logic rst_n,
|
||||
|
||||
input logic ev_valid,
|
||||
input subsys_e ev_subsys,
|
||||
input event_e ev_event,
|
||||
input logic [63:0] ev_arg0,
|
||||
input logic [63:0] ev_arg1,
|
||||
input logic [63:0] ev_arg2,
|
||||
input logic [63:0] ev_arg3,
|
||||
input logic [31:0] ev_flags
|
||||
);
|
||||
|
||||
integer fd;
|
||||
longint unsigned cycle_count;
|
||||
|
||||
initial begin
|
||||
fd = $fopen(FILENAME, "w");
|
||||
if (fd == 0) begin
|
||||
$fatal(1, "[trace_sink_stub %0s] cannot open %0s", SINK_LABEL, FILENAME);
|
||||
end
|
||||
$fdisplay(fd, "# retroDE_ps2 trace, schema v%0d, sink=%0s",
|
||||
SCHEMA_VERSION, SINK_LABEL);
|
||||
$fdisplay(fd, "# columns: cycle subsystem event arg0 arg1 arg2 arg3 flags");
|
||||
cycle_count = 64'd0;
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin
|
||||
cycle_count <= 64'd0;
|
||||
end else begin
|
||||
cycle_count <= cycle_count + 64'd1;
|
||||
|
||||
if (ev_valid) begin
|
||||
if (ev_flags == 32'd0) begin
|
||||
$fdisplay(fd,
|
||||
"%0d %0s %0s 0x%016h 0x%016h 0x%016h 0x%016h -",
|
||||
cycle_count,
|
||||
subsys_str(ev_subsys),
|
||||
event_str(ev_event),
|
||||
ev_arg0, ev_arg1, ev_arg2, ev_arg3);
|
||||
end else begin
|
||||
$fdisplay(fd,
|
||||
"%0d %0s %0s 0x%016h 0x%016h 0x%016h 0x%016h 0x%08h",
|
||||
cycle_count,
|
||||
subsys_str(ev_subsys),
|
||||
event_str(ev_event),
|
||||
ev_arg0, ev_arg1, ev_arg2, ev_arg3,
|
||||
ev_flags);
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
final begin
|
||||
if (fd != 0) $fclose(fd);
|
||||
end
|
||||
|
||||
endmodule : trace_sink_stub
|
||||
Reference in New Issue
Block a user