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,263 @@
|
||||
// retroDE_ps2 — tb_sif_command_echo
|
||||
//
|
||||
// First two-actor coordination proof on the SIF seam. TB drives only the
|
||||
// EE side of sif_mailbox_stub; sif_mailbox_peer_stub drives the IOP side
|
||||
// in response. Demonstrates that the mailbox supports a real command-echo
|
||||
// handshake without requiring a live IOP peer.
|
||||
//
|
||||
// Scenario:
|
||||
// 1. TB writes MSCOM = cmd (0xFEED_FACE)
|
||||
// 2. TB writes MSFLG = CMD_PENDING_BIT (0x1)
|
||||
// 3. peer observes MSFLG, reads MSCOM, writes SMCOM = cmd, writes
|
||||
// SMFLG = CMD_ACK_BIT (0x2)
|
||||
// 4. TB reads SMCOM — expects 0xFEED_FACE
|
||||
// 5. TB reads SMFLG — expects CMD_ACK_BIT
|
||||
//
|
||||
// Pass criteria:
|
||||
// - peer done_o asserts within a bounded window
|
||||
// - SMCOM contains the command value written by EE
|
||||
// - SMFLG contains the ack bit
|
||||
// - trace shows mailbox writes with side_id=IOP for the peer's responses
|
||||
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_sif_command_echo;
|
||||
|
||||
logic clk;
|
||||
logic rst_n;
|
||||
|
||||
initial clk = 1'b0;
|
||||
always #5 clk = ~clk;
|
||||
|
||||
localparam logic [7:0] MSCOM = 8'h00;
|
||||
localparam logic [7:0] SMCOM = 8'h10;
|
||||
localparam logic [7:0] MSFLG = 8'h20;
|
||||
localparam logic [7:0] SMFLG = 8'h30;
|
||||
localparam logic [31:0] CMD_PENDING_BIT = 32'h0000_0001;
|
||||
localparam logic [31:0] CMD_ACK_BIT = 32'h0000_0002;
|
||||
localparam logic [31:0] CMD_VALUE = 32'hFEED_FACE;
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Mailbox + peer
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
// EE side (TB-driven)
|
||||
logic ee_wr_en, ee_rd_en;
|
||||
logic [7:0] ee_addr;
|
||||
logic [31:0] ee_wr_data;
|
||||
logic [31:0] ee_rd_data;
|
||||
logic ee_rd_valid;
|
||||
|
||||
// IOP side (peer-driven)
|
||||
logic peer_rd_en;
|
||||
logic [7:0] peer_rd_addr;
|
||||
logic [31:0] peer_rd_data;
|
||||
logic peer_rd_valid;
|
||||
|
||||
logic peer_wr_en;
|
||||
logic [7:0] peer_wr_addr_w;
|
||||
logic [31:0] peer_wr_data_w;
|
||||
|
||||
logic peer_done;
|
||||
|
||||
logic ev_valid;
|
||||
trace_pkg::subsys_e ev_subsys;
|
||||
trace_pkg::event_e ev_event;
|
||||
logic [63:0] ev_arg0, ev_arg1, ev_arg2, ev_arg3;
|
||||
logic [31:0] ev_flags;
|
||||
|
||||
sif_mailbox_stub u_mailbox (
|
||||
.clk(clk), .rst_n(rst_n),
|
||||
.ee_wr_en(ee_wr_en), .ee_rd_en(ee_rd_en),
|
||||
.ee_addr(ee_addr), .ee_wr_data(ee_wr_data),
|
||||
.ee_rd_data(ee_rd_data), .ee_rd_valid(ee_rd_valid),
|
||||
.iop_wr_en(peer_wr_en), .iop_rd_en(peer_rd_en),
|
||||
.iop_addr(peer_wr_en ? peer_wr_addr_w : peer_rd_addr),
|
||||
.iop_wr_data(peer_wr_data_w),
|
||||
.iop_rd_data(peer_rd_data), .iop_rd_valid(peer_rd_valid),
|
||||
.ev_valid(ev_valid), .ev_subsys(ev_subsys),
|
||||
.ev_event(ev_event),
|
||||
.ev_arg0(ev_arg0), .ev_arg1(ev_arg1),
|
||||
.ev_arg2(ev_arg2), .ev_arg3(ev_arg3),
|
||||
.ev_flags(ev_flags)
|
||||
);
|
||||
|
||||
sif_mailbox_peer_stub u_peer (
|
||||
.clk(clk), .rst_n(rst_n),
|
||||
.obs_rd_en(peer_rd_en), .obs_rd_addr(peer_rd_addr),
|
||||
.obs_rd_data(peer_rd_data), .obs_rd_valid(peer_rd_valid),
|
||||
.resp_wr_en(peer_wr_en), .resp_wr_addr(peer_wr_addr_w),
|
||||
.resp_wr_data(peer_wr_data_w),
|
||||
.done_o(peer_done)
|
||||
);
|
||||
|
||||
trace_sink_stub #(.FILENAME("sif_command_echo.trace"), .SINK_LABEL("sif"))
|
||||
u_trace_sif (
|
||||
.clk(clk), .rst_n(rst_n),
|
||||
.ev_valid(ev_valid), .ev_subsys(ev_subsys),
|
||||
.ev_event(ev_event),
|
||||
.ev_arg0(ev_arg0), .ev_arg1(ev_arg1),
|
||||
.ev_arg2(ev_arg2), .ev_arg3(ev_arg3),
|
||||
.ev_flags(ev_flags)
|
||||
);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Counters
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
int iop_side_writes; // peer responses (side_id==1, write)
|
||||
int ee_side_writes; // TB writes (side_id==0, write)
|
||||
int errors;
|
||||
|
||||
// Capture the first two peer-originated writes so we can enforce the
|
||||
// exact protocol chronology (SMCOM=cmd, then SMFLG=ack), not just the
|
||||
// final-storage outcome.
|
||||
logic [7:0] captured_wr_addr [0:1];
|
||||
logic [31:0] captured_wr_data [0:1];
|
||||
|
||||
initial begin
|
||||
iop_side_writes = 0;
|
||||
ee_side_writes = 0;
|
||||
errors = 0;
|
||||
captured_wr_addr[0] = 8'd0;
|
||||
captured_wr_addr[1] = 8'd0;
|
||||
captured_wr_data[0] = 32'd0;
|
||||
captured_wr_data[1] = 32'd0;
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst_n && ev_valid && ev_event == trace_pkg::EV_WRITE) begin
|
||||
if (ev_arg2[7:0] == 8'd0) ee_side_writes <= ee_side_writes + 1;
|
||||
if (ev_arg2[7:0] == 8'd1) begin
|
||||
// Latch the first two peer writes for chronology checks.
|
||||
if (iop_side_writes == 0) begin
|
||||
captured_wr_addr[0] <= ev_arg0[7:0];
|
||||
captured_wr_data[0] <= ev_arg1[31:0];
|
||||
end else if (iop_side_writes == 1) begin
|
||||
captured_wr_addr[1] <= ev_arg0[7:0];
|
||||
captured_wr_data[1] <= ev_arg1[31:0];
|
||||
end
|
||||
iop_side_writes <= iop_side_writes + 1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// EE-side helpers
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
task automatic ee_write(input logic [7:0] addr, input logic [31:0] data);
|
||||
@(negedge clk);
|
||||
ee_wr_en = 1'b1;
|
||||
ee_addr = addr;
|
||||
ee_wr_data = data;
|
||||
@(negedge clk);
|
||||
ee_wr_en = 1'b0;
|
||||
ee_addr = 8'd0;
|
||||
ee_wr_data = 32'd0;
|
||||
endtask
|
||||
|
||||
task automatic ee_read_expect(input logic [7:0] addr, input logic [31:0] expected,
|
||||
input string label);
|
||||
@(negedge clk);
|
||||
ee_rd_en = 1'b1;
|
||||
ee_addr = addr;
|
||||
@(negedge clk);
|
||||
ee_rd_en = 1'b0;
|
||||
ee_addr = 8'd0;
|
||||
if (ee_rd_data !== expected || ee_rd_valid !== 1'b1) begin
|
||||
$error("[tb_sif_command_echo] EE read %s: got 0x%08h valid=%0b expected 0x%08h",
|
||||
label, ee_rd_data, ee_rd_valid, expected);
|
||||
errors = errors + 1;
|
||||
end
|
||||
endtask
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Stimulus
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
int wait_cycles;
|
||||
|
||||
initial begin
|
||||
rst_n = 1'b0;
|
||||
ee_wr_en = 1'b0;
|
||||
ee_rd_en = 1'b0;
|
||||
ee_addr = 8'd0;
|
||||
ee_wr_data = 32'd0;
|
||||
|
||||
repeat (4) @(posedge clk);
|
||||
rst_n = 1'b1;
|
||||
repeat (2) @(posedge clk);
|
||||
|
||||
// 1. EE writes the command value into MSCOM.
|
||||
ee_write(MSCOM, CMD_VALUE);
|
||||
|
||||
// 2. EE sets CMD_PENDING_BIT in MSFLG — the peer's doorbell.
|
||||
ee_write(MSFLG, CMD_PENDING_BIT);
|
||||
|
||||
// 3. Wait for peer to complete its one-shot exchange. Bounded
|
||||
// wait: worst case ~10 cycles per state across 6 states plus
|
||||
// some polling slack.
|
||||
wait_cycles = 0;
|
||||
while (!peer_done && wait_cycles < 200) begin
|
||||
@(posedge clk);
|
||||
wait_cycles = wait_cycles + 1;
|
||||
end
|
||||
if (!peer_done) begin
|
||||
$error("[tb_sif_command_echo] peer did not complete within %0d cycles",
|
||||
wait_cycles);
|
||||
errors = errors + 1;
|
||||
end
|
||||
|
||||
// Let the mailbox settle.
|
||||
repeat (2) @(posedge clk);
|
||||
|
||||
// 4. EE reads SMCOM — must be the echoed command.
|
||||
ee_read_expect(SMCOM, CMD_VALUE, "SMCOM (echoed command)");
|
||||
|
||||
// 5. EE reads SMFLG — must be CMD_ACK_BIT.
|
||||
ee_read_expect(SMFLG, CMD_ACK_BIT, "SMFLG (ack)");
|
||||
|
||||
repeat (4) @(posedge clk);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
$display("[tb_sif_command_echo] peer_done=%0b wait_cycles=%0d ee_side_writes=%0d iop_side_writes=%0d errors=%0d",
|
||||
peer_done, wait_cycles, ee_side_writes, iop_side_writes, errors);
|
||||
|
||||
if (!peer_done)
|
||||
$error("peer_done never asserted");
|
||||
if (ee_side_writes < 2) $error("expected >= 2 EE-side writes, got %0d", ee_side_writes);
|
||||
if (iop_side_writes < 2) $error("expected >= 2 IOP-side writes (peer SMCOM+SMFLG), got %0d",
|
||||
iop_side_writes);
|
||||
|
||||
// Protocol chronology: first peer write must be SMCOM=CMD_VALUE,
|
||||
// second must be SMFLG=CMD_ACK_BIT, in that order.
|
||||
if (captured_wr_addr[0] !== SMCOM)
|
||||
$error("peer write[0] expected SMCOM (0x%02h), got 0x%02h", SMCOM, captured_wr_addr[0]);
|
||||
if (captured_wr_data[0] !== CMD_VALUE)
|
||||
$error("peer write[0] SMCOM data expected 0x%08h, got 0x%08h",
|
||||
CMD_VALUE, captured_wr_data[0]);
|
||||
if (captured_wr_addr[1] !== SMFLG)
|
||||
$error("peer write[1] expected SMFLG (0x%02h), got 0x%02h", SMFLG, captured_wr_addr[1]);
|
||||
if (captured_wr_data[1] !== CMD_ACK_BIT)
|
||||
$error("peer write[1] SMFLG data expected 0x%08h, got 0x%08h",
|
||||
CMD_ACK_BIT, captured_wr_data[1]);
|
||||
|
||||
if (errors == 0 && peer_done &&
|
||||
ee_side_writes >= 2 && iop_side_writes >= 2 &&
|
||||
captured_wr_addr[0] === SMCOM && captured_wr_data[0] === CMD_VALUE &&
|
||||
captured_wr_addr[1] === SMFLG && captured_wr_data[1] === CMD_ACK_BIT)
|
||||
$display("[tb_sif_command_echo] PASS");
|
||||
else
|
||||
$display("[tb_sif_command_echo] FAIL");
|
||||
|
||||
$finish;
|
||||
end
|
||||
|
||||
initial begin
|
||||
#200000;
|
||||
$error("[tb_sif_command_echo] timeout");
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule : tb_sif_command_echo
|
||||
Reference in New Issue
Block a user