ec82764bef
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>
218 lines
8.0 KiB
Systemverilog
218 lines
8.0 KiB
Systemverilog
// 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
|