// retroDE_ps2 — tb_gs_tile_zflush  (Ch323 Brick 1b — focused FSM-sequencing TB)
//
// Proves the Ch323 TP_ZFLUSH phase BEFORE the big two-batch proof (Codex: fast failure
// surface for state-sequencing bugs). Renders ONE 16x16 combined-TAZ tile with
// TILE_SPILL_ENABLE=1 and checks the Z-flush stream:
//   - exactly 256 Z emits (one per tile pixel — no missing last / no duplicate first)
//   - each z_flush_addr = pixel_index*4 (Z-backing-relative, 32-bit Z), each index once
//   - z_flush_data == the tile Z RAM contents at flush time (snooped via tile_z writes:
//     CLEAR sets all 256 to TILE_CLEAR_Z, RENDER overwrites covered pixels) — Z is NOT
//     truncated/endian-swapped
//   - color flush count UNCHANGED (256, full tile) — the color path is untouched
//   - the FSM actually entered TP_ZFLUSH (after the color FLUSH)
`timescale 1ns/1ps

module tb_gs_tile_zflush;
    localparam int H_ACTIVE = 16, V_ACTIVE = 16;
    localparam logic [2:0] TP_FLUSH = 3'd3, TP_ZFLUSH = 3'd6;
    localparam logic [31:0] TILE_CLEAR_Z = 32'h0000_4000;   // matches gs_stub

    logic clk = 0; always #5 clk = ~clk;
    logic rst_n, core_go;
    logic [7:0] r,g,b; logic hsync,vsync,de;
    logic core_halt, dma_done_seen, frame_seen, raster_overflow, frame_toggle, dma_done_toggle;
    logic        z_flush_emit; logic [31:0] z_flush_addr, z_flush_data;
    // Reload staging: this TB exercises Z-FLUSH, so make reload a re-clear (staging returns the
    // clear values, always warm) — the tile starts as if cleared, so the Z-flush check is unchanged.
    logic        reload_start; logic [7:0] tile_reload_raddr;
    wire         tile_reload_ready = 1'b1;
    wire [31:0]  tile_reload_color = 32'hFF00_8000;  // TILE_CLEAR_COLOR
    wire [31:0]  tile_reload_z     = 32'h0000_4000;  // TILE_CLEAR_Z

    top_psmct32_raster_demo_bram #(
        .H_ACTIVE(H_ACTIVE), .V_ACTIVE(V_ACTIVE),
        .VRAM_BYTES(8*1024), .PSMCT32_SWIZZLE(1'b0),
        .COMBINED_TAZ(1'b1), .TILE_LOCAL(1'b1), .TILE_SPILL_ENABLE(1'b1)
    ) dut (
        .clk(clk), .rst_n(rst_n), .core_go(core_go),
        .r(r), .g(g), .b(b), .hsync(hsync), .vsync(vsync), .de(de),
        .core_halt(core_halt), .dma_done_seen(dma_done_seen),
        .frame_seen(frame_seen), .raster_overflow(raster_overflow),
        .frame_toggle(frame_toggle), .dma_done_toggle(dma_done_toggle),
        .joy_a_pressed_i(1'b0), .joy_b_pressed_i(1'b0),
        .z_flush_emit_o(z_flush_emit), .z_flush_addr_o(z_flush_addr), .z_flush_data_o(z_flush_data),
        .reload_start_o(reload_start), .tile_reload_raddr_o(tile_reload_raddr),
        .tile_reload_ready_i(tile_reload_ready), .tile_reload_color_i(tile_reload_color),
        .tile_reload_z_i(tile_reload_z)
    );

    // ---- snoop the tile Z RAM contents (mirror), indexed by TILE index (waddr 0..255):
    // CLEAR writes all 256, RENDER overwrites covered pixels.
    logic [31:0] z_mirror [0:255];
    // ---- capture the Z-flush stream keyed by SCREEN pixel index (addr>>2). The Z is flushed
    // at the screen position (screen_index*4), mirroring the color flush, so we map tile index
    // -> screen index = (tile_oy+y)*fbw*64 + (tile_ox+x) at compare time. Single tile @ origin.
    logic [31:0] z_got    [0:2047];
    bit          z_seen   [0:2047];
    int z_count, color_count;
    logic [5:0]  fbw_seen;
    bit saw_zflush, zflush_after_flush;
    logic [2:0] phase_d;

    initial begin
        for (int i=0;i<256;i++)  z_mirror[i]=32'hX;
        for (int i=0;i<2048;i++) begin z_got[i]=32'h0; z_seen[i]=1'b0; end
        z_count=0; color_count=0; fbw_seen=6'd1; saw_zflush=0; zflush_after_flush=0; phase_d=3'd0;
    end

    always_ff @(posedge clk) begin
        if (rst_n) begin
            // tile Z RAM mirror (any phase that writes Z: CLEAR + RENDER).
            if (dut.u_gs.tile_z_we) z_mirror[dut.u_gs.tile_z_waddr] <= dut.u_gs.tile_z_wdata;
            // color flush emits (raster_pixel_emit) — must stay 256 (full tile), unchanged.
            if (dut.u_gs.raster_pixel_emit) begin
                color_count <= color_count + 1;
                fbw_seen    <= dut.u_gs.ras_fbw;   // FB width (×64 px) for the screen-index map
            end
            // Z-flush stream capture, keyed by screen pixel index = addr>>2.
            if (z_flush_emit) begin
                int sidx; sidx = int'(z_flush_addr >> 2);
                if (sidx >= 0 && sidx < 2048) begin
                    z_got[sidx] <= z_flush_data; z_seen[sidx] <= 1'b1;
                end
                z_count <= z_count + 1;
            end
            // phase tracking: did we enter TP_ZFLUSH, and did it follow TP_FLUSH?
            phase_d <= dut.u_gs.tile_phase;
            if (dut.u_gs.tile_phase == TP_ZFLUSH) begin
                saw_zflush <= 1'b1;
                if (phase_d == TP_FLUSH) zflush_after_flush <= 1'b1;
            end
        end
    end

    int errors;
    task automatic chk(input bit c, input string m);
        if (!c) begin errors++; $display("[zflush] FAIL: %s", m); end endtask

    initial begin
        errors=0; rst_n=0; core_go=0;
        repeat(4) @(posedge clk); rst_n=1; repeat(8) @(posedge clk);
        @(negedge clk); core_go=1'b1; @(negedge clk); core_go=1'b0;
        wait (core_halt==1'b1); repeat(4) @(posedge clk);
        wait (dma_done_seen==1'b1); repeat(10) @(posedge clk);
        if (dut.xfer_busy==1'b1) wait (dut.xfer_busy==1'b0);
        @(posedge dut.u_gs.raster_active);
        @(negedge dut.u_gs.raster_active);   // falls only after TP_ZFLUSH completes
        repeat(10) @(posedge clk);

        // ---- assertions ----
        chk(saw_zflush,          "FSM never entered TP_ZFLUSH");
        chk(zflush_after_flush,  "TP_ZFLUSH did not follow TP_FLUSH");
        chk(z_count == 256,      $sformatf("z_flush count=%0d exp 256 (off-by-one?)", z_count));
        chk(color_count == 256,  $sformatf("color flush count=%0d exp 256 (color path disturbed!)", color_count));
        begin int miss=0, mism=0, stride; stride = int'(fbw_seen) * 64;
            for (int i=0;i<256;i++) begin
                int x, y, sidx; x = i & 8'h0F; y = (i >> 4) & 8'h0F;
                sidx = y*stride + x;                 // single tile @ origin (ox=oy=0)
                if (!z_seen[sidx]) miss++;
                else if (z_got[sidx] !== z_mirror[i]) begin
                    mism++;
                    if (mism<=6) $display("[zflush] Z mismatch tile idx %0d (screen %0d): flushed %08x exp(tileZ) %08x",
                                          i, sidx, z_got[sidx], z_mirror[i]);
                end
            end
            chk(miss==0,  $sformatf("%0d tile pixels never Z-flushed at their screen addr", miss));
            chk(mism==0,  $sformatf("%0d Z values != tile Z RAM", mism));
        end
        chk(!raster_overflow, "raster_overflow set");

        $display("[zflush] z_count=%0d color_count=%0d saw_zflush=%0d after_flush=%0d errors=%0d",
                 z_count, color_count, saw_zflush, zflush_after_flush, errors);
        if (errors==0) $display("[tb_gs_tile_zflush] PASS");
        else           $display("[tb_gs_tile_zflush] FAIL");
        $finish;
    end
    initial begin #20000000; $display("[tb_gs_tile_zflush] TIMEOUT"); $finish; end
endmodule
