Files
retroDE_ps2/rtl/sif/sif_dma_ee_ack_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

186 lines
6.7 KiB
Systemverilog

// retroDE_ps2 — sif_dma_ee_ack_peer_stub
//
// Protocol combiner for the first reverse-direction (IOP→EE) combined
// control+data SIF milestone. Mirror of sif_dma_ack_peer_stub with
// polarity swapped to observe the IOP's doorbell from the EE side and
// respond back IOP-ward through the mailbox.
//
// Explicitly NOT an EE. This module has no code execution, no bus
// master, no capability beyond composing two existing SIF primitives.
// Kept under `rtl/sif/` with the other protocol scaffolding so it
// does not get misread as EE maturity progress.
//
// Contract refs:
// docs/contracts/sif.md
//
// Layering:
// sif_mailbox_stub — storage primitive
// sif_dma_ee_ram_bridge_stub — reverse-direction data-plane landing
// sif_dma_ee_ack_peer_stub — THIS module. Ties them together on
// the EE side.
//
// Protocol (one-shot, reverse direction):
// 1. IOP writes SMCOM = cmd (what the IOP wants to say)
// 2. IOP writes SMFLG = CMD_PENDING_BIT (doorbell IOP→EE)
// 3. IOP DMAC ch9 transfers bounded payload through the SIF egress
// bridge, which lands qwords in EE RAM. Bridge's last_seen_o rises
// on the final beat and stays high.
// 4. this peer observes (SMFLG & CMD_PENDING_BIT) AND payload_complete
// 5. peer reads SMCOM (captures the command)
// 6. peer writes MSCOM = cmd (echo back IOP-ward)
// 7. peer writes MSFLG = CMD_ACK_BIT (ack back IOP-ward)
// 8. terminal DONE (one-shot for this milestone)
//
// The peer does NOT clear SMFLG or MSFLG — lifecycle is the TB's
// responsibility, consistent with sif_mailbox_peer_stub's guardrail.
//
// Ordering guarantee: the load-bearing behaviour is that the peer will
// not advance to the ack write sequence unless BOTH the doorbell AND
// payload_complete are observed simultaneously. The milestone is about
// verifying that the ack is gated on data arriving, not just control.
//
// Ports connect to:
// obs_* → sif_mailbox_stub ee_rd_* (peer reads SMFLG, then SMCOM)
// resp_* → sif_mailbox_stub ee_wr_* (peer writes MSCOM, then MSFLG)
// payload_complete ← sif_dma_ee_ram_bridge_stub.last_seen_o
`timescale 1ns/1ps
module sif_dma_ee_ack_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,
// Mailbox observation (EE-side read port)
output logic obs_rd_en,
output logic [7:0] obs_rd_addr,
input logic [31:0] obs_rd_data,
input logic obs_rd_valid,
// Mailbox response (EE-side write port)
output logic resp_wr_en,
output logic [7:0] resp_wr_addr,
output logic [31:0] resp_wr_data,
// Payload completion indication from sif_dma_ee_ram_bridge_stub (level)
input logic payload_complete,
// Status
output logic done_o,
output logic [31:0] ack_count_o
);
typedef enum logic [2:0] {
S_POLL_REQ = 3'd0, // pulse rd_en for SMFLG
S_POLL_WAIT = 3'd1, // wait for rd_valid, gate on BOTH conditions
S_SMCOM_REQ = 3'd2, // pulse rd_en for SMCOM
S_SMCOM_WAIT = 3'd3, // wait for rd_valid, latch cmd
S_WRITE_MSCOM = 3'd4, // drive wr_en, addr=MSCOM, data=cmd
S_WRITE_MSFLG = 3'd5, // drive wr_en, addr=MSFLG, data=ACK
S_DONE = 3'd6 // terminal (one-shot for this milestone)
} state_e;
state_e state;
logic [31:0] latched_cmd;
// ------------------------------------------------------------------
// State machine — advance to SMCOM_REQ only when SMFLG pending is set
// AND payload_complete is observed simultaneously. This is the
// load-bearing guarantee of the whole combiner.
// ------------------------------------------------------------------
always_ff @(posedge clk) begin
if (!rst_n) begin
state <= S_POLL_REQ;
latched_cmd <= 32'd0;
end else begin
unique case (state)
S_POLL_REQ: state <= S_POLL_WAIT;
S_POLL_WAIT: begin
if (obs_rd_valid) begin
if (((obs_rd_data & CMD_PENDING_BIT) != 32'd0) &&
payload_complete)
state <= S_SMCOM_REQ;
else
state <= S_POLL_REQ; // keep polling
end
end
S_SMCOM_REQ: state <= S_SMCOM_WAIT;
S_SMCOM_WAIT: begin
if (obs_rd_valid) begin
latched_cmd <= obs_rd_data;
state <= S_WRITE_MSCOM;
end
end
S_WRITE_MSCOM: state <= S_WRITE_MSFLG;
S_WRITE_MSFLG: state <= S_DONE;
S_DONE: state <= S_DONE;
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 = SMFLG_OFF;
end
S_SMCOM_REQ: begin
obs_rd_en = 1'b1;
obs_rd_addr = SMCOM_OFF;
end
S_WRITE_MSCOM: begin
resp_wr_en = 1'b1;
resp_wr_addr = MSCOM_OFF;
resp_wr_data = latched_cmd;
end
S_WRITE_MSFLG: begin
resp_wr_en = 1'b1;
resp_wr_addr = MSFLG_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_MSFLG) begin
ack_count_o <= ack_count_o + 32'd1;
done_o <= 1'b1;
end
end
endmodule : sif_dma_ee_ack_peer_stub