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

182 lines
6.3 KiB
Systemverilog

// retroDE_ps2 — sif_dma_ack_peer_stub
//
// Protocol combiner for the first combined control+data SIF milestone.
// Observes a mailbox command doorbell on one seam and the SIF DMA receive
// endpoint's payload-complete indication on the other; only issues the
// mailbox ack sequence once BOTH are true.
//
// Explicitly NOT an IOP. This module has no code execution, no bus master,
// and no capability beyond composing two existing SIF primitives. Kept
// under `rtl/sif/` with the other SIF scaffolding.
//
// Contract refs:
// docs/contracts/sif.md
//
// Layering:
// sif_mailbox_stub — storage primitive
// sif_mailbox_peer_stub — mailbox-only active peer (no DMA awareness)
// sif_dma_stub — data-plane receive endpoint
// sif_dma_ack_peer_stub — THIS module. Wires the two together.
//
// Protocol (one-shot):
// 1. EE writes MSCOM = cmd
// 2. EE writes MSFLG = CMD_PENDING_BIT (request doorbell)
// 3. DMAC transfers bounded payload into sif_dma_stub
// 4. sif_dma_stub asserts last_seen once the final beat arrives
// 5. this peer observes (MSFLG & CMD_PENDING_BIT) AND last_seen
// 6. peer reads MSCOM
// 7. peer writes SMCOM = cmd
// 8. peer writes SMFLG = CMD_ACK_BIT
// 9. terminal DONE (one-shot for this milestone)
//
// The peer does NOT clear MSFLG or SMFLG — lifecycle is the TB's
// responsibility, consistent with sif_mailbox_peer_stub's guardrail.
//
// Ports connect to:
// obs_* → sif_mailbox_stub iop_rd_* (peer reads MSFLG, then MSCOM)
// resp_* → sif_mailbox_stub iop_wr_* (peer writes SMCOM, then SMFLG)
// payload_complete ← sif_dma_stub.last_seen
`timescale 1ns/1ps
module sif_dma_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 (IOP-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 (IOP-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_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 MSFLG
S_POLL_WAIT = 3'd1, // wait for rd_valid, gate on BOTH conditions
S_MSCOM_REQ = 3'd2, // pulse rd_en for MSCOM
S_MSCOM_WAIT = 3'd3, // wait for 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
S_DONE = 3'd6 // terminal (one-shot for this milestone)
} state_e;
state_e state;
logic [31:0] latched_cmd;
// ------------------------------------------------------------------
// State machine — advance to MSCOM_REQ only when MSFLG 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_MSCOM_REQ;
else
state <= S_POLL_REQ; // keep polling
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: 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 = 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: unconditionally
// transitions to S_DONE on the next edge, so this observes
// exactly one completion.
ack_count_o <= ack_count_o + 32'd1;
done_o <= 1'b1;
end
end
endmodule : sif_dma_ack_peer_stub