// retroDE_ps2 — tb_gs_axi_aw_regbuf (Ch443)
//
// AW twin of tb_gs_axi_w_regbuf. Focused scoreboard for the one-entry fully-
// registered AXI AW buffer. Verifies:
//   (1) EXACTLY-ONCE, IN-ORDER delivery with payload integrity {AWADDR,AWLEN,
//       AWSIZE,AWBURST} under randomized upstream offer + downstream backpressure
//       (a drop, dup, or reorder trips the sequence scoreboard).
//   (2) The NO-COMBINATIONAL-BYPASS contract: u_awready === !full every cycle, so a
//       fall-through `u_awready = !full || d_awready` (which would leak downstream
//       AWREADY back upstream into the Z FSM) is caught. A directed phase forces the
//       full && d_awready case.
//   (3) Downstream payload held STABLE while d_awvalid && !d_awready.
//   (4) Full drain leaves the buffer empty with equal produced/consumed counts.
`timescale 1ns/1ps

module tb_gs_axi_aw_regbuf;
    localparam int AW = 32, LN = 8, SZ = 3, BR = 2;
    logic clk = 0; always #5 clk = ~clk;   // 100 MHz
    logic rst_n;

    logic [AW-1:0] u_awaddr; logic [LN-1:0] u_awlen; logic [SZ-1:0] u_awsize; logic [BR-1:0] u_awburst; logic u_awvalid, u_awready;
    logic [AW-1:0] d_awaddr; logic [LN-1:0] d_awlen; logic [SZ-1:0] d_awsize; logic [BR-1:0] d_awburst; logic d_awvalid; logic d_awready;

    gs_axi_aw_regbuf #(.ADDR_W(AW), .LEN_W(LN), .SIZE_W(SZ), .BURST_W(BR)) dut (
        .clk(clk), .rst_n(rst_n),
        .u_awaddr(u_awaddr), .u_awlen(u_awlen), .u_awsize(u_awsize), .u_awburst(u_awburst), .u_awvalid(u_awvalid), .u_awready(u_awready),
        .d_awaddr(d_awaddr), .d_awlen(d_awlen), .d_awsize(d_awsize), .d_awburst(d_awburst), .d_awvalid(d_awvalid), .d_awready(d_awready)
    );

    int errors; initial errors = 0;

    // distinct payload per sequence value (fills all fields)
    function automatic logic [AW-1:0] mk_addr(input logic [31:0] s); mk_addr = (s ^ 32'hCAFE_0000) | 32'd4; endfunction
    function automatic logic [LN-1:0] mk_len (input logic [31:0] s); mk_len  = s[7:0]; endfunction
    function automatic logic [SZ-1:0] mk_size(input logic [31:0] s); mk_size = s[2:0] ^ 3'd5; endfunction
    function automatic logic [BR-1:0] mk_brst(input logic [31:0] s); mk_brst = s[1:0] | 2'b01; endfunction

    // LFSR backpressure on both sides
    logic [15:0] ul = 16'hACE1, dl = 16'h1357;
    always_ff @(posedge clk) begin
        ul <= {ul[14:0], ul[15]^ul[13]^ul[12]^ul[10]};
        dl <= {dl[14:0], dl[15]^dl[13]^dl[12]^dl[10]};
    end
    logic force_ready, force_stall, prod_freeze;
    assign d_awready = force_ready ? 1'b1 : (force_stall ? 1'b0 : (dl[0] | dl[3]));

    // AXI-legal producer: assert u_awvalid with STABLE payload until accepted.
    logic [31:0] wr_seq;
    logic        pending;
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin wr_seq <= 0; pending <= 1'b0; end
        else if (u_awvalid && u_awready) begin
            wr_seq  <= wr_seq + 1;
            pending <= (ul[0] | ul[3]) && !prod_freeze;
        end
        else if (!pending) pending <= (ul[0] | ul[3]) && !prod_freeze;
    end
    assign u_awvalid  = pending;
    assign u_awaddr   = mk_addr(wr_seq);
    assign u_awlen    = mk_len(wr_seq);
    assign u_awsize   = mk_size(wr_seq);
    assign u_awburst  = mk_brst(wr_seq);

    // (1) downstream scoreboard: exactly-once, in-order, payload-correct
    logic [31:0] rd_seq;
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) rd_seq <= 0;
        else if (d_awvalid && d_awready) begin
            if (d_awaddr !== mk_addr(rd_seq) || d_awlen !== mk_len(rd_seq) ||
                d_awsize !== mk_size(rd_seq) || d_awburst !== mk_brst(rd_seq)) begin
                if (errors < 20) $error("[awbuf] drop/dup/reorder/payload at seq %0d: addr %h len %h size %h burst %b",
                                        rd_seq, d_awaddr, d_awlen, d_awsize, d_awburst);
                errors++;
            end
            rd_seq <= rd_seq + 1;
        end
    end

    // (2) NO combinational downstream-ready bypass: u_awready must equal !full.
    always_ff @(posedge clk) if (rst_n) begin
        if (u_awready !== !dut.full) begin
            if (errors < 20) $error("[awbuf] u_awready(%b) != !full(%b) — combinational bypass?", u_awready, dut.full);
            errors++;
        end
    end
    logic saw_full_and_ready; initial saw_full_and_ready = 1'b0;
    always_ff @(posedge clk) if (rst_n && dut.full && d_awready) saw_full_and_ready <= 1'b1;

    // (3) while stalled, the SAME beat must still be presented.
    logic [AW-1:0] hold_a; logic [LN-1:0] hold_l; logic [SZ-1:0] hold_s; logic [BR-1:0] hold_b; logic hold_v;
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin hold_v <= 1'b0; hold_a <= '0; hold_l <= '0; hold_s <= '0; hold_b <= '0; end
        else begin
            if (hold_v) begin
                if (!d_awvalid) begin
                    if (errors < 20) $error("[awbuf] d_awvalid deasserted while stalled"); errors++;
                end else if (d_awaddr !== hold_a || d_awlen !== hold_l || d_awsize !== hold_s || d_awburst !== hold_b) begin
                    if (errors < 20) $error("[awbuf] downstream {awaddr,awlen,awsize,awburst} changed while stalled"); errors++;
                end
            end
            hold_v <= d_awvalid && !d_awready;
            hold_a <= d_awaddr; hold_l <= d_awlen; hold_s <= d_awsize; hold_b <= d_awburst;
        end
    end

    initial begin
        rst_n = 0; force_ready = 0; force_stall = 0; prod_freeze = 0;
        repeat (6) @(posedge clk); rst_n = 1;
        @(posedge clk);
        if (d_awvalid !== 1'b0) begin $error("[awbuf] not empty after reset"); errors++; end

        // Phase 1: randomized offer + backpressure
        repeat (20000) @(posedge clk);

        // Phase 2 (directed): stall downstream so the buffer fills and STAYS full
        // (u_awready must read 0 = !full), then hold full while d_awready=1.
        force_stall = 1; repeat (200) @(posedge clk);
        force_stall = 0; force_ready = 1; repeat (200) @(posedge clk);
        force_ready = 0;

        // Phase 3: freeze producer, drain fully
        prod_freeze = 1; force_ready = 1;
        begin int g; g = 0; while ((wr_seq !== rd_seq) && g < 4000) begin @(posedge clk); g++; end end
        repeat (10) @(posedge clk);

        if (d_awvalid !== 1'b0) begin $error("[awbuf] not empty after drain (d_awvalid=%b)", d_awvalid); errors++; end
        if (wr_seq !== rd_seq) begin $error("[awbuf] count mismatch: in %0d out %0d", wr_seq, rd_seq); errors++; end
        if (wr_seq < 32'd2000) begin $error("[awbuf] too few transfers (%0d) — not meaningful", wr_seq); errors++; end
        if (!saw_full_and_ready) begin $error("[awbuf] coverage: full && d_awready never observed — no-bypass case unexercised"); errors++; end

        $display("[tb_gs_axi_aw_regbuf] in=%0d out=%0d errors=%0d", wr_seq, rd_seq, errors);
        if (errors == 0) $display("[tb_gs_axi_aw_regbuf] PASS");
        else             $display("[tb_gs_axi_aw_regbuf] FAIL");
        $finish;
    end

    initial begin #2000000; $error("[tb_gs_axi_aw_regbuf] TIMEOUT"); $finish; end
endmodule : tb_gs_axi_aw_regbuf
