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