// 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