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