ec82764bef
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression (272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps, and all dump-derived textures/traces) is excluded via .gitignore and stays local. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
260 lines
16 KiB
Systemverilog
260 lines
16 KiB
Systemverilog
// 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<V_ACTIVE) && (hcnt_d<H_ACTIVE)) begin
|
|
cap_r[vcnt_d][hcnt_d]<=r; cap_g[vcnt_d][hcnt_d]<=g; cap_b[vcnt_d][hcnt_d]<=b; end
|
|
function automatic logic [23:0] px(input int y, input int x); px={cap_b[y][x],cap_g[y][x],cap_r[y][x]}; endfunction
|
|
|
|
// reload handshake witnesses
|
|
bit saw_reload_ready, saw_reload_start;
|
|
always_ff @(posedge clk) begin
|
|
if (!rst_n) begin saw_reload_ready<=0; saw_reload_start<=0; end
|
|
else begin
|
|
if (reload_start) saw_reload_start<=1'b1;
|
|
if (tile_reload_ready) saw_reload_ready<=1'b1;
|
|
end
|
|
end
|
|
|
|
// ---- DEBUG: trace region-A pixel (1,1) Z through the chain ----
|
|
// tile-local index for (lx=1,ly=1) = ly*16+lx = 17. screen Z byte = (1*64+1)*4=0x104.
|
|
// Z block in mem = (Z_BASE+0x104)>>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;p<fill_blk88_n && p<8;p++) $display("[lpddr] fill read#%0d of block 0x88 word1 = 0x%08x", p, fillZ_blk88[p]);
|
|
$display("[lpddr] u_reload.z_ram[16..19] = %08x %08x %08x %08x (idx17 should be 0x8000)",
|
|
u_reload.z_ram[16], u_reload.z_ram[17], u_reload.z_ram[18], u_reload.z_ram[19]);
|
|
$display("[lpddr] u_reload final fill state rst_q=%0d row=%0d beat=%0d", u_reload.rst_q, u_reload.row, u_reload.beat);
|
|
|
|
// The integration proof THROUGH the real LPDDR path:
|
|
chk(saw_reload_start, "gs_stub never pulsed reload_start");
|
|
chk(saw_reload_ready, "gs_tile_reload never asserted reload_ready (pulse-vs-toggle / fill hang)");
|
|
chk(!c_ovf && !z_ovf, "spill FIFO overflow");
|
|
chk(c_beats>0 && 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
|