// retroDE_ps2 — tb_gs_zbuffer (Brick 2b)
//
// White-box UNIT TB for the gs_stub Z-buffer (depth test) datapath.
// Wires gs_stub + vram_stub (byte-addressable, combinational read2)
// directly via gif_reg_* (no DMA). vram_stub.read2 is the stored-Z
// read port (Z_RD_REGISTERED=0); the raster_pixel_emit channel is the
// single VRAM write port (fb color + Z value serialized 2-beat).
//
// Covers, for a FLAT PSMCT32 SPRITE with TEST_1.ZTE=1, ZBUF PSMZ32:
//   - GEQUAL: a fragment with Z >= stored Z passes (writes color + Z);
//             a fragment with Z < stored Z fails (no fb write, no Z write).
//   - GREATER: Z > stored passes; Z == stored fails.
//   - ALWAYS / NEVER.
//   - ZMSK=1 gates the Z update (color still writes on pass; Z unchanged).
//   - Draw-order independence: NEAR (large Z) then FAR (small Z) overlap;
//     GEQUAL → near wins in the overlap.
//
// VRAM layout (PSMCT32 fb at FBP=0, FBW=1 → 64 px/row; PSMZ32 Z buffer
// at ZBP=1 → byte base 1*2048=0x800). Sprites are small (a few px) so
// addresses are easy to predict. The Z buffer is pre-seeded by direct
// writes through the same vram_stub write port before each scenario.

`timescale 1ns/1ps

module tb_gs_zbuffer;

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

    logic        gif_reg_wr_en;
    logic [7:0]  gif_reg_num;
    logic [63:0] gif_reg_data;

    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 [3:0]  raster_pixel_be_q;
    logic [31:0] raster_pixel_mask_q;
    logic [5:0]  raster_pixel_psm_q;
    logic        raster_active;
    logic        raster_overflow;

    logic        gs_tex_rd_en;
    logic [31:0] gs_tex_rd_addr;
    logic        gs_fb_rd_en;
    logic [31:0] gs_fb_rd_addr;
    logic        gs_z_rd_en;
    logic [31:0] gs_z_rd_addr;
    logic [31:0] read2_data;

    // gs_stub with combinational read2 (Z_RD_REGISTERED=0, matches vram_stub).
    gs_stub #(
        .FB_RD_REGISTERED(1'b0),
        .Z_RD_REGISTERED (1'b0)
    ) 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_g(), .bg_b(),
        .pmode_q(), .dispfb1_q(), .display1_q(),
        .prim_q(), .rgbaq_q(),
        .xyz2_q(), .xyzf2_q(),
        .frame_1_q(), .zbuf_1_q(),
        .tex0_1_q(), .tex0_1_cbp_q(), .tex0_1_cpsm_q(),
        .tex0_1_csm_q(), .tex0_1_csa_q(), .tex0_1_cld_q(), .tex0_1_wr_q(),
        .bitbltbuf_q(), .trxpos_q(), .trxreg_q(), .trxdir_q(), .trxdir_wr_q(),
        .prim_complete(), .prim_complete_count(),
        .prim_v0_q(), .prim_v1_q(), .prim_v2_q(),
        .prim_color_q(),
        .prim_color_v0_q(), .prim_color_v1_q(), .prim_color_v2_q(),
        .prim_v0_decoded_q(), .prim_v1_decoded_q(), .prim_v2_decoded_q(),
        .prim_v0_color_decoded_q(), .prim_v1_color_decoded_q(), .prim_v2_color_decoded_q(),
        .pixel_emit(), .pixel_emit_count(),
        .pixel_x_q(), .pixel_y_q(),
        .pixel_color_q(),
        .pixel_fbp_q(), .pixel_fbw_q(), .pixel_psm_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_pixel_be_q(raster_pixel_be_q),
        .raster_pixel_mask_q(raster_pixel_mask_q),
        .raster_pixel_psm_q(raster_pixel_psm_q),
        .raster_active(raster_active),
        .raster_overflow(raster_overflow),
        .raster_fifo_full(),
        .raster_degenerate(),
        .tex_rd_en(gs_tex_rd_en), .tex_rd_addr(gs_tex_rd_addr), .tex_rd_data(read2_data),
        .fb_rd_en(gs_fb_rd_en),   .fb_rd_addr(gs_fb_rd_addr),   .fb_rd_data(read2_data),
        .z_rd_en(gs_z_rd_en),     .z_rd_addr(gs_z_rd_addr),     .z_rd_data(read2_data),
        .ev_valid(), .ev_subsys(), .ev_event(),
        .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
    );

    // ----- VRAM (byte-addressable, combinational read2) -----
    // TB-direct write override so the harness can pre-seed fb + Z.
    logic        tb_we;
    logic [31:0] tb_waddr;
    logic [31:0] tb_wdata;

    logic        vram_we;
    logic [31:0] vram_waddr;
    logic [31:0] vram_wdata;
    logic [3:0]  vram_wbe;

    assign vram_we    = tb_we ? 1'b1            : raster_pixel_emit;
    assign vram_waddr = tb_we ? tb_waddr        : raster_pixel_fb_addr_q;
    assign vram_wdata = tb_we ? tb_wdata        : raster_pixel_color_q[31:0];
    assign vram_wbe   = tb_we ? 4'b1111         : raster_pixel_be_q;

    // read2 address mux (matches the wrapper arbitration; only one of the
    // three consumers is ever active for a Z-tested flat sprite).
    logic [31:0] read2_addr;
    assign read2_addr = gs_tex_rd_en ? gs_tex_rd_addr
                      : gs_fb_rd_en  ? gs_fb_rd_addr
                      : gs_z_rd_en   ? gs_z_rd_addr
                                     : 32'd0;

    logic [31:0] rd_addr;
    logic [31:0] rd_data;

    vram_stub u_vram (
        .clk(clk), .rst_n(rst_n),
        .write_en  (vram_we),
        .write_addr(vram_waddr),
        .write_data(vram_wdata),
        .write_be  (vram_wbe),
        .write_mask(32'hFFFF_FFFF),
        .read_addr (rd_addr),
        .read_data (rd_data),
        .read2_addr(read2_addr),
        .read2_data(read2_data)
    );

    // ---- encoders ----
    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_TEST_1  = 8'h47;
    localparam logic [7:0] R_FRAME_1 = 8'h4C;
    localparam logic [7:0] R_ZBUF_1  = 8'h4E;

    localparam logic [63:0] PRIM_SPRITE = 64'd6;
    // FRAME_1: FBP=0, FBW=1 (64 px/row), PSMCT32.
    localparam logic [63:0] FRAME_1_VAL = 64'h0000_0000_0001_0000;
    // ZBUF_1: ZBP=1 (byte base 0x800), PSM=PSMZ32(0), ZMSK depends.

    function automatic logic [63:0] xyz2_dataz(input logic [11:0] x_int,
                                               input logic [11:0] y_int,
                                               input logic [31:0] z);
        // X=[15:4], Y=[31:20], Z=[63:32].
        return {z, y_int, 4'd0, x_int, 4'd0};
    endfunction

    function automatic logic [63:0] test1_z(input logic ztst_en, input logic [1:0] ztst);
        // ZTE=bit16, ZTST=bits[18:17].
        logic [63:0] v;
        v = 64'd0;
        v[16]    = ztst_en;
        v[18:17] = ztst;
        return v;
    endfunction

    function automatic logic [63:0] zbuf1(input logic [8:0] zbp, input logic zmsk);
        // ZBP=[8:0], PSM=[27:24]=0 (PSMZ32), ZMSK=bit0. The task spec
        // places ZMSK at bit 0 which overlaps ZBP[0]; we keep ZBP EVEN
        // (bit0=0) in every test so the two never alias.
        logic [63:0] v;
        v = 64'd0;
        v[8:0]   = zbp;
        v[27:24] = 4'd0;        // PSMZ32
        v[0]     = zmsk;
        return v;
    endfunction

    // fb byte address for (x,y): FBP*2048 + (y*FBW*64 + x)*4. FBP=0, FBW=1.
    function automatic logic [31:0] fb_addr_of(input logic [11:0] x, input logic [11:0] y);
        return ((y * 64) + x) * 4;
    endfunction
    // Z byte address: ZBP*2048 + (y*FBW*64 + x)*4. ZBP=2 -> 0x1000.
    function automatic logic [31:0] z_addr_of(input logic [11:0] x, input logic [11:0] y);
        return 32'h1000 + (((y * 64) + x) * 4);
    endfunction

    task automatic drive_reg(input logic [7:0] num, input logic [63:0] data);
        @(negedge clk);
        gif_reg_wr_en = 1'b1;
        gif_reg_num   = num;
        gif_reg_data  = data;
        @(posedge clk);
        @(negedge clk);
        gif_reg_wr_en = 1'b0;
        @(posedge clk);
    endtask

    task automatic tb_write(input logic [31:0] addr, input logic [31:0] data);
        @(negedge clk);
        tb_we    = 1'b1;
        tb_waddr = addr;
        tb_wdata = data;
        @(posedge clk);
        @(negedge clk);
        tb_we = 1'b0;
        @(posedge clk);
    endtask

    // Combinational read helper (task; iverilog-12 forbids delays in
    // functions). Drives rd_addr, settles, returns rd_data via output.
    task automatic peek(input logic [31:0] addr, output logic [31:0] val);
        rd_addr = addr;
        #1;
        val = rd_data;
    endtask

    int errors;

    task automatic expect_fb(input logic [11:0] x, input logic [11:0] y,
                             input logic [31:0] exp, input string label);
        logic [31:0] got;
        peek(fb_addr_of(x, y), got);
        if (got !== exp) begin
            $error("[%s] fb(%0d,%0d) got 0x%08x exp 0x%08x", label, x, y, got, exp);
            errors = errors + 1;
        end
    endtask

    task automatic expect_z(input logic [11:0] x, input logic [11:0] y,
                            input logic [31:0] exp, input string label);
        logic [31:0] got;
        peek(z_addr_of(x, y), got);
        if (got !== exp) begin
            $error("[%s] Z(%0d,%0d) got 0x%08x exp 0x%08x", label, x, y, got, exp);
            errors = errors + 1;
        end
    endtask

    // Draw a 1x1 Z-tested flat sprite at (x,y) with color/Z; ZTST + ZMSK
    // configured per call. Returns after raster drains.
    task automatic draw_ztest_pixel(input logic [11:0]  x,
                                    input logic [11:0]  y,
                                    input logic [31:0]  color,
                                    input logic [31:0]  z,
                                    input logic [1:0]   ztst,
                                    input logic         zmsk);
        drive_reg(R_PRIM,    PRIM_SPRITE);
        drive_reg(R_FRAME_1, FRAME_1_VAL);
        drive_reg(R_TEST_1,  test1_z(1'b1, ztst));
        drive_reg(R_ZBUF_1,  zbuf1(9'd2, zmsk));
        drive_reg(R_RGBAQ,   {32'd0, color});
        drive_reg(R_XYZ2,    xyz2_dataz(x, y, z));   // v1
        drive_reg(R_XYZ2,    xyz2_dataz(x, y, z));   // v2 close (1x1)
        repeat (30) @(posedge clk);
    endtask

    initial begin
        rst_n         = 1'b0;
        gif_reg_wr_en = 1'b0;
        gif_reg_num   = 8'd0;
        gif_reg_data  = 64'd0;
        tb_we         = 1'b0;
        tb_waddr      = 32'd0;
        tb_wdata      = 32'd0;
        rd_addr       = 32'd0;
        errors        = 0;

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

        // ============================================================
        // Scenario 1 — GEQUAL pass: stored Z=0x100, frag Z=0x200 (>=).
        // Pixel passes → color written, Z updated to 0x200.
        // ============================================================
        tb_write(z_addr_of(1, 1), 32'h0000_0100);   // seed stored Z
        tb_write(fb_addr_of(1, 1), 32'hDEAD_BEEF);   // seed fb (sentinel)
        draw_ztest_pixel(12'd1, 12'd1, 32'h0000_00AA, 32'h0000_0200, 2'd2, 1'b0);
        expect_fb(1, 1, 32'h0000_00AA, "geq_pass_color");
        expect_z (1, 1, 32'h0000_0200, "geq_pass_z");

        // ============================================================
        // Scenario 2 — GEQUAL fail: stored Z=0x200, frag Z=0x100 (<).
        // Pixel fails → fb stays sentinel, Z stays 0x200.
        // ============================================================
        tb_write(z_addr_of(2, 1), 32'h0000_0200);
        tb_write(fb_addr_of(2, 1), 32'hDEAD_BEEF);
        draw_ztest_pixel(12'd2, 12'd1, 32'h0000_00BB, 32'h0000_0100, 2'd2, 1'b0);
        expect_fb(2, 1, 32'hDEAD_BEEF, "geq_fail_color");   // discarded
        expect_z (2, 1, 32'h0000_0200, "geq_fail_z");       // unchanged

        // ============================================================
        // Scenario 3 — GEQUAL equal-Z passes (>=): stored=frag=0x55.
        // ============================================================
        tb_write(z_addr_of(3, 1), 32'h0000_0055);
        tb_write(fb_addr_of(3, 1), 32'hDEAD_BEEF);
        draw_ztest_pixel(12'd3, 12'd1, 32'h0000_00CC, 32'h0000_0055, 2'd2, 1'b0);
        expect_fb(3, 1, 32'h0000_00CC, "geq_equal_color");
        expect_z (3, 1, 32'h0000_0055, "geq_equal_z");

        // ============================================================
        // Scenario 4 — GREATER equal-Z FAILS: stored=frag=0x55.
        // ============================================================
        tb_write(z_addr_of(4, 1), 32'h0000_0055);
        tb_write(fb_addr_of(4, 1), 32'hDEAD_BEEF);
        draw_ztest_pixel(12'd4, 12'd1, 32'h0000_00DD, 32'h0000_0055, 2'd3, 1'b0);
        expect_fb(4, 1, 32'hDEAD_BEEF, "gtr_equal_fail_color");
        expect_z (4, 1, 32'h0000_0055, "gtr_equal_fail_z");

        // GREATER strictly-greater PASSES.
        tb_write(z_addr_of(5, 1), 32'h0000_0055);
        tb_write(fb_addr_of(5, 1), 32'hDEAD_BEEF);
        draw_ztest_pixel(12'd5, 12'd1, 32'h0000_00EE, 32'h0000_0056, 2'd3, 1'b0);
        expect_fb(5, 1, 32'h0000_00EE, "gtr_pass_color");
        expect_z (5, 1, 32'h0000_0056, "gtr_pass_z");

        // ============================================================
        // Scenario 5 — ALWAYS passes regardless of stored Z.
        // ============================================================
        tb_write(z_addr_of(6, 1), 32'hFFFF_FFFF);   // huge stored Z
        tb_write(fb_addr_of(6, 1), 32'hDEAD_BEEF);
        draw_ztest_pixel(12'd6, 12'd1, 32'h0000_0011, 32'h0000_0001, 2'd1, 1'b0);
        expect_fb(6, 1, 32'h0000_0011, "always_color");
        expect_z (6, 1, 32'h0000_0001, "always_z");

        // ============================================================
        // Scenario 6 — NEVER: nothing written.
        // ============================================================
        tb_write(z_addr_of(7, 1), 32'h0000_0000);
        tb_write(fb_addr_of(7, 1), 32'hDEAD_BEEF);
        draw_ztest_pixel(12'd7, 12'd1, 32'h0000_0022, 32'h0000_9999, 2'd0, 1'b0);
        expect_fb(7, 1, 32'hDEAD_BEEF, "never_color");
        expect_z (7, 1, 32'h0000_0000, "never_z");

        // ============================================================
        // Scenario 7 — ZMSK=1: pass writes color but NOT Z.
        // stored=0x100 frag=0x200 GEQUAL passes; Z must remain 0x100.
        // ============================================================
        tb_write(z_addr_of(8, 1), 32'h0000_0100);
        tb_write(fb_addr_of(8, 1), 32'hDEAD_BEEF);
        draw_ztest_pixel(12'd8, 12'd1, 32'h0000_0033, 32'h0000_0200, 2'd2, 1'b1);
        expect_fb(8, 1, 32'h0000_0033, "zmsk_color");
        expect_z (8, 1, 32'h0000_0100, "zmsk_z_unchanged");

        // ============================================================
        // Scenario 8 — DRAW-ORDER INDEPENDENCE.
        // Clear Z at (10,1). Draw NEAR (large Z=0x300, color NEAR) then
        // FAR (small Z=0x100, color FAR) at the SAME pixel with GEQUAL.
        // Near wins: fb == NEAR, Z == 0x300.  Proves depth gated, NOT
        // last-write-wins.
        // ============================================================
        tb_write(z_addr_of(10, 1), 32'h0000_0000);   // clear Z
        tb_write(fb_addr_of(10, 1), 32'h0000_0000);
        draw_ztest_pixel(12'd10, 12'd1, 32'h0000_00AA, 32'h0000_0300, 2'd2, 1'b0); // NEAR
        draw_ztest_pixel(12'd10, 12'd1, 32'h0000_00BB, 32'h0000_0100, 2'd2, 1'b0); // FAR (behind)
        expect_fb(10, 1, 32'h0000_00AA, "order_near_wins_color");
        expect_z (10, 1, 32'h0000_0300, "order_near_wins_z");
        begin
            logic [31:0] ov;
            peek(fb_addr_of(10, 1), ov);
            if (ov === 32'h0000_00BB) begin
                $error("draw-order: overlap is FAR (last-drawn) — depth NOT gated!");
                errors = errors + 1;
            end
        end

        $display("[tb_gs_zbuffer] raster_emits=%0d overflow=%b errors=%0d",
                 raster_pixel_emit_count, raster_overflow, errors);
        if (raster_overflow) begin
            $error("raster_overflow set"); errors = errors + 1;
        end
        if (errors == 0) $display("[tb_gs_zbuffer] PASS");
        else             $display("[tb_gs_zbuffer] FAIL");
        $finish;
    end

    initial begin
        #5000000;
        $error("[tb_gs_zbuffer] TIMEOUT");
        $finish;
    end

endmodule : tb_gs_zbuffer
