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