Files
retroDE_ps2/sim/tb/integration/tb_sif_command_echo.sv
T
thejayman77 ec82764bef 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>
2026-06-29 20:10:50 -04:00

264 lines
9.4 KiB
Systemverilog

// 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