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