Files
retroDE_ps2/rtl/sif/sif_mailbox_peer_stub.sv
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

185 lines
6.8 KiB
Systemverilog

// retroDE_ps2 — sif_mailbox_peer_stub
//
// Re-armable active peer for the SIF mailbox. Second step on the two-actor
// coordination track (A'' — lifecycle). Observes one mailbox/flag pattern
// and responds with a known acknowledgement pattern. NOT an IOP — does not
// execute code, does not boot anything, does not claim to be a CPU.
//
// Contract refs:
// docs/contracts/sif.md (mailbox/flag-only SIF stub)
// docs/stub_module_plan.md (Wave 2 SIF track)
//
// Canonical command-echo protocol:
// 1. EE writes MSCOM = cmd
// 2. EE writes MSFLG = CMD_PENDING_BIT (doorbell rising edge)
// 3. peer polls MSFLG; when it sees CMD_PENDING_BIT set AND it has not
// already responded to the current request, it reads MSCOM
// 4. peer writes SMCOM = <the cmd it just read>
// 5. peer writes SMFLG = CMD_ACK_BIT
// 6. peer latches `responded` and resumes polling; it will NOT respond
// again until the TB (or EE) clears CMD_PENDING_BIT in MSFLG
// 7. when the peer observes CMD_PENDING_BIT cleared, `responded` clears
// and the next rising edge of CMD_PENDING_BIT triggers a fresh echo
//
// The peer still does NOT clear any mailbox state itself. Re-arm is the
// TB's responsibility; the peer just refuses to double-fire while the
// doorbell bit is still high.
//
// Ports connect directly to sif_mailbox_stub's IOP-side register port:
// obs_* → mailbox iop_rd_* (peer reads MSFLG then MSCOM)
// resp_* → mailbox iop_wr_* (peer writes SMCOM then SMFLG)
//
// All peer activity is visible through the mailbox's own trace output
// (side_id=IOP=1). The peer does not emit its own trace; `ack_count_o`
// provides a testbench synchronisation point.
`timescale 1ns/1ps
module sif_mailbox_peer_stub
#(
parameter logic [7:0] MSCOM_OFF = 8'h00,
parameter logic [7:0] SMCOM_OFF = 8'h10,
parameter logic [7:0] MSFLG_OFF = 8'h20,
parameter logic [7:0] SMFLG_OFF = 8'h30,
parameter logic [31:0] CMD_PENDING_BIT = 32'h0000_0001,
parameter logic [31:0] CMD_ACK_BIT = 32'h0000_0002
) (
input logic clk,
input logic rst_n,
// Observation — connects to mailbox iop_rd_*
output logic obs_rd_en,
output logic [7:0] obs_rd_addr,
input logic [31:0] obs_rd_data,
input logic obs_rd_valid,
// Response — connects to mailbox iop_wr_*
output logic resp_wr_en,
output logic [7:0] resp_wr_addr,
output logic [31:0] resp_wr_data,
// Status
output logic done_o, // latched high after the first ack
output logic [31:0] ack_count_o // monotonic count of completed acks
);
typedef enum logic [2:0] {
S_POLL_REQ = 3'd0, // drive rd_en for MSFLG
S_POLL_WAIT = 3'd1, // wait for obs_rd_valid, decide
S_MSCOM_REQ = 3'd2, // drive rd_en for MSCOM
S_MSCOM_WAIT = 3'd3, // wait for obs_rd_valid, latch cmd
S_WRITE_SMCOM = 3'd4, // drive wr_en, addr=SMCOM, data=cmd
S_WRITE_SMFLG = 3'd5 // drive wr_en, addr=SMFLG, data=ACK
} state_e;
state_e state;
logic [31:0] latched_cmd;
logic responded; // peer has already acked the current
// doorbell assertion; suppresses re-fire
// until the doorbell is observed low
// ------------------------------------------------------------------
// State machine
// ------------------------------------------------------------------
always_ff @(posedge clk) begin
if (!rst_n) begin
state <= S_POLL_REQ;
latched_cmd <= 32'd0;
responded <= 1'b0;
end else begin
unique case (state)
S_POLL_REQ: state <= S_POLL_WAIT;
S_POLL_WAIT: begin
if (obs_rd_valid) begin
if (responded) begin
// Waiting for the TB to clear CMD_PENDING_BIT
// before we arm again.
if ((obs_rd_data & CMD_PENDING_BIT) == 32'd0)
responded <= 1'b0;
state <= S_POLL_REQ;
end else begin
if ((obs_rd_data & CMD_PENDING_BIT) != 32'd0)
state <= S_MSCOM_REQ;
else
state <= S_POLL_REQ;
end
end
end
S_MSCOM_REQ: state <= S_MSCOM_WAIT;
S_MSCOM_WAIT: begin
if (obs_rd_valid) begin
latched_cmd <= obs_rd_data;
state <= S_WRITE_SMCOM;
end
end
S_WRITE_SMCOM: state <= S_WRITE_SMFLG;
S_WRITE_SMFLG: begin
responded <= 1'b1; // refuse to re-fire until MSFLG
// clears
state <= S_POLL_REQ;
end
default: state <= S_POLL_REQ;
endcase
end
end
// ------------------------------------------------------------------
// Output drive (combinational, one-hot on state)
// ------------------------------------------------------------------
always_comb begin
obs_rd_en = 1'b0;
obs_rd_addr = 8'd0;
resp_wr_en = 1'b0;
resp_wr_addr = 8'd0;
resp_wr_data = 32'd0;
unique case (state)
S_POLL_REQ: begin
obs_rd_en = 1'b1;
obs_rd_addr = MSFLG_OFF;
end
S_MSCOM_REQ: begin
obs_rd_en = 1'b1;
obs_rd_addr = MSCOM_OFF;
end
S_WRITE_SMCOM: begin
resp_wr_en = 1'b1;
resp_wr_addr = SMCOM_OFF;
resp_wr_data = latched_cmd;
end
S_WRITE_SMFLG: begin
resp_wr_en = 1'b1;
resp_wr_addr = SMFLG_OFF;
resp_wr_data = CMD_ACK_BIT;
end
default: ;
endcase
end
// ------------------------------------------------------------------
// Ack bookkeeping
// ------------------------------------------------------------------
always_ff @(posedge clk) begin
if (!rst_n) begin
ack_count_o <= 32'd0;
done_o <= 1'b0;
end else if (state == S_WRITE_SMFLG) begin
// S_WRITE_SMFLG is a single-cycle state: the state machine
// unconditionally transitions to S_POLL_REQ on the next edge,
// so this branch is observed exactly once per completed ack.
ack_count_o <= ack_count_o + 32'd1;
done_o <= 1'b1;
end
end
endmodule : sif_mailbox_peer_stub