// retroDE_ps2 — tb_sif_command_echo_rearm
//
// A'' lifecycle proof: two consecutive command echoes through the SIF
// mailbox with an explicit TB-driven re-arm between them. Validates that
// the peer responds exactly once per doorbell assertion and does not
// re-fire while the doorbell stays high.
//
// Scenario:
//   1. EE writes MSCOM = cmd1 (0xFEED_FACE)
//   2. EE writes MSFLG = CMD_PENDING_BIT
//   3. wait for ack_count_o == 1
//   4. verify SMCOM == cmd1, SMFLG == CMD_ACK_BIT
//   5. spin for a while with MSFLG still high; ack_count_o must stay at 1
//   6. TB clears MSFLG (write 0) — explicit re-arm
//   7. TB clears SMFLG (write 0)
//   8. EE writes MSCOM = cmd2 (0xDEAD_BEEF)
//   9. EE writes MSFLG = CMD_PENDING_BIT (second doorbell rising edge)
//  10. wait for ack_count_o == 2
//  11. verify SMCOM == cmd2 (NOT stale cmd1), SMFLG == CMD_ACK_BIT
//
// Key properties enforced:
//   - one ack per doorbell rising edge
//   - no re-fire while doorbell stays asserted
//   - second exchange picks up cmd2, not stale cmd1
//   - peer captures exactly four writes in order:
//       [0] SMCOM = cmd1
//       [1] SMFLG = ACK
//       [2] SMCOM = cmd2
//       [3] SMFLG = ACK

`timescale 1ns/1ps

module tb_sif_command_echo_rearm;

    logic clk;
    logic rst_n;

    initial clk = 1'b0;
    always #5 clk = ~clk;

    localparam logic [7:0]  MSCOM = 8'h00;
    localparam logic [7:0]  SMCOM = 8'h10;
    localparam logic [7:0]  MSFLG = 8'h20;
    localparam logic [7:0]  SMFLG = 8'h30;
    localparam logic [31:0] CMD_PENDING_BIT = 32'h0000_0001;
    localparam logic [31:0] CMD_ACK_BIT     = 32'h0000_0002;
    localparam logic [31:0] CMD1_VALUE      = 32'hFEED_FACE;
    localparam logic [31:0] CMD2_VALUE      = 32'hDEAD_BEEF;

    // ------------------------------------------------------------------
    // Mailbox + peer
    // ------------------------------------------------------------------

    logic        ee_wr_en, ee_rd_en;
    logic [7:0]  ee_addr;
    logic [31:0] ee_wr_data;
    logic [31:0] ee_rd_data;
    logic        ee_rd_valid;

    logic        peer_rd_en;
    logic [7:0]  peer_rd_addr;
    logic [31:0] peer_rd_data;
    logic        peer_rd_valid;

    logic        peer_wr_en;
    logic [7:0]  peer_wr_addr_w;
    logic [31:0] peer_wr_data_w;

    logic        peer_done;
    logic [31:0] peer_ack_count;

    logic                ev_valid;
    trace_pkg::subsys_e  ev_subsys;
    trace_pkg::event_e   ev_event;
    logic [63:0]         ev_arg0, ev_arg1, ev_arg2, ev_arg3;
    logic [31:0]         ev_flags;

    sif_mailbox_stub u_mailbox (
        .clk(clk), .rst_n(rst_n),
        .ee_wr_en(ee_wr_en), .ee_rd_en(ee_rd_en),
        .ee_addr(ee_addr), .ee_wr_data(ee_wr_data),
        .ee_rd_data(ee_rd_data), .ee_rd_valid(ee_rd_valid),
        .iop_wr_en(peer_wr_en), .iop_rd_en(peer_rd_en),
        .iop_addr(peer_wr_en ? peer_wr_addr_w : peer_rd_addr),
        .iop_wr_data(peer_wr_data_w),
        .iop_rd_data(peer_rd_data), .iop_rd_valid(peer_rd_valid),
        .ev_valid(ev_valid), .ev_subsys(ev_subsys),
        .ev_event(ev_event),
        .ev_arg0(ev_arg0), .ev_arg1(ev_arg1),
        .ev_arg2(ev_arg2), .ev_arg3(ev_arg3),
        .ev_flags(ev_flags)
    );

    sif_mailbox_peer_stub u_peer (
        .clk(clk), .rst_n(rst_n),
        .obs_rd_en(peer_rd_en), .obs_rd_addr(peer_rd_addr),
        .obs_rd_data(peer_rd_data), .obs_rd_valid(peer_rd_valid),
        .resp_wr_en(peer_wr_en), .resp_wr_addr(peer_wr_addr_w),
        .resp_wr_data(peer_wr_data_w),
        .done_o(peer_done), .ack_count_o(peer_ack_count)
    );

    trace_sink_stub #(.FILENAME("sif_command_echo_rearm.trace"), .SINK_LABEL("sif"))
        u_trace_sif (
            .clk(clk), .rst_n(rst_n),
            .ev_valid(ev_valid), .ev_subsys(ev_subsys),
            .ev_event(ev_event),
            .ev_arg0(ev_arg0), .ev_arg1(ev_arg1),
            .ev_arg2(ev_arg2), .ev_arg3(ev_arg3),
            .ev_flags(ev_flags)
        );

    // ------------------------------------------------------------------
    // Capture peer-originated writes in order (up to 4 expected)
    // ------------------------------------------------------------------

    int          iop_writes;
    int          ee_writes;
    logic [7:0]  captured_addr [0:3];
    logic [31:0] captured_data [0:3];
    int          errors;

    initial begin
        iop_writes = 0;
        ee_writes  = 0;
        for (int i = 0; i < 4; i++) begin
            captured_addr[i] = 8'd0;
            captured_data[i] = 32'd0;
        end
        errors = 0;
    end

    always_ff @(posedge clk) begin
        if (rst_n && ev_valid && ev_event == trace_pkg::EV_WRITE) begin
            if (ev_arg2[7:0] == 8'd0) ee_writes <= ee_writes + 1;
            if (ev_arg2[7:0] == 8'd1) begin
                if (iop_writes < 4) begin
                    captured_addr[iop_writes] <= ev_arg0[7:0];
                    captured_data[iop_writes] <= ev_arg1[31:0];
                end
                iop_writes <= iop_writes + 1;
            end
        end
    end

    // ------------------------------------------------------------------
    // EE-side helpers
    // ------------------------------------------------------------------

    task automatic ee_write(input logic [7:0] addr, input logic [31:0] data);
        @(negedge clk);
        ee_wr_en   = 1'b1;
        ee_addr    = addr;
        ee_wr_data = data;
        @(negedge clk);
        ee_wr_en   = 1'b0;
        ee_addr    = 8'd0;
        ee_wr_data = 32'd0;
    endtask

    task automatic ee_read_expect(input logic [7:0] addr, input logic [31:0] expected,
                                  input string label);
        @(negedge clk);
        ee_rd_en = 1'b1;
        ee_addr  = addr;
        @(negedge clk);
        ee_rd_en = 1'b0;
        ee_addr  = 8'd0;
        if (ee_rd_data !== expected || ee_rd_valid !== 1'b1) begin
            $error("[tb_sif_command_echo_rearm] EE read %s: got 0x%08h valid=%0b expected 0x%08h",
                   label, ee_rd_data, ee_rd_valid, expected);
            errors = errors + 1;
        end
    endtask

    task automatic wait_for_ack(input int target, input string label);
        int spin;
        spin = 0;
        while (peer_ack_count < target && spin < 200) begin
            @(posedge clk);
            spin = spin + 1;
        end
        if (peer_ack_count < target) begin
            $error("[tb_sif_command_echo_rearm] peer did not reach ack_count=%0d (%s) within %0d cycles",
                   target, label, spin);
            errors = errors + 1;
        end
    endtask

    // ------------------------------------------------------------------
    // Stimulus
    // ------------------------------------------------------------------

    int before_rearm_count;

    initial begin
        rst_n      = 1'b0;
        ee_wr_en   = 1'b0;
        ee_rd_en   = 1'b0;
        ee_addr    = 8'd0;
        ee_wr_data = 32'd0;

        repeat (4) @(posedge clk);
        rst_n = 1'b1;
        repeat (2) @(posedge clk);

        // ---- Exchange 1 ----
        ee_write(MSCOM, CMD1_VALUE);
        ee_write(MSFLG, CMD_PENDING_BIT);
        wait_for_ack(1, "first echo");
        repeat (2) @(posedge clk);

        ee_read_expect(SMCOM, CMD1_VALUE,  "SMCOM after cmd1");
        ee_read_expect(SMFLG, CMD_ACK_BIT, "SMFLG after cmd1");

        // Spin with MSFLG still high — ack count must not advance.
        before_rearm_count = peer_ack_count;
        repeat (40) @(posedge clk);
        if (peer_ack_count !== before_rearm_count) begin
            $error("[tb_sif_command_echo_rearm] peer re-fired while doorbell still high: count went %0d → %0d",
                   before_rearm_count, peer_ack_count);
            errors = errors + 1;
        end

        // ---- Explicit re-arm (TB owns lifecycle) ----
        ee_write(MSFLG, 32'd0);
        ee_write(SMFLG, 32'd0);
        repeat (4) @(posedge clk);

        // ---- Exchange 2 ----
        ee_write(MSCOM, CMD2_VALUE);
        ee_write(MSFLG, CMD_PENDING_BIT);
        wait_for_ack(2, "second echo");
        repeat (2) @(posedge clk);

        ee_read_expect(SMCOM, CMD2_VALUE,  "SMCOM after cmd2 (must not be stale cmd1)");
        ee_read_expect(SMFLG, CMD_ACK_BIT, "SMFLG after cmd2");

        repeat (4) @(posedge clk);

        // ------------------------------------------------------------------
        $display("[tb_sif_command_echo_rearm] ack_count=%0d ee_writes=%0d iop_writes=%0d captured={SMCOM=0x%08h, SMFLG=0x%08h, SMCOM=0x%08h, SMFLG=0x%08h} errors=%0d",
                 peer_ack_count, ee_writes, iop_writes,
                 captured_data[0], captured_data[1],
                 captured_data[2], captured_data[3], errors);

        if (peer_ack_count !== 32'd2)
            $error("expected ack_count=2, got %0d", peer_ack_count);

        // Peer-write chronology across both exchanges
        if (captured_addr[0] !== SMCOM || captured_data[0] !== CMD1_VALUE)
            $error("write[0] expected SMCOM=0x%08h, got addr=0x%02h data=0x%08h",
                   CMD1_VALUE, captured_addr[0], captured_data[0]);
        if (captured_addr[1] !== SMFLG || captured_data[1] !== CMD_ACK_BIT)
            $error("write[1] expected SMFLG=0x%08h, got addr=0x%02h data=0x%08h",
                   CMD_ACK_BIT, captured_addr[1], captured_data[1]);
        if (captured_addr[2] !== SMCOM || captured_data[2] !== CMD2_VALUE)
            $error("write[2] expected SMCOM=0x%08h (cmd2), got addr=0x%02h data=0x%08h",
                   CMD2_VALUE, captured_addr[2], captured_data[2]);
        if (captured_addr[3] !== SMFLG || captured_data[3] !== CMD_ACK_BIT)
            $error("write[3] expected SMFLG=0x%08h, got addr=0x%02h data=0x%08h",
                   CMD_ACK_BIT, captured_addr[3], captured_data[3]);

        if (iop_writes !== 4)
            $error("expected exactly 4 IOP-side writes across two exchanges, got %0d",
                   iop_writes);

        if (errors == 0 && peer_ack_count == 32'd2 && iop_writes == 4 &&
            captured_addr[0] === SMCOM && captured_data[0] === CMD1_VALUE &&
            captured_addr[1] === SMFLG && captured_data[1] === CMD_ACK_BIT &&
            captured_addr[2] === SMCOM && captured_data[2] === CMD2_VALUE &&
            captured_addr[3] === SMFLG && captured_data[3] === CMD_ACK_BIT)
            $display("[tb_sif_command_echo_rearm] PASS");
        else
            $display("[tb_sif_command_echo_rearm] FAIL");

        $finish;
    end

    initial begin
        #400000;
        $error("[tb_sif_command_echo_rearm] timeout");
        $finish;
    end

endmodule : tb_sif_command_echo_rearm
