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