// retroDE_ps2 — tb_sif_negative_path
//
// Negative-path coordination proof for the SIF mailbox seam. Exercises
// malformed or incomplete lifecycle usage and verifies that the peer
// refuses to false-ack while staying available to recover when the TB
// eventually does the right thing.
//
// Matches the SIF contract's "mismatched or stalled handshakes" debug-
// visibility requirement (docs/contracts/sif.md).
//
// Three phases:
//
//   Phase A — no doorbell: after reset, no EE activity for a long window.
//     Peer must stay silent. ack_count_o == 0, done_o == 0.
//
//   Phase B — doorbell stuck high: one legitimate exchange, then the TB
//     fails to re-arm. MSFLG stays asserted; TB even re-writes the same
//     bit to probe for spurious re-fire. Peer must respond exactly once.
//
//   Phase C — proper re-arm recovers: TB finally clears MSFLG (and SMFLG
//     for cleanliness), then issues a second distinct command. Peer
//     must echo the new command, not stale state.
//
// Key assertions:
//   - ack_count stays at 0 through Phase A's quiet window
//   - ack_count stays at 1 through Phase B's stuck-doorbell window
//   - ack_count advances to 2 only after proper re-arm + new doorbell
//   - SMCOM at end of Phase C holds cmd2, not stale cmd1

`timescale 1ns/1ps

module tb_sif_negative_path;

    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'hCAFE_CAFE;
    localparam logic [31:0] CMD2_VALUE      = 32'h1234_5678;

    // ------------------------------------------------------------------
    // 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_negative_path.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)
        );

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

    int errors;

    initial errors = 0;

    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_negative_path] 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_negative_path] peer did not reach ack_count=%0d (%s) within %0d cycles",
                   target, label, spin);
            errors = errors + 1;
        end
    endtask

    // ------------------------------------------------------------------
    // Phase markers (for $display traceability)
    // ------------------------------------------------------------------

    int phase_a_ack_observed;
    int phase_b_ack_observed;
    int phase_c_ack_observed;

    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;
        phase_a_ack_observed = 0;
        phase_b_ack_observed = 0;
        phase_c_ack_observed = 0;

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

        // ---- Phase A: no doorbell ----
        // Peer must stay silent throughout this window.
        repeat (80) @(posedge clk);
        phase_a_ack_observed = peer_ack_count;
        if (peer_ack_count !== 32'd0) begin
            $error("[tb_sif_negative_path] Phase A: peer fired without a doorbell (ack_count=%0d)",
                   peer_ack_count);
            errors = errors + 1;
        end
        if (peer_done !== 1'b0) begin
            $error("[tb_sif_negative_path] Phase A: done_o asserted without any ack");
            errors = errors + 1;
        end

        // ---- Phase B: legitimate exchange, then stuck doorbell ----
        ee_write(MSCOM, CMD1_VALUE);
        ee_write(MSFLG, CMD_PENDING_BIT);
        wait_for_ack(1, "Phase B first ack");
        repeat (2) @(posedge clk);

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

        // Now leave MSFLG stuck high. TB even probes by re-writing MSFLG
        // with the same bit set — redundant from the storage view, but a
        // common real-world mistake. Peer must not re-fire.
        ee_write(MSFLG, CMD_PENDING_BIT);          // same value, no transition
        repeat (40) @(posedge clk);
        ee_write(MSFLG, CMD_PENDING_BIT | 32'h4);  // extra unrelated bit on
        repeat (40) @(posedge clk);

        phase_b_ack_observed = peer_ack_count;
        if (peer_ack_count !== 32'd1) begin
            $error("[tb_sif_negative_path] Phase B: peer re-fired while doorbell stayed high (count=%0d)",
                   peer_ack_count);
            errors = errors + 1;
        end

        // ---- Phase C: proper re-arm + fresh command ----
        ee_write(MSFLG, 32'd0);                    // clear doorbell
        ee_write(SMFLG, 32'd0);                    // TB also clears its
                                                   // own response flag
        repeat (6) @(posedge clk);                 // let peer observe
                                                   // MSFLG low → clears
                                                   // responded

        ee_write(MSCOM, CMD2_VALUE);
        ee_write(MSFLG, CMD_PENDING_BIT);
        wait_for_ack(2, "Phase C second ack");
        repeat (2) @(posedge clk);

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

        phase_c_ack_observed = peer_ack_count;
        if (peer_ack_count !== 32'd2) begin
            $error("[tb_sif_negative_path] Phase C: expected ack_count=2, got %0d",
                   peer_ack_count);
            errors = errors + 1;
        end

        repeat (4) @(posedge clk);

        // ------------------------------------------------------------------
        $display("[tb_sif_negative_path] phase_a_ack=%0d phase_b_ack=%0d phase_c_ack=%0d errors=%0d",
                 phase_a_ack_observed, phase_b_ack_observed, phase_c_ack_observed, errors);

        if (errors == 0 &&
            phase_a_ack_observed == 0 &&
            phase_b_ack_observed == 1 &&
            phase_c_ack_observed == 2)
            $display("[tb_sif_negative_path] PASS");
        else
            $display("[tb_sif_negative_path] FAIL");

        $finish;
    end

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

endmodule : tb_sif_negative_path
