// retroDE_ps2 — tb_gs_raster_queue_full_pop (Ch87 audit, Ch88 update)
//
// White-box TB for the Ch87 audit-medium fix: when the raster
// FIFO is FULL AND a pop fires this cycle, an arriving
// prim_complete must land in the just-freed slot rather than
// dropping with raster_overflow.
//
// Ch87 (single-cycle SCAN): pop_ok fires at end-of-scan, the
// same cycle as the last pixel emit.
//
// Ch88 (3-stage pixel pipeline + R_DRAIN): pop_ok fires at
// drain-done, three cycles after S0 produces the corner pixel
// (S0 corner → S1 latches corner → S2 latches corner → drain).
// The race we exercise here moves accordingly: v8 must close on
// the drain-done cycle, not on end-of-scan.
//
// Black-box tests through the DMAC + gif_packed_stub pipeline
// can't easily place a primitive close at the EXACT race cycle
// because the GIF A+D pipeline drains at a fixed rate and
// bounding-box scan cycle counts vary. This TB instantiates
// gs_stub alone and drives gif_reg_* directly so every commit
// lands on a precisely-controlled cycle.
//
// Stream (each event is one posedge clk):
//   T0  PRIM = SPRITE
//   T1  FRAME_1 (FBP=2, FBW=10, PSMCT32)
//   T2  RGBAQ
//   T3  v1 = (0,0)
//   T4  v2 = (2,2) close S1 — push S1; T5 pop S1 → R_SCAN
//   T5  v3 = (3,3)
//   T6  v4 = (5,5) close S2 — push (count=1)
//   T7  v5 = (6,6)
//   T8  v6 = (8,8) close S3 — push (count=2 full)
//   T9  idle
//   T10 v7 = (9,9)
//   T11..T16  idle (six idles to land v8 on drain-done)
//   T17 v8 = (11,11) close S4
//        *** AT T17 — load-bearing race ***
//        Pipelined SCAN(S1) timeline (3×3 box):
//          T5  pop S1, state ← R_SCAN
//          T6..T14 S0 generates 9 coords (corner at T14)
//          T15 state = R_DRAIN, s1_valid_q = 1 (corner)
//          T16 state = R_DRAIN, s2_valid_q = 1 (corner)
//          T17 ras_drain_done = 1 → pop_ok = 1
//        At T17: fifo_full = 1, prim_complete_now = 1, pop_ok = 1.
//        Without fix: push_drop = 1 → raster_overflow latches,
//                     S4 dropped → only 3 sprites rasterize.
//        With fix:    push_ok = 1, S4 lands in the pop-freed
//                     slot, count net unchanged at 2.
//                     All 4 sprites eventually rasterize.
//
// SPRITEs are 3×3 → 9 pixels each. With fix: 4 × 9 = 36 pixels
// total, raster_overflow=0. Without fix: 3 × 9 = 27 pixels,
// raster_overflow=1.

`timescale 1ns/1ps

module tb_gs_raster_queue_full_pop;

    logic clk;
    logic rst_n;
    initial clk = 1'b0;
    always #5 clk = ~clk;

    // gs_stub inputs
    logic        gif_reg_wr_en;
    logic [7:0]  gif_reg_num;
    logic [63:0] gif_reg_data;

    // gs_stub outputs (most are tied off — we only watch raster_*)
    logic [7:0]          bg_r, bg_g, bg_b;
    logic [63:0]         prim_q, rgbaq_q, xyz2_q, xyzf2_q, frame_1_q, zbuf_1_q;
    logic                prim_complete;
    logic [31:0]         prim_complete_count;
    logic [63:0]         prim_v0_q, prim_v1_q, prim_v2_q;
    logic [63:0]         prim_color_q;
    logic [63:0]         prim_color_v0_q, prim_color_v1_q, prim_color_v2_q;
    trace_pkg::vertex_t  prim_v0_decoded_q, prim_v1_decoded_q, prim_v2_decoded_q;
    trace_pkg::color_t   prim_v0_color_decoded_q, prim_v1_color_decoded_q, prim_v2_color_decoded_q;
    logic                pixel_emit;
    logic [31:0]         pixel_emit_count;
    logic [11:0]         pixel_x_q, pixel_y_q;
    logic [63:0]         pixel_color_q;
    logic [8:0]          pixel_fbp_q;
    logic [5:0]          pixel_fbw_q, pixel_psm_q;
    logic [31:0]         pixel_fb_addr_q;
    logic                raster_pixel_emit;
    logic [31:0]         raster_pixel_emit_count;
    logic [11:0]         raster_pixel_x_q, raster_pixel_y_q;
    logic [63:0]         raster_pixel_color_q;
    logic [31:0]         raster_pixel_fb_addr_q;
    logic                raster_active;
    logic                raster_overflow;
    logic                raster_degenerate;
    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;

    gs_stub u_gs (
        .clk(clk), .rst_n(rst_n),
        .reg_wr_en(1'b0), .reg_wr_addr(16'd0), .reg_wr_data(64'd0),
        .gif_reg_wr_en(gif_reg_wr_en),
        .gif_reg_num(gif_reg_num),
        .gif_reg_data(gif_reg_data),
        .bg_r(bg_r), .bg_g(bg_g), .bg_b(bg_b),
        .prim_q(prim_q), .rgbaq_q(rgbaq_q),
        .xyz2_q(xyz2_q), .xyzf2_q(xyzf2_q),
        .frame_1_q(frame_1_q), .zbuf_1_q(zbuf_1_q),
        .prim_complete(prim_complete),
        .prim_complete_count(prim_complete_count),
        .prim_v0_q(prim_v0_q), .prim_v1_q(prim_v1_q), .prim_v2_q(prim_v2_q),
        .prim_color_q(prim_color_q),
        .prim_color_v0_q(prim_color_v0_q),
        .prim_color_v1_q(prim_color_v1_q),
        .prim_color_v2_q(prim_color_v2_q),
        .prim_v0_decoded_q(prim_v0_decoded_q),
        .prim_v1_decoded_q(prim_v1_decoded_q),
        .prim_v2_decoded_q(prim_v2_decoded_q),
        .prim_v0_color_decoded_q(prim_v0_color_decoded_q),
        .prim_v1_color_decoded_q(prim_v1_color_decoded_q),
        .prim_v2_color_decoded_q(prim_v2_color_decoded_q),
        .pixel_emit(pixel_emit),
        .pixel_emit_count(pixel_emit_count),
        .pixel_x_q(pixel_x_q), .pixel_y_q(pixel_y_q),
        .pixel_color_q(pixel_color_q),
        .pixel_fbp_q(pixel_fbp_q),
        .pixel_fbw_q(pixel_fbw_q),
        .pixel_psm_q(pixel_psm_q),
        .pixel_fb_addr_q(pixel_fb_addr_q),
        .raster_pixel_emit(raster_pixel_emit),
        .raster_pixel_emit_count(raster_pixel_emit_count),
        .raster_pixel_x_q(raster_pixel_x_q),
        .raster_pixel_y_q(raster_pixel_y_q),
        .raster_pixel_color_q(raster_pixel_color_q),
        .raster_pixel_fb_addr_q(raster_pixel_fb_addr_q),
        .raster_active(raster_active),
        .raster_overflow(raster_overflow),
        .raster_degenerate(raster_degenerate),
        .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)
    );

    int raster_pixel_pulse_count;
    int errors;

    initial begin
        raster_pixel_pulse_count = 0;
        errors = 0;
    end

    always_ff @(posedge clk) begin
        if (rst_n && raster_pixel_emit) raster_pixel_pulse_count <= raster_pixel_pulse_count + 1;
    end

    // Cycle-aligned $display block (commented out by default — un-
    // comment to debug FIFO timing). At the load-bearing drain-done
    // cycle, expect:
    //   state=R_DRAIN count=2 push_ok=1 push_drop=0 pop_ok=1
    //   prim_cn=1 drain_done=1 ovf=0
    // i.e., the audit-medium fix lets push land in the pop-freed
    // slot rather than dropping the primitive.
    //
    // int cycle_idx;
    // initial cycle_idx = 0;
    // always_ff @(posedge clk) if (rst_n) begin
    //     cycle_idx <= cycle_idx + 1;
    //     if (cycle_idx < 30) $display(...);
    // end

    // Drive helpers — each task advances time by exactly one clock
    // cycle with the requested inputs visible at that cycle's posedge.
    // Inputs are set on negedge so they're stable for the next
    // posedge, and the next call starts on the following negedge.
    task automatic step_drive(input logic        wr_en,
                              input logic [7:0]  num,
                              input logic [63:0] data);
        @(negedge clk);
        gif_reg_wr_en = wr_en;
        gif_reg_num   = num;
        gif_reg_data  = data;
        @(posedge clk);
    endtask

    task automatic drive_reg(input logic [7:0] num, input logic [63:0] data);
        step_drive(1'b1, num, data);
    endtask

    task automatic drive_idle();
        step_drive(1'b0, 8'd0, 64'd0);
    endtask

    function automatic logic [63:0] xyz2_data(input logic [11:0] x_int,
                                              input logic [11:0] y_int);
        return {32'd0, y_int, 4'd0, x_int, 4'd0};
    endfunction

    localparam logic [7:0] R_PRIM    = 8'h00;
    localparam logic [7:0] R_RGBAQ   = 8'h01;
    localparam logic [7:0] R_XYZ2    = 8'h05;
    localparam logic [7:0] R_FRAME_1 = 8'h4C;

    localparam logic [63:0] PRIM_SPRITE = 64'd6;
    localparam logic [63:0] FRAME_1_VAL = 64'h0000_0000_000A_0002;
    localparam logic [63:0] RGBAQ_VAL   = 64'h0000_0000_FF00_30FF;

    initial begin
        rst_n         = 1'b0;
        gif_reg_wr_en = 1'b0;
        gif_reg_num   = 8'd0;
        gif_reg_data  = 64'd0;

        // Hold reset for several cycles.
        repeat (4) @(posedge clk);
        rst_n = 1'b1;
        repeat (2) @(posedge clk);

        // T0..T2 — set up PRIM / FRAME_1 / RGBAQ.
        drive_reg(R_PRIM,    PRIM_SPRITE);
        drive_reg(R_FRAME_1, FRAME_1_VAL);
        drive_reg(R_RGBAQ,   RGBAQ_VAL);

        // T3..T4 — sprite 1 vertex pair (closes at T4).
        drive_reg(R_XYZ2, xyz2_data(12'd0, 12'd0));   // v1
        drive_reg(R_XYZ2, xyz2_data(12'd2, 12'd2));   // v2 — close S1
        // SCAN(S1) cycles T5..T13 (9 pixels in a 3×3 box).

        // T5..T6 — sprite 2 vertices (close at T6, FIFO push).
        drive_reg(R_XYZ2, xyz2_data(12'd3, 12'd3));   // v3
        drive_reg(R_XYZ2, xyz2_data(12'd5, 12'd5));   // v4 — close S2

        // T7..T8 — sprite 3 vertices (close at T8, FIFO push to FULL).
        drive_reg(R_XYZ2, xyz2_data(12'd6, 12'd6));   // v5
        drive_reg(R_XYZ2, xyz2_data(12'd8, 12'd8));   // v6 — close S3

        // After S3 close, idle then commit v7 then six idles, so v8
        // close lands EXACTLY on the drain-done cycle of SCAN(S1).
        // The Ch88 pipeline pushes the race three cycles past the
        // old end-of-scan: S0 produces the corner, then S1 latches
        // it, then S2 latches it, then drain-done fires.
        // The FSM has a 1-cycle latency between push S1 and FSM
        // entering R_SCAN (pop fires the cycle after push), which
        // is already absorbed into the cycle count below.
        drive_idle();                                          // idle
        drive_reg(R_XYZ2, xyz2_data(12'd9, 12'd9));            // v7
        drive_idle();                                          // idle
        drive_idle();                                          // idle
        drive_idle();                                          // idle
        drive_idle();                                          // idle (Ch88 pipeline shift)
        drive_idle();                                          // idle (Ch88 pipeline shift)
        drive_idle();                                          // idle (Ch88 pipeline shift)

        // *** load-bearing cycle ***: v8 closes S4 exactly when
        // SCAN(S1) reaches drain-done (state=R_DRAIN with both
        // pipeline valids low), and the FIFO is full. Without the
        // audit-medium fix, this push drops with raster_overflow=1.
        drive_reg(R_XYZ2, xyz2_data(12'd11, 12'd11));          // v8 — close S4

        // Stop driving. Let the FSM finish all queued sprites.
        drive_idle();
        repeat (200) @(posedge clk);

        $display("[tb_gs_raster_queue_full_pop] raster_pixel_pulse_count=%0d  raster_pixel_emit_count=%0d  raster_overflow=%b  raster_degenerate=%b",
                 raster_pixel_pulse_count, raster_pixel_emit_count,
                 raster_overflow, raster_degenerate);

        // *** Load-bearing assertions ***
        // With the Ch87 audit-medium fix: S4 lands in the pop-freed
        // slot at T13. All 4 sprites rasterize → 36 pixels emit.
        // Without the fix: S4 drops at T13, only 3 sprites rasterize
        // → 27 pixels emit, and raster_overflow latches.
        if (raster_pixel_pulse_count != 36) begin
            $error("raster_emit pulses=%0d (expected 36 = 4 sprites × 9 px). 27 means S4 dropped at full+pop+push race.",
                   raster_pixel_pulse_count);
            errors = errors + 1;
        end
        if (raster_pixel_emit_count != 32'd36) begin
            $error("raster_pixel_emit_count=%0d (expected 36)", raster_pixel_emit_count);
            errors = errors + 1;
        end
        if (raster_overflow !== 1'b0) begin
            $error("raster_overflow=%b — full+pop+push race lost a primitive!", raster_overflow);
            errors = errors + 1;
        end
        if (raster_degenerate !== 1'b0) begin
            $error("raster_degenerate=%b (expected 0)", raster_degenerate);
            errors = errors + 1;
        end

        if (errors == 0) $display("[tb_gs_raster_queue_full_pop] PASS");
        else             $display("[tb_gs_raster_queue_full_pop] FAIL");
        $finish;
    end

    initial begin
        #5000000;
        $error("[tb_gs_raster_queue_full_pop] timeout");
        $finish;
    end

endmodule : tb_gs_raster_queue_full_pop
