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
+217
View File
@@ -0,0 +1,217 @@
// retroDE_ps2 — boot_install_agent_stub (Ch55 / Ch56)
//
// Minimal external producer that streams a coordinated low-RAM handler
// image into EE RAM through the SIF EE-RAM bridge. Emits 32-bit beats
// on a ready/valid handshake compatible with sif_dma_ee_ram_bridge_stub.
//
// NOT an IOP, NOT a full boot firmware. This is the thinnest possible
// stand-in for "whatever on real PS2 populates EE useg [0x80..0x1FF]
// with exception-entry + safe-return stubs before the EE starts
// faulting" (IOP→EE SIF DMA, BootROM/CDVD handoff, etc.). The point
// is to validate the transport path and the coordinated-install
// thesis, not to model the producer's identity.
//
// Payload source (Ch56):
// USE_IMAGE_FILE=0 (default) — built-in Ch54 image, hardcoded below
// USE_IMAGE_FILE=1 — $readmemh(IMAGE_FILE, payload) once
// at sim start, expects TOTAL_WORDS
// hex words
// Transport (timing, handshake, trace) is identical across both modes.
//
// Built-in image (USE_IMAGE_FILE=0):
// word[0..3] → AdES handler at useg 0x80..0x8C:
// MFC0 $26, $14 (32'h401A7000)
// ADDIU $26, $26, 4 (32'h275A0004)
// JR $26 (32'h03400008)
// RFE (32'h42000010)
// word[4..95] → 46× (JR $31; NOP) safe-return pairs covering
// useg 0x90..0x1FC.
//
// Downstream contract (matches sif_dma_ee_ram_bridge_stub upstream):
// out_valid / out_data[31:0] / out_last / out_ready
// out_last asserted on the final word. One-beat-per-cycle while
// out_ready is high.
//
// Trace:
// SUBSYS_SIF / EV_DMA_START once on go.
// SUBSYS_SIF / EV_DMA_BEAT per accepted beat.
// arg0 = word index, arg1 = word data, arg2 = MASTER_ID,
// arg3 = TOTAL_WORDS, flags bit0 = out_last.
// SUBSYS_SIF / EV_DMA_DONE once on completion.
`timescale 1ns/1ps
module boot_install_agent_stub
import trace_pkg::*;
#(
parameter int TOTAL_WORDS = 96,
parameter logic [7:0] MASTER_ID = 8'd6, // install agent
parameter bit USE_IMAGE_FILE = 1'b0, // 0: built-in ROM, 1: $readmemh
parameter string IMAGE_FILE = ""
) (
input logic clk,
input logic rst_n,
input logic go_i,
output logic out_valid,
output logic [31:0] out_data,
output logic out_last,
input logic out_ready,
output logic busy_o,
output logic done_o,
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
);
// ------------------------------------------------------------------
// Payload ROM
// ------------------------------------------------------------------
// The 4-word exception-return stub at [0..3] plus (JR $31; NOP)
// pairs filling the rest requires TOTAL_WORDS to be even and at
// least 4. Guard explicitly (Ch55 audit low-1): odd values would
// walk past the array end in the pair loop.
initial begin
if (TOTAL_WORDS < 4 || (TOTAL_WORDS & 1) != 0) begin
$fatal(1, "boot_install_agent_stub: TOTAL_WORDS must be even and >= 4, got %0d",
TOTAL_WORDS);
end
end
logic [31:0] payload [0:TOTAL_WORDS-1];
initial begin
if (USE_IMAGE_FILE) begin
if (IMAGE_FILE == "") begin
$fatal(1, "boot_install_agent_stub: USE_IMAGE_FILE=1 but IMAGE_FILE is empty");
end
$readmemh(IMAGE_FILE, payload);
end else begin
payload[0] = 32'h401A7000; // MFC0 $26, $14
payload[1] = 32'h275A0004; // ADDIU $26, $26, 4
payload[2] = 32'h03400008; // JR $26
payload[3] = 32'h42000010; // RFE (delay slot)
for (int i = 4; i < TOTAL_WORDS; i = i + 2) begin
payload[i] = 32'h03E00008; // JR $31
payload[i + 1] = 32'h00000000; // NOP
end
end
end
// ------------------------------------------------------------------
// Streaming FSM
// ------------------------------------------------------------------
typedef enum logic [1:0] {
S_IDLE = 2'd0,
S_STREAM = 2'd1,
S_DONE = 2'd2
} state_e;
state_e state;
logic [31:0] idx; // next word to emit
logic accept_beat;
assign accept_beat = out_valid && out_ready;
assign out_valid = (state == S_STREAM);
assign out_data = (state == S_STREAM) ? payload[idx[$clog2(TOTAL_WORDS)-1:0]]
: 32'd0;
assign out_last = (state == S_STREAM) && (idx == TOTAL_WORDS - 1);
assign busy_o = (state == S_STREAM);
assign done_o = (state == S_DONE);
always_ff @(posedge clk) begin
if (!rst_n) begin
state <= S_IDLE;
idx <= 32'd0;
end else begin
unique case (state)
S_IDLE: begin
if (go_i) begin
state <= S_STREAM;
idx <= 32'd0;
end
end
S_STREAM: begin
if (accept_beat) begin
if (idx == TOTAL_WORDS - 1) state <= S_DONE;
else idx <= idx + 32'd1;
end
end
S_DONE: ; // terminal
default: state <= S_IDLE;
endcase
end
end
// ------------------------------------------------------------------
// Trace
// ------------------------------------------------------------------
// START fires combinationally on the cycle the caller pulses go_i
// while we're still in S_IDLE. That cycle has out_valid=0 and
// accept_beat=0, so the event doesn't compete with a BEAT event
// in the priority if-else below (the bug pre-fix: flopping
// go_latched delayed START onto the same cycle as beat 0, dropping
// one of the two).
logic go_pulse;
assign go_pulse = (state == S_IDLE) && go_i;
logic done_edge;
state_e state_prev;
always_ff @(posedge clk) begin
if (!rst_n) state_prev <= S_IDLE;
else state_prev <= state;
end
assign done_edge = (state == S_DONE) && (state_prev != S_DONE);
always_ff @(posedge clk) begin
if (!rst_n) begin
ev_valid <= 1'b0;
ev_subsys <= SUBSYS_SIF;
ev_event <= EV_DMA_START;
ev_arg0 <= 64'd0;
ev_arg1 <= 64'd0;
ev_arg2 <= 64'd0;
ev_arg3 <= 64'd0;
ev_flags <= 32'd0;
end else if (go_pulse) begin
ev_valid <= 1'b1;
ev_subsys <= SUBSYS_SIF;
ev_event <= EV_DMA_START;
ev_arg0 <= 64'd0;
ev_arg1 <= 64'd0;
ev_arg2 <= {56'd0, MASTER_ID};
ev_arg3 <= 64'(TOTAL_WORDS);
ev_flags <= 32'd0;
end else if (accept_beat) begin
ev_valid <= 1'b1;
ev_subsys <= SUBSYS_SIF;
ev_event <= EV_DMA_BEAT;
ev_arg0 <= {32'd0, idx};
ev_arg1 <= {32'd0, out_data};
ev_arg2 <= {56'd0, MASTER_ID};
ev_arg3 <= 64'(TOTAL_WORDS);
ev_flags <= {31'd0, out_last};
end else if (done_edge) begin
ev_valid <= 1'b1;
ev_subsys <= SUBSYS_SIF;
ev_event <= EV_DMA_DONE;
ev_arg0 <= 64'(TOTAL_WORDS);
ev_arg1 <= 64'd0;
ev_arg2 <= {56'd0, MASTER_ID};
ev_arg3 <= 64'(TOTAL_WORDS);
ev_flags <= 32'd0;
end else begin
ev_valid <= 1'b0;
end
end
endmodule : boot_install_agent_stub