// retroDE_ps2 — tb_gs_tile_spill_lpddr (Ch323 Brick 2 — REAL LPDDR-path integration proof) // // The gap that let the board bugs through: tb_gs_tile_spill_reload (Brick 1) forced // tile_reload_ready=1 and served an on-chip backing — it NEVER exercised the real // gs_z_flush_writer / gs_tile_reload / EMIF round-trip, and gs_stub PULSES reload_start // while gs_tile_reload edge-detects it (pulse-vs-toggle). This TB wires the SAME modules // the de25 wires — gs_stub -> 2x gs_z_flush_writer (color/Z) -> behavioral 256-bit EMIF // -> gs_tile_reload -> gs_stub serve port — across an ASYNC emif clock, and proves the // two-batch depth result (region A = red color1, region B = blue color2) THROUGH THE REAL // DATA PATH. Small LPDDR bases (C=0, Z=0x1000) keep the behavioral mem tiny. `timescale 1ns/1ps module tb_gs_tile_spill_lpddr; localparam int H_ACTIVE = 16, V_ACTIVE = 16; localparam logic [31:0] COLOR1 = 32'hFF0000FF, COLOR2 = 32'hFFFF0000; // red / blue (ABGR) localparam [29:0] C_BASE = 30'h0000_0000, Z_BASE = 30'h0000_1000; logic clk=0; always #5 clk = ~clk; // design clock logic eclk=0; always #3 eclk = ~eclk; // emif clock (faster, async) logic rst_n, erst_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; logic flush_emit; logic [31:0] flush_addr, flush_color32; logic cflush_emit; logic [31:0] cflush_addr, cflush_data; logic reload_start; logic [7:0] tile_reload_raddr; logic [29:0] reload_base; logic [31:0] tile_reload_color, tile_reload_z; logic tile_reload_ready; logic [2:0] tile_phase_o; 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), .flush_emit_o(flush_emit), .flush_addr_o(flush_addr), .flush_color32_o(flush_color32), .tile_color_flush_emit_o(cflush_emit), .tile_color_flush_addr_o(cflush_addr), .tile_color_flush_data_o(cflush_data), .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), .reload_base_o(reload_base), .tile_reload_ready_i(tile_reload_ready), .tile_reload_color_i(tile_reload_color), .tile_reload_z_i(tile_reload_z), .tile_phase_o(tile_phase_o) ); // ================= REAL spill writers (color + Z) ================= wire [29:0] cw_aa, zw_aa; wire [1:0] cw_ab, zw_ab; wire [6:0] cw_id, zw_id; wire [7:0] cw_al, zw_al; wire [2:0] cw_as, zw_as; wire cw_av, zw_av, cw_ar, zw_ar; wire [255:0] cw_wd, zw_wd; wire [31:0] cw_ws, zw_ws; wire cw_wl, zw_wl, cw_wv, zw_wv, cw_wr, zw_wr; wire [1:0] cw_br, zw_br; wire cw_bv, zw_bv, cw_brd, zw_brd; wire [31:0] c_beats, c_errs, z_beats, z_errs; wire c_ovf, z_ovf; gs_z_flush_writer #(.Z_BASE(C_BASE), .FB_BASE(30'h8000), .FB_BYTES(32'h1000), .TEX_BASE(30'h9000), .TEX_BYTES(32'h1000)) u_color ( .gs_clk(clk), .gs_rst_n(rst_n), .enable(1'b1), .trace_clear(1'b0), .z_flush_emit(cflush_emit), .z_flush_addr(cflush_addr), .z_flush_data(cflush_data), .z_write_beats(c_beats), .z_wr_errs(c_errs), .fifo_overflow(c_ovf), .axi_clk(eclk), .axi_rst_n(erst_n), .awaddr(cw_aa), .awburst(cw_ab), .awid(cw_id), .awlen(cw_al), .awsize(cw_as), .awvalid(cw_av), .awready(cw_ar), .wdata(cw_wd), .wstrb(cw_ws), .wlast(cw_wl), .wvalid(cw_wv), .wready(cw_wr), .bresp(cw_br), .bvalid(cw_bv), .bready(cw_brd) ); gs_z_flush_writer #(.Z_BASE(Z_BASE), .FB_BASE(30'h8000), .FB_BYTES(32'h1000), .TEX_BASE(30'h9000), .TEX_BYTES(32'h1000)) u_z ( .gs_clk(clk), .gs_rst_n(rst_n), .enable(1'b1), .trace_clear(1'b0), .z_flush_emit(z_flush_emit), .z_flush_addr(z_flush_addr), .z_flush_data(z_flush_data), .z_write_beats(z_beats), .z_wr_errs(z_errs), .fifo_overflow(z_ovf), .axi_clk(eclk), .axi_rst_n(erst_n), .awaddr(zw_aa), .awburst(zw_ab), .awid(zw_id), .awlen(zw_al), .awsize(zw_as), .awvalid(zw_av), .awready(zw_ar), .wdata(zw_wd), .wstrb(zw_ws), .wlast(zw_wl), .wvalid(zw_wv), .wready(zw_wr), .bresp(zw_br), .bvalid(zw_bv), .bready(zw_brd) ); // ================= REAL reload engine ================= wire [29:0] rl_aa; wire [1:0] rl_ab; wire [6:0] rl_id; wire [7:0] rl_al; wire [2:0] rl_as; wire rl_av, rl_ar; wire [255:0] rl_rd; wire [1:0] rl_rr; wire rl_rl, rl_rv, rl_rrdy; wire [31:0] rl_cbeats, rl_zbeats, rl_rderrs; gs_tile_reload #(.COLOR_BASE(C_BASE), .Z_BASE(Z_BASE), .TILE_W(16), .TILE_H(16), .STRIDE_BYTES(256), .ROW_BEATS(2), .COLOR_W(32)) u_reload ( .axi_clk(eclk), .axi_rst_n(erst_n), .reload_start(reload_start), .reload_base(reload_base), .reload_done(), .color_beats(rl_cbeats), .z_beats(rl_zbeats), .rd_errs(rl_rderrs), .araddr(rl_aa), .arburst(rl_ab), .arid(rl_id), .arlen(rl_al), .arsize(rl_as), .arvalid(rl_av), .arready(rl_ar), .rdata(rl_rd), .rresp(rl_rr), .rlast(rl_rl), .rvalid(rl_rv), .rready(rl_rrdy), .serve_clk(clk), .raddr(tile_reload_raddr), .color_o(tile_reload_color), .z_o(tile_reload_z), .reload_ready(tile_reload_ready) ); // ================= behavioral 256-bit EMIF (shared mem; eclk) ================= // Two independent write slaves (color/Z regions disjoint) + one read slave (reload). logic [255:0] mem [0:1023]; // ---- color write slave ---- typedef enum logic [1:0] {WA,WD,WB} ws_t; ws_t cws; logic [29:0] cbeat; assign cw_ar = (cws==WA); assign cw_wr = (cws==WD); assign cw_bv = (cws==WB); assign cw_br = 2'b00; always_ff @(posedge eclk or negedge erst_n) begin if (!erst_n) cws<=WA; else case (cws) WA: if (cw_av) begin cbeat<=cw_aa[29:5]; cws<=WD; end WD: if (cw_wv) begin for (int by=0;by<32;by++) if (cw_ws[by]) mem[cbeat[9:0]][by*8+:8]<=cw_wd[by*8+:8]; cws<=WB; end WB: if (cw_brd) cws<=WA; endcase end // ---- Z write slave ---- ws_t zws; logic [29:0] zbeat; assign zw_ar = (zws==WA); assign zw_wr = (zws==WD); assign zw_bv = (zws==WB); assign zw_br = 2'b00; always_ff @(posedge eclk or negedge erst_n) begin if (!erst_n) zws<=WA; else case (zws) WA: if (zw_av) begin zbeat<=zw_aa[29:5]; zws<=WD; end WD: if (zw_wv) begin for (int by=0;by<32;by++) if (zw_ws[by]) mem[zbeat[9:0]][by*8+:8]<=zw_wd[by*8+:8]; zws<=WB; end WB: if (zw_brd) zws<=WA; endcase end // ---- reload read slave (a few-cycle latency to exercise async timing) ---- typedef enum logic [1:0] {RA,RL,RV} rs_t; rs_t rs; logic [29:0] rbeat; logic [2:0] rlat; assign rl_ar = (rs==RA); assign rl_rv = (rs==RV); assign rl_rl = (rs==RV); assign rl_rr = 2'b00; assign rl_rd = mem[rbeat[9:0]]; always_ff @(posedge eclk or negedge erst_n) begin if (!erst_n) begin rs<=RA; rlat<=0; end else case (rs) RA: if (rl_av) begin rbeat<=rl_aa[29:5]; rlat<=3'd4; rs<=RL; end RL: if (rlat==0) rs<=RV; else rlat<=rlat-1'b1; RV: if (rl_rrdy) rs<=RA; endcase end // ================= scanout capture (from Brick-1 TB) ================= logic [7:0] cap_r[0:V_ACTIVE-1][0:H_ACTIVE-1], cap_g[0:V_ACTIVE-1][0:H_ACTIVE-1], cap_b[0:V_ACTIVE-1][0:H_ACTIVE-1]; bit capture_armed; logic [31:0] hcnt_d, vcnt_d; always_ff @(posedge clk) begin if (!rst_n) begin hcnt_d<=0; vcnt_d<=0; end else begin hcnt_d<=32'(dut.u_pcrtc.hcnt); vcnt_d<=32'(dut.u_pcrtc.vcnt); end end always_ff @(posedge clk) if (rst_n && capture_armed && de && (vcnt_d>5 = 0x88, lane = 0x104[4:2] = 1 (bits 63:32). int reload_pass, zflush_evict; logic [31:0] zA_spilled_data [0:3]; // z_flush_data emitted for region-A screen addr 0x104, per eviction logic zA_spilled_seen [0:3]; logic [31:0] zA_reload_seen [0:3]; // tile_reload_z_i written into tile RAM idx 17, per reload pass logic [31:0] zA_mem_at_reload [0:3]; bit mem_has_8000; // did 0x8000 ever appear anywhere in the Z region after batch1? // emif-domain: each time the reload fill completes a read of the region-A Z block (0x88), // capture what rdata word1 it got + how many fills ran. logic [31:0] fillZ_blk88 [0:7]; int fill_blk88_n; int fill_done_edges; logic rdone_d; always_ff @(posedge eclk) begin if (!erst_n) begin fill_blk88_n<=0; fill_done_edges<=0; rdone_d<=0; end else begin if (rl_rv && rl_rrdy && rl_aa==30'h0000_1100 && fill_blk88_n<8) begin fillZ_blk88[fill_blk88_n[2:0]]<=rl_rd[63:32]; fill_blk88_n<=fill_blk88_n+1; end rdone_d <= u_reload.reload_done; if (u_reload.reload_done && !rdone_d) fill_done_edges<=fill_done_edges+1; end end always_ff @(posedge clk) begin if (!rst_n) begin reload_pass<=0; zflush_evict<=0; zA_spilled_seen[0]<=0; zA_spilled_seen[1]<=0; zA_spilled_seen[2]<=0; zA_spilled_seen[3]<=0; end else begin if (reload_start) reload_pass<=reload_pass+1; // region A screen Z byte addr = (1*64+1)*4 = 0x104. Capture what's emitted for it. if (z_flush_emit && z_flush_addr[15:0]==16'h0104 && zflush_evict<4) begin zA_spilled_data[zflush_evict[1:0]]<=z_flush_data; zA_spilled_seen[zflush_evict[1:0]]<=1'b1; end // count evictions: a rising edge of TP_ZFLUSH entry (phase 6) if (tile_phase_o==3'd6 && dut.u_gs.tile_sweep_r==9'd0) zflush_evict<=zflush_evict+1; if (dut.u_gs.reload_wr_we && dut.u_gs.reload_wr_addr==8'd17 && reload_pass<4) begin zA_reload_seen[reload_pass[1:0]] <= tile_reload_z; zA_mem_at_reload[reload_pass[1:0]] <= mem[10'h088][63:32]; end end end int errors; task automatic chk(input bit c, input string m); if (!c) begin errors++; $display("[lpddr] FAIL: %s", m); end endtask logic [23:0] rA, rB; initial begin errors=0; core_go=0; // Ch323 — simulate a CLEAN BOOT from SCRUB LPDDR: fill the backing with GARBAGE (high Z). // The clean-Z bootstrap must still produce red-on-blue: batch-1 renders from the local // CLEAR (it does NOT reload this garbage), spills, and batch-2 reloads batch-1's spill. for (int i=0;i<1024;i++) mem[i]=256'h7EED7EED_7EED7EED_7EED7EED_7EED7EED_7EED7EED_7EED7EED_7EED7EED_7EED7EED; rst_n=0; erst_n=0; repeat(8) @(posedge eclk); erst_n=1; repeat(4) @(posedge clk); rst_n=1; repeat(8) @(posedge clk); @(negedge clk); core_go<=1'b1; @(negedge clk); core_go<=1'b0; $display("[lpddr] @%0t booted, waiting core_halt", $time); 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); $display("[lpddr] @%0t waiting raster_active", $time); wait (dut.u_gs.raster_active==1'b1); wait (dut.u_gs.raster_active==1'b0); repeat(10) @(posedge clk); @(posedge dut.u_pcrtc.end_of_frame); @(posedge clk); capture_armed<=1'b1; @(posedge dut.u_pcrtc.end_of_frame); @(posedge clk); capture_armed<=1'b0; rA = px(1,1); rB = px(10,2); $display("[lpddr] regionA(1,1)=%06x regionB(10,2)=%06x", rA, rB); $display("[lpddr] writers: c_beats=%0d z_beats=%0d c_ovf=%0d z_ovf=%0d reload: cbeats=%0d zbeats=%0d rderrs=%0d", c_beats, z_beats, c_ovf, z_ovf, rl_cbeats, rl_zbeats, rl_rderrs); $display("[lpddr] handshake: saw_reload_start=%0d saw_reload_ready=%0d reload_passes=%0d", saw_reload_start, saw_reload_ready, reload_pass); $display("[lpddr] DEBUG region-A(idx17,screen 0x104) Z trace (evicts=%0d, reload passes=%0d):", zflush_evict, reload_pass); for (int p=0;p<4;p++) if (zA_spilled_seen[p]) $display("[lpddr] evict %0d: z_flush emitted Z=0x%08x for region-A addr", p, zA_spilled_data[p]); for (int p=0;p<4;p++) $display("[lpddr] reload slot %0d: idx17 served Z=0x%08x (mem lane1 was 0x%08x)", p, zA_reload_seen[p], zA_mem_at_reload[p]); // scan the whole Z region in mem for any 0x8000 word mem_has_8000 = 1'b0; for (int bk=10'h080; bk<10'h0F0; bk++) for (int wd=0; wd<8; wd++) if (mem[bk][wd*32+:32]==32'h0000_8000) mem_has_8000 = 1'b1; $display("[lpddr] mem Z-region contains a 0x8000 word anywhere: %0d", mem_has_8000); $display("[lpddr] fills completed (reload_done edges)=%0d, region-A Z-block reads=%0d:", fill_done_edges, fill_blk88_n); for (int p=0;p0 && z_beats>0, "spill writers wrote 0 beats"); // Color MUST match Z (both are 256 tile px / 8 = 32 beats per eviction). A color>Z mismatch // means the color writer is over-fed (e.g. from generic raster_pixel_emit, not the dedicated // TP_FLUSH color stream) — the Ch323 board "108 vs 64 + overflow" bug. chk(c_beats==z_beats, $sformatf("color beats (%0d) != Z beats (%0d) — color writer over-fed (wrong flush stream)", c_beats, z_beats)); chk(rl_cbeats>0 && rl_zbeats>0, "reload read 0 beats"); chk(rA==COLOR1[23:0], $sformatf("region A = %06x, expected red color1 %06x (reload failed depth)", rA, COLOR1[23:0])); chk(rB==COLOR2[23:0], $sformatf("region B = %06x, expected blue color2 %06x", rB, COLOR2[23:0])); if (errors==0) $display("[tb_gs_tile_spill_lpddr] PASS"); else $display("[tb_gs_tile_spill_lpddr] FAIL (errors=%0d)", errors); $finish; end initial begin #5000000; $display("[tb_gs_tile_spill_lpddr] TIMEOUT"); $display("[tb_gs_tile_spill_lpddr] FAIL"); $finish; end endmodule