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,230 @@
|
||||
// retroDE_ps2 — sif_mailbox_stub
|
||||
//
|
||||
// Minimal EE↔IOP subsystem-interface mailbox shell. First stub on the SIF
|
||||
// track. Standalone unit — does not yet integrate with any live IOP core.
|
||||
// Testbenches drive both the EE-side port and the IOP-side port directly,
|
||||
// playing both roles, to prove the register semantics without requiring
|
||||
// a full dual-CPU bring-up.
|
||||
//
|
||||
// Contract refs:
|
||||
// docs/stub_module_plan.md (Wave 2, item 10)
|
||||
// docs/contracts/sif.md (mailbox/flag-only stub is allowed here)
|
||||
//
|
||||
// Register surface (offsets within the SIF block):
|
||||
// 0x00 MSCOM — 32-bit mailbox, conventionally EE→IOP
|
||||
// 0x10 SMCOM — 32-bit mailbox, conventionally IOP→EE
|
||||
// 0x20 MSFLG — 32-bit flag word, conventionally EE-owned for set,
|
||||
// IOP-owned for clear (directional semantics deferred)
|
||||
// 0x30 SMFLG — 32-bit flag word, conventionally IOP-owned for set,
|
||||
// EE-owned for clear (directional semantics deferred)
|
||||
//
|
||||
// Wave 2 scope intentionally does NOT enforce direction or set/clear
|
||||
// semantics. Both ports can read and write any register with plain
|
||||
// replace-on-write. The trace records which side initiated each access
|
||||
// (side_id in arg2) so future-wave work can layer directional rules on
|
||||
// top without changing the storage model.
|
||||
//
|
||||
// Port semantics:
|
||||
// Each side (EE / IOP) has an independent register port:
|
||||
// wr_en, rd_en, addr[7:0], wr_data[31:0], rd_data[31:0], rd_valid
|
||||
// Reads have 1-cycle latency to match the existing stub ecosystem.
|
||||
//
|
||||
// Write arbitration (per-register):
|
||||
// - EE and IOP writes to *different* registers on the same cycle both
|
||||
// land. Storage is not serialized across independent registers.
|
||||
// - EE and IOP writes to the *same* register on the same cycle: EE
|
||||
// wins, IOP write is dropped that cycle.
|
||||
// - Trace is limited to one event per cycle by the shared trace bus
|
||||
// (priority EE > IOP). An IOP write that lands silently when EE is
|
||||
// driving a different register will not be traced this wave — future
|
||||
// waves can add a second trace output port if that becomes a gap.
|
||||
//
|
||||
// Trace payload schema (SUBSYS_SIF, existing EV_READ/EV_WRITE codes):
|
||||
// SIF WRITE arg0=offset arg1=data arg2=side_id arg3=0 flags[0]=1
|
||||
// SIF READ arg0=offset arg1=data arg2=side_id arg3=0 flags[0]=0
|
||||
// side_id: 0 = EE, 1 = IOP
|
||||
//
|
||||
// Trace priority on same cycle: EE write > IOP write > EE read > IOP read.
|
||||
// In practice TBs drive at most one operation per cycle.
|
||||
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module sif_mailbox_stub
|
||||
import trace_pkg::*;
|
||||
(
|
||||
input logic clk,
|
||||
input logic rst_n,
|
||||
|
||||
// EE-side register port
|
||||
input logic ee_wr_en,
|
||||
input logic ee_rd_en,
|
||||
input logic [7:0] ee_addr,
|
||||
input logic [31:0] ee_wr_data,
|
||||
output logic [31:0] ee_rd_data,
|
||||
output logic ee_rd_valid,
|
||||
|
||||
// IOP-side register port
|
||||
input logic iop_wr_en,
|
||||
input logic iop_rd_en,
|
||||
input logic [7:0] iop_addr,
|
||||
input logic [31:0] iop_wr_data,
|
||||
output logic [31:0] iop_rd_data,
|
||||
output logic iop_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 [7:0] MSCOM_OFF = 8'h00;
|
||||
localparam logic [7:0] SMCOM_OFF = 8'h10;
|
||||
localparam logic [7:0] MSFLG_OFF = 8'h20;
|
||||
localparam logic [7:0] SMFLG_OFF = 8'h30;
|
||||
|
||||
localparam logic [63:0] SIDE_EE = 64'd0;
|
||||
localparam logic [63:0] SIDE_IOP = 64'd1;
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Register file
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
logic [31:0] mscom;
|
||||
logic [31:0] smcom;
|
||||
logic [31:0] msflg;
|
||||
logic [31:0] smflg;
|
||||
|
||||
function automatic logic [31:0] select_reg(input logic [7:0] offset,
|
||||
input logic [31:0] mscom_v,
|
||||
input logic [31:0] smcom_v,
|
||||
input logic [31:0] msflg_v,
|
||||
input logic [31:0] smflg_v);
|
||||
case (offset)
|
||||
MSCOM_OFF: select_reg = mscom_v;
|
||||
SMCOM_OFF: select_reg = smcom_v;
|
||||
MSFLG_OFF: select_reg = msflg_v;
|
||||
SMFLG_OFF: select_reg = smflg_v;
|
||||
default: select_reg = 32'hDEAD_BEEF;
|
||||
endcase
|
||||
endfunction
|
||||
|
||||
// Per-register write arbitration: EE wins on same-register collision,
|
||||
// but writes to different registers land independently.
|
||||
logic ee_hits_mscom, ee_hits_smcom, ee_hits_msflg, ee_hits_smflg;
|
||||
logic iop_hits_mscom, iop_hits_smcom, iop_hits_msflg, iop_hits_smflg;
|
||||
|
||||
assign ee_hits_mscom = ee_wr_en && (ee_addr == MSCOM_OFF);
|
||||
assign ee_hits_smcom = ee_wr_en && (ee_addr == SMCOM_OFF);
|
||||
assign ee_hits_msflg = ee_wr_en && (ee_addr == MSFLG_OFF);
|
||||
assign ee_hits_smflg = ee_wr_en && (ee_addr == SMFLG_OFF);
|
||||
|
||||
assign iop_hits_mscom = iop_wr_en && (iop_addr == MSCOM_OFF);
|
||||
assign iop_hits_smcom = iop_wr_en && (iop_addr == SMCOM_OFF);
|
||||
assign iop_hits_msflg = iop_wr_en && (iop_addr == MSFLG_OFF);
|
||||
assign iop_hits_smflg = iop_wr_en && (iop_addr == SMFLG_OFF);
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin
|
||||
mscom <= 32'd0;
|
||||
smcom <= 32'd0;
|
||||
msflg <= 32'd0;
|
||||
smflg <= 32'd0;
|
||||
end else begin
|
||||
if (ee_hits_mscom) mscom <= ee_wr_data;
|
||||
else if (iop_hits_mscom) mscom <= iop_wr_data;
|
||||
|
||||
if (ee_hits_smcom) smcom <= ee_wr_data;
|
||||
else if (iop_hits_smcom) smcom <= iop_wr_data;
|
||||
|
||||
if (ee_hits_msflg) msflg <= ee_wr_data;
|
||||
else if (iop_hits_msflg) msflg <= iop_wr_data;
|
||||
|
||||
if (ee_hits_smflg) smflg <= ee_wr_data;
|
||||
else if (iop_hits_smflg) smflg <= iop_wr_data;
|
||||
end
|
||||
end
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Reads (1-cycle latency, both ports independent)
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin
|
||||
ee_rd_data <= 32'd0;
|
||||
ee_rd_valid <= 1'b0;
|
||||
iop_rd_data <= 32'd0;
|
||||
iop_rd_valid <= 1'b0;
|
||||
end else begin
|
||||
ee_rd_valid <= ee_rd_en;
|
||||
if (ee_rd_en)
|
||||
ee_rd_data <= select_reg(ee_addr, mscom, smcom, msflg, smflg);
|
||||
|
||||
iop_rd_valid <= iop_rd_en;
|
||||
if (iop_rd_en)
|
||||
iop_rd_data <= select_reg(iop_addr, mscom, smcom, msflg, smflg);
|
||||
end
|
||||
end
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Trace emission — priority EE_wr > IOP_wr > EE_rd > IOP_rd.
|
||||
// Reads emit with the data that will be delivered next cycle, keeping
|
||||
// the trace line self-consistent.
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin
|
||||
ev_valid <= 1'b0;
|
||||
ev_subsys <= SUBSYS_SIF;
|
||||
ev_event <= EV_READ;
|
||||
ev_arg0 <= 64'd0;
|
||||
ev_arg1 <= 64'd0;
|
||||
ev_arg2 <= 64'd0;
|
||||
ev_arg3 <= 64'd0;
|
||||
ev_flags <= 32'd0;
|
||||
end else if (ee_wr_en) begin
|
||||
ev_valid <= 1'b1;
|
||||
ev_subsys <= SUBSYS_SIF;
|
||||
ev_event <= EV_WRITE;
|
||||
ev_arg0 <= {56'd0, ee_addr};
|
||||
ev_arg1 <= {32'd0, ee_wr_data};
|
||||
ev_arg2 <= SIDE_EE;
|
||||
ev_arg3 <= 64'd0;
|
||||
ev_flags <= 32'h0000_0001;
|
||||
end else if (iop_wr_en) begin
|
||||
ev_valid <= 1'b1;
|
||||
ev_subsys <= SUBSYS_SIF;
|
||||
ev_event <= EV_WRITE;
|
||||
ev_arg0 <= {56'd0, iop_addr};
|
||||
ev_arg1 <= {32'd0, iop_wr_data};
|
||||
ev_arg2 <= SIDE_IOP;
|
||||
ev_arg3 <= 64'd0;
|
||||
ev_flags <= 32'h0000_0001;
|
||||
end else if (ee_rd_en) begin
|
||||
ev_valid <= 1'b1;
|
||||
ev_subsys <= SUBSYS_SIF;
|
||||
ev_event <= EV_READ;
|
||||
ev_arg0 <= {56'd0, ee_addr};
|
||||
ev_arg1 <= {32'd0, select_reg(ee_addr, mscom, smcom, msflg, smflg)};
|
||||
ev_arg2 <= SIDE_EE;
|
||||
ev_arg3 <= 64'd0;
|
||||
ev_flags <= 32'd0;
|
||||
end else if (iop_rd_en) begin
|
||||
ev_valid <= 1'b1;
|
||||
ev_subsys <= SUBSYS_SIF;
|
||||
ev_event <= EV_READ;
|
||||
ev_arg0 <= {56'd0, iop_addr};
|
||||
ev_arg1 <= {32'd0, select_reg(iop_addr, mscom, smcom, msflg, smflg)};
|
||||
ev_arg2 <= SIDE_IOP;
|
||||
ev_arg3 <= 64'd0;
|
||||
ev_flags <= 32'd0;
|
||||
end else begin
|
||||
ev_valid <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule : sif_mailbox_stub
|
||||
Reference in New Issue
Block a user