// retroDE_ps2 — tb_gs_async_fifo (Ch357, Codex)
//
// Scoreboard for gs_async_fifo after the REGISTERED-empty change (rempty <= rempty_nxt, the read-side twin of the
// registered wfull). Two ASYNCHRONOUS clocks. The writer pushes a strictly increasing sequence; the reader pops and
// asserts each rdata equals the next expected value -> catches ANY duplicate (same value twice) or drop (skipped value)
// and guarantees in-order delivery. Covers: continuous reads, final-entry empty assertion, asynchronous write arrival,
// wrap/full backpressure, and randomized read/write gaps under async clocks.

`timescale 1ns/1ps

module tb_gs_async_fifo #(
    parameter bit TEST_BANKED = 1'b0,
    parameter bit TEST_QUADRANT = 1'b0,
    parameter bit TEST_QUAD_WIDTH4 = 1'b0,   // Ch440: 2 depth x 4 width banks
    parameter bit TEST_REGISTERED = TEST_BANKED || TEST_QUADRANT || TEST_QUAD_WIDTH4
);
    // Ch440: the 4-width-bank variant runs at the PRODUCTION 93-bit width so the
    // odd 23/23/23/24 remainder split and every width bank are exercised; the
    // other variants keep the legacy 32-bit width.
    localparam int WIDTH = TEST_QUAD_WIDTH4 ? 93 : 32;
    localparam int DEPTH = 8;

    // Production request-FIFO corner: 40 MHz raster producer into the
    // ~310 MHz EMIF consumer.  A slower-reader test cannot detect publishing
    // the write pointer before a staged RAM commit.
    logic wclk=0; always #12.5 wclk=~wclk;  // 40 MHz
    logic rclk=0; always #1.6  rclk=~rclk;  // 312.5 MHz, async to wclk
    logic wrst_n, rrst_n;

    logic              wr, wfull;
    logic [WIDTH-1:0]  wdata;
    logic              rd, dut_rd, rempty;
    logic [WIDTH-1:0]  rdata;
    logic              registered_pending;

    gs_async_fifo #(.WIDTH(WIDTH), .DEPTH(DEPTH),
                    .REGISTERED_READ(TEST_REGISTERED), .BANKED_READ(TEST_BANKED),
                    .QUADRANT_READ(TEST_QUADRANT), .QUAD_WIDTH4_READ(TEST_QUAD_WIDTH4)) dut (
        .wclk(wclk), .wrst_n(wrst_n), .wr(wr), .wdata(wdata), .wfull(wfull),
        .rclk(rclk), .rrst_n(rrst_n), .rd(dut_rd), .rdata(rdata), .rempty(rempty)
    );

    // independent LFSR backpressure on each clock
    logic [15:0] wl=16'hBEEF; always_ff @(posedge wclk) wl<={wl[14:0], wl[15]^wl[13]^wl[12]^wl[10]};
    logic [15:0] rl=16'h1234; always_ff @(posedge rclk) rl<={rl[14:0], rl[15]^rl[13]^rl[12]^rl[10]};

    logic want_write, force_read_all, stop_write;
    assign want_write = wl[0] | wl[3];          // ~75% offered writes
    assign wr    = want_write && !stop_write;   // FIFO gates internally with !wfull; stop_write freezes the producer
    assign rd    = force_read_all ? 1'b1 : (rl[1] | rl[4]);   // continuous-read phase forces rd=1
    // gs_async_fifo's rd input is an accepted-read handshake.  Keep the
    // randomized read request separate so the test explicitly enforces that
    // interface contract, exactly as every production wrapper does.
    assign dut_rd = rd && !rempty && (!TEST_REGISTERED || !registered_pending);
    always_ff @(posedge rclk or negedge rrst_n) begin
        if (!rrst_n) registered_pending <= 1'b0;
        else         registered_pending <= TEST_REGISTERED && dut_rd;
    end

    logic [WIDTH-1:0] wr_seq, rd_seq;           // sequence counter to write / next expected to read
    logic [WIDTH-1:0] wr_pat, rd_pat;           // payload for wr_seq / expected reconstruction for rd_seq

    // Ch440: payload generator. For the 4-width-bank variant, spread a DISTINCT
    // NONZERO pattern into each of the four 23/23/23/24 banks (sequence counter
    // XORed with per-bank constants, one bank inverted) so that a swapped, broken,
    // or zeroed upper bank changes the reconstructed word and is caught by the
    // full-word scoreboard. Other variants keep the plain incrementing payload.
    // Generate-guarded so the 32-bit variants never elaborate the 93-bit selects.
    generate
        if (TEST_QUAD_WIDTH4) begin : g_payload
            function automatic logic [WIDTH-1:0] mk(input logic [WIDTH-1:0] s);
                mk = '0;
                mk[22:0]  =  s[22:0] ^ 23'h2AAAAA;                    // bank0
                mk[45:23] =  s[22:0] ^ 23'h555555;                    // bank1 (distinct const)
                mk[68:46] = ~s[22:0] ^ 23'h0F0F0F;                    // bank2 (inverted)
                mk[92:69] = {s[7:0], s[15:8], 8'hA5} ^ 24'hC33C5A;    // bank3 (24-bit remainder)
            endfunction
            assign wr_pat = mk(wr_seq);
            assign rd_pat = mk(rd_seq);
        end else begin : g_payload
            assign wr_pat = wr_seq;
            assign rd_pat = rd_seq;
        end
    endgenerate
    assign wdata = wr_pat;

    int errors; initial errors=0;
    int sb_err;                        // scoreboard-only error counter (reset + written solely by the reader always_ff)

    // writer: count accepted writes, advance the sequence
    always_ff @(posedge wclk or negedge wrst_n) begin
        if (!wrst_n) wr_seq <= '0;
        else if (wr && !wfull) wr_seq <= wr_seq + 1;
    end

    // reader scoreboard: every accepted read must equal the next expected sequence value (in order, no dup/drop)
    always_ff @(posedge rclk or negedge rrst_n) begin
        if (!rrst_n) begin rd_seq <= '0; sb_err <= 0; end
        else if (TEST_REGISTERED ? registered_pending : dut_rd) begin
            if (rdata !== rd_pat) begin
                if (sb_err < 20) $error("[afifo] out-of-order/dup/drop/bank: got %h expected %h (seq %0d)", rdata, rd_pat, rd_seq);
                sb_err <= sb_err + 1;
            end
            rd_seq <= rd_seq + 1;
        end
    end

    task automatic run_cycles(input int n_r); repeat (n_r) @(posedge rclk); endtask

    initial begin
        wrst_n=0; rrst_n=0; force_read_all=0; stop_write=0;
        repeat (6) @(posedge wclk); wrst_n=1;
        repeat (6) @(posedge rclk); rrst_n=1;

        // reset check: FIFO must come up EMPTY
        @(posedge rclk);
        if (rempty !== 1'b1) begin $error("[afifo] rempty not asserted after reset"); errors++; end

        // ---- Phase 1: randomized async read/write (wrap/full exercised many times) ----
        run_cycles(30000);

        // ---- Phase 2: continuous reads -> drain fully; assert final-entry empty ----
        force_read_all = 1'b1;
        run_cycles(4000);
        force_read_all = 1'b0;

        // ---- Phase 3: FREEZE the writer, drain fully, assert EMPTY + counts equal (final-entry empty assertion) ----
        stop_write = 1'b1;
        force_read_all = 1'b1;
        begin int g; g=0; while ((wr_seq !== rd_seq) && g<40000) begin @(posedge rclk); g++; end end
        run_cycles(20);

        // ---- checks ----
        if (rempty !== 1'b1)      begin $error("[afifo] FIFO not EMPTY after full drain (rempty=%0b)", rempty); errors++; end
        if (wr_seq !== rd_seq)    begin $error("[afifo] count mismatch: wrote %0d read %0d (drop/dup)", wr_seq, rd_seq); errors++; end
        if (wr_seq < 32'd1000)    begin $error("[afifo] too few transfers (%0d) — test not meaningful", wr_seq); errors++; end
        errors = errors + sb_err;

        $display("[tb_gs_async_fifo] wrote=%0d read=%0d sb_err=%0d rempty=%0b errors=%0d",
                 wr_seq, rd_seq, sb_err, rempty, errors);
        if (errors==0) $display("[tb_gs_async_fifo] PASS");
        else           $display("[tb_gs_async_fifo] FAIL");
        $finish;
    end

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