// retroDE_ps2 — tb_sif_command_echo
//
// First two-actor coordination proof on the SIF seam. TB drives only the
// EE side of sif_mailbox_stub; sif_mailbox_peer_stub drives the IOP side
// in response. Demonstrates that the mailbox supports a real command-echo
// handshake without requiring a live IOP peer.
//
// Scenario:
//   1. TB writes MSCOM = cmd (0xFEED_FACE)
//   2. TB writes MSFLG = CMD_PENDING_BIT (0x1)
//   3. peer observes MSFLG, reads MSCOM, writes SMCOM = cmd, writes
//      SMFLG = CMD_ACK_BIT (0x2)
//   4. TB reads SMCOM — expects 0xFEED_FACE
//   5. TB reads SMFLG — expects CMD_ACK_BIT
//
// Pass criteria:
//   - peer done_o asserts within a bounded window
//   - SMCOM contains the command value written by EE
//   - SMFLG contains the ack bit
//   - trace shows mailbox writes with side_id=IOP for the peer's responses

`timescale 1ns/1ps

module tb_sif_command_echo;

    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] CMD_VALUE       = 32'hFEED_FACE;

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

    // EE side (TB-driven)
    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;

    // IOP side (peer-driven)
    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                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)
    );

    trace_sink_stub #(.FILENAME("sif_command_echo.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)
        );

    // ------------------------------------------------------------------
    // Counters
    // ------------------------------------------------------------------

    int iop_side_writes;   // peer responses (side_id==1, write)
    int ee_side_writes;    // TB writes   (side_id==0, write)
    int errors;

    // Capture the first two peer-originated writes so we can enforce the
    // exact protocol chronology (SMCOM=cmd, then SMFLG=ack), not just the
    // final-storage outcome.
    logic [7:0]  captured_wr_addr [0:1];
    logic [31:0] captured_wr_data [0:1];

    initial begin
        iop_side_writes = 0;
        ee_side_writes  = 0;
        errors          = 0;
        captured_wr_addr[0] = 8'd0;
        captured_wr_addr[1] = 8'd0;
        captured_wr_data[0] = 32'd0;
        captured_wr_data[1] = 32'd0;
    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_side_writes <= ee_side_writes + 1;
            if (ev_arg2[7:0] == 8'd1) begin
                // Latch the first two peer writes for chronology checks.
                if (iop_side_writes == 0) begin
                    captured_wr_addr[0] <= ev_arg0[7:0];
                    captured_wr_data[0] <= ev_arg1[31:0];
                end else if (iop_side_writes == 1) begin
                    captured_wr_addr[1] <= ev_arg0[7:0];
                    captured_wr_data[1] <= ev_arg1[31:0];
                end
                iop_side_writes <= iop_side_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] EE read %s: got 0x%08h valid=%0b expected 0x%08h",
                   label, ee_rd_data, ee_rd_valid, expected);
            errors = errors + 1;
        end
    endtask

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

    int wait_cycles;

    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);

        // 1. EE writes the command value into MSCOM.
        ee_write(MSCOM, CMD_VALUE);

        // 2. EE sets CMD_PENDING_BIT in MSFLG — the peer's doorbell.
        ee_write(MSFLG, CMD_PENDING_BIT);

        // 3. Wait for peer to complete its one-shot exchange. Bounded
        //    wait: worst case ~10 cycles per state across 6 states plus
        //    some polling slack.
        wait_cycles = 0;
        while (!peer_done && wait_cycles < 200) begin
            @(posedge clk);
            wait_cycles = wait_cycles + 1;
        end
        if (!peer_done) begin
            $error("[tb_sif_command_echo] peer did not complete within %0d cycles",
                   wait_cycles);
            errors = errors + 1;
        end

        // Let the mailbox settle.
        repeat (2) @(posedge clk);

        // 4. EE reads SMCOM — must be the echoed command.
        ee_read_expect(SMCOM, CMD_VALUE, "SMCOM (echoed command)");

        // 5. EE reads SMFLG — must be CMD_ACK_BIT.
        ee_read_expect(SMFLG, CMD_ACK_BIT, "SMFLG (ack)");

        repeat (4) @(posedge clk);

        // ------------------------------------------------------------------
        $display("[tb_sif_command_echo] peer_done=%0b wait_cycles=%0d ee_side_writes=%0d iop_side_writes=%0d errors=%0d",
                 peer_done, wait_cycles, ee_side_writes, iop_side_writes, errors);

        if (!peer_done)
            $error("peer_done never asserted");
        if (ee_side_writes  < 2) $error("expected >= 2 EE-side writes, got %0d", ee_side_writes);
        if (iop_side_writes < 2) $error("expected >= 2 IOP-side writes (peer SMCOM+SMFLG), got %0d",
                                        iop_side_writes);

        // Protocol chronology: first peer write must be SMCOM=CMD_VALUE,
        // second must be SMFLG=CMD_ACK_BIT, in that order.
        if (captured_wr_addr[0] !== SMCOM)
            $error("peer write[0] expected SMCOM (0x%02h), got 0x%02h", SMCOM, captured_wr_addr[0]);
        if (captured_wr_data[0] !== CMD_VALUE)
            $error("peer write[0] SMCOM data expected 0x%08h, got 0x%08h",
                   CMD_VALUE, captured_wr_data[0]);
        if (captured_wr_addr[1] !== SMFLG)
            $error("peer write[1] expected SMFLG (0x%02h), got 0x%02h", SMFLG, captured_wr_addr[1]);
        if (captured_wr_data[1] !== CMD_ACK_BIT)
            $error("peer write[1] SMFLG data expected 0x%08h, got 0x%08h",
                   CMD_ACK_BIT, captured_wr_data[1]);

        if (errors == 0 && peer_done &&
            ee_side_writes >= 2 && iop_side_writes >= 2 &&
            captured_wr_addr[0] === SMCOM && captured_wr_data[0] === CMD_VALUE &&
            captured_wr_addr[1] === SMFLG && captured_wr_data[1] === CMD_ACK_BIT)
            $display("[tb_sif_command_echo] PASS");
        else
            $display("[tb_sif_command_echo] FAIL");

        $finish;
    end

    initial begin
        #200000;
        $error("[tb_sif_command_echo] timeout");
        $finish;
    end

endmodule : tb_sif_command_echo
