Snapshot: fog implementation + fidelity tooling baseline (pre bilinear-clamp fix)
Per-vertex GS fog end-to-end (gs_stub emit incl. persp_emit5, gs_prim_list_feeder XYZ2->XYZF2 on PRIM.FGE, gs_make_sh3_scheduler_fixture.py F/FGE packing), new fog TBs, fidelity attribution tooling. Functional baseline before removing the dead bilinear lerp8 clamps (Codex: 161-node comb loop -> -0.042ns setup fail). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -46,6 +46,12 @@ module tb_top_psmct32_feeder_persp_demo;
|
||||
.TILE_MULTIPRIM(1'b0), .TILE_PRIM_COUNT(1), .TILE_FIFO_DEPTH(8),
|
||||
.BIN_BUFFER_ENABLE(1'b0), .HEARTBEAT_SPLICE_ENABLE(1'b0),
|
||||
.FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(256),
|
||||
`ifdef GRAD_BOARDPATH
|
||||
// Ch417 — verify the ACTUAL BOARD gradient config: registered numerator feeding the single combinational
|
||||
// divider, held for the five-cycle settle window. This used to select GRAD_SEQ_DIVIDER=1 by mistake and
|
||||
// therefore did not test the production control path its target name claimed to cover.
|
||||
.GRAD_SEQ_DIVIDER(1'b0), .GRAD_DIV_CYCLES(5),
|
||||
`endif
|
||||
.PERSPECTIVE_CORRECT(1'b1)
|
||||
) dut (
|
||||
.clk(clk), .rst_n(rst_n), .core_go(core_go),
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// Integration proof for the runtime CLUT writer mux in the BRAM board top.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_runtime_clut;
|
||||
logic clk = 1'b0;
|
||||
always #5 clk = ~clk;
|
||||
logic rst_n, core_go;
|
||||
logic runtime_wr_en, runtime_busy;
|
||||
logic [7:0] runtime_wr_idx;
|
||||
logic [31:0] runtime_wr_data;
|
||||
logic [7:0] r,g,b;
|
||||
logic hsync,vsync,de,core_halt,dma_done_seen,frame_seen,raster_overflow,frame_toggle,dma_done_toggle;
|
||||
int errors;
|
||||
|
||||
top_psmct32_raster_demo_bram #(.PSMCT32_SWIZZLE(1'b0)) dut (
|
||||
.clk, .rst_n, .core_go, .r, .g, .b, .hsync, .vsync, .de,
|
||||
.core_halt, .dma_done_seen, .frame_seen, .raster_overflow,
|
||||
.frame_toggle, .dma_done_toggle,
|
||||
.joy_a_pressed_i(1'b0), .joy_b_pressed_i(1'b0),
|
||||
.runtime_clut_wr_en_i(runtime_wr_en),
|
||||
.runtime_clut_wr_idx_i(runtime_wr_idx),
|
||||
.runtime_clut_wr_data_i(runtime_wr_data),
|
||||
.runtime_clut_busy_i(runtime_busy)
|
||||
);
|
||||
|
||||
task automatic runtime_write(input logic [7:0] idx, input logic [31:0] data);
|
||||
@(posedge clk);
|
||||
runtime_wr_idx <= idx;
|
||||
runtime_wr_data <= data;
|
||||
runtime_wr_en <= 1'b1;
|
||||
@(posedge clk);
|
||||
runtime_wr_en <= 1'b0;
|
||||
endtask
|
||||
|
||||
task automatic check(input string label, input logic [31:0] got, input logic [31:0] want);
|
||||
if (got !== want) begin
|
||||
$error("[%s] got 0x%08x expected 0x%08x",label,got,want);
|
||||
errors = errors + 1;
|
||||
end
|
||||
endtask
|
||||
|
||||
initial begin
|
||||
rst_n=0; core_go=0; runtime_wr_en=0; runtime_wr_idx=0; runtime_wr_data=0; runtime_busy=0; errors=0;
|
||||
repeat(4) @(posedge clk);
|
||||
rst_n=1;
|
||||
runtime_write(8'h00,32'h10203040);
|
||||
runtime_write(8'h37,32'hA1B2C3D4);
|
||||
runtime_write(8'hFF,32'h55667788);
|
||||
repeat(2) @(posedge clk);
|
||||
check("clut[0]",dut.u_clut.mem[0],32'h10203040);
|
||||
check("clut[55]",dut.u_clut.mem[8'h37],32'hA1B2C3D4);
|
||||
check("clut[255]",dut.u_clut.mem[8'hFF],32'h55667788);
|
||||
runtime_busy=1;
|
||||
repeat(2) @(posedge clk);
|
||||
if (dut.clut_busy !== 1'b1) begin $error("runtime busy did not stall CLUT consumer"); errors=errors+1; end
|
||||
if(errors) $fatal(1,"tb_top_psmct32_runtime_clut FAILED: %0d errors",errors);
|
||||
$display("tb_top_psmct32_runtime_clut PASS: writer mux + busy gate");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
@@ -0,0 +1,246 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_sh3_lpddr_fb (Ch353 Brick 1c — LPDDR-only direct PSMCT32 framebuffer proof)
|
||||
//
|
||||
// Same SH3 draw as tb_top_psmct32_sh3_real_draw_demo, but the framebuffer is NOT mirrored into BRAM:
|
||||
// - FB_LPDDR_ONLY=1 suppresses the BRAM raster write (xfer/CLUT upload still lands in BRAM);
|
||||
// - the exposed PSMCT32 raster stream (flush_color32_o/flush_addr_o/flush_emit_o, gated by PSMCT32 psm)
|
||||
// drives gs_lpddr_axi_master#(.PIX_BYTES(4)) -> a behavioral, strobe-honouring LPDDR framebuffer;
|
||||
// - an end-of-scene `flush` pulse pushes the final partial beat.
|
||||
// PROOF: every emitted raster pixel (captured into an "ideal" frame) must appear byte-exact in the LPDDR FB.
|
||||
// This validates the LPDDR-only direct path itself (writer fidelity), independent of render correctness.
|
||||
// LOCAL/gitignored fixtures (dump-derived); skip-guard if absent.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_sh3_lpddr_fb;
|
||||
`ifdef SH3_FULL_FRAME
|
||||
`include "sh3_full_params.vh" // 256x334 full-frame bounding box
|
||||
`else
|
||||
`include "sh3_real_params.vh" // 256x120 cropped (default)
|
||||
`endif
|
||||
localparam int FB_WORDS = FBPXW*FBH; // cropped 256x120 here; full-frame swaps the fixture (FBH=334)
|
||||
localparam int FB_BYTES = FB_WORDS*4;
|
||||
|
||||
logic clk; logic rst_n; initial clk=1'b0; always #5 clk=~clk; // GS/design clock
|
||||
logic axi_clk2; initial axi_clk2=1'b0; always #2 axi_clk2=~axi_clk2; // faster EMIF-like AXI clock (models the board's 310MHz drain; exercises the real async-FIFO CDC)
|
||||
logic 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 feeder_go_tb, feeder_ready_tb;
|
||||
|
||||
// texture-cache tap + cache<->behavioral texture LPDDR (cloned from the oracle)
|
||||
logic gs_tex_rd_en_o; logic [31:0] gs_tex_rd_addr_o;
|
||||
logic [31:0] tex_cache_data; logic tex_cache_ready;
|
||||
logic [31:0] tex_cache_hits, tex_bram_hits;
|
||||
logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid;
|
||||
logic [7:0] arlen; logic [2:0] arsize; logic arvalid, arready;
|
||||
logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready;
|
||||
logic fill_start, fill_done; logic [31:0] fill_beats, fill_bytes, tex_rd_errs, fill_crc_w;
|
||||
|
||||
// exposed PSMCT32 raster stream (Ch323 ports) -> LPDDR FB writer
|
||||
logic flush_emit_w; logic [31:0] flush_addr_w; logic [31:0] flush_color32_w; logic [5:0] flush_psm_w;
|
||||
|
||||
top_psmct32_raster_demo_bram #(
|
||||
.H_ACTIVE(FBPXW), .V_ACTIVE(FBH),
|
||||
.VRAM_BYTES(VRAM_BYTES_P), .VRAM_ENABLE_READ2(1'b0), .PSMCT32_SWIZZLE(1'b0),
|
||||
.COMBINED_TAZ(1'b0), .TILE_LOCAL(1'b0), .TILE_COLS(1), .TILE_ROWS(1),
|
||||
.TILE_MULTIPRIM(1'b0), .TILE_PRIM_COUNT(1), .TILE_FIFO_DEPTH(8),
|
||||
.BIN_BUFFER_ENABLE(1'b0), .HEARTBEAT_SPLICE_ENABLE(1'b0),
|
||||
.FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(STG_WORDS),
|
||||
.PERSPECTIVE_CORRECT(1'b1), .PERSP_RECIP_IDX_BITS(11),
|
||||
.GRAD_SEQ_DIVIDER(1'b1), .GRAD_DIV_CYCLES(4),
|
||||
.GS_LPDDR_TEX(1'b1), .TEX_VRAM_BASE(TEX_VRAM_BASE), .TEX_CACHE_BYTES(TEX_BYTES),
|
||||
.CLUT_CSM1_ENABLE(1'b1),
|
||||
.FB_LPDDR_ONLY(1'b1) // Ch353 — suppress BRAM raster mirror (CLUT/xfer still write)
|
||||
) 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),
|
||||
.feeder_stg_we_i(1'b0), .feeder_stg_waddr_i(12'd0), .feeder_stg_wdata_i(64'd0),
|
||||
.feeder_go_i(feeder_go_tb), .feeder_ready_o(feeder_ready_tb), .feeder_records_o(), .feeder_waits_o(),
|
||||
.flush_emit_o(flush_emit_w), .flush_addr_o(flush_addr_w),
|
||||
.flush_pix16_o(), .flush_color32_o(flush_color32_w), .flush_psm_o(flush_psm_w),
|
||||
.gs_tex_rd_en_o(gs_tex_rd_en_o), .gs_tex_rd_addr_o(gs_tex_rd_addr_o),
|
||||
.tex_cache_data_i(tex_cache_data), .tex_cache_ready_i(tex_cache_ready),
|
||||
.tex_cache_hits_o(tex_cache_hits), .tex_bram_hits_o(tex_bram_hits)
|
||||
);
|
||||
|
||||
gs_texture_cache #(
|
||||
.LPDDR_TEX_BASE(LPDDR_TEX_BASE), .TEX_VRAM_BASE(TEX_VRAM_BASE),
|
||||
.TEX_BYTES(TEX_BYTES), .N_BEATS(N_BEATS)
|
||||
) u_cache (
|
||||
.axi_clk(clk), .axi_rst_n(rst_n),
|
||||
.fill_start(fill_start), .fill_done(fill_done),
|
||||
.fill_beats(fill_beats), .fill_bytes(fill_bytes), .rd_errs(tex_rd_errs), .fill_crc(fill_crc_w),
|
||||
.araddr(araddr), .arburst(arburst), .arid(arid), .arlen(arlen),
|
||||
.arsize(arsize), .arvalid(arvalid), .arready(arready),
|
||||
.rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready),
|
||||
.sample_clk(clk), .tex_rd_en(gs_tex_rd_en_o),
|
||||
.tex_rd_addr(gs_tex_rd_addr_o), .tex_rd_data(tex_cache_data), .tex_ready(tex_cache_ready)
|
||||
);
|
||||
|
||||
// behavioral texture LPDDR (cloned from the oracle)
|
||||
logic [31:0] lpddr_mem [0:(TEX_BYTES/4)-1];
|
||||
typedef enum logic [1:0] { S_IDLE, S_WAIT, S_DATA } sstate_t; sstate_t sst;
|
||||
logic [3:0] dly; int beat_idx;
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin sst<=S_IDLE; arready<=0; rvalid<=0; rlast<=0; rresp<=2'b00; rdata<='0; dly<='0; end
|
||||
else begin
|
||||
arready<=0; rvalid<=0; rlast<=0;
|
||||
case (sst)
|
||||
S_IDLE: if (arvalid) begin arready<=1; beat_idx<=(araddr-LPDDR_TEX_BASE)>>5; dly<=4'd2; sst<=S_WAIT; end
|
||||
S_WAIT: if (dly==0) sst<=S_DATA; else dly<=dly-1'b1;
|
||||
S_DATA: if (rready) begin
|
||||
for (int w=0; w<8; w++) rdata[w*32 +: 32] <= lpddr_mem[beat_idx*8 + w];
|
||||
rresp<=2'b00; rvalid<=1; rlast<=1; sst<=S_IDLE;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// ---- Ch353 LPDDR FRAMEBUFFER writer (PSMCT32) + behavioral strobe-honouring LPDDR FB ----
|
||||
logic fb_commit=0; always #13 fb_commit=~fb_commit; // free-run ctrl_commit so arm latches (PIX_BYTES=4)
|
||||
logic fb_flush=0;
|
||||
logic [255:0] fbw_wdata; logic [31:0] fbw_wstrb, fbw_awaddr; logic fbw_awvalid, fbw_wvalid;
|
||||
logic [31:0] fbw_beats, fbw_ovf; logic fbw_idle, fbw_drained;
|
||||
gs_lpddr_axi_master #(.FIFO_DEPTH(64), .PIX_BYTES(4)) u_fbwriter (
|
||||
.gs_clk(clk), .gs_rst_n(rst_n), .enable(1'b1),
|
||||
.arm(1'b1), .canary(1'b0), .fb_base(32'h0), .ctrl_commit(fb_commit),
|
||||
.px_emit(flush_emit_w && (flush_psm_w == 6'h00)), // PSMCT32 raster emits only
|
||||
.px_addr(flush_addr_w), .px_pix32(flush_color32_w), .flush(fb_flush),
|
||||
.axi_clk(axi_clk2), .axi_rst_n(rst_n),
|
||||
.awaddr(fbw_awaddr), .awlen(), .awsize(), .awburst(), .awid(),
|
||||
.awvalid(fbw_awvalid), .awready(1'b1),
|
||||
.wdata(fbw_wdata), .wstrb(fbw_wstrb), .wlast(), .wvalid(fbw_wvalid), .wready(1'b1),
|
||||
.bvalid(1'b1), .bready(), .bresp(2'b00),
|
||||
.beats_written(fbw_beats), .bursts_issued(), .bresp_err_count(),
|
||||
.fifo_overflow_count(fbw_ovf), .idle(fbw_idle), .frame_drained(fbw_drained)
|
||||
);
|
||||
// behavioral LPDDR FB: write only strobed bytes at the latched AW addr
|
||||
logic [7:0] fb_lpddr [0:FB_BYTES-1];
|
||||
logic [31:0] fb_aw_lat;
|
||||
always_ff @(posedge axi_clk2) begin // writer's AXI domain
|
||||
if (fbw_awvalid) fb_aw_lat <= fbw_awaddr;
|
||||
if (fbw_wvalid) for (int i=0;i<32;i++) if (fbw_wstrb[i]) begin
|
||||
int a; a = fb_aw_lat + i; if (a>=0 && a<FB_BYTES) fb_lpddr[a] <= fbw_wdata[i*8 +: 8];
|
||||
end
|
||||
end
|
||||
// "ideal" frame: every emitted PSMCT32 pixel, captured straight off the raster stream
|
||||
logic [31:0] ideal [0:FB_WORDS-1]; logic ideal_set [0:FB_WORDS-1];
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst_n && flush_emit_w && (flush_psm_w == 6'h00)) begin
|
||||
ideal [flush_addr_w>>2] <= flush_color32_w;
|
||||
ideal_set[flush_addr_w>>2] <= 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
// ---- Ch353 (Codex #1) FULL-FRAME bounded ORACLE: score the rendered frame vs the reference texel map.
|
||||
// Distinct from the transport proof above — this proves the UNCROPPED renderer is correct, not just that
|
||||
// bytes reached LPDDR. Same bounded <=1-texel acceptance as tb_top_psmct32_sh3_real_draw_demo.
|
||||
logic [31:0] idx_words [0:(512*512/4)-1];
|
||||
logic [31:0] pal [0:255];
|
||||
logic [31:0] refmap [0:FB_WORDS-1];
|
||||
function automatic logic [7:0] sh3_idx(input integer u, input integer v);
|
||||
integer lin; logic [31:0] w; lin=v*TW+u; w=idx_words[lin/4]; sh3_idx=w[(8*(lin%4)) +: 8];
|
||||
endfunction
|
||||
function automatic logic [23:0] exp_cell(input integer u, input integer v);
|
||||
exp_cell = pal[sh3_idx(u,v)][23:0]; endfunction
|
||||
|
||||
int errors;
|
||||
initial begin
|
||||
errors=0; feeder_go_tb=1'b0;
|
||||
for (int i=0;i<FB_WORDS;i++) begin ideal_set[i]=1'b0; ideal[i]=32'd0; end // model the precleared FB (Brick-3 preclear)
|
||||
lpddr_mem[0]='x;
|
||||
$readmemh(`FEEDER_SH3_REAL_FILE, dut.g_feeder.feeder_stg);
|
||||
$readmemh("../../data/top_psmct32_raster_demo/sh3_real_tex_lpddr.mem", lpddr_mem);
|
||||
$readmemh("../../data/top_psmct32_raster_demo/sh3_real_idx.mem", idx_words);
|
||||
$readmemh("../../data/top_psmct32_raster_demo/sh3_real_pal.mem", pal);
|
||||
`ifdef SH3_FULL_FRAME
|
||||
$readmemh("../../data/top_psmct32_raster_demo/sh3_full_refmap.mem", refmap);
|
||||
`else
|
||||
$readmemh("../../data/top_psmct32_raster_demo/sh3_real_refmap.mem", refmap);
|
||||
`endif
|
||||
if (lpddr_mem[0] === 32'bx) begin
|
||||
$display("[tb_top_psmct32_sh3_lpddr_fb] SKIP — sh3_real_*.mem absent (run gs_make_sh3_real_draw_fixture.py)");
|
||||
$finish;
|
||||
end
|
||||
|
||||
rst_n=1'b0; core_go=1'b0; fill_start=1'b0;
|
||||
repeat(4) @(posedge clk); rst_n=1'b1; repeat(8) @(posedge clk);
|
||||
|
||||
// warm the texture cache
|
||||
@(posedge clk) fill_start<=1'b1;
|
||||
begin int gd=0; while (!fill_done && gd<200000) begin @(posedge clk); gd++; end end
|
||||
if (!fill_done) begin $error("cache fill_done never asserted"); errors++; end
|
||||
if (fill_crc_w!==32'hfbdeaa32) begin $error("cache fill_crc=%08x exp fbdeaa32", fill_crc_w); errors++; end
|
||||
|
||||
// boot the EE bootlet (uploads CLUT via BITBLT into BRAM) + render the scene
|
||||
@(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);
|
||||
if (!tex_cache_ready) begin $error("tex cache not ready before raster"); errors++; end
|
||||
wait (feeder_ready_tb==1'b1); // whole scene rendered + drained
|
||||
repeat(50) @(posedge clk);
|
||||
|
||||
// end-of-scene flush, then wait on the ORDERED drain ack. frame_drained (axi-domain) is set only when the
|
||||
// EOF marker pops — i.e. AFTER the last data beat's BRESP (in-order FIFO + per-beat B). It's a stable level
|
||||
// (not a transient like fbw_idle), so sampling it here is race-free. This is Codex's explicit drain ack.
|
||||
@(negedge clk) fb_flush<=1'b1; @(negedge clk) fb_flush<=1'b0;
|
||||
begin int d=0; while(!fbw_drained && d<400000) begin @(posedge clk); d++; end end
|
||||
if (!fbw_drained) begin $error("[fbproof] frame_drained never asserted — writer not drained"); errors++; end
|
||||
if (fbw_ovf!==0) begin $error("[fbproof] FIFO overflow=%0d", fbw_ovf); errors++; end
|
||||
|
||||
// PROOF: every covered (emitted) pixel must be byte-exact in the LPDDR FB
|
||||
begin
|
||||
int nset, mism; nset=0; mism=0;
|
||||
for (int w=0; w<FB_WORDS; w++) if (ideal_set[w]) begin
|
||||
logic [31:0] fbw;
|
||||
nset++;
|
||||
fbw = {fb_lpddr[w*4+3], fb_lpddr[w*4+2], fb_lpddr[w*4+1], fb_lpddr[w*4]};
|
||||
if (fbw !== ideal[w]) begin
|
||||
if (mism<10) $error("[fbproof] word %0d (byte %0d) LPDDR=%08x ideal=%08x", w, w*4, fbw, ideal[w]);
|
||||
mism++;
|
||||
end
|
||||
end
|
||||
if (nset==0) begin $error("[fbproof] no PSMCT32 pixels captured — psm gate (0x00) wrong?"); errors++; end
|
||||
errors += mism;
|
||||
$display("[fbproof] covered=%0d words, mismatches=%0d, writer beats=%0d", nset, mism, fbw_beats);
|
||||
end
|
||||
|
||||
// ===== Ch353 (Codex #1) FULL-FRAME bounded ORACLE: rendered frame vs reference texel map, <=1 texel =====
|
||||
begin
|
||||
int m_total, m_ok, int_total, int_ok, cb;
|
||||
m_total=0; m_ok=0; int_total=0; int_ok=0; cb=0;
|
||||
for (int py=0; py<FBH; py++) for (int px=0; px<FBPXW; px++) begin
|
||||
logic [31:0] rm; logic [23:0] fb; int tu, tv, D;
|
||||
rm = refmap[py*FBPXW+px];
|
||||
if (rm[31]) begin // covered by the draw
|
||||
tu = (rm>>9)&9'h1FF; tv = rm&9'h1FF;
|
||||
fb = ideal[py*FBPXW+px][23:0]; // rendered color (== LPDDR FB, proven equal above)
|
||||
m_total++;
|
||||
D=9;
|
||||
for (int rad=0; rad<=1; rad++)
|
||||
for (int du=-rad; du<=rad; du++) for (int dv=-rad; dv<=rad; dv++) begin
|
||||
int ch; ch=(du<0?-du:du); if((dv<0?-dv:dv)>ch) ch=(dv<0?-dv:dv);
|
||||
if (ch==rad && D==9 && (tu+du)>=0 && (tu+du)<TW && (tv+dv)>=0 && (tv+dv)<TH
|
||||
&& fb===exp_cell(tu+du, tv+dv)) D=rad;
|
||||
end
|
||||
if (D<=1) m_ok++;
|
||||
if (rm[30]) begin int_total++; if (D<=1) int_ok++; end // interior (seam-free band excluded)
|
||||
begin bit f; f=1'b0; for (int i=0;i<256;i++) if (pal[i][23:0]===fb) f=1'b1; if (!f) cb++; end
|
||||
end
|
||||
end
|
||||
$display("[oracle] full-frame <=1texel ALL=%0d/%0d (%.1f%%) INT=%.1f%% clut_bad=%0d",
|
||||
m_ok, m_total, (m_total>0)?100.0*m_ok/m_total:0.0,
|
||||
(int_total>0)?100.0*int_ok/int_total:0.0, cb);
|
||||
if (cb !== 0) begin $error("[oracle] %0d covered px NOT a CLUT entry (wrong palette)", cb); errors++; end
|
||||
if (int_total>0 && (100.0*int_ok/int_total) < 95.0)
|
||||
begin $error("[oracle] interior <=1texel %.1f%% < 95%% (render wrong)", 100.0*int_ok/int_total); errors++; end
|
||||
end
|
||||
|
||||
$display("[tb_top_psmct32_sh3_lpddr_fb] errors=%0d", errors);
|
||||
if (errors==0) $display("[tb_top_psmct32_sh3_lpddr_fb] PASS");
|
||||
else $display("[tb_top_psmct32_sh3_lpddr_fb] FAIL");
|
||||
$finish;
|
||||
end
|
||||
initial begin #20000000; $error("[tb_top_psmct32_sh3_lpddr_fb] TIMEOUT"); $finish; end
|
||||
endmodule : tb_top_psmct32_sh3_lpddr_fb
|
||||
@@ -0,0 +1,352 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_sh3_lpddr_fb_seq (Ch353 Brick 3 — LPDDR-only FB host-start SEQUENCE proof)
|
||||
//
|
||||
// Proves Codex's round-5 board sequence at the module level, ahead of the owner fit:
|
||||
// * FEEDER_AUTOSTART=0 -> boot setup reaches C_READY WITHOUT a boot render.
|
||||
// * RENDER EPOCH: the end-of-scene flush (EOF) fires ONLY on a feeder_ready RISE that follows an accepted
|
||||
// host GO (render_inflight), so the setup->ready edge can NOT enqueue a false EOF.
|
||||
// * frame_drained is a HARD gate (no timeout): scanout enable is driven by it, EMIF-domain coherent.
|
||||
//
|
||||
// Acceptance (Codex): boot reaches ready; BEFORE GO zero raster writes / zero EOF / frame_drained=0; preclear (FB=0)
|
||||
// no BRESP; texture fill verifies (crc); host sets base 0 / canary off / arms writer; GO drops then re-raises ready;
|
||||
// exactly ONE EOF -> exactly ONE frame_drained; scanout reads exactly 334*32=10688 beats and matches ALL 256x334
|
||||
// pixels (incl. black); underflow=0, rd/wr errors=0, FIFO overflow=0; a synchronized frame_drained reaches "bridge".
|
||||
// LOCAL/gitignored fixtures (dump-derived); skip-guard if absent.
|
||||
`timescale 1ns/1ps
|
||||
`define SH3_FULL_FRAME 1
|
||||
|
||||
module tb_top_psmct32_sh3_lpddr_fb_seq;
|
||||
`include "sh3_full_params.vh" // 256x334 full-frame bounding box (FBPXW,FBH,STG_WORDS,TEX_*,TW,TH,...)
|
||||
localparam int W = FBPXW, H = FBH; // 256 x 334
|
||||
localparam int STRIDE = W*4; // 1024 B (PSMCT32, no padding)
|
||||
localparam int ROW_BEATS = STRIDE/32; // 32
|
||||
localparam int FB_BYTES = STRIDE*H; // 342016
|
||||
localparam int FB_WORDS = W*H;
|
||||
localparam int BEATS_PER_FRAME = ROW_BEATS*H; // 10688
|
||||
localparam int H_ACT=W, V_ACT=H, H_BP=32, H_FP=8, V_BP=16, V_FP=8;
|
||||
localparam int H_TOT=H_BP+H_ACT+H_FP, V_TOT=V_BP+V_ACT+V_FP;
|
||||
|
||||
logic clk=0; always #5 clk=~clk; // GS/design clock (feeder + writer gs-side)
|
||||
logic emif_clk=0; always #2 emif_clk=~emif_clk; // EMIF (fast, independent) — writer axi-side + scanout axi-side
|
||||
logic video_clk=0; always #7 video_clk=~video_clk;// video (independent of both)
|
||||
logic bridge_clk=0;always #11 bridge_clk=~bridge_clk; // "HPS bridge" domain (frame_drained diagnostic sync)
|
||||
logic rst_n;
|
||||
logic fb_commit=0; always #13 fb_commit=~fb_commit; // free-run ctrl_commit so arm/base/canary latch
|
||||
|
||||
int errors; initial errors=0;
|
||||
|
||||
// ================= bram-top: SH3 draw via the feeder, FB_LPDDR_ONLY, AUTOSTART OFF =================
|
||||
logic core_go; logic [7:0] r,g,b; logic hsync,vsync_o,de;
|
||||
logic core_halt,dma_done_seen,frame_seen,raster_overflow,frame_toggle,dma_done_toggle;
|
||||
logic feeder_go_tb, feeder_ready_tb;
|
||||
logic gs_tex_rd_en_o; logic [31:0] gs_tex_rd_addr_o;
|
||||
logic [31:0] tex_cache_data; logic tex_cache_ready;
|
||||
logic [31:0] tex_cache_hits, tex_bram_hits;
|
||||
logic flush_emit_w; logic [31:0] flush_addr_w; logic [31:0] flush_color32_w; logic [5:0] flush_psm_w;
|
||||
|
||||
top_psmct32_raster_demo_bram #(
|
||||
.H_ACTIVE(FBPXW), .V_ACTIVE(FBH),
|
||||
.VRAM_BYTES(VRAM_BYTES_P), .VRAM_ENABLE_READ2(1'b0), .PSMCT32_SWIZZLE(1'b0),
|
||||
.COMBINED_TAZ(1'b0), .TILE_LOCAL(1'b0), .TILE_COLS(1), .TILE_ROWS(1),
|
||||
.TILE_MULTIPRIM(1'b0), .TILE_PRIM_COUNT(1), .TILE_FIFO_DEPTH(8),
|
||||
.BIN_BUFFER_ENABLE(1'b0), .HEARTBEAT_SPLICE_ENABLE(1'b0),
|
||||
.FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(STG_WORDS), .FEEDER_AUTOSTART(1'b0), // Ch353 — no boot render
|
||||
.PERSPECTIVE_CORRECT(1'b1), .PERSP_RECIP_IDX_BITS(11),
|
||||
.GRAD_SEQ_DIVIDER(1'b1), .GRAD_DIV_CYCLES(4),
|
||||
.GS_LPDDR_TEX(1'b1), .TEX_VRAM_BASE(TEX_VRAM_BASE), .TEX_CACHE_BYTES(TEX_BYTES),
|
||||
.CLUT_CSM1_ENABLE(1'b1),
|
||||
.FB_LPDDR_ONLY(1'b1)
|
||||
) dut (
|
||||
.clk(clk), .rst_n(rst_n), .core_go(core_go),
|
||||
.r(r), .g(g), .b(b), .hsync(hsync), .vsync(vsync_o), .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),
|
||||
.feeder_stg_we_i(1'b0), .feeder_stg_waddr_i(12'd0), .feeder_stg_wdata_i(64'd0),
|
||||
.feeder_go_i(feeder_go_tb), .feeder_ready_o(feeder_ready_tb), .feeder_records_o(), .feeder_waits_o(),
|
||||
.flush_emit_o(flush_emit_w), .flush_addr_o(flush_addr_w),
|
||||
.flush_pix16_o(), .flush_color32_o(flush_color32_w), .flush_psm_o(flush_psm_w),
|
||||
.gs_tex_rd_en_o(gs_tex_rd_en_o), .gs_tex_rd_addr_o(gs_tex_rd_addr_o),
|
||||
.tex_cache_data_i(tex_cache_data), .tex_cache_ready_i(tex_cache_ready),
|
||||
.tex_cache_hits_o(tex_cache_hits), .tex_bram_hits_o(tex_bram_hits)
|
||||
);
|
||||
|
||||
// texture cache + behavioral texture LPDDR (from Brick 1c)
|
||||
logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid; logic [7:0] arlen; logic [2:0] arsize;
|
||||
logic arvalid, arready; logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready;
|
||||
logic fill_start, fill_done; logic [31:0] fill_beats, fill_bytes, tex_rd_errs, fill_crc_w;
|
||||
gs_texture_cache #(.LPDDR_TEX_BASE(LPDDR_TEX_BASE), .TEX_VRAM_BASE(TEX_VRAM_BASE),
|
||||
.TEX_BYTES(TEX_BYTES), .N_BEATS(N_BEATS)) u_cache (
|
||||
.axi_clk(clk), .axi_rst_n(rst_n), .fill_start(fill_start), .fill_done(fill_done),
|
||||
.fill_beats(fill_beats), .fill_bytes(fill_bytes), .rd_errs(tex_rd_errs), .fill_crc(fill_crc_w),
|
||||
.araddr(araddr), .arburst(arburst), .arid(arid), .arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready),
|
||||
.rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready),
|
||||
.sample_clk(clk), .tex_rd_en(gs_tex_rd_en_o), .tex_rd_addr(gs_tex_rd_addr_o),
|
||||
.tex_rd_data(tex_cache_data), .tex_ready(tex_cache_ready)
|
||||
);
|
||||
logic [31:0] lpddr_mem [0:(TEX_BYTES/4)-1];
|
||||
typedef enum logic [1:0] { S_IDLE, S_WAIT, S_DATA } sstate_t; sstate_t sst; logic [3:0] tdly; int tbeat;
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin sst<=S_IDLE; arready<=0; rvalid<=0; rlast<=0; rresp<=0; rdata<=0; tdly<=0; end
|
||||
else begin
|
||||
arready<=0; rvalid<=0; rlast<=0;
|
||||
case (sst)
|
||||
S_IDLE: if (arvalid) begin arready<=1; tbeat<=(araddr-LPDDR_TEX_BASE)>>5; tdly<=4'd2; sst<=S_WAIT; end
|
||||
S_WAIT: if (tdly==0) sst<=S_DATA; else tdly<=tdly-1'b1;
|
||||
S_DATA: if (rready) begin for (int w=0;w<8;w++) rdata[w*32 +: 32]<=lpddr_mem[tbeat*8+w];
|
||||
rresp<=2'b00; rvalid<=1; rlast<=1; sst<=S_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// ================= RENDER EPOCH (Codex round 5) — flush only on a ready-rise that FOLLOWS an accepted GO =================
|
||||
// render_inflight set when a GO is accepted (feeder_go pulse while ready); the end-of-scene EOF flush fires on the
|
||||
// NEXT feeder_ready rise (render+drain done) and clears the epoch. The FEEDER_AUTOSTART=0 setup->ready edge has
|
||||
// render_inflight=0, so it can NOT produce a false EOF.
|
||||
logic fb_flush=0; logic render_inflight=0, feeder_ready_q=0; int eof_count;
|
||||
wire feeder_ready_rise = feeder_ready_tb && !feeder_ready_q;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin render_inflight<=0; feeder_ready_q<=0; fb_flush<=0; eof_count<=0; end
|
||||
else begin
|
||||
feeder_ready_q <= feeder_ready_tb;
|
||||
fb_flush <= 1'b0;
|
||||
if (feeder_go_tb && feeder_ready_tb) render_inflight <= 1'b1; // accepted host GO opens the epoch
|
||||
if (render_inflight && feeder_ready_rise) begin
|
||||
fb_flush <= 1'b1; // exactly one EOF per render epoch
|
||||
render_inflight <= 1'b0;
|
||||
eof_count <= eof_count + 1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ================= Ch353 PSMCT32 writer + behavioral (precleared) LPDDR FB =================
|
||||
logic wr_arm=0, wr_canary=0; logic [31:0] wr_base=32'h0; // host-controlled (Codex step 5)
|
||||
logic [255:0] fbw_wdata; logic [31:0] fbw_wstrb, fbw_awaddr; logic fbw_awvalid, fbw_wvalid;
|
||||
logic [31:0] fbw_beats, fbw_ovf, fbw_bresp_err; logic fbw_idle, fbw_drained;
|
||||
gs_lpddr_axi_master #(.FIFO_DEPTH(64), .PIX_BYTES(4)) u_wr (
|
||||
.gs_clk(clk), .gs_rst_n(rst_n), .enable(1'b1),
|
||||
.arm(wr_arm), .canary(wr_canary), .fb_base(wr_base), .ctrl_commit(fb_commit),
|
||||
.px_emit(flush_emit_w && (flush_psm_w == 6'h00)), .px_addr(flush_addr_w), .px_pix32(flush_color32_w), .flush(fb_flush),
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n),
|
||||
.awaddr(fbw_awaddr), .awlen(), .awsize(), .awburst(), .awid(),
|
||||
.awvalid(fbw_awvalid), .awready(1'b1),
|
||||
.wdata(fbw_wdata), .wstrb(fbw_wstrb), .wlast(), .wvalid(fbw_wvalid), .wready(1'b1),
|
||||
.bvalid(1'b1), .bready(), .bresp(2'b00),
|
||||
.beats_written(fbw_beats), .bursts_issued(), .bresp_err_count(fbw_bresp_err),
|
||||
.fifo_overflow_count(fbw_ovf), .idle(fbw_idle), .frame_drained(fbw_drained)
|
||||
);
|
||||
logic [7:0] fb [0:FB_BYTES-1]; logic [31:0] fb_awlat;
|
||||
always_ff @(posedge emif_clk) begin
|
||||
if (fbw_awvalid) fb_awlat <= fbw_awaddr;
|
||||
if (fbw_wvalid) for (int i=0;i<32;i++) if (fbw_wstrb[i]) begin
|
||||
int a; a = fb_awlat + i; if (a>=0 && a<FB_BYTES) fb[a] <= fbw_wdata[i*8 +: 8];
|
||||
end
|
||||
end
|
||||
// "ideal" render (every emitted PSMCT32 pixel) — for the bounded oracle
|
||||
logic [31:0] ideal [0:FB_WORDS-1]; logic ideal_set [0:FB_WORDS-1];
|
||||
always_ff @(posedge clk) if (rst_n && flush_emit_w && (flush_psm_w==6'h00)) begin
|
||||
ideal[flush_addr_w>>2] <= flush_color32_w; ideal_set[flush_addr_w>>2] <= 1'b1;
|
||||
end
|
||||
|
||||
// ================= SCANOUT reads the SAME FB back (Brick 2), gated on frame_drained =================
|
||||
logic [11:0] px, py; logic vsync, in_win;
|
||||
logic [7:0] so_r, so_g, so_b; logic so_underflow; logic [31:0] so_rd_errs; logic so_line_valid;
|
||||
logic [29:0] so_araddr; logic [1:0] so_arburst; logic [6:0] so_arid; logic [7:0] so_arlen;
|
||||
logic [2:0] so_arsize; logic so_arvalid, so_arready;
|
||||
logic [255:0] so_rdata; logic [1:0] so_rresp; logic so_rlast, so_rvalid, so_rready;
|
||||
gs_lpddr_scanout_lb #(.FB_BASE(30'd0), .STRIDE_BYTES(STRIDE), .ROW_BEATS(ROW_BEATS),
|
||||
.N_ROWS(H), .PSMCT32(1'b1)) u_scan (
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n), .enable(fbw_drained), // HARD gate — frame_drained, no timeout
|
||||
.video_clk(video_clk), .frame_start(vsync),
|
||||
.pixel_x(px), .pixel_y(py), .in_window(in_win),
|
||||
.r(so_r), .g(so_g), .b(so_b),
|
||||
.line_valid(so_line_valid), .underflow(so_underflow), .rd_errs(so_rd_errs),
|
||||
.araddr(so_araddr), .arburst(so_arburst), .arid(so_arid), .arlen(so_arlen), .arsize(so_arsize),
|
||||
.arvalid(so_arvalid), .arready(so_arready), .rdata(so_rdata), .rresp(so_rresp),
|
||||
.rlast(so_rlast), .rvalid(so_rvalid), .rready(so_rready)
|
||||
);
|
||||
// FB read model — single-beat, VARIABLE AR/R latency; counts read beats per EMIF-domain frame
|
||||
logic [7:0] rlfsr=8'h3C; always_ff @(posedge emif_clk) rlfsr<={rlfsr[6:0], rlfsr[7]^rlfsr[5]^rlfsr[4]^rlfsr[3]};
|
||||
typedef enum logic [1:0] { R_IDLE, R_WAIT, R_DATA } rst_t; rst_t rst_state;
|
||||
logic [3:0] rdly; logic [29:0] rd_addr_l; int read_beats;
|
||||
logic [2:0] vs_e; wire vs_edge_e = vs_e[1] && !vs_e[2]; int fb_reads, fb_reads_last;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin rst_state<=R_IDLE; so_arready<=0; so_rvalid<=0; so_rlast<=0; so_rresp<=0; so_rdata<=0; rdly<=0;
|
||||
read_beats<=0; vs_e<=0; fb_reads<=0; fb_reads_last<=0; end
|
||||
else begin
|
||||
so_arready<=0; so_rvalid<=0; so_rlast<=0;
|
||||
vs_e <= {vs_e[1:0], vsync};
|
||||
if (vs_edge_e) begin fb_reads_last <= fb_reads; fb_reads <= 0; end
|
||||
case (rst_state)
|
||||
R_IDLE: if (so_arvalid) begin so_arready<=1; rd_addr_l<=so_araddr; rdly<=rlfsr[2:0]; rst_state<=R_WAIT; end
|
||||
R_WAIT: if (rdly==0) rst_state<=R_DATA; else rdly<=rdly-1'b1;
|
||||
R_DATA: if (so_rready) begin
|
||||
for (int w=0;w<8;w++) begin int a; a=rd_addr_l+w*4;
|
||||
so_rdata[w*32 +: 32] <= (a+3<FB_BYTES) ? {fb[a+3],fb[a+2],fb[a+1],fb[a]} : 32'd0; end
|
||||
so_rresp<=2'b00; so_rvalid<=1; so_rlast<=1; read_beats<=read_beats+1;
|
||||
if (!vs_edge_e) fb_reads<=fb_reads+1;
|
||||
rst_state<=R_IDLE;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// REAL video raster (independent video_clk, active + blanking incl. vertical back porch)
|
||||
logic vid_run=0; logic [11:0] rawx, rawy;
|
||||
always_ff @(posedge video_clk) begin
|
||||
if (!vid_run) begin rawx<=0; rawy<=0; end
|
||||
else if (rawx==H_TOT-1) begin rawx<=0; rawy<=(rawy==V_TOT-1)?12'd0:rawy+1'b1; end
|
||||
else rawx<=rawx+1'b1;
|
||||
end
|
||||
wire active = (rawx>=H_BP)&&(rawx<H_BP+H_ACT)&&(rawy>=V_BP)&&(rawy<V_BP+V_ACT);
|
||||
assign px = active ? (rawx - H_BP) : 12'd0;
|
||||
assign py = (rawy>=V_BP && rawy<V_BP+V_ACT) ? (rawy - V_BP) : 12'd0;
|
||||
assign in_win = active;
|
||||
assign vsync = vid_run && (rawx==0) && (rawy==0);
|
||||
|
||||
// pixel compare — 1-video-cycle registered latency; compare vs the FB (incl. black background)
|
||||
logic [11:0] px_q, py_q; logic inwin_q, run_q;
|
||||
always_ff @(posedge video_clk) begin px_q<=px; py_q<=py; inwin_q<=in_win; run_q<=vid_run; end
|
||||
int checked; initial checked=0; logic scoring=0;
|
||||
always_ff @(posedge video_clk) if (scoring && run_q) begin
|
||||
logic [7:0] er,eg,eb; logic [31:0] w;
|
||||
if (inwin_q) begin int a; a=py_q*STRIDE+px_q*4; w={fb[a+3],fb[a+2],fb[a+1],fb[a]}; er=w[7:0]; eg=w[15:8]; eb=w[23:16]; end
|
||||
else begin er=0; eg=0; eb=0; end
|
||||
checked++;
|
||||
if (so_r!==er || so_g!==eg || so_b!==eb) begin
|
||||
if (errors<12) $error("[scan] px(%0d,%0d) in=%b got(%02x,%02x,%02x) exp(%02x,%02x,%02x)",
|
||||
px_q,py_q,inwin_q, so_r,so_g,so_b, er,eg,eb);
|
||||
errors++;
|
||||
end
|
||||
end
|
||||
|
||||
// ================= "HPS bridge" frame_drained diagnostic sync (Codex step: expose synchronized frame_drained) =================
|
||||
logic [2:0] bd_sync; wire bridge_drained = bd_sync[2];
|
||||
always_ff @(posedge bridge_clk or negedge rst_n) begin
|
||||
if (!rst_n) bd_sync <= 3'd0; else bd_sync <= {bd_sync[1:0], fbw_drained};
|
||||
end
|
||||
|
||||
// pre-GO raster-write monitor (raster emits before GO would be a boot-render leak)
|
||||
int raster_emits_preGO; logic count_preGO=0;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) raster_emits_preGO<=0;
|
||||
else if (count_preGO && flush_emit_w && (flush_psm_w==6'h00)) raster_emits_preGO<=raster_emits_preGO+1;
|
||||
end
|
||||
|
||||
// ============================================ SEQUENCE ============================================
|
||||
// full-frame bounded oracle helpers
|
||||
logic [31:0] idx_words [0:(512*512/4)-1]; logic [31:0] pal [0:255]; logic [31:0] refmap [0:FB_WORDS-1];
|
||||
function automatic logic [7:0] sh3_idx(input integer u, input integer v);
|
||||
integer lin; logic [31:0] w; lin=v*TW+u; w=idx_words[lin/4]; sh3_idx=w[(8*(lin%4)) +: 8]; endfunction
|
||||
function automatic logic [23:0] exp_cell(input integer u, input integer v); exp_cell=pal[sh3_idx(u,v)][23:0]; endfunction
|
||||
|
||||
initial begin
|
||||
errors=0; feeder_go_tb=1'b0; fill_start=1'b0; wr_arm=0; wr_canary=0; wr_base=32'h0;
|
||||
for (int i=0;i<FB_WORDS;i++) begin ideal_set[i]=1'b0; ideal[i]=32'd0; end
|
||||
for (int i=0;i<FB_BYTES;i++) fb[i]=8'h00; // Codex step 2 — model the HPS FB preclear (starts black)
|
||||
lpddr_mem[0]='x;
|
||||
$readmemh(`FEEDER_SH3_REAL_FILE, dut.g_feeder.feeder_stg);
|
||||
$readmemh("../../data/top_psmct32_raster_demo/sh3_real_tex_lpddr.mem", lpddr_mem);
|
||||
$readmemh("../../data/top_psmct32_raster_demo/sh3_real_idx.mem", idx_words);
|
||||
$readmemh("../../data/top_psmct32_raster_demo/sh3_real_pal.mem", pal);
|
||||
$readmemh("../../data/top_psmct32_raster_demo/sh3_full_refmap.mem", refmap);
|
||||
if (lpddr_mem[0] === 32'bx) begin
|
||||
$display("[tb_top_psmct32_sh3_lpddr_fb_seq] SKIP — sh3_*.mem absent (run gs_make_sh3_real_draw_fixture.py --full-frame)");
|
||||
$finish;
|
||||
end
|
||||
|
||||
rst_n=1'b0; core_go=1'b0;
|
||||
repeat(6) @(posedge clk); rst_n=1'b1; repeat(8) @(posedge clk);
|
||||
|
||||
// ---- Codex step 4: fill + verify the texture cache ----
|
||||
@(posedge clk) fill_start<=1'b1;
|
||||
begin int gd=0; while(!fill_done && gd<200000) begin @(posedge clk); gd++; end end
|
||||
if (!fill_done) begin $error("[seq] cache fill_done never asserted"); errors++; end
|
||||
if (fill_crc_w!==32'hfbdeaa32)begin $error("[seq] cache fill_crc=%08x exp fbdeaa32", fill_crc_w); errors++; end
|
||||
if (tex_rd_errs!==0) begin $error("[seq] tex fill read errors=%0d", tex_rd_errs); errors++; end
|
||||
|
||||
// ---- Codex step 1: boot setup + CLUT upload; feeder must reach READY with NO boot render (AUTOSTART=0) ----
|
||||
count_preGO<=1'b1;
|
||||
@(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);
|
||||
wait (feeder_ready_tb==1'b1); // setup -> C_READY (no render)
|
||||
repeat(200) @(posedge clk); // dwell: a boot render (if any) would have emitted by now
|
||||
|
||||
// ---- Codex acceptance: BEFORE GO — zero raster writes, zero EOF, frame_drained=0 ----
|
||||
if (raster_emits_preGO!==0) begin $error("[seq] %0d raster emits BEFORE GO (boot-render leak — AUTOSTART!=0?)", raster_emits_preGO); errors++; end
|
||||
if (fbw_beats!==0) begin $error("[seq] writer wrote %0d beats BEFORE GO (should be 0)", fbw_beats); errors++; end
|
||||
if (eof_count!==0) begin $error("[seq] %0d EOF markers BEFORE GO (render-epoch leaked on setup ready-rise)", eof_count); errors++; end
|
||||
if (fbw_drained!==1'b0) begin $error("[seq] frame_drained asserted BEFORE GO"); errors++; end
|
||||
if (fbw_bresp_err!==0) begin $error("[seq] preclear/idle BRESP errors=%0d", fbw_bresp_err); errors++; end
|
||||
$display("[seq] pre-GO clean: raster_emits=0 writer_beats=0 eof=0 frame_drained=0 (ready reached without boot render)");
|
||||
|
||||
// ---- Codex step 5: host configures base 0, canary off, arms the writer (latched via ctrl_commit) ----
|
||||
wr_base<=32'h0; wr_canary<=1'b0; wr_arm<=1'b1;
|
||||
repeat(40) @(posedge clk); // let a ctrl_commit edge snapshot arm/base/canary
|
||||
count_preGO<=1'b0;
|
||||
|
||||
// ---- Codex step 6: GO — ready must DROP then RISE after rendering ----
|
||||
@(negedge clk); feeder_go_tb=1'b1; @(negedge clk); feeder_go_tb=1'b0;
|
||||
begin int d=0; while(feeder_ready_tb && d<2000) begin @(posedge clk); d++; end end
|
||||
if (feeder_ready_tb!==1'b0) begin $error("[seq] feeder_ready did not drop after GO"); errors++; end
|
||||
wait (feeder_ready_tb==1'b1); // whole scene rendered + drained -> ready rises (fires the EOF flush)
|
||||
|
||||
// ---- Codex step 7: exactly ONE EOF -> exactly ONE frame_drained ----
|
||||
begin int d=0; while(!fbw_drained && d<400000) begin @(posedge clk); d++; end end
|
||||
if (!fbw_drained) begin $error("[seq] frame_drained never asserted after GO"); errors++; end
|
||||
repeat(200) @(posedge clk);
|
||||
if (eof_count!==1) begin $error("[seq] EOF count=%0d exp 1 (render epoch must emit exactly one)", eof_count); errors++; end
|
||||
if (fbw_ovf!==0) begin $error("[seq] writer FIFO overflow=%0d", fbw_ovf); errors++; end
|
||||
if (fbw_bresp_err!==0) begin $error("[seq] writer BRESP errors=%0d", fbw_bresp_err); errors++; end
|
||||
$display("[seq] post-GO: eof_count=%0d frame_drained=1 writer_beats=%0d ovf=0", eof_count, fbw_beats);
|
||||
|
||||
// ---- Codex step: bridge receives a synchronized frame_drained ----
|
||||
begin int d=0; while(!bridge_drained && d<2000) begin @(posedge bridge_clk); d++; end end
|
||||
if (!bridge_drained) begin $error("[seq] synchronized frame_drained never reached the bridge domain"); errors++; end
|
||||
|
||||
// ---- Codex step 8: scanout auto-enables from the ack; score exactly one frame ----
|
||||
vid_run=1;
|
||||
begin int d=0; while(!vsync && d<200000) begin @(posedge video_clk); d++; end end
|
||||
@(posedge video_clk); while(!vsync) @(posedge video_clk); // prime one frame -> 2nd vsync
|
||||
begin scoring=1;
|
||||
@(posedge video_clk); while(!vsync) @(posedge video_clk); // score exactly one frame
|
||||
scoring=0;
|
||||
repeat(60) @(posedge emif_clk);
|
||||
if (fb_reads_last!==BEATS_PER_FRAME) begin
|
||||
$error("[seq] scanout read beats/frame=%0d exp %0d (%0dx%0d)", fb_reads_last, BEATS_PER_FRAME, H, ROW_BEATS); errors++;
|
||||
end
|
||||
end
|
||||
if (so_underflow!==0) begin $error("[seq] scanout underflow asserted"); errors++; end
|
||||
if (so_rd_errs !==0) begin $error("[seq] scanout rd_errs=%0d", so_rd_errs); errors++; end
|
||||
if (checked < H_ACT*V_ACT) begin $error("[seq] only %0d pixels checked (exp >= %0d)", checked, H_ACT*V_ACT); errors++; end
|
||||
|
||||
// ---- bounded oracle: the rendered frame is a correct SH3 draw (<=1 texel) ----
|
||||
begin
|
||||
int m_total,m_ok,int_total,int_ok,cb; m_total=0;m_ok=0;int_total=0;int_ok=0;cb=0;
|
||||
for (int y=0;y<H;y++) for (int x=0;x<W;x++) begin
|
||||
logic [31:0] rm; logic [23:0] fbc; int tu,tv,D; rm=refmap[y*W+x];
|
||||
if (rm[31]) begin
|
||||
tu=(rm>>9)&9'h1FF; tv=rm&9'h1FF; fbc=ideal[y*W+x][23:0]; m_total++; D=9;
|
||||
for (int rad=0;rad<=1;rad++) for (int du=-rad;du<=rad;du++) for (int dv=-rad;dv<=rad;dv++) begin
|
||||
int ch; ch=(du<0?-du:du); if((dv<0?-dv:dv)>ch) ch=(dv<0?-dv:dv);
|
||||
if (ch==rad && D==9 && (tu+du)>=0 && (tu+du)<TW && (tv+dv)>=0 && (tv+dv)<TH && fbc===exp_cell(tu+du,tv+dv)) D=rad;
|
||||
end
|
||||
if (D<=1) m_ok++;
|
||||
if (rm[30]) begin int_total++; if (D<=1) int_ok++; end
|
||||
begin bit f; f=1'b0; for (int i=0;i<256;i++) if (pal[i][23:0]===fbc) f=1'b1; if (!f) cb++; end
|
||||
end
|
||||
end
|
||||
$display("[seq][oracle] <=1texel ALL=%0d/%0d (%.1f%%) INT=%.1f%% clut_bad=%0d",
|
||||
m_ok,m_total,(m_total>0)?100.0*m_ok/m_total:0.0,(int_total>0)?100.0*int_ok/int_total:0.0,cb);
|
||||
if (cb!==0) begin $error("[seq][oracle] %0d covered px not a CLUT entry", cb); errors++; end
|
||||
if (int_total>0 && (100.0*int_ok/int_total)<95.0) begin $error("[seq][oracle] interior <=1texel %.1f%% < 95%%", 100.0*int_ok/int_total); errors++; end
|
||||
end
|
||||
|
||||
$display("[tb_top_psmct32_sh3_lpddr_fb_seq] checked=%0d px, read_beats/frame=%0d (exp %0d), eof=%0d underflow=%0b rd_errs=%0d ovf=%0d errors=%0d",
|
||||
checked, fb_reads_last, BEATS_PER_FRAME, eof_count, so_underflow, so_rd_errs, fbw_ovf, errors);
|
||||
if (errors==0) $display("[tb_top_psmct32_sh3_lpddr_fb_seq] PASS");
|
||||
else $display("[tb_top_psmct32_sh3_lpddr_fb_seq] FAIL");
|
||||
$finish;
|
||||
end
|
||||
initial begin #40000000; $error("[tb_top_psmct32_sh3_lpddr_fb_seq] TIMEOUT"); $finish; end
|
||||
endmodule : tb_top_psmct32_sh3_lpddr_fb_seq
|
||||
@@ -0,0 +1,313 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_sh3_multidraw (Ch354 Brick 1 — multi-draw accumulation into the LPDDR framebuffer)
|
||||
//
|
||||
// Composites N authentic SH3 draws (sharing one texture/CLUT) into ONE LPDDR PSMCT32 framebuffer through the SAME
|
||||
// Ch353 LPDDR-FB path (FEEDER_AUTOSTART=0 + render-epoch EOF + frame_drained hard gate + line-buffer scanout), and
|
||||
// proves Codex's Brick-1 acceptance:
|
||||
// * records_emitted == NTRIS (all draws' triangles emitted)
|
||||
// * max staging address stays < STG_WORDS (2048); FIFO/writer overflow == 0
|
||||
// * ALL batches complete before ONE ordered frame_drained
|
||||
// * scanout/preclear geometry = 256xFBH (338); exactly FBH*32 read beats/frame
|
||||
// * bounded <=1-texel oracle vs the INDEPENDENT combined refmap, with the OVERLAP region scored separately
|
||||
// (that is where multi-draw accumulation is actually proven).
|
||||
// LOCAL/gitignored fixtures (dump-derived); skip-guard if absent.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_sh3_multidraw;
|
||||
`include "sh3_multi_params.vh" // FBPXW=256, FBH=338, STG_WORDS=2048, NTRIS=204, NDRAWS, TW/TH, ...
|
||||
localparam int W = FBPXW, H = FBH;
|
||||
localparam int STRIDE = W*4; // 1024 B
|
||||
localparam int ROW_BEATS = STRIDE/32; // 32
|
||||
localparam int FB_BYTES = STRIDE*H;
|
||||
localparam int FB_WORDS = W*H;
|
||||
localparam int BEATS_PER_FRAME = ROW_BEATS*H; // 338*32 = 10816
|
||||
localparam int H_ACT=W, V_ACT=H, H_BP=32, H_FP=8, V_BP=16, V_FP=8;
|
||||
localparam int H_TOT=H_BP+H_ACT+H_FP, V_TOT=V_BP+V_ACT+V_FP;
|
||||
|
||||
logic clk=0; always #5 clk=~clk; // GS/design clock
|
||||
logic emif_clk=0; always #2 emif_clk=~emif_clk;
|
||||
logic video_clk=0; always #7 video_clk=~video_clk;
|
||||
logic rst_n; logic fb_commit=0; always #13 fb_commit=~fb_commit;
|
||||
int errors; initial errors=0;
|
||||
|
||||
// ===== bram-top: multi-draw SH3 via the feeder, FB_LPDDR_ONLY, AUTOSTART off, STG_WORDS=2048 =====
|
||||
logic core_go; logic [7:0] r,g,b; logic hsync,vsync_o,de;
|
||||
logic core_halt,dma_done_seen,frame_seen,raster_overflow,frame_toggle,dma_done_toggle;
|
||||
logic feeder_go_tb, feeder_ready_tb; logic [15:0] feeder_records_w; logic [31:0] feeder_waits_w;
|
||||
logic gs_tex_rd_en_o; logic [31:0] gs_tex_rd_addr_o; logic [31:0] tex_cache_data; logic tex_cache_ready;
|
||||
logic [31:0] tex_cache_hits, tex_bram_hits;
|
||||
logic flush_emit_w; logic [31:0] flush_addr_w; logic [31:0] flush_color32_w; logic [5:0] flush_psm_w;
|
||||
|
||||
top_psmct32_raster_demo_bram #(
|
||||
.H_ACTIVE(FBPXW), .V_ACTIVE(FBH),
|
||||
.VRAM_BYTES(VRAM_BYTES_P), .VRAM_ENABLE_READ2(1'b0), .PSMCT32_SWIZZLE(1'b0),
|
||||
.COMBINED_TAZ(1'b0), .TILE_LOCAL(1'b0), .TILE_COLS(1), .TILE_ROWS(1),
|
||||
.TILE_MULTIPRIM(1'b0), .TILE_PRIM_COUNT(1), .TILE_FIFO_DEPTH(8),
|
||||
.BIN_BUFFER_ENABLE(1'b0), .HEARTBEAT_SPLICE_ENABLE(1'b0),
|
||||
.FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(STG_WORDS), .FEEDER_AUTOSTART(1'b0), // 2048 staging, no boot render
|
||||
.PERSPECTIVE_CORRECT(1'b1), .PERSP_RECIP_IDX_BITS(11),
|
||||
.GRAD_SEQ_DIVIDER(1'b1), .GRAD_DIV_CYCLES(4),
|
||||
.GS_LPDDR_TEX(1'b1), .TEX_VRAM_BASE(TEX_VRAM_BASE), .TEX_CACHE_BYTES(TEX_BYTES),
|
||||
.CLUT_CSM1_ENABLE(1'b1), .FB_LPDDR_ONLY(1'b1)
|
||||
) dut (
|
||||
.clk(clk), .rst_n(rst_n), .core_go(core_go),
|
||||
.r(r), .g(g), .b(b), .hsync(hsync), .vsync(vsync_o), .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),
|
||||
.feeder_stg_we_i(1'b0), .feeder_stg_waddr_i(12'd0), .feeder_stg_wdata_i(64'd0),
|
||||
.feeder_go_i(feeder_go_tb), .feeder_ready_o(feeder_ready_tb),
|
||||
.feeder_records_o(feeder_records_w), .feeder_waits_o(feeder_waits_w),
|
||||
.flush_emit_o(flush_emit_w), .flush_addr_o(flush_addr_w),
|
||||
.flush_pix16_o(), .flush_color32_o(flush_color32_w), .flush_psm_o(flush_psm_w),
|
||||
.gs_tex_rd_en_o(gs_tex_rd_en_o), .gs_tex_rd_addr_o(gs_tex_rd_addr_o),
|
||||
.tex_cache_data_i(tex_cache_data), .tex_cache_ready_i(tex_cache_ready),
|
||||
.tex_cache_hits_o(tex_cache_hits), .tex_bram_hits_o(tex_bram_hits)
|
||||
);
|
||||
|
||||
// texture cache + behavioral texture LPDDR
|
||||
logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid; logic [7:0] arlen; logic [2:0] arsize;
|
||||
logic arvalid, arready; logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready;
|
||||
logic fill_start, fill_done; logic [31:0] fill_beats, fill_bytes, tex_rd_errs, fill_crc_w;
|
||||
gs_texture_cache #(.LPDDR_TEX_BASE(LPDDR_TEX_BASE), .TEX_VRAM_BASE(TEX_VRAM_BASE),
|
||||
.TEX_BYTES(TEX_BYTES), .N_BEATS(N_BEATS)) u_cache (
|
||||
.axi_clk(clk), .axi_rst_n(rst_n), .fill_start(fill_start), .fill_done(fill_done),
|
||||
.fill_beats(fill_beats), .fill_bytes(fill_bytes), .rd_errs(tex_rd_errs), .fill_crc(fill_crc_w),
|
||||
.araddr(araddr), .arburst(arburst), .arid(arid), .arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready),
|
||||
.rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready),
|
||||
.sample_clk(clk), .tex_rd_en(gs_tex_rd_en_o), .tex_rd_addr(gs_tex_rd_addr_o),
|
||||
.tex_rd_data(tex_cache_data), .tex_ready(tex_cache_ready)
|
||||
);
|
||||
logic [31:0] lpddr_mem [0:(TEX_BYTES/4)-1];
|
||||
typedef enum logic [1:0] { S_IDLE, S_WAIT, S_DATA } sstate_t; sstate_t sst; logic [3:0] tdly; int tbeat;
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin sst<=S_IDLE; arready<=0; rvalid<=0; rlast<=0; rresp<=0; rdata<=0; tdly<=0; end
|
||||
else begin
|
||||
arready<=0; rvalid<=0; rlast<=0;
|
||||
case (sst)
|
||||
S_IDLE: if (arvalid) begin arready<=1; tbeat<=(araddr-LPDDR_TEX_BASE)>>5; tdly<=4'd2; sst<=S_WAIT; end
|
||||
S_WAIT: if (tdly==0) sst<=S_DATA; else tdly<=tdly-1'b1;
|
||||
S_DATA: if (rready) begin for (int w=0;w<8;w++) rdata[w*32 +: 32]<=lpddr_mem[tbeat*8+w];
|
||||
rresp<=2'b00; rvalid<=1; rlast<=1; sst<=S_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// ===== render epoch (same as the board) + PSMCT32 writer + precleared LPDDR FB =====
|
||||
logic fb_flush=0, render_inflight=0, feeder_ready_q=0; int eof_count;
|
||||
wire feeder_ready_rise = feeder_ready_tb && !feeder_ready_q;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin render_inflight<=0; feeder_ready_q<=0; fb_flush<=0; eof_count<=0; end
|
||||
else begin
|
||||
feeder_ready_q<=feeder_ready_tb; fb_flush<=1'b0;
|
||||
if (feeder_go_tb && feeder_ready_tb) render_inflight<=1'b1;
|
||||
if (render_inflight && feeder_ready_rise) begin fb_flush<=1'b1; render_inflight<=1'b0; eof_count<=eof_count+1; end
|
||||
end
|
||||
end
|
||||
logic wr_arm=0; logic [255:0] fbw_wdata; logic [31:0] fbw_wstrb, fbw_awaddr; logic fbw_awvalid, fbw_wvalid;
|
||||
logic [31:0] fbw_beats, fbw_ovf, fbw_bresp_err; logic fbw_idle, fbw_drained;
|
||||
gs_lpddr_axi_master #(.FIFO_DEPTH(64), .PIX_BYTES(4)) u_wr (
|
||||
.gs_clk(clk), .gs_rst_n(rst_n), .enable(1'b1),
|
||||
.arm(wr_arm), .canary(1'b0), .fb_base(32'h0), .ctrl_commit(fb_commit),
|
||||
.px_emit(flush_emit_w && (flush_psm_w==6'h00)), .px_addr(flush_addr_w), .px_pix32(flush_color32_w), .flush(fb_flush),
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n),
|
||||
.awaddr(fbw_awaddr), .awlen(), .awsize(), .awburst(), .awid(), .awvalid(fbw_awvalid), .awready(1'b1),
|
||||
.wdata(fbw_wdata), .wstrb(fbw_wstrb), .wlast(), .wvalid(fbw_wvalid), .wready(1'b1),
|
||||
.bvalid(1'b1), .bready(), .bresp(2'b00),
|
||||
.beats_written(fbw_beats), .bursts_issued(), .bresp_err_count(fbw_bresp_err),
|
||||
.fifo_overflow_count(fbw_ovf), .idle(fbw_idle), .frame_drained(fbw_drained)
|
||||
);
|
||||
logic [7:0] fb [0:FB_BYTES-1]; logic [31:0] fb_awlat;
|
||||
always_ff @(posedge emif_clk) begin
|
||||
if (fbw_awvalid) fb_awlat<=fbw_awaddr;
|
||||
if (fbw_wvalid) for (int i=0;i<32;i++) if (fbw_wstrb[i]) begin
|
||||
int aa; aa=fb_awlat+i; if (aa>=0 && aa<FB_BYTES) fb[aa]<=fbw_wdata[i*8 +: 8];
|
||||
end
|
||||
end
|
||||
logic [31:0] ideal [0:FB_WORDS-1]; logic ideal_set [0:FB_WORDS-1];
|
||||
always_ff @(posedge clk) if (rst_n && flush_emit_w && (flush_psm_w==6'h00)) begin
|
||||
ideal[flush_addr_w>>2]<=flush_color32_w; ideal_set[flush_addr_w>>2]<=1'b1;
|
||||
end
|
||||
|
||||
// ===== scanout reads the FB back (256x338) =====
|
||||
logic [11:0] px, py; logic vsync, in_win; logic [7:0] so_r, so_g, so_b;
|
||||
logic so_underflow; logic [31:0] so_rd_errs; logic so_line_valid;
|
||||
logic [29:0] so_araddr; logic [1:0] so_arburst; logic [6:0] so_arid; logic [7:0] so_arlen;
|
||||
logic [2:0] so_arsize; logic so_arvalid, so_arready;
|
||||
logic [255:0] so_rdata; logic [1:0] so_rresp; logic so_rlast, so_rvalid, so_rready;
|
||||
gs_lpddr_scanout_lb #(.FB_BASE(30'd0), .STRIDE_BYTES(STRIDE), .ROW_BEATS(ROW_BEATS),
|
||||
.N_ROWS(H), .PSMCT32(1'b1)) u_scan (
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n), .enable(fbw_drained),
|
||||
.video_clk(video_clk), .frame_start(vsync), .pixel_x(px), .pixel_y(py), .in_window(in_win),
|
||||
.r(so_r), .g(so_g), .b(so_b), .line_valid(so_line_valid), .underflow(so_underflow), .rd_errs(so_rd_errs),
|
||||
.araddr(so_araddr), .arburst(so_arburst), .arid(so_arid), .arlen(so_arlen), .arsize(so_arsize),
|
||||
.arvalid(so_arvalid), .arready(so_arready), .rdata(so_rdata), .rresp(so_rresp),
|
||||
.rlast(so_rlast), .rvalid(so_rvalid), .rready(so_rready)
|
||||
);
|
||||
logic [7:0] rlfsr=8'h3C; always_ff @(posedge emif_clk) rlfsr<={rlfsr[6:0], rlfsr[7]^rlfsr[5]^rlfsr[4]^rlfsr[3]};
|
||||
typedef enum logic [1:0] { R_IDLE, R_WAIT, R_DATA } rst_t; rst_t rst_state;
|
||||
logic [3:0] rdly; logic [29:0] rd_addr_l; int read_beats;
|
||||
logic [2:0] vs_e; wire vs_edge_e = vs_e[1] && !vs_e[2]; int fb_reads, fb_reads_last;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin rst_state<=R_IDLE; so_arready<=0; so_rvalid<=0; so_rlast<=0; so_rresp<=0; so_rdata<=0; rdly<=0;
|
||||
read_beats<=0; vs_e<=0; fb_reads<=0; fb_reads_last<=0; end
|
||||
else begin
|
||||
so_arready<=0; so_rvalid<=0; so_rlast<=0; vs_e<={vs_e[1:0], vsync};
|
||||
if (vs_edge_e) begin fb_reads_last<=fb_reads; fb_reads<=0; end
|
||||
case (rst_state)
|
||||
R_IDLE: if (so_arvalid) begin so_arready<=1; rd_addr_l<=so_araddr; rdly<=rlfsr[2:0]; rst_state<=R_WAIT; end
|
||||
R_WAIT: if (rdly==0) rst_state<=R_DATA; else rdly<=rdly-1'b1;
|
||||
R_DATA: if (so_rready) begin
|
||||
for (int w=0;w<8;w++) begin int aa; aa=rd_addr_l+w*4;
|
||||
so_rdata[w*32 +: 32] <= (aa+3<FB_BYTES) ? {fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]} : 32'd0; end
|
||||
so_rresp<=2'b00; so_rvalid<=1; so_rlast<=1; read_beats<=read_beats+1;
|
||||
if (!vs_edge_e) fb_reads<=fb_reads+1; rst_state<=R_IDLE;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
logic vid_run=0; logic [11:0] rawx, rawy;
|
||||
always_ff @(posedge video_clk) begin
|
||||
if (!vid_run) begin rawx<=0; rawy<=0; end
|
||||
else if (rawx==H_TOT-1) begin rawx<=0; rawy<=(rawy==V_TOT-1)?12'd0:rawy+1'b1; end
|
||||
else rawx<=rawx+1'b1;
|
||||
end
|
||||
wire active = (rawx>=H_BP)&&(rawx<H_BP+H_ACT)&&(rawy>=V_BP)&&(rawy<V_BP+V_ACT);
|
||||
assign px=active?(rawx-H_BP):12'd0; assign py=(rawy>=V_BP&&rawy<V_BP+V_ACT)?(rawy-V_BP):12'd0;
|
||||
assign in_win=active; assign vsync=vid_run&&(rawx==0)&&(rawy==0);
|
||||
logic [11:0] px_q, py_q; logic inwin_q, run_q;
|
||||
always_ff @(posedge video_clk) begin px_q<=px; py_q<=py; inwin_q<=in_win; run_q<=vid_run; end
|
||||
int checked; initial checked=0; logic scoring=0;
|
||||
always_ff @(posedge video_clk) if (scoring && run_q) begin
|
||||
logic [7:0] er,eg,eb; logic [31:0] w;
|
||||
if (inwin_q) begin int aa; aa=py_q*STRIDE+px_q*4; w={fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]}; er=w[7:0]; eg=w[15:8]; eb=w[23:16]; end
|
||||
else begin er=0; eg=0; eb=0; end
|
||||
checked++;
|
||||
if (so_r!==er||so_g!==eg||so_b!==eb) begin
|
||||
if (errors<12) $error("[scan] px(%0d,%0d) got(%02x,%02x,%02x) exp(%02x,%02x,%02x)", px_q,py_q, so_r,so_g,so_b, er,eg,eb);
|
||||
errors++;
|
||||
end
|
||||
end
|
||||
|
||||
// ===== combined INDEPENDENT oracle + overlap scoring =====
|
||||
logic [31:0] idx_words [0:(512*512/4)-1]; logic [31:0] pal [0:255]; logic [31:0] refmap [0:FB_WORDS-1];
|
||||
function automatic logic [7:0] sh3_idx(input integer u, input integer v);
|
||||
integer lin; logic [31:0] w; lin=v*TW+u; w=idx_words[lin/4]; sh3_idx=w[(8*(lin%4)) +: 8]; endfunction
|
||||
function automatic logic [23:0] exp_cell(input integer u, input integer v); exp_cell=pal[sh3_idx(u,v)][23:0]; endfunction
|
||||
|
||||
// diagnostic: +TAG=<tag> selects the fixture set (multi | d548 | d761 | d974 ...); +FBDUMP=<file> dumps the RTL FB.
|
||||
string ftag; string fdump; int is_multi;
|
||||
initial begin
|
||||
errors=0; feeder_go_tb=1'b0; fill_start=1'b0; wr_arm=0;
|
||||
if (!$value$plusargs("TAG=%s", ftag)) ftag="multi";
|
||||
is_multi = (ftag=="multi");
|
||||
for (int i=0;i<FB_WORDS;i++) begin ideal_set[i]=1'b0; ideal[i]=32'd0; end
|
||||
for (int i=0;i<FB_BYTES;i++) fb[i]=8'h00; // preclear
|
||||
lpddr_mem[0]='x;
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/feeder_sh3_%s.mem", ftag), dut.g_feeder.feeder_stg);
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_%s_tex_lpddr.mem", ftag), lpddr_mem);
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_%s_idx.mem", ftag), idx_words);
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_%s_pal.mem", ftag), pal);
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_%s_refmap.mem", ftag), refmap);
|
||||
$display("[md] TAG=%s (is_multi=%0d)", ftag, is_multi);
|
||||
if (lpddr_mem[0]===32'bx) begin
|
||||
$display("[tb_top_psmct32_sh3_multidraw] SKIP — sh3_%s_*.mem absent (run gs_make_sh3_multidraw_fixture.py --emit)", ftag);
|
||||
$finish;
|
||||
end
|
||||
rst_n=1'b0; core_go=1'b0; repeat(6) @(posedge clk); rst_n=1'b1; repeat(8) @(posedge clk);
|
||||
|
||||
@(posedge clk) fill_start<=1'b1;
|
||||
begin int gd=0; while(!fill_done && gd<200000) begin @(posedge clk); gd++; end end
|
||||
if (!fill_done) begin $error("[seq] cache fill_done never asserted"); errors++; end
|
||||
if (fill_crc_w!==32'hfbdeaa32)begin $error("[seq] cache fill_crc=%08x exp fbdeaa32", fill_crc_w); errors++; end
|
||||
|
||||
@(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);
|
||||
wait (feeder_ready_tb==1'b1); repeat(200) @(posedge clk); // C_READY, no boot render
|
||||
if (fbw_beats!==0) begin $error("[seq] writer wrote %0d beats BEFORE GO", fbw_beats); errors++; end
|
||||
if (eof_count!==0) begin $error("[seq] %0d EOF BEFORE GO", eof_count); errors++; end
|
||||
|
||||
wr_arm<=1'b1; repeat(40) @(posedge clk);
|
||||
@(negedge clk); feeder_go_tb=1'b1; @(negedge clk); feeder_go_tb=1'b0;
|
||||
begin int d=0; while(feeder_ready_tb && d<4000) begin @(posedge clk); d++; end end
|
||||
if (feeder_ready_tb!==1'b0) begin $error("[seq] ready did not drop after GO"); errors++; end
|
||||
wait (feeder_ready_tb==1'b1); // ALL batches drained -> ready rises
|
||||
|
||||
begin int d=0; while(!fbw_drained && d<800000) begin @(posedge clk); d++; end end
|
||||
if (!fbw_drained) begin $error("[seq] frame_drained never asserted"); errors++; end
|
||||
repeat(200) @(posedge clk);
|
||||
// ---- Codex acceptance ----
|
||||
if (eof_count!==1) begin $error("[md] eof_count=%0d exp 1 (one ordered drain after ALL batches)", eof_count); errors++; end
|
||||
if (is_multi && feeder_records_w!==NTRIS) begin $error("[md] records_emitted=%0d exp %0d (NTRIS)", feeder_records_w, NTRIS); errors++; end
|
||||
if (fbw_ovf!==0) begin $error("[md] writer FIFO overflow=%0d", fbw_ovf); errors++; end
|
||||
if (fbw_bresp_err!==0) begin $error("[md] writer BRESP errors=%0d", fbw_bresp_err); errors++; end
|
||||
if (raster_overflow!==1'b0) begin $error("[md] raster_overflow asserted"); errors++; end
|
||||
$display("[md] post-GO: eof=%0d records_emitted=%0d (exp %0d) writer_beats=%0d ovf=0", eof_count, feeder_records_w, NTRIS, fbw_beats);
|
||||
|
||||
vid_run=1;
|
||||
begin int d=0; while(!vsync && d<200000) begin @(posedge video_clk); d++; end end
|
||||
@(posedge video_clk); while(!vsync) @(posedge video_clk);
|
||||
begin scoring=1;
|
||||
@(posedge video_clk); while(!vsync) @(posedge video_clk);
|
||||
scoring=0; repeat(60) @(posedge emif_clk);
|
||||
if (fb_reads_last!==BEATS_PER_FRAME) begin
|
||||
$error("[md] scanout beats/frame=%0d exp %0d (%0dx%0d)", fb_reads_last, BEATS_PER_FRAME, H, ROW_BEATS); errors++; end
|
||||
end
|
||||
if (so_underflow!==0) begin $error("[md] scanout underflow"); errors++; end
|
||||
if (so_rd_errs !==0) begin $error("[md] scanout rd_errs=%0d", so_rd_errs); errors++; end
|
||||
if (checked < H_ACT*V_ACT) begin $error("[md] only %0d px checked (exp >= %0d)", checked, H_ACT*V_ACT); errors++; end
|
||||
|
||||
// ---- combined oracle: ALL covered + the OVERLAP subset (refmap bit28) scored SEPARATELY ----
|
||||
// Score four cohorts. The ACCEPTANCE gate is on INTERIOR (bit30 = away from triangle edges) — the SAME
|
||||
// seam-free standard Ch353 used (its ALL was only ~96%, gated INTERIOR>=95%). ALL and the raw OVERLAP
|
||||
// region are seam-heavy (draw boundaries = sub-pixel which-draw-wins ambiguity + the Ch352 perspective/8b
|
||||
// reciprocal limit) and reported informationally. OVERLAP-INTERIOR is the real multi-draw ACCUMULATION proof:
|
||||
// pixels covered by >1 draw AND away from edges must render correctly.
|
||||
begin
|
||||
int m_tot,m_ok,mi_tot,mi_ok,o_tot,o_ok,oi_tot,oi_ok,cb;
|
||||
m_tot=0;m_ok=0;mi_tot=0;mi_ok=0;o_tot=0;o_ok=0;oi_tot=0;oi_ok=0;cb=0;
|
||||
for (int y=0;y<H;y++) for (int x=0;x<W;x++) begin
|
||||
logic [31:0] rm; logic [23:0] fbc; int tu,tv,D; rm=refmap[y*W+x];
|
||||
if (rm[31]) begin
|
||||
tu=(rm>>9)&9'h1FF; tv=rm&9'h1FF; fbc=ideal[y*W+x][23:0]; m_tot++; D=9;
|
||||
for (int rad=0;rad<=1;rad++) for (int du=-rad;du<=rad;du++) for (int dv=-rad;dv<=rad;dv++) begin
|
||||
int ch; ch=(du<0?-du:du); if((dv<0?-dv:dv)>ch) ch=(dv<0?-dv:dv);
|
||||
if (ch==rad && D==9 && (tu+du)>=0 && (tu+du)<TW && (tv+dv)>=0 && (tv+dv)<TH && fbc===exp_cell(tu+du,tv+dv)) D=rad;
|
||||
end
|
||||
if (D<=1) m_ok++;
|
||||
if (rm[30]) begin mi_tot++; if (D<=1) mi_ok++; end // interior (seam-free)
|
||||
if (rm[28]) begin o_tot++; if (D<=1) o_ok++; end // overlap (accumulation, seam-incl)
|
||||
if (rm[28]&&rm[30]) begin oi_tot++; if (D<=1) oi_ok++; end // overlap AND interior
|
||||
begin bit f; f=1'b0; for (int i=0;i<256;i++) if (pal[i][23:0]===fbc) f=1'b1; if (!f) cb++; end
|
||||
end
|
||||
end
|
||||
$display("[md][oracle] <=1texel ALL=%0d/%0d (%.1f%%) INTERIOR=%0d/%0d (%.1f%%) OVERLAP=%0d/%0d (%.1f%%) OVERLAP-INT=%0d/%0d (%.1f%%) clut_bad=%0d",
|
||||
m_ok,m_tot,(m_tot>0)?100.0*m_ok/m_tot:0.0, mi_ok,mi_tot,(mi_tot>0)?100.0*mi_ok/mi_tot:0.0,
|
||||
o_ok,o_tot,(o_tot>0)?100.0*o_ok/o_tot:0.0, oi_ok,oi_tot,(oi_tot>0)?100.0*oi_ok/oi_tot:0.0, cb);
|
||||
// ACCEPTANCE (Codex): the Brick-1 claim is MULTI-DRAW ACCUMULATION, proven bit-exact externally
|
||||
// (combined RTL == RTL-paint-order composition of the isolated single renders, 0/86528 px diff, see
|
||||
// compose_check + docs/ch354_audit_log.md). clut_bad and the overlap-exercised count ARE gated (a valid
|
||||
// composited render). The <=1-texel fidelity above is a SEPARATE, draw-dependent metric (the existing
|
||||
// perspective-sampling fidelity limitation: 89761=97.5%, 89548=95.6%, 89974=93.2% individually) and is
|
||||
// REPORTED, NOT gated — it is not part of the accumulation acceptance.
|
||||
if (cb!==0) begin $error("[md][oracle] %0d covered px not a CLUT entry", cb); errors++; end
|
||||
if (is_multi && o_tot<1000) begin $error("[md][oracle] only %0d overlap px (<1000) — accumulation not exercised", o_tot); errors++; end
|
||||
end
|
||||
|
||||
// diagnostic: dump the RTL framebuffer (one 32-bit PSMCT32 word per FB pixel, row-major) for Python composition
|
||||
if ($value$plusargs("FBDUMP=%s", fdump)) begin
|
||||
int fh; fh=$fopen(fdump,"w");
|
||||
for (int y=0;y<H;y++) for (int x=0;x<W;x++) begin
|
||||
int aa; aa=y*STRIDE+x*4; $fwrite(fh, "%08x\n", {fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]});
|
||||
end
|
||||
$fclose(fh); $display("[md] dumped RTL FB (%0dx%0d) -> %s", W, H, fdump);
|
||||
end
|
||||
|
||||
$display("[tb_top_psmct32_sh3_multidraw] checked=%0d px, beats/frame=%0d (exp %0d), records=%0d eof=%0d underflow=%0b ovf=%0d errors=%0d",
|
||||
checked, fb_reads_last, BEATS_PER_FRAME, feeder_records_w, eof_count, so_underflow, fbw_ovf, errors);
|
||||
if (errors==0) $display("[tb_top_psmct32_sh3_multidraw] PASS");
|
||||
else $display("[tb_top_psmct32_sh3_multidraw] FAIL");
|
||||
$finish;
|
||||
end
|
||||
initial begin #80000000; $error("[tb_top_psmct32_sh3_multidraw] TIMEOUT"); $finish; end
|
||||
endmodule : tb_top_psmct32_sh3_multidraw
|
||||
@@ -0,0 +1,353 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_sh3_multitex (Ch355 Brick 1 — MULTI-TEXTURE composition)
|
||||
//
|
||||
// Two authentic SH3 draws with DIFFERENT TEX0/CLUT accumulate into ONE LPDDR framebuffer via scene-level texture
|
||||
// rebind + staged-list retriggering. Dump order: A=idx19562 (tbp=11264/CBP_A) THEN B=idx89761 (tbp=9216/CBP_B).
|
||||
// Proves Codex's Ch355 gates:
|
||||
// * TWO cache fills, each fill_done low->high, expected beats/bytes, 0 read errors, texture-specific CRC (A then B).
|
||||
// * NO stale-frame_drained race: EACH scene requires an observed frame_drained high->low->high (not a lingering 1).
|
||||
// * preclear EXACTLY once; scanout stays disabled until the SECOND fresh drain.
|
||||
// * records_emitted per list; final FB scored vs the independent A->B reference; OVERLAP scored separately.
|
||||
// * FB 320x381 (FBW=5), stride 1280, scanout 15240 beats/frame.
|
||||
// LOCAL/gitignored fixtures; skip-guard if absent.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_sh3_multitex;
|
||||
`include "sh3_mt_params.vh" // FBPXW=320, FBH=381, CBP_A/CBP_B, NTRIS_A/B, CRC_TEX_A/B, ...
|
||||
localparam int W = FBPXW, H = FBH;
|
||||
localparam int STRIDE = W*4; // 1280
|
||||
localparam int ROW_BEATS = STRIDE/32; // 40
|
||||
localparam int FB_BYTES = STRIDE*H; // 487680
|
||||
localparam int FB_WORDS = W*H;
|
||||
localparam int BEATS_PER_FRAME = ROW_BEATS*H; // 15240
|
||||
localparam int H_ACT=W, V_ACT=H, H_BP=32, H_FP=8, V_BP=16, V_FP=8;
|
||||
localparam int H_TOT=H_BP+H_ACT+H_FP, V_TOT=V_BP+V_ACT+V_FP;
|
||||
|
||||
logic clk=0; always #5 clk=~clk;
|
||||
logic emif_clk=0; always #2 emif_clk=~emif_clk;
|
||||
logic video_clk=0; always #7 video_clk=~video_clk;
|
||||
logic rst_n; logic fb_commit=0; always #13 fb_commit=~fb_commit;
|
||||
int errors; initial errors=0;
|
||||
|
||||
// ===== bram-top (FB_LPDDR_ONLY, AUTOSTART off, STG 2048), H/V_ACTIVE = 320x381 =====
|
||||
logic core_go; logic [7:0] r,g,b; logic hsync,vsync_o,de;
|
||||
logic core_halt,dma_done_seen,frame_seen,raster_overflow,frame_toggle,dma_done_toggle;
|
||||
logic feeder_go_tb, feeder_ready_tb; logic [15:0] feeder_records_w; logic [31:0] feeder_waits_w;
|
||||
logic gs_tex_rd_en_o; logic [31:0] gs_tex_rd_addr_o; logic [31:0] tex_cache_data; logic tex_cache_ready;
|
||||
logic [31:0] tex_cache_hits, tex_bram_hits;
|
||||
logic flush_emit_w; logic [31:0] flush_addr_w; logic [31:0] flush_color32_w; logic [5:0] flush_psm_w;
|
||||
|
||||
top_psmct32_raster_demo_bram #(
|
||||
.H_ACTIVE(FBPXW), .V_ACTIVE(FBH),
|
||||
.VRAM_BYTES(VRAM_BYTES_P), .VRAM_ENABLE_READ2(1'b0), .PSMCT32_SWIZZLE(1'b0),
|
||||
.COMBINED_TAZ(1'b0), .TILE_LOCAL(1'b0), .TILE_COLS(1), .TILE_ROWS(1),
|
||||
.TILE_MULTIPRIM(1'b0), .TILE_PRIM_COUNT(1), .TILE_FIFO_DEPTH(8),
|
||||
.BIN_BUFFER_ENABLE(1'b0), .HEARTBEAT_SPLICE_ENABLE(1'b0),
|
||||
.FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(STG_WORDS), .FEEDER_AUTOSTART(1'b0),
|
||||
.PERSPECTIVE_CORRECT(1'b1), .PERSP_RECIP_IDX_BITS(11),
|
||||
.GRAD_SEQ_DIVIDER(1'b1), .GRAD_DIV_CYCLES(4),
|
||||
.GS_LPDDR_TEX(1'b1), .TEX_VRAM_BASE(TEX_VRAM_BASE), .TEX_CACHE_BYTES(TEX_BYTES),
|
||||
.CLUT_CSM1_ENABLE(1'b1), .FB_LPDDR_ONLY(1'b1)
|
||||
) dut (
|
||||
.clk(clk), .rst_n(rst_n), .core_go(core_go),
|
||||
.r(r), .g(g), .b(b), .hsync(hsync), .vsync(vsync_o), .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),
|
||||
.feeder_stg_we_i(1'b0), .feeder_stg_waddr_i(12'd0), .feeder_stg_wdata_i(64'd0),
|
||||
.feeder_go_i(feeder_go_tb), .feeder_ready_o(feeder_ready_tb),
|
||||
.feeder_records_o(feeder_records_w), .feeder_waits_o(feeder_waits_w),
|
||||
.flush_emit_o(flush_emit_w), .flush_addr_o(flush_addr_w),
|
||||
.flush_pix16_o(), .flush_color32_o(flush_color32_w), .flush_psm_o(flush_psm_w),
|
||||
.gs_tex_rd_en_o(gs_tex_rd_en_o), .gs_tex_rd_addr_o(gs_tex_rd_addr_o),
|
||||
.tex_cache_data_i(tex_cache_data), .tex_cache_ready_i(tex_cache_ready),
|
||||
.tex_cache_hits_o(tex_cache_hits), .tex_bram_hits_o(tex_bram_hits)
|
||||
);
|
||||
|
||||
// texture cache + behavioral texture LPDDR (RELOADED between fills for the rebind)
|
||||
logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid; logic [7:0] arlen; logic [2:0] arsize;
|
||||
logic arvalid, arready; logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready;
|
||||
logic fill_start, fill_done; logic [31:0] fill_beats, fill_bytes, tex_rd_errs, fill_crc_w;
|
||||
gs_texture_cache #(.LPDDR_TEX_BASE(LPDDR_TEX_BASE), .TEX_VRAM_BASE(TEX_VRAM_BASE),
|
||||
.TEX_BYTES(TEX_BYTES), .N_BEATS(N_BEATS)) u_cache (
|
||||
.axi_clk(clk), .axi_rst_n(rst_n), .fill_start(fill_start), .fill_done(fill_done),
|
||||
.fill_beats(fill_beats), .fill_bytes(fill_bytes), .rd_errs(tex_rd_errs), .fill_crc(fill_crc_w),
|
||||
.araddr(araddr), .arburst(arburst), .arid(arid), .arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready),
|
||||
.rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready),
|
||||
.sample_clk(clk), .tex_rd_en(gs_tex_rd_en_o), .tex_rd_addr(gs_tex_rd_addr_o),
|
||||
.tex_rd_data(tex_cache_data), .tex_ready(tex_cache_ready)
|
||||
);
|
||||
logic [31:0] lpddr_mem [0:(TEX_BYTES/4)-1]; // reloaded: tex A, then tex B
|
||||
typedef enum logic [1:0] { S_IDLE, S_WAIT, S_DATA } sstate_t; sstate_t sst; logic [3:0] tdly; int tbeat;
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin sst<=S_IDLE; arready<=0; rvalid<=0; rlast<=0; rresp<=0; rdata<=0; tdly<=0; end
|
||||
else begin arready<=0; rvalid<=0; rlast<=0;
|
||||
case (sst)
|
||||
S_IDLE: if (arvalid) begin arready<=1; tbeat<=(araddr-LPDDR_TEX_BASE)>>5; tdly<=4'd2; sst<=S_WAIT; end
|
||||
S_WAIT: if (tdly==0) sst<=S_DATA; else tdly<=tdly-1'b1;
|
||||
S_DATA: if (rready) begin for (int w=0;w<8;w++) rdata[w*32 +: 32]<=lpddr_mem[tbeat*8+w];
|
||||
rresp<=2'b00; rvalid<=1; rlast<=1; sst<=S_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// render epoch (per scene) + PSMCT32 writer + precleared LPDDR FB
|
||||
logic fb_flush=0, render_inflight=0, feeder_ready_q=0; int eof_count;
|
||||
wire feeder_ready_rise = feeder_ready_tb && !feeder_ready_q;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin render_inflight<=0; feeder_ready_q<=0; fb_flush<=0; eof_count<=0; end
|
||||
else begin feeder_ready_q<=feeder_ready_tb; fb_flush<=1'b0;
|
||||
if (feeder_go_tb && feeder_ready_tb) render_inflight<=1'b1;
|
||||
if (render_inflight && feeder_ready_rise) begin fb_flush<=1'b1; render_inflight<=1'b0; eof_count<=eof_count+1; end
|
||||
end
|
||||
end
|
||||
logic wr_arm=0; logic [255:0] fbw_wdata; logic [31:0] fbw_wstrb, fbw_awaddr; logic fbw_awvalid, fbw_wvalid;
|
||||
logic [31:0] fbw_beats, fbw_ovf, fbw_bresp_err; logic fbw_idle, fbw_drained;
|
||||
gs_lpddr_axi_master #(.FIFO_DEPTH(64), .PIX_BYTES(4)) u_wr (
|
||||
.gs_clk(clk), .gs_rst_n(rst_n), .enable(1'b1),
|
||||
.arm(wr_arm), .canary(1'b0), .fb_base(32'h0), .ctrl_commit(fb_commit),
|
||||
.px_emit(flush_emit_w && (flush_psm_w==6'h00)), .px_addr(flush_addr_w), .px_pix32(flush_color32_w), .flush(fb_flush),
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n),
|
||||
.awaddr(fbw_awaddr), .awlen(), .awsize(), .awburst(), .awid(), .awvalid(fbw_awvalid), .awready(1'b1),
|
||||
.wdata(fbw_wdata), .wstrb(fbw_wstrb), .wlast(), .wvalid(fbw_wvalid), .wready(1'b1),
|
||||
.bvalid(1'b1), .bready(), .bresp(2'b00),
|
||||
.beats_written(fbw_beats), .bursts_issued(), .bresp_err_count(fbw_bresp_err),
|
||||
.fifo_overflow_count(fbw_ovf), .idle(fbw_idle), .frame_drained(fbw_drained)
|
||||
);
|
||||
logic [7:0] fb [0:FB_BYTES-1]; logic [31:0] fb_awlat;
|
||||
always_ff @(posedge emif_clk) begin
|
||||
if (fbw_awvalid) fb_awlat<=fbw_awaddr;
|
||||
if (fbw_wvalid) for (int i=0;i<32;i++) if (fbw_wstrb[i]) begin
|
||||
int aa; aa=fb_awlat+i; if (aa>=0 && aa<FB_BYTES) fb[aa]<=fbw_wdata[i*8 +: 8]; end
|
||||
end
|
||||
logic [31:0] ideal [0:FB_WORDS-1];
|
||||
always_ff @(posedge clk) if (rst_n && flush_emit_w && (flush_psm_w==6'h00)) ideal[flush_addr_w>>2]<=flush_color32_w;
|
||||
|
||||
// FRESH-DRAIN observer (Codex): count frame_drained low->high and high->low edges (emif domain).
|
||||
logic fd_q; int fd_rises, fd_falls;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin fd_q<=0; fd_rises<=0; fd_falls<=0; end
|
||||
else begin fd_q<=fbw_drained;
|
||||
if (fbw_drained && !fd_q) fd_rises<=fd_rises+1;
|
||||
if (!fbw_drained && fd_q) fd_falls<=fd_falls+1;
|
||||
end
|
||||
end
|
||||
|
||||
// scanout (320x381), host-gated: enable = scan_en (video_src) AND frame_drained
|
||||
logic scan_en=0; wire so_enable = scan_en & fbw_drained;
|
||||
logic [11:0] px, py; logic vsync, in_win; logic [7:0] so_r, so_g, so_b;
|
||||
logic so_underflow; logic [31:0] so_rd_errs; logic so_line_valid;
|
||||
logic [29:0] so_araddr; logic [1:0] so_arburst; logic [6:0] so_arid; logic [7:0] so_arlen;
|
||||
logic [2:0] so_arsize; logic so_arvalid, so_arready;
|
||||
logic [255:0] so_rdata; logic [1:0] so_rresp; logic so_rlast, so_rvalid, so_rready;
|
||||
gs_lpddr_scanout_lb #(.FB_BASE(30'd0), .STRIDE_BYTES(STRIDE), .ROW_BEATS(ROW_BEATS),
|
||||
.N_ROWS(H), .PSMCT32(1'b1)) u_scan (
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n), .enable(so_enable),
|
||||
.video_clk(video_clk), .frame_start(vsync), .pixel_x(px), .pixel_y(py), .in_window(in_win),
|
||||
.r(so_r), .g(so_g), .b(so_b), .line_valid(so_line_valid), .underflow(so_underflow), .rd_errs(so_rd_errs),
|
||||
.araddr(so_araddr), .arburst(so_arburst), .arid(so_arid), .arlen(so_arlen), .arsize(so_arsize),
|
||||
.arvalid(so_arvalid), .arready(so_arready), .rdata(so_rdata), .rresp(so_rresp),
|
||||
.rlast(so_rlast), .rvalid(so_rvalid), .rready(so_rready)
|
||||
);
|
||||
logic [7:0] rlfsr=8'h3C; always_ff @(posedge emif_clk) rlfsr<={rlfsr[6:0], rlfsr[7]^rlfsr[5]^rlfsr[4]^rlfsr[3]};
|
||||
typedef enum logic [1:0] { R_IDLE, R_WAIT, R_DATA } rst_t; rst_t rst_state;
|
||||
logic [3:0] rdly; logic [29:0] rd_addr_l; int read_beats;
|
||||
logic [2:0] vs_e; wire vs_edge_e = vs_e[1] && !vs_e[2]; int fb_reads, fb_reads_last;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin rst_state<=R_IDLE; so_arready<=0; so_rvalid<=0; so_rlast<=0; so_rresp<=0; so_rdata<=0; rdly<=0;
|
||||
read_beats<=0; vs_e<=0; fb_reads<=0; fb_reads_last<=0; end
|
||||
else begin so_arready<=0; so_rvalid<=0; so_rlast<=0; vs_e<={vs_e[1:0], vsync};
|
||||
if (vs_edge_e) begin fb_reads_last<=fb_reads; fb_reads<=0; end
|
||||
case (rst_state)
|
||||
R_IDLE: if (so_arvalid) begin so_arready<=1; rd_addr_l<=so_araddr; rdly<=rlfsr[2:0]; rst_state<=R_WAIT; end
|
||||
R_WAIT: if (rdly==0) rst_state<=R_DATA; else rdly<=rdly-1'b1;
|
||||
R_DATA: if (so_rready) begin
|
||||
for (int w=0;w<8;w++) begin int aa; aa=rd_addr_l+w*4;
|
||||
so_rdata[w*32 +: 32] <= (aa+3<FB_BYTES) ? {fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]} : 32'd0; end
|
||||
so_rresp<=2'b00; so_rvalid<=1; so_rlast<=1; read_beats<=read_beats+1;
|
||||
if (!vs_edge_e) fb_reads<=fb_reads+1; rst_state<=R_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
logic vid_run=0; logic [11:0] rawx, rawy;
|
||||
always_ff @(posedge video_clk) begin
|
||||
if (!vid_run) begin rawx<=0; rawy<=0; end
|
||||
else if (rawx==H_TOT-1) begin rawx<=0; rawy<=(rawy==V_TOT-1)?12'd0:rawy+1'b1; end
|
||||
else rawx<=rawx+1'b1;
|
||||
end
|
||||
wire active = (rawx>=H_BP)&&(rawx<H_BP+H_ACT)&&(rawy>=V_BP)&&(rawy<V_BP+V_ACT);
|
||||
assign px=active?(rawx-H_BP):12'd0; assign py=(rawy>=V_BP&&rawy<V_BP+V_ACT)?(rawy-V_BP):12'd0;
|
||||
assign in_win=active; assign vsync=vid_run&&(rawx==0)&&(rawy==0);
|
||||
logic [11:0] px_q, py_q; logic inwin_q, run_q;
|
||||
always_ff @(posedge video_clk) begin px_q<=px; py_q<=py; inwin_q<=in_win; run_q<=vid_run; end
|
||||
int checked; initial checked=0; logic scoring=0;
|
||||
always_ff @(posedge video_clk) if (scoring && run_q) begin
|
||||
logic [7:0] er,eg,eb; logic [31:0] w;
|
||||
if (inwin_q) begin int aa; aa=py_q*STRIDE+px_q*4; w={fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]}; er=w[7:0]; eg=w[15:8]; eb=w[23:16]; end
|
||||
else begin er=0; eg=0; eb=0; end
|
||||
checked++;
|
||||
if (so_r!==er||so_g!==eg||so_b!==eb) begin
|
||||
if (errors<12) $error("[scan] px(%0d,%0d) got(%02x,%02x,%02x) exp(%02x,%02x,%02x)", px_q,py_q, so_r,so_g,so_b, er,eg,eb); errors++; end
|
||||
end
|
||||
|
||||
// A->B composed reference + per-group palette/idx (for the oracle)
|
||||
logic [31:0] idxA [0:(512*512/4)-1]; logic [31:0] palA [0:255];
|
||||
logic [31:0] idxB [0:(512*512/4)-1]; logic [31:0] palB [0:255]; logic [31:0] refmap [0:FB_WORDS-1];
|
||||
|
||||
// ---- one-scene runner: GO, wait render+drain, and REQUIRE frame_drained high->low->high (fresh, not stale) ----
|
||||
task automatic run_scene(input string nm, input int exp_records);
|
||||
int r0, f0, d=0; logic fd_before;
|
||||
r0=fd_rises; f0=fd_falls; fd_before=fbw_drained; // stale high from a PRIOR scene?
|
||||
@(negedge clk); feeder_go_tb=1'b1; @(negedge clk); feeder_go_tb=1'b0;
|
||||
// ready drops (render started)
|
||||
d=0; while (feeder_ready_tb && d<4000) begin @(posedge clk); d++; end
|
||||
if (feeder_ready_tb!==1'b0) begin $error("[mt] %s: feeder_ready did not drop after GO", nm); errors++; end
|
||||
// ANTI-STALE (Codex): if frame_drained was HIGH before GO (a prior scene's drain), require a FALL first — proves
|
||||
// THIS scene's beats actually flowed and cleared the stale high (never accept a lingering 1 as this scene done).
|
||||
if (fd_before) begin
|
||||
d=0; while (fd_falls==f0 && d<800000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_falls==f0) begin $error("[mt] %s: frame_drained never fell from stale high — STALE drain (scene did not render fresh)", nm); errors++; end
|
||||
end
|
||||
// require a fresh RISE (this scene's ordered drain) either way -> high->low->high for B, low->high for A.
|
||||
d=0; while (fd_rises<=r0 && d<800000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_rises<=r0) begin $error("[mt] %s: frame_drained never rose — scene did not drain", nm); errors++; end
|
||||
repeat(100) @(posedge clk);
|
||||
if (feeder_records_w!==exp_records) begin $error("[mt] %s: records_emitted=%0d exp %0d", nm, feeder_records_w, exp_records); errors++; end
|
||||
if (fbw_ovf!==0 || fbw_bresp_err!==0) begin $error("[mt] %s: writer ovf=%0d bresp=%0d", nm, fbw_ovf, fbw_bresp_err); errors++; end
|
||||
$display("[mt] scene %s: fresh drain (falls %0d->%0d, rises %0d->%0d), records=%0d, ovf=0", nm, f0, fd_falls, r0, fd_rises, feeder_records_w);
|
||||
endtask
|
||||
|
||||
task automatic fill_cache(input string nm, input logic [31:0] exp_crc);
|
||||
int d=0; logic d0;
|
||||
d0=fill_done;
|
||||
@(posedge clk) fill_start<=1'b1; @(posedge clk) fill_start<=1'b0;
|
||||
// require a FRESH fill_done low->high (cache rearm), not a lingering high
|
||||
d=0; while (fill_done && d<1000) begin @(posedge clk); d++; end // wait it drops (busy)
|
||||
d=0; while (!fill_done && d<400000) begin @(posedge clk); d++; end // then rises (done)
|
||||
if (!fill_done) begin $error("[mt] fill %s: fill_done never rose (rearm failed)", nm); errors++; end
|
||||
if (fill_crc_w!==exp_crc) begin $error("[mt] fill %s: crc=%08x exp %08x", nm, fill_crc_w, exp_crc); errors++; end
|
||||
if (fill_beats!==N_BEATS) begin $error("[mt] fill %s: beats=%0d exp %0d", nm, fill_beats, N_BEATS); errors++; end
|
||||
if (fill_bytes!==TEX_BYTES) begin $error("[mt] fill %s: bytes=%0d exp %0d", nm, fill_bytes, TEX_BYTES); errors++; end
|
||||
if (tex_rd_errs!==0) begin $error("[mt] fill %s: rd_errs=%0d", nm, tex_rd_errs); errors++; end
|
||||
$display("[mt] cache fill %s: fresh done, crc=0x%08x (exp %08x), beats=%0d bytes=%0d rd_errs=0", nm, fill_crc_w, exp_crc, fill_beats, fill_bytes);
|
||||
endtask
|
||||
|
||||
// group-flag texel lookup — reads the module-level arrays (NO per-call array copy). gb=1 -> group B, else A.
|
||||
function automatic logic [23:0] cellg(input bit gb, input integer u, input integer v);
|
||||
integer lin; logic [31:0] w; logic [7:0] ix;
|
||||
lin=v*TW+u; w = gb ? idxB[lin/4] : idxA[lin/4]; ix=w[(8*(lin%4)) +: 8];
|
||||
cellg = gb ? palB[ix][23:0] : palA[ix][23:0];
|
||||
endfunction
|
||||
|
||||
string only_m; string fdump; int mode; // +ONLY=AB|A|B ; +FBDUMP=<file> (Codex isolation/composition diagnostic)
|
||||
initial begin
|
||||
errors=0; feeder_go_tb=1'b0; fill_start=1'b0; wr_arm=0; scan_en=0;
|
||||
if (!$value$plusargs("ONLY=%s", only_m)) only_m="AB";
|
||||
mode = (only_m=="A") ? 0 : (only_m=="B") ? 1 : 2; // 0=A-only(palA) 1=B-only(palB) 2=AB(both)
|
||||
for (int i=0;i<FB_WORDS;i++) ideal[i]=32'd0;
|
||||
for (int i=0;i<FB_BYTES;i++) fb[i]=8'h00; // PRECLEAR EXACTLY ONCE
|
||||
idxA[0]='x;
|
||||
$readmemh("../../data/top_psmct32_raster_demo/sh3_mtA_idx.mem", idxA);
|
||||
$readmemh("../../data/top_psmct32_raster_demo/sh3_mtA_pal.mem", palA);
|
||||
$readmemh("../../data/top_psmct32_raster_demo/sh3_mtB_idx.mem", idxB);
|
||||
$readmemh("../../data/top_psmct32_raster_demo/sh3_mtB_pal.mem", palB);
|
||||
if (mode==0) $readmemh("../../data/top_psmct32_raster_demo/sh3_mtA_refmap.mem", refmap);
|
||||
else if (mode==1) $readmemh("../../data/top_psmct32_raster_demo/sh3_mtB_refmap.mem", refmap);
|
||||
else $readmemh("../../data/top_psmct32_raster_demo/sh3_mt_refmap.mem", refmap);
|
||||
$display("[mt] ONLY=%s (mode=%0d)", only_m, mode);
|
||||
if (idxA[0]===32'bx) begin $display("[tb_top_psmct32_sh3_multitex] SKIP — sh3_mt*.mem absent (run gs_make_sh3_multitex_fixture.py --emit)"); $finish; end
|
||||
|
||||
rst_n=1'b0; core_go=1'b0; repeat(6) @(posedge clk); rst_n=1'b1; repeat(8) @(posedge clk);
|
||||
|
||||
// boot the bootlet (uploads BOTH relocated CLUTs to CBP_A, CBP_B)
|
||||
@(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);
|
||||
wait (feeder_ready_tb==1'b1); repeat(50) @(posedge clk);
|
||||
wr_arm<=1'b1; repeat(40) @(posedge clk);
|
||||
|
||||
// ===== SCENE A (runs in AB and A-only): texture A -> cache (fill #1), stage list A, GO, fresh drain =====
|
||||
if (mode!=1) begin
|
||||
$readmemh("../../data/top_psmct32_raster_demo/sh3_mtA_tex_lpddr.mem", lpddr_mem);
|
||||
fill_cache("A", CRC_TEX_A);
|
||||
$readmemh("../../data/top_psmct32_raster_demo/feeder_sh3_mtA.mem", dut.g_feeder.feeder_stg);
|
||||
run_scene("A", NTRIS_A);
|
||||
if (mode==2 && scan_en!==1'b0) begin $error("[mt] scanout enabled before scene B"); errors++; end
|
||||
end
|
||||
// ===== SCENE B (runs in AB and B-only): REBIND texture B -> cache (fill #2 rearm), stage list B, GO, drain =====
|
||||
if (mode!=0) begin
|
||||
$readmemh("../../data/top_psmct32_raster_demo/sh3_mtB_tex_lpddr.mem", lpddr_mem);
|
||||
fill_cache("B", CRC_TEX_B);
|
||||
$readmemh("../../data/top_psmct32_raster_demo/feeder_sh3_mtB.mem", dut.g_feeder.feeder_stg);
|
||||
run_scene("B", NTRIS_B);
|
||||
end
|
||||
|
||||
// enable scanout only after the final fresh drain
|
||||
scan_en<=1'b1; repeat(20) @(posedge clk);
|
||||
if (mode==2) begin
|
||||
if (fd_rises<2) begin $error("[mt] expected 2 ordered drains (A,B): rises=%0d", fd_rises); errors++; end
|
||||
if (fd_falls<1) begin $error("[mt] scene B never cleared the stale drain (falls=%0d) — stale-drain race", fd_falls); errors++; end
|
||||
end else if (fd_rises<1) begin $error("[mt] isolated scene did not drain: rises=%0d", fd_rises); errors++; end
|
||||
|
||||
// ===== scanout: score final FB vs the A->B reference; overlap separately =====
|
||||
vid_run=1;
|
||||
begin int d=0; while(!vsync && d<300000) begin @(posedge video_clk); d++; end end
|
||||
@(posedge video_clk); while(!vsync) @(posedge video_clk);
|
||||
begin scoring=1; @(posedge video_clk); while(!vsync) @(posedge video_clk); scoring=0; repeat(60) @(posedge emif_clk);
|
||||
if (fb_reads_last!==BEATS_PER_FRAME) begin $error("[mt] scanout beats/frame=%0d exp %0d", fb_reads_last, BEATS_PER_FRAME); errors++; end
|
||||
end
|
||||
if (so_underflow!==0) begin $error("[mt] scanout underflow"); errors++; end
|
||||
if (so_rd_errs !==0) begin $error("[mt] scanout rd_errs=%0d", so_rd_errs); errors++; end
|
||||
if (checked < H_ACT*V_ACT) begin $error("[mt] only %0d px checked (exp >= %0d)", checked, H_ACT*V_ACT); errors++; end
|
||||
|
||||
// The reference stored (tu,tv) of the WINNING group per pixel (B on overlap, else the covering group). Since
|
||||
// the per-pixel owner-group is not encoded, accept the RTL colour if it matches the <=1-texel neighbourhood in
|
||||
// EITHER palette A or B (multi-texture: the winning group's texel is one of the two). overlap (bit28) scored
|
||||
// separately. clut_bad requires the colour to exist in palette A OR B.
|
||||
begin
|
||||
int m_tot,m_ok,o_tot,o_ok,cb,D; bit mt; m_tot=0;m_ok=0;o_tot=0;o_ok=0;cb=0;
|
||||
for (int y=0;y<H;y++) for (int x=0;x<W;x++) begin
|
||||
logic [31:0] rm; logic [23:0] fbc; int tu,tv; bit ovl; rm=refmap[y*W+x];
|
||||
if (rm[31]) begin
|
||||
tu=(rm>>9)&9'h1FF; tv=rm&9'h1FF; ovl=rm[28]; fbc=ideal[y*W+x][23:0]; m_tot++; D=9;
|
||||
for (int rad=0;rad<=1 && D==9;rad++) for (int du=-rad;du<=rad;du++) for (int dv=-rad;dv<=rad;dv++) begin
|
||||
int ch; ch=(du<0?-du:du); if((dv<0?-dv:dv)>ch) ch=(dv<0?-dv:dv);
|
||||
if (ch==rad && D==9 && (tu+du)>=0 && (tu+du)<TW && (tv+dv)>=0 && (tv+dv)<TH) begin
|
||||
mt = (mode==0) ? (fbc===cellg(1'b0,tu+du,tv+dv)) // A-only: palette A
|
||||
: (mode==1) ? (fbc===cellg(1'b1,tu+du,tv+dv)) // B-only: palette B
|
||||
: (fbc===cellg(1'b0,tu+du,tv+dv) || fbc===cellg(1'b1,tu+du,tv+dv));
|
||||
if (mt) D=rad;
|
||||
end
|
||||
end
|
||||
if (D<=1) m_ok++;
|
||||
if (ovl) begin o_tot++; if (D<=1) o_ok++; end
|
||||
// clut_bad = a pixel the RTL ACTUALLY WROTE (ideal!=0) whose colour is not in the expected palette.
|
||||
// Reference-covered pixels the RTL left black at a coverage edge (ideal==0) are texel misses (already
|
||||
// penalized in <=1texel), NOT wrong-CLUT — do not count them here.
|
||||
if (ideal[y*W+x]!==32'd0) begin bit f; f=1'b0; for (int i=0;i<256;i++)
|
||||
if ((mode!=1 && palA[i][23:0]===fbc) || (mode!=0 && palB[i][23:0]===fbc)) f=1'b1;
|
||||
if(!f) cb++; end
|
||||
end
|
||||
end
|
||||
$display("[mt][oracle] ONLY=%s <=1texel ALL=%0d/%0d (%.1f%%) OVERLAP(A&B)=%0d/%0d (%.1f%%) clut_bad=%0d",
|
||||
only_m, m_ok,m_tot,(m_tot>0)?100.0*m_ok/m_tot:0.0, o_ok,o_tot,(o_tot>0)?100.0*o_ok/o_tot:0.0, cb);
|
||||
if (cb!==0) begin $error("[mt][oracle] ONLY=%s: %0d covered px not in the expected palette", only_m, cb); errors++; end
|
||||
if (mode==2) begin
|
||||
if (o_tot<500) begin $error("[mt][oracle] only %0d overlap px — accumulation not exercised", o_tot); errors++; end
|
||||
// OVERLAP is the composite proof (B over A). ALL reported. Per-draw A/B fidelity settled by the ONLY=A/B runs.
|
||||
if (o_tot>0 && (100.0*o_ok/o_tot)<95.0) begin $error("[mt][oracle] OVERLAP composite <=1texel %.1f%% < 95%%", 100.0*o_ok/o_tot); errors++; end
|
||||
end
|
||||
end
|
||||
// FB dump for the Codex composition check (A-only + B-only -> compose -> compare AB combined)
|
||||
if ($value$plusargs("FBDUMP=%s", fdump)) begin
|
||||
int fh; fh=$fopen(fdump,"w");
|
||||
for (int y=0;y<H;y++) for (int x=0;x<W;x++) begin int aa; aa=y*STRIDE+x*4; $fwrite(fh,"%08x\n",{fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]}); end
|
||||
$fclose(fh); $display("[mt] dumped RTL FB (%0dx%0d) -> %s", W, H, fdump);
|
||||
end
|
||||
|
||||
$display("[tb_top_psmct32_sh3_multitex] checked=%0d beats/frame=%0d (exp %0d) fresh_drains(rise/fall)=%0d/%0d records=%0d underflow=%0b ovf=%0d errors=%0d",
|
||||
checked, fb_reads_last, BEATS_PER_FRAME, fd_rises, fd_falls, feeder_records_w, so_underflow, fbw_ovf, errors);
|
||||
if (errors==0) $display("[tb_top_psmct32_sh3_multitex] PASS"); else $display("[tb_top_psmct32_sh3_multitex] FAIL");
|
||||
$finish;
|
||||
end
|
||||
initial begin #120000000; $error("[tb_top_psmct32_sh3_multitex] TIMEOUT"); $finish; end
|
||||
endmodule : tb_top_psmct32_sh3_multitex
|
||||
@@ -0,0 +1,391 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_sh3_sched (Ch356 — N-TEXTURE SCHEDULER, data-driven epoch descriptors)
|
||||
//
|
||||
// Generalizes Ch355's two-group flow to N authentic SH3 draw epochs, each with a DIFFERENT TEX0/CLUT, accumulating
|
||||
// into ONE LPDDR framebuffer via a data-driven scheduler over epoch descriptors (sh3_sched_params.vh). Default
|
||||
// N_EPOCHS=3: E0=idx11671 E1=idx19562 E2=idx89761. Proves Codex's Ch356 integration acceptance:
|
||||
// * preclear EXACTLY once.
|
||||
// * N FRESH cache fills, each fill_done low->high, expected beats/bytes, 0 read errors, epoch CRC (EPk_CRC).
|
||||
// * N lists STREAMED through the feeder staging WRITE PORT (feeder_stg_we/waddr/wdata) — NOT the $readmemh backdoor
|
||||
// (closes the Ch355 sim gap where the write-port sequencing was untested). Each streams the full STG_WORDS so
|
||||
// staging is fully reset per epoch (no stale words), and word0 (the ntris header) is exercised first.
|
||||
// * N FRESH ORDERED drains: epoch 0 low->high (first render); epochs 1..N-1 high->low->high (stale cleared).
|
||||
// * scanout stays disabled until the LAST fresh drain; exact BEATS_PER_FRAME (=ROW_BEATS*H).
|
||||
// * final FB scored vs the independent composed reference using the PER-PIXEL OWNER epoch (exact palette per owner);
|
||||
// multi-epoch (>=2) overlap scored separately as the accumulation proof.
|
||||
// * +ONLY=<k> renders a SINGLE epoch (+FBDUMP dumps its FB) for the composition==isolated bit-for-bit check.
|
||||
// LOCAL/gitignored fixtures; skip-guard if absent.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_sh3_sched;
|
||||
`include "sh3_sched_params.vh" // FBPXW=384, FBH=381, N_EPOCHS=3, EPk_CRC/EPk_NTRIS/EPk_CBP, TEX_*, ...
|
||||
localparam int W = FBPXW, H = FBH;
|
||||
localparam int STRIDE = W*4; // 1536
|
||||
localparam int ROW_BEATS = STRIDE/32; // 48
|
||||
localparam int FB_BYTES = STRIDE*H; // 585216
|
||||
localparam int FB_WORDS = W*H;
|
||||
localparam int BEATS_PER_FRAME = ROW_BEATS*H; // 18288
|
||||
localparam int H_ACT=W, V_ACT=H, H_BP=32, H_FP=8, V_BP=16, V_FP=8;
|
||||
localparam int H_TOT=H_BP+H_ACT+H_FP, V_TOT=V_BP+V_ACT+V_FP;
|
||||
localparam int NEP = N_EPOCHS; // 3
|
||||
|
||||
// per-epoch expected CRC / records (from the descriptor params)
|
||||
logic [31:0] EP_CRC [0:2]; int EP_REC [0:2];
|
||||
initial begin
|
||||
EP_CRC[0]=EP0_CRC; EP_CRC[1]=EP1_CRC; EP_CRC[2]=EP2_CRC;
|
||||
EP_REC[0]=EP0_NTRIS; EP_REC[1]=EP1_NTRIS; EP_REC[2]=EP2_NTRIS;
|
||||
end
|
||||
|
||||
logic clk=0; always #5 clk=~clk;
|
||||
logic emif_clk=0; always #2 emif_clk=~emif_clk;
|
||||
logic video_clk=0; always #7 video_clk=~video_clk;
|
||||
logic rst_n; logic fb_commit=0; always #13 fb_commit=~fb_commit;
|
||||
int errors; initial errors=0;
|
||||
|
||||
// ===== bram-top (FB_LPDDR_ONLY, AUTOSTART off, STG 2048), H/V_ACTIVE = 384x381 =====
|
||||
logic core_go; logic [7:0] r,g,b; logic hsync,vsync_o,de;
|
||||
logic core_halt,dma_done_seen,frame_seen,raster_overflow,frame_toggle,dma_done_toggle;
|
||||
logic feeder_go_tb, feeder_ready_tb; logic [15:0] feeder_records_w; logic [31:0] feeder_waits_w;
|
||||
logic gs_tex_rd_en_o; logic [31:0] gs_tex_rd_addr_o; logic [31:0] tex_cache_data; logic tex_cache_ready;
|
||||
logic [31:0] tex_cache_hits, tex_bram_hits;
|
||||
logic flush_emit_w; logic [31:0] flush_addr_w; logic [31:0] flush_color32_w; logic [5:0] flush_psm_w;
|
||||
// feeder staging WRITE PORT (streamed per epoch)
|
||||
logic stg_we; logic [11:0] stg_waddr; logic [63:0] stg_wdata;
|
||||
|
||||
top_psmct32_raster_demo_bram #(
|
||||
.H_ACTIVE(FBPXW), .V_ACTIVE(FBH),
|
||||
.VRAM_BYTES(VRAM_BYTES_P), .VRAM_ENABLE_READ2(1'b0), .PSMCT32_SWIZZLE(1'b0),
|
||||
.COMBINED_TAZ(1'b0), .TILE_LOCAL(1'b0), .TILE_COLS(1), .TILE_ROWS(1),
|
||||
.TILE_MULTIPRIM(1'b0), .TILE_PRIM_COUNT(1), .TILE_FIFO_DEPTH(8),
|
||||
.BIN_BUFFER_ENABLE(1'b0), .HEARTBEAT_SPLICE_ENABLE(1'b0),
|
||||
.FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(STG_WORDS), .FEEDER_AUTOSTART(1'b0),
|
||||
.PERSPECTIVE_CORRECT(1'b1), .PERSP_RECIP_IDX_BITS(11),
|
||||
.GRAD_SEQ_DIVIDER(1'b1), .GRAD_DIV_CYCLES(4),
|
||||
.GS_LPDDR_TEX(1'b1), .TEX_VRAM_BASE(TEX_VRAM_BASE), .TEX_CACHE_BYTES(TEX_BYTES),
|
||||
.CLUT_CSM1_ENABLE(1'b1), .FB_LPDDR_ONLY(1'b1)
|
||||
) dut (
|
||||
.clk(clk), .rst_n(rst_n), .core_go(core_go),
|
||||
.r(r), .g(g), .b(b), .hsync(hsync), .vsync(vsync_o), .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),
|
||||
.feeder_stg_we_i(stg_we), .feeder_stg_waddr_i(stg_waddr), .feeder_stg_wdata_i(stg_wdata),
|
||||
.feeder_go_i(feeder_go_tb), .feeder_ready_o(feeder_ready_tb),
|
||||
.feeder_records_o(feeder_records_w), .feeder_waits_o(feeder_waits_w),
|
||||
.flush_emit_o(flush_emit_w), .flush_addr_o(flush_addr_w),
|
||||
.flush_pix16_o(), .flush_color32_o(flush_color32_w), .flush_psm_o(flush_psm_w),
|
||||
.gs_tex_rd_en_o(gs_tex_rd_en_o), .gs_tex_rd_addr_o(gs_tex_rd_addr_o),
|
||||
.tex_cache_data_i(tex_cache_data), .tex_cache_ready_i(tex_cache_ready),
|
||||
.tex_cache_hits_o(tex_cache_hits), .tex_bram_hits_o(tex_bram_hits)
|
||||
);
|
||||
|
||||
// texture cache + behavioral texture LPDDR (RELOADED between fills for each epoch's rebind)
|
||||
logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid; logic [7:0] arlen; logic [2:0] arsize;
|
||||
logic arvalid, arready; logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready;
|
||||
logic fill_start, fill_done; logic [31:0] fill_beats, fill_bytes, tex_rd_errs, fill_crc_w;
|
||||
gs_texture_cache #(.LPDDR_TEX_BASE(LPDDR_TEX_BASE), .TEX_VRAM_BASE(TEX_VRAM_BASE),
|
||||
.TEX_BYTES(TEX_BYTES), .N_BEATS(N_BEATS)) u_cache (
|
||||
.axi_clk(clk), .axi_rst_n(rst_n), .fill_start(fill_start), .fill_done(fill_done),
|
||||
.fill_beats(fill_beats), .fill_bytes(fill_bytes), .rd_errs(tex_rd_errs), .fill_crc(fill_crc_w),
|
||||
.araddr(araddr), .arburst(arburst), .arid(arid), .arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready),
|
||||
.rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready),
|
||||
.sample_clk(clk), .tex_rd_en(gs_tex_rd_en_o), .tex_rd_addr(gs_tex_rd_addr_o),
|
||||
.tex_rd_data(tex_cache_data), .tex_ready(tex_cache_ready)
|
||||
);
|
||||
logic [31:0] lpddr_mem [0:(TEX_BYTES/4)-1]; // reloaded per epoch: tex0, tex1, tex2
|
||||
typedef enum logic [1:0] { S_IDLE, S_WAIT, S_DATA } sstate_t; sstate_t sst; logic [3:0] tdly; int tbeat;
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin sst<=S_IDLE; arready<=0; rvalid<=0; rlast<=0; rresp<=0; rdata<=0; tdly<=0; end
|
||||
else begin arready<=0; rvalid<=0; rlast<=0;
|
||||
case (sst)
|
||||
S_IDLE: if (arvalid) begin arready<=1; tbeat<=(araddr-LPDDR_TEX_BASE)>>5; tdly<=4'd2; sst<=S_WAIT; end
|
||||
S_WAIT: if (tdly==0) sst<=S_DATA; else tdly<=tdly-1'b1;
|
||||
S_DATA: if (rready) begin for (int w=0;w<8;w++) rdata[w*32 +: 32]<=lpddr_mem[tbeat*8+w];
|
||||
rresp<=2'b00; rvalid<=1; rlast<=1; sst<=S_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// render epoch (per scene) + PSMCT32 writer + precleared LPDDR FB
|
||||
logic fb_flush=0, render_inflight=0, feeder_ready_q=0; int eof_count;
|
||||
wire feeder_ready_rise = feeder_ready_tb && !feeder_ready_q;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin render_inflight<=0; feeder_ready_q<=0; fb_flush<=0; eof_count<=0; end
|
||||
else begin feeder_ready_q<=feeder_ready_tb; fb_flush<=1'b0;
|
||||
if (feeder_go_tb && feeder_ready_tb) render_inflight<=1'b1;
|
||||
if (render_inflight && feeder_ready_rise) begin fb_flush<=1'b1; render_inflight<=1'b0; eof_count<=eof_count+1; end
|
||||
end
|
||||
end
|
||||
logic wr_arm=0; logic [255:0] fbw_wdata; logic [31:0] fbw_wstrb, fbw_awaddr; logic fbw_awvalid, fbw_wvalid;
|
||||
logic [31:0] fbw_beats, fbw_ovf, fbw_bresp_err; logic fbw_idle, fbw_drained;
|
||||
gs_lpddr_axi_master #(.FIFO_DEPTH(64), .PIX_BYTES(4)) u_wr (
|
||||
.gs_clk(clk), .gs_rst_n(rst_n), .enable(1'b1),
|
||||
.arm(wr_arm), .canary(1'b0), .fb_base(32'h0), .ctrl_commit(fb_commit),
|
||||
.px_emit(flush_emit_w && (flush_psm_w==6'h00)), .px_addr(flush_addr_w), .px_pix32(flush_color32_w), .flush(fb_flush),
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n),
|
||||
.awaddr(fbw_awaddr), .awlen(), .awsize(), .awburst(), .awid(), .awvalid(fbw_awvalid), .awready(1'b1),
|
||||
.wdata(fbw_wdata), .wstrb(fbw_wstrb), .wlast(), .wvalid(fbw_wvalid), .wready(1'b1),
|
||||
.bvalid(1'b1), .bready(), .bresp(2'b00),
|
||||
.beats_written(fbw_beats), .bursts_issued(), .bresp_err_count(fbw_bresp_err),
|
||||
.fifo_overflow_count(fbw_ovf), .idle(fbw_idle), .frame_drained(fbw_drained)
|
||||
);
|
||||
logic [7:0] fb [0:FB_BYTES-1]; logic [31:0] fb_awlat;
|
||||
always_ff @(posedge emif_clk) begin
|
||||
if (fbw_awvalid) fb_awlat<=fbw_awaddr;
|
||||
if (fbw_wvalid) for (int i=0;i<32;i++) if (fbw_wstrb[i]) begin
|
||||
int aa; aa=fb_awlat+i; if (aa>=0 && aa<FB_BYTES) fb[aa]<=fbw_wdata[i*8 +: 8]; end
|
||||
end
|
||||
logic [31:0] ideal [0:FB_WORDS-1];
|
||||
always_ff @(posedge clk) if (rst_n && flush_emit_w && (flush_psm_w==6'h00)) ideal[flush_addr_w>>2]<=flush_color32_w;
|
||||
|
||||
// FRESH-DRAIN observer: count frame_drained low->high and high->low edges (emif domain).
|
||||
logic fd_q; int fd_rises, fd_falls;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin fd_q<=0; fd_rises<=0; fd_falls<=0; end
|
||||
else begin fd_q<=fbw_drained;
|
||||
if (fbw_drained && !fd_q) fd_rises<=fd_rises+1;
|
||||
if (!fbw_drained && fd_q) fd_falls<=fd_falls+1;
|
||||
end
|
||||
end
|
||||
|
||||
// scanout (384x381), host-gated: enable = scan_en (video_src) AND frame_drained
|
||||
logic scan_en=0; wire so_enable = scan_en & fbw_drained;
|
||||
logic [11:0] px, py; logic vsync, in_win; logic [7:0] so_r, so_g, so_b;
|
||||
logic so_underflow; logic [31:0] so_rd_errs; logic so_line_valid;
|
||||
logic [29:0] so_araddr; logic [1:0] so_arburst; logic [6:0] so_arid; logic [7:0] so_arlen;
|
||||
logic [2:0] so_arsize; logic so_arvalid, so_arready;
|
||||
logic [255:0] so_rdata; logic [1:0] so_rresp; logic so_rlast, so_rvalid, so_rready;
|
||||
gs_lpddr_scanout_lb #(.FB_BASE(30'd0), .STRIDE_BYTES(STRIDE), .ROW_BEATS(ROW_BEATS),
|
||||
.N_ROWS(H), .PSMCT32(1'b1)) u_scan (
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n), .enable(so_enable),
|
||||
.video_clk(video_clk), .frame_start(vsync), .pixel_x(px), .pixel_y(py), .in_window(in_win),
|
||||
.r(so_r), .g(so_g), .b(so_b), .line_valid(so_line_valid), .underflow(so_underflow), .rd_errs(so_rd_errs),
|
||||
.araddr(so_araddr), .arburst(so_arburst), .arid(so_arid), .arlen(so_arlen), .arsize(so_arsize),
|
||||
.arvalid(so_arvalid), .arready(so_arready), .rdata(so_rdata), .rresp(so_rresp),
|
||||
.rlast(so_rlast), .rvalid(so_rvalid), .rready(so_rready)
|
||||
);
|
||||
logic [7:0] rlfsr=8'h3C; always_ff @(posedge emif_clk) rlfsr<={rlfsr[6:0], rlfsr[7]^rlfsr[5]^rlfsr[4]^rlfsr[3]};
|
||||
typedef enum logic [1:0] { R_IDLE, R_WAIT, R_DATA } rst_t; rst_t rst_state;
|
||||
logic [3:0] rdly; logic [29:0] rd_addr_l; int read_beats;
|
||||
logic [2:0] vs_e; wire vs_edge_e = vs_e[1] && !vs_e[2]; int fb_reads, fb_reads_last;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin rst_state<=R_IDLE; so_arready<=0; so_rvalid<=0; so_rlast<=0; so_rresp<=0; so_rdata<=0; rdly<=0;
|
||||
read_beats<=0; vs_e<=0; fb_reads<=0; fb_reads_last<=0; end
|
||||
else begin so_arready<=0; so_rvalid<=0; so_rlast<=0; vs_e<={vs_e[1:0], vsync};
|
||||
if (vs_edge_e) begin fb_reads_last<=fb_reads; fb_reads<=0; end
|
||||
case (rst_state)
|
||||
R_IDLE: if (so_arvalid) begin so_arready<=1; rd_addr_l<=so_araddr; rdly<=rlfsr[2:0]; rst_state<=R_WAIT; end
|
||||
R_WAIT: if (rdly==0) rst_state<=R_DATA; else rdly<=rdly-1'b1;
|
||||
R_DATA: if (so_rready) begin
|
||||
for (int w=0;w<8;w++) begin int aa; aa=rd_addr_l+w*4;
|
||||
so_rdata[w*32 +: 32] <= (aa+3<FB_BYTES) ? {fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]} : 32'd0; end
|
||||
so_rresp<=2'b00; so_rvalid<=1; so_rlast<=1; read_beats<=read_beats+1;
|
||||
if (!vs_edge_e) fb_reads<=fb_reads+1; rst_state<=R_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
logic vid_run=0; logic [11:0] rawx, rawy;
|
||||
always_ff @(posedge video_clk) begin
|
||||
if (!vid_run) begin rawx<=0; rawy<=0; end
|
||||
else if (rawx==H_TOT-1) begin rawx<=0; rawy<=(rawy==V_TOT-1)?12'd0:rawy+1'b1; end
|
||||
else rawx<=rawx+1'b1;
|
||||
end
|
||||
wire active = (rawx>=H_BP)&&(rawx<H_BP+H_ACT)&&(rawy>=V_BP)&&(rawy<V_BP+V_ACT);
|
||||
assign px=active?(rawx-H_BP):12'd0; assign py=(rawy>=V_BP&&rawy<V_BP+V_ACT)?(rawy-V_BP):12'd0;
|
||||
assign in_win=active; assign vsync=vid_run&&(rawx==0)&&(rawy==0);
|
||||
logic [11:0] px_q, py_q; logic inwin_q, run_q;
|
||||
always_ff @(posedge video_clk) begin px_q<=px; py_q<=py; inwin_q<=in_win; run_q<=vid_run; end
|
||||
int checked; initial checked=0; logic scoring=0;
|
||||
always_ff @(posedge video_clk) if (scoring && run_q) begin
|
||||
logic [7:0] er,eg,eb; logic [31:0] w;
|
||||
if (inwin_q) begin int aa; aa=py_q*STRIDE+px_q*4; w={fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]}; er=w[7:0]; eg=w[15:8]; eb=w[23:16]; end
|
||||
else begin er=0; eg=0; eb=0; end
|
||||
checked++;
|
||||
if (so_r!==er||so_g!==eg||so_b!==eb) begin
|
||||
if (errors<12) $error("[scan] px(%0d,%0d) got(%02x,%02x,%02x) exp(%02x,%02x,%02x)", px_q,py_q, so_r,so_g,so_b, er,eg,eb); errors++; end
|
||||
end
|
||||
|
||||
// per-epoch idx/pal (for the oracle) + composed reference (owner epoch in [26:24], multi in [28]) + per-epoch refmaps
|
||||
logic [31:0] idx [0:2][0:(512*512/4)-1]; logic [31:0] pal [0:2][0:255];
|
||||
logic [31:0] refmap [0:FB_WORDS-1]; logic [31:0] refmap_ep [0:2][0:FB_WORDS-1];
|
||||
logic [63:0] stg_buf [0:STG_WORDS-1]; // one epoch's staging list, streamed through the write port
|
||||
// temps: iverilog can't $readmemh into a 2D-array slice, so load flat then copy into the [e] plane
|
||||
logic [31:0] t_idx [0:(512*512/4)-1]; logic [31:0] t_pal [0:255]; logic [31:0] t_rm [0:FB_WORDS-1];
|
||||
|
||||
// ---- stream one epoch's list into the feeder staging via the WRITE PORT (word0 first, full STG reset) ----
|
||||
task automatic stream_list(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/feeder_sh3_sched%0d.mem", k);
|
||||
for (int i=0;i<STG_WORDS;i++) stg_buf[i]=64'd0;
|
||||
$readmemh(fn, stg_buf);
|
||||
@(negedge clk);
|
||||
for (int i=0;i<STG_WORDS;i++) begin
|
||||
stg_we<=1'b1; stg_waddr<=i[11:0]; stg_wdata<=stg_buf[i]; @(negedge clk);
|
||||
end
|
||||
stg_we<=1'b0; stg_waddr<=12'd0; stg_wdata<=64'd0; @(negedge clk);
|
||||
// sanity: header word0 (ntris) landed correctly in the DUT staging
|
||||
if (dut.g_feeder.feeder_stg[0][31:0] !== stg_buf[0][31:0]) begin
|
||||
$error("[sched] epoch %0d: staged word0=%08x exp %08x (write-port mis-slot)", k, dut.g_feeder.feeder_stg[0][31:0], stg_buf[0][31:0]); errors++; end
|
||||
$display("[sched] epoch %0d: streamed %0d words via write port; word0(ntris)=%0d", k, STG_WORDS, dut.g_feeder.feeder_stg[0][31:0]);
|
||||
endtask
|
||||
|
||||
// ---- one-scene runner: GO, wait render+drain, REQUIRE fresh frame_drained (high->low->high, or low->high on first) ----
|
||||
task automatic run_scene(input int k, input int exp_records);
|
||||
int r0, f0, d=0; logic fd_before;
|
||||
r0=fd_rises; f0=fd_falls; fd_before=fbw_drained;
|
||||
@(negedge clk); feeder_go_tb=1'b1; @(negedge clk); feeder_go_tb=1'b0;
|
||||
d=0; while (feeder_ready_tb && d<4000) begin @(posedge clk); d++; end
|
||||
if (feeder_ready_tb!==1'b0) begin $error("[sched] epoch %0d: feeder_ready did not drop after GO", k); errors++; end
|
||||
if (fd_before) begin
|
||||
d=0; while (fd_falls==f0 && d<800000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_falls==f0) begin $error("[sched] epoch %0d: frame_drained never fell from stale high — STALE drain", k); errors++; end
|
||||
end
|
||||
d=0; while (fd_rises<=r0 && d<800000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_rises<=r0) begin $error("[sched] epoch %0d: frame_drained never rose — scene did not drain", k); errors++; end
|
||||
repeat(100) @(posedge clk);
|
||||
if (feeder_records_w!==exp_records) begin $error("[sched] epoch %0d: records_emitted=%0d exp %0d", k, feeder_records_w, exp_records); errors++; end
|
||||
if (fbw_ovf!==0 || fbw_bresp_err!==0) begin $error("[sched] epoch %0d: writer ovf=%0d bresp=%0d", k, fbw_ovf, fbw_bresp_err); errors++; end
|
||||
$display("[sched] epoch %0d: fresh drain (falls %0d->%0d, rises %0d->%0d), records=%0d, ovf=0", k, f0, fd_falls, r0, fd_rises, feeder_records_w);
|
||||
endtask
|
||||
|
||||
task automatic fill_cache(input int k, input logic [31:0] exp_crc);
|
||||
int d=0;
|
||||
@(posedge clk) fill_start<=1'b1; @(posedge clk) fill_start<=1'b0;
|
||||
d=0; while (fill_done && d<1000) begin @(posedge clk); d++; end // wait it drops (busy)
|
||||
d=0; while (!fill_done && d<400000) begin @(posedge clk); d++; end // then rises (done)
|
||||
if (!fill_done) begin $error("[sched] fill %0d: fill_done never rose (rearm failed)", k); errors++; end
|
||||
if (fill_crc_w!==exp_crc) begin $error("[sched] fill %0d: crc=%08x exp %08x", k, fill_crc_w, exp_crc); errors++; end
|
||||
if (fill_beats!==N_BEATS) begin $error("[sched] fill %0d: beats=%0d exp %0d", k, fill_beats, N_BEATS); errors++; end
|
||||
if (fill_bytes!==TEX_BYTES) begin $error("[sched] fill %0d: bytes=%0d exp %0d", k, fill_bytes, TEX_BYTES); errors++; end
|
||||
if (tex_rd_errs!==0) begin $error("[sched] fill %0d: rd_errs=%0d", k, tex_rd_errs); errors++; end
|
||||
$display("[sched] cache fill %0d: fresh done, crc=0x%08x (exp %08x), beats=%0d bytes=%0d rd_errs=0", k, fill_crc_w, exp_crc, fill_beats, fill_bytes);
|
||||
endtask
|
||||
|
||||
// texel lookup in epoch e's palette (module-level arrays, no per-call copy)
|
||||
function automatic logic [23:0] cell_e(input int e, input integer u, input integer v);
|
||||
integer lin; logic [31:0] w; logic [7:0] ix;
|
||||
lin=v*TW+u; w=idx[e][lin/4]; ix=w[(8*(lin%4)) +: 8]; cell_e=pal[e][ix][23:0];
|
||||
endfunction
|
||||
|
||||
// run ONE epoch fully (rebind tex -> fresh fill+CRC -> stream list -> GO -> fresh ordered drain)
|
||||
task automatic run_epoch(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/sh3_sched%0d_tex_lpddr.mem", k);
|
||||
$readmemh(fn, lpddr_mem);
|
||||
fill_cache(k, EP_CRC[k]);
|
||||
stream_list(k);
|
||||
run_scene(k, EP_REC[k]);
|
||||
endtask
|
||||
|
||||
string only_m; string fdump; int only_k; // +ONLY=ALL | +ONLY=<k> ; +FBDUMP=<file>
|
||||
initial begin
|
||||
errors=0; feeder_go_tb=1'b0; fill_start=1'b0; wr_arm=0; scan_en=0; stg_we=0; stg_waddr=0; stg_wdata=0;
|
||||
if (!$value$plusargs("ONLY=%s", only_m)) only_m="ALL";
|
||||
only_k = (only_m=="ALL") ? -1 : only_m.atoi();
|
||||
for (int i=0;i<FB_WORDS;i++) ideal[i]=32'd0;
|
||||
for (int i=0;i<FB_BYTES;i++) fb[i]=8'h00; // PRECLEAR EXACTLY ONCE
|
||||
idx[0][0]='x;
|
||||
for (int e=0;e<NEP;e++) begin
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_sched%0d_idx.mem", e), t_idx);
|
||||
for (int i=0;i<(512*512/4);i++) idx[e][i]=t_idx[i];
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_sched%0d_pal.mem", e), t_pal);
|
||||
for (int i=0;i<256;i++) pal[e][i]=t_pal[i];
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_sched%0d_refmap.mem", e), t_rm);
|
||||
for (int i=0;i<FB_WORDS;i++) refmap_ep[e][i]=t_rm[i];
|
||||
end
|
||||
$readmemh("../../data/top_psmct32_raster_demo/sh3_sched_refmap.mem", refmap);
|
||||
$display("[sched] ONLY=%s (only_k=%0d), N_EPOCHS=%0d", only_m, only_k, NEP);
|
||||
if (idx[0][0]===32'bx) begin $display("[tb_top_psmct32_sh3_sched] SKIP — sh3_sched*.mem absent (run gs_make_sh3_scheduler_fixture.py --emit)"); $finish; end
|
||||
|
||||
rst_n=1'b0; core_go=1'b0; repeat(6) @(posedge clk); rst_n=1'b1; repeat(8) @(posedge clk);
|
||||
|
||||
// boot the bootlet (uploads ALL N relocated CLUTs to their distinct CBPs)
|
||||
@(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);
|
||||
wait (feeder_ready_tb==1'b1); repeat(50) @(posedge clk);
|
||||
wr_arm<=1'b1; repeat(40) @(posedge clk);
|
||||
|
||||
// ===== scheduler: iterate the epoch descriptors in dump order (or a single epoch for the isolation check) =====
|
||||
if (only_k<0) begin
|
||||
for (int k=0;k<NEP;k++) begin
|
||||
run_epoch(k);
|
||||
if (k<NEP-1 && scan_en!==1'b0) begin $error("[sched] scanout enabled before the last epoch"); errors++; end
|
||||
end
|
||||
end else begin
|
||||
run_epoch(only_k);
|
||||
end
|
||||
|
||||
// enable scanout only after the final fresh drain
|
||||
scan_en<=1'b1; repeat(20) @(posedge clk);
|
||||
if (only_k<0) begin
|
||||
if (fd_rises<NEP) begin $error("[sched] expected %0d ordered drains: rises=%0d", NEP, fd_rises); errors++; end
|
||||
if (fd_falls<NEP-1)begin $error("[sched] epochs after the first never cleared stale drains (falls=%0d)", fd_falls); errors++; end
|
||||
end else if (fd_rises<1) begin $error("[sched] isolated epoch did not drain: rises=%0d", fd_rises); errors++; end
|
||||
|
||||
// ===== scanout: score final FB vs the composed reference (per-pixel OWNER epoch), overlap separately =====
|
||||
vid_run=1;
|
||||
begin int d=0; while(!vsync && d<300000) begin @(posedge video_clk); d++; end end
|
||||
@(posedge video_clk); while(!vsync) @(posedge video_clk);
|
||||
begin scoring=1; @(posedge video_clk); while(!vsync) @(posedge video_clk); scoring=0; repeat(60) @(posedge emif_clk);
|
||||
if (fb_reads_last!==BEATS_PER_FRAME) begin $error("[sched] scanout beats/frame=%0d exp %0d", fb_reads_last, BEATS_PER_FRAME); errors++; end
|
||||
end
|
||||
if (so_underflow!==0) begin $error("[sched] scanout underflow"); errors++; end
|
||||
if (so_rd_errs !==0) begin $error("[sched] scanout rd_errs=%0d", so_rd_errs); errors++; end
|
||||
if (checked < H_ACT*V_ACT) begin $error("[sched] only %0d px checked (exp >= %0d)", checked, H_ACT*V_ACT); errors++; end
|
||||
|
||||
// oracle (multi-texture correctness): a covered pixel must equal ONE of the COVERING epochs' texels. Each epoch
|
||||
// stores its OWN (tu,tv) per pixel in refmap_ep[k]; we accept if the RTL colour matches any covering epoch's texel
|
||||
// in the <=1-texel neighbourhood (owner-first, then neighbours — handles coverage-edge owner ambiguity between
|
||||
// adjacent-order epochs, exactly the tolerance Ch355 used). multi-epoch pixels (>=2 covering) scored separately as
|
||||
// the accumulation composite proof. clut_bad = an RTL-written pixel (ideal!=0) whose colour is in NO epoch palette
|
||||
// (palettes are pairwise-distinct, so this still proves the pixel came from a real texture+CLUT, not garbage).
|
||||
begin
|
||||
int m_tot,m_ok,o_tot,o_ok,cb,D,ncov; bit mt; m_tot=0;m_ok=0;o_tot=0;o_ok=0;cb=0;
|
||||
for (int y=0;y<H;y++) for (int x=0;x<W;x++) begin
|
||||
int o; logic [23:0] fbc; bit cov; o=y*W+x;
|
||||
cov = (only_k<0) ? refmap[o][31] : refmap_ep[only_k][o][31];
|
||||
if (cov) begin
|
||||
fbc=ideal[o][23:0]; m_tot++; D=9; ncov=0;
|
||||
for (int e=0;e<NEP;e++) begin
|
||||
if (!(only_k>=0 && e!=only_k) && refmap_ep[e][o][31]) begin
|
||||
int tu,tv; tu=(refmap_ep[e][o]>>9)&9'h1FF; tv=refmap_ep[e][o]&9'h1FF; ncov++;
|
||||
for (int rad=0;rad<=1;rad++) for (int du=-rad;du<=rad;du++) for (int dv=-rad;dv<=rad;dv++) begin
|
||||
int ch; ch=(du<0?-du:du); if((dv<0?-dv:dv)>ch) ch=(dv<0?-dv:dv);
|
||||
if (ch==rad && (tu+du)>=0 && (tu+du)<TW && (tv+dv)>=0 && (tv+dv)<TH) begin
|
||||
mt=(fbc===cell_e(e,tu+du,tv+dv)); if (mt && rad<D) D=rad;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if (D<=1) m_ok++;
|
||||
if (only_k<0 && refmap[o][28]) begin o_tot++; if (D<=1) o_ok++; end // multi-epoch (>=2 covering)
|
||||
if (ideal[o]!==32'd0) begin bit f; f=1'b0;
|
||||
for (int e=0;e<NEP;e++) for (int i=0;i<256;i++) if (pal[e][i][23:0]===fbc) f=1'b1;
|
||||
if(!f) cb++; end
|
||||
end
|
||||
end
|
||||
$display("[sched][oracle] ONLY=%s <=1texel ALL=%0d/%0d (%.1f%%) MULTI(>=2)=%0d/%0d (%.1f%%) clut_bad=%0d",
|
||||
only_m, m_ok,m_tot,(m_tot>0)?100.0*m_ok/m_tot:0.0, o_ok,o_tot,(o_tot>0)?100.0*o_ok/o_tot:0.0, cb);
|
||||
if (cb!==0) begin $error("[sched][oracle] ONLY=%s: %0d covered px in NO epoch palette", only_m, cb); errors++; end
|
||||
if (only_k<0) begin
|
||||
if (o_tot<500) begin $error("[sched][oracle] only %0d multi-epoch px — accumulation not exercised", o_tot); errors++; end
|
||||
// MULTI <=1texel is TEXTURE FIDELITY (RTL 11-bit reciprocal LUT precision), NOT accumulation correctness.
|
||||
// It tracks the per-epoch isolated fidelity (ONLY=k gives 93.0/93.3/95.6%); the multi-epoch subset sits at
|
||||
// the same reciprocal floor (~92%). The ACCUMULATION proof is separate and EXACT: compose_sched.py shows
|
||||
// the joint ALL render == the composited ONLY=k isolation dumps 100% BIT-FOR-BIT. So gate this at the
|
||||
// documented reciprocal floor, not an accumulation-implying bar.
|
||||
if (o_tot>0 && (100.0*o_ok/o_tot)<90.0) begin $error("[sched][oracle] MULTI <=1texel %.1f%% < 90%% reciprocal floor", 100.0*o_ok/o_tot); errors++; end
|
||||
end
|
||||
end
|
||||
// FB dump for the composition==isolated check (per-epoch dumps composed externally -> compare vs ALL dump)
|
||||
if ($value$plusargs("FBDUMP=%s", fdump)) begin
|
||||
int fh; fh=$fopen(fdump,"w");
|
||||
for (int y=0;y<H;y++) for (int x=0;x<W;x++) begin int aa; aa=y*STRIDE+x*4; $fwrite(fh,"%08x\n",{fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]}); end
|
||||
$fclose(fh); $display("[sched] dumped RTL FB (%0dx%0d) -> %s", W, H, fdump);
|
||||
end
|
||||
|
||||
$display("[tb_top_psmct32_sh3_sched] checked=%0d beats/frame=%0d (exp %0d) fresh_drains(rise/fall)=%0d/%0d records=%0d underflow=%0b ovf=%0d errors=%0d",
|
||||
checked, fb_reads_last, BEATS_PER_FRAME, fd_rises, fd_falls, feeder_records_w, so_underflow, fbw_ovf, errors);
|
||||
if (errors==0) $display("[tb_top_psmct32_sh3_sched] PASS"); else $display("[tb_top_psmct32_sh3_sched] FAIL");
|
||||
$finish;
|
||||
end
|
||||
initial begin #160000000; $error("[tb_top_psmct32_sh3_sched] TIMEOUT"); $finish; end
|
||||
endmodule : tb_top_psmct32_sh3_sched
|
||||
@@ -0,0 +1,392 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_sh3_sched640 (Ch357 — NATIVE 640x480 LPDDR framebuffer)
|
||||
//
|
||||
// Ch357 = the Ch356 N-texture scheduler on a NATIVE 640x480 LPDDR framebuffer (FBW=10, stride 2560, size 0x12C000,
|
||||
// 80 beats/row, 38400 beats/frame). Draws land at their AUTHENTIC screen coordinates (NO union-origin translation);
|
||||
// the scheduler/flow is UNCHANGED from Ch356, only the FB geometry differs (from sh3_s640_params.vh). Default
|
||||
// N_EPOCHS=3: E0=idx11671 E1=idx19562 E2=idx89761. Proves Codex's Ch357 acceptance:
|
||||
// * preclear EXACTLY once.
|
||||
// * N FRESH cache fills, each fill_done low->high, expected beats/bytes, 0 read errors, epoch CRC (EPk_CRC).
|
||||
// * N lists STREAMED through the feeder staging WRITE PORT (feeder_stg_we/waddr/wdata) — NOT the $readmemh backdoor
|
||||
// (closes the Ch355 sim gap where the write-port sequencing was untested). Each streams the full STG_WORDS so
|
||||
// staging is fully reset per epoch (no stale words), and word0 (the ntris header) is exercised first.
|
||||
// * N FRESH ORDERED drains: epoch 0 low->high (first render); epochs 1..N-1 high->low->high (stale cleared).
|
||||
// * scanout stays disabled until the LAST fresh drain; exact BEATS_PER_FRAME (=ROW_BEATS*H).
|
||||
// * final FB scored vs the independent composed reference using the PER-PIXEL OWNER epoch (exact palette per owner);
|
||||
// multi-epoch (>=2) overlap scored separately as the accumulation proof.
|
||||
// * +ONLY=<k> renders a SINGLE epoch (+FBDUMP dumps its FB) for the composition==isolated bit-for-bit check.
|
||||
// LOCAL/gitignored fixtures; skip-guard if absent.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_sh3_sched640;
|
||||
`include "sh3_s640_params.vh" // FBPXW=640, FBH=480, N_EPOCHS=3, EPk_CRC/EPk_NTRIS/EPk_CBP, TEX_*, ...
|
||||
localparam int W = FBPXW, H = FBH;
|
||||
localparam int STRIDE = W*4; // 2560
|
||||
localparam int ROW_BEATS = STRIDE/32; // 80
|
||||
localparam int FB_BYTES = STRIDE*H; // 1228800 (0x12C000)
|
||||
localparam int FB_WORDS = W*H;
|
||||
localparam int BEATS_PER_FRAME = ROW_BEATS*H; // 38400
|
||||
localparam int H_ACT=W, V_ACT=H, H_BP=32, H_FP=8, V_BP=16, V_FP=8;
|
||||
localparam int H_TOT=H_BP+H_ACT+H_FP, V_TOT=V_BP+V_ACT+V_FP;
|
||||
localparam int NEP = N_EPOCHS; // 3
|
||||
|
||||
// per-epoch expected CRC / records (from the descriptor params)
|
||||
logic [31:0] EP_CRC [0:2]; int EP_REC [0:2];
|
||||
initial begin
|
||||
EP_CRC[0]=EP0_CRC; EP_CRC[1]=EP1_CRC; EP_CRC[2]=EP2_CRC;
|
||||
EP_REC[0]=EP0_NTRIS; EP_REC[1]=EP1_NTRIS; EP_REC[2]=EP2_NTRIS;
|
||||
end
|
||||
|
||||
logic clk=0; always #5 clk=~clk;
|
||||
logic emif_clk=0; always #2 emif_clk=~emif_clk;
|
||||
logic video_clk=0; always #7 video_clk=~video_clk;
|
||||
logic rst_n; logic fb_commit=0; always #13 fb_commit=~fb_commit;
|
||||
int errors; initial errors=0;
|
||||
|
||||
// ===== bram-top (FB_LPDDR_ONLY, AUTOSTART off, STG 2048), H/V_ACTIVE = 384x381 =====
|
||||
logic core_go; logic [7:0] r,g,b; logic hsync,vsync_o,de;
|
||||
logic core_halt,dma_done_seen,frame_seen,raster_overflow,frame_toggle,dma_done_toggle;
|
||||
logic feeder_go_tb, feeder_ready_tb; logic [15:0] feeder_records_w; logic [31:0] feeder_waits_w;
|
||||
logic gs_tex_rd_en_o; logic [31:0] gs_tex_rd_addr_o; logic [31:0] tex_cache_data; logic tex_cache_ready;
|
||||
logic [31:0] tex_cache_hits, tex_bram_hits;
|
||||
logic flush_emit_w; logic [31:0] flush_addr_w; logic [31:0] flush_color32_w; logic [5:0] flush_psm_w;
|
||||
// feeder staging WRITE PORT (streamed per epoch)
|
||||
logic stg_we; logic [11:0] stg_waddr; logic [63:0] stg_wdata;
|
||||
|
||||
top_psmct32_raster_demo_bram #(
|
||||
.H_ACTIVE(FBPXW), .V_ACTIVE(FBH),
|
||||
.VRAM_BYTES(VRAM_BYTES_P), .VRAM_ENABLE_READ2(1'b0), .PSMCT32_SWIZZLE(1'b0),
|
||||
.COMBINED_TAZ(1'b0), .TILE_LOCAL(1'b0), .TILE_COLS(1), .TILE_ROWS(1),
|
||||
.TILE_MULTIPRIM(1'b0), .TILE_PRIM_COUNT(1), .TILE_FIFO_DEPTH(8),
|
||||
.BIN_BUFFER_ENABLE(1'b0), .HEARTBEAT_SPLICE_ENABLE(1'b0),
|
||||
.FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(STG_WORDS), .FEEDER_AUTOSTART(1'b0),
|
||||
.PERSPECTIVE_CORRECT(1'b1), .PERSP_RECIP_IDX_BITS(11),
|
||||
.GRAD_SEQ_DIVIDER(1'b1), .GRAD_DIV_CYCLES(4),
|
||||
.GS_LPDDR_TEX(1'b1), .TEX_VRAM_BASE(TEX_VRAM_BASE), .TEX_CACHE_BYTES(TEX_BYTES),
|
||||
.CLUT_CSM1_ENABLE(1'b1), .FB_LPDDR_ONLY(1'b1)
|
||||
) dut (
|
||||
.clk(clk), .rst_n(rst_n), .core_go(core_go),
|
||||
.r(r), .g(g), .b(b), .hsync(hsync), .vsync(vsync_o), .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),
|
||||
.feeder_stg_we_i(stg_we), .feeder_stg_waddr_i(stg_waddr), .feeder_stg_wdata_i(stg_wdata),
|
||||
.feeder_go_i(feeder_go_tb), .feeder_ready_o(feeder_ready_tb),
|
||||
.feeder_records_o(feeder_records_w), .feeder_waits_o(feeder_waits_w),
|
||||
.flush_emit_o(flush_emit_w), .flush_addr_o(flush_addr_w),
|
||||
.flush_pix16_o(), .flush_color32_o(flush_color32_w), .flush_psm_o(flush_psm_w),
|
||||
.gs_tex_rd_en_o(gs_tex_rd_en_o), .gs_tex_rd_addr_o(gs_tex_rd_addr_o),
|
||||
.tex_cache_data_i(tex_cache_data), .tex_cache_ready_i(tex_cache_ready),
|
||||
.tex_cache_hits_o(tex_cache_hits), .tex_bram_hits_o(tex_bram_hits)
|
||||
);
|
||||
|
||||
// texture cache + behavioral texture LPDDR (RELOADED between fills for each epoch's rebind)
|
||||
logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid; logic [7:0] arlen; logic [2:0] arsize;
|
||||
logic arvalid, arready; logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready;
|
||||
logic fill_start, fill_done; logic [31:0] fill_beats, fill_bytes, tex_rd_errs, fill_crc_w;
|
||||
gs_texture_cache #(.LPDDR_TEX_BASE(LPDDR_TEX_BASE), .TEX_VRAM_BASE(TEX_VRAM_BASE),
|
||||
.TEX_BYTES(TEX_BYTES), .N_BEATS(N_BEATS)) u_cache (
|
||||
.axi_clk(clk), .axi_rst_n(rst_n), .fill_start(fill_start), .fill_done(fill_done),
|
||||
.fill_beats(fill_beats), .fill_bytes(fill_bytes), .rd_errs(tex_rd_errs), .fill_crc(fill_crc_w),
|
||||
.araddr(araddr), .arburst(arburst), .arid(arid), .arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready),
|
||||
.rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready),
|
||||
.sample_clk(clk), .tex_rd_en(gs_tex_rd_en_o), .tex_rd_addr(gs_tex_rd_addr_o),
|
||||
.tex_rd_data(tex_cache_data), .tex_ready(tex_cache_ready)
|
||||
);
|
||||
logic [31:0] lpddr_mem [0:(TEX_BYTES/4)-1]; // reloaded per epoch: tex0, tex1, tex2
|
||||
typedef enum logic [1:0] { S_IDLE, S_WAIT, S_DATA } sstate_t; sstate_t sst; logic [3:0] tdly; int tbeat;
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin sst<=S_IDLE; arready<=0; rvalid<=0; rlast<=0; rresp<=0; rdata<=0; tdly<=0; end
|
||||
else begin arready<=0; rvalid<=0; rlast<=0;
|
||||
case (sst)
|
||||
S_IDLE: if (arvalid) begin arready<=1; tbeat<=(araddr-LPDDR_TEX_BASE)>>5; tdly<=4'd2; sst<=S_WAIT; end
|
||||
S_WAIT: if (tdly==0) sst<=S_DATA; else tdly<=tdly-1'b1;
|
||||
S_DATA: if (rready) begin for (int w=0;w<8;w++) rdata[w*32 +: 32]<=lpddr_mem[tbeat*8+w];
|
||||
rresp<=2'b00; rvalid<=1; rlast<=1; sst<=S_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// render epoch (per scene) + PSMCT32 writer + precleared LPDDR FB
|
||||
logic fb_flush=0, render_inflight=0, feeder_ready_q=0; int eof_count;
|
||||
wire feeder_ready_rise = feeder_ready_tb && !feeder_ready_q;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin render_inflight<=0; feeder_ready_q<=0; fb_flush<=0; eof_count<=0; end
|
||||
else begin feeder_ready_q<=feeder_ready_tb; fb_flush<=1'b0;
|
||||
if (feeder_go_tb && feeder_ready_tb) render_inflight<=1'b1;
|
||||
if (render_inflight && feeder_ready_rise) begin fb_flush<=1'b1; render_inflight<=1'b0; eof_count<=eof_count+1; end
|
||||
end
|
||||
end
|
||||
logic wr_arm=0; logic [255:0] fbw_wdata; logic [31:0] fbw_wstrb, fbw_awaddr; logic fbw_awvalid, fbw_wvalid;
|
||||
logic [31:0] fbw_beats, fbw_ovf, fbw_bresp_err; logic fbw_idle, fbw_drained;
|
||||
gs_lpddr_axi_master #(.FIFO_DEPTH(64), .PIX_BYTES(4)) u_wr (
|
||||
.gs_clk(clk), .gs_rst_n(rst_n), .enable(1'b1),
|
||||
.arm(wr_arm), .canary(1'b0), .fb_base(32'h0), .ctrl_commit(fb_commit),
|
||||
.px_emit(flush_emit_w && (flush_psm_w==6'h00)), .px_addr(flush_addr_w), .px_pix32(flush_color32_w), .flush(fb_flush),
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n),
|
||||
.awaddr(fbw_awaddr), .awlen(), .awsize(), .awburst(), .awid(), .awvalid(fbw_awvalid), .awready(1'b1),
|
||||
.wdata(fbw_wdata), .wstrb(fbw_wstrb), .wlast(), .wvalid(fbw_wvalid), .wready(1'b1),
|
||||
.bvalid(1'b1), .bready(), .bresp(2'b00),
|
||||
.beats_written(fbw_beats), .bursts_issued(), .bresp_err_count(fbw_bresp_err),
|
||||
.fifo_overflow_count(fbw_ovf), .idle(fbw_idle), .frame_drained(fbw_drained)
|
||||
);
|
||||
logic [7:0] fb [0:FB_BYTES-1]; logic [31:0] fb_awlat;
|
||||
always_ff @(posedge emif_clk) begin
|
||||
if (fbw_awvalid) fb_awlat<=fbw_awaddr;
|
||||
if (fbw_wvalid) for (int i=0;i<32;i++) if (fbw_wstrb[i]) begin
|
||||
int aa; aa=fb_awlat+i; if (aa>=0 && aa<FB_BYTES) fb[aa]<=fbw_wdata[i*8 +: 8]; end
|
||||
end
|
||||
logic [31:0] ideal [0:FB_WORDS-1];
|
||||
always_ff @(posedge clk) if (rst_n && flush_emit_w && (flush_psm_w==6'h00)) ideal[flush_addr_w>>2]<=flush_color32_w;
|
||||
|
||||
// FRESH-DRAIN observer: count frame_drained low->high and high->low edges (emif domain).
|
||||
logic fd_q; int fd_rises, fd_falls;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin fd_q<=0; fd_rises<=0; fd_falls<=0; end
|
||||
else begin fd_q<=fbw_drained;
|
||||
if (fbw_drained && !fd_q) fd_rises<=fd_rises+1;
|
||||
if (!fbw_drained && fd_q) fd_falls<=fd_falls+1;
|
||||
end
|
||||
end
|
||||
|
||||
// scanout (384x381), host-gated: enable = scan_en (video_src) AND frame_drained
|
||||
logic scan_en=0; wire so_enable = scan_en & fbw_drained;
|
||||
logic [11:0] px, py; logic vsync, in_win; logic [7:0] so_r, so_g, so_b;
|
||||
logic so_underflow; logic [31:0] so_rd_errs; logic so_line_valid;
|
||||
logic [29:0] so_araddr; logic [1:0] so_arburst; logic [6:0] so_arid; logic [7:0] so_arlen;
|
||||
logic [2:0] so_arsize; logic so_arvalid, so_arready;
|
||||
logic [255:0] so_rdata; logic [1:0] so_rresp; logic so_rlast, so_rvalid, so_rready;
|
||||
gs_lpddr_scanout_lb #(.FB_BASE(30'd0), .STRIDE_BYTES(STRIDE), .ROW_BEATS(ROW_BEATS),
|
||||
.N_ROWS(H), .PSMCT32(1'b1)) u_scan (
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n), .enable(so_enable),
|
||||
.video_clk(video_clk), .frame_start(vsync), .pixel_x(px), .pixel_y(py), .in_window(in_win),
|
||||
.r(so_r), .g(so_g), .b(so_b), .line_valid(so_line_valid), .underflow(so_underflow), .rd_errs(so_rd_errs),
|
||||
.araddr(so_araddr), .arburst(so_arburst), .arid(so_arid), .arlen(so_arlen), .arsize(so_arsize),
|
||||
.arvalid(so_arvalid), .arready(so_arready), .rdata(so_rdata), .rresp(so_rresp),
|
||||
.rlast(so_rlast), .rvalid(so_rvalid), .rready(so_rready)
|
||||
);
|
||||
logic [7:0] rlfsr=8'h3C; always_ff @(posedge emif_clk) rlfsr<={rlfsr[6:0], rlfsr[7]^rlfsr[5]^rlfsr[4]^rlfsr[3]};
|
||||
typedef enum logic [1:0] { R_IDLE, R_WAIT, R_DATA } rst_t; rst_t rst_state;
|
||||
logic [3:0] rdly; logic [29:0] rd_addr_l; int read_beats;
|
||||
logic [2:0] vs_e; wire vs_edge_e = vs_e[1] && !vs_e[2]; int fb_reads, fb_reads_last;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin rst_state<=R_IDLE; so_arready<=0; so_rvalid<=0; so_rlast<=0; so_rresp<=0; so_rdata<=0; rdly<=0;
|
||||
read_beats<=0; vs_e<=0; fb_reads<=0; fb_reads_last<=0; end
|
||||
else begin so_arready<=0; so_rvalid<=0; so_rlast<=0; vs_e<={vs_e[1:0], vsync};
|
||||
if (vs_edge_e) begin fb_reads_last<=fb_reads; fb_reads<=0; end
|
||||
case (rst_state)
|
||||
R_IDLE: if (so_arvalid) begin so_arready<=1; rd_addr_l<=so_araddr; rdly<=rlfsr[2:0]; rst_state<=R_WAIT; end
|
||||
R_WAIT: if (rdly==0) rst_state<=R_DATA; else rdly<=rdly-1'b1;
|
||||
R_DATA: if (so_rready) begin
|
||||
for (int w=0;w<8;w++) begin int aa; aa=rd_addr_l+w*4;
|
||||
so_rdata[w*32 +: 32] <= (aa+3<FB_BYTES) ? {fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]} : 32'd0; end
|
||||
so_rresp<=2'b00; so_rvalid<=1; so_rlast<=1; read_beats<=read_beats+1;
|
||||
if (!vs_edge_e) fb_reads<=fb_reads+1; rst_state<=R_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
logic vid_run=0; logic [11:0] rawx, rawy;
|
||||
always_ff @(posedge video_clk) begin
|
||||
if (!vid_run) begin rawx<=0; rawy<=0; end
|
||||
else if (rawx==H_TOT-1) begin rawx<=0; rawy<=(rawy==V_TOT-1)?12'd0:rawy+1'b1; end
|
||||
else rawx<=rawx+1'b1;
|
||||
end
|
||||
wire active = (rawx>=H_BP)&&(rawx<H_BP+H_ACT)&&(rawy>=V_BP)&&(rawy<V_BP+V_ACT);
|
||||
assign px=active?(rawx-H_BP):12'd0; assign py=(rawy>=V_BP&&rawy<V_BP+V_ACT)?(rawy-V_BP):12'd0;
|
||||
assign in_win=active; assign vsync=vid_run&&(rawx==0)&&(rawy==0);
|
||||
logic [11:0] px_q, py_q; logic inwin_q, run_q;
|
||||
always_ff @(posedge video_clk) begin px_q<=px; py_q<=py; inwin_q<=in_win; run_q<=vid_run; end
|
||||
int checked; initial checked=0; logic scoring=0;
|
||||
always_ff @(posedge video_clk) if (scoring && run_q) begin
|
||||
logic [7:0] er,eg,eb; logic [31:0] w;
|
||||
if (inwin_q) begin int aa; aa=py_q*STRIDE+px_q*4; w={fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]}; er=w[7:0]; eg=w[15:8]; eb=w[23:16]; end
|
||||
else begin er=0; eg=0; eb=0; end
|
||||
checked++;
|
||||
if (so_r!==er||so_g!==eg||so_b!==eb) begin
|
||||
if (errors<12) $error("[scan] px(%0d,%0d) got(%02x,%02x,%02x) exp(%02x,%02x,%02x)", px_q,py_q, so_r,so_g,so_b, er,eg,eb); errors++; end
|
||||
end
|
||||
|
||||
// per-epoch idx/pal (for the oracle) + composed reference (owner epoch in [26:24], multi in [28]) + per-epoch refmaps
|
||||
logic [31:0] idx [0:2][0:(512*512/4)-1]; logic [31:0] pal [0:2][0:255];
|
||||
logic [31:0] refmap [0:FB_WORDS-1]; logic [31:0] refmap_ep [0:2][0:FB_WORDS-1];
|
||||
logic [63:0] stg_buf [0:STG_WORDS-1]; // one epoch's staging list, streamed through the write port
|
||||
// temps: iverilog can't $readmemh into a 2D-array slice, so load flat then copy into the [e] plane
|
||||
logic [31:0] t_idx [0:(512*512/4)-1]; logic [31:0] t_pal [0:255]; logic [31:0] t_rm [0:FB_WORDS-1];
|
||||
|
||||
// ---- stream one epoch's list into the feeder staging via the WRITE PORT (word0 first, full STG reset) ----
|
||||
task automatic stream_list(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/feeder_sh3_s640%0d.mem", k);
|
||||
for (int i=0;i<STG_WORDS;i++) stg_buf[i]=64'd0;
|
||||
$readmemh(fn, stg_buf);
|
||||
@(negedge clk);
|
||||
for (int i=0;i<STG_WORDS;i++) begin
|
||||
stg_we<=1'b1; stg_waddr<=i[11:0]; stg_wdata<=stg_buf[i]; @(negedge clk);
|
||||
end
|
||||
stg_we<=1'b0; stg_waddr<=12'd0; stg_wdata<=64'd0; @(negedge clk);
|
||||
// sanity: header word0 (ntris) landed correctly in the DUT staging
|
||||
if (dut.g_feeder.feeder_stg[0][31:0] !== stg_buf[0][31:0]) begin
|
||||
$error("[sched] epoch %0d: staged word0=%08x exp %08x (write-port mis-slot)", k, dut.g_feeder.feeder_stg[0][31:0], stg_buf[0][31:0]); errors++; end
|
||||
$display("[sched] epoch %0d: streamed %0d words via write port; word0(ntris)=%0d", k, STG_WORDS, dut.g_feeder.feeder_stg[0][31:0]);
|
||||
endtask
|
||||
|
||||
// ---- one-scene runner: GO, wait render+drain, REQUIRE fresh frame_drained (high->low->high, or low->high on first) ----
|
||||
task automatic run_scene(input int k, input int exp_records);
|
||||
int r0, f0, d=0; logic fd_before;
|
||||
r0=fd_rises; f0=fd_falls; fd_before=fbw_drained;
|
||||
@(negedge clk); feeder_go_tb=1'b1; @(negedge clk); feeder_go_tb=1'b0;
|
||||
d=0; while (feeder_ready_tb && d<4000) begin @(posedge clk); d++; end
|
||||
if (feeder_ready_tb!==1'b0) begin $error("[sched] epoch %0d: feeder_ready did not drop after GO", k); errors++; end
|
||||
if (fd_before) begin
|
||||
d=0; while (fd_falls==f0 && d<800000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_falls==f0) begin $error("[sched] epoch %0d: frame_drained never fell from stale high — STALE drain", k); errors++; end
|
||||
end
|
||||
d=0; while (fd_rises<=r0 && d<800000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_rises<=r0) begin $error("[sched] epoch %0d: frame_drained never rose — scene did not drain", k); errors++; end
|
||||
repeat(100) @(posedge clk);
|
||||
if (feeder_records_w!==exp_records) begin $error("[sched] epoch %0d: records_emitted=%0d exp %0d", k, feeder_records_w, exp_records); errors++; end
|
||||
if (fbw_ovf!==0 || fbw_bresp_err!==0) begin $error("[sched] epoch %0d: writer ovf=%0d bresp=%0d", k, fbw_ovf, fbw_bresp_err); errors++; end
|
||||
$display("[sched] epoch %0d: fresh drain (falls %0d->%0d, rises %0d->%0d), records=%0d, ovf=0", k, f0, fd_falls, r0, fd_rises, feeder_records_w);
|
||||
endtask
|
||||
|
||||
task automatic fill_cache(input int k, input logic [31:0] exp_crc);
|
||||
int d=0;
|
||||
@(posedge clk) fill_start<=1'b1; @(posedge clk) fill_start<=1'b0;
|
||||
d=0; while (fill_done && d<1000) begin @(posedge clk); d++; end // wait it drops (busy)
|
||||
d=0; while (!fill_done && d<400000) begin @(posedge clk); d++; end // then rises (done)
|
||||
if (!fill_done) begin $error("[sched] fill %0d: fill_done never rose (rearm failed)", k); errors++; end
|
||||
if (fill_crc_w!==exp_crc) begin $error("[sched] fill %0d: crc=%08x exp %08x", k, fill_crc_w, exp_crc); errors++; end
|
||||
if (fill_beats!==N_BEATS) begin $error("[sched] fill %0d: beats=%0d exp %0d", k, fill_beats, N_BEATS); errors++; end
|
||||
if (fill_bytes!==TEX_BYTES) begin $error("[sched] fill %0d: bytes=%0d exp %0d", k, fill_bytes, TEX_BYTES); errors++; end
|
||||
if (tex_rd_errs!==0) begin $error("[sched] fill %0d: rd_errs=%0d", k, tex_rd_errs); errors++; end
|
||||
$display("[sched] cache fill %0d: fresh done, crc=0x%08x (exp %08x), beats=%0d bytes=%0d rd_errs=0", k, fill_crc_w, exp_crc, fill_beats, fill_bytes);
|
||||
endtask
|
||||
|
||||
// texel lookup in epoch e's palette (module-level arrays, no per-call copy)
|
||||
function automatic logic [23:0] cell_e(input int e, input integer u, input integer v);
|
||||
integer lin; logic [31:0] w; logic [7:0] ix;
|
||||
lin=v*TW+u; w=idx[e][lin/4]; ix=w[(8*(lin%4)) +: 8]; cell_e=pal[e][ix][23:0];
|
||||
endfunction
|
||||
|
||||
// run ONE epoch fully (rebind tex -> fresh fill+CRC -> stream list -> GO -> fresh ordered drain)
|
||||
task automatic run_epoch(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/sh3_s640%0d_tex_lpddr.mem", k);
|
||||
$readmemh(fn, lpddr_mem);
|
||||
fill_cache(k, EP_CRC[k]);
|
||||
stream_list(k);
|
||||
run_scene(k, EP_REC[k]);
|
||||
endtask
|
||||
|
||||
string only_m; string fdump; int only_k; // +ONLY=ALL | +ONLY=<k> ; +FBDUMP=<file>
|
||||
initial begin
|
||||
errors=0; feeder_go_tb=1'b0; fill_start=1'b0; wr_arm=0; scan_en=0; stg_we=0; stg_waddr=0; stg_wdata=0;
|
||||
if (!$value$plusargs("ONLY=%s", only_m)) only_m="ALL";
|
||||
only_k = (only_m=="ALL") ? -1 : only_m.atoi();
|
||||
for (int i=0;i<FB_WORDS;i++) ideal[i]=32'd0;
|
||||
for (int i=0;i<FB_BYTES;i++) fb[i]=8'h00; // PRECLEAR EXACTLY ONCE
|
||||
idx[0][0]='x;
|
||||
for (int e=0;e<NEP;e++) begin
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_s640%0d_idx.mem", e), t_idx);
|
||||
for (int i=0;i<(512*512/4);i++) idx[e][i]=t_idx[i];
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_s640%0d_pal.mem", e), t_pal);
|
||||
for (int i=0;i<256;i++) pal[e][i]=t_pal[i];
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_s640%0d_refmap.mem", e), t_rm);
|
||||
for (int i=0;i<FB_WORDS;i++) refmap_ep[e][i]=t_rm[i];
|
||||
end
|
||||
$readmemh("../../data/top_psmct32_raster_demo/sh3_s640_refmap.mem", refmap);
|
||||
$display("[sched] ONLY=%s (only_k=%0d), N_EPOCHS=%0d", only_m, only_k, NEP);
|
||||
if (idx[0][0]===32'bx) begin $display("[tb_top_psmct32_sh3_sched640] SKIP — sh3_s640*.mem absent (run gs_make_sh3_scheduler_fixture.py --emit)"); $finish; end
|
||||
|
||||
rst_n=1'b0; core_go=1'b0; repeat(6) @(posedge clk); rst_n=1'b1; repeat(8) @(posedge clk);
|
||||
|
||||
// boot the bootlet (uploads ALL N relocated CLUTs to their distinct CBPs)
|
||||
@(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);
|
||||
wait (feeder_ready_tb==1'b1); repeat(50) @(posedge clk);
|
||||
wr_arm<=1'b1; repeat(40) @(posedge clk);
|
||||
|
||||
// ===== scheduler: iterate the epoch descriptors in dump order (or a single epoch for the isolation check) =====
|
||||
if (only_k<0) begin
|
||||
for (int k=0;k<NEP;k++) begin
|
||||
run_epoch(k);
|
||||
if (k<NEP-1 && scan_en!==1'b0) begin $error("[sched] scanout enabled before the last epoch"); errors++; end
|
||||
end
|
||||
end else begin
|
||||
run_epoch(only_k);
|
||||
end
|
||||
|
||||
// enable scanout only after the final fresh drain
|
||||
scan_en<=1'b1; repeat(20) @(posedge clk);
|
||||
if (only_k<0) begin
|
||||
if (fd_rises<NEP) begin $error("[sched] expected %0d ordered drains: rises=%0d", NEP, fd_rises); errors++; end
|
||||
if (fd_falls<NEP-1)begin $error("[sched] epochs after the first never cleared stale drains (falls=%0d)", fd_falls); errors++; end
|
||||
end else if (fd_rises<1) begin $error("[sched] isolated epoch did not drain: rises=%0d", fd_rises); errors++; end
|
||||
|
||||
// ===== scanout: score final FB vs the composed reference (per-pixel OWNER epoch), overlap separately =====
|
||||
vid_run=1;
|
||||
begin int d=0; while(!vsync && d<300000) begin @(posedge video_clk); d++; end end
|
||||
@(posedge video_clk); while(!vsync) @(posedge video_clk);
|
||||
begin scoring=1; @(posedge video_clk); while(!vsync) @(posedge video_clk); scoring=0; repeat(60) @(posedge emif_clk);
|
||||
if (fb_reads_last!==BEATS_PER_FRAME) begin $error("[sched] scanout beats/frame=%0d exp %0d", fb_reads_last, BEATS_PER_FRAME); errors++; end
|
||||
end
|
||||
if (so_underflow!==0) begin $error("[sched] scanout underflow"); errors++; end
|
||||
if (so_rd_errs !==0) begin $error("[sched] scanout rd_errs=%0d", so_rd_errs); errors++; end
|
||||
if (checked < H_ACT*V_ACT) begin $error("[sched] only %0d px checked (exp >= %0d)", checked, H_ACT*V_ACT); errors++; end
|
||||
|
||||
// oracle (multi-texture correctness): a covered pixel must equal ONE of the COVERING epochs' texels. Each epoch
|
||||
// stores its OWN (tu,tv) per pixel in refmap_ep[k]; we accept if the RTL colour matches any covering epoch's texel
|
||||
// in the <=1-texel neighbourhood (owner-first, then neighbours — handles coverage-edge owner ambiguity between
|
||||
// adjacent-order epochs, exactly the tolerance Ch355 used). multi-epoch pixels (>=2 covering) scored separately as
|
||||
// the accumulation composite proof. clut_bad = an RTL-written pixel (ideal!=0) whose colour is in NO epoch palette
|
||||
// (palettes are pairwise-distinct, so this still proves the pixel came from a real texture+CLUT, not garbage).
|
||||
begin
|
||||
int m_tot,m_ok,o_tot,o_ok,cb,D,ncov; bit mt; m_tot=0;m_ok=0;o_tot=0;o_ok=0;cb=0;
|
||||
for (int y=0;y<H;y++) for (int x=0;x<W;x++) begin
|
||||
int o; logic [23:0] fbc; bit cov; o=y*W+x;
|
||||
cov = (only_k<0) ? refmap[o][31] : refmap_ep[only_k][o][31];
|
||||
if (cov) begin
|
||||
fbc=ideal[o][23:0]; m_tot++; D=9; ncov=0;
|
||||
for (int e=0;e<NEP;e++) begin
|
||||
if (!(only_k>=0 && e!=only_k) && refmap_ep[e][o][31]) begin
|
||||
int tu,tv; tu=(refmap_ep[e][o]>>9)&9'h1FF; tv=refmap_ep[e][o]&9'h1FF; ncov++;
|
||||
for (int rad=0;rad<=1;rad++) for (int du=-rad;du<=rad;du++) for (int dv=-rad;dv<=rad;dv++) begin
|
||||
int ch; ch=(du<0?-du:du); if((dv<0?-dv:dv)>ch) ch=(dv<0?-dv:dv);
|
||||
if (ch==rad && (tu+du)>=0 && (tu+du)<TW && (tv+dv)>=0 && (tv+dv)<TH) begin
|
||||
mt=(fbc===cell_e(e,tu+du,tv+dv)); if (mt && rad<D) D=rad;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if (D<=1) m_ok++;
|
||||
if (only_k<0 && refmap[o][28]) begin o_tot++; if (D<=1) o_ok++; end // multi-epoch (>=2 covering)
|
||||
if (ideal[o]!==32'd0) begin bit f; f=1'b0;
|
||||
for (int e=0;e<NEP;e++) for (int i=0;i<256;i++) if (pal[e][i][23:0]===fbc) f=1'b1;
|
||||
if(!f) cb++; end
|
||||
end
|
||||
end
|
||||
$display("[sched][oracle] ONLY=%s <=1texel ALL=%0d/%0d (%.1f%%) MULTI(>=2)=%0d/%0d (%.1f%%) clut_bad=%0d",
|
||||
only_m, m_ok,m_tot,(m_tot>0)?100.0*m_ok/m_tot:0.0, o_ok,o_tot,(o_tot>0)?100.0*o_ok/o_tot:0.0, cb);
|
||||
if (cb!==0) begin $error("[sched][oracle] ONLY=%s: %0d covered px in NO epoch palette", only_m, cb); errors++; end
|
||||
if (only_k<0) begin
|
||||
if (o_tot<500) begin $error("[sched][oracle] only %0d multi-epoch px — accumulation not exercised", o_tot); errors++; end
|
||||
// MULTI <=1texel is TEXTURE FIDELITY (RTL 11-bit reciprocal LUT precision), NOT accumulation correctness.
|
||||
// It tracks the per-epoch isolated fidelity (ONLY=k gives 93.0/93.3/95.6%); the multi-epoch subset sits at
|
||||
// the same reciprocal floor (~92%). The ACCUMULATION proof is separate and EXACT: compose_sched.py shows
|
||||
// the joint ALL render == the composited ONLY=k isolation dumps 100% BIT-FOR-BIT. So gate this at the
|
||||
// documented reciprocal floor, not an accumulation-implying bar.
|
||||
if (o_tot>0 && (100.0*o_ok/o_tot)<90.0) begin $error("[sched][oracle] MULTI <=1texel %.1f%% < 90%% reciprocal floor", 100.0*o_ok/o_tot); errors++; end
|
||||
end
|
||||
end
|
||||
// FB dump for the composition==isolated check (per-epoch dumps composed externally -> compare vs ALL dump)
|
||||
if ($value$plusargs("FBDUMP=%s", fdump)) begin
|
||||
int fh; fh=$fopen(fdump,"w");
|
||||
for (int y=0;y<H;y++) for (int x=0;x<W;x++) begin int aa; aa=y*STRIDE+x*4; $fwrite(fh,"%08x\n",{fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]}); end
|
||||
$fclose(fh); $display("[sched] dumped RTL FB (%0dx%0d) -> %s", W, H, fdump);
|
||||
end
|
||||
|
||||
$display("[tb_top_psmct32_sh3_sched640] checked=%0d beats/frame=%0d (exp %0d) fresh_drains(rise/fall)=%0d/%0d records=%0d underflow=%0b ovf=%0d errors=%0d",
|
||||
checked, fb_reads_last, BEATS_PER_FRAME, fd_rises, fd_falls, feeder_records_w, so_underflow, fbw_ovf, errors);
|
||||
if (errors==0) $display("[tb_top_psmct32_sh3_sched640] PASS"); else $display("[tb_top_psmct32_sh3_sched640] FAIL");
|
||||
$finish;
|
||||
end
|
||||
initial begin #320000000; $error("[tb_top_psmct32_sh3_sched640] TIMEOUT"); $finish; end
|
||||
endmodule : tb_top_psmct32_sh3_sched640
|
||||
@@ -0,0 +1,204 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_sh3_zarb (Ch357 — SHARED-LPDDR arbitration integration sim, randomized backpressure)
|
||||
//
|
||||
// Codex pre-fit gate 1: "Shared-LPDDR integration sim with randomized backpressure. Arbitration is part of Ch357, not
|
||||
// just wiring." This routes gs_lpddr_zc_emit's THREE AXI streams through the REAL de25 arbiters onto ONE shared LPDDR:
|
||||
// * color writes (c_*) -> gs_lpddr_wr_arb port s0 (FB-writer, priority)
|
||||
// * Z writes (z_aw/w/b) -> gs_lpddr_wr_arb port s2 (repurposed tile-Z-flush port)
|
||||
// * Z reads (z_ar/r) -> gs_lpddr_rd_arb port s0
|
||||
// During render only color/Z traffic is active (scanout/texture idle, per gate 2). One behavioral LPDDR serves BOTH the
|
||||
// merged write channel (m_aw/w/b) and the read channel (m_ar/r) with RANDOM backpressure on every channel. Replays the
|
||||
// raster fragment trace (zsched_frags.txt) + an independent clamp16 scoreboard; asserts final color+Z == scoreboard,
|
||||
// suppression, per-epoch ordered drain, disjoint ranges, no deadlock. This proves the arbitration under contention before
|
||||
// wiring the de25 top. Strong-reject primary fixture, FB 256x210.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_sh3_zarb;
|
||||
localparam int FB_PXW=256, FB_H=210;
|
||||
localparam int NPX=FB_PXW*FB_H;
|
||||
localparam [31:0] COLBASE=32'h0000_0000, ZBASE=32'h0014_0000, TEXBASE=32'h0020_0000;
|
||||
localparam int MEMBEATS=(TEXBASE>>5); // one array up to the texture base (0x200000/32 = 65536 beats)
|
||||
|
||||
logic gs_clk=0; always #5 gs_clk=~gs_clk;
|
||||
logic axi_clk=0; always #2 axi_clk=~axi_clk;
|
||||
logic gs_rst_n, axi_rst_n; int errors; initial errors=0;
|
||||
|
||||
logic enable, clear_start, clear_done, frame_drained;
|
||||
logic g_valid, g_ready; logic [11:0] g_x, g_y; logic [15:0] g_zq; logic g_zmsk, g_ztest, g_scene; logic [31:0] g_color;
|
||||
// zc_emit Z AXI
|
||||
logic [31:0] z_araddr; logic [7:0] z_arlen; logic [2:0] z_arsize; logic [1:0] z_arburst; logic z_arvalid, z_arready;
|
||||
logic [255:0] z_rdata; logic [1:0] z_rresp; logic z_rlast, z_rvalid, z_rready;
|
||||
logic [31:0] z_awaddr; logic [7:0] z_awlen; logic [2:0] z_awsize; logic [1:0] z_awburst; logic z_awvalid, z_awready;
|
||||
logic [255:0] z_wdata; logic [31:0] z_wstrb; logic z_wlast, z_wvalid, z_wready; logic z_bvalid, z_bready; logic [1:0] z_bresp;
|
||||
// zc_emit Color AXI
|
||||
logic [31:0] c_awaddr; logic [7:0] c_awlen; logic [2:0] c_awsize; logic [1:0] c_awburst; logic c_awvalid, c_awready;
|
||||
logic [255:0] c_wdata; logic [31:0] c_wstrb; logic c_wlast, c_wvalid, c_wready; logic c_bvalid, c_bready; logic [1:0] c_bresp;
|
||||
logic [31:0] z_beats_read, z_beats_written, c_beats_written, col_ovf, bresp_err; logic idle;
|
||||
|
||||
gs_lpddr_zc_emit #(.COLBASE(COLBASE), .ZBASE(ZBASE), .FB_PXW(FB_PXW), .FB_H(FB_H), .REQ_DEPTH(32), .COL_DEPTH(64)) dut (
|
||||
.gs_clk(gs_clk), .gs_rst_n(gs_rst_n), .enable(enable),
|
||||
.g_valid(g_valid), .g_ready(g_ready), .g_x(g_x), .g_y(g_y), .g_zq(g_zq), .g_zmsk(g_zmsk),
|
||||
.g_ztest(g_ztest), .g_ztst(2'd2), .g_color(g_color), .g_be(4'hF), .g_scene(g_scene),
|
||||
.axi_clk(axi_clk), .axi_rst_n(axi_rst_n), .clear_start(clear_start), .clear_done(clear_done), .frame_drained(frame_drained),
|
||||
.z_araddr(z_araddr), .z_arlen(z_arlen), .z_arsize(z_arsize), .z_arburst(z_arburst), .z_arvalid(z_arvalid), .z_arready(z_arready),
|
||||
.z_rdata(z_rdata), .z_rresp(z_rresp), .z_rlast(z_rlast), .z_rvalid(z_rvalid), .z_rready(z_rready),
|
||||
.z_awaddr(z_awaddr), .z_awlen(z_awlen), .z_awsize(z_awsize), .z_awburst(z_awburst), .z_awvalid(z_awvalid), .z_awready(z_awready),
|
||||
.z_wdata(z_wdata), .z_wstrb(z_wstrb), .z_wlast(z_wlast), .z_wvalid(z_wvalid), .z_wready(z_wready),
|
||||
.z_bvalid(z_bvalid), .z_bready(z_bready), .z_bresp(z_bresp),
|
||||
.c_awaddr(c_awaddr), .c_awlen(c_awlen), .c_awsize(c_awsize), .c_awburst(c_awburst), .c_awvalid(c_awvalid), .c_awready(c_awready),
|
||||
.c_wdata(c_wdata), .c_wstrb(c_wstrb), .c_wlast(c_wlast), .c_wvalid(c_wvalid), .c_wready(c_wready),
|
||||
.c_bvalid(c_bvalid), .c_bready(c_bready), .c_bresp(c_bresp),
|
||||
.z_beats_read(z_beats_read), .z_beats_written(z_beats_written), .c_beats_written(c_beats_written),
|
||||
.col_ovf(col_ovf), .bresp_err(bresp_err), .idle(idle)
|
||||
);
|
||||
|
||||
// ---- write arbiter: s0 = color, s2 = Z, s1/s3 inert ; master -> shared LPDDR write ----
|
||||
logic [29:0] m_awaddr; logic [1:0] m_awburst; logic [6:0] m_awid; logic [7:0] m_awlen; logic [2:0] m_awsize;
|
||||
logic m_awvalid, m_awready; logic [255:0] m_wdata; logic [31:0] m_wstrb; logic m_wlast, m_wvalid, m_wready;
|
||||
logic m_bvalid, m_bready; logic [1:0] m_bresp;
|
||||
gs_lpddr_wr_arb u_wr_arb (
|
||||
.clk(axi_clk), .rst_n(axi_rst_n),
|
||||
.s0_awaddr(c_awaddr[29:0]), .s0_awburst(c_awburst), .s0_awid(7'd0), .s0_awlen(c_awlen), .s0_awsize(c_awsize),
|
||||
.s0_awvalid(c_awvalid), .s0_awready(c_awready), .s0_wdata(c_wdata), .s0_wstrb(c_wstrb), .s0_wlast(c_wlast),
|
||||
.s0_wvalid(c_wvalid), .s0_wready(c_wready), .s0_bresp(c_bresp), .s0_bvalid(c_bvalid), .s0_bready(c_bready),
|
||||
.s1_awaddr(30'd0), .s1_awburst(2'b01), .s1_awid(7'd1), .s1_awlen(8'd0), .s1_awsize(3'b101),
|
||||
.s1_awvalid(1'b0), .s1_awready(), .s1_wdata(256'd0), .s1_wstrb(32'd0), .s1_wlast(1'b0),
|
||||
.s1_wvalid(1'b0), .s1_wready(), .s1_bresp(), .s1_bvalid(), .s1_bready(1'b0),
|
||||
.s2_awaddr(z_awaddr[29:0]), .s2_awburst(z_awburst), .s2_awid(7'd2), .s2_awlen(z_awlen), .s2_awsize(z_awsize),
|
||||
.s2_awvalid(z_awvalid), .s2_awready(z_awready), .s2_wdata(z_wdata), .s2_wstrb(z_wstrb), .s2_wlast(z_wlast),
|
||||
.s2_wvalid(z_wvalid), .s2_wready(z_wready), .s2_bresp(z_bresp), .s2_bvalid(z_bvalid), .s2_bready(z_bready),
|
||||
.s3_awaddr(30'd0), .s3_awburst(2'b01), .s3_awid(7'd3), .s3_awlen(8'd0), .s3_awsize(3'b101),
|
||||
.s3_awvalid(1'b0), .s3_awready(), .s3_wdata(256'd0), .s3_wstrb(32'd0), .s3_wlast(1'b0),
|
||||
.s3_wvalid(1'b0), .s3_wready(), .s3_bresp(), .s3_bvalid(), .s3_bready(1'b0),
|
||||
.m_awaddr(m_awaddr), .m_awburst(m_awburst), .m_awid(m_awid), .m_awlen(m_awlen), .m_awsize(m_awsize),
|
||||
.m_awvalid(m_awvalid), .m_awready(m_awready), .m_wdata(m_wdata), .m_wstrb(m_wstrb), .m_wlast(m_wlast),
|
||||
.m_wvalid(m_wvalid), .m_wready(m_wready), .m_bvalid(m_bvalid), .m_bready(m_bready), .m_bresp(m_bresp)
|
||||
);
|
||||
|
||||
// ---- read arbiter: s0 = Z read ; s1/s2/s3 inert (scanout/tex idle during render) ; master -> shared LPDDR read ----
|
||||
logic [29:0] m_araddr; logic [1:0] m_arburst; logic [6:0] m_arid; logic [7:0] m_arlen; logic [2:0] m_arsize;
|
||||
logic m_arvalid, m_arready; logic [255:0] m_rdata; logic [1:0] m_rresp; logic m_rlast, m_rvalid, m_rready;
|
||||
gs_lpddr_rd_arb u_rd_arb (
|
||||
.clk(axi_clk), .rst_n(axi_rst_n),
|
||||
.s0_araddr(z_araddr[29:0]), .s0_arburst(z_arburst), .s0_arid(7'd0), .s0_arlen(z_arlen), .s0_arsize(z_arsize),
|
||||
.s0_arvalid(z_arvalid), .s0_arready(z_arready), .s0_rdata(z_rdata), .s0_rresp(z_rresp), .s0_rlast(z_rlast),
|
||||
.s0_rvalid(z_rvalid), .s0_rready(z_rready),
|
||||
.s1_araddr(30'd0), .s1_arburst(2'b01), .s1_arid(7'd1), .s1_arlen(8'd0), .s1_arsize(3'b101),
|
||||
.s1_arvalid(1'b0), .s1_arready(), .s1_rdata(), .s1_rresp(), .s1_rlast(), .s1_rvalid(), .s1_rready(1'b0),
|
||||
.s2_araddr(30'd0), .s2_arburst(2'b01), .s2_arid(7'd2), .s2_arlen(8'd0), .s2_arsize(3'b101),
|
||||
.s2_arvalid(1'b0), .s2_arready(), .s2_rdata(), .s2_rresp(), .s2_rlast(), .s2_rvalid(), .s2_rready(1'b0),
|
||||
.s3_araddr(30'd0), .s3_arburst(2'b01), .s3_arid(7'd3), .s3_arlen(8'd0), .s3_arsize(3'b101),
|
||||
.s3_arvalid(1'b0), .s3_arready(), .s3_rdata(), .s3_rresp(), .s3_rlast(), .s3_rvalid(), .s3_rready(1'b0),
|
||||
.m_araddr(m_araddr), .m_arburst(m_arburst), .m_arid(m_arid), .m_arlen(m_arlen), .m_arsize(m_arsize),
|
||||
.m_arvalid(m_arvalid), .m_arready(m_arready), .m_rdata(m_rdata), .m_rresp(m_rresp), .m_rlast(m_rlast),
|
||||
.m_rvalid(m_rvalid), .m_rready(m_rready)
|
||||
);
|
||||
|
||||
logic [15:0] lf=16'hF00D; always_ff @(posedge axi_clk) lf<={lf[14:0], lf[15]^lf[13]^lf[12]^lf[10]};
|
||||
|
||||
// ---- ONE shared behavioral LPDDR (write + read channels), address-decoded, random backpressure ----
|
||||
logic [255:0] shmem [0:MEMBEATS-1];
|
||||
logic [29:0] wa; logic [255:0] wd; logic [31:0] ws; logic aw_s, w_s; logic [3:0] bd; logic bp;
|
||||
logic [29:0] ra; logic rp; logic [3:0] rd_dly;
|
||||
always_ff @(posedge axi_clk or negedge axi_rst_n) begin
|
||||
if(!axi_rst_n) begin
|
||||
m_awready<=0; m_wready<=0; m_bvalid<=0; m_bresp<=0; aw_s<=0; w_s<=0; bp<=0; bd<=0;
|
||||
m_arready<=0; m_rvalid<=0; m_rlast<=0; m_rresp<=0; m_rdata<=0; rp<=0; rd_dly<=0;
|
||||
end else begin
|
||||
// write channel
|
||||
m_awready<=(!aw_s)?lf[0]:1'b0; m_wready<=(!w_s)?lf[1]:1'b0;
|
||||
if(m_awvalid&&m_awready) begin wa<=m_awaddr; aw_s<=1; m_awready<=0; end
|
||||
if(m_wvalid&&m_wready) begin wd<=m_wdata; ws<=m_wstrb; w_s<=1; m_wready<=0; end
|
||||
if(aw_s&&w_s&&!bp&&!m_bvalid) begin
|
||||
for(int b=0;b<32;b++) if(ws[b]) shmem[wa>>5][b*8+:8]<=wd[b*8+:8];
|
||||
bp<=1; bd<={1'b0,lf[4:2]};
|
||||
end
|
||||
if(bp&&!m_bvalid) begin if(bd==0) begin m_bvalid<=1;m_bresp<=0;bp<=0;aw_s<=0;w_s<=0; end else bd<=bd-1; end
|
||||
if(m_bvalid&&m_bready) m_bvalid<=0;
|
||||
// read channel
|
||||
m_arready<=(!rp&&!m_rvalid)?lf[8]:1'b0;
|
||||
if(m_arvalid&&m_arready) begin ra<=m_araddr; rp<=1; rd_dly<={1'b0,lf[7:5]}; m_arready<=0; end
|
||||
if(rp&&!m_rvalid) begin if(rd_dly==0) begin m_rdata<=shmem[ra>>5]; m_rresp<=0; m_rvalid<=1; m_rlast<=1; rp<=0; end else rd_dly<=rd_dly-1; end
|
||||
if(m_rvalid&&m_rready) begin m_rvalid<=0; m_rlast<=0; end
|
||||
end
|
||||
end
|
||||
|
||||
// disjoint-range monitors on the MERGED master streams
|
||||
always_ff @(posedge axi_clk) if(axi_rst_n) begin
|
||||
if(m_awvalid) begin
|
||||
if(!((m_awaddr<30'(COLBASE+18288*32)) || (m_awaddr>=30'(ZBASE) && m_awaddr<30'(ZBASE+13440*32))))
|
||||
begin $error("[zarb] merged AW %h not in color/Z range",m_awaddr); errors++; end
|
||||
end
|
||||
if(m_arvalid && (m_araddr<30'(ZBASE) || m_araddr>=30'(ZBASE+13440*32))) begin $error("[zarb] merged AR %h not in Z range",m_araddr); errors++; end
|
||||
end
|
||||
|
||||
// ---- scoreboard + trace feed (same clamp16 persistent-Z model as the ROP gate) ----
|
||||
logic [15:0] sb_z [0:NPX-1]; logic [31:0] sb_col [0:NPX-1]; logic sb_seen [0:NPX-1];
|
||||
function automatic logic [15:0] cl16(input logic [31:0] z); cl16=(|z[31:16])?16'hFFFF:z[15:0]; endfunction
|
||||
int nfed;
|
||||
task automatic feed_frag(input int ep, input int x, input int y, input int z, input logic [31:0] col);
|
||||
int o; logic [15:0] zq; logic pass;
|
||||
@(negedge gs_clk); g_valid<=1; g_scene<=0; g_x<=x[11:0]; g_y<=y[11:0]; g_zq<=cl16(z); g_zmsk<=0; g_ztest<=1; g_color<=col;
|
||||
@(posedge gs_clk); while(!(g_valid && g_ready)) @(posedge gs_clk);
|
||||
@(negedge gs_clk); g_valid<=0;
|
||||
o=y*FB_PXW+x; zq=cl16(z); pass=(zq>=sb_z[o]);
|
||||
if(pass) begin sb_col[o]=col; sb_seen[o]=1; sb_z[o]=zq; end
|
||||
nfed++;
|
||||
if(lf[7]) repeat(1+lf[1:0]) @(posedge gs_clk);
|
||||
endtask
|
||||
task automatic send_marker();
|
||||
@(negedge gs_clk); g_valid<=1; g_scene<=1; @(posedge gs_clk);
|
||||
while(!(g_valid && g_ready)) @(posedge gs_clk);
|
||||
@(negedge gs_clk); g_valid<=0; g_scene<=0;
|
||||
endtask
|
||||
|
||||
int fh, r, ep, x, y, z, cep, drains; logic [31:0] col;
|
||||
initial begin
|
||||
errors=0; enable=0; clear_start=0; g_valid=0; g_scene=0; g_x=0; g_y=0; g_zq=0; g_zmsk=0; g_ztest=1; g_color=0; nfed=0; drains=0;
|
||||
for(int i=0;i<NPX;i++) begin sb_z[i]=16'h0000; sb_col[i]=32'd0; sb_seen[i]=1'b0; end
|
||||
for(int b=0;b<MEMBEATS;b++) shmem[b]=256'd0; // host preclears color+Z (Z re-precleared by clear_start)
|
||||
gs_rst_n=0; axi_rst_n=0; repeat(6) @(posedge axi_clk); gs_rst_n=1; axi_rst_n=1; repeat(4) @(posedge axi_clk);
|
||||
enable=1;
|
||||
@(negedge axi_clk) clear_start=1; @(negedge axi_clk) clear_start=0;
|
||||
begin int g=0; while(!clear_done && g<600000) begin @(posedge axi_clk); g++; end end
|
||||
if(!clear_done) begin $error("[zarb] Z preclear timeout"); errors++; end
|
||||
|
||||
fh=$fopen("zsched_frags.txt","r");
|
||||
if(fh==0) begin $display("[tb_top_psmct32_sh3_zarb] SKIP — zsched_frags.txt absent (run make tb_top_psmct32_sh3_zsched)"); $finish; end
|
||||
cep=0;
|
||||
while(!$feof(fh)) begin
|
||||
r=$fscanf(fh, "%d %d %d %d %h\n", ep, x, y, z, col);
|
||||
if(r==5) begin
|
||||
if(ep!=cep) begin
|
||||
send_marker();
|
||||
begin int g=0; while(!frame_drained && g<1000000) begin @(posedge axi_clk); g++; end end
|
||||
if(!frame_drained) begin $error("[zarb] epoch %0d: frame_drained never rose",cep); errors++; end else drains++;
|
||||
cep=ep;
|
||||
end
|
||||
feed_frag(ep, x, y, z, col);
|
||||
end else r=$fgetc(fh);
|
||||
end
|
||||
$fclose(fh);
|
||||
send_marker();
|
||||
begin int g=0; while(!frame_drained && g<1000000) begin @(posedge axi_clk); g++; end end
|
||||
if(!frame_drained) begin $error("[zarb] final: frame_drained never rose"); errors++; end else drains++;
|
||||
repeat(80) @(posedge axi_clk);
|
||||
|
||||
// final: shared LPDDR (via the merged/arbitrated path) == scoreboard
|
||||
for(int o=0;o<NPX;o++) begin
|
||||
logic [15:0] zs; int zb, zl; zb=(ZBASE + o*2)>>5; zl=o&15; zs=shmem[zb][zl*16+:16];
|
||||
if(zs!==sb_z[o]) begin if(errors<12)$error("[zarb] Z px%0d=%04x exp %04x",o,zs,sb_z[o]);errors++; end
|
||||
end
|
||||
for(int o=0;o<NPX;o++) begin
|
||||
logic [31:0] cs; cs=shmem[o>>3][(o[2:0])*32+:32]; // COLBASE=0
|
||||
if(sb_seen[o]) begin if(cs!==sb_col[o]) begin if(errors<12)$error("[zarb] COLOR px%0d=%08x exp %08x",o,cs,sb_col[o]);errors++; end end
|
||||
else begin if(cs!==32'd0) begin if(errors<12)$error("[zarb] px%0d never-passed color=%08x (suppress)",o,cs);errors++; end end
|
||||
end
|
||||
if(col_ovf!==0) begin $error("[zarb] col_ovf=%0d",col_ovf);errors++; end
|
||||
if(bresp_err!==0)begin $error("[zarb] bresp_err=%0d",bresp_err);errors++; end
|
||||
$display("[zarb] fed=%0d drains=%0d z_wr=%0d c_wr=%0d col_ovf=%0d bresp_err=%0d errors=%0d (COL 0x%0h..0x%0h, Z 0x%0h..0x%0h)",
|
||||
nfed, drains, z_beats_written, c_beats_written, col_ovf, bresp_err, errors, COLBASE, COLBASE+18288*32, ZBASE, ZBASE+13440*32);
|
||||
if(errors==0) $display("[tb_top_psmct32_sh3_zarb] PASS"); else $display("[tb_top_psmct32_sh3_zarb] FAIL");
|
||||
$finish;
|
||||
end
|
||||
initial begin #500000000; $error("[tb_top_psmct32_sh3_zarb] TIMEOUT"); $finish; end
|
||||
endmodule : tb_top_psmct32_sh3_zarb
|
||||
@@ -0,0 +1,423 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_sh3_zint (Ch357 — REAL-RASTER persistent-Z board integration sim)
|
||||
//
|
||||
// Codex's pre-fit gate: prove the persistent-Z ROP through the SAME path the de25 top wires (GS_SH3_LPDDR_FB_Z):
|
||||
// REAL demo (feeder + raster, authz sh3_zsched scene) -> gs_lpddr_zc_emit -> REAL de25 arbiters
|
||||
// (gs_lpddr_wr_arb s0=color / s2=Z-write ; gs_lpddr_rd_arb s3=Z-read) -> ONE shared behavioral LPDDR
|
||||
// with randomized backpressure. Unlike tb_top_psmct32_sh3_zarb (which fed a handshake-throttled trace),
|
||||
// the raster CANNOT honor g_ready, so this is the gate that catches request-FIFO fragment DROPS.
|
||||
//
|
||||
// Asserts (Codex list):
|
||||
// * final LPDDR color + Z == independent clamp16 persistent-Z scoreboard over the ACTUAL emitted fragments;
|
||||
// * zero fragment drops (g_valid && !g_ready) — the whole reason this sim exists;
|
||||
// * exactly one ordered scene marker (sh3_fb_flush) per accepted GO; no fragment after a marker within a scene;
|
||||
// * Z cache invalidated (clear_done) after preclear and before the first GO; Z persists across epochs;
|
||||
// * a FRESH ordered frame_drained per epoch; scanout never referenced during render (rd_arb s0/s1/s2 idle);
|
||||
// * merged AW only in color/Z ranges, merged AR only in Z range (disjoint-map monitor).
|
||||
// LOCAL/gitignored fixtures; skip-guard if absent.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_sh3_zint;
|
||||
`include "sh3_zsched_params.vh" // FBPXW=384, FBH=381, N_EPOCHS=3, EPk_CRC/EPk_NTRIS, TEX_*, ...
|
||||
localparam int W = FBPXW, H = FBH;
|
||||
localparam int NPX = W*H;
|
||||
localparam int NEP = N_EPOCHS;
|
||||
localparam [31:0] COLBASE = 32'h0000_0000, ZBASE = 32'h0014_0000;
|
||||
localparam int COL_TOP = NPX*4; // color region byte-size (disjoint below ZBASE)
|
||||
localparam int Z_TOP = NPX*2; // Z region byte-size
|
||||
localparam int MEMBEATS = 65536; // shared LPDDR: covers ZBASE + Z_TOP
|
||||
|
||||
// per-epoch expected CRC / records
|
||||
logic [31:0] EP_CRC [0:2]; int EP_REC [0:2];
|
||||
initial begin
|
||||
EP_CRC[0]=EP0_CRC; EP_CRC[1]=EP1_CRC; EP_CRC[2]=EP2_CRC;
|
||||
EP_REC[0]=EP0_NTRIS; EP_REC[1]=EP1_NTRIS; EP_REC[2]=EP2_NTRIS;
|
||||
end
|
||||
|
||||
logic clk=0; always #5 clk=~clk;
|
||||
logic emif_clk=0; always #2 emif_clk=~emif_clk;
|
||||
logic rst_n; logic fb_commit=0; always #13 fb_commit=~fb_commit;
|
||||
int errors; initial errors=0;
|
||||
|
||||
// clamp16 (vendored PCSX2 PSMZ16S source-Z saturation)
|
||||
function automatic logic [15:0] cl16(input logic [31:0] z); cl16=(|z[31:16])?16'hFFFF:z[15:0]; endfunction
|
||||
|
||||
// ===== bram-top (FB_LPDDR_ONLY, AUTOSTART off), 384x381 =====
|
||||
logic core_go; logic [7:0] r,g,b; logic hsync,vsync_o,de;
|
||||
logic core_halt,dma_done_seen,frame_seen,raster_overflow,frame_toggle,dma_done_toggle;
|
||||
logic feeder_go_tb, feeder_ready_tb; logic [15:0] feeder_records_w; logic [31:0] feeder_waits_w;
|
||||
logic gs_tex_rd_en_o; logic [31:0] gs_tex_rd_addr_o; logic [31:0] tex_cache_data; logic tex_cache_ready;
|
||||
logic [31:0] tex_cache_hits, tex_bram_hits;
|
||||
logic flush_emit_w; logic [31:0] flush_addr_w; logic [31:0] flush_color32_w; logic [5:0] flush_psm_w;
|
||||
logic [11:0] flush_x_w, flush_y_w; logic [31:0] flush_z_w;
|
||||
logic stg_we; logic [11:0] stg_waddr; logic [63:0] stg_wdata;
|
||||
|
||||
top_psmct32_raster_demo_bram #(
|
||||
.H_ACTIVE(FBPXW), .V_ACTIVE(FBH),
|
||||
.VRAM_BYTES(VRAM_BYTES_P), .VRAM_ENABLE_READ2(1'b0), .PSMCT32_SWIZZLE(1'b0),
|
||||
.COMBINED_TAZ(1'b0), .TILE_LOCAL(1'b0), .TILE_COLS(1), .TILE_ROWS(1),
|
||||
.TILE_MULTIPRIM(1'b0), .TILE_PRIM_COUNT(1), .TILE_FIFO_DEPTH(8),
|
||||
.BIN_BUFFER_ENABLE(1'b0), .HEARTBEAT_SPLICE_ENABLE(1'b0),
|
||||
.FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(STG_WORDS), .FEEDER_AUTOSTART(1'b0),
|
||||
.PERSPECTIVE_CORRECT(1'b1), .PERSP_RECIP_IDX_BITS(11),
|
||||
.GRAD_SEQ_DIVIDER(1'b1), .GRAD_DIV_CYCLES(4),
|
||||
.GS_LPDDR_TEX(1'b1), .TEX_VRAM_BASE(TEX_VRAM_BASE), .TEX_CACHE_BYTES(TEX_BYTES),
|
||||
.CLUT_CSM1_ENABLE(1'b1), .FB_LPDDR_ONLY(1'b1)
|
||||
) dut (
|
||||
.clk(clk), .rst_n(rst_n), .core_go(core_go),
|
||||
.r(r), .g(g), .b(b), .hsync(hsync), .vsync(vsync_o), .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),
|
||||
.feeder_stg_we_i(stg_we), .feeder_stg_waddr_i(stg_waddr), .feeder_stg_wdata_i(stg_wdata),
|
||||
.feeder_go_i(feeder_go_tb), .feeder_ready_o(feeder_ready_tb),
|
||||
.feeder_records_o(feeder_records_w), .feeder_waits_o(feeder_waits_w),
|
||||
.flush_emit_o(flush_emit_w), .flush_addr_o(flush_addr_w),
|
||||
.flush_pix16_o(), .flush_color32_o(flush_color32_w), .flush_psm_o(flush_psm_w),
|
||||
.flush_x_o(flush_x_w), .flush_y_o(flush_y_w), .flush_z_o(flush_z_w),
|
||||
.gs_tex_rd_en_o(gs_tex_rd_en_o), .gs_tex_rd_addr_o(gs_tex_rd_addr_o),
|
||||
.tex_cache_data_i(tex_cache_data), .tex_cache_ready_i(tex_cache_ready),
|
||||
.tex_cache_hits_o(tex_cache_hits), .tex_bram_hits_o(tex_bram_hits)
|
||||
);
|
||||
|
||||
// texture cache + behavioral texture LPDDR (reloaded per epoch)
|
||||
logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid; logic [7:0] arlen; logic [2:0] arsize;
|
||||
logic arvalid, arready; logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready;
|
||||
logic fill_start, fill_done; logic [31:0] fill_beats, fill_bytes, tex_rd_errs, fill_crc_w;
|
||||
gs_texture_cache #(.LPDDR_TEX_BASE(LPDDR_TEX_BASE), .TEX_VRAM_BASE(TEX_VRAM_BASE),
|
||||
.TEX_BYTES(TEX_BYTES), .N_BEATS(N_BEATS)) u_cache (
|
||||
.axi_clk(clk), .axi_rst_n(rst_n), .fill_start(fill_start), .fill_done(fill_done),
|
||||
.fill_beats(fill_beats), .fill_bytes(fill_bytes), .rd_errs(tex_rd_errs), .fill_crc(fill_crc_w),
|
||||
.araddr(araddr), .arburst(arburst), .arid(arid), .arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready),
|
||||
.rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready),
|
||||
.sample_clk(clk), .tex_rd_en(gs_tex_rd_en_o), .tex_rd_addr(gs_tex_rd_addr_o),
|
||||
.tex_rd_data(tex_cache_data), .tex_ready(tex_cache_ready)
|
||||
);
|
||||
logic [31:0] lpddr_mem [0:(TEX_BYTES/4)-1];
|
||||
typedef enum logic [1:0] { S_IDLE, S_WAIT, S_DATA } sstate_t; sstate_t sst; logic [3:0] tdly; int tbeat;
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin sst<=S_IDLE; arready<=0; rvalid<=0; rlast<=0; rresp<=0; rdata<=0; tdly<=0; end
|
||||
else begin arready<=0; rvalid<=0; rlast<=0;
|
||||
case (sst)
|
||||
S_IDLE: if (arvalid) begin arready<=1; tbeat<=(araddr-LPDDR_TEX_BASE)>>5; tdly<=4'd2; sst<=S_WAIT; end
|
||||
S_WAIT: if (tdly==0) sst<=S_DATA; else tdly<=tdly-1'b1;
|
||||
S_DATA: if (rready) begin for (int w=0;w<8;w++) rdata[w*32 +: 32]<=lpddr_mem[tbeat*8+w];
|
||||
rresp<=2'b00; rvalid<=1; rlast<=1; sst<=S_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// ===== render epoch: one ordered scene marker (sh3_fb_flush) per accepted GO =====
|
||||
logic fb_flush=0, render_inflight=0, feeder_ready_q=0; int eof_count;
|
||||
wire feeder_ready_rise = feeder_ready_tb && !feeder_ready_q;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin render_inflight<=0; feeder_ready_q<=0; fb_flush<=0; eof_count<=0; end
|
||||
else begin feeder_ready_q<=feeder_ready_tb; fb_flush<=1'b0;
|
||||
if (feeder_go_tb && feeder_ready_tb) render_inflight<=1'b1;
|
||||
if (render_inflight && feeder_ready_rise) begin fb_flush<=1'b1; render_inflight<=1'b0; eof_count<=eof_count+1; end
|
||||
end
|
||||
end
|
||||
|
||||
// ===== gs_lpddr_zc_emit — Z-then-color emit, fed by the REAL raster (NO handshake) =====
|
||||
logic zc_enable, clear_start, clear_done, frame_drained;
|
||||
wire [15:0] g_zq = cl16(flush_z_w);
|
||||
wire frag_v = flush_emit_w && (flush_psm_w==6'h00);
|
||||
wire g_valid = frag_v || fb_flush;
|
||||
wire g_scene = fb_flush;
|
||||
wire [11:0] g_x = flush_x_w, g_y = flush_y_w;
|
||||
wire [31:0] g_color = flush_color32_w;
|
||||
wire g_ready;
|
||||
// Z AXI
|
||||
logic [31:0] z_araddr; logic [7:0] z_arlen; logic [2:0] z_arsize; logic [1:0] z_arburst; logic z_arvalid, z_arready;
|
||||
logic [255:0] z_rdata; logic [1:0] z_rresp; logic z_rlast, z_rvalid, z_rready;
|
||||
logic [31:0] z_awaddr; logic [7:0] z_awlen; logic [2:0] z_awsize; logic [1:0] z_awburst; logic z_awvalid, z_awready;
|
||||
logic [255:0] z_wdata; logic [31:0] z_wstrb; logic z_wlast, z_wvalid, z_wready; logic z_bvalid, z_bready; logic [1:0] z_bresp;
|
||||
// Color AXI
|
||||
logic [31:0] c_awaddr; logic [7:0] c_awlen; logic [2:0] c_awsize; logic [1:0] c_awburst; logic c_awvalid, c_awready;
|
||||
logic [255:0] c_wdata; logic [31:0] c_wstrb; logic c_wlast, c_wvalid, c_wready; logic c_bvalid, c_bready; logic [1:0] c_bresp;
|
||||
logic [31:0] z_beats_read, z_beats_written, c_beats_written, col_ovf, bresp_err; logic zc_idle;
|
||||
gs_lpddr_zc_emit #(.COLBASE(COLBASE), .ZBASE(ZBASE), .FB_PXW(W), .FB_H(H), .REQ_DEPTH(1024), .COL_DEPTH(128)) u_zc (
|
||||
.gs_clk(clk), .gs_rst_n(rst_n), .enable(zc_enable),
|
||||
.g_valid(g_valid), .g_ready(g_ready), .g_x(g_x), .g_y(g_y), .g_zq(g_zq), .g_zmsk(1'b0),
|
||||
.g_ztest(1'b1), .g_ztst(2'd2), .g_color(g_color), .g_be(4'hF), .g_scene(g_scene),
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n), .clear_start(clear_start), .clear_done(clear_done), .frame_drained(frame_drained),
|
||||
.z_araddr(z_araddr), .z_arlen(z_arlen), .z_arsize(z_arsize), .z_arburst(z_arburst), .z_arvalid(z_arvalid), .z_arready(z_arready),
|
||||
.z_rdata(z_rdata), .z_rresp(z_rresp), .z_rlast(z_rlast), .z_rvalid(z_rvalid), .z_rready(z_rready),
|
||||
.z_awaddr(z_awaddr), .z_awlen(z_awlen), .z_awsize(z_awsize), .z_awburst(z_awburst), .z_awvalid(z_awvalid), .z_awready(z_awready),
|
||||
.z_wdata(z_wdata), .z_wstrb(z_wstrb), .z_wlast(z_wlast), .z_wvalid(z_wvalid), .z_wready(z_wready),
|
||||
.z_bvalid(z_bvalid), .z_bready(z_bready), .z_bresp(z_bresp),
|
||||
.c_awaddr(c_awaddr), .c_awlen(c_awlen), .c_awsize(c_awsize), .c_awburst(c_awburst), .c_awvalid(c_awvalid), .c_awready(c_awready),
|
||||
.c_wdata(c_wdata), .c_wstrb(c_wstrb), .c_wlast(c_wlast), .c_wvalid(c_wvalid), .c_wready(c_wready),
|
||||
.c_bvalid(c_bvalid), .c_bready(c_bready), .c_bresp(c_bresp),
|
||||
.z_beats_read(z_beats_read), .z_beats_written(z_beats_written), .c_beats_written(c_beats_written),
|
||||
.col_ovf(col_ovf), .bresp_err(bresp_err), .idle(zc_idle)
|
||||
);
|
||||
|
||||
// fragment-drop guard (design clk): the request FIFO silently drops when g_ready is low
|
||||
int g_drops; int frags_fed;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin g_drops<=0; frags_fed<=0; end
|
||||
else begin
|
||||
if (frag_v && !g_ready) g_drops<=g_drops+1;
|
||||
if (frag_v && g_ready) frags_fed<=frags_fed+1;
|
||||
end
|
||||
end
|
||||
// clear ordering: clear_done must rise before the first GO. no psm0 fragment before clear_done.
|
||||
logic saw_clear_done=0, first_go_done=0; int pre_clear_frags;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin saw_clear_done<=0; pre_clear_frags<=0; end
|
||||
else begin
|
||||
if (clear_done) saw_clear_done<=1;
|
||||
if (frag_v && !saw_clear_done) pre_clear_frags<=pre_clear_frags+1;
|
||||
end
|
||||
end
|
||||
|
||||
// ===== REAL de25 write arbiter: s0=color, s2=Z-write ; s1/s3 inert =====
|
||||
logic [29:0] m_awaddr; logic [1:0] m_awburst; logic [6:0] m_awid; logic [7:0] m_awlen; logic [2:0] m_awsize;
|
||||
logic m_awvalid, m_awready; logic [255:0] m_wdata; logic [31:0] m_wstrb; logic m_wlast, m_wvalid, m_wready;
|
||||
logic m_bvalid, m_bready; logic [1:0] m_bresp;
|
||||
gs_lpddr_wr_arb u_wr_arb (
|
||||
.clk(emif_clk), .rst_n(rst_n),
|
||||
.s0_awaddr(c_awaddr[29:0]), .s0_awburst(c_awburst), .s0_awid(7'd0), .s0_awlen(c_awlen), .s0_awsize(c_awsize),
|
||||
.s0_awvalid(c_awvalid), .s0_awready(c_awready), .s0_wdata(c_wdata), .s0_wstrb(c_wstrb), .s0_wlast(c_wlast),
|
||||
.s0_wvalid(c_wvalid), .s0_wready(c_wready), .s0_bresp(c_bresp), .s0_bvalid(c_bvalid), .s0_bready(c_bready),
|
||||
.s1_awaddr(30'd0), .s1_awburst(2'b01), .s1_awid(7'd1), .s1_awlen(8'd0), .s1_awsize(3'b101),
|
||||
.s1_awvalid(1'b0), .s1_awready(), .s1_wdata(256'd0), .s1_wstrb(32'd0), .s1_wlast(1'b0),
|
||||
.s1_wvalid(1'b0), .s1_wready(), .s1_bresp(), .s1_bvalid(), .s1_bready(1'b0),
|
||||
.s2_awaddr(z_awaddr[29:0]), .s2_awburst(z_awburst), .s2_awid(7'd2), .s2_awlen(z_awlen), .s2_awsize(z_awsize),
|
||||
.s2_awvalid(z_awvalid), .s2_awready(z_awready), .s2_wdata(z_wdata), .s2_wstrb(z_wstrb), .s2_wlast(z_wlast),
|
||||
.s2_wvalid(z_wvalid), .s2_wready(z_wready), .s2_bresp(z_bresp), .s2_bvalid(z_bvalid), .s2_bready(z_bready),
|
||||
.s3_awaddr(30'd0), .s3_awburst(2'b01), .s3_awid(7'd3), .s3_awlen(8'd0), .s3_awsize(3'b101),
|
||||
.s3_awvalid(1'b0), .s3_awready(), .s3_wdata(256'd0), .s3_wstrb(32'd0), .s3_wlast(1'b0),
|
||||
.s3_wvalid(1'b0), .s3_wready(), .s3_bresp(), .s3_bvalid(), .s3_bready(1'b0),
|
||||
.m_awaddr(m_awaddr), .m_awburst(m_awburst), .m_awid(m_awid), .m_awlen(m_awlen), .m_awsize(m_awsize),
|
||||
.m_awvalid(m_awvalid), .m_awready(m_awready), .m_wdata(m_wdata), .m_wstrb(m_wstrb), .m_wlast(m_wlast),
|
||||
.m_wvalid(m_wvalid), .m_wready(m_wready), .m_bvalid(m_bvalid), .m_bready(m_bready), .m_bresp(m_bresp)
|
||||
);
|
||||
|
||||
// ===== REAL de25 read arbiter: s3=Z-read (mirrors de25 reload port) ; s0/s1/s2 idle during render =====
|
||||
logic [29:0] m_araddr; logic [1:0] m_arburst; logic [6:0] m_arid; logic [7:0] m_arlen; logic [2:0] m_arsize;
|
||||
logic m_arvalid, m_arready; logic [255:0] m_rdata; logic [1:0] m_rresp; logic m_rlast, m_rvalid, m_rready;
|
||||
gs_lpddr_rd_arb u_rd_arb (
|
||||
.clk(emif_clk), .rst_n(rst_n),
|
||||
.s0_araddr(30'd0), .s0_arburst(2'b01), .s0_arid(7'd0), .s0_arlen(8'd0), .s0_arsize(3'b101),
|
||||
.s0_arvalid(1'b0), .s0_arready(), .s0_rdata(), .s0_rresp(), .s0_rlast(), .s0_rvalid(), .s0_rready(1'b0),
|
||||
.s1_araddr(30'd0), .s1_arburst(2'b01), .s1_arid(7'd1), .s1_arlen(8'd0), .s1_arsize(3'b101),
|
||||
.s1_arvalid(1'b0), .s1_arready(), .s1_rdata(), .s1_rresp(), .s1_rlast(), .s1_rvalid(), .s1_rready(1'b0),
|
||||
.s2_araddr(30'd0), .s2_arburst(2'b01), .s2_arid(7'd2), .s2_arlen(8'd0), .s2_arsize(3'b101),
|
||||
.s2_arvalid(1'b0), .s2_arready(), .s2_rdata(), .s2_rresp(), .s2_rlast(), .s2_rvalid(), .s2_rready(1'b0),
|
||||
.s3_araddr(z_araddr[29:0]), .s3_arburst(z_arburst), .s3_arid(7'd3), .s3_arlen(z_arlen), .s3_arsize(z_arsize),
|
||||
.s3_arvalid(z_arvalid), .s3_arready(z_arready), .s3_rdata(z_rdata), .s3_rresp(z_rresp), .s3_rlast(z_rlast),
|
||||
.s3_rvalid(z_rvalid), .s3_rready(z_rready),
|
||||
.m_araddr(m_araddr), .m_arburst(m_arburst), .m_arid(m_arid), .m_arlen(m_arlen), .m_arsize(m_arsize),
|
||||
.m_arvalid(m_arvalid), .m_arready(m_arready), .m_rdata(m_rdata), .m_rresp(m_rresp), .m_rlast(m_rlast),
|
||||
.m_rvalid(m_rvalid), .m_rready(m_rready)
|
||||
);
|
||||
|
||||
logic [15:0] lf=16'hF00D; always_ff @(posedge emif_clk) lf<={lf[14:0], lf[15]^lf[13]^lf[12]^lf[10]};
|
||||
|
||||
// ===== ONE shared behavioral LPDDR (write + read), address-decoded, random backpressure =====
|
||||
logic [255:0] shmem [0:MEMBEATS-1];
|
||||
logic [29:0] wa; logic [255:0] wd; logic [31:0] ws; logic aw_s, w_s; logic [3:0] bd; logic bp;
|
||||
logic [29:0] ra; logic rp; logic [3:0] rd_dly;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if(!rst_n) begin
|
||||
m_awready<=0; m_wready<=0; m_bvalid<=0; m_bresp<=0; aw_s<=0; w_s<=0; bp<=0; bd<=0;
|
||||
m_arready<=0; m_rvalid<=0; m_rlast<=0; m_rresp<=0; m_rdata<=0; rp<=0; rd_dly<=0;
|
||||
end else begin
|
||||
m_awready<=(!aw_s)?lf[0]:1'b0; m_wready<=(!w_s)?lf[1]:1'b0;
|
||||
if(m_awvalid&&m_awready) begin wa<=m_awaddr; aw_s<=1; m_awready<=0; end
|
||||
if(m_wvalid&&m_wready) begin wd<=m_wdata; ws<=m_wstrb; w_s<=1; m_wready<=0; end
|
||||
if(aw_s&&w_s&&!bp&&!m_bvalid) begin
|
||||
for(int bb=0;bb<32;bb++) if(ws[bb]) shmem[wa>>5][bb*8+:8]<=wd[bb*8+:8];
|
||||
bp<=1; bd<={1'b0,lf[4:2]};
|
||||
end
|
||||
if(bp&&!m_bvalid) begin if(bd==0) begin m_bvalid<=1;m_bresp<=0;bp<=0;aw_s<=0;w_s<=0; end else bd<=bd-1; end
|
||||
if(m_bvalid&&m_bready) m_bvalid<=0;
|
||||
m_arready<=(!rp&&!m_rvalid)?lf[8]:1'b0;
|
||||
if(m_arvalid&&m_arready) begin ra<=m_araddr; rp<=1; rd_dly<={1'b0,lf[7:5]}; m_arready<=0; end
|
||||
if(rp&&!m_rvalid) begin if(rd_dly==0) begin m_rdata<=shmem[ra>>5]; m_rresp<=0; m_rvalid<=1; m_rlast<=1; rp<=0; end else rd_dly<=rd_dly-1; end
|
||||
if(m_rvalid&&m_rready) begin m_rvalid<=0; m_rlast<=0; end
|
||||
end
|
||||
end
|
||||
// disjoint-map monitors on the MERGED master streams
|
||||
always_ff @(posedge emif_clk) if(rst_n) begin
|
||||
if(m_awvalid) begin
|
||||
if(!((m_awaddr<30'(COLBASE+COL_TOP)) || (m_awaddr>=30'(ZBASE) && m_awaddr<30'(ZBASE+Z_TOP))))
|
||||
begin $error("[zint] merged AW %h not in color/Z range",m_awaddr); errors++; end
|
||||
end
|
||||
if(m_arvalid && (m_araddr<30'(ZBASE) || m_araddr>=30'(ZBASE+Z_TOP))) begin $error("[zint] merged AR %h not in Z range",m_araddr); errors++; end
|
||||
end
|
||||
|
||||
// ===== clamp16 persistent-Z SCOREBOARD over the ACTUAL emitted fragments (design clk) =====
|
||||
// Mirrors gs_lpddr_z_rmw: GEQUAL vs stored, Z_CLEAR=0. Persists across epochs. Gated active after clear_done.
|
||||
logic [15:0] sb_z [0:NPX-1];
|
||||
logic [31:0] sb_col[0:NPX-1];
|
||||
logic sb_wr [0:NPX-1];
|
||||
integer sb_frag, sb_pass;
|
||||
// no-fragment-after-marker: a fragment must not appear in the same scene after fb_flush (before next GO)
|
||||
logic scene_ended=0; int frag_after_marker;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin
|
||||
sb_frag<=0; sb_pass<=0; scene_ended<=0; frag_after_marker<=0;
|
||||
end else begin
|
||||
if (feeder_go_tb && feeder_ready_tb) scene_ended<=0; // new scene opens
|
||||
if (fb_flush) scene_ended<=1; // ordered marker closes the scene
|
||||
if (frag_v && saw_clear_done) begin
|
||||
int o; logic [15:0] zq;
|
||||
if (scene_ended) frag_after_marker<=frag_after_marker+1; // fragment after this scene's marker = ordering bug
|
||||
o = g_y*W + g_x; zq = cl16(flush_z_w);
|
||||
sb_frag<=sb_frag+1;
|
||||
if (o>=0 && o<NPX) begin
|
||||
if (zq >= sb_z[o]) begin sb_z[o]<=zq; sb_col[o]<=flush_color32_w; sb_wr[o]<=1'b1; sb_pass<=sb_pass+1; end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// FRESH-DRAIN observer (emif domain)
|
||||
logic fd_q; int fd_rises, fd_falls;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin fd_q<=0; fd_rises<=0; fd_falls<=0; end
|
||||
else begin fd_q<=frame_drained;
|
||||
if (frame_drained && !fd_q) fd_rises<=fd_rises+1;
|
||||
if (!frame_drained && fd_q) fd_falls<=fd_falls+1;
|
||||
end
|
||||
end
|
||||
|
||||
// clear_start: mirror de25 — pulse once at the writer ARM (after preclear, before first GO)
|
||||
logic wr_arm=0, arm_e1, arm_e2, arm_e3;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin arm_e1<=0; arm_e2<=0; arm_e3<=0; clear_start<=0; end
|
||||
else begin arm_e1<=wr_arm; arm_e2<=arm_e1; arm_e3<=arm_e2; clear_start<=(arm_e2 && !arm_e3); end
|
||||
end
|
||||
|
||||
// ---- feeder staging stream (write port), per epoch ----
|
||||
logic [63:0] stg_buf [0:STG_WORDS-1];
|
||||
task automatic stream_list(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/feeder_sh3_zsched%0d.mem", k);
|
||||
for (int i=0;i<STG_WORDS;i++) stg_buf[i]=64'd0;
|
||||
$readmemh(fn, stg_buf);
|
||||
@(negedge clk);
|
||||
for (int i=0;i<STG_WORDS;i++) begin stg_we<=1'b1; stg_waddr<=i[11:0]; stg_wdata<=stg_buf[i]; @(negedge clk); end
|
||||
stg_we<=1'b0; stg_waddr<=12'd0; stg_wdata<=64'd0; @(negedge clk);
|
||||
if (dut.g_feeder.feeder_stg[0][31:0] !== stg_buf[0][31:0]) begin
|
||||
$error("[zint] epoch %0d: staged word0=%08x exp %08x", k, dut.g_feeder.feeder_stg[0][31:0], stg_buf[0][31:0]); errors++; end
|
||||
endtask
|
||||
|
||||
task automatic fill_cache(input int k, input logic [31:0] exp_crc);
|
||||
int d=0;
|
||||
@(posedge clk) fill_start<=1'b1; @(posedge clk) fill_start<=1'b0;
|
||||
d=0; while (fill_done && d<1000) begin @(posedge clk); d++; end
|
||||
d=0; while (!fill_done && d<400000) begin @(posedge clk); d++; end
|
||||
if (!fill_done) begin $error("[zint] fill %0d: fill_done never rose", k); errors++; end
|
||||
if (fill_crc_w!==exp_crc) begin $error("[zint] fill %0d: crc=%08x exp %08x", k, fill_crc_w, exp_crc); errors++; end
|
||||
if (tex_rd_errs!==0) begin $error("[zint] fill %0d: rd_errs=%0d", k, tex_rd_errs); errors++; end
|
||||
endtask
|
||||
|
||||
// one-scene runner: GO, wait render+drain, REQUIRE a FRESH ordered frame_drained
|
||||
task automatic run_scene(input int k, input int exp_records);
|
||||
int r0, f0, d=0; logic fd_before; int gd0, fm0;
|
||||
r0=fd_rises; f0=fd_falls; fd_before=frame_drained; gd0=g_drops; fm0=frag_after_marker;
|
||||
@(negedge clk); feeder_go_tb=1'b1; @(negedge clk); feeder_go_tb=1'b0;
|
||||
d=0; while (feeder_ready_tb && d<4000) begin @(posedge clk); d++; end
|
||||
if (feeder_ready_tb!==1'b0) begin $error("[zint] epoch %0d: feeder_ready did not drop after GO", k); errors++; end
|
||||
if (fd_before) begin
|
||||
d=0; while (fd_falls==f0 && d<1500000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_falls==f0) begin $error("[zint] epoch %0d: frame_drained never fell from stale high", k); errors++; end
|
||||
end
|
||||
d=0; while (fd_rises<=r0 && d<1500000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_rises<=r0) begin $error("[zint] epoch %0d: frame_drained never rose", k); errors++; end
|
||||
repeat(100) @(posedge clk);
|
||||
if (feeder_records_w!==exp_records) begin $error("[zint] epoch %0d: records=%0d exp %0d", k, feeder_records_w, exp_records); errors++; end
|
||||
if (col_ovf!==0 || bresp_err!==0) begin $error("[zint] epoch %0d: col_ovf=%0d bresp_err=%0d", k, col_ovf, bresp_err); errors++; end
|
||||
if (g_drops!==gd0) begin $error("[zint] epoch %0d: %0d FRAGMENT DROPS (request FIFO overflow)", k, g_drops-gd0); errors++; end
|
||||
if (frag_after_marker!==fm0) begin $error("[zint] epoch %0d: %0d fragment(s) after scene marker (ordering)", k, frag_after_marker-fm0); errors++; end
|
||||
$display("[zint] epoch %0d: fresh drain (f %0d->%0d, r %0d->%0d) records=%0d drops=%0d fed=%0d pass=%0d",
|
||||
k, f0, fd_falls, r0, fd_rises, feeder_records_w, g_drops, frags_fed, sb_pass);
|
||||
endtask
|
||||
|
||||
task automatic run_epoch(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/sh3_zsched%0d_tex_lpddr.mem", k);
|
||||
$readmemh(fn, lpddr_mem);
|
||||
fill_cache(k, EP_CRC[k]);
|
||||
stream_list(k);
|
||||
run_scene(k, EP_REC[k]);
|
||||
endtask
|
||||
|
||||
initial begin
|
||||
errors=0; feeder_go_tb=1'b0; fill_start=1'b0; wr_arm=0; zc_enable=1'b1; stg_we=0; stg_waddr=0; stg_wdata=0;
|
||||
for (int i=0;i<NPX;i++) begin sb_z[i]=16'h0000; sb_col[i]=32'd0; sb_wr[i]=1'b0; end
|
||||
for (int b=0;b<MEMBEATS;b++) shmem[b]=256'd0; // PRECLEAR color+Z EXACTLY ONCE
|
||||
// skip-guard: probe a fixture (via $fopen — avoids the $readmemh range warning)
|
||||
begin int pfh; pfh=$fopen("../../data/top_psmct32_raster_demo/sh3_zsched0_tex_lpddr.mem","r");
|
||||
if (pfh==0) begin $display("[tb_top_psmct32_sh3_zint] SKIP — sh3_zsched*.mem absent (run gs_make_sh3_zscheduler_fixture.py --emit)"); $finish; end
|
||||
$fclose(pfh);
|
||||
end
|
||||
|
||||
rst_n=1'b0; core_go=1'b0; repeat(6) @(posedge clk); rst_n=1'b1; 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);
|
||||
wait (feeder_ready_tb==1'b1); repeat(50) @(posedge clk);
|
||||
|
||||
// ARM (-> clear_start) AFTER preclear, BEFORE first GO. z_rmw's clear_start writes Z_CLEAR to EVERY Z beat
|
||||
// (NBEATS=NPX/16, through wr_arb s2 under random backpressure) then asserts clear_done -> it is the authoritative
|
||||
// Z preclear. Wait for it (the board host likewise gates the first GO on the clear_done status bit).
|
||||
wr_arm<=1'b1;
|
||||
begin int d=0; while (!clear_done && d<600000) begin @(posedge emif_clk); d++; end end
|
||||
if (!clear_done) begin $error("[zint] clear_done never rose after ARM (Z preclear did not finish)"); errors++; end
|
||||
else $display("[zint] Z preclear complete (clear_done), z_beats_written(clear)=%0d", z_beats_written);
|
||||
repeat(40) @(posedge clk);
|
||||
|
||||
for (int k=0;k<NEP;k++) run_epoch(k);
|
||||
|
||||
if (pre_clear_frags!==0) begin $error("[zint] %0d psm0 fragment(s) before clear_done", pre_clear_frags); errors++; end
|
||||
if (fd_rises<NEP) begin $error("[zint] expected %0d ordered drains: rises=%0d", NEP, fd_rises); errors++; end
|
||||
if (fd_falls<NEP-1) begin $error("[zint] epochs after the first never cleared stale (falls=%0d)", fd_falls); errors++; end
|
||||
if (eof_count!==NEP)begin $error("[zint] scene markers=%0d != accepted GOs=%0d", eof_count, NEP); errors++; end
|
||||
if (g_drops!==0) begin $error("[zint] TOTAL %0d fragment drops", g_drops); errors++; end
|
||||
|
||||
// ===== final memory compare: shared LPDDR color + Z == scoreboard =====
|
||||
begin
|
||||
int zmis, cmis, zchk, cchk; zmis=0; cmis=0; zchk=0; cchk=0;
|
||||
for (int o=0;o<NPX;o++) begin
|
||||
logic [15:0] zs; logic [31:0] cs; int zb, zl;
|
||||
zb=(ZBASE + o*2)>>5; zl=o&15; zs=shmem[zb][zl*16+:16];
|
||||
zchk++;
|
||||
if (zs !== sb_z[o]) begin if (zmis<10) $error("[zint] Z px%0d got %04x exp %04x", o, zs, sb_z[o]); zmis++; end
|
||||
if (sb_wr[o]) begin
|
||||
cs=shmem[o>>3][(o[2:0])*32+:32]; cchk++;
|
||||
if (cs[23:0] !== sb_col[o][23:0]) begin if (cmis<10) $error("[zint] COL px%0d got %06x exp %06x", o, cs[23:0], sb_col[o][23:0]); cmis++; end
|
||||
end
|
||||
end
|
||||
$display("[zint] final compare: Z %0d/%0d mismatch, COLOR %0d/%0d mismatch (written px=%0d)", zmis, zchk, cmis, cchk, cchk);
|
||||
end
|
||||
|
||||
begin
|
||||
string fdump;
|
||||
if ($value$plusargs("FBDUMP=%s", fdump)) begin
|
||||
int fh;
|
||||
fh = $fopen(fdump, "w");
|
||||
if (fh == 0) begin
|
||||
$error("[zint] could not open FBDUMP '%s'", fdump);
|
||||
errors++;
|
||||
end else begin
|
||||
for (int o=0;o<NPX;o++) begin
|
||||
logic [31:0] cs;
|
||||
cs = shmem[o>>3][(o[2:0])*32+:32];
|
||||
$fdisplay(fh, "%08x", cs);
|
||||
end
|
||||
$fclose(fh);
|
||||
$display("[zint] dumped final COLOR FB (%0dx%0d) -> %s", W, H, fdump);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
$display("[tb_top_psmct32_sh3_zint] fed=%0d pass=%0d drops=%0d markers=%0d drains(r/f)=%0d/%0d col_ovf=%0d bresp_err=%0d errors=%0d",
|
||||
frags_fed, sb_pass, g_drops, eof_count, fd_rises, fd_falls, col_ovf, bresp_err, errors);
|
||||
$display("[zint] map: COLOR 0x%0h..0x%0h Z 0x%0h..0x%0h", COLBASE, COLBASE+COL_TOP, ZBASE, ZBASE+Z_TOP);
|
||||
if (errors==0) $display("[tb_top_psmct32_sh3_zint] PASS"); else $display("[tb_top_psmct32_sh3_zint] FAIL");
|
||||
$finish;
|
||||
end
|
||||
initial begin #200000000; $error("[tb_top_psmct32_sh3_zint] TIMEOUT"); $finish; end
|
||||
endmodule : tb_top_psmct32_sh3_zint
|
||||
@@ -0,0 +1,423 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_sh3_zint640 (Ch358 — REAL-RASTER persistent-Z board integration sim at NATIVE 640x480)
|
||||
//
|
||||
// Codex's pre-fit gate: prove the persistent-Z ROP through the SAME path the de25 top wires (GS_SH3_LPDDR_FB_Z):
|
||||
// REAL demo (feeder + raster, authz sh3_zs640 NATIVE-640x480 strong-reject scene) -> gs_lpddr_zc_emit -> REAL de25 arbiters
|
||||
// (gs_lpddr_wr_arb s0=color / s2=Z-write ; gs_lpddr_rd_arb s3=Z-read) -> ONE shared behavioral LPDDR
|
||||
// with randomized backpressure. Unlike tb_top_psmct32_sh3_zarb (which fed a handshake-throttled trace),
|
||||
// the raster CANNOT honor g_ready, so this is the gate that catches request-FIFO fragment DROPS.
|
||||
//
|
||||
// Asserts (Codex list):
|
||||
// * final LPDDR color + Z == independent clamp16 persistent-Z scoreboard over the ACTUAL emitted fragments;
|
||||
// * zero fragment drops (g_valid && !g_ready) — the whole reason this sim exists;
|
||||
// * exactly one ordered scene marker (sh3_fb_flush) per accepted GO; no fragment after a marker within a scene;
|
||||
// * Z cache invalidated (clear_done) after preclear and before the first GO; Z persists across epochs;
|
||||
// * a FRESH ordered frame_drained per epoch; scanout never referenced during render (rd_arb s0/s1/s2 idle);
|
||||
// * merged AW only in color/Z ranges, merged AR only in Z range (disjoint-map monitor).
|
||||
// LOCAL/gitignored fixtures; skip-guard if absent.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_sh3_zint640;
|
||||
`include "sh3_zs640_params.vh" // FBPXW=640, FBH=480, N_EPOCHS=3, EPk_CRC/EPk_NTRIS, TEX_*, ...
|
||||
localparam int W = FBPXW, H = FBH;
|
||||
localparam int NPX = W*H;
|
||||
localparam int NEP = N_EPOCHS;
|
||||
localparam [31:0] COLBASE = 32'h0000_0000, ZBASE = 32'h0014_0000;
|
||||
localparam int COL_TOP = NPX*4; // color region byte-size (disjoint below ZBASE)
|
||||
localparam int Z_TOP = NPX*2; // Z region byte-size
|
||||
localparam int MEMBEATS = 65536; // shared LPDDR: covers ZBASE + Z_TOP
|
||||
|
||||
// per-epoch expected CRC / records
|
||||
logic [31:0] EP_CRC [0:2]; int EP_REC [0:2];
|
||||
initial begin
|
||||
EP_CRC[0]=EP0_CRC; EP_CRC[1]=EP1_CRC; EP_CRC[2]=EP2_CRC;
|
||||
EP_REC[0]=EP0_NTRIS; EP_REC[1]=EP1_NTRIS; EP_REC[2]=EP2_NTRIS;
|
||||
end
|
||||
|
||||
logic clk=0; always #5 clk=~clk;
|
||||
logic emif_clk=0; always #2 emif_clk=~emif_clk;
|
||||
logic rst_n; logic fb_commit=0; always #13 fb_commit=~fb_commit;
|
||||
int errors; initial errors=0;
|
||||
|
||||
// clamp16 (vendored PCSX2 PSMZ16S source-Z saturation)
|
||||
function automatic logic [15:0] cl16(input logic [31:0] z); cl16=(|z[31:16])?16'hFFFF:z[15:0]; endfunction
|
||||
|
||||
// ===== bram-top (FB_LPDDR_ONLY, AUTOSTART off), 384x381 =====
|
||||
logic core_go; logic [7:0] r,g,b; logic hsync,vsync_o,de;
|
||||
logic core_halt,dma_done_seen,frame_seen,raster_overflow,frame_toggle,dma_done_toggle;
|
||||
logic feeder_go_tb, feeder_ready_tb; logic [15:0] feeder_records_w; logic [31:0] feeder_waits_w;
|
||||
logic gs_tex_rd_en_o; logic [31:0] gs_tex_rd_addr_o; logic [31:0] tex_cache_data; logic tex_cache_ready;
|
||||
logic [31:0] tex_cache_hits, tex_bram_hits;
|
||||
logic flush_emit_w; logic [31:0] flush_addr_w; logic [31:0] flush_color32_w; logic [5:0] flush_psm_w;
|
||||
logic [11:0] flush_x_w, flush_y_w; logic [31:0] flush_z_w;
|
||||
logic stg_we; logic [11:0] stg_waddr; logic [63:0] stg_wdata;
|
||||
|
||||
top_psmct32_raster_demo_bram #(
|
||||
.H_ACTIVE(FBPXW), .V_ACTIVE(FBH),
|
||||
.VRAM_BYTES(VRAM_BYTES_P), .VRAM_ENABLE_READ2(1'b0), .PSMCT32_SWIZZLE(1'b0),
|
||||
.COMBINED_TAZ(1'b0), .TILE_LOCAL(1'b0), .TILE_COLS(1), .TILE_ROWS(1),
|
||||
.TILE_MULTIPRIM(1'b0), .TILE_PRIM_COUNT(1), .TILE_FIFO_DEPTH(8),
|
||||
.BIN_BUFFER_ENABLE(1'b0), .HEARTBEAT_SPLICE_ENABLE(1'b0),
|
||||
.FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(STG_WORDS), .FEEDER_AUTOSTART(1'b0),
|
||||
.PERSPECTIVE_CORRECT(1'b1), .PERSP_RECIP_IDX_BITS(11),
|
||||
.GRAD_SEQ_DIVIDER(1'b1), .GRAD_DIV_CYCLES(4),
|
||||
.GS_LPDDR_TEX(1'b1), .TEX_VRAM_BASE(TEX_VRAM_BASE), .TEX_CACHE_BYTES(TEX_BYTES),
|
||||
.CLUT_CSM1_ENABLE(1'b1), .FB_LPDDR_ONLY(1'b1)
|
||||
) dut (
|
||||
.clk(clk), .rst_n(rst_n), .core_go(core_go),
|
||||
.r(r), .g(g), .b(b), .hsync(hsync), .vsync(vsync_o), .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),
|
||||
.feeder_stg_we_i(stg_we), .feeder_stg_waddr_i(stg_waddr), .feeder_stg_wdata_i(stg_wdata),
|
||||
.feeder_go_i(feeder_go_tb), .feeder_ready_o(feeder_ready_tb),
|
||||
.feeder_records_o(feeder_records_w), .feeder_waits_o(feeder_waits_w),
|
||||
.flush_emit_o(flush_emit_w), .flush_addr_o(flush_addr_w),
|
||||
.flush_pix16_o(), .flush_color32_o(flush_color32_w), .flush_psm_o(flush_psm_w),
|
||||
.flush_x_o(flush_x_w), .flush_y_o(flush_y_w), .flush_z_o(flush_z_w),
|
||||
.gs_tex_rd_en_o(gs_tex_rd_en_o), .gs_tex_rd_addr_o(gs_tex_rd_addr_o),
|
||||
.tex_cache_data_i(tex_cache_data), .tex_cache_ready_i(tex_cache_ready),
|
||||
.tex_cache_hits_o(tex_cache_hits), .tex_bram_hits_o(tex_bram_hits)
|
||||
);
|
||||
|
||||
// texture cache + behavioral texture LPDDR (reloaded per epoch)
|
||||
logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid; logic [7:0] arlen; logic [2:0] arsize;
|
||||
logic arvalid, arready; logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready;
|
||||
logic fill_start, fill_done; logic [31:0] fill_beats, fill_bytes, tex_rd_errs, fill_crc_w;
|
||||
gs_texture_cache #(.LPDDR_TEX_BASE(LPDDR_TEX_BASE), .TEX_VRAM_BASE(TEX_VRAM_BASE),
|
||||
.TEX_BYTES(TEX_BYTES), .N_BEATS(N_BEATS)) u_cache (
|
||||
.axi_clk(clk), .axi_rst_n(rst_n), .fill_start(fill_start), .fill_done(fill_done),
|
||||
.fill_beats(fill_beats), .fill_bytes(fill_bytes), .rd_errs(tex_rd_errs), .fill_crc(fill_crc_w),
|
||||
.araddr(araddr), .arburst(arburst), .arid(arid), .arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready),
|
||||
.rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready),
|
||||
.sample_clk(clk), .tex_rd_en(gs_tex_rd_en_o), .tex_rd_addr(gs_tex_rd_addr_o),
|
||||
.tex_rd_data(tex_cache_data), .tex_ready(tex_cache_ready)
|
||||
);
|
||||
logic [31:0] lpddr_mem [0:(TEX_BYTES/4)-1];
|
||||
typedef enum logic [1:0] { S_IDLE, S_WAIT, S_DATA } sstate_t; sstate_t sst; logic [3:0] tdly; int tbeat;
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin sst<=S_IDLE; arready<=0; rvalid<=0; rlast<=0; rresp<=0; rdata<=0; tdly<=0; end
|
||||
else begin arready<=0; rvalid<=0; rlast<=0;
|
||||
case (sst)
|
||||
S_IDLE: if (arvalid) begin arready<=1; tbeat<=(araddr-LPDDR_TEX_BASE)>>5; tdly<=4'd2; sst<=S_WAIT; end
|
||||
S_WAIT: if (tdly==0) sst<=S_DATA; else tdly<=tdly-1'b1;
|
||||
S_DATA: if (rready) begin for (int w=0;w<8;w++) rdata[w*32 +: 32]<=lpddr_mem[tbeat*8+w];
|
||||
rresp<=2'b00; rvalid<=1; rlast<=1; sst<=S_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// ===== render epoch: one ordered scene marker (sh3_fb_flush) per accepted GO =====
|
||||
logic fb_flush=0, render_inflight=0, feeder_ready_q=0; int eof_count;
|
||||
wire feeder_ready_rise = feeder_ready_tb && !feeder_ready_q;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin render_inflight<=0; feeder_ready_q<=0; fb_flush<=0; eof_count<=0; end
|
||||
else begin feeder_ready_q<=feeder_ready_tb; fb_flush<=1'b0;
|
||||
if (feeder_go_tb && feeder_ready_tb) render_inflight<=1'b1;
|
||||
if (render_inflight && feeder_ready_rise) begin fb_flush<=1'b1; render_inflight<=1'b0; eof_count<=eof_count+1; end
|
||||
end
|
||||
end
|
||||
|
||||
// ===== gs_lpddr_zc_emit — Z-then-color emit, fed by the REAL raster (NO handshake) =====
|
||||
logic zc_enable, clear_start, clear_done, frame_drained;
|
||||
wire [15:0] g_zq = cl16(flush_z_w);
|
||||
wire frag_v = flush_emit_w && (flush_psm_w==6'h00);
|
||||
wire g_valid = frag_v || fb_flush;
|
||||
wire g_scene = fb_flush;
|
||||
wire [11:0] g_x = flush_x_w, g_y = flush_y_w;
|
||||
wire [31:0] g_color = flush_color32_w;
|
||||
wire g_ready;
|
||||
// Z AXI
|
||||
logic [31:0] z_araddr; logic [7:0] z_arlen; logic [2:0] z_arsize; logic [1:0] z_arburst; logic z_arvalid, z_arready;
|
||||
logic [255:0] z_rdata; logic [1:0] z_rresp; logic z_rlast, z_rvalid, z_rready;
|
||||
logic [31:0] z_awaddr; logic [7:0] z_awlen; logic [2:0] z_awsize; logic [1:0] z_awburst; logic z_awvalid, z_awready;
|
||||
logic [255:0] z_wdata; logic [31:0] z_wstrb; logic z_wlast, z_wvalid, z_wready; logic z_bvalid, z_bready; logic [1:0] z_bresp;
|
||||
// Color AXI
|
||||
logic [31:0] c_awaddr; logic [7:0] c_awlen; logic [2:0] c_awsize; logic [1:0] c_awburst; logic c_awvalid, c_awready;
|
||||
logic [255:0] c_wdata; logic [31:0] c_wstrb; logic c_wlast, c_wvalid, c_wready; logic c_bvalid, c_bready; logic [1:0] c_bresp;
|
||||
logic [31:0] z_beats_read, z_beats_written, c_beats_written, col_ovf, bresp_err; logic zc_idle;
|
||||
gs_lpddr_zc_emit #(.COLBASE(COLBASE), .ZBASE(ZBASE), .FB_PXW(W), .FB_H(H), .REQ_DEPTH(1024), .COL_DEPTH(128)) u_zc (
|
||||
.gs_clk(clk), .gs_rst_n(rst_n), .enable(zc_enable),
|
||||
.g_valid(g_valid), .g_ready(g_ready), .g_x(g_x), .g_y(g_y), .g_zq(g_zq), .g_zmsk(1'b0),
|
||||
.g_ztest(1'b1), .g_ztst(2'd2), .g_color(g_color), .g_be(4'hF), .g_scene(g_scene),
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n), .clear_start(clear_start), .clear_done(clear_done), .frame_drained(frame_drained),
|
||||
.z_araddr(z_araddr), .z_arlen(z_arlen), .z_arsize(z_arsize), .z_arburst(z_arburst), .z_arvalid(z_arvalid), .z_arready(z_arready),
|
||||
.z_rdata(z_rdata), .z_rresp(z_rresp), .z_rlast(z_rlast), .z_rvalid(z_rvalid), .z_rready(z_rready),
|
||||
.z_awaddr(z_awaddr), .z_awlen(z_awlen), .z_awsize(z_awsize), .z_awburst(z_awburst), .z_awvalid(z_awvalid), .z_awready(z_awready),
|
||||
.z_wdata(z_wdata), .z_wstrb(z_wstrb), .z_wlast(z_wlast), .z_wvalid(z_wvalid), .z_wready(z_wready),
|
||||
.z_bvalid(z_bvalid), .z_bready(z_bready), .z_bresp(z_bresp),
|
||||
.c_awaddr(c_awaddr), .c_awlen(c_awlen), .c_awsize(c_awsize), .c_awburst(c_awburst), .c_awvalid(c_awvalid), .c_awready(c_awready),
|
||||
.c_wdata(c_wdata), .c_wstrb(c_wstrb), .c_wlast(c_wlast), .c_wvalid(c_wvalid), .c_wready(c_wready),
|
||||
.c_bvalid(c_bvalid), .c_bready(c_bready), .c_bresp(c_bresp),
|
||||
.z_beats_read(z_beats_read), .z_beats_written(z_beats_written), .c_beats_written(c_beats_written),
|
||||
.col_ovf(col_ovf), .bresp_err(bresp_err), .idle(zc_idle)
|
||||
);
|
||||
|
||||
// fragment-drop guard (design clk): the request FIFO silently drops when g_ready is low
|
||||
int g_drops; int frags_fed;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin g_drops<=0; frags_fed<=0; end
|
||||
else begin
|
||||
if (frag_v && !g_ready) g_drops<=g_drops+1;
|
||||
if (frag_v && g_ready) frags_fed<=frags_fed+1;
|
||||
end
|
||||
end
|
||||
// clear ordering: clear_done must rise before the first GO. no psm0 fragment before clear_done.
|
||||
logic saw_clear_done=0, first_go_done=0; int pre_clear_frags;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin saw_clear_done<=0; pre_clear_frags<=0; end
|
||||
else begin
|
||||
if (clear_done) saw_clear_done<=1;
|
||||
if (frag_v && !saw_clear_done) pre_clear_frags<=pre_clear_frags+1;
|
||||
end
|
||||
end
|
||||
|
||||
// ===== REAL de25 write arbiter: s0=color, s2=Z-write ; s1/s3 inert =====
|
||||
logic [29:0] m_awaddr; logic [1:0] m_awburst; logic [6:0] m_awid; logic [7:0] m_awlen; logic [2:0] m_awsize;
|
||||
logic m_awvalid, m_awready; logic [255:0] m_wdata; logic [31:0] m_wstrb; logic m_wlast, m_wvalid, m_wready;
|
||||
logic m_bvalid, m_bready; logic [1:0] m_bresp;
|
||||
gs_lpddr_wr_arb u_wr_arb (
|
||||
.clk(emif_clk), .rst_n(rst_n),
|
||||
.s0_awaddr(c_awaddr[29:0]), .s0_awburst(c_awburst), .s0_awid(7'd0), .s0_awlen(c_awlen), .s0_awsize(c_awsize),
|
||||
.s0_awvalid(c_awvalid), .s0_awready(c_awready), .s0_wdata(c_wdata), .s0_wstrb(c_wstrb), .s0_wlast(c_wlast),
|
||||
.s0_wvalid(c_wvalid), .s0_wready(c_wready), .s0_bresp(c_bresp), .s0_bvalid(c_bvalid), .s0_bready(c_bready),
|
||||
.s1_awaddr(30'd0), .s1_awburst(2'b01), .s1_awid(7'd1), .s1_awlen(8'd0), .s1_awsize(3'b101),
|
||||
.s1_awvalid(1'b0), .s1_awready(), .s1_wdata(256'd0), .s1_wstrb(32'd0), .s1_wlast(1'b0),
|
||||
.s1_wvalid(1'b0), .s1_wready(), .s1_bresp(), .s1_bvalid(), .s1_bready(1'b0),
|
||||
.s2_awaddr(z_awaddr[29:0]), .s2_awburst(z_awburst), .s2_awid(7'd2), .s2_awlen(z_awlen), .s2_awsize(z_awsize),
|
||||
.s2_awvalid(z_awvalid), .s2_awready(z_awready), .s2_wdata(z_wdata), .s2_wstrb(z_wstrb), .s2_wlast(z_wlast),
|
||||
.s2_wvalid(z_wvalid), .s2_wready(z_wready), .s2_bresp(z_bresp), .s2_bvalid(z_bvalid), .s2_bready(z_bready),
|
||||
.s3_awaddr(30'd0), .s3_awburst(2'b01), .s3_awid(7'd3), .s3_awlen(8'd0), .s3_awsize(3'b101),
|
||||
.s3_awvalid(1'b0), .s3_awready(), .s3_wdata(256'd0), .s3_wstrb(32'd0), .s3_wlast(1'b0),
|
||||
.s3_wvalid(1'b0), .s3_wready(), .s3_bresp(), .s3_bvalid(), .s3_bready(1'b0),
|
||||
.m_awaddr(m_awaddr), .m_awburst(m_awburst), .m_awid(m_awid), .m_awlen(m_awlen), .m_awsize(m_awsize),
|
||||
.m_awvalid(m_awvalid), .m_awready(m_awready), .m_wdata(m_wdata), .m_wstrb(m_wstrb), .m_wlast(m_wlast),
|
||||
.m_wvalid(m_wvalid), .m_wready(m_wready), .m_bvalid(m_bvalid), .m_bready(m_bready), .m_bresp(m_bresp)
|
||||
);
|
||||
|
||||
// ===== REAL de25 read arbiter: s3=Z-read (mirrors de25 reload port) ; s0/s1/s2 idle during render =====
|
||||
logic [29:0] m_araddr; logic [1:0] m_arburst; logic [6:0] m_arid; logic [7:0] m_arlen; logic [2:0] m_arsize;
|
||||
logic m_arvalid, m_arready; logic [255:0] m_rdata; logic [1:0] m_rresp; logic m_rlast, m_rvalid, m_rready;
|
||||
gs_lpddr_rd_arb u_rd_arb (
|
||||
.clk(emif_clk), .rst_n(rst_n),
|
||||
.s0_araddr(30'd0), .s0_arburst(2'b01), .s0_arid(7'd0), .s0_arlen(8'd0), .s0_arsize(3'b101),
|
||||
.s0_arvalid(1'b0), .s0_arready(), .s0_rdata(), .s0_rresp(), .s0_rlast(), .s0_rvalid(), .s0_rready(1'b0),
|
||||
.s1_araddr(30'd0), .s1_arburst(2'b01), .s1_arid(7'd1), .s1_arlen(8'd0), .s1_arsize(3'b101),
|
||||
.s1_arvalid(1'b0), .s1_arready(), .s1_rdata(), .s1_rresp(), .s1_rlast(), .s1_rvalid(), .s1_rready(1'b0),
|
||||
.s2_araddr(30'd0), .s2_arburst(2'b01), .s2_arid(7'd2), .s2_arlen(8'd0), .s2_arsize(3'b101),
|
||||
.s2_arvalid(1'b0), .s2_arready(), .s2_rdata(), .s2_rresp(), .s2_rlast(), .s2_rvalid(), .s2_rready(1'b0),
|
||||
.s3_araddr(z_araddr[29:0]), .s3_arburst(z_arburst), .s3_arid(7'd3), .s3_arlen(z_arlen), .s3_arsize(z_arsize),
|
||||
.s3_arvalid(z_arvalid), .s3_arready(z_arready), .s3_rdata(z_rdata), .s3_rresp(z_rresp), .s3_rlast(z_rlast),
|
||||
.s3_rvalid(z_rvalid), .s3_rready(z_rready),
|
||||
.m_araddr(m_araddr), .m_arburst(m_arburst), .m_arid(m_arid), .m_arlen(m_arlen), .m_arsize(m_arsize),
|
||||
.m_arvalid(m_arvalid), .m_arready(m_arready), .m_rdata(m_rdata), .m_rresp(m_rresp), .m_rlast(m_rlast),
|
||||
.m_rvalid(m_rvalid), .m_rready(m_rready)
|
||||
);
|
||||
|
||||
logic [15:0] lf=16'hF00D; always_ff @(posedge emif_clk) lf<={lf[14:0], lf[15]^lf[13]^lf[12]^lf[10]};
|
||||
|
||||
// ===== ONE shared behavioral LPDDR (write + read), address-decoded, random backpressure =====
|
||||
logic [255:0] shmem [0:MEMBEATS-1];
|
||||
logic [29:0] wa; logic [255:0] wd; logic [31:0] ws; logic aw_s, w_s; logic [3:0] bd; logic bp;
|
||||
logic [29:0] ra; logic rp; logic [3:0] rd_dly;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if(!rst_n) begin
|
||||
m_awready<=0; m_wready<=0; m_bvalid<=0; m_bresp<=0; aw_s<=0; w_s<=0; bp<=0; bd<=0;
|
||||
m_arready<=0; m_rvalid<=0; m_rlast<=0; m_rresp<=0; m_rdata<=0; rp<=0; rd_dly<=0;
|
||||
end else begin
|
||||
m_awready<=(!aw_s)?lf[0]:1'b0; m_wready<=(!w_s)?lf[1]:1'b0;
|
||||
if(m_awvalid&&m_awready) begin wa<=m_awaddr; aw_s<=1; m_awready<=0; end
|
||||
if(m_wvalid&&m_wready) begin wd<=m_wdata; ws<=m_wstrb; w_s<=1; m_wready<=0; end
|
||||
if(aw_s&&w_s&&!bp&&!m_bvalid) begin
|
||||
for(int bb=0;bb<32;bb++) if(ws[bb]) shmem[wa>>5][bb*8+:8]<=wd[bb*8+:8];
|
||||
bp<=1; bd<={1'b0,lf[4:2]};
|
||||
end
|
||||
if(bp&&!m_bvalid) begin if(bd==0) begin m_bvalid<=1;m_bresp<=0;bp<=0;aw_s<=0;w_s<=0; end else bd<=bd-1; end
|
||||
if(m_bvalid&&m_bready) m_bvalid<=0;
|
||||
m_arready<=(!rp&&!m_rvalid)?lf[8]:1'b0;
|
||||
if(m_arvalid&&m_arready) begin ra<=m_araddr; rp<=1; rd_dly<={1'b0,lf[7:5]}; m_arready<=0; end
|
||||
if(rp&&!m_rvalid) begin if(rd_dly==0) begin m_rdata<=shmem[ra>>5]; m_rresp<=0; m_rvalid<=1; m_rlast<=1; rp<=0; end else rd_dly<=rd_dly-1; end
|
||||
if(m_rvalid&&m_rready) begin m_rvalid<=0; m_rlast<=0; end
|
||||
end
|
||||
end
|
||||
// disjoint-map monitors on the MERGED master streams
|
||||
always_ff @(posedge emif_clk) if(rst_n) begin
|
||||
if(m_awvalid) begin
|
||||
if(!((m_awaddr<30'(COLBASE+COL_TOP)) || (m_awaddr>=30'(ZBASE) && m_awaddr<30'(ZBASE+Z_TOP))))
|
||||
begin $error("[zint] merged AW %h not in color/Z range",m_awaddr); errors++; end
|
||||
end
|
||||
if(m_arvalid && (m_araddr<30'(ZBASE) || m_araddr>=30'(ZBASE+Z_TOP))) begin $error("[zint] merged AR %h not in Z range",m_araddr); errors++; end
|
||||
end
|
||||
|
||||
// ===== clamp16 persistent-Z SCOREBOARD over the ACTUAL emitted fragments (design clk) =====
|
||||
// Mirrors gs_lpddr_z_rmw: GEQUAL vs stored, Z_CLEAR=0. Persists across epochs. Gated active after clear_done.
|
||||
logic [15:0] sb_z [0:NPX-1];
|
||||
logic [31:0] sb_col[0:NPX-1];
|
||||
logic sb_wr [0:NPX-1];
|
||||
integer sb_frag, sb_pass;
|
||||
// no-fragment-after-marker: a fragment must not appear in the same scene after fb_flush (before next GO)
|
||||
logic scene_ended=0; int frag_after_marker;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin
|
||||
sb_frag<=0; sb_pass<=0; scene_ended<=0; frag_after_marker<=0;
|
||||
end else begin
|
||||
if (feeder_go_tb && feeder_ready_tb) scene_ended<=0; // new scene opens
|
||||
if (fb_flush) scene_ended<=1; // ordered marker closes the scene
|
||||
if (frag_v && saw_clear_done) begin
|
||||
int o; logic [15:0] zq;
|
||||
if (scene_ended) frag_after_marker<=frag_after_marker+1; // fragment after this scene's marker = ordering bug
|
||||
o = g_y*W + g_x; zq = cl16(flush_z_w);
|
||||
sb_frag<=sb_frag+1;
|
||||
if (o>=0 && o<NPX) begin
|
||||
if (zq >= sb_z[o]) begin sb_z[o]<=zq; sb_col[o]<=flush_color32_w; sb_wr[o]<=1'b1; sb_pass<=sb_pass+1; end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// FRESH-DRAIN observer (emif domain)
|
||||
logic fd_q; int fd_rises, fd_falls;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin fd_q<=0; fd_rises<=0; fd_falls<=0; end
|
||||
else begin fd_q<=frame_drained;
|
||||
if (frame_drained && !fd_q) fd_rises<=fd_rises+1;
|
||||
if (!frame_drained && fd_q) fd_falls<=fd_falls+1;
|
||||
end
|
||||
end
|
||||
|
||||
// clear_start: mirror de25 — pulse once at the writer ARM (after preclear, before first GO)
|
||||
logic wr_arm=0, arm_e1, arm_e2, arm_e3;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin arm_e1<=0; arm_e2<=0; arm_e3<=0; clear_start<=0; end
|
||||
else begin arm_e1<=wr_arm; arm_e2<=arm_e1; arm_e3<=arm_e2; clear_start<=(arm_e2 && !arm_e3); end
|
||||
end
|
||||
|
||||
// ---- feeder staging stream (write port), per epoch ----
|
||||
logic [63:0] stg_buf [0:STG_WORDS-1];
|
||||
task automatic stream_list(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/feeder_sh3_zs640%0d.mem", k);
|
||||
for (int i=0;i<STG_WORDS;i++) stg_buf[i]=64'd0;
|
||||
$readmemh(fn, stg_buf);
|
||||
@(negedge clk);
|
||||
for (int i=0;i<STG_WORDS;i++) begin stg_we<=1'b1; stg_waddr<=i[11:0]; stg_wdata<=stg_buf[i]; @(negedge clk); end
|
||||
stg_we<=1'b0; stg_waddr<=12'd0; stg_wdata<=64'd0; @(negedge clk);
|
||||
if (dut.g_feeder.feeder_stg[0][31:0] !== stg_buf[0][31:0]) begin
|
||||
$error("[zint] epoch %0d: staged word0=%08x exp %08x", k, dut.g_feeder.feeder_stg[0][31:0], stg_buf[0][31:0]); errors++; end
|
||||
endtask
|
||||
|
||||
task automatic fill_cache(input int k, input logic [31:0] exp_crc);
|
||||
int d=0;
|
||||
@(posedge clk) fill_start<=1'b1; @(posedge clk) fill_start<=1'b0;
|
||||
d=0; while (fill_done && d<1000) begin @(posedge clk); d++; end
|
||||
d=0; while (!fill_done && d<400000) begin @(posedge clk); d++; end
|
||||
if (!fill_done) begin $error("[zint] fill %0d: fill_done never rose", k); errors++; end
|
||||
if (fill_crc_w!==exp_crc) begin $error("[zint] fill %0d: crc=%08x exp %08x", k, fill_crc_w, exp_crc); errors++; end
|
||||
if (tex_rd_errs!==0) begin $error("[zint] fill %0d: rd_errs=%0d", k, tex_rd_errs); errors++; end
|
||||
endtask
|
||||
|
||||
// one-scene runner: GO, wait render+drain, REQUIRE a FRESH ordered frame_drained
|
||||
task automatic run_scene(input int k, input int exp_records);
|
||||
int r0, f0, d=0; logic fd_before; int gd0, fm0;
|
||||
r0=fd_rises; f0=fd_falls; fd_before=frame_drained; gd0=g_drops; fm0=frag_after_marker;
|
||||
@(negedge clk); feeder_go_tb=1'b1; @(negedge clk); feeder_go_tb=1'b0;
|
||||
d=0; while (feeder_ready_tb && d<4000) begin @(posedge clk); d++; end
|
||||
if (feeder_ready_tb!==1'b0) begin $error("[zint] epoch %0d: feeder_ready did not drop after GO", k); errors++; end
|
||||
if (fd_before) begin
|
||||
d=0; while (fd_falls==f0 && d<1500000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_falls==f0) begin $error("[zint] epoch %0d: frame_drained never fell from stale high", k); errors++; end
|
||||
end
|
||||
d=0; while (fd_rises<=r0 && d<1500000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_rises<=r0) begin $error("[zint] epoch %0d: frame_drained never rose", k); errors++; end
|
||||
repeat(100) @(posedge clk);
|
||||
if (feeder_records_w!==exp_records) begin $error("[zint] epoch %0d: records=%0d exp %0d", k, feeder_records_w, exp_records); errors++; end
|
||||
if (col_ovf!==0 || bresp_err!==0) begin $error("[zint] epoch %0d: col_ovf=%0d bresp_err=%0d", k, col_ovf, bresp_err); errors++; end
|
||||
if (g_drops!==gd0) begin $error("[zint] epoch %0d: %0d FRAGMENT DROPS (request FIFO overflow)", k, g_drops-gd0); errors++; end
|
||||
if (frag_after_marker!==fm0) begin $error("[zint] epoch %0d: %0d fragment(s) after scene marker (ordering)", k, frag_after_marker-fm0); errors++; end
|
||||
$display("[zint] epoch %0d: fresh drain (f %0d->%0d, r %0d->%0d) records=%0d drops=%0d fed=%0d pass=%0d",
|
||||
k, f0, fd_falls, r0, fd_rises, feeder_records_w, g_drops, frags_fed, sb_pass);
|
||||
endtask
|
||||
|
||||
task automatic run_epoch(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/sh3_zs640%0d_tex_lpddr.mem", k);
|
||||
$readmemh(fn, lpddr_mem);
|
||||
fill_cache(k, EP_CRC[k]);
|
||||
stream_list(k);
|
||||
run_scene(k, EP_REC[k]);
|
||||
endtask
|
||||
|
||||
initial begin
|
||||
errors=0; feeder_go_tb=1'b0; fill_start=1'b0; wr_arm=0; zc_enable=1'b1; stg_we=0; stg_waddr=0; stg_wdata=0;
|
||||
for (int i=0;i<NPX;i++) begin sb_z[i]=16'h0000; sb_col[i]=32'd0; sb_wr[i]=1'b0; end
|
||||
for (int b=0;b<MEMBEATS;b++) shmem[b]=256'd0; // PRECLEAR color+Z EXACTLY ONCE
|
||||
// skip-guard: probe a fixture (via $fopen — avoids the $readmemh range warning)
|
||||
begin int pfh; pfh=$fopen("../../data/top_psmct32_raster_demo/sh3_zs6400_tex_lpddr.mem","r");
|
||||
if (pfh==0) begin $display("[tb_top_psmct32_sh3_zint640] SKIP — sh3_zs640*.mem absent (run gs_make_sh3_scheduler_fixture.py --authz --fb640 --tag zs640 --emit)"); $finish; end
|
||||
$fclose(pfh);
|
||||
end
|
||||
|
||||
rst_n=1'b0; core_go=1'b0; repeat(6) @(posedge clk); rst_n=1'b1; 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);
|
||||
wait (feeder_ready_tb==1'b1); repeat(50) @(posedge clk);
|
||||
|
||||
// ARM (-> clear_start) AFTER preclear, BEFORE first GO. z_rmw's clear_start writes Z_CLEAR to EVERY Z beat
|
||||
// (NBEATS=NPX/16, through wr_arb s2 under random backpressure) then asserts clear_done -> it is the authoritative
|
||||
// Z preclear. Wait for it (the board host likewise gates the first GO on the clear_done status bit).
|
||||
wr_arm<=1'b1;
|
||||
begin int d=0; while (!clear_done && d<600000) begin @(posedge emif_clk); d++; end end
|
||||
if (!clear_done) begin $error("[zint] clear_done never rose after ARM (Z preclear did not finish)"); errors++; end
|
||||
else $display("[zint] Z preclear complete (clear_done), z_beats_written(clear)=%0d", z_beats_written);
|
||||
repeat(40) @(posedge clk);
|
||||
|
||||
for (int k=0;k<NEP;k++) run_epoch(k);
|
||||
|
||||
if (pre_clear_frags!==0) begin $error("[zint] %0d psm0 fragment(s) before clear_done", pre_clear_frags); errors++; end
|
||||
if (fd_rises<NEP) begin $error("[zint] expected %0d ordered drains: rises=%0d", NEP, fd_rises); errors++; end
|
||||
if (fd_falls<NEP-1) begin $error("[zint] epochs after the first never cleared stale (falls=%0d)", fd_falls); errors++; end
|
||||
if (eof_count!==NEP)begin $error("[zint] scene markers=%0d != accepted GOs=%0d", eof_count, NEP); errors++; end
|
||||
if (g_drops!==0) begin $error("[zint] TOTAL %0d fragment drops", g_drops); errors++; end
|
||||
|
||||
// ===== final memory compare: shared LPDDR color + Z == scoreboard =====
|
||||
begin
|
||||
int zmis, cmis, zchk, cchk; zmis=0; cmis=0; zchk=0; cchk=0;
|
||||
for (int o=0;o<NPX;o++) begin
|
||||
logic [15:0] zs; logic [31:0] cs; int zb, zl;
|
||||
zb=(ZBASE + o*2)>>5; zl=o&15; zs=shmem[zb][zl*16+:16];
|
||||
zchk++;
|
||||
if (zs !== sb_z[o]) begin if (zmis<10) $error("[zint] Z px%0d got %04x exp %04x", o, zs, sb_z[o]); zmis++; end
|
||||
if (sb_wr[o]) begin
|
||||
cs=shmem[o>>3][(o[2:0])*32+:32]; cchk++;
|
||||
if (cs[23:0] !== sb_col[o][23:0]) begin if (cmis<10) $error("[zint] COL px%0d got %06x exp %06x", o, cs[23:0], sb_col[o][23:0]); cmis++; end
|
||||
end
|
||||
end
|
||||
$display("[zint] final compare: Z %0d/%0d mismatch, COLOR %0d/%0d mismatch (written px=%0d)", zmis, zchk, cmis, cchk, cchk);
|
||||
end
|
||||
|
||||
begin
|
||||
string fdump;
|
||||
if ($value$plusargs("FBDUMP=%s", fdump)) begin
|
||||
int fh;
|
||||
fh = $fopen(fdump, "w");
|
||||
if (fh == 0) begin
|
||||
$error("[zint] could not open FBDUMP '%s'", fdump);
|
||||
errors++;
|
||||
end else begin
|
||||
for (int o=0;o<NPX;o++) begin
|
||||
logic [31:0] cs;
|
||||
cs = shmem[o>>3][(o[2:0])*32+:32];
|
||||
$fdisplay(fh, "%08x", cs);
|
||||
end
|
||||
$fclose(fh);
|
||||
$display("[zint] dumped final COLOR FB (%0dx%0d) -> %s", W, H, fdump);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
$display("[tb_top_psmct32_sh3_zint640] fed=%0d pass=%0d drops=%0d markers=%0d drains(r/f)=%0d/%0d col_ovf=%0d bresp_err=%0d errors=%0d",
|
||||
frags_fed, sb_pass, g_drops, eof_count, fd_rises, fd_falls, col_ovf, bresp_err, errors);
|
||||
$display("[zint] map: COLOR 0x%0h..0x%0h Z 0x%0h..0x%0h", COLBASE, COLBASE+COL_TOP, ZBASE, ZBASE+Z_TOP);
|
||||
if (errors==0) $display("[tb_top_psmct32_sh3_zint640] PASS"); else $display("[tb_top_psmct32_sh3_zint640] FAIL");
|
||||
$finish;
|
||||
end
|
||||
initial begin #400000000; $error("[tb_top_psmct32_sh3_zint640] TIMEOUT"); $finish; end
|
||||
endmodule : tb_top_psmct32_sh3_zint640
|
||||
@@ -0,0 +1,673 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_sh3_zint640_shared (Ch360 — four-epoch real-raster persistent-Z integration)
|
||||
//
|
||||
// Codex's pre-fit gate: prove the persistent-Z ROP through the SAME path the de25 top wires (GS_SH3_LPDDR_FB_Z):
|
||||
// REAL demo (feeder + raster, authz sh3_zs640c12 NATIVE-640x480 strong-reject scene) -> gs_lpddr_zc_emit -> REAL de25 arbiters
|
||||
// (gs_lpddr_wr_arb s0=color / s2=Z-write ; gs_lpddr_rd_arb s3=Z-read) -> ONE shared behavioral LPDDR
|
||||
// with randomized backpressure. Unlike tb_top_psmct32_sh3_zarb (which fed a handshake-throttled trace),
|
||||
// the raster CANNOT honor g_ready, so this is the gate that catches request-FIFO fragment DROPS.
|
||||
//
|
||||
// Asserts (Codex list):
|
||||
// * final LPDDR color + Z == independent clamp16 persistent-Z scoreboard over the ACTUAL emitted fragments;
|
||||
// * zero fragment drops (g_valid && !g_ready) — the whole reason this sim exists;
|
||||
// * exactly one ordered scene marker (sh3_fb_flush) per accepted GO; no fragment after a marker within a scene;
|
||||
// * Z cache invalidated (clear_done) after preclear and before the first GO; Z persists across epochs;
|
||||
// * a FRESH ordered frame_drained per epoch; scanout never referenced during render (rd_arb s0/s1/s2 idle);
|
||||
// * merged AW only in color/Z ranges, merged AR only in Z range (disjoint-map monitor).
|
||||
// LOCAL/gitignored fixtures; skip-guard if absent.
|
||||
`ifndef SH3_SHARED_PARAMS
|
||||
`error "Define SH3_SHARED_PARAMS as a quoted generated params header"
|
||||
`endif
|
||||
`ifndef SH3_SHARED_TAG
|
||||
`error "Define SH3_SHARED_TAG as a quoted fixture tag"
|
||||
`endif
|
||||
`ifndef SH3_SHARED_EPOCH_TABLE
|
||||
`error "Define SH3_SHARED_EPOCH_TABLE as a quoted generated descriptor table"
|
||||
`endif
|
||||
`ifndef SH3_SHARED_RECIP_BITS
|
||||
`define SH3_SHARED_RECIP_BITS 11
|
||||
`endif
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_sh3_zint640_shared;
|
||||
`ifdef SH3_FAST_GRAD
|
||||
// Full-frame fidelity runs need the quotient, not cycle-accurate setup
|
||||
// latency. The combinational simulation path is signed-/ bit-exact to
|
||||
// gs_grad_divider and avoids 64 iterations for each of 20 attributes.
|
||||
localparam bit TB_GRAD_SEQ = 1'b0;
|
||||
localparam int TB_GRAD_CYCLES = 1;
|
||||
`elsif SH3_BOARD_GRAD
|
||||
// Production Ch417 configuration: the same registered-numerator
|
||||
// combinational divider used by the DE25 top, including its complete
|
||||
// five-cycle settle contract. This mode is the pre-fit control-path gate;
|
||||
// SH3_FAST_GRAD remains useful for quicker framebuffer iterations.
|
||||
localparam bit TB_GRAD_SEQ = 1'b0;
|
||||
localparam int TB_GRAD_CYCLES = 5;
|
||||
`else
|
||||
localparam bit TB_GRAD_SEQ = 1'b1;
|
||||
localparam int TB_GRAD_CYCLES = 4;
|
||||
`endif
|
||||
integer debug_tfx_samples = 0;
|
||||
always @(posedge clk) begin
|
||||
if ($test$plusargs("DEBUG_TFX") && dut.u_gs.comb_state_r == 3'd2 && debug_tfx_samples < 8) begin
|
||||
$display("[zint][tfx] tex0=%016x tfx=%0d mod=%0d tex=%08x interp=%08x cs_prev=%08x",
|
||||
dut.u_gs.tex0_1_q, dut.u_gs.comb_tfx, dut.u_gs.comb_modulate,
|
||||
dut.u_gs.s1_tex_color, dut.u_gs.comb_interp_color, dut.u_gs.comb_cs_r);
|
||||
debug_tfx_samples = debug_tfx_samples + 1;
|
||||
end
|
||||
end
|
||||
`include `SH3_SHARED_PARAMS
|
||||
localparam string FIXTURE_TAG = `SH3_SHARED_TAG;
|
||||
localparam int MAX_EPOCHS = 1024;
|
||||
localparam int W = FBPXW, H = FBH;
|
||||
localparam int NPX = W*H;
|
||||
localparam int NEP = N_EPOCHS;
|
||||
localparam [31:0] COLBASE = 32'h0000_0000, ZBASE = 32'h0014_0000;
|
||||
localparam int COL_TOP = NPX*4; // color region byte-size (disjoint below ZBASE)
|
||||
localparam int Z_TOP = NPX*2; // Z region byte-size
|
||||
localparam int MEMBEATS = 65536; // shared LPDDR: covers ZBASE + Z_TOP
|
||||
|
||||
// per-epoch expected CRC / records
|
||||
// Ch360 — four epochs share one texture: EPk_REUSE=1 means the epoch runs on the resident cache without a fill.
|
||||
logic [31:0] EP_CRC [0:MAX_EPOCHS-1]; int EP_REC [0:MAX_EPOCHS-1]; bit EP_REUSE [0:MAX_EPOCHS-1]; int fills;
|
||||
initial begin
|
||||
`include `SH3_SHARED_EPOCH_TABLE
|
||||
fills=0;
|
||||
end
|
||||
|
||||
`ifdef SH3_SHARED_REQ_DEPTH
|
||||
localparam int TB_REQ_DEPTH = `SH3_SHARED_REQ_DEPTH;
|
||||
`else
|
||||
localparam int TB_REQ_DEPTH = 1024; // Ch359 production depth (Codex: cold-Z burst peaks at 478)
|
||||
`endif
|
||||
logic clk=0; always #5 clk=~clk;
|
||||
logic emif_clk=0; always #2 emif_clk=~emif_clk;
|
||||
logic rst_n; logic fb_commit=0; always #13 fb_commit=~fb_commit;
|
||||
int errors; initial errors=0;
|
||||
|
||||
// clamp16 (vendored PCSX2 PSMZ16S source-Z saturation)
|
||||
function automatic logic [15:0] cl16(input logic [31:0] z); cl16=(|z[31:16])?16'hFFFF:z[15:0]; endfunction
|
||||
|
||||
// ===== bram-top (FB_LPDDR_ONLY, AUTOSTART off), 384x381 =====
|
||||
logic core_go; logic [7:0] r,g,b; logic hsync,vsync_o,de;
|
||||
logic core_halt,dma_done_seen,frame_seen,raster_overflow,frame_toggle,dma_done_toggle;
|
||||
logic feeder_go_tb, feeder_ready_tb; logic [15:0] feeder_records_w; logic [31:0] feeder_waits_w;
|
||||
logic gs_tex_rd_en_o; logic [31:0] gs_tex_rd_addr_o; logic [31:0] tex_cache_data; logic tex_cache_ready;
|
||||
logic [31:0] tex_cache_hits, tex_bram_hits;
|
||||
logic flush_emit_w; logic [31:0] flush_addr_w; logic [31:0] flush_color32_w; logic [3:0] flush_be_w; logic [5:0] flush_psm_w;
|
||||
logic [11:0] flush_x_w, flush_y_w; logic [31:0] flush_z_w;
|
||||
logic stg_we; logic [11:0] stg_waddr; logic [63:0] stg_wdata;
|
||||
// Optional HPS-resident palette path. Normal shared-fixture runs hold it idle;
|
||||
// the runtime-CLUT target drives a complete palette before each epoch.
|
||||
logic runtime_clut_wr_en, runtime_clut_busy;
|
||||
logic [7:0] runtime_clut_wr_idx;
|
||||
logic [31:0] runtime_clut_wr_data;
|
||||
logic [31:0] runtime_pal [0:255];
|
||||
logic flush_abe_w;
|
||||
logic [16:0] flush_alpha_w;
|
||||
logic flush_ztest_w, flush_zmsk_w; logic [1:0] flush_ztst_w;
|
||||
integer frag_fh;
|
||||
integer frag_epoch;
|
||||
|
||||
top_psmct32_raster_demo_bram #(
|
||||
.H_ACTIVE(FBPXW), .V_ACTIVE(FBH),
|
||||
.VRAM_BYTES(VRAM_BYTES_P), .VRAM_ENABLE_READ2(1'b0), .PSMCT32_SWIZZLE(1'b0),
|
||||
.COMBINED_TAZ(1'b0), .TILE_LOCAL(1'b0), .TILE_COLS(1), .TILE_ROWS(1),
|
||||
.TILE_MULTIPRIM(1'b0), .TILE_PRIM_COUNT(1), .TILE_FIFO_DEPTH(8),
|
||||
.BIN_BUFFER_ENABLE(1'b0), .HEARTBEAT_SPLICE_ENABLE(1'b0),
|
||||
.FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(STG_WORDS), .FEEDER_AUTOSTART(1'b0),
|
||||
.PERSPECTIVE_CORRECT(1'b1), .PERSP_RECIP_IDX_BITS(`SH3_SHARED_RECIP_BITS),
|
||||
`ifdef SH3_SUBPIXEL_XY
|
||||
.SUBPIXEL_XY(1'b1),
|
||||
`endif
|
||||
`ifdef SH3_SHARED_NO_BILINEAR
|
||||
.BILINEAR_ENABLE(1'b0), .PALETTE_BILINEAR(1'b0),
|
||||
`else
|
||||
.BILINEAR_ENABLE(1'b1), .PALETTE_BILINEAR(1'b1),
|
||||
`endif
|
||||
.GRAD_SEQ_DIVIDER(TB_GRAD_SEQ), .GRAD_DIV_CYCLES(TB_GRAD_CYCLES),
|
||||
.GS_LPDDR_TEX(1'b1), .TEX_VRAM_BASE(TEX_VRAM_BASE), .TEX_CACHE_BYTES(TEX_BYTES),
|
||||
.CLUT_CSM1_ENABLE(1'b1), .RAM_SIZE_BYTES(32 * 1024), .FB_LPDDR_ONLY(1'b1)
|
||||
) dut (
|
||||
.clk(clk), .rst_n(rst_n), .core_go(core_go),
|
||||
.r(r), .g(g), .b(b), .hsync(hsync), .vsync(vsync_o), .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),
|
||||
.feeder_stg_we_i(stg_we), .feeder_stg_waddr_i(stg_waddr), .feeder_stg_wdata_i(stg_wdata),
|
||||
.feeder_go_i(feeder_go_tb), .feeder_ready_o(feeder_ready_tb),
|
||||
.feeder_records_o(feeder_records_w), .feeder_waits_o(feeder_waits_w),
|
||||
.flush_emit_o(flush_emit_w), .flush_addr_o(flush_addr_w),
|
||||
.flush_pix16_o(), .flush_color32_o(flush_color32_w), .flush_be_o(flush_be_w), .flush_psm_o(flush_psm_w),
|
||||
.flush_x_o(flush_x_w), .flush_y_o(flush_y_w), .flush_z_o(flush_z_w),
|
||||
.flush_abe_o(flush_abe_w), .flush_alpha_o(flush_alpha_w),
|
||||
.flush_ztest_o(flush_ztest_w), .flush_ztst_o(flush_ztst_w), .flush_zmsk_o(flush_zmsk_w),
|
||||
.gs_tex_rd_en_o(gs_tex_rd_en_o), .gs_tex_rd_addr_o(gs_tex_rd_addr_o),
|
||||
.tex_cache_data_i(tex_cache_data), .tex_cache_ready_i(tex_cache_ready),
|
||||
.tex_cache_hits_o(tex_cache_hits), .tex_bram_hits_o(tex_bram_hits),
|
||||
.runtime_clut_wr_en_i(runtime_clut_wr_en), .runtime_clut_wr_idx_i(runtime_clut_wr_idx),
|
||||
.runtime_clut_wr_data_i(runtime_clut_wr_data), .runtime_clut_busy_i(runtime_clut_busy)
|
||||
);
|
||||
|
||||
// texture cache + behavioral texture LPDDR (reloaded per epoch)
|
||||
logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid; logic [7:0] arlen; logic [2:0] arsize;
|
||||
logic arvalid, arready; logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready;
|
||||
logic fill_start, fill_done; logic [31:0] fill_beats, fill_bytes, tex_rd_errs, fill_crc_w;
|
||||
gs_texture_cache #(.LPDDR_TEX_BASE(LPDDR_TEX_BASE), .TEX_VRAM_BASE(TEX_VRAM_BASE),
|
||||
.TEX_BYTES(TEX_BYTES), .N_BEATS(N_BEATS)) u_cache (
|
||||
.axi_clk(clk), .axi_rst_n(rst_n), .fill_start(fill_start), .fill_done(fill_done),
|
||||
.fill_beats(fill_beats), .fill_bytes(fill_bytes), .rd_errs(tex_rd_errs), .fill_crc(fill_crc_w),
|
||||
.araddr(araddr), .arburst(arburst), .arid(arid), .arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready),
|
||||
.rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready),
|
||||
.sample_clk(clk), .tex_rd_en(gs_tex_rd_en_o), .tex_rd_addr(gs_tex_rd_addr_o),
|
||||
.tex_rd_data(tex_cache_data), .tex_ready(tex_cache_ready)
|
||||
);
|
||||
logic [31:0] lpddr_mem [0:(TEX_BYTES/4)-1];
|
||||
typedef enum logic [1:0] { S_IDLE, S_WAIT, S_DATA } sstate_t; sstate_t sst; logic [3:0] tdly; int tbeat;
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin sst<=S_IDLE; arready<=0; rvalid<=0; rlast<=0; rresp<=0; rdata<=0; tdly<=0; end
|
||||
else begin arready<=0; rvalid<=0; rlast<=0;
|
||||
case (sst)
|
||||
S_IDLE: if (arvalid) begin arready<=1; tbeat<=(araddr-LPDDR_TEX_BASE)>>5; tdly<=4'd2; sst<=S_WAIT; end
|
||||
S_WAIT: if (tdly==0) sst<=S_DATA; else tdly<=tdly-1'b1;
|
||||
S_DATA: if (rready) begin for (int w=0;w<8;w++) rdata[w*32 +: 32]<=lpddr_mem[tbeat*8+w];
|
||||
rresp<=2'b00; rvalid<=1; rlast<=1; sst<=S_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// ===== render epoch: one ordered scene marker (sh3_fb_flush) per accepted GO =====
|
||||
logic fb_flush=0, render_inflight=0, feeder_ready_q=0; int eof_count;
|
||||
wire feeder_ready_rise = feeder_ready_tb && !feeder_ready_q;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin render_inflight<=0; feeder_ready_q<=0; fb_flush<=0; eof_count<=0; end
|
||||
else begin feeder_ready_q<=feeder_ready_tb; fb_flush<=1'b0;
|
||||
if (feeder_go_tb && feeder_ready_tb) render_inflight<=1'b1;
|
||||
if (render_inflight && feeder_ready_rise) begin fb_flush<=1'b1; render_inflight<=1'b0; eof_count<=eof_count+1; end
|
||||
end
|
||||
end
|
||||
|
||||
// ===== gs_lpddr_zc_emit — Z-then-color emit, fed by the REAL raster (NO handshake) =====
|
||||
logic zc_enable, clear_start, clear_done, frame_drained;
|
||||
wire [15:0] g_zq = cl16(flush_z_w);
|
||||
wire frag_v = flush_emit_w && (flush_psm_w==6'h00);
|
||||
wire g_valid = frag_v || fb_flush;
|
||||
wire g_scene = fb_flush;
|
||||
wire [11:0] g_x = flush_x_w, g_y = flush_y_w;
|
||||
wire [31:0] g_color = flush_color32_w;
|
||||
wire [16:0] g_alpha = flush_alpha_w;
|
||||
wire [3:0] g_be = flush_be_w;
|
||||
wire g_ready;
|
||||
// Z AXI
|
||||
logic [31:0] z_araddr; logic [7:0] z_arlen; logic [2:0] z_arsize; logic [1:0] z_arburst; logic z_arvalid, z_arready;
|
||||
logic [255:0] z_rdata; logic [1:0] z_rresp; logic z_rlast, z_rvalid, z_rready;
|
||||
logic [31:0] z_awaddr; logic [7:0] z_awlen; logic [2:0] z_awsize; logic [1:0] z_awburst; logic z_awvalid, z_awready;
|
||||
logic [255:0] z_wdata; logic [31:0] z_wstrb; logic z_wlast, z_wvalid, z_wready; logic z_bvalid, z_bready; logic [1:0] z_bresp;
|
||||
logic [31:0] d_araddr; logic [7:0] d_arlen; logic [2:0] d_arsize; logic [1:0] d_arburst; logic d_arvalid, d_arready;
|
||||
logic [255:0] d_rdata; logic [1:0] d_rresp; logic d_rlast, d_rvalid, d_rready;
|
||||
// Color AXI
|
||||
logic [31:0] c_awaddr; logic [7:0] c_awlen; logic [2:0] c_awsize; logic [1:0] c_awburst; logic c_awvalid, c_awready;
|
||||
logic [255:0] c_wdata; logic [31:0] c_wstrb; logic c_wlast, c_wvalid, c_wready; logic c_bvalid, c_bready; logic [1:0] c_bresp;
|
||||
logic [31:0] z_beats_read, z_beats_written, c_beats_written, col_ovf, bresp_err; logic zc_idle;
|
||||
gs_lpddr_zc_emit #(.COLBASE(COLBASE), .ZBASE(ZBASE), .FB_PXW(W), .FB_H(H), .REQ_DEPTH(TB_REQ_DEPTH), .COL_DEPTH(128)) u_zc (
|
||||
.gs_clk(clk), .gs_rst_n(rst_n), .enable(zc_enable),
|
||||
.g_valid(g_valid), .g_ready(g_ready), .g_x(g_x), .g_y(g_y), .g_zq(g_zq), .g_zmsk(flush_zmsk_w),
|
||||
.g_ztest(flush_ztest_w), .g_ztst(flush_ztst_w), .g_color(g_color), .g_alpha(g_alpha), .g_be(g_be), .g_scene(g_scene),
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n), .clear_start(clear_start), .clear_done(clear_done), .frame_drained(frame_drained),
|
||||
.z_araddr(z_araddr), .z_arlen(z_arlen), .z_arsize(z_arsize), .z_arburst(z_arburst), .z_arvalid(z_arvalid), .z_arready(z_arready),
|
||||
.z_rdata(z_rdata), .z_rresp(z_rresp), .z_rlast(z_rlast), .z_rvalid(z_rvalid), .z_rready(z_rready),
|
||||
.d_araddr(d_araddr), .d_arlen(d_arlen), .d_arsize(d_arsize), .d_arburst(d_arburst), .d_arvalid(d_arvalid), .d_arready(d_arready),
|
||||
.d_rdata(d_rdata), .d_rresp(d_rresp), .d_rlast(d_rlast), .d_rvalid(d_rvalid), .d_rready(d_rready),
|
||||
.z_awaddr(z_awaddr), .z_awlen(z_awlen), .z_awsize(z_awsize), .z_awburst(z_awburst), .z_awvalid(z_awvalid), .z_awready(z_awready),
|
||||
.z_wdata(z_wdata), .z_wstrb(z_wstrb), .z_wlast(z_wlast), .z_wvalid(z_wvalid), .z_wready(z_wready),
|
||||
.z_bvalid(z_bvalid), .z_bready(z_bready), .z_bresp(z_bresp),
|
||||
.c_awaddr(c_awaddr), .c_awlen(c_awlen), .c_awsize(c_awsize), .c_awburst(c_awburst), .c_awvalid(c_awvalid), .c_awready(c_awready),
|
||||
.c_wdata(c_wdata), .c_wstrb(c_wstrb), .c_wlast(c_wlast), .c_wvalid(c_wvalid), .c_wready(c_wready),
|
||||
.c_bvalid(c_bvalid), .c_bready(c_bready), .c_bresp(c_bresp),
|
||||
.z_beats_read(z_beats_read), .z_beats_written(z_beats_written), .c_beats_written(c_beats_written),
|
||||
.col_ovf(col_ovf), .bresp_err(bresp_err), .idle(zc_idle)
|
||||
);
|
||||
|
||||
// Ch359 DIAGNOSTIC — request-FIFO occupancy high-water (write-domain binary pointers, hierarchical):
|
||||
// sizes the burst that overflows REQ_DEPTH=256 (would a deeper FIFO absorb it, or is the ep0 dirty-miss
|
||||
// streak service-rate-bound?). Sampled on the write clock; wbin-rbin_sampled is a safe over-estimate.
|
||||
// (Codex correction: occupancy must be computed ENTIRELY in the write clock domain — wbin vs the
|
||||
// write-domain-SYNCHRONIZED read pointer rgray_s2 converted gray->binary — with the modulus derived from the
|
||||
// selected depth, not a hard-coded 2*256.)
|
||||
int req_hiwater; logic hw_clear;
|
||||
function automatic int g2b(input int g);
|
||||
int b; b=g; b^=b>>1; b^=b>>2; b^=b>>4; b^=b>>8; b^=b>>16; return b;
|
||||
endfunction
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) req_hiwater <= 0;
|
||||
else if (hw_clear) req_hiwater <= 0;
|
||||
else begin
|
||||
int occ;
|
||||
occ = (int'(u_zc.u_req.wbin) - g2b(int'(u_zc.u_req.rgray_s2))) % (2*TB_REQ_DEPTH);
|
||||
if (occ < 0) occ += 2*TB_REQ_DEPTH;
|
||||
if (occ > req_hiwater) req_hiwater <= occ;
|
||||
end
|
||||
end
|
||||
// fragment-drop guard (design clk): the request FIFO silently drops when g_ready is low
|
||||
int g_drops; int frags_fed;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin g_drops<=0; frags_fed<=0; end
|
||||
else begin
|
||||
if (frag_v && !g_ready) g_drops<=g_drops+1;
|
||||
if (frag_v && g_ready) frags_fed<=frags_fed+1;
|
||||
end
|
||||
end
|
||||
// clear ordering: clear_done must rise before the first GO. no psm0 fragment before clear_done.
|
||||
logic saw_clear_done=0, first_go_done=0; int pre_clear_frags;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin saw_clear_done<=0; pre_clear_frags<=0; end
|
||||
else begin
|
||||
if (clear_done) saw_clear_done<=1;
|
||||
if (frag_v && !saw_clear_done) pre_clear_frags<=pre_clear_frags+1;
|
||||
end
|
||||
end
|
||||
|
||||
// ===== REAL de25 write arbiter: s0=color, s2=Z-write ; s1/s3 inert =====
|
||||
logic [29:0] m_awaddr; logic [1:0] m_awburst; logic [6:0] m_awid; logic [7:0] m_awlen; logic [2:0] m_awsize;
|
||||
logic m_awvalid, m_awready; logic [255:0] m_wdata; logic [31:0] m_wstrb; logic m_wlast, m_wvalid, m_wready;
|
||||
logic m_bvalid, m_bready; logic [1:0] m_bresp;
|
||||
gs_lpddr_wr_arb u_wr_arb (
|
||||
.clk(emif_clk), .rst_n(rst_n),
|
||||
.s0_awaddr(c_awaddr[29:0]), .s0_awburst(c_awburst), .s0_awid(7'd0), .s0_awlen(c_awlen), .s0_awsize(c_awsize),
|
||||
.s0_awvalid(c_awvalid), .s0_awready(c_awready), .s0_wdata(c_wdata), .s0_wstrb(c_wstrb), .s0_wlast(c_wlast),
|
||||
.s0_wvalid(c_wvalid), .s0_wready(c_wready), .s0_bresp(c_bresp), .s0_bvalid(c_bvalid), .s0_bready(c_bready),
|
||||
.s1_awaddr(30'd0), .s1_awburst(2'b01), .s1_awid(7'd1), .s1_awlen(8'd0), .s1_awsize(3'b101),
|
||||
.s1_awvalid(1'b0), .s1_awready(), .s1_wdata(256'd0), .s1_wstrb(32'd0), .s1_wlast(1'b0),
|
||||
.s1_wvalid(1'b0), .s1_wready(), .s1_bresp(), .s1_bvalid(), .s1_bready(1'b0),
|
||||
.s2_awaddr(z_awaddr[29:0]), .s2_awburst(z_awburst), .s2_awid(7'd2), .s2_awlen(z_awlen), .s2_awsize(z_awsize),
|
||||
.s2_awvalid(z_awvalid), .s2_awready(z_awready), .s2_wdata(z_wdata), .s2_wstrb(z_wstrb), .s2_wlast(z_wlast),
|
||||
.s2_wvalid(z_wvalid), .s2_wready(z_wready), .s2_bresp(z_bresp), .s2_bvalid(z_bvalid), .s2_bready(z_bready),
|
||||
.s3_awaddr(30'd0), .s3_awburst(2'b01), .s3_awid(7'd3), .s3_awlen(8'd0), .s3_awsize(3'b101),
|
||||
.s3_awvalid(1'b0), .s3_awready(), .s3_wdata(256'd0), .s3_wstrb(32'd0), .s3_wlast(1'b0),
|
||||
.s3_wvalid(1'b0), .s3_wready(), .s3_bresp(), .s3_bvalid(), .s3_bready(1'b0),
|
||||
.m_awaddr(m_awaddr), .m_awburst(m_awburst), .m_awid(m_awid), .m_awlen(m_awlen), .m_awsize(m_awsize),
|
||||
.m_awvalid(m_awvalid), .m_awready(m_awready), .m_wdata(m_wdata), .m_wstrb(m_wstrb), .m_wlast(m_wlast),
|
||||
.m_wvalid(m_wvalid), .m_wready(m_wready), .m_bvalid(m_bvalid), .m_bready(m_bready), .m_bresp(m_bresp)
|
||||
);
|
||||
|
||||
// ===== REAL de25 read arbiter: s3=Z-read (mirrors de25 reload port) ; s0/s1/s2 idle during render =====
|
||||
logic [29:0] m_araddr; logic [1:0] m_arburst; logic [6:0] m_arid; logic [7:0] m_arlen; logic [2:0] m_arsize;
|
||||
logic m_arvalid, m_arready; logic [255:0] m_rdata; logic [1:0] m_rresp; logic m_rlast, m_rvalid, m_rready;
|
||||
gs_lpddr_rd_arb u_rd_arb (
|
||||
.clk(emif_clk), .rst_n(rst_n),
|
||||
.s0_araddr(30'd0), .s0_arburst(2'b01), .s0_arid(7'd0), .s0_arlen(8'd0), .s0_arsize(3'b101),
|
||||
.s0_arvalid(1'b0), .s0_arready(), .s0_rdata(), .s0_rresp(), .s0_rlast(), .s0_rvalid(), .s0_rready(1'b0),
|
||||
.s1_araddr(30'd0), .s1_arburst(2'b01), .s1_arid(7'd1), .s1_arlen(8'd0), .s1_arsize(3'b101),
|
||||
.s1_arvalid(1'b0), .s1_arready(), .s1_rdata(), .s1_rresp(), .s1_rlast(), .s1_rvalid(), .s1_rready(1'b0),
|
||||
.s2_araddr(30'd0), .s2_arburst(2'b01), .s2_arid(7'd2), .s2_arlen(8'd0), .s2_arsize(3'b101),
|
||||
.s2_arvalid(1'b0), .s2_arready(), .s2_rdata(), .s2_rresp(), .s2_rlast(), .s2_rvalid(), .s2_rready(1'b0),
|
||||
.s3_araddr(z_araddr[29:0]), .s3_arburst(z_arburst), .s3_arid(7'd3), .s3_arlen(z_arlen), .s3_arsize(z_arsize),
|
||||
.s3_arvalid(z_arvalid), .s3_arready(z_arready), .s3_rdata(z_rdata), .s3_rresp(z_rresp), .s3_rlast(z_rlast),
|
||||
.s3_rvalid(z_rvalid), .s3_rready(z_rready),
|
||||
.s4_araddr(d_araddr[29:0]), .s4_arburst(d_arburst), .s4_arid(7'd4), .s4_arlen(d_arlen), .s4_arsize(d_arsize),
|
||||
.s4_arvalid(d_arvalid), .s4_arready(d_arready), .s4_rdata(d_rdata), .s4_rresp(d_rresp), .s4_rlast(d_rlast),
|
||||
.s4_rvalid(d_rvalid), .s4_rready(d_rready),
|
||||
.m_araddr(m_araddr), .m_arburst(m_arburst), .m_arid(m_arid), .m_arlen(m_arlen), .m_arsize(m_arsize),
|
||||
.m_arvalid(m_arvalid), .m_arready(m_arready), .m_rdata(m_rdata), .m_rresp(m_rresp), .m_rlast(m_rlast),
|
||||
.m_rvalid(m_rvalid), .m_rready(m_rready)
|
||||
);
|
||||
|
||||
logic [15:0] lf=16'hF00D; always_ff @(posedge emif_clk) lf<={lf[14:0], lf[15]^lf[13]^lf[12]^lf[10]};
|
||||
|
||||
// ===== ONE shared behavioral LPDDR (write + read), address-decoded, random backpressure =====
|
||||
logic [255:0] shmem [0:MEMBEATS-1];
|
||||
logic [29:0] wa; logic [255:0] wd; logic [31:0] ws; logic aw_s, w_s; logic [3:0] bd; logic bp;
|
||||
logic [29:0] ra; logic rp; logic [3:0] rd_dly;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if(!rst_n) begin
|
||||
m_awready<=0; m_wready<=0; m_bvalid<=0; m_bresp<=0; aw_s<=0; w_s<=0; bp<=0; bd<=0;
|
||||
m_arready<=0; m_rvalid<=0; m_rlast<=0; m_rresp<=0; m_rdata<=0; rp<=0; rd_dly<=0;
|
||||
end else begin
|
||||
m_awready<=(!aw_s)?lf[0]:1'b0; m_wready<=(!w_s)?lf[1]:1'b0;
|
||||
if(m_awvalid&&m_awready) begin wa<=m_awaddr; aw_s<=1; m_awready<=0; end
|
||||
if(m_wvalid&&m_wready) begin wd<=m_wdata; ws<=m_wstrb; w_s<=1; m_wready<=0; end
|
||||
if(aw_s&&w_s&&!bp&&!m_bvalid) begin
|
||||
for(int bb=0;bb<32;bb++) if(ws[bb]) shmem[wa>>5][bb*8+:8]<=wd[bb*8+:8];
|
||||
bp<=1; bd<={1'b0,lf[4:2]};
|
||||
end
|
||||
if(bp&&!m_bvalid) begin if(bd==0) begin m_bvalid<=1;m_bresp<=0;bp<=0;aw_s<=0;w_s<=0; end else bd<=bd-1; end
|
||||
if(m_bvalid&&m_bready) m_bvalid<=0;
|
||||
m_arready<=(!rp&&!m_rvalid)?lf[8]:1'b0;
|
||||
if(m_arvalid&&m_arready) begin ra<=m_araddr; rp<=1; rd_dly<={1'b0,lf[7:5]}; m_arready<=0; end
|
||||
if(rp&&!m_rvalid) begin if(rd_dly==0) begin m_rdata<=shmem[ra>>5]; m_rresp<=0; m_rvalid<=1; m_rlast<=1; rp<=0; end else rd_dly<=rd_dly-1; end
|
||||
if(m_rvalid&&m_rready) begin m_rvalid<=0; m_rlast<=0; end
|
||||
end
|
||||
end
|
||||
// disjoint-map monitors on the MERGED master streams
|
||||
always_ff @(posedge emif_clk) if(rst_n) begin
|
||||
if(m_awvalid) begin
|
||||
if(!((m_awaddr<30'(COLBASE+COL_TOP)) || (m_awaddr>=30'(ZBASE) && m_awaddr<30'(ZBASE+Z_TOP))))
|
||||
begin $error("[zint] merged AW %h not in color/Z range",m_awaddr); errors++; end
|
||||
end
|
||||
if(m_arvalid && !((m_araddr<COL_TOP) || (m_araddr>=30'(ZBASE) && m_araddr<30'(ZBASE+Z_TOP)))) begin $error("[zint] merged AR %h not in color/Z range",m_araddr); errors++; end
|
||||
end
|
||||
|
||||
// ===== clamp16 persistent-Z SCOREBOARD over the ACTUAL emitted fragments (design clk) =====
|
||||
// Mirrors gs_lpddr_z_rmw: GEQUAL vs stored, Z_CLEAR=0. Persists across epochs. Gated active after clear_done.
|
||||
logic [15:0] sb_z [0:NPX-1];
|
||||
logic [31:0] sb_col[0:NPX-1];
|
||||
logic sb_wr [0:NPX-1];
|
||||
integer sb_frag, sb_pass;
|
||||
function automatic [7:0] sel_rgb(input [7:0] cs,input [7:0] cd,input [1:0] sel);
|
||||
sel_rgb=(sel==0)?cs:(sel==1)?cd:8'd0;
|
||||
endfunction
|
||||
function automatic [7:0] blend_rgb(input [7:0] cs,input [7:0] cd,input [1:0] aa,input [1:0] bb,input [1:0] cc,input [1:0] dd,input [7:0] as,input [7:0] ad,input [7:0] fix);
|
||||
integer coef, val, op_a, op_b, op_d;
|
||||
begin
|
||||
coef=(cc==0)?((as>128)?128:as):(cc==1)?ad:fix;
|
||||
op_a=sel_rgb(cs,cd,aa); op_b=sel_rgb(cs,cd,bb); op_d=sel_rgb(cs,cd,dd);
|
||||
val=(((op_a-op_b)*coef) >>> 7)+op_d;
|
||||
blend_rgb=(val<0)?0:(val>255)?255:val;
|
||||
end
|
||||
endfunction
|
||||
function automatic [31:0] expected_color(input [31:0] cs,input [31:0] cd,input [16:0] alpha,input [3:0] be);
|
||||
logic [31:0] blended;
|
||||
begin
|
||||
if (!alpha[16]) blended=cs;
|
||||
else blended={cs[31:24],
|
||||
blend_rgb(cs[23:16],cd[23:16],alpha[15:14],alpha[13:12],alpha[11:10],alpha[9:8],cs[31:24],cd[31:24],alpha[7:0]),
|
||||
blend_rgb(cs[15:8], cd[15:8], alpha[15:14],alpha[13:12],alpha[11:10],alpha[9:8],cs[31:24],cd[31:24],alpha[7:0]),
|
||||
blend_rgb(cs[7:0], cd[7:0], alpha[15:14],alpha[13:12],alpha[11:10],alpha[9:8],cs[31:24],cd[31:24],alpha[7:0])};
|
||||
for (int i=0;i<4;i++)
|
||||
expected_color[i*8 +: 8]=be[i]?blended[i*8 +: 8]:cd[i*8 +: 8];
|
||||
end
|
||||
endfunction
|
||||
// no-fragment-after-marker: a fragment must not appear in the same scene after fb_flush (before next GO)
|
||||
logic scene_ended=0; int frag_after_marker;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin
|
||||
sb_frag<=0; sb_pass<=0; scene_ended<=0; frag_after_marker<=0;
|
||||
end else begin
|
||||
if (feeder_go_tb && feeder_ready_tb) scene_ended<=0; // new scene opens
|
||||
if (fb_flush) scene_ended<=1; // ordered marker closes the scene
|
||||
if (frag_v && saw_clear_done) begin
|
||||
int o; logic [15:0] zq;
|
||||
`ifdef SH3_SHARED_TRACE
|
||||
if (frag_fh != 0)
|
||||
$fwrite(frag_fh, "%0d %0d %0d %0d %08x\n", frag_epoch, g_x, g_y, flush_z_w, flush_color32_w);
|
||||
`endif
|
||||
if (scene_ended) frag_after_marker<=frag_after_marker+1; // fragment after this scene's marker = ordering bug
|
||||
o = g_y*W + g_x; zq = cl16(flush_z_w);
|
||||
sb_frag<=sb_frag+1;
|
||||
if (o>=0 && o<NPX) begin
|
||||
if (!flush_ztest_w || (flush_ztst_w==2'd1)
|
||||
|| ((flush_ztst_w==2'd2) && zq >= sb_z[o])
|
||||
|| ((flush_ztst_w==2'd3) && zq > sb_z[o])) begin
|
||||
if (!flush_zmsk_w) sb_z[o]<=zq;
|
||||
sb_col[o]<=expected_color(flush_color32_w,sb_col[o],flush_alpha_w,flush_be_w);
|
||||
sb_wr[o]<=1'b1; sb_pass<=sb_pass+1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// FRESH-DRAIN observer (emif domain)
|
||||
logic fd_q; int fd_rises, fd_falls;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin fd_q<=0; fd_rises<=0; fd_falls<=0; end
|
||||
else begin fd_q<=frame_drained;
|
||||
if (frame_drained && !fd_q) fd_rises<=fd_rises+1;
|
||||
if (!frame_drained && fd_q) fd_falls<=fd_falls+1;
|
||||
end
|
||||
end
|
||||
|
||||
// clear_start: mirror de25 — pulse once at the writer ARM (after preclear, before first GO)
|
||||
logic wr_arm=0, arm_e1, arm_e2, arm_e3;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin arm_e1<=0; arm_e2<=0; arm_e3<=0; clear_start<=0; end
|
||||
else begin arm_e1<=wr_arm; arm_e2<=arm_e1; arm_e3<=arm_e2; clear_start<=(arm_e2 && !arm_e3); end
|
||||
end
|
||||
|
||||
// ---- feeder staging stream (write port), per epoch ----
|
||||
logic [63:0] stg_buf [0:STG_WORDS-1];
|
||||
task automatic stream_list(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/feeder_sh3_%s%0d.mem", FIXTURE_TAG, k);
|
||||
for (int i=0;i<STG_WORDS;i++) stg_buf[i]=64'd0;
|
||||
$readmemh(fn, stg_buf);
|
||||
@(negedge clk);
|
||||
for (int i=0;i<STG_WORDS;i++) begin stg_we<=1'b1; stg_waddr<=i[11:0]; stg_wdata<=stg_buf[i]; @(negedge clk); end
|
||||
stg_we<=1'b0; stg_waddr<=12'd0; stg_wdata<=64'd0; @(negedge clk);
|
||||
if (dut.g_feeder.feeder_stg[0] !== stg_buf[0]) begin
|
||||
$error("[zint] epoch %0d: staged word0=%016x exp %016x", k, dut.g_feeder.feeder_stg[0], stg_buf[0]); errors++; end
|
||||
endtask
|
||||
|
||||
task automatic fill_cache(input int k, input logic [31:0] exp_crc);
|
||||
int d=0;
|
||||
fills=fills+1;
|
||||
@(posedge clk) fill_start<=1'b1; @(posedge clk) fill_start<=1'b0;
|
||||
d=0; while (fill_done && d<1000) begin @(posedge clk); d++; end
|
||||
d=0; while (!fill_done && d<400000) begin @(posedge clk); d++; end
|
||||
if (!fill_done) begin $error("[zint] fill %0d: fill_done never rose", k); errors++; end
|
||||
if (fill_crc_w!==exp_crc) begin $error("[zint] fill %0d: crc=%08x exp %08x", k, fill_crc_w, exp_crc); errors++; end
|
||||
if (tex_rd_errs!==0) begin $error("[zint] fill %0d: rd_errs=%0d", k, tex_rd_errs); errors++; end
|
||||
endtask
|
||||
|
||||
`ifdef SH3_SHARED_RUNTIME_CLUT
|
||||
task automatic load_runtime_palette(input int k);
|
||||
string fn;
|
||||
fn = $sformatf("../../data/top_psmct32_raster_demo/sh3_%s%0d_pal.mem", FIXTURE_TAG, k);
|
||||
$readmemh(fn, runtime_pal);
|
||||
@(negedge clk);
|
||||
runtime_clut_busy <= 1'b1;
|
||||
for (int i=0; i<256; i++) begin
|
||||
runtime_clut_wr_en <= 1'b1;
|
||||
runtime_clut_wr_idx <= i[7:0];
|
||||
runtime_clut_wr_data <= runtime_pal[i];
|
||||
@(negedge clk);
|
||||
end
|
||||
runtime_clut_wr_en <= 1'b0;
|
||||
runtime_clut_wr_idx <= '0;
|
||||
runtime_clut_wr_data <= '0;
|
||||
@(negedge clk);
|
||||
runtime_clut_busy <= 1'b0;
|
||||
@(negedge clk);
|
||||
endtask
|
||||
`endif
|
||||
|
||||
// one-scene runner: GO, wait render+drain, REQUIRE a FRESH ordered frame_drained
|
||||
task automatic run_scene(input int k, input int exp_records);
|
||||
int r0, f0, d=0; logic fd_before; int gd0, fm0;
|
||||
r0=fd_rises; f0=fd_falls; fd_before=frame_drained; gd0=g_drops; fm0=frag_after_marker;
|
||||
@(negedge clk); feeder_go_tb=1'b1; @(negedge clk); feeder_go_tb=1'b0;
|
||||
d=0; while (feeder_ready_tb && d<4000) begin @(posedge clk); d++; end
|
||||
if (feeder_ready_tb!==1'b0) begin $error("[zint] epoch %0d: feeder_ready did not drop after GO", k); errors++; end
|
||||
if (fd_before) begin
|
||||
d=0; while (fd_falls==f0 && d<6000000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_falls==f0) begin $error("[zint] epoch %0d: frame_drained never fell from stale high", k); errors++; end
|
||||
end
|
||||
d=0; while (fd_rises<=r0 && d<6000000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_rises<=r0) begin $error("[zint] epoch %0d: frame_drained never rose", k); errors++; end
|
||||
repeat(100) @(posedge clk);
|
||||
// The extended Ch402 header keeps TEX0 at word 5. Check the complete
|
||||
// 64-bit commit so high-half state such as TFX cannot disappear silently.
|
||||
if (dut.u_gs.tex0_1_q !== stg_buf[5]) begin
|
||||
$error("[zint] epoch %0d: TEX0 commit=%016x exp=%016x", k, dut.u_gs.tex0_1_q, stg_buf[5]); errors++; end
|
||||
if (feeder_records_w!==exp_records) begin $error("[zint] epoch %0d: records=%0d exp %0d", k, feeder_records_w, exp_records); errors++; end
|
||||
if (col_ovf!==0 || bresp_err!==0) begin $error("[zint] epoch %0d: col_ovf=%0d bresp_err=%0d", k, col_ovf, bresp_err); errors++; end
|
||||
if (g_drops!==gd0) begin $error("[zint] epoch %0d: %0d FRAGMENT DROPS (request FIFO overflow)", k, g_drops-gd0); errors++; end
|
||||
if (frag_after_marker!==fm0) begin $error("[zint] epoch %0d: %0d fragment(s) after scene marker (ordering)", k, frag_after_marker-fm0); errors++; end
|
||||
$display("[zint] epoch %0d: fresh drain (f %0d->%0d, r %0d->%0d) records=%0d drops=%0d fed=%0d pass=%0d",
|
||||
k, f0, fd_falls, r0, fd_rises, feeder_records_w, g_drops, frags_fed, sb_pass);
|
||||
$fflush();
|
||||
endtask
|
||||
|
||||
int cold_start_epoch, cold_asset_epoch, resident_asset_epoch;
|
||||
task automatic run_epoch(input int k);
|
||||
int asset_k;
|
||||
string fn;
|
||||
if (k == cold_start_epoch) asset_k = cold_asset_epoch;
|
||||
else if (!EP_REUSE[k]) asset_k = k;
|
||||
else asset_k = resident_asset_epoch;
|
||||
fn=$sformatf("../../data/top_psmct32_raster_demo/sh3_%s%0d_tex_lpddr.mem", FIXTURE_TAG, asset_k);
|
||||
if ((k == cold_start_epoch) || !EP_REUSE[k]) begin
|
||||
$readmemh(fn, lpddr_mem);
|
||||
fill_cache(k, EP_CRC[k]);
|
||||
resident_asset_epoch = asset_k;
|
||||
end else begin
|
||||
// Ch359 — EXPLICIT REUSE: no reload, no fill pulse. Fail-closed residency: the fill CRC register is
|
||||
// only rewritten by a fill, so it must still hold the prior epoch's verified CRC (same shared texture).
|
||||
if (fill_crc_w!==EP_CRC[k]) begin $error("%s epoch %0d: REUSE but resident crc=%08x exp %08x — residency broken", "[zint]", k, fill_crc_w, EP_CRC[k]); errors++; end
|
||||
else $display("%s epoch %0d: REUSE resident texture (crc=0x%08x, no fill)", "[zint]", k, fill_crc_w);
|
||||
end
|
||||
`ifdef SH3_SHARED_RUNTIME_CLUT
|
||||
load_runtime_palette(asset_k);
|
||||
`endif
|
||||
stream_list(k);
|
||||
frag_epoch = k;
|
||||
run_scene(k, EP_REC[k]);
|
||||
endtask
|
||||
|
||||
initial begin
|
||||
int first_epoch, last_epoch, run_epochs, expected_fed;
|
||||
bit check_expected_fed;
|
||||
errors=0; feeder_go_tb=1'b0; fill_start=1'b0; wr_arm=0; zc_enable=1'b1; stg_we=0; stg_waddr=0; stg_wdata=0; hw_clear=1'b0;
|
||||
runtime_clut_wr_en=0; runtime_clut_wr_idx='0; runtime_clut_wr_data='0; runtime_clut_busy=0; frag_epoch=0; frag_fh=0;
|
||||
if (!$value$plusargs("START_EPOCH=%d", first_epoch)) first_epoch=0;
|
||||
if (first_epoch < 0 || first_epoch >= NEP) begin
|
||||
$error("[zint] START_EPOCH=%0d outside 0..%0d", first_epoch, NEP-1); $finish;
|
||||
end
|
||||
if (!$value$plusargs("END_EPOCH=%d", last_epoch)) last_epoch=NEP-1;
|
||||
if (last_epoch < first_epoch || last_epoch >= NEP) begin
|
||||
$error("[zint] END_EPOCH=%0d outside START_EPOCH=%0d..%0d", last_epoch, first_epoch, NEP-1); $finish;
|
||||
end
|
||||
run_epochs = last_epoch-first_epoch+1;
|
||||
check_expected_fed = $value$plusargs("EXPECT_FED=%d", expected_fed);
|
||||
cold_start_epoch = first_epoch;
|
||||
if (!$value$plusargs("COLD_ASSET_EPOCH=%d", cold_asset_epoch)) cold_asset_epoch=first_epoch;
|
||||
if (cold_asset_epoch < 0 || cold_asset_epoch > first_epoch) begin
|
||||
$error("[zint] COLD_ASSET_EPOCH=%0d outside 0..START_EPOCH=%0d", cold_asset_epoch, first_epoch); $finish;
|
||||
end
|
||||
resident_asset_epoch = cold_asset_epoch;
|
||||
`ifdef SH3_SHARED_TRACE
|
||||
frag_fh=$fopen($sformatf("%s_frags.txt", FIXTURE_TAG), "w");
|
||||
if (frag_fh==0) begin $error("[zint] could not open %s fragment trace", FIXTURE_TAG); $finish; end
|
||||
`endif
|
||||
for (int i=0;i<NPX;i++) begin sb_z[i]=16'h0000; sb_col[i]=32'd0; sb_wr[i]=1'b0; end
|
||||
for (int b=0;b<MEMBEATS;b++) shmem[b]=256'd0; // PRECLEAR color+Z EXACTLY ONCE
|
||||
// skip-guard: probe a fixture (via $fopen — avoids the $readmemh range warning)
|
||||
begin int pfh; pfh=$fopen($sformatf("../../data/top_psmct32_raster_demo/sh3_%s0_tex_lpddr.mem", FIXTURE_TAG),"r");
|
||||
if (pfh==0) begin $display("[tb_top_psmct32_sh3_zint640_shared] SKIP — sh3_%s*.mem absent", FIXTURE_TAG); $finish; end
|
||||
$fclose(pfh);
|
||||
end
|
||||
// A late-epoch-only run begins with cold Z and an explicit texture fill.
|
||||
// Reuse epochs name only their feeder list, so COLD_ASSET_EPOCH points
|
||||
// at the earlier descriptor that owns the shared texture and palette.
|
||||
|
||||
rst_n=1'b0; core_go=1'b0; repeat(6) @(posedge clk); rst_n=1'b1; 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);
|
||||
`ifdef SH3_SHARED_RUNTIME_CLUT
|
||||
// Runtime fixtures begin after the HPS has completed boot handoff. This
|
||||
// standalone raster top has no HPS bootlet agent, so model that settled
|
||||
// state while explicitly exercising all palette/list/render transactions.
|
||||
force dut.dma_done_seen = 1'b1;
|
||||
force dut.xfer_busy = 1'b0;
|
||||
`else
|
||||
wait (dma_done_seen==1'b1); repeat(10) @(posedge clk);
|
||||
if (dut.xfer_busy==1'b1) wait (dut.xfer_busy==1'b0);
|
||||
`endif
|
||||
wait (feeder_ready_tb==1'b1); repeat(50) @(posedge clk);
|
||||
|
||||
// ARM (-> clear_start) AFTER preclear, BEFORE first GO. z_rmw's clear_start writes Z_CLEAR to EVERY Z beat
|
||||
// (NBEATS=NPX/16, through wr_arb s2 under random backpressure) then asserts clear_done -> it is the authoritative
|
||||
// Z preclear. Wait for it (the board host likewise gates the first GO on the clear_done status bit).
|
||||
wr_arm<=1'b1;
|
||||
begin int d=0; while (!clear_done && d<600000) begin @(posedge emif_clk); d++; end end
|
||||
if (!clear_done) begin $error("[zint] clear_done never rose after ARM (Z preclear did not finish)"); errors++; end
|
||||
else $display("[zint] Z preclear complete (clear_done), z_beats_written(clear)=%0d", z_beats_written);
|
||||
repeat(40) @(posedge clk);
|
||||
|
||||
// Ch405 focused read-only-Z proof. After the real clear engine has
|
||||
// completed, optionally seed alternating pixels with max depth in both
|
||||
// the shared LPDDR model and independent scoreboard. Authentic fan
|
||||
// fragments (ZMSK=1) must reject on max lanes, blend on zero lanes,
|
||||
// and leave every stored Z lane unchanged.
|
||||
if ($test$plusargs("CHECKER_Z")) begin
|
||||
for (int o=0;o<NPX;o++) begin
|
||||
logic [15:0] seed; int zb, zl;
|
||||
seed = o[0] ? 16'hffff : 16'h0000;
|
||||
sb_z[o] = seed;
|
||||
zb=(ZBASE + o*2)>>5; zl=o&15;
|
||||
shmem[zb][zl*16+:16] = seed;
|
||||
end
|
||||
$display("[zint] CHECKER_Z seeded alternating 0000/ffff after clear");
|
||||
end
|
||||
|
||||
for (int k=first_epoch;k<=last_epoch;k++) begin
|
||||
int fed0, dr0;
|
||||
fed0=frags_fed; dr0=g_drops;
|
||||
run_epoch(k);
|
||||
$display("[zint][sweep] depth=%0d epoch %0d: accepted=%0d drops=%0d req_hiwater=%0d",
|
||||
TB_REQ_DEPTH, k, frags_fed-fed0, g_drops-dr0, req_hiwater);
|
||||
$fflush();
|
||||
@(negedge clk); hw_clear<=1'b1; @(negedge clk); hw_clear<=1'b0; // per-epoch high-water
|
||||
end
|
||||
|
||||
if (pre_clear_frags!==0) begin $error("[zint] %0d psm0 fragment(s) before clear_done", pre_clear_frags); errors++; end
|
||||
if (fd_rises<run_epochs) begin $error("[zint] expected %0d ordered drains: rises=%0d", run_epochs, fd_rises); errors++; end
|
||||
if (fd_falls<(run_epochs-1)) begin $error("[zint] epochs after the first never cleared stale (falls=%0d)", fd_falls); errors++; end
|
||||
if (eof_count!==run_epochs)begin $error("[zint] scene markers=%0d != accepted GOs=%0d", eof_count, run_epochs); errors++; end
|
||||
if (g_drops!==0) begin $error("[zint] TOTAL %0d fragment drops", g_drops); errors++; end
|
||||
if (check_expected_fed && (frags_fed !== expected_fed)) begin
|
||||
$error("[zint] fed=%0d, expected exact regression count %0d", frags_fed, expected_fed);
|
||||
errors++;
|
||||
end
|
||||
// Descriptor-driven residency: shared-texture epochs reuse a verified fill; multi-texture descriptors refill.
|
||||
begin
|
||||
int exp_fills; exp_fills=0;
|
||||
for (int k=first_epoch;k<=last_epoch;k++)
|
||||
if ((k==first_epoch) || !EP_REUSE[k]) exp_fills++;
|
||||
if (fills!==exp_fills) begin $error("[zint] fills=%0d exp=%0d from descriptor reuse flags", fills, exp_fills); errors++; end
|
||||
end
|
||||
|
||||
// ===== final memory compare: shared LPDDR color + Z == scoreboard =====
|
||||
begin
|
||||
int zmis, cmis, zchk, cchk; zmis=0; cmis=0; zchk=0; cchk=0;
|
||||
for (int o=0;o<NPX;o++) begin
|
||||
logic [15:0] zs; logic [31:0] cs; int zb, zl;
|
||||
zb=(ZBASE + o*2)>>5; zl=o&15; zs=shmem[zb][zl*16+:16];
|
||||
zchk++;
|
||||
if (zs !== sb_z[o]) begin if (zmis<10) $error("[zint] Z px%0d got %04x exp %04x", o, zs, sb_z[o]); zmis++; end
|
||||
if (sb_wr[o]) begin
|
||||
cs=shmem[o>>3][(o[2:0])*32+:32]; cchk++;
|
||||
if (cs[23:0] !== sb_col[o][23:0]) begin if (cmis<10) $error("[zint] COL px%0d got %06x exp %06x", o, cs[23:0], sb_col[o][23:0]); cmis++; end
|
||||
end
|
||||
end
|
||||
$display("[zint] final compare: Z %0d/%0d mismatch, COLOR %0d/%0d mismatch (written px=%0d)", zmis, zchk, cmis, cchk, cchk);
|
||||
if (zmis != 0 || cmis != 0) errors += zmis + cmis;
|
||||
end
|
||||
|
||||
begin
|
||||
string fdump;
|
||||
if ($value$plusargs("FBDUMP=%s", fdump)) begin
|
||||
int fh;
|
||||
fh = $fopen(fdump, "w");
|
||||
if (fh == 0) begin
|
||||
$error("[zint] could not open FBDUMP '%s'", fdump);
|
||||
errors++;
|
||||
end else begin
|
||||
for (int o=0;o<NPX;o++) begin
|
||||
logic [31:0] cs;
|
||||
cs = shmem[o>>3][(o[2:0])*32+:32];
|
||||
$fdisplay(fh, "%08x", cs);
|
||||
end
|
||||
$fclose(fh);
|
||||
$display("[zint] dumped final COLOR FB (%0dx%0d) -> %s", W, H, fdump);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
$display("[tb_top_psmct32_sh3_zint640_shared] fed=%0d pass=%0d drops=%0d markers=%0d drains(r/f)=%0d/%0d col_ovf=%0d bresp_err=%0d errors=%0d",
|
||||
frags_fed, sb_pass, g_drops, eof_count, fd_rises, fd_falls, col_ovf, bresp_err, errors);
|
||||
$display("[zint] req-FIFO depth=%0d (diagnostic sweep variant when != 1024)", TB_REQ_DEPTH);
|
||||
$display("[zint] map: COLOR 0x%0h..0x%0h Z 0x%0h..0x%0h", COLBASE, COLBASE+COL_TOP, ZBASE, ZBASE+Z_TOP);
|
||||
if (errors==0) $display("[tb_top_psmct32_sh3_zint640_shared] PASS"); else $display("[tb_top_psmct32_sh3_zint640_shared] FAIL");
|
||||
if (frag_fh != 0) $fclose(frag_fh);
|
||||
$finish;
|
||||
end
|
||||
initial begin #800000000; $error("[tb_top_psmct32_sh3_zint640_shared] TIMEOUT"); $finish; end
|
||||
endmodule : tb_top_psmct32_sh3_zint640_shared
|
||||
@@ -0,0 +1,469 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_sh3_zint640c12 (Ch360 — four-epoch real-raster persistent-Z integration)
|
||||
//
|
||||
// Codex's pre-fit gate: prove the persistent-Z ROP through the SAME path the de25 top wires (GS_SH3_LPDDR_FB_Z):
|
||||
// REAL demo (feeder + raster, authz sh3_zs640c12 NATIVE-640x480 strong-reject scene) -> gs_lpddr_zc_emit -> REAL de25 arbiters
|
||||
// (gs_lpddr_wr_arb s0=color / s2=Z-write ; gs_lpddr_rd_arb s3=Z-read) -> ONE shared behavioral LPDDR
|
||||
// with randomized backpressure. Unlike tb_top_psmct32_sh3_zarb (which fed a handshake-throttled trace),
|
||||
// the raster CANNOT honor g_ready, so this is the gate that catches request-FIFO fragment DROPS.
|
||||
//
|
||||
// Asserts (Codex list):
|
||||
// * final LPDDR color + Z == independent clamp16 persistent-Z scoreboard over the ACTUAL emitted fragments;
|
||||
// * zero fragment drops (g_valid && !g_ready) — the whole reason this sim exists;
|
||||
// * exactly one ordered scene marker (sh3_fb_flush) per accepted GO; no fragment after a marker within a scene;
|
||||
// * Z cache invalidated (clear_done) after preclear and before the first GO; Z persists across epochs;
|
||||
// * a FRESH ordered frame_drained per epoch; scanout never referenced during render (rd_arb s0/s1/s2 idle);
|
||||
// * merged AW only in color/Z ranges, merged AR only in Z range (disjoint-map monitor).
|
||||
// LOCAL/gitignored fixtures; skip-guard if absent.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_sh3_zint640c12;
|
||||
`include "sh3_zs640c12_params.vh" // FBPXW=640, FBH=480, N_EPOCHS=4, EPk_CRC/EPk_NTRIS, TEX_*, ...
|
||||
localparam int W = FBPXW, H = FBH;
|
||||
localparam int NPX = W*H;
|
||||
localparam int NEP = N_EPOCHS;
|
||||
localparam [31:0] COLBASE = 32'h0000_0000, ZBASE = 32'h0014_0000;
|
||||
localparam int COL_TOP = NPX*4; // color region byte-size (disjoint below ZBASE)
|
||||
localparam int Z_TOP = NPX*2; // Z region byte-size
|
||||
localparam int MEMBEATS = 65536; // shared LPDDR: covers ZBASE + Z_TOP
|
||||
|
||||
// per-epoch expected CRC / records
|
||||
// Ch360 — four epochs share one texture: EPk_REUSE=1 means the epoch runs on the resident cache without a fill.
|
||||
logic [31:0] EP_CRC [0:3]; int EP_REC [0:3]; bit EP_REUSE [0:3]; int fills;
|
||||
initial begin
|
||||
EP_CRC[0]=EP0_CRC; EP_CRC[1]=EP1_CRC; EP_CRC[2]=EP2_CRC; EP_CRC[3]=EP3_CRC;
|
||||
EP_REC[0]=EP0_NTRIS; EP_REC[1]=EP1_NTRIS; EP_REC[2]=EP2_NTRIS; EP_REC[3]=EP3_NTRIS;
|
||||
EP_REUSE[0]=EP0_REUSE; EP_REUSE[1]=EP1_REUSE; EP_REUSE[2]=EP2_REUSE; EP_REUSE[3]=EP3_REUSE;
|
||||
fills=0;
|
||||
end
|
||||
|
||||
`ifdef C12_REQ_DEPTH
|
||||
localparam int TB_REQ_DEPTH = `C12_REQ_DEPTH; // Ch359 sim-only depth-sweep variant (Codex-authorized)
|
||||
`else
|
||||
localparam int TB_REQ_DEPTH = 1024; // Ch359 production depth (Codex: cold-Z burst peaks at 478)
|
||||
`endif
|
||||
logic clk=0; always #5 clk=~clk;
|
||||
logic emif_clk=0; always #2 emif_clk=~emif_clk;
|
||||
logic rst_n; logic fb_commit=0; always #13 fb_commit=~fb_commit;
|
||||
int errors; initial errors=0;
|
||||
|
||||
// clamp16 (vendored PCSX2 PSMZ16S source-Z saturation)
|
||||
function automatic logic [15:0] cl16(input logic [31:0] z); cl16=(|z[31:16])?16'hFFFF:z[15:0]; endfunction
|
||||
|
||||
// ===== bram-top (FB_LPDDR_ONLY, AUTOSTART off), 384x381 =====
|
||||
logic core_go; logic [7:0] r,g,b; logic hsync,vsync_o,de;
|
||||
logic core_halt,dma_done_seen,frame_seen,raster_overflow,frame_toggle,dma_done_toggle;
|
||||
logic feeder_go_tb, feeder_ready_tb; logic [15:0] feeder_records_w; logic [31:0] feeder_waits_w;
|
||||
logic gs_tex_rd_en_o; logic [31:0] gs_tex_rd_addr_o; logic [31:0] tex_cache_data; logic tex_cache_ready;
|
||||
logic [31:0] tex_cache_hits, tex_bram_hits;
|
||||
logic flush_emit_w; logic [31:0] flush_addr_w; logic [31:0] flush_color32_w; logic [5:0] flush_psm_w;
|
||||
logic [11:0] flush_x_w, flush_y_w; logic [31:0] flush_z_w;
|
||||
logic stg_we; logic [11:0] stg_waddr; logic [63:0] stg_wdata;
|
||||
|
||||
top_psmct32_raster_demo_bram #(
|
||||
.H_ACTIVE(FBPXW), .V_ACTIVE(FBH),
|
||||
.VRAM_BYTES(VRAM_BYTES_P), .VRAM_ENABLE_READ2(1'b0), .PSMCT32_SWIZZLE(1'b0),
|
||||
.COMBINED_TAZ(1'b0), .TILE_LOCAL(1'b0), .TILE_COLS(1), .TILE_ROWS(1),
|
||||
.TILE_MULTIPRIM(1'b0), .TILE_PRIM_COUNT(1), .TILE_FIFO_DEPTH(8),
|
||||
.BIN_BUFFER_ENABLE(1'b0), .HEARTBEAT_SPLICE_ENABLE(1'b0),
|
||||
.FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(STG_WORDS), .FEEDER_AUTOSTART(1'b0),
|
||||
.PERSPECTIVE_CORRECT(1'b1), .PERSP_RECIP_IDX_BITS(11),
|
||||
.GRAD_SEQ_DIVIDER(1'b1), .GRAD_DIV_CYCLES(4),
|
||||
.GS_LPDDR_TEX(1'b1), .TEX_VRAM_BASE(TEX_VRAM_BASE), .TEX_CACHE_BYTES(TEX_BYTES),
|
||||
.CLUT_CSM1_ENABLE(1'b1), .FB_LPDDR_ONLY(1'b1)
|
||||
) dut (
|
||||
.clk(clk), .rst_n(rst_n), .core_go(core_go),
|
||||
.r(r), .g(g), .b(b), .hsync(hsync), .vsync(vsync_o), .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),
|
||||
.feeder_stg_we_i(stg_we), .feeder_stg_waddr_i(stg_waddr), .feeder_stg_wdata_i(stg_wdata),
|
||||
.feeder_go_i(feeder_go_tb), .feeder_ready_o(feeder_ready_tb),
|
||||
.feeder_records_o(feeder_records_w), .feeder_waits_o(feeder_waits_w),
|
||||
.flush_emit_o(flush_emit_w), .flush_addr_o(flush_addr_w),
|
||||
.flush_pix16_o(), .flush_color32_o(flush_color32_w), .flush_psm_o(flush_psm_w),
|
||||
.flush_x_o(flush_x_w), .flush_y_o(flush_y_w), .flush_z_o(flush_z_w),
|
||||
.gs_tex_rd_en_o(gs_tex_rd_en_o), .gs_tex_rd_addr_o(gs_tex_rd_addr_o),
|
||||
.tex_cache_data_i(tex_cache_data), .tex_cache_ready_i(tex_cache_ready),
|
||||
.tex_cache_hits_o(tex_cache_hits), .tex_bram_hits_o(tex_bram_hits)
|
||||
);
|
||||
|
||||
// texture cache + behavioral texture LPDDR (reloaded per epoch)
|
||||
logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid; logic [7:0] arlen; logic [2:0] arsize;
|
||||
logic arvalid, arready; logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready;
|
||||
logic fill_start, fill_done; logic [31:0] fill_beats, fill_bytes, tex_rd_errs, fill_crc_w;
|
||||
gs_texture_cache #(.LPDDR_TEX_BASE(LPDDR_TEX_BASE), .TEX_VRAM_BASE(TEX_VRAM_BASE),
|
||||
.TEX_BYTES(TEX_BYTES), .N_BEATS(N_BEATS)) u_cache (
|
||||
.axi_clk(clk), .axi_rst_n(rst_n), .fill_start(fill_start), .fill_done(fill_done),
|
||||
.fill_beats(fill_beats), .fill_bytes(fill_bytes), .rd_errs(tex_rd_errs), .fill_crc(fill_crc_w),
|
||||
.araddr(araddr), .arburst(arburst), .arid(arid), .arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready),
|
||||
.rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready),
|
||||
.sample_clk(clk), .tex_rd_en(gs_tex_rd_en_o), .tex_rd_addr(gs_tex_rd_addr_o),
|
||||
.tex_rd_data(tex_cache_data), .tex_ready(tex_cache_ready)
|
||||
);
|
||||
logic [31:0] lpddr_mem [0:(TEX_BYTES/4)-1];
|
||||
typedef enum logic [1:0] { S_IDLE, S_WAIT, S_DATA } sstate_t; sstate_t sst; logic [3:0] tdly; int tbeat;
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin sst<=S_IDLE; arready<=0; rvalid<=0; rlast<=0; rresp<=0; rdata<=0; tdly<=0; end
|
||||
else begin arready<=0; rvalid<=0; rlast<=0;
|
||||
case (sst)
|
||||
S_IDLE: if (arvalid) begin arready<=1; tbeat<=(araddr-LPDDR_TEX_BASE)>>5; tdly<=4'd2; sst<=S_WAIT; end
|
||||
S_WAIT: if (tdly==0) sst<=S_DATA; else tdly<=tdly-1'b1;
|
||||
S_DATA: if (rready) begin for (int w=0;w<8;w++) rdata[w*32 +: 32]<=lpddr_mem[tbeat*8+w];
|
||||
rresp<=2'b00; rvalid<=1; rlast<=1; sst<=S_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// ===== render epoch: one ordered scene marker (sh3_fb_flush) per accepted GO =====
|
||||
logic fb_flush=0, render_inflight=0, feeder_ready_q=0; int eof_count;
|
||||
wire feeder_ready_rise = feeder_ready_tb && !feeder_ready_q;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin render_inflight<=0; feeder_ready_q<=0; fb_flush<=0; eof_count<=0; end
|
||||
else begin feeder_ready_q<=feeder_ready_tb; fb_flush<=1'b0;
|
||||
if (feeder_go_tb && feeder_ready_tb) render_inflight<=1'b1;
|
||||
if (render_inflight && feeder_ready_rise) begin fb_flush<=1'b1; render_inflight<=1'b0; eof_count<=eof_count+1; end
|
||||
end
|
||||
end
|
||||
|
||||
// ===== gs_lpddr_zc_emit — Z-then-color emit, fed by the REAL raster (NO handshake) =====
|
||||
logic zc_enable, clear_start, clear_done, frame_drained;
|
||||
wire [15:0] g_zq = cl16(flush_z_w);
|
||||
wire frag_v = flush_emit_w && (flush_psm_w==6'h00);
|
||||
wire g_valid = frag_v || fb_flush;
|
||||
wire g_scene = fb_flush;
|
||||
wire [11:0] g_x = flush_x_w, g_y = flush_y_w;
|
||||
wire [31:0] g_color = flush_color32_w;
|
||||
wire g_ready;
|
||||
// Z AXI
|
||||
logic [31:0] z_araddr; logic [7:0] z_arlen; logic [2:0] z_arsize; logic [1:0] z_arburst; logic z_arvalid, z_arready;
|
||||
logic [255:0] z_rdata; logic [1:0] z_rresp; logic z_rlast, z_rvalid, z_rready;
|
||||
logic [31:0] z_awaddr; logic [7:0] z_awlen; logic [2:0] z_awsize; logic [1:0] z_awburst; logic z_awvalid, z_awready;
|
||||
logic [255:0] z_wdata; logic [31:0] z_wstrb; logic z_wlast, z_wvalid, z_wready; logic z_bvalid, z_bready; logic [1:0] z_bresp;
|
||||
// Color AXI
|
||||
logic [31:0] c_awaddr; logic [7:0] c_awlen; logic [2:0] c_awsize; logic [1:0] c_awburst; logic c_awvalid, c_awready;
|
||||
logic [255:0] c_wdata; logic [31:0] c_wstrb; logic c_wlast, c_wvalid, c_wready; logic c_bvalid, c_bready; logic [1:0] c_bresp;
|
||||
logic [31:0] z_beats_read, z_beats_written, c_beats_written, col_ovf, bresp_err; logic zc_idle;
|
||||
gs_lpddr_zc_emit #(.COLBASE(COLBASE), .ZBASE(ZBASE), .FB_PXW(W), .FB_H(H), .REQ_DEPTH(TB_REQ_DEPTH), .COL_DEPTH(128)) u_zc (
|
||||
.gs_clk(clk), .gs_rst_n(rst_n), .enable(zc_enable),
|
||||
.g_valid(g_valid), .g_ready(g_ready), .g_x(g_x), .g_y(g_y), .g_zq(g_zq), .g_zmsk(1'b0),
|
||||
.g_ztest(1'b1), .g_ztst(2'd2), .g_color(g_color), .g_be(4'hF), .g_scene(g_scene),
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n), .clear_start(clear_start), .clear_done(clear_done), .frame_drained(frame_drained),
|
||||
.z_araddr(z_araddr), .z_arlen(z_arlen), .z_arsize(z_arsize), .z_arburst(z_arburst), .z_arvalid(z_arvalid), .z_arready(z_arready),
|
||||
.z_rdata(z_rdata), .z_rresp(z_rresp), .z_rlast(z_rlast), .z_rvalid(z_rvalid), .z_rready(z_rready),
|
||||
.z_awaddr(z_awaddr), .z_awlen(z_awlen), .z_awsize(z_awsize), .z_awburst(z_awburst), .z_awvalid(z_awvalid), .z_awready(z_awready),
|
||||
.z_wdata(z_wdata), .z_wstrb(z_wstrb), .z_wlast(z_wlast), .z_wvalid(z_wvalid), .z_wready(z_wready),
|
||||
.z_bvalid(z_bvalid), .z_bready(z_bready), .z_bresp(z_bresp),
|
||||
.c_awaddr(c_awaddr), .c_awlen(c_awlen), .c_awsize(c_awsize), .c_awburst(c_awburst), .c_awvalid(c_awvalid), .c_awready(c_awready),
|
||||
.c_wdata(c_wdata), .c_wstrb(c_wstrb), .c_wlast(c_wlast), .c_wvalid(c_wvalid), .c_wready(c_wready),
|
||||
.c_bvalid(c_bvalid), .c_bready(c_bready), .c_bresp(c_bresp),
|
||||
.z_beats_read(z_beats_read), .z_beats_written(z_beats_written), .c_beats_written(c_beats_written),
|
||||
.col_ovf(col_ovf), .bresp_err(bresp_err), .idle(zc_idle)
|
||||
);
|
||||
|
||||
// Ch359 DIAGNOSTIC — request-FIFO occupancy high-water (write-domain binary pointers, hierarchical):
|
||||
// sizes the burst that overflows REQ_DEPTH=256 (would a deeper FIFO absorb it, or is the ep0 dirty-miss
|
||||
// streak service-rate-bound?). Sampled on the write clock; wbin-rbin_sampled is a safe over-estimate.
|
||||
// (Codex correction: occupancy must be computed ENTIRELY in the write clock domain — wbin vs the
|
||||
// write-domain-SYNCHRONIZED read pointer rgray_s2 converted gray->binary — with the modulus derived from the
|
||||
// selected depth, not a hard-coded 2*256.)
|
||||
int req_hiwater; logic hw_clear;
|
||||
function automatic int g2b(input int g);
|
||||
int b; b=g; b^=b>>1; b^=b>>2; b^=b>>4; b^=b>>8; b^=b>>16; return b;
|
||||
endfunction
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) req_hiwater <= 0;
|
||||
else if (hw_clear) req_hiwater <= 0;
|
||||
else begin
|
||||
int occ;
|
||||
occ = (int'(u_zc.u_req.wbin) - g2b(int'(u_zc.u_req.rgray_s2))) % (2*TB_REQ_DEPTH);
|
||||
if (occ < 0) occ += 2*TB_REQ_DEPTH;
|
||||
if (occ > req_hiwater) req_hiwater <= occ;
|
||||
end
|
||||
end
|
||||
// fragment-drop guard (design clk): the request FIFO silently drops when g_ready is low
|
||||
int g_drops; int frags_fed;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin g_drops<=0; frags_fed<=0; end
|
||||
else begin
|
||||
if (frag_v && !g_ready) g_drops<=g_drops+1;
|
||||
if (frag_v && g_ready) frags_fed<=frags_fed+1;
|
||||
end
|
||||
end
|
||||
// clear ordering: clear_done must rise before the first GO. no psm0 fragment before clear_done.
|
||||
logic saw_clear_done=0, first_go_done=0; int pre_clear_frags;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin saw_clear_done<=0; pre_clear_frags<=0; end
|
||||
else begin
|
||||
if (clear_done) saw_clear_done<=1;
|
||||
if (frag_v && !saw_clear_done) pre_clear_frags<=pre_clear_frags+1;
|
||||
end
|
||||
end
|
||||
|
||||
// ===== REAL de25 write arbiter: s0=color, s2=Z-write ; s1/s3 inert =====
|
||||
logic [29:0] m_awaddr; logic [1:0] m_awburst; logic [6:0] m_awid; logic [7:0] m_awlen; logic [2:0] m_awsize;
|
||||
logic m_awvalid, m_awready; logic [255:0] m_wdata; logic [31:0] m_wstrb; logic m_wlast, m_wvalid, m_wready;
|
||||
logic m_bvalid, m_bready; logic [1:0] m_bresp;
|
||||
gs_lpddr_wr_arb u_wr_arb (
|
||||
.clk(emif_clk), .rst_n(rst_n),
|
||||
.s0_awaddr(c_awaddr[29:0]), .s0_awburst(c_awburst), .s0_awid(7'd0), .s0_awlen(c_awlen), .s0_awsize(c_awsize),
|
||||
.s0_awvalid(c_awvalid), .s0_awready(c_awready), .s0_wdata(c_wdata), .s0_wstrb(c_wstrb), .s0_wlast(c_wlast),
|
||||
.s0_wvalid(c_wvalid), .s0_wready(c_wready), .s0_bresp(c_bresp), .s0_bvalid(c_bvalid), .s0_bready(c_bready),
|
||||
.s1_awaddr(30'd0), .s1_awburst(2'b01), .s1_awid(7'd1), .s1_awlen(8'd0), .s1_awsize(3'b101),
|
||||
.s1_awvalid(1'b0), .s1_awready(), .s1_wdata(256'd0), .s1_wstrb(32'd0), .s1_wlast(1'b0),
|
||||
.s1_wvalid(1'b0), .s1_wready(), .s1_bresp(), .s1_bvalid(), .s1_bready(1'b0),
|
||||
.s2_awaddr(z_awaddr[29:0]), .s2_awburst(z_awburst), .s2_awid(7'd2), .s2_awlen(z_awlen), .s2_awsize(z_awsize),
|
||||
.s2_awvalid(z_awvalid), .s2_awready(z_awready), .s2_wdata(z_wdata), .s2_wstrb(z_wstrb), .s2_wlast(z_wlast),
|
||||
.s2_wvalid(z_wvalid), .s2_wready(z_wready), .s2_bresp(z_bresp), .s2_bvalid(z_bvalid), .s2_bready(z_bready),
|
||||
.s3_awaddr(30'd0), .s3_awburst(2'b01), .s3_awid(7'd3), .s3_awlen(8'd0), .s3_awsize(3'b101),
|
||||
.s3_awvalid(1'b0), .s3_awready(), .s3_wdata(256'd0), .s3_wstrb(32'd0), .s3_wlast(1'b0),
|
||||
.s3_wvalid(1'b0), .s3_wready(), .s3_bresp(), .s3_bvalid(), .s3_bready(1'b0),
|
||||
.m_awaddr(m_awaddr), .m_awburst(m_awburst), .m_awid(m_awid), .m_awlen(m_awlen), .m_awsize(m_awsize),
|
||||
.m_awvalid(m_awvalid), .m_awready(m_awready), .m_wdata(m_wdata), .m_wstrb(m_wstrb), .m_wlast(m_wlast),
|
||||
.m_wvalid(m_wvalid), .m_wready(m_wready), .m_bvalid(m_bvalid), .m_bready(m_bready), .m_bresp(m_bresp)
|
||||
);
|
||||
|
||||
// ===== REAL de25 read arbiter: s3=Z-read (mirrors de25 reload port) ; s0/s1/s2 idle during render =====
|
||||
logic [29:0] m_araddr; logic [1:0] m_arburst; logic [6:0] m_arid; logic [7:0] m_arlen; logic [2:0] m_arsize;
|
||||
logic m_arvalid, m_arready; logic [255:0] m_rdata; logic [1:0] m_rresp; logic m_rlast, m_rvalid, m_rready;
|
||||
gs_lpddr_rd_arb u_rd_arb (
|
||||
.clk(emif_clk), .rst_n(rst_n),
|
||||
.s0_araddr(30'd0), .s0_arburst(2'b01), .s0_arid(7'd0), .s0_arlen(8'd0), .s0_arsize(3'b101),
|
||||
.s0_arvalid(1'b0), .s0_arready(), .s0_rdata(), .s0_rresp(), .s0_rlast(), .s0_rvalid(), .s0_rready(1'b0),
|
||||
.s1_araddr(30'd0), .s1_arburst(2'b01), .s1_arid(7'd1), .s1_arlen(8'd0), .s1_arsize(3'b101),
|
||||
.s1_arvalid(1'b0), .s1_arready(), .s1_rdata(), .s1_rresp(), .s1_rlast(), .s1_rvalid(), .s1_rready(1'b0),
|
||||
.s2_araddr(30'd0), .s2_arburst(2'b01), .s2_arid(7'd2), .s2_arlen(8'd0), .s2_arsize(3'b101),
|
||||
.s2_arvalid(1'b0), .s2_arready(), .s2_rdata(), .s2_rresp(), .s2_rlast(), .s2_rvalid(), .s2_rready(1'b0),
|
||||
.s3_araddr(z_araddr[29:0]), .s3_arburst(z_arburst), .s3_arid(7'd3), .s3_arlen(z_arlen), .s3_arsize(z_arsize),
|
||||
.s3_arvalid(z_arvalid), .s3_arready(z_arready), .s3_rdata(z_rdata), .s3_rresp(z_rresp), .s3_rlast(z_rlast),
|
||||
.s3_rvalid(z_rvalid), .s3_rready(z_rready),
|
||||
.m_araddr(m_araddr), .m_arburst(m_arburst), .m_arid(m_arid), .m_arlen(m_arlen), .m_arsize(m_arsize),
|
||||
.m_arvalid(m_arvalid), .m_arready(m_arready), .m_rdata(m_rdata), .m_rresp(m_rresp), .m_rlast(m_rlast),
|
||||
.m_rvalid(m_rvalid), .m_rready(m_rready)
|
||||
);
|
||||
|
||||
logic [15:0] lf=16'hF00D; always_ff @(posedge emif_clk) lf<={lf[14:0], lf[15]^lf[13]^lf[12]^lf[10]};
|
||||
|
||||
// ===== ONE shared behavioral LPDDR (write + read), address-decoded, random backpressure =====
|
||||
logic [255:0] shmem [0:MEMBEATS-1];
|
||||
logic [29:0] wa; logic [255:0] wd; logic [31:0] ws; logic aw_s, w_s; logic [3:0] bd; logic bp;
|
||||
logic [29:0] ra; logic rp; logic [3:0] rd_dly;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if(!rst_n) begin
|
||||
m_awready<=0; m_wready<=0; m_bvalid<=0; m_bresp<=0; aw_s<=0; w_s<=0; bp<=0; bd<=0;
|
||||
m_arready<=0; m_rvalid<=0; m_rlast<=0; m_rresp<=0; m_rdata<=0; rp<=0; rd_dly<=0;
|
||||
end else begin
|
||||
m_awready<=(!aw_s)?lf[0]:1'b0; m_wready<=(!w_s)?lf[1]:1'b0;
|
||||
if(m_awvalid&&m_awready) begin wa<=m_awaddr; aw_s<=1; m_awready<=0; end
|
||||
if(m_wvalid&&m_wready) begin wd<=m_wdata; ws<=m_wstrb; w_s<=1; m_wready<=0; end
|
||||
if(aw_s&&w_s&&!bp&&!m_bvalid) begin
|
||||
for(int bb=0;bb<32;bb++) if(ws[bb]) shmem[wa>>5][bb*8+:8]<=wd[bb*8+:8];
|
||||
bp<=1; bd<={1'b0,lf[4:2]};
|
||||
end
|
||||
if(bp&&!m_bvalid) begin if(bd==0) begin m_bvalid<=1;m_bresp<=0;bp<=0;aw_s<=0;w_s<=0; end else bd<=bd-1; end
|
||||
if(m_bvalid&&m_bready) m_bvalid<=0;
|
||||
m_arready<=(!rp&&!m_rvalid)?lf[8]:1'b0;
|
||||
if(m_arvalid&&m_arready) begin ra<=m_araddr; rp<=1; rd_dly<={1'b0,lf[7:5]}; m_arready<=0; end
|
||||
if(rp&&!m_rvalid) begin if(rd_dly==0) begin m_rdata<=shmem[ra>>5]; m_rresp<=0; m_rvalid<=1; m_rlast<=1; rp<=0; end else rd_dly<=rd_dly-1; end
|
||||
if(m_rvalid&&m_rready) begin m_rvalid<=0; m_rlast<=0; end
|
||||
end
|
||||
end
|
||||
// disjoint-map monitors on the MERGED master streams
|
||||
always_ff @(posedge emif_clk) if(rst_n) begin
|
||||
if(m_awvalid) begin
|
||||
if(!((m_awaddr<30'(COLBASE+COL_TOP)) || (m_awaddr>=30'(ZBASE) && m_awaddr<30'(ZBASE+Z_TOP))))
|
||||
begin $error("[zint] merged AW %h not in color/Z range",m_awaddr); errors++; end
|
||||
end
|
||||
if(m_arvalid && (m_araddr<30'(ZBASE) || m_araddr>=30'(ZBASE+Z_TOP))) begin $error("[zint] merged AR %h not in Z range",m_araddr); errors++; end
|
||||
end
|
||||
|
||||
// ===== clamp16 persistent-Z SCOREBOARD over the ACTUAL emitted fragments (design clk) =====
|
||||
// Mirrors gs_lpddr_z_rmw: GEQUAL vs stored, Z_CLEAR=0. Persists across epochs. Gated active after clear_done.
|
||||
logic [15:0] sb_z [0:NPX-1];
|
||||
logic [31:0] sb_col[0:NPX-1];
|
||||
logic sb_wr [0:NPX-1];
|
||||
integer sb_frag, sb_pass;
|
||||
// no-fragment-after-marker: a fragment must not appear in the same scene after fb_flush (before next GO)
|
||||
logic scene_ended=0; int frag_after_marker;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin
|
||||
sb_frag<=0; sb_pass<=0; scene_ended<=0; frag_after_marker<=0;
|
||||
end else begin
|
||||
if (feeder_go_tb && feeder_ready_tb) scene_ended<=0; // new scene opens
|
||||
if (fb_flush) scene_ended<=1; // ordered marker closes the scene
|
||||
if (frag_v && saw_clear_done) begin
|
||||
int o; logic [15:0] zq;
|
||||
if (scene_ended) frag_after_marker<=frag_after_marker+1; // fragment after this scene's marker = ordering bug
|
||||
o = g_y*W + g_x; zq = cl16(flush_z_w);
|
||||
sb_frag<=sb_frag+1;
|
||||
if (o>=0 && o<NPX) begin
|
||||
if (zq >= sb_z[o]) begin sb_z[o]<=zq; sb_col[o]<=flush_color32_w; sb_wr[o]<=1'b1; sb_pass<=sb_pass+1; end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// FRESH-DRAIN observer (emif domain)
|
||||
logic fd_q; int fd_rises, fd_falls;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin fd_q<=0; fd_rises<=0; fd_falls<=0; end
|
||||
else begin fd_q<=frame_drained;
|
||||
if (frame_drained && !fd_q) fd_rises<=fd_rises+1;
|
||||
if (!frame_drained && fd_q) fd_falls<=fd_falls+1;
|
||||
end
|
||||
end
|
||||
|
||||
// clear_start: mirror de25 — pulse once at the writer ARM (after preclear, before first GO)
|
||||
logic wr_arm=0, arm_e1, arm_e2, arm_e3;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin arm_e1<=0; arm_e2<=0; arm_e3<=0; clear_start<=0; end
|
||||
else begin arm_e1<=wr_arm; arm_e2<=arm_e1; arm_e3<=arm_e2; clear_start<=(arm_e2 && !arm_e3); end
|
||||
end
|
||||
|
||||
// ---- feeder staging stream (write port), per epoch ----
|
||||
logic [63:0] stg_buf [0:STG_WORDS-1];
|
||||
task automatic stream_list(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/feeder_sh3_zs640c12%0d.mem", k);
|
||||
for (int i=0;i<STG_WORDS;i++) stg_buf[i]=64'd0;
|
||||
$readmemh(fn, stg_buf);
|
||||
@(negedge clk);
|
||||
for (int i=0;i<STG_WORDS;i++) begin stg_we<=1'b1; stg_waddr<=i[11:0]; stg_wdata<=stg_buf[i]; @(negedge clk); end
|
||||
stg_we<=1'b0; stg_waddr<=12'd0; stg_wdata<=64'd0; @(negedge clk);
|
||||
if (dut.g_feeder.feeder_stg[0][31:0] !== stg_buf[0][31:0]) begin
|
||||
$error("[zint] epoch %0d: staged word0=%08x exp %08x", k, dut.g_feeder.feeder_stg[0][31:0], stg_buf[0][31:0]); errors++; end
|
||||
endtask
|
||||
|
||||
task automatic fill_cache(input int k, input logic [31:0] exp_crc);
|
||||
int d=0;
|
||||
fills=fills+1;
|
||||
@(posedge clk) fill_start<=1'b1; @(posedge clk) fill_start<=1'b0;
|
||||
d=0; while (fill_done && d<1000) begin @(posedge clk); d++; end
|
||||
d=0; while (!fill_done && d<400000) begin @(posedge clk); d++; end
|
||||
if (!fill_done) begin $error("[zint] fill %0d: fill_done never rose", k); errors++; end
|
||||
if (fill_crc_w!==exp_crc) begin $error("[zint] fill %0d: crc=%08x exp %08x", k, fill_crc_w, exp_crc); errors++; end
|
||||
if (tex_rd_errs!==0) begin $error("[zint] fill %0d: rd_errs=%0d", k, tex_rd_errs); errors++; end
|
||||
endtask
|
||||
|
||||
// one-scene runner: GO, wait render+drain, REQUIRE a FRESH ordered frame_drained
|
||||
task automatic run_scene(input int k, input int exp_records);
|
||||
int r0, f0, d=0; logic fd_before; int gd0, fm0;
|
||||
r0=fd_rises; f0=fd_falls; fd_before=frame_drained; gd0=g_drops; fm0=frag_after_marker;
|
||||
@(negedge clk); feeder_go_tb=1'b1; @(negedge clk); feeder_go_tb=1'b0;
|
||||
d=0; while (feeder_ready_tb && d<4000) begin @(posedge clk); d++; end
|
||||
if (feeder_ready_tb!==1'b0) begin $error("[zint] epoch %0d: feeder_ready did not drop after GO", k); errors++; end
|
||||
if (fd_before) begin
|
||||
d=0; while (fd_falls==f0 && d<6000000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_falls==f0) begin $error("[zint] epoch %0d: frame_drained never fell from stale high", k); errors++; end
|
||||
end
|
||||
d=0; while (fd_rises<=r0 && d<6000000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_rises<=r0) begin $error("[zint] epoch %0d: frame_drained never rose", k); errors++; end
|
||||
repeat(100) @(posedge clk);
|
||||
if (feeder_records_w!==exp_records) begin $error("[zint] epoch %0d: records=%0d exp %0d", k, feeder_records_w, exp_records); errors++; end
|
||||
if (col_ovf!==0 || bresp_err!==0) begin $error("[zint] epoch %0d: col_ovf=%0d bresp_err=%0d", k, col_ovf, bresp_err); errors++; end
|
||||
if (g_drops!==gd0) begin $error("[zint] epoch %0d: %0d FRAGMENT DROPS (request FIFO overflow)", k, g_drops-gd0); errors++; end
|
||||
if (frag_after_marker!==fm0) begin $error("[zint] epoch %0d: %0d fragment(s) after scene marker (ordering)", k, frag_after_marker-fm0); errors++; end
|
||||
$display("[zint] epoch %0d: fresh drain (f %0d->%0d, r %0d->%0d) records=%0d drops=%0d fed=%0d pass=%0d",
|
||||
k, f0, fd_falls, r0, fd_rises, feeder_records_w, g_drops, frags_fed, sb_pass);
|
||||
endtask
|
||||
|
||||
task automatic run_epoch(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/sh3_zs640c12%0d_tex_lpddr.mem", k);
|
||||
if (!EP_REUSE[k]) begin
|
||||
$readmemh(fn, lpddr_mem);
|
||||
fill_cache(k, EP_CRC[k]);
|
||||
end else begin
|
||||
// Ch359 — EXPLICIT REUSE: no reload, no fill pulse. Fail-closed residency: the fill CRC register is
|
||||
// only rewritten by a fill, so it must still hold the prior epoch's verified CRC (same shared texture).
|
||||
if (fill_crc_w!==EP_CRC[k]) begin $error("%s epoch %0d: REUSE but resident crc=%08x exp %08x — residency broken", "[zint]", k, fill_crc_w, EP_CRC[k]); errors++; end
|
||||
else $display("%s epoch %0d: REUSE resident texture (crc=0x%08x, no fill)", "[zint]", k, fill_crc_w);
|
||||
end
|
||||
stream_list(k);
|
||||
run_scene(k, EP_REC[k]);
|
||||
endtask
|
||||
|
||||
initial begin
|
||||
errors=0; feeder_go_tb=1'b0; fill_start=1'b0; wr_arm=0; zc_enable=1'b1; stg_we=0; stg_waddr=0; stg_wdata=0; hw_clear=1'b0;
|
||||
for (int i=0;i<NPX;i++) begin sb_z[i]=16'h0000; sb_col[i]=32'd0; sb_wr[i]=1'b0; end
|
||||
for (int b=0;b<MEMBEATS;b++) shmem[b]=256'd0; // PRECLEAR color+Z EXACTLY ONCE
|
||||
// skip-guard: probe a fixture (via $fopen — avoids the $readmemh range warning)
|
||||
begin int pfh; pfh=$fopen("../../data/top_psmct32_raster_demo/sh3_zs640c120_tex_lpddr.mem","r");
|
||||
if (pfh==0) begin $display("[tb_top_psmct32_sh3_zint640c12] SKIP — sh3_zs640c12*.mem absent (run gs_make_sh3_scheduler_fixture.py --authz --fb640 --tag zs640c12 --emit)"); $finish; end
|
||||
$fclose(pfh);
|
||||
end
|
||||
|
||||
rst_n=1'b0; core_go=1'b0; repeat(6) @(posedge clk); rst_n=1'b1; 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);
|
||||
wait (feeder_ready_tb==1'b1); repeat(50) @(posedge clk);
|
||||
|
||||
// ARM (-> clear_start) AFTER preclear, BEFORE first GO. z_rmw's clear_start writes Z_CLEAR to EVERY Z beat
|
||||
// (NBEATS=NPX/16, through wr_arb s2 under random backpressure) then asserts clear_done -> it is the authoritative
|
||||
// Z preclear. Wait for it (the board host likewise gates the first GO on the clear_done status bit).
|
||||
wr_arm<=1'b1;
|
||||
begin int d=0; while (!clear_done && d<600000) begin @(posedge emif_clk); d++; end end
|
||||
if (!clear_done) begin $error("[zint] clear_done never rose after ARM (Z preclear did not finish)"); errors++; end
|
||||
else $display("[zint] Z preclear complete (clear_done), z_beats_written(clear)=%0d", z_beats_written);
|
||||
repeat(40) @(posedge clk);
|
||||
|
||||
for (int k=0;k<NEP;k++) begin
|
||||
int fed0, dr0;
|
||||
fed0=frags_fed; dr0=g_drops;
|
||||
run_epoch(k);
|
||||
$display("[zint][sweep] depth=%0d epoch %0d: accepted=%0d drops=%0d req_hiwater=%0d",
|
||||
TB_REQ_DEPTH, k, frags_fed-fed0, g_drops-dr0, req_hiwater);
|
||||
@(negedge clk); hw_clear<=1'b1; @(negedge clk); hw_clear<=1'b0; // per-epoch high-water
|
||||
end
|
||||
|
||||
if (pre_clear_frags!==0) begin $error("[zint] %0d psm0 fragment(s) before clear_done", pre_clear_frags); errors++; end
|
||||
if (fd_rises<NEP) begin $error("[zint] expected %0d ordered drains: rises=%0d", NEP, fd_rises); errors++; end
|
||||
if (fd_falls<NEP-1) begin $error("[zint] epochs after the first never cleared stale (falls=%0d)", fd_falls); errors++; end
|
||||
if (eof_count!==NEP)begin $error("[zint] scene markers=%0d != accepted GOs=%0d", eof_count, NEP); errors++; end
|
||||
if (g_drops!==0) begin $error("[zint] TOTAL %0d fragment drops", g_drops); errors++; end
|
||||
// Ch359 — the WHOLE run must perform exactly ONE verified texture fill (epoch 1 reuses the resident cache)
|
||||
if (fills!==1) begin $error("[zint] expected exactly ONE texture fill, got %0d (residency contract)", fills); errors++; end
|
||||
|
||||
// ===== final memory compare: shared LPDDR color + Z == scoreboard =====
|
||||
begin
|
||||
int zmis, cmis, zchk, cchk; zmis=0; cmis=0; zchk=0; cchk=0;
|
||||
for (int o=0;o<NPX;o++) begin
|
||||
logic [15:0] zs; logic [31:0] cs; int zb, zl;
|
||||
zb=(ZBASE + o*2)>>5; zl=o&15; zs=shmem[zb][zl*16+:16];
|
||||
zchk++;
|
||||
if (zs !== sb_z[o]) begin if (zmis<10) $error("[zint] Z px%0d got %04x exp %04x", o, zs, sb_z[o]); zmis++; end
|
||||
if (sb_wr[o]) begin
|
||||
cs=shmem[o>>3][(o[2:0])*32+:32]; cchk++;
|
||||
if (cs[23:0] !== sb_col[o][23:0]) begin if (cmis<10) $error("[zint] COL px%0d got %06x exp %06x", o, cs[23:0], sb_col[o][23:0]); cmis++; end
|
||||
end
|
||||
end
|
||||
$display("[zint] final compare: Z %0d/%0d mismatch, COLOR %0d/%0d mismatch (written px=%0d)", zmis, zchk, cmis, cchk, cchk);
|
||||
end
|
||||
|
||||
begin
|
||||
string fdump;
|
||||
if ($value$plusargs("FBDUMP=%s", fdump)) begin
|
||||
int fh;
|
||||
fh = $fopen(fdump, "w");
|
||||
if (fh == 0) begin
|
||||
$error("[zint] could not open FBDUMP '%s'", fdump);
|
||||
errors++;
|
||||
end else begin
|
||||
for (int o=0;o<NPX;o++) begin
|
||||
logic [31:0] cs;
|
||||
cs = shmem[o>>3][(o[2:0])*32+:32];
|
||||
$fdisplay(fh, "%08x", cs);
|
||||
end
|
||||
$fclose(fh);
|
||||
$display("[zint] dumped final COLOR FB (%0dx%0d) -> %s", W, H, fdump);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
$display("[tb_top_psmct32_sh3_zint640c12] fed=%0d pass=%0d drops=%0d markers=%0d drains(r/f)=%0d/%0d col_ovf=%0d bresp_err=%0d errors=%0d",
|
||||
frags_fed, sb_pass, g_drops, eof_count, fd_rises, fd_falls, col_ovf, bresp_err, errors);
|
||||
$display("[zint] req-FIFO depth=%0d (diagnostic sweep variant when != 1024)", TB_REQ_DEPTH);
|
||||
$display("[zint] map: COLOR 0x%0h..0x%0h Z 0x%0h..0x%0h", COLBASE, COLBASE+COL_TOP, ZBASE, ZBASE+Z_TOP);
|
||||
if (errors==0) $display("[tb_top_psmct32_sh3_zint640c12] PASS"); else $display("[tb_top_psmct32_sh3_zint640c12] FAIL");
|
||||
$finish;
|
||||
end
|
||||
initial begin #400000000; $error("[tb_top_psmct32_sh3_zint640c12] TIMEOUT"); $finish; end
|
||||
endmodule : tb_top_psmct32_sh3_zint640c12
|
||||
@@ -0,0 +1,469 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_sh3_zint640c6 (Ch358 — REAL-RASTER persistent-Z board integration sim at NATIVE 640x480)
|
||||
//
|
||||
// Codex's pre-fit gate: prove the persistent-Z ROP through the SAME path the de25 top wires (GS_SH3_LPDDR_FB_Z):
|
||||
// REAL demo (feeder + raster, authz sh3_zs640c6 NATIVE-640x480 strong-reject scene) -> gs_lpddr_zc_emit -> REAL de25 arbiters
|
||||
// (gs_lpddr_wr_arb s0=color / s2=Z-write ; gs_lpddr_rd_arb s3=Z-read) -> ONE shared behavioral LPDDR
|
||||
// with randomized backpressure. Unlike tb_top_psmct32_sh3_zarb (which fed a handshake-throttled trace),
|
||||
// the raster CANNOT honor g_ready, so this is the gate that catches request-FIFO fragment DROPS.
|
||||
//
|
||||
// Asserts (Codex list):
|
||||
// * final LPDDR color + Z == independent clamp16 persistent-Z scoreboard over the ACTUAL emitted fragments;
|
||||
// * zero fragment drops (g_valid && !g_ready) — the whole reason this sim exists;
|
||||
// * exactly one ordered scene marker (sh3_fb_flush) per accepted GO; no fragment after a marker within a scene;
|
||||
// * Z cache invalidated (clear_done) after preclear and before the first GO; Z persists across epochs;
|
||||
// * a FRESH ordered frame_drained per epoch; scanout never referenced during render (rd_arb s0/s1/s2 idle);
|
||||
// * merged AW only in color/Z ranges, merged AR only in Z range (disjoint-map monitor).
|
||||
// LOCAL/gitignored fixtures; skip-guard if absent.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_sh3_zint640c6;
|
||||
`include "sh3_zs640c6_params.vh" // FBPXW=640, FBH=480, N_EPOCHS=3, EPk_CRC/EPk_NTRIS, TEX_*, ...
|
||||
localparam int W = FBPXW, H = FBH;
|
||||
localparam int NPX = W*H;
|
||||
localparam int NEP = N_EPOCHS;
|
||||
localparam [31:0] COLBASE = 32'h0000_0000, ZBASE = 32'h0014_0000;
|
||||
localparam int COL_TOP = NPX*4; // color region byte-size (disjoint below ZBASE)
|
||||
localparam int Z_TOP = NPX*2; // Z region byte-size
|
||||
localparam int MEMBEATS = 65536; // shared LPDDR: covers ZBASE + Z_TOP
|
||||
|
||||
// per-epoch expected CRC / records
|
||||
// Ch359 — TWO epochs sharing ONE texture: EPk_REUSE=1 means the epoch runs on the RESIDENT cache (no fill).
|
||||
logic [31:0] EP_CRC [0:1]; int EP_REC [0:1]; bit EP_REUSE [0:1]; int fills;
|
||||
initial begin
|
||||
EP_CRC[0]=EP0_CRC; EP_CRC[1]=EP1_CRC;
|
||||
EP_REC[0]=EP0_NTRIS; EP_REC[1]=EP1_NTRIS;
|
||||
EP_REUSE[0]=EP0_REUSE; EP_REUSE[1]=EP1_REUSE;
|
||||
fills=0;
|
||||
end
|
||||
|
||||
`ifdef C6_REQ_DEPTH
|
||||
localparam int TB_REQ_DEPTH = `C6_REQ_DEPTH; // Ch359 sim-only depth-sweep variant (Codex-authorized)
|
||||
`else
|
||||
localparam int TB_REQ_DEPTH = 1024; // Ch359 production depth (Codex: cold-Z burst peaks at 478)
|
||||
`endif
|
||||
logic clk=0; always #5 clk=~clk;
|
||||
logic emif_clk=0; always #2 emif_clk=~emif_clk;
|
||||
logic rst_n; logic fb_commit=0; always #13 fb_commit=~fb_commit;
|
||||
int errors; initial errors=0;
|
||||
|
||||
// clamp16 (vendored PCSX2 PSMZ16S source-Z saturation)
|
||||
function automatic logic [15:0] cl16(input logic [31:0] z); cl16=(|z[31:16])?16'hFFFF:z[15:0]; endfunction
|
||||
|
||||
// ===== bram-top (FB_LPDDR_ONLY, AUTOSTART off), 384x381 =====
|
||||
logic core_go; logic [7:0] r,g,b; logic hsync,vsync_o,de;
|
||||
logic core_halt,dma_done_seen,frame_seen,raster_overflow,frame_toggle,dma_done_toggle;
|
||||
logic feeder_go_tb, feeder_ready_tb; logic [15:0] feeder_records_w; logic [31:0] feeder_waits_w;
|
||||
logic gs_tex_rd_en_o; logic [31:0] gs_tex_rd_addr_o; logic [31:0] tex_cache_data; logic tex_cache_ready;
|
||||
logic [31:0] tex_cache_hits, tex_bram_hits;
|
||||
logic flush_emit_w; logic [31:0] flush_addr_w; logic [31:0] flush_color32_w; logic [5:0] flush_psm_w;
|
||||
logic [11:0] flush_x_w, flush_y_w; logic [31:0] flush_z_w;
|
||||
logic stg_we; logic [11:0] stg_waddr; logic [63:0] stg_wdata;
|
||||
|
||||
top_psmct32_raster_demo_bram #(
|
||||
.H_ACTIVE(FBPXW), .V_ACTIVE(FBH),
|
||||
.VRAM_BYTES(VRAM_BYTES_P), .VRAM_ENABLE_READ2(1'b0), .PSMCT32_SWIZZLE(1'b0),
|
||||
.COMBINED_TAZ(1'b0), .TILE_LOCAL(1'b0), .TILE_COLS(1), .TILE_ROWS(1),
|
||||
.TILE_MULTIPRIM(1'b0), .TILE_PRIM_COUNT(1), .TILE_FIFO_DEPTH(8),
|
||||
.BIN_BUFFER_ENABLE(1'b0), .HEARTBEAT_SPLICE_ENABLE(1'b0),
|
||||
.FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(STG_WORDS), .FEEDER_AUTOSTART(1'b0),
|
||||
.PERSPECTIVE_CORRECT(1'b1), .PERSP_RECIP_IDX_BITS(11),
|
||||
.GRAD_SEQ_DIVIDER(1'b1), .GRAD_DIV_CYCLES(4),
|
||||
.GS_LPDDR_TEX(1'b1), .TEX_VRAM_BASE(TEX_VRAM_BASE), .TEX_CACHE_BYTES(TEX_BYTES),
|
||||
.CLUT_CSM1_ENABLE(1'b1), .FB_LPDDR_ONLY(1'b1)
|
||||
) dut (
|
||||
.clk(clk), .rst_n(rst_n), .core_go(core_go),
|
||||
.r(r), .g(g), .b(b), .hsync(hsync), .vsync(vsync_o), .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),
|
||||
.feeder_stg_we_i(stg_we), .feeder_stg_waddr_i(stg_waddr), .feeder_stg_wdata_i(stg_wdata),
|
||||
.feeder_go_i(feeder_go_tb), .feeder_ready_o(feeder_ready_tb),
|
||||
.feeder_records_o(feeder_records_w), .feeder_waits_o(feeder_waits_w),
|
||||
.flush_emit_o(flush_emit_w), .flush_addr_o(flush_addr_w),
|
||||
.flush_pix16_o(), .flush_color32_o(flush_color32_w), .flush_psm_o(flush_psm_w),
|
||||
.flush_x_o(flush_x_w), .flush_y_o(flush_y_w), .flush_z_o(flush_z_w),
|
||||
.gs_tex_rd_en_o(gs_tex_rd_en_o), .gs_tex_rd_addr_o(gs_tex_rd_addr_o),
|
||||
.tex_cache_data_i(tex_cache_data), .tex_cache_ready_i(tex_cache_ready),
|
||||
.tex_cache_hits_o(tex_cache_hits), .tex_bram_hits_o(tex_bram_hits)
|
||||
);
|
||||
|
||||
// texture cache + behavioral texture LPDDR (reloaded per epoch)
|
||||
logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid; logic [7:0] arlen; logic [2:0] arsize;
|
||||
logic arvalid, arready; logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready;
|
||||
logic fill_start, fill_done; logic [31:0] fill_beats, fill_bytes, tex_rd_errs, fill_crc_w;
|
||||
gs_texture_cache #(.LPDDR_TEX_BASE(LPDDR_TEX_BASE), .TEX_VRAM_BASE(TEX_VRAM_BASE),
|
||||
.TEX_BYTES(TEX_BYTES), .N_BEATS(N_BEATS)) u_cache (
|
||||
.axi_clk(clk), .axi_rst_n(rst_n), .fill_start(fill_start), .fill_done(fill_done),
|
||||
.fill_beats(fill_beats), .fill_bytes(fill_bytes), .rd_errs(tex_rd_errs), .fill_crc(fill_crc_w),
|
||||
.araddr(araddr), .arburst(arburst), .arid(arid), .arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready),
|
||||
.rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready),
|
||||
.sample_clk(clk), .tex_rd_en(gs_tex_rd_en_o), .tex_rd_addr(gs_tex_rd_addr_o),
|
||||
.tex_rd_data(tex_cache_data), .tex_ready(tex_cache_ready)
|
||||
);
|
||||
logic [31:0] lpddr_mem [0:(TEX_BYTES/4)-1];
|
||||
typedef enum logic [1:0] { S_IDLE, S_WAIT, S_DATA } sstate_t; sstate_t sst; logic [3:0] tdly; int tbeat;
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin sst<=S_IDLE; arready<=0; rvalid<=0; rlast<=0; rresp<=0; rdata<=0; tdly<=0; end
|
||||
else begin arready<=0; rvalid<=0; rlast<=0;
|
||||
case (sst)
|
||||
S_IDLE: if (arvalid) begin arready<=1; tbeat<=(araddr-LPDDR_TEX_BASE)>>5; tdly<=4'd2; sst<=S_WAIT; end
|
||||
S_WAIT: if (tdly==0) sst<=S_DATA; else tdly<=tdly-1'b1;
|
||||
S_DATA: if (rready) begin for (int w=0;w<8;w++) rdata[w*32 +: 32]<=lpddr_mem[tbeat*8+w];
|
||||
rresp<=2'b00; rvalid<=1; rlast<=1; sst<=S_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// ===== render epoch: one ordered scene marker (sh3_fb_flush) per accepted GO =====
|
||||
logic fb_flush=0, render_inflight=0, feeder_ready_q=0; int eof_count;
|
||||
wire feeder_ready_rise = feeder_ready_tb && !feeder_ready_q;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin render_inflight<=0; feeder_ready_q<=0; fb_flush<=0; eof_count<=0; end
|
||||
else begin feeder_ready_q<=feeder_ready_tb; fb_flush<=1'b0;
|
||||
if (feeder_go_tb && feeder_ready_tb) render_inflight<=1'b1;
|
||||
if (render_inflight && feeder_ready_rise) begin fb_flush<=1'b1; render_inflight<=1'b0; eof_count<=eof_count+1; end
|
||||
end
|
||||
end
|
||||
|
||||
// ===== gs_lpddr_zc_emit — Z-then-color emit, fed by the REAL raster (NO handshake) =====
|
||||
logic zc_enable, clear_start, clear_done, frame_drained;
|
||||
wire [15:0] g_zq = cl16(flush_z_w);
|
||||
wire frag_v = flush_emit_w && (flush_psm_w==6'h00);
|
||||
wire g_valid = frag_v || fb_flush;
|
||||
wire g_scene = fb_flush;
|
||||
wire [11:0] g_x = flush_x_w, g_y = flush_y_w;
|
||||
wire [31:0] g_color = flush_color32_w;
|
||||
wire g_ready;
|
||||
// Z AXI
|
||||
logic [31:0] z_araddr; logic [7:0] z_arlen; logic [2:0] z_arsize; logic [1:0] z_arburst; logic z_arvalid, z_arready;
|
||||
logic [255:0] z_rdata; logic [1:0] z_rresp; logic z_rlast, z_rvalid, z_rready;
|
||||
logic [31:0] z_awaddr; logic [7:0] z_awlen; logic [2:0] z_awsize; logic [1:0] z_awburst; logic z_awvalid, z_awready;
|
||||
logic [255:0] z_wdata; logic [31:0] z_wstrb; logic z_wlast, z_wvalid, z_wready; logic z_bvalid, z_bready; logic [1:0] z_bresp;
|
||||
// Color AXI
|
||||
logic [31:0] c_awaddr; logic [7:0] c_awlen; logic [2:0] c_awsize; logic [1:0] c_awburst; logic c_awvalid, c_awready;
|
||||
logic [255:0] c_wdata; logic [31:0] c_wstrb; logic c_wlast, c_wvalid, c_wready; logic c_bvalid, c_bready; logic [1:0] c_bresp;
|
||||
logic [31:0] z_beats_read, z_beats_written, c_beats_written, col_ovf, bresp_err; logic zc_idle;
|
||||
gs_lpddr_zc_emit #(.COLBASE(COLBASE), .ZBASE(ZBASE), .FB_PXW(W), .FB_H(H), .REQ_DEPTH(TB_REQ_DEPTH), .COL_DEPTH(128)) u_zc (
|
||||
.gs_clk(clk), .gs_rst_n(rst_n), .enable(zc_enable),
|
||||
.g_valid(g_valid), .g_ready(g_ready), .g_x(g_x), .g_y(g_y), .g_zq(g_zq), .g_zmsk(1'b0),
|
||||
.g_ztest(1'b1), .g_ztst(2'd2), .g_color(g_color), .g_be(4'hF), .g_scene(g_scene),
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n), .clear_start(clear_start), .clear_done(clear_done), .frame_drained(frame_drained),
|
||||
.z_araddr(z_araddr), .z_arlen(z_arlen), .z_arsize(z_arsize), .z_arburst(z_arburst), .z_arvalid(z_arvalid), .z_arready(z_arready),
|
||||
.z_rdata(z_rdata), .z_rresp(z_rresp), .z_rlast(z_rlast), .z_rvalid(z_rvalid), .z_rready(z_rready),
|
||||
.z_awaddr(z_awaddr), .z_awlen(z_awlen), .z_awsize(z_awsize), .z_awburst(z_awburst), .z_awvalid(z_awvalid), .z_awready(z_awready),
|
||||
.z_wdata(z_wdata), .z_wstrb(z_wstrb), .z_wlast(z_wlast), .z_wvalid(z_wvalid), .z_wready(z_wready),
|
||||
.z_bvalid(z_bvalid), .z_bready(z_bready), .z_bresp(z_bresp),
|
||||
.c_awaddr(c_awaddr), .c_awlen(c_awlen), .c_awsize(c_awsize), .c_awburst(c_awburst), .c_awvalid(c_awvalid), .c_awready(c_awready),
|
||||
.c_wdata(c_wdata), .c_wstrb(c_wstrb), .c_wlast(c_wlast), .c_wvalid(c_wvalid), .c_wready(c_wready),
|
||||
.c_bvalid(c_bvalid), .c_bready(c_bready), .c_bresp(c_bresp),
|
||||
.z_beats_read(z_beats_read), .z_beats_written(z_beats_written), .c_beats_written(c_beats_written),
|
||||
.col_ovf(col_ovf), .bresp_err(bresp_err), .idle(zc_idle)
|
||||
);
|
||||
|
||||
// Ch359 DIAGNOSTIC — request-FIFO occupancy high-water (write-domain binary pointers, hierarchical):
|
||||
// sizes the burst that overflows REQ_DEPTH=256 (would a deeper FIFO absorb it, or is the ep0 dirty-miss
|
||||
// streak service-rate-bound?). Sampled on the write clock; wbin-rbin_sampled is a safe over-estimate.
|
||||
// (Codex correction: occupancy must be computed ENTIRELY in the write clock domain — wbin vs the
|
||||
// write-domain-SYNCHRONIZED read pointer rgray_s2 converted gray->binary — with the modulus derived from the
|
||||
// selected depth, not a hard-coded 2*256.)
|
||||
int req_hiwater; logic hw_clear;
|
||||
function automatic int g2b(input int g);
|
||||
int b; b=g; b^=b>>1; b^=b>>2; b^=b>>4; b^=b>>8; b^=b>>16; return b;
|
||||
endfunction
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) req_hiwater <= 0;
|
||||
else if (hw_clear) req_hiwater <= 0;
|
||||
else begin
|
||||
int occ;
|
||||
occ = (int'(u_zc.u_req.wbin) - g2b(int'(u_zc.u_req.rgray_s2))) % (2*TB_REQ_DEPTH);
|
||||
if (occ < 0) occ += 2*TB_REQ_DEPTH;
|
||||
if (occ > req_hiwater) req_hiwater <= occ;
|
||||
end
|
||||
end
|
||||
// fragment-drop guard (design clk): the request FIFO silently drops when g_ready is low
|
||||
int g_drops; int frags_fed;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin g_drops<=0; frags_fed<=0; end
|
||||
else begin
|
||||
if (frag_v && !g_ready) g_drops<=g_drops+1;
|
||||
if (frag_v && g_ready) frags_fed<=frags_fed+1;
|
||||
end
|
||||
end
|
||||
// clear ordering: clear_done must rise before the first GO. no psm0 fragment before clear_done.
|
||||
logic saw_clear_done=0, first_go_done=0; int pre_clear_frags;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin saw_clear_done<=0; pre_clear_frags<=0; end
|
||||
else begin
|
||||
if (clear_done) saw_clear_done<=1;
|
||||
if (frag_v && !saw_clear_done) pre_clear_frags<=pre_clear_frags+1;
|
||||
end
|
||||
end
|
||||
|
||||
// ===== REAL de25 write arbiter: s0=color, s2=Z-write ; s1/s3 inert =====
|
||||
logic [29:0] m_awaddr; logic [1:0] m_awburst; logic [6:0] m_awid; logic [7:0] m_awlen; logic [2:0] m_awsize;
|
||||
logic m_awvalid, m_awready; logic [255:0] m_wdata; logic [31:0] m_wstrb; logic m_wlast, m_wvalid, m_wready;
|
||||
logic m_bvalid, m_bready; logic [1:0] m_bresp;
|
||||
gs_lpddr_wr_arb u_wr_arb (
|
||||
.clk(emif_clk), .rst_n(rst_n),
|
||||
.s0_awaddr(c_awaddr[29:0]), .s0_awburst(c_awburst), .s0_awid(7'd0), .s0_awlen(c_awlen), .s0_awsize(c_awsize),
|
||||
.s0_awvalid(c_awvalid), .s0_awready(c_awready), .s0_wdata(c_wdata), .s0_wstrb(c_wstrb), .s0_wlast(c_wlast),
|
||||
.s0_wvalid(c_wvalid), .s0_wready(c_wready), .s0_bresp(c_bresp), .s0_bvalid(c_bvalid), .s0_bready(c_bready),
|
||||
.s1_awaddr(30'd0), .s1_awburst(2'b01), .s1_awid(7'd1), .s1_awlen(8'd0), .s1_awsize(3'b101),
|
||||
.s1_awvalid(1'b0), .s1_awready(), .s1_wdata(256'd0), .s1_wstrb(32'd0), .s1_wlast(1'b0),
|
||||
.s1_wvalid(1'b0), .s1_wready(), .s1_bresp(), .s1_bvalid(), .s1_bready(1'b0),
|
||||
.s2_awaddr(z_awaddr[29:0]), .s2_awburst(z_awburst), .s2_awid(7'd2), .s2_awlen(z_awlen), .s2_awsize(z_awsize),
|
||||
.s2_awvalid(z_awvalid), .s2_awready(z_awready), .s2_wdata(z_wdata), .s2_wstrb(z_wstrb), .s2_wlast(z_wlast),
|
||||
.s2_wvalid(z_wvalid), .s2_wready(z_wready), .s2_bresp(z_bresp), .s2_bvalid(z_bvalid), .s2_bready(z_bready),
|
||||
.s3_awaddr(30'd0), .s3_awburst(2'b01), .s3_awid(7'd3), .s3_awlen(8'd0), .s3_awsize(3'b101),
|
||||
.s3_awvalid(1'b0), .s3_awready(), .s3_wdata(256'd0), .s3_wstrb(32'd0), .s3_wlast(1'b0),
|
||||
.s3_wvalid(1'b0), .s3_wready(), .s3_bresp(), .s3_bvalid(), .s3_bready(1'b0),
|
||||
.m_awaddr(m_awaddr), .m_awburst(m_awburst), .m_awid(m_awid), .m_awlen(m_awlen), .m_awsize(m_awsize),
|
||||
.m_awvalid(m_awvalid), .m_awready(m_awready), .m_wdata(m_wdata), .m_wstrb(m_wstrb), .m_wlast(m_wlast),
|
||||
.m_wvalid(m_wvalid), .m_wready(m_wready), .m_bvalid(m_bvalid), .m_bready(m_bready), .m_bresp(m_bresp)
|
||||
);
|
||||
|
||||
// ===== REAL de25 read arbiter: s3=Z-read (mirrors de25 reload port) ; s0/s1/s2 idle during render =====
|
||||
logic [29:0] m_araddr; logic [1:0] m_arburst; logic [6:0] m_arid; logic [7:0] m_arlen; logic [2:0] m_arsize;
|
||||
logic m_arvalid, m_arready; logic [255:0] m_rdata; logic [1:0] m_rresp; logic m_rlast, m_rvalid, m_rready;
|
||||
gs_lpddr_rd_arb u_rd_arb (
|
||||
.clk(emif_clk), .rst_n(rst_n),
|
||||
.s0_araddr(30'd0), .s0_arburst(2'b01), .s0_arid(7'd0), .s0_arlen(8'd0), .s0_arsize(3'b101),
|
||||
.s0_arvalid(1'b0), .s0_arready(), .s0_rdata(), .s0_rresp(), .s0_rlast(), .s0_rvalid(), .s0_rready(1'b0),
|
||||
.s1_araddr(30'd0), .s1_arburst(2'b01), .s1_arid(7'd1), .s1_arlen(8'd0), .s1_arsize(3'b101),
|
||||
.s1_arvalid(1'b0), .s1_arready(), .s1_rdata(), .s1_rresp(), .s1_rlast(), .s1_rvalid(), .s1_rready(1'b0),
|
||||
.s2_araddr(30'd0), .s2_arburst(2'b01), .s2_arid(7'd2), .s2_arlen(8'd0), .s2_arsize(3'b101),
|
||||
.s2_arvalid(1'b0), .s2_arready(), .s2_rdata(), .s2_rresp(), .s2_rlast(), .s2_rvalid(), .s2_rready(1'b0),
|
||||
.s3_araddr(z_araddr[29:0]), .s3_arburst(z_arburst), .s3_arid(7'd3), .s3_arlen(z_arlen), .s3_arsize(z_arsize),
|
||||
.s3_arvalid(z_arvalid), .s3_arready(z_arready), .s3_rdata(z_rdata), .s3_rresp(z_rresp), .s3_rlast(z_rlast),
|
||||
.s3_rvalid(z_rvalid), .s3_rready(z_rready),
|
||||
.m_araddr(m_araddr), .m_arburst(m_arburst), .m_arid(m_arid), .m_arlen(m_arlen), .m_arsize(m_arsize),
|
||||
.m_arvalid(m_arvalid), .m_arready(m_arready), .m_rdata(m_rdata), .m_rresp(m_rresp), .m_rlast(m_rlast),
|
||||
.m_rvalid(m_rvalid), .m_rready(m_rready)
|
||||
);
|
||||
|
||||
logic [15:0] lf=16'hF00D; always_ff @(posedge emif_clk) lf<={lf[14:0], lf[15]^lf[13]^lf[12]^lf[10]};
|
||||
|
||||
// ===== ONE shared behavioral LPDDR (write + read), address-decoded, random backpressure =====
|
||||
logic [255:0] shmem [0:MEMBEATS-1];
|
||||
logic [29:0] wa; logic [255:0] wd; logic [31:0] ws; logic aw_s, w_s; logic [3:0] bd; logic bp;
|
||||
logic [29:0] ra; logic rp; logic [3:0] rd_dly;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if(!rst_n) begin
|
||||
m_awready<=0; m_wready<=0; m_bvalid<=0; m_bresp<=0; aw_s<=0; w_s<=0; bp<=0; bd<=0;
|
||||
m_arready<=0; m_rvalid<=0; m_rlast<=0; m_rresp<=0; m_rdata<=0; rp<=0; rd_dly<=0;
|
||||
end else begin
|
||||
m_awready<=(!aw_s)?lf[0]:1'b0; m_wready<=(!w_s)?lf[1]:1'b0;
|
||||
if(m_awvalid&&m_awready) begin wa<=m_awaddr; aw_s<=1; m_awready<=0; end
|
||||
if(m_wvalid&&m_wready) begin wd<=m_wdata; ws<=m_wstrb; w_s<=1; m_wready<=0; end
|
||||
if(aw_s&&w_s&&!bp&&!m_bvalid) begin
|
||||
for(int bb=0;bb<32;bb++) if(ws[bb]) shmem[wa>>5][bb*8+:8]<=wd[bb*8+:8];
|
||||
bp<=1; bd<={1'b0,lf[4:2]};
|
||||
end
|
||||
if(bp&&!m_bvalid) begin if(bd==0) begin m_bvalid<=1;m_bresp<=0;bp<=0;aw_s<=0;w_s<=0; end else bd<=bd-1; end
|
||||
if(m_bvalid&&m_bready) m_bvalid<=0;
|
||||
m_arready<=(!rp&&!m_rvalid)?lf[8]:1'b0;
|
||||
if(m_arvalid&&m_arready) begin ra<=m_araddr; rp<=1; rd_dly<={1'b0,lf[7:5]}; m_arready<=0; end
|
||||
if(rp&&!m_rvalid) begin if(rd_dly==0) begin m_rdata<=shmem[ra>>5]; m_rresp<=0; m_rvalid<=1; m_rlast<=1; rp<=0; end else rd_dly<=rd_dly-1; end
|
||||
if(m_rvalid&&m_rready) begin m_rvalid<=0; m_rlast<=0; end
|
||||
end
|
||||
end
|
||||
// disjoint-map monitors on the MERGED master streams
|
||||
always_ff @(posedge emif_clk) if(rst_n) begin
|
||||
if(m_awvalid) begin
|
||||
if(!((m_awaddr<30'(COLBASE+COL_TOP)) || (m_awaddr>=30'(ZBASE) && m_awaddr<30'(ZBASE+Z_TOP))))
|
||||
begin $error("[zint] merged AW %h not in color/Z range",m_awaddr); errors++; end
|
||||
end
|
||||
if(m_arvalid && (m_araddr<30'(ZBASE) || m_araddr>=30'(ZBASE+Z_TOP))) begin $error("[zint] merged AR %h not in Z range",m_araddr); errors++; end
|
||||
end
|
||||
|
||||
// ===== clamp16 persistent-Z SCOREBOARD over the ACTUAL emitted fragments (design clk) =====
|
||||
// Mirrors gs_lpddr_z_rmw: GEQUAL vs stored, Z_CLEAR=0. Persists across epochs. Gated active after clear_done.
|
||||
logic [15:0] sb_z [0:NPX-1];
|
||||
logic [31:0] sb_col[0:NPX-1];
|
||||
logic sb_wr [0:NPX-1];
|
||||
integer sb_frag, sb_pass;
|
||||
// no-fragment-after-marker: a fragment must not appear in the same scene after fb_flush (before next GO)
|
||||
logic scene_ended=0; int frag_after_marker;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin
|
||||
sb_frag<=0; sb_pass<=0; scene_ended<=0; frag_after_marker<=0;
|
||||
end else begin
|
||||
if (feeder_go_tb && feeder_ready_tb) scene_ended<=0; // new scene opens
|
||||
if (fb_flush) scene_ended<=1; // ordered marker closes the scene
|
||||
if (frag_v && saw_clear_done) begin
|
||||
int o; logic [15:0] zq;
|
||||
if (scene_ended) frag_after_marker<=frag_after_marker+1; // fragment after this scene's marker = ordering bug
|
||||
o = g_y*W + g_x; zq = cl16(flush_z_w);
|
||||
sb_frag<=sb_frag+1;
|
||||
if (o>=0 && o<NPX) begin
|
||||
if (zq >= sb_z[o]) begin sb_z[o]<=zq; sb_col[o]<=flush_color32_w; sb_wr[o]<=1'b1; sb_pass<=sb_pass+1; end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// FRESH-DRAIN observer (emif domain)
|
||||
logic fd_q; int fd_rises, fd_falls;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin fd_q<=0; fd_rises<=0; fd_falls<=0; end
|
||||
else begin fd_q<=frame_drained;
|
||||
if (frame_drained && !fd_q) fd_rises<=fd_rises+1;
|
||||
if (!frame_drained && fd_q) fd_falls<=fd_falls+1;
|
||||
end
|
||||
end
|
||||
|
||||
// clear_start: mirror de25 — pulse once at the writer ARM (after preclear, before first GO)
|
||||
logic wr_arm=0, arm_e1, arm_e2, arm_e3;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin arm_e1<=0; arm_e2<=0; arm_e3<=0; clear_start<=0; end
|
||||
else begin arm_e1<=wr_arm; arm_e2<=arm_e1; arm_e3<=arm_e2; clear_start<=(arm_e2 && !arm_e3); end
|
||||
end
|
||||
|
||||
// ---- feeder staging stream (write port), per epoch ----
|
||||
logic [63:0] stg_buf [0:STG_WORDS-1];
|
||||
task automatic stream_list(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/feeder_sh3_zs640c6%0d.mem", k);
|
||||
for (int i=0;i<STG_WORDS;i++) stg_buf[i]=64'd0;
|
||||
$readmemh(fn, stg_buf);
|
||||
@(negedge clk);
|
||||
for (int i=0;i<STG_WORDS;i++) begin stg_we<=1'b1; stg_waddr<=i[11:0]; stg_wdata<=stg_buf[i]; @(negedge clk); end
|
||||
stg_we<=1'b0; stg_waddr<=12'd0; stg_wdata<=64'd0; @(negedge clk);
|
||||
if (dut.g_feeder.feeder_stg[0][31:0] !== stg_buf[0][31:0]) begin
|
||||
$error("[zint] epoch %0d: staged word0=%08x exp %08x", k, dut.g_feeder.feeder_stg[0][31:0], stg_buf[0][31:0]); errors++; end
|
||||
endtask
|
||||
|
||||
task automatic fill_cache(input int k, input logic [31:0] exp_crc);
|
||||
int d=0;
|
||||
fills=fills+1;
|
||||
@(posedge clk) fill_start<=1'b1; @(posedge clk) fill_start<=1'b0;
|
||||
d=0; while (fill_done && d<1000) begin @(posedge clk); d++; end
|
||||
d=0; while (!fill_done && d<400000) begin @(posedge clk); d++; end
|
||||
if (!fill_done) begin $error("[zint] fill %0d: fill_done never rose", k); errors++; end
|
||||
if (fill_crc_w!==exp_crc) begin $error("[zint] fill %0d: crc=%08x exp %08x", k, fill_crc_w, exp_crc); errors++; end
|
||||
if (tex_rd_errs!==0) begin $error("[zint] fill %0d: rd_errs=%0d", k, tex_rd_errs); errors++; end
|
||||
endtask
|
||||
|
||||
// one-scene runner: GO, wait render+drain, REQUIRE a FRESH ordered frame_drained
|
||||
task automatic run_scene(input int k, input int exp_records);
|
||||
int r0, f0, d=0; logic fd_before; int gd0, fm0;
|
||||
r0=fd_rises; f0=fd_falls; fd_before=frame_drained; gd0=g_drops; fm0=frag_after_marker;
|
||||
@(negedge clk); feeder_go_tb=1'b1; @(negedge clk); feeder_go_tb=1'b0;
|
||||
d=0; while (feeder_ready_tb && d<4000) begin @(posedge clk); d++; end
|
||||
if (feeder_ready_tb!==1'b0) begin $error("[zint] epoch %0d: feeder_ready did not drop after GO", k); errors++; end
|
||||
if (fd_before) begin
|
||||
d=0; while (fd_falls==f0 && d<6000000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_falls==f0) begin $error("[zint] epoch %0d: frame_drained never fell from stale high", k); errors++; end
|
||||
end
|
||||
d=0; while (fd_rises<=r0 && d<6000000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_rises<=r0) begin $error("[zint] epoch %0d: frame_drained never rose", k); errors++; end
|
||||
repeat(100) @(posedge clk);
|
||||
if (feeder_records_w!==exp_records) begin $error("[zint] epoch %0d: records=%0d exp %0d", k, feeder_records_w, exp_records); errors++; end
|
||||
if (col_ovf!==0 || bresp_err!==0) begin $error("[zint] epoch %0d: col_ovf=%0d bresp_err=%0d", k, col_ovf, bresp_err); errors++; end
|
||||
if (g_drops!==gd0) begin $error("[zint] epoch %0d: %0d FRAGMENT DROPS (request FIFO overflow)", k, g_drops-gd0); errors++; end
|
||||
if (frag_after_marker!==fm0) begin $error("[zint] epoch %0d: %0d fragment(s) after scene marker (ordering)", k, frag_after_marker-fm0); errors++; end
|
||||
$display("[zint] epoch %0d: fresh drain (f %0d->%0d, r %0d->%0d) records=%0d drops=%0d fed=%0d pass=%0d",
|
||||
k, f0, fd_falls, r0, fd_rises, feeder_records_w, g_drops, frags_fed, sb_pass);
|
||||
endtask
|
||||
|
||||
task automatic run_epoch(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/sh3_zs640c6%0d_tex_lpddr.mem", k);
|
||||
if (!EP_REUSE[k]) begin
|
||||
$readmemh(fn, lpddr_mem);
|
||||
fill_cache(k, EP_CRC[k]);
|
||||
end else begin
|
||||
// Ch359 — EXPLICIT REUSE: no reload, no fill pulse. Fail-closed residency: the fill CRC register is
|
||||
// only rewritten by a fill, so it must still hold the prior epoch's verified CRC (same shared texture).
|
||||
if (fill_crc_w!==EP_CRC[k]) begin $error("%s epoch %0d: REUSE but resident crc=%08x exp %08x — residency broken", "[zint]", k, fill_crc_w, EP_CRC[k]); errors++; end
|
||||
else $display("%s epoch %0d: REUSE resident texture (crc=0x%08x, no fill)", "[zint]", k, fill_crc_w);
|
||||
end
|
||||
stream_list(k);
|
||||
run_scene(k, EP_REC[k]);
|
||||
endtask
|
||||
|
||||
initial begin
|
||||
errors=0; feeder_go_tb=1'b0; fill_start=1'b0; wr_arm=0; zc_enable=1'b1; stg_we=0; stg_waddr=0; stg_wdata=0; hw_clear=1'b0;
|
||||
for (int i=0;i<NPX;i++) begin sb_z[i]=16'h0000; sb_col[i]=32'd0; sb_wr[i]=1'b0; end
|
||||
for (int b=0;b<MEMBEATS;b++) shmem[b]=256'd0; // PRECLEAR color+Z EXACTLY ONCE
|
||||
// skip-guard: probe a fixture (via $fopen — avoids the $readmemh range warning)
|
||||
begin int pfh; pfh=$fopen("../../data/top_psmct32_raster_demo/sh3_zs640c60_tex_lpddr.mem","r");
|
||||
if (pfh==0) begin $display("[tb_top_psmct32_sh3_zint640c6] SKIP — sh3_zs640c6*.mem absent (run gs_make_sh3_scheduler_fixture.py --authz --fb640 --tag zs640c6 --emit)"); $finish; end
|
||||
$fclose(pfh);
|
||||
end
|
||||
|
||||
rst_n=1'b0; core_go=1'b0; repeat(6) @(posedge clk); rst_n=1'b1; 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);
|
||||
wait (feeder_ready_tb==1'b1); repeat(50) @(posedge clk);
|
||||
|
||||
// ARM (-> clear_start) AFTER preclear, BEFORE first GO. z_rmw's clear_start writes Z_CLEAR to EVERY Z beat
|
||||
// (NBEATS=NPX/16, through wr_arb s2 under random backpressure) then asserts clear_done -> it is the authoritative
|
||||
// Z preclear. Wait for it (the board host likewise gates the first GO on the clear_done status bit).
|
||||
wr_arm<=1'b1;
|
||||
begin int d=0; while (!clear_done && d<600000) begin @(posedge emif_clk); d++; end end
|
||||
if (!clear_done) begin $error("[zint] clear_done never rose after ARM (Z preclear did not finish)"); errors++; end
|
||||
else $display("[zint] Z preclear complete (clear_done), z_beats_written(clear)=%0d", z_beats_written);
|
||||
repeat(40) @(posedge clk);
|
||||
|
||||
for (int k=0;k<NEP;k++) begin
|
||||
int fed0, dr0;
|
||||
fed0=frags_fed; dr0=g_drops;
|
||||
run_epoch(k);
|
||||
$display("[zint][sweep] depth=%0d epoch %0d: accepted=%0d drops=%0d req_hiwater=%0d",
|
||||
TB_REQ_DEPTH, k, frags_fed-fed0, g_drops-dr0, req_hiwater);
|
||||
@(negedge clk); hw_clear<=1'b1; @(negedge clk); hw_clear<=1'b0; // per-epoch high-water
|
||||
end
|
||||
|
||||
if (pre_clear_frags!==0) begin $error("[zint] %0d psm0 fragment(s) before clear_done", pre_clear_frags); errors++; end
|
||||
if (fd_rises<NEP) begin $error("[zint] expected %0d ordered drains: rises=%0d", NEP, fd_rises); errors++; end
|
||||
if (fd_falls<NEP-1) begin $error("[zint] epochs after the first never cleared stale (falls=%0d)", fd_falls); errors++; end
|
||||
if (eof_count!==NEP)begin $error("[zint] scene markers=%0d != accepted GOs=%0d", eof_count, NEP); errors++; end
|
||||
if (g_drops!==0) begin $error("[zint] TOTAL %0d fragment drops", g_drops); errors++; end
|
||||
// Ch359 — the WHOLE run must perform exactly ONE verified texture fill (epoch 1 reuses the resident cache)
|
||||
if (fills!==1) begin $error("[zint] expected exactly ONE texture fill, got %0d (residency contract)", fills); errors++; end
|
||||
|
||||
// ===== final memory compare: shared LPDDR color + Z == scoreboard =====
|
||||
begin
|
||||
int zmis, cmis, zchk, cchk; zmis=0; cmis=0; zchk=0; cchk=0;
|
||||
for (int o=0;o<NPX;o++) begin
|
||||
logic [15:0] zs; logic [31:0] cs; int zb, zl;
|
||||
zb=(ZBASE + o*2)>>5; zl=o&15; zs=shmem[zb][zl*16+:16];
|
||||
zchk++;
|
||||
if (zs !== sb_z[o]) begin if (zmis<10) $error("[zint] Z px%0d got %04x exp %04x", o, zs, sb_z[o]); zmis++; end
|
||||
if (sb_wr[o]) begin
|
||||
cs=shmem[o>>3][(o[2:0])*32+:32]; cchk++;
|
||||
if (cs[23:0] !== sb_col[o][23:0]) begin if (cmis<10) $error("[zint] COL px%0d got %06x exp %06x", o, cs[23:0], sb_col[o][23:0]); cmis++; end
|
||||
end
|
||||
end
|
||||
$display("[zint] final compare: Z %0d/%0d mismatch, COLOR %0d/%0d mismatch (written px=%0d)", zmis, zchk, cmis, cchk, cchk);
|
||||
end
|
||||
|
||||
begin
|
||||
string fdump;
|
||||
if ($value$plusargs("FBDUMP=%s", fdump)) begin
|
||||
int fh;
|
||||
fh = $fopen(fdump, "w");
|
||||
if (fh == 0) begin
|
||||
$error("[zint] could not open FBDUMP '%s'", fdump);
|
||||
errors++;
|
||||
end else begin
|
||||
for (int o=0;o<NPX;o++) begin
|
||||
logic [31:0] cs;
|
||||
cs = shmem[o>>3][(o[2:0])*32+:32];
|
||||
$fdisplay(fh, "%08x", cs);
|
||||
end
|
||||
$fclose(fh);
|
||||
$display("[zint] dumped final COLOR FB (%0dx%0d) -> %s", W, H, fdump);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
$display("[tb_top_psmct32_sh3_zint640c6] fed=%0d pass=%0d drops=%0d markers=%0d drains(r/f)=%0d/%0d col_ovf=%0d bresp_err=%0d errors=%0d",
|
||||
frags_fed, sb_pass, g_drops, eof_count, fd_rises, fd_falls, col_ovf, bresp_err, errors);
|
||||
$display("[zint] req-FIFO depth=%0d (diagnostic sweep variant when != 1024)", TB_REQ_DEPTH);
|
||||
$display("[zint] map: COLOR 0x%0h..0x%0h Z 0x%0h..0x%0h", COLBASE, COLBASE+COL_TOP, ZBASE, ZBASE+Z_TOP);
|
||||
if (errors==0) $display("[tb_top_psmct32_sh3_zint640c6] PASS"); else $display("[tb_top_psmct32_sh3_zint640c6] FAIL");
|
||||
$finish;
|
||||
end
|
||||
initial begin #400000000; $error("[tb_top_psmct32_sh3_zint640c6] TIMEOUT"); $finish; end
|
||||
endmodule : tb_top_psmct32_sh3_zint640c6
|
||||
@@ -0,0 +1,477 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_sh3_zint640rt3 (runtime-CLUT persistent-Z board integration at NATIVE 640x480)
|
||||
//
|
||||
// Codex's pre-fit gate: prove the persistent-Z ROP through the SAME path the de25 top wires (GS_SH3_LPDDR_FB_Z):
|
||||
// REAL demo (feeder + raster, authz sh3_zsrt3 NATIVE-640x480 strong-reject scene) -> gs_lpddr_zc_emit -> REAL de25 arbiters
|
||||
// (gs_lpddr_wr_arb s0=color / s2=Z-write ; gs_lpddr_rd_arb s3=Z-read) -> ONE shared behavioral LPDDR
|
||||
// with randomized backpressure. Unlike tb_top_psmct32_sh3_zarb (which fed a handshake-throttled trace),
|
||||
// the raster CANNOT honor g_ready, so this is the gate that catches request-FIFO fragment DROPS.
|
||||
//
|
||||
// Asserts (Codex list):
|
||||
// * final LPDDR color + Z == independent clamp16 persistent-Z scoreboard over the ACTUAL emitted fragments;
|
||||
// * zero fragment drops (g_valid && !g_ready) — the whole reason this sim exists;
|
||||
// * exactly one ordered scene marker (sh3_fb_flush) per accepted GO; no fragment after a marker within a scene;
|
||||
// * Z cache invalidated (clear_done) after preclear and before the first GO; Z persists across epochs;
|
||||
// * a FRESH ordered frame_drained per epoch; scanout never referenced during render (rd_arb s0/s1/s2 idle);
|
||||
// * merged AW only in color/Z ranges, merged AR only in Z range (disjoint-map monitor).
|
||||
// LOCAL/gitignored fixtures; skip-guard if absent.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_sh3_zint640rt3;
|
||||
`include "sh3_zsrt3_params.vh" // FBPXW=640, FBH=480, N_EPOCHS=3, EPk_CRC/EPk_NTRIS, TEX_*, ...
|
||||
localparam int W = FBPXW, H = FBH;
|
||||
localparam int NPX = W*H;
|
||||
localparam int NEP = N_EPOCHS;
|
||||
localparam [31:0] COLBASE = 32'h0000_0000, ZBASE = 32'h0014_0000;
|
||||
localparam int COL_TOP = NPX*4; // color region byte-size (disjoint below ZBASE)
|
||||
localparam int Z_TOP = NPX*2; // Z region byte-size
|
||||
localparam int MEMBEATS = 65536; // shared LPDDR: covers ZBASE + Z_TOP
|
||||
|
||||
// per-epoch expected CRC / records
|
||||
logic [31:0] EP_CRC [0:2]; int EP_REC [0:2];
|
||||
initial begin
|
||||
EP_CRC[0]=EP0_CRC; EP_CRC[1]=EP1_CRC; EP_CRC[2]=EP2_CRC;
|
||||
EP_REC[0]=EP0_NTRIS; EP_REC[1]=EP1_NTRIS; EP_REC[2]=EP2_NTRIS;
|
||||
end
|
||||
|
||||
logic clk=0; always #5 clk=~clk;
|
||||
logic emif_clk=0; always #2 emif_clk=~emif_clk;
|
||||
logic rst_n; logic fb_commit=0; always #13 fb_commit=~fb_commit;
|
||||
int errors; initial errors=0;
|
||||
|
||||
// clamp16 (vendored PCSX2 PSMZ16S source-Z saturation)
|
||||
function automatic logic [15:0] cl16(input logic [31:0] z); cl16=(|z[31:16])?16'hFFFF:z[15:0]; endfunction
|
||||
|
||||
// ===== bram-top (FB_LPDDR_ONLY, AUTOSTART off), 384x381 =====
|
||||
logic core_go; logic [7:0] r,g,b; logic hsync,vsync_o,de;
|
||||
logic core_halt,dma_done_seen,frame_seen,raster_overflow,frame_toggle,dma_done_toggle;
|
||||
logic feeder_go_tb, feeder_ready_tb; logic [15:0] feeder_records_w; logic [31:0] feeder_waits_w;
|
||||
logic gs_tex_rd_en_o; logic [31:0] gs_tex_rd_addr_o; logic [31:0] tex_cache_data; logic tex_cache_ready;
|
||||
logic [31:0] tex_cache_hits, tex_bram_hits;
|
||||
logic flush_emit_w; logic [31:0] flush_addr_w; logic [31:0] flush_color32_w; logic [5:0] flush_psm_w;
|
||||
logic [11:0] flush_x_w, flush_y_w; logic [31:0] flush_z_w;
|
||||
logic stg_we; logic [11:0] stg_waddr; logic [63:0] stg_wdata;
|
||||
// Mirrors the core-side output of the runtime-CLUT CDC: the bridge and
|
||||
// CDC have focused gates; this integration gate proves the rendered scene.
|
||||
logic runtime_clut_wr_en, runtime_clut_busy;
|
||||
logic [7:0] runtime_clut_wr_idx;
|
||||
logic [31:0] runtime_clut_wr_data;
|
||||
logic [31:0] runtime_pal [0:255];
|
||||
integer frag_fh;
|
||||
integer frag_epoch;
|
||||
|
||||
top_psmct32_raster_demo_bram #(
|
||||
.H_ACTIVE(FBPXW), .V_ACTIVE(FBH),
|
||||
.VRAM_BYTES(VRAM_BYTES_P), .VRAM_ENABLE_READ2(1'b0), .PSMCT32_SWIZZLE(1'b0),
|
||||
.COMBINED_TAZ(1'b0), .TILE_LOCAL(1'b0), .TILE_COLS(1), .TILE_ROWS(1),
|
||||
.TILE_MULTIPRIM(1'b0), .TILE_PRIM_COUNT(1), .TILE_FIFO_DEPTH(8),
|
||||
.BIN_BUFFER_ENABLE(1'b0), .HEARTBEAT_SPLICE_ENABLE(1'b0),
|
||||
.FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(STG_WORDS), .FEEDER_AUTOSTART(1'b0),
|
||||
.PERSPECTIVE_CORRECT(1'b1), .PERSP_RECIP_IDX_BITS(11),
|
||||
.GRAD_SEQ_DIVIDER(1'b1), .GRAD_DIV_CYCLES(4),
|
||||
.GS_LPDDR_TEX(1'b1), .TEX_VRAM_BASE(TEX_VRAM_BASE), .TEX_CACHE_BYTES(TEX_BYTES),
|
||||
.CLUT_CSM1_ENABLE(1'b1), .FB_LPDDR_ONLY(1'b1)
|
||||
) dut (
|
||||
.clk(clk), .rst_n(rst_n), .core_go(core_go),
|
||||
.r(r), .g(g), .b(b), .hsync(hsync), .vsync(vsync_o), .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),
|
||||
.feeder_stg_we_i(stg_we), .feeder_stg_waddr_i(stg_waddr), .feeder_stg_wdata_i(stg_wdata),
|
||||
.feeder_go_i(feeder_go_tb), .feeder_ready_o(feeder_ready_tb),
|
||||
.feeder_records_o(feeder_records_w), .feeder_waits_o(feeder_waits_w),
|
||||
.flush_emit_o(flush_emit_w), .flush_addr_o(flush_addr_w),
|
||||
.flush_pix16_o(), .flush_color32_o(flush_color32_w), .flush_psm_o(flush_psm_w),
|
||||
.flush_x_o(flush_x_w), .flush_y_o(flush_y_w), .flush_z_o(flush_z_w),
|
||||
.gs_tex_rd_en_o(gs_tex_rd_en_o), .gs_tex_rd_addr_o(gs_tex_rd_addr_o),
|
||||
.tex_cache_data_i(tex_cache_data), .tex_cache_ready_i(tex_cache_ready),
|
||||
.tex_cache_hits_o(tex_cache_hits), .tex_bram_hits_o(tex_bram_hits),
|
||||
.runtime_clut_wr_en_i(runtime_clut_wr_en), .runtime_clut_wr_idx_i(runtime_clut_wr_idx),
|
||||
.runtime_clut_wr_data_i(runtime_clut_wr_data), .runtime_clut_busy_i(runtime_clut_busy)
|
||||
);
|
||||
|
||||
// texture cache + behavioral texture LPDDR (reloaded per epoch)
|
||||
logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid; logic [7:0] arlen; logic [2:0] arsize;
|
||||
logic arvalid, arready; logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready;
|
||||
logic fill_start, fill_done; logic [31:0] fill_beats, fill_bytes, tex_rd_errs, fill_crc_w;
|
||||
gs_texture_cache #(.LPDDR_TEX_BASE(LPDDR_TEX_BASE), .TEX_VRAM_BASE(TEX_VRAM_BASE),
|
||||
.TEX_BYTES(TEX_BYTES), .N_BEATS(N_BEATS)) u_cache (
|
||||
.axi_clk(clk), .axi_rst_n(rst_n), .fill_start(fill_start), .fill_done(fill_done),
|
||||
.fill_beats(fill_beats), .fill_bytes(fill_bytes), .rd_errs(tex_rd_errs), .fill_crc(fill_crc_w),
|
||||
.araddr(araddr), .arburst(arburst), .arid(arid), .arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready),
|
||||
.rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready),
|
||||
.sample_clk(clk), .tex_rd_en(gs_tex_rd_en_o), .tex_rd_addr(gs_tex_rd_addr_o),
|
||||
.tex_rd_data(tex_cache_data), .tex_ready(tex_cache_ready)
|
||||
);
|
||||
logic [31:0] lpddr_mem [0:(TEX_BYTES/4)-1];
|
||||
typedef enum logic [1:0] { S_IDLE, S_WAIT, S_DATA } sstate_t; sstate_t sst; logic [3:0] tdly; int tbeat;
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin sst<=S_IDLE; arready<=0; rvalid<=0; rlast<=0; rresp<=0; rdata<=0; tdly<=0; end
|
||||
else begin arready<=0; rvalid<=0; rlast<=0;
|
||||
case (sst)
|
||||
S_IDLE: if (arvalid) begin arready<=1; tbeat<=(araddr-LPDDR_TEX_BASE)>>5; tdly<=4'd2; sst<=S_WAIT; end
|
||||
S_WAIT: if (tdly==0) sst<=S_DATA; else tdly<=tdly-1'b1;
|
||||
S_DATA: if (rready) begin for (int w=0;w<8;w++) rdata[w*32 +: 32]<=lpddr_mem[tbeat*8+w];
|
||||
rresp<=2'b00; rvalid<=1; rlast<=1; sst<=S_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// ===== render epoch: one ordered scene marker (sh3_fb_flush) per accepted GO =====
|
||||
logic fb_flush=0, render_inflight=0, feeder_ready_q=0; int eof_count;
|
||||
wire feeder_ready_rise = feeder_ready_tb && !feeder_ready_q;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin render_inflight<=0; feeder_ready_q<=0; fb_flush<=0; eof_count<=0; end
|
||||
else begin feeder_ready_q<=feeder_ready_tb; fb_flush<=1'b0;
|
||||
if (feeder_go_tb && feeder_ready_tb) render_inflight<=1'b1;
|
||||
if (render_inflight && feeder_ready_rise) begin fb_flush<=1'b1; render_inflight<=1'b0; eof_count<=eof_count+1; end
|
||||
end
|
||||
end
|
||||
|
||||
// ===== gs_lpddr_zc_emit — Z-then-color emit, fed by the REAL raster (NO handshake) =====
|
||||
logic zc_enable, clear_start, clear_done, frame_drained;
|
||||
wire [15:0] g_zq = cl16(flush_z_w);
|
||||
wire frag_v = flush_emit_w && (flush_psm_w==6'h00);
|
||||
wire g_valid = frag_v || fb_flush;
|
||||
wire g_scene = fb_flush;
|
||||
wire [11:0] g_x = flush_x_w, g_y = flush_y_w;
|
||||
wire [31:0] g_color = flush_color32_w;
|
||||
wire g_ready;
|
||||
// Z AXI
|
||||
logic [31:0] z_araddr; logic [7:0] z_arlen; logic [2:0] z_arsize; logic [1:0] z_arburst; logic z_arvalid, z_arready;
|
||||
logic [255:0] z_rdata; logic [1:0] z_rresp; logic z_rlast, z_rvalid, z_rready;
|
||||
logic [31:0] z_awaddr; logic [7:0] z_awlen; logic [2:0] z_awsize; logic [1:0] z_awburst; logic z_awvalid, z_awready;
|
||||
logic [255:0] z_wdata; logic [31:0] z_wstrb; logic z_wlast, z_wvalid, z_wready; logic z_bvalid, z_bready; logic [1:0] z_bresp;
|
||||
// Color AXI
|
||||
logic [31:0] c_awaddr; logic [7:0] c_awlen; logic [2:0] c_awsize; logic [1:0] c_awburst; logic c_awvalid, c_awready;
|
||||
logic [255:0] c_wdata; logic [31:0] c_wstrb; logic c_wlast, c_wvalid, c_wready; logic c_bvalid, c_bready; logic [1:0] c_bresp;
|
||||
logic [31:0] z_beats_read, z_beats_written, c_beats_written, col_ovf, bresp_err; logic zc_idle;
|
||||
gs_lpddr_zc_emit #(.COLBASE(COLBASE), .ZBASE(ZBASE), .FB_PXW(W), .FB_H(H), .REQ_DEPTH(1024), .COL_DEPTH(128)) u_zc (
|
||||
.gs_clk(clk), .gs_rst_n(rst_n), .enable(zc_enable),
|
||||
.g_valid(g_valid), .g_ready(g_ready), .g_x(g_x), .g_y(g_y), .g_zq(g_zq), .g_zmsk(1'b0),
|
||||
.g_ztest(1'b1), .g_ztst(2'd2), .g_color(g_color), .g_be(4'hF), .g_scene(g_scene),
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n), .clear_start(clear_start), .clear_done(clear_done), .frame_drained(frame_drained),
|
||||
.z_araddr(z_araddr), .z_arlen(z_arlen), .z_arsize(z_arsize), .z_arburst(z_arburst), .z_arvalid(z_arvalid), .z_arready(z_arready),
|
||||
.z_rdata(z_rdata), .z_rresp(z_rresp), .z_rlast(z_rlast), .z_rvalid(z_rvalid), .z_rready(z_rready),
|
||||
.z_awaddr(z_awaddr), .z_awlen(z_awlen), .z_awsize(z_awsize), .z_awburst(z_awburst), .z_awvalid(z_awvalid), .z_awready(z_awready),
|
||||
.z_wdata(z_wdata), .z_wstrb(z_wstrb), .z_wlast(z_wlast), .z_wvalid(z_wvalid), .z_wready(z_wready),
|
||||
.z_bvalid(z_bvalid), .z_bready(z_bready), .z_bresp(z_bresp),
|
||||
.c_awaddr(c_awaddr), .c_awlen(c_awlen), .c_awsize(c_awsize), .c_awburst(c_awburst), .c_awvalid(c_awvalid), .c_awready(c_awready),
|
||||
.c_wdata(c_wdata), .c_wstrb(c_wstrb), .c_wlast(c_wlast), .c_wvalid(c_wvalid), .c_wready(c_wready),
|
||||
.c_bvalid(c_bvalid), .c_bready(c_bready), .c_bresp(c_bresp),
|
||||
.z_beats_read(z_beats_read), .z_beats_written(z_beats_written), .c_beats_written(c_beats_written),
|
||||
.col_ovf(col_ovf), .bresp_err(bresp_err), .idle(zc_idle)
|
||||
);
|
||||
|
||||
// fragment-drop guard (design clk): the request FIFO silently drops when g_ready is low
|
||||
int g_drops; int frags_fed;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin g_drops<=0; frags_fed<=0; end
|
||||
else begin
|
||||
if (frag_v && !g_ready) g_drops<=g_drops+1;
|
||||
if (frag_v && g_ready) frags_fed<=frags_fed+1;
|
||||
end
|
||||
end
|
||||
// clear ordering: clear_done must rise before the first GO. no psm0 fragment before clear_done.
|
||||
logic saw_clear_done=0, first_go_done=0; int pre_clear_frags;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin saw_clear_done<=0; pre_clear_frags<=0; end
|
||||
else begin
|
||||
if (clear_done) saw_clear_done<=1;
|
||||
if (frag_v && !saw_clear_done) pre_clear_frags<=pre_clear_frags+1;
|
||||
end
|
||||
end
|
||||
|
||||
// ===== REAL de25 write arbiter: s0=color, s2=Z-write ; s1/s3 inert =====
|
||||
logic [29:0] m_awaddr; logic [1:0] m_awburst; logic [6:0] m_awid; logic [7:0] m_awlen; logic [2:0] m_awsize;
|
||||
logic m_awvalid, m_awready; logic [255:0] m_wdata; logic [31:0] m_wstrb; logic m_wlast, m_wvalid, m_wready;
|
||||
logic m_bvalid, m_bready; logic [1:0] m_bresp;
|
||||
gs_lpddr_wr_arb u_wr_arb (
|
||||
.clk(emif_clk), .rst_n(rst_n),
|
||||
.s0_awaddr(c_awaddr[29:0]), .s0_awburst(c_awburst), .s0_awid(7'd0), .s0_awlen(c_awlen), .s0_awsize(c_awsize),
|
||||
.s0_awvalid(c_awvalid), .s0_awready(c_awready), .s0_wdata(c_wdata), .s0_wstrb(c_wstrb), .s0_wlast(c_wlast),
|
||||
.s0_wvalid(c_wvalid), .s0_wready(c_wready), .s0_bresp(c_bresp), .s0_bvalid(c_bvalid), .s0_bready(c_bready),
|
||||
.s1_awaddr(30'd0), .s1_awburst(2'b01), .s1_awid(7'd1), .s1_awlen(8'd0), .s1_awsize(3'b101),
|
||||
.s1_awvalid(1'b0), .s1_awready(), .s1_wdata(256'd0), .s1_wstrb(32'd0), .s1_wlast(1'b0),
|
||||
.s1_wvalid(1'b0), .s1_wready(), .s1_bresp(), .s1_bvalid(), .s1_bready(1'b0),
|
||||
.s2_awaddr(z_awaddr[29:0]), .s2_awburst(z_awburst), .s2_awid(7'd2), .s2_awlen(z_awlen), .s2_awsize(z_awsize),
|
||||
.s2_awvalid(z_awvalid), .s2_awready(z_awready), .s2_wdata(z_wdata), .s2_wstrb(z_wstrb), .s2_wlast(z_wlast),
|
||||
.s2_wvalid(z_wvalid), .s2_wready(z_wready), .s2_bresp(z_bresp), .s2_bvalid(z_bvalid), .s2_bready(z_bready),
|
||||
.s3_awaddr(30'd0), .s3_awburst(2'b01), .s3_awid(7'd3), .s3_awlen(8'd0), .s3_awsize(3'b101),
|
||||
.s3_awvalid(1'b0), .s3_awready(), .s3_wdata(256'd0), .s3_wstrb(32'd0), .s3_wlast(1'b0),
|
||||
.s3_wvalid(1'b0), .s3_wready(), .s3_bresp(), .s3_bvalid(), .s3_bready(1'b0),
|
||||
.m_awaddr(m_awaddr), .m_awburst(m_awburst), .m_awid(m_awid), .m_awlen(m_awlen), .m_awsize(m_awsize),
|
||||
.m_awvalid(m_awvalid), .m_awready(m_awready), .m_wdata(m_wdata), .m_wstrb(m_wstrb), .m_wlast(m_wlast),
|
||||
.m_wvalid(m_wvalid), .m_wready(m_wready), .m_bvalid(m_bvalid), .m_bready(m_bready), .m_bresp(m_bresp)
|
||||
);
|
||||
|
||||
// ===== REAL de25 read arbiter: s3=Z-read (mirrors de25 reload port) ; s0/s1/s2 idle during render =====
|
||||
logic [29:0] m_araddr; logic [1:0] m_arburst; logic [6:0] m_arid; logic [7:0] m_arlen; logic [2:0] m_arsize;
|
||||
logic m_arvalid, m_arready; logic [255:0] m_rdata; logic [1:0] m_rresp; logic m_rlast, m_rvalid, m_rready;
|
||||
gs_lpddr_rd_arb u_rd_arb (
|
||||
.clk(emif_clk), .rst_n(rst_n),
|
||||
.s0_araddr(30'd0), .s0_arburst(2'b01), .s0_arid(7'd0), .s0_arlen(8'd0), .s0_arsize(3'b101),
|
||||
.s0_arvalid(1'b0), .s0_arready(), .s0_rdata(), .s0_rresp(), .s0_rlast(), .s0_rvalid(), .s0_rready(1'b0),
|
||||
.s1_araddr(30'd0), .s1_arburst(2'b01), .s1_arid(7'd1), .s1_arlen(8'd0), .s1_arsize(3'b101),
|
||||
.s1_arvalid(1'b0), .s1_arready(), .s1_rdata(), .s1_rresp(), .s1_rlast(), .s1_rvalid(), .s1_rready(1'b0),
|
||||
.s2_araddr(30'd0), .s2_arburst(2'b01), .s2_arid(7'd2), .s2_arlen(8'd0), .s2_arsize(3'b101),
|
||||
.s2_arvalid(1'b0), .s2_arready(), .s2_rdata(), .s2_rresp(), .s2_rlast(), .s2_rvalid(), .s2_rready(1'b0),
|
||||
.s3_araddr(z_araddr[29:0]), .s3_arburst(z_arburst), .s3_arid(7'd3), .s3_arlen(z_arlen), .s3_arsize(z_arsize),
|
||||
.s3_arvalid(z_arvalid), .s3_arready(z_arready), .s3_rdata(z_rdata), .s3_rresp(z_rresp), .s3_rlast(z_rlast),
|
||||
.s3_rvalid(z_rvalid), .s3_rready(z_rready),
|
||||
.m_araddr(m_araddr), .m_arburst(m_arburst), .m_arid(m_arid), .m_arlen(m_arlen), .m_arsize(m_arsize),
|
||||
.m_arvalid(m_arvalid), .m_arready(m_arready), .m_rdata(m_rdata), .m_rresp(m_rresp), .m_rlast(m_rlast),
|
||||
.m_rvalid(m_rvalid), .m_rready(m_rready)
|
||||
);
|
||||
|
||||
logic [15:0] lf=16'hF00D; always_ff @(posedge emif_clk) lf<={lf[14:0], lf[15]^lf[13]^lf[12]^lf[10]};
|
||||
|
||||
// ===== ONE shared behavioral LPDDR (write + read), address-decoded, random backpressure =====
|
||||
logic [255:0] shmem [0:MEMBEATS-1];
|
||||
logic [29:0] wa; logic [255:0] wd; logic [31:0] ws; logic aw_s, w_s; logic [3:0] bd; logic bp;
|
||||
logic [29:0] ra; logic rp; logic [3:0] rd_dly;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if(!rst_n) begin
|
||||
m_awready<=0; m_wready<=0; m_bvalid<=0; m_bresp<=0; aw_s<=0; w_s<=0; bp<=0; bd<=0;
|
||||
m_arready<=0; m_rvalid<=0; m_rlast<=0; m_rresp<=0; m_rdata<=0; rp<=0; rd_dly<=0;
|
||||
end else begin
|
||||
m_awready<=(!aw_s)?lf[0]:1'b0; m_wready<=(!w_s)?lf[1]:1'b0;
|
||||
if(m_awvalid&&m_awready) begin wa<=m_awaddr; aw_s<=1; m_awready<=0; end
|
||||
if(m_wvalid&&m_wready) begin wd<=m_wdata; ws<=m_wstrb; w_s<=1; m_wready<=0; end
|
||||
if(aw_s&&w_s&&!bp&&!m_bvalid) begin
|
||||
for(int bb=0;bb<32;bb++) if(ws[bb]) shmem[wa>>5][bb*8+:8]<=wd[bb*8+:8];
|
||||
bp<=1; bd<={1'b0,lf[4:2]};
|
||||
end
|
||||
if(bp&&!m_bvalid) begin if(bd==0) begin m_bvalid<=1;m_bresp<=0;bp<=0;aw_s<=0;w_s<=0; end else bd<=bd-1; end
|
||||
if(m_bvalid&&m_bready) m_bvalid<=0;
|
||||
m_arready<=(!rp&&!m_rvalid)?lf[8]:1'b0;
|
||||
if(m_arvalid&&m_arready) begin ra<=m_araddr; rp<=1; rd_dly<={1'b0,lf[7:5]}; m_arready<=0; end
|
||||
if(rp&&!m_rvalid) begin if(rd_dly==0) begin m_rdata<=shmem[ra>>5]; m_rresp<=0; m_rvalid<=1; m_rlast<=1; rp<=0; end else rd_dly<=rd_dly-1; end
|
||||
if(m_rvalid&&m_rready) begin m_rvalid<=0; m_rlast<=0; end
|
||||
end
|
||||
end
|
||||
// disjoint-map monitors on the MERGED master streams
|
||||
always_ff @(posedge emif_clk) if(rst_n) begin
|
||||
if(m_awvalid) begin
|
||||
if(!((m_awaddr<30'(COLBASE+COL_TOP)) || (m_awaddr>=30'(ZBASE) && m_awaddr<30'(ZBASE+Z_TOP))))
|
||||
begin $error("[zint] merged AW %h not in color/Z range",m_awaddr); errors++; end
|
||||
end
|
||||
if(m_arvalid && (m_araddr<30'(ZBASE) || m_araddr>=30'(ZBASE+Z_TOP))) begin $error("[zint] merged AR %h not in Z range",m_araddr); errors++; end
|
||||
end
|
||||
|
||||
// ===== clamp16 persistent-Z SCOREBOARD over the ACTUAL emitted fragments (design clk) =====
|
||||
// Mirrors gs_lpddr_z_rmw: GEQUAL vs stored, Z_CLEAR=0. Persists across epochs. Gated active after clear_done.
|
||||
logic [15:0] sb_z [0:NPX-1];
|
||||
logic [31:0] sb_col[0:NPX-1];
|
||||
logic sb_wr [0:NPX-1];
|
||||
integer sb_frag, sb_pass;
|
||||
// no-fragment-after-marker: a fragment must not appear in the same scene after fb_flush (before next GO)
|
||||
logic scene_ended=0; int frag_after_marker;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin
|
||||
sb_frag<=0; sb_pass<=0; scene_ended<=0; frag_after_marker<=0;
|
||||
end else begin
|
||||
if (feeder_go_tb && feeder_ready_tb) scene_ended<=0; // new scene opens
|
||||
if (fb_flush) scene_ended<=1; // ordered marker closes the scene
|
||||
if (frag_v && saw_clear_done) begin
|
||||
int o; logic [15:0] zq;
|
||||
if (frag_fh != 0)
|
||||
$fwrite(frag_fh, "%0d %0d %0d %0d %08x\n", frag_epoch, g_x, g_y, flush_z_w, flush_color32_w);
|
||||
if (scene_ended) frag_after_marker<=frag_after_marker+1; // fragment after this scene's marker = ordering bug
|
||||
o = g_y*W + g_x; zq = cl16(flush_z_w);
|
||||
sb_frag<=sb_frag+1;
|
||||
if (o>=0 && o<NPX) begin
|
||||
if (zq >= sb_z[o]) begin sb_z[o]<=zq; sb_col[o]<=flush_color32_w; sb_wr[o]<=1'b1; sb_pass<=sb_pass+1; end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// FRESH-DRAIN observer (emif domain)
|
||||
logic fd_q; int fd_rises, fd_falls;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin fd_q<=0; fd_rises<=0; fd_falls<=0; end
|
||||
else begin fd_q<=frame_drained;
|
||||
if (frame_drained && !fd_q) fd_rises<=fd_rises+1;
|
||||
if (!frame_drained && fd_q) fd_falls<=fd_falls+1;
|
||||
end
|
||||
end
|
||||
|
||||
// clear_start: mirror de25 — pulse once at the writer ARM (after preclear, before first GO)
|
||||
logic wr_arm=0, arm_e1, arm_e2, arm_e3;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin arm_e1<=0; arm_e2<=0; arm_e3<=0; clear_start<=0; end
|
||||
else begin arm_e1<=wr_arm; arm_e2<=arm_e1; arm_e3<=arm_e2; clear_start<=(arm_e2 && !arm_e3); end
|
||||
end
|
||||
|
||||
// ---- feeder staging stream (write port), per epoch ----
|
||||
logic [63:0] stg_buf [0:STG_WORDS-1];
|
||||
task automatic stream_list(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/feeder_sh3_zsrt3%0d.mem", k);
|
||||
for (int i=0;i<STG_WORDS;i++) stg_buf[i]=64'd0;
|
||||
$readmemh(fn, stg_buf);
|
||||
@(negedge clk);
|
||||
for (int i=0;i<STG_WORDS;i++) begin stg_we<=1'b1; stg_waddr<=i[11:0]; stg_wdata<=stg_buf[i]; @(negedge clk); end
|
||||
stg_we<=1'b0; stg_waddr<=12'd0; stg_wdata<=64'd0; @(negedge clk);
|
||||
if (dut.g_feeder.feeder_stg[0][31:0] !== stg_buf[0][31:0]) begin
|
||||
$error("[zint] epoch %0d: staged word0=%08x exp %08x", k, dut.g_feeder.feeder_stg[0][31:0], stg_buf[0][31:0]); errors++; end
|
||||
endtask
|
||||
|
||||
task automatic fill_cache(input int k, input logic [31:0] exp_crc);
|
||||
int d=0;
|
||||
$display("[zint-rt3] epoch %0d: texture fill start", k); $fflush();
|
||||
@(posedge clk) fill_start<=1'b1; @(posedge clk) fill_start<=1'b0;
|
||||
d=0; while (fill_done && d<1000) begin @(posedge clk); d++; end
|
||||
d=0; while (!fill_done && d<400000) begin @(posedge clk); d++; end
|
||||
if (!fill_done) begin $error("[zint] fill %0d: fill_done never rose", k); errors++; end
|
||||
if (fill_crc_w!==exp_crc) begin $error("[zint] fill %0d: crc=%08x exp %08x", k, fill_crc_w, exp_crc); errors++; end
|
||||
if (tex_rd_errs!==0) begin $error("[zint] fill %0d: rd_errs=%0d", k, tex_rd_errs); errors++; end
|
||||
$display("[zint-rt3] epoch %0d: texture fill done", k); $fflush();
|
||||
endtask
|
||||
|
||||
task automatic load_runtime_palette(input int k);
|
||||
string fn;
|
||||
fn = $sformatf("../../data/top_psmct32_raster_demo/sh3_zsrt3%0d_pal.mem", k);
|
||||
$display("[zint-rt3] epoch %0d: runtime palette start", k); $fflush();
|
||||
$readmemh(fn, runtime_pal);
|
||||
@(negedge clk);
|
||||
runtime_clut_busy <= 1'b1;
|
||||
for (int i=0; i<256; i++) begin
|
||||
runtime_clut_wr_en <= 1'b1;
|
||||
runtime_clut_wr_idx <= i[7:0];
|
||||
runtime_clut_wr_data <= runtime_pal[i];
|
||||
@(negedge clk);
|
||||
end
|
||||
runtime_clut_wr_en <= 1'b0;
|
||||
runtime_clut_wr_idx <= '0;
|
||||
runtime_clut_wr_data <= '0;
|
||||
@(negedge clk);
|
||||
runtime_clut_busy <= 1'b0;
|
||||
@(negedge clk);
|
||||
$display("[zint-rt3] epoch %0d: runtime palette done", k); $fflush();
|
||||
endtask
|
||||
|
||||
// one-scene runner: GO, wait render+drain, REQUIRE a FRESH ordered frame_drained
|
||||
task automatic run_scene(input int k, input int exp_records);
|
||||
int r0, f0, d=0; logic fd_before; int gd0, fm0;
|
||||
r0=fd_rises; f0=fd_falls; fd_before=frame_drained; gd0=g_drops; fm0=frag_after_marker;
|
||||
@(negedge clk); feeder_go_tb=1'b1; @(negedge clk); feeder_go_tb=1'b0;
|
||||
d=0; while (feeder_ready_tb && d<4000) begin @(posedge clk); d++; end
|
||||
if (feeder_ready_tb!==1'b0) begin $error("[zint] epoch %0d: feeder_ready did not drop after GO", k); errors++; end
|
||||
if (fd_before) begin
|
||||
d=0; while (fd_falls==f0 && d<1500000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_falls==f0) begin $error("[zint] epoch %0d: frame_drained never fell from stale high", k); errors++; end
|
||||
end
|
||||
d=0; while (fd_rises<=r0 && d<1500000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_rises<=r0) begin $error("[zint] epoch %0d: frame_drained never rose", k); errors++; end
|
||||
repeat(100) @(posedge clk);
|
||||
if (feeder_records_w!==exp_records) begin $error("[zint] epoch %0d: records=%0d exp %0d", k, feeder_records_w, exp_records); errors++; end
|
||||
if (col_ovf!==0 || bresp_err!==0) begin $error("[zint] epoch %0d: col_ovf=%0d bresp_err=%0d", k, col_ovf, bresp_err); errors++; end
|
||||
if (g_drops!==gd0) begin $error("[zint] epoch %0d: %0d FRAGMENT DROPS (request FIFO overflow)", k, g_drops-gd0); errors++; end
|
||||
if (frag_after_marker!==fm0) begin $error("[zint] epoch %0d: %0d fragment(s) after scene marker (ordering)", k, frag_after_marker-fm0); errors++; end
|
||||
$display("[zint] epoch %0d: fresh drain (f %0d->%0d, r %0d->%0d) records=%0d drops=%0d fed=%0d pass=%0d",
|
||||
k, f0, fd_falls, r0, fd_rises, feeder_records_w, g_drops, frags_fed, sb_pass);
|
||||
endtask
|
||||
|
||||
task automatic run_epoch(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/sh3_zsrt3%0d_tex_lpddr.mem", k);
|
||||
$display("[zint-rt3] epoch %0d: begin", k); $fflush();
|
||||
$readmemh(fn, lpddr_mem);
|
||||
fill_cache(k, EP_CRC[k]);
|
||||
load_runtime_palette(k);
|
||||
stream_list(k);
|
||||
frag_epoch = k;
|
||||
run_scene(k, EP_REC[k]);
|
||||
$display("[zint-rt3] epoch %0d: done", k); $fflush();
|
||||
endtask
|
||||
|
||||
initial begin
|
||||
errors=0; feeder_go_tb=1'b0; fill_start=1'b0; wr_arm=0; zc_enable=1'b1; stg_we=0; stg_waddr=0; stg_wdata=0;
|
||||
runtime_clut_wr_en=0; runtime_clut_wr_idx='0; runtime_clut_wr_data='0; runtime_clut_busy=0; frag_epoch=0;
|
||||
frag_fh=$fopen("zsrt3_frags.txt", "w");
|
||||
if (frag_fh==0) begin $error("[zint] could not open zsrt3_frags.txt"); $finish; end
|
||||
for (int i=0;i<NPX;i++) begin sb_z[i]=16'h0000; sb_col[i]=32'd0; sb_wr[i]=1'b0; end
|
||||
for (int b=0;b<MEMBEATS;b++) shmem[b]=256'd0; // PRECLEAR color+Z EXACTLY ONCE
|
||||
// skip-guard: probe a fixture (via $fopen — avoids the $readmemh range warning)
|
||||
begin int pfh; pfh=$fopen("../../data/top_psmct32_raster_demo/sh3_zsrt30_tex_lpddr.mem","r");
|
||||
if (pfh==0) begin $display("[tb_top_psmct32_sh3_zint640rt3] SKIP — sh3_zsrt3*.mem absent (run gs_make_sh3_scheduler_fixture.py --authz --fb640 --tag zsrt3 --runtime-clut --emit)"); $finish; end
|
||||
$fclose(pfh);
|
||||
end
|
||||
|
||||
rst_n=1'b0; core_go=1'b0; repeat(6) @(posedge clk); rst_n=1'b1; repeat(8) @(posedge clk);
|
||||
@(negedge clk); core_go=1'b1; @(negedge clk); core_go=1'b0;
|
||||
begin int d=0; while(core_halt!==1'b1 && d<2000000) begin @(posedge clk); d++; end
|
||||
if(core_halt!==1'b1) begin $error("[zint-rt3] boot timeout waiting for core_halt"); $finish; end end
|
||||
repeat(4) @(posedge clk);
|
||||
// zsrt3 is an HPS-resident runtime-CLUT fixture. Its board flow begins after
|
||||
// the boot handoff has completed, but this standalone raster top has no HPS
|
||||
// bootlet agent to produce that one-shot DMA-complete event. Model the already
|
||||
// initialized board state; the test still drives every staged list, palette,
|
||||
// texture fill, render, drain, and LPDDR transaction explicitly.
|
||||
force dut.dma_done_seen = 1'b1;
|
||||
force dut.xfer_busy = 1'b0;
|
||||
begin int d=0; while(feeder_ready_tb!==1'b1 && d<2000000) begin @(posedge clk); d++; end
|
||||
if(feeder_ready_tb!==1'b1) begin $error("[zint-rt3] boot timeout waiting for feeder_ready"); $finish; end end
|
||||
repeat(50) @(posedge clk);
|
||||
$display("[zint-rt3] boot and feeder ready"); $fflush();
|
||||
|
||||
// ARM (-> clear_start) AFTER preclear, BEFORE first GO. z_rmw's clear_start writes Z_CLEAR to EVERY Z beat
|
||||
// (NBEATS=NPX/16, through wr_arb s2 under random backpressure) then asserts clear_done -> it is the authoritative
|
||||
// Z preclear. Wait for it (the board host likewise gates the first GO on the clear_done status bit).
|
||||
wr_arm<=1'b1;
|
||||
begin int d=0; while (!clear_done && d<600000) begin @(posedge emif_clk); d++; end end
|
||||
if (!clear_done) begin $error("[zint] clear_done never rose after ARM (Z preclear did not finish)"); errors++; end
|
||||
else $display("[zint] Z preclear complete (clear_done), z_beats_written(clear)=%0d", z_beats_written);
|
||||
repeat(40) @(posedge clk);
|
||||
|
||||
for (int k=0;k<NEP;k++) run_epoch(k);
|
||||
|
||||
if (pre_clear_frags!==0) begin $error("[zint] %0d psm0 fragment(s) before clear_done", pre_clear_frags); errors++; end
|
||||
if (fd_rises<NEP) begin $error("[zint] expected %0d ordered drains: rises=%0d", NEP, fd_rises); errors++; end
|
||||
if (fd_falls<NEP-1) begin $error("[zint] epochs after the first never cleared stale (falls=%0d)", fd_falls); errors++; end
|
||||
if (eof_count!==NEP)begin $error("[zint] scene markers=%0d != accepted GOs=%0d", eof_count, NEP); errors++; end
|
||||
if (g_drops!==0) begin $error("[zint] TOTAL %0d fragment drops", g_drops); errors++; end
|
||||
|
||||
// ===== final memory compare: shared LPDDR color + Z == scoreboard =====
|
||||
begin
|
||||
int zmis, cmis, zchk, cchk; zmis=0; cmis=0; zchk=0; cchk=0;
|
||||
for (int o=0;o<NPX;o++) begin
|
||||
logic [15:0] zs; logic [31:0] cs; int zb, zl;
|
||||
zb=(ZBASE + o*2)>>5; zl=o&15; zs=shmem[zb][zl*16+:16];
|
||||
zchk++;
|
||||
if (zs !== sb_z[o]) begin if (zmis<10) $error("[zint] Z px%0d got %04x exp %04x", o, zs, sb_z[o]); zmis++; end
|
||||
if (sb_wr[o]) begin
|
||||
cs=shmem[o>>3][(o[2:0])*32+:32]; cchk++;
|
||||
if (cs[23:0] !== sb_col[o][23:0]) begin if (cmis<10) $error("[zint] COL px%0d got %06x exp %06x", o, cs[23:0], sb_col[o][23:0]); cmis++; end
|
||||
end
|
||||
end
|
||||
$display("[zint] final compare: Z %0d/%0d mismatch, COLOR %0d/%0d mismatch (written px=%0d)", zmis, zchk, cmis, cchk, cchk);
|
||||
end
|
||||
|
||||
begin
|
||||
string fdump;
|
||||
if ($value$plusargs("FBDUMP=%s", fdump)) begin
|
||||
int fh;
|
||||
fh = $fopen(fdump, "w");
|
||||
if (fh == 0) begin
|
||||
$error("[zint] could not open FBDUMP '%s'", fdump);
|
||||
errors++;
|
||||
end else begin
|
||||
for (int o=0;o<NPX;o++) begin
|
||||
logic [31:0] cs;
|
||||
cs = shmem[o>>3][(o[2:0])*32+:32];
|
||||
$fdisplay(fh, "%08x", cs);
|
||||
end
|
||||
$fclose(fh);
|
||||
$display("[zint] dumped final COLOR FB (%0dx%0d) -> %s", W, H, fdump);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
$display("[tb_top_psmct32_sh3_zint640rt3] fed=%0d pass=%0d drops=%0d markers=%0d drains(r/f)=%0d/%0d col_ovf=%0d bresp_err=%0d errors=%0d",
|
||||
frags_fed, sb_pass, g_drops, eof_count, fd_rises, fd_falls, col_ovf, bresp_err, errors);
|
||||
$display("[zint] map: COLOR 0x%0h..0x%0h Z 0x%0h..0x%0h", COLBASE, COLBASE+COL_TOP, ZBASE, ZBASE+Z_TOP);
|
||||
$fclose(frag_fh);
|
||||
if (errors==0) $display("[tb_top_psmct32_sh3_zint640rt3] PASS"); else $display("[tb_top_psmct32_sh3_zint640rt3] FAIL");
|
||||
$finish;
|
||||
end
|
||||
initial begin #400000000; $error("[tb_top_psmct32_sh3_zint640rt3] TIMEOUT"); $finish; end
|
||||
endmodule : tb_top_psmct32_sh3_zint640rt3
|
||||
@@ -0,0 +1,184 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_sh3_zrop (Ch357 — persistent-Z ROP acceptance gate, TRACE-based scoreboard)
|
||||
//
|
||||
// Codex option A: verify the LPDDR-Z ROP (gs_lpddr_zc_emit) against the raster's ACTUAL emitted fragments. This TB replays
|
||||
// the captured fragment trace (tb_top_psmct32_sh3_zsched -> zsched_frags.txt: "epoch x y persp_z color" per line) through
|
||||
// zc_emit across two clock domains, with a per-epoch scene marker, and scores it with an INDEPENDENT software scoreboard
|
||||
// (clamp16 / GEQUAL / persistence). Proves Codex's Ch357 gates:
|
||||
// * every replayed fragment enters the ROP EXACTLY once (we feed each once and wait g_ready — no drop, no double).
|
||||
// * the scoreboard predicts every pass/reject and the final packed 16-bit Z; failed depth tests emit NO color.
|
||||
// * final color + Z LPDDR memories match the scoreboard EXACTLY.
|
||||
// * the ordered end-of-scene marker drain waits BOTH pipelines (color + Z BRESPs) before frame_drained.
|
||||
// This is the ROP-correctness gate; the raster's Z-interp fidelity (float-oracle 3971 / 87.6% owner agreement) is a SEPARATE
|
||||
// recorded limitation, not tested here. Primary fixture = sh3_zsched [8634,12757,145742], FB 256x210, Z base 0x140000.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_sh3_zrop;
|
||||
localparam int FB_PXW=256, FB_H=210;
|
||||
localparam int NPX=FB_PXW*FB_H; // 53760
|
||||
localparam int ZBEATS=(NPX+15)/16; // 3360
|
||||
localparam int CBEATS=(NPX+7)/8; // 6720
|
||||
localparam [31:0] COLBASE=32'h0000_0000, ZBASE=32'h0014_0000;
|
||||
localparam int ZBW=$clog2(ZBEATS), CBW=$clog2(CBEATS);
|
||||
|
||||
logic gs_clk=0; always #5 gs_clk=~gs_clk;
|
||||
logic axi_clk=0; always #2 axi_clk=~axi_clk; // emif faster than design -> ROP drains faster than the trace feeds
|
||||
logic gs_rst_n, axi_rst_n; int errors; initial errors=0;
|
||||
|
||||
logic enable, clear_start, clear_done, frame_drained;
|
||||
logic g_valid, g_ready; logic [11:0] g_x, g_y; logic [15:0] g_zq; logic g_zmsk, g_ztest, g_scene; logic [31:0] g_color;
|
||||
logic [31:0] z_araddr; logic [7:0] z_arlen; logic [2:0] z_arsize; logic [1:0] z_arburst; logic z_arvalid, z_arready;
|
||||
logic [255:0] z_rdata; logic [1:0] z_rresp; logic z_rlast, z_rvalid, z_rready;
|
||||
logic [31:0] z_awaddr; logic [7:0] z_awlen; logic [2:0] z_awsize; logic [1:0] z_awburst; logic z_awvalid, z_awready;
|
||||
logic [255:0] z_wdata; logic [31:0] z_wstrb; logic z_wlast, z_wvalid, z_wready; logic z_bvalid, z_bready; logic [1:0] z_bresp;
|
||||
logic [31:0] c_awaddr; logic [7:0] c_awlen; logic [2:0] c_awsize; logic [1:0] c_awburst; logic c_awvalid, c_awready;
|
||||
logic [255:0] c_wdata; logic [31:0] c_wstrb; logic c_wlast, c_wvalid, c_wready; logic c_bvalid, c_bready; logic [1:0] c_bresp;
|
||||
logic [31:0] z_beats_read, z_beats_written, c_beats_written, col_ovf, bresp_err; logic idle;
|
||||
|
||||
gs_lpddr_zc_emit #(.COLBASE(COLBASE), .ZBASE(ZBASE), .FB_PXW(FB_PXW), .FB_H(FB_H), .REQ_DEPTH(32), .COL_DEPTH(64)) dut (
|
||||
.gs_clk(gs_clk), .gs_rst_n(gs_rst_n), .enable(enable),
|
||||
.g_valid(g_valid), .g_ready(g_ready), .g_x(g_x), .g_y(g_y), .g_zq(g_zq), .g_zmsk(g_zmsk),
|
||||
.g_ztest(g_ztest), .g_ztst(2'd2), .g_color(g_color), .g_be(4'hF), .g_scene(g_scene),
|
||||
.axi_clk(axi_clk), .axi_rst_n(axi_rst_n), .clear_start(clear_start), .clear_done(clear_done), .frame_drained(frame_drained),
|
||||
.z_araddr(z_araddr), .z_arlen(z_arlen), .z_arsize(z_arsize), .z_arburst(z_arburst), .z_arvalid(z_arvalid), .z_arready(z_arready),
|
||||
.z_rdata(z_rdata), .z_rresp(z_rresp), .z_rlast(z_rlast), .z_rvalid(z_rvalid), .z_rready(z_rready),
|
||||
.z_awaddr(z_awaddr), .z_awlen(z_awlen), .z_awsize(z_awsize), .z_awburst(z_awburst), .z_awvalid(z_awvalid), .z_awready(z_awready),
|
||||
.z_wdata(z_wdata), .z_wstrb(z_wstrb), .z_wlast(z_wlast), .z_wvalid(z_wvalid), .z_wready(z_wready),
|
||||
.z_bvalid(z_bvalid), .z_bready(z_bready), .z_bresp(z_bresp),
|
||||
.c_awaddr(c_awaddr), .c_awlen(c_awlen), .c_awsize(c_awsize), .c_awburst(c_awburst), .c_awvalid(c_awvalid), .c_awready(c_awready),
|
||||
.c_wdata(c_wdata), .c_wstrb(c_wstrb), .c_wlast(c_wlast), .c_wvalid(c_wvalid), .c_wready(c_wready),
|
||||
.c_bvalid(c_bvalid), .c_bready(c_bready), .c_bresp(c_bresp),
|
||||
.z_beats_read(z_beats_read), .z_beats_written(z_beats_written), .c_beats_written(c_beats_written),
|
||||
.col_ovf(col_ovf), .bresp_err(bresp_err), .idle(idle)
|
||||
);
|
||||
|
||||
logic [15:0] lf=16'hCAFE; always_ff @(posedge axi_clk) lf<={lf[14:0], lf[15]^lf[13]^lf[12]^lf[10]};
|
||||
|
||||
// ---- Z LPDDR slave (256b, random backpressure) ----
|
||||
logic [255:0] zmem [0:ZBEATS-1];
|
||||
logic [31:0] zra; logic zrp; logic [3:0] zrd; logic [31:0] zwa; logic [255:0] zwd; logic zaw,zw; logic [3:0] zbd; logic zbp;
|
||||
always_ff @(posedge axi_clk or negedge axi_rst_n) begin
|
||||
if(!axi_rst_n) begin z_arready<=0;z_rvalid<=0;z_rlast<=0;z_rresp<=0;z_rdata<=0;zrp<=0;zrd<=0;
|
||||
z_awready<=0;z_wready<=0;z_bvalid<=0;z_bresp<=0;zaw<=0;zw<=0;zbp<=0;zbd<=0; end
|
||||
else begin
|
||||
z_arready<=(!zrp&&!z_rvalid)?lf[0]:1'b0;
|
||||
if(z_arvalid&&z_arready) begin zra<=z_araddr;zrp<=1;zrd<={1'b0,lf[3:1]};z_arready<=0; end
|
||||
if(zrp&&!z_rvalid) begin if(zrd==0) begin z_rdata<=zmem[(zra-ZBASE)>>5];z_rresp<=0;z_rvalid<=1;z_rlast<=1;zrp<=0; end else zrd<=zrd-1; end
|
||||
if(z_rvalid&&z_rready) begin z_rvalid<=0;z_rlast<=0; end
|
||||
z_awready<=(!zaw)?lf[4]:1'b0; z_wready<=(!zw)?lf[5]:1'b0;
|
||||
if(z_awvalid&&z_awready) begin zwa<=z_awaddr;zaw<=1;z_awready<=0; end
|
||||
if(z_wvalid&&z_wready) begin zwd<=z_wdata;zw<=1;z_wready<=0; end
|
||||
if(zaw&&zw&&!zbp&&!z_bvalid) begin zmem[(zwa-ZBASE)>>5]<=zwd;zbp<=1;zbd<={1'b0,lf[8:6]}; end
|
||||
if(zbp&&!z_bvalid) begin if(zbd==0) begin z_bvalid<=1;z_bresp<=0;zbp<=0;zaw<=0;zw<=0; end else zbd<=zbd-1; end
|
||||
if(z_bvalid&&z_bready) z_bvalid<=0;
|
||||
end
|
||||
end
|
||||
// ---- Color LPDDR slave (256b, per-byte wstrb, random backpressure) ----
|
||||
logic [255:0] cmem [0:CBEATS-1];
|
||||
logic [31:0] cwa; logic [255:0] cwd; logic [31:0] cws; logic caw,cw; logic [3:0] cbd; logic cbp;
|
||||
always_ff @(posedge axi_clk or negedge axi_rst_n) begin
|
||||
if(!axi_rst_n) begin c_awready<=0;c_wready<=0;c_bvalid<=0;c_bresp<=0;caw<=0;cw<=0;cbp<=0;cbd<=0; end
|
||||
else begin
|
||||
c_awready<=(!caw)?lf[9]:1'b0; c_wready<=(!cw)?lf[11]:1'b0;
|
||||
if(c_awvalid&&c_awready) begin cwa<=c_awaddr;caw<=1;c_awready<=0; end
|
||||
if(c_wvalid&&c_wready) begin cwd<=c_wdata;cws<=c_wstrb;cw<=1;c_wready<=0; end
|
||||
if(caw&&cw&&!cbp&&!c_bvalid) begin
|
||||
for(int b=0;b<32;b++) if(cws[b]) cmem[(cwa-COLBASE)>>5][b*8+:8]<=cwd[b*8+:8];
|
||||
cbp<=1;cbd<={1'b0,lf[14:12]};
|
||||
end
|
||||
if(cbp&&!c_bvalid) begin if(cbd==0) begin c_bvalid<=1;c_bresp<=0;cbp<=0;caw<=0;cw<=0; end else cbd<=cbd-1; end
|
||||
if(c_bvalid&&c_bready) c_bvalid<=0;
|
||||
end
|
||||
end
|
||||
// disjoint-range monitors
|
||||
always_ff @(posedge axi_clk) if(axi_rst_n) begin
|
||||
if(z_arvalid && (z_araddr<ZBASE || z_araddr>=ZBASE+ZBEATS*32)) begin $error("Z AR %h OOR",z_araddr);errors++; end
|
||||
if(z_awvalid && (z_awaddr<ZBASE || z_awaddr>=ZBASE+ZBEATS*32)) begin $error("Z AW %h OOR",z_awaddr);errors++; end
|
||||
if(c_awvalid && (c_awaddr<COLBASE || c_awaddr>=COLBASE+CBEATS*32)) begin $error("C AW %h OOR",c_awaddr);errors++; end
|
||||
end
|
||||
|
||||
// ---- independent scoreboard (clamp16 persistent-Z over the fed fragments) ----
|
||||
logic [15:0] sb_z [0:NPX-1]; logic [31:0] sb_col [0:NPX-1]; logic sb_seen [0:NPX-1];
|
||||
function automatic logic [15:0] cl16(input logic [31:0] z); cl16=(|z[31:16])?16'hFFFF:z[15:0]; endfunction
|
||||
|
||||
// ---- feed one fragment: drive g_*, wait acceptance (exactly once), update the scoreboard ----
|
||||
int nfed;
|
||||
task automatic feed_frag(input int ep, input int x, input int y, input int z, input logic [31:0] col);
|
||||
int o; logic [15:0] zq; logic pass;
|
||||
@(negedge gs_clk);
|
||||
g_valid<=1; g_scene<=0; g_x<=x[11:0]; g_y<=y[11:0]; g_zq<=cl16(z); g_zmsk<=0; g_ztest<=1; g_color<=col;
|
||||
@(posedge gs_clk);
|
||||
while(!(g_valid && g_ready)) @(posedge gs_clk); // wait acceptance — NEVER drop a fragment
|
||||
@(negedge gs_clk); g_valid<=0;
|
||||
// scoreboard (in feed order == ROP processing order)
|
||||
o=y*FB_PXW+x; zq=cl16(z); pass=(zq>=sb_z[o]);
|
||||
if(pass) begin sb_col[o]=col; sb_seen[o]=1; sb_z[o]=zq; end
|
||||
nfed++;
|
||||
if(lf[7]) repeat(1+lf[1:0]) @(posedge gs_clk); // random inter-fragment gap
|
||||
endtask
|
||||
task automatic send_marker();
|
||||
@(negedge gs_clk); g_valid<=1; g_scene<=1; @(posedge gs_clk);
|
||||
while(!(g_valid && g_ready)) @(posedge gs_clk);
|
||||
@(negedge gs_clk); g_valid<=0; g_scene<=0;
|
||||
endtask
|
||||
|
||||
int fh, r, ep, x, y, z, cep, drains; logic [31:0] col; logic [1023:0] line;
|
||||
initial begin
|
||||
errors=0; enable=0; clear_start=0; g_valid=0; g_scene=0; g_x=0; g_y=0; g_zq=0; g_zmsk=0; g_ztest=1; g_color=0; nfed=0; drains=0;
|
||||
for(int i=0;i<NPX;i++) begin sb_z[i]=16'h0000; sb_col[i]=32'd0; sb_seen[i]=1'b0; end
|
||||
for(int b=0;b<CBEATS;b++) cmem[b]=256'd0; // host preclears the color FB
|
||||
gs_rst_n=0; axi_rst_n=0; repeat(6) @(posedge axi_clk); gs_rst_n=1; axi_rst_n=1; repeat(4) @(posedge axi_clk);
|
||||
enable=1;
|
||||
@(negedge axi_clk) clear_start=1; @(negedge axi_clk) clear_start=0; // preclear Z once
|
||||
begin int g=0; while(!clear_done && g<400000) begin @(posedge axi_clk); g++; end end
|
||||
if(!clear_done) begin $error("[zrop] Z preclear timeout"); errors++; end
|
||||
|
||||
fh=$fopen("zsched_frags.txt","r");
|
||||
if(fh==0) begin $display("[tb_top_psmct32_sh3_zrop] SKIP — zsched_frags.txt absent (run make tb_top_psmct32_sh3_zsched)"); $finish; end
|
||||
cep=0;
|
||||
while(!$feof(fh)) begin
|
||||
// zsched_frags.txt currently carries "epoch x y z color u v". Read one full line so the optional
|
||||
// trailing texture-debug fields cannot be re-parsed as a bogus next fragment.
|
||||
if(!$fgets(line, fh)) begin end
|
||||
r=$sscanf(line, "%d %d %d %d %h", ep, x, y, z, col);
|
||||
if(r==5) begin
|
||||
if(x<0 || x>=FB_PXW || y<0 || y>=FB_H) begin
|
||||
$error("[zrop] trace coord OOR ep=%0d x=%0d y=%0d z=%0d col=%08x", ep, x, y, z, col);
|
||||
errors++;
|
||||
end else begin
|
||||
if(ep!=cep) begin // epoch boundary -> ordered scene drain
|
||||
send_marker();
|
||||
begin int g=0; while(!frame_drained && g<800000) begin @(posedge axi_clk); g++; end end
|
||||
if(!frame_drained) begin $error("[zrop] epoch %0d: frame_drained never rose",cep); errors++; end
|
||||
else drains++;
|
||||
cep=ep;
|
||||
end
|
||||
feed_frag(ep, x, y, z, col);
|
||||
end
|
||||
end
|
||||
end
|
||||
$fclose(fh);
|
||||
// final scene marker (last epoch)
|
||||
send_marker();
|
||||
begin int g=0; while(!frame_drained && g<800000) begin @(posedge axi_clk); g++; end end
|
||||
if(!frame_drained) begin $error("[zrop] final: frame_drained never rose"); errors++; end else drains++;
|
||||
repeat(60) @(posedge axi_clk);
|
||||
|
||||
// ---- gates: final Z + color LPDDR == scoreboard ; suppression ; counts ----
|
||||
for(int o=0;o<NPX;o++) begin
|
||||
logic [15:0] zs; zs=zmem[o>>4][(o[3:0])*16+:16];
|
||||
if(zs!==sb_z[o]) begin if(errors<12)$error("[zrop] Z px%0d=%04x exp %04x",o,zs,sb_z[o]);errors++; end
|
||||
end
|
||||
for(int o=0;o<NPX;o++) begin
|
||||
logic [31:0] cs; cs=cmem[o>>3][(o[2:0])*32+:32];
|
||||
if(sb_seen[o]) begin if(cs!==sb_col[o]) begin if(errors<12)$error("[zrop] COLOR px%0d=%08x exp %08x",o,cs,sb_col[o]);errors++; end end
|
||||
else begin if(cs!==32'd0) begin if(errors<12)$error("[zrop] px%0d never-passed but color=%08x (suppression fail)",o,cs);errors++; end end
|
||||
end
|
||||
if(col_ovf!==0) begin $error("[zrop] col_ovf=%0d",col_ovf);errors++; end
|
||||
if(bresp_err!==0)begin $error("[zrop] bresp_err=%0d",bresp_err);errors++; end
|
||||
$display("[zrop] fed=%0d fragments, scene-drains=%0d, z_wr=%0d c_wr=%0d col_ovf=%0d bresp_err=%0d errors=%0d",
|
||||
nfed, drains, z_beats_written, c_beats_written, col_ovf, bresp_err, errors);
|
||||
if(errors==0) $display("[tb_top_psmct32_sh3_zrop] PASS"); else $display("[tb_top_psmct32_sh3_zrop] FAIL");
|
||||
$finish;
|
||||
end
|
||||
initial begin #400000000; $error("[tb_top_psmct32_sh3_zrop] TIMEOUT"); $finish; end
|
||||
endmodule : tb_top_psmct32_sh3_zrop
|
||||
@@ -0,0 +1,448 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_sh3_zs640_cap (Ch358 — authentic-Z capture at NATIVE 640x480, strong-reject scene)
|
||||
//
|
||||
// Generalizes Ch355's two-group flow to N authentic SH3 draw epochs, each with a DIFFERENT TEX0/CLUT, accumulating
|
||||
// into ONE LPDDR framebuffer via a data-driven scheduler over epoch descriptors (sh3_zs640_params.vh). Default
|
||||
// N_EPOCHS=3 strong-reject trio: E0=idx8634 E1=idx12757 E2=idx145742 at AUTHENTIC native coords x[407..619] y[97..306].
|
||||
// Also captures the fragment stream {epoch,x,y,persp_z,color} -> zs640_frags.txt. Proves the Ch356 integration acceptance:
|
||||
// * preclear EXACTLY once.
|
||||
// * N FRESH cache fills, each fill_done low->high, expected beats/bytes, 0 read errors, epoch CRC (EPk_CRC).
|
||||
// * N lists STREAMED through the feeder staging WRITE PORT (feeder_stg_we/waddr/wdata) — NOT the $readmemh backdoor
|
||||
// (closes the Ch355 sim gap where the write-port sequencing was untested). Each streams the full STG_WORDS so
|
||||
// staging is fully reset per epoch (no stale words), and word0 (the ntris header) is exercised first.
|
||||
// * N FRESH ORDERED drains: epoch 0 low->high (first render); epochs 1..N-1 high->low->high (stale cleared).
|
||||
// * scanout stays disabled until the LAST fresh drain; exact BEATS_PER_FRAME (=ROW_BEATS*H).
|
||||
// * final FB scored vs the independent composed reference using the PER-PIXEL OWNER epoch (exact palette per owner);
|
||||
// multi-epoch (>=2) overlap scored separately as the accumulation proof.
|
||||
// * +ONLY=<k> renders a SINGLE epoch (+FBDUMP dumps its FB) for the composition==isolated bit-for-bit check.
|
||||
// LOCAL/gitignored fixtures; skip-guard if absent.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_sh3_zs640_cap;
|
||||
`include "sh3_zs640_params.vh" // FBPXW=640, FBH=480, N_EPOCHS=3, EPk_CRC/EPk_NTRIS/EPk_CBP, TEX_*, ...
|
||||
localparam int W = FBPXW, H = FBH;
|
||||
localparam int STRIDE = W*4; // 1536
|
||||
localparam int ROW_BEATS = STRIDE/32; // 48
|
||||
localparam int FB_BYTES = STRIDE*H; // 585216
|
||||
localparam int FB_WORDS = W*H;
|
||||
localparam int BEATS_PER_FRAME = ROW_BEATS*H; // 18288
|
||||
localparam int H_ACT=W, V_ACT=H, H_BP=32, H_FP=8, V_BP=16, V_FP=8;
|
||||
localparam int H_TOT=H_BP+H_ACT+H_FP, V_TOT=V_BP+V_ACT+V_FP;
|
||||
localparam int NEP = N_EPOCHS; // 3
|
||||
|
||||
// per-epoch expected CRC / records (from the descriptor params)
|
||||
logic [31:0] EP_CRC [0:2]; int EP_REC [0:2];
|
||||
initial begin
|
||||
EP_CRC[0]=EP0_CRC; EP_CRC[1]=EP1_CRC; EP_CRC[2]=EP2_CRC;
|
||||
EP_REC[0]=EP0_NTRIS; EP_REC[1]=EP1_NTRIS; EP_REC[2]=EP2_NTRIS;
|
||||
end
|
||||
|
||||
logic clk=0; always #5 clk=~clk;
|
||||
logic emif_clk=0; always #2 emif_clk=~emif_clk;
|
||||
logic video_clk=0; always #7 video_clk=~video_clk;
|
||||
logic rst_n; logic fb_commit=0; always #13 fb_commit=~fb_commit;
|
||||
int errors; initial errors=0;
|
||||
|
||||
// ===== bram-top (FB_LPDDR_ONLY, AUTOSTART off, STG 2048), H/V_ACTIVE = 384x381 =====
|
||||
logic core_go; logic [7:0] r,g,b; logic hsync,vsync_o,de;
|
||||
logic core_halt,dma_done_seen,frame_seen,raster_overflow,frame_toggle,dma_done_toggle;
|
||||
logic feeder_go_tb, feeder_ready_tb; logic [15:0] feeder_records_w; logic [31:0] feeder_waits_w;
|
||||
logic gs_tex_rd_en_o; logic [31:0] gs_tex_rd_addr_o; logic [31:0] tex_cache_data; logic tex_cache_ready;
|
||||
logic [31:0] tex_cache_hits, tex_bram_hits;
|
||||
logic flush_emit_w; logic [31:0] flush_addr_w; logic [31:0] flush_color32_w; logic [5:0] flush_psm_w;
|
||||
logic [11:0] flush_x_w, flush_y_w; logic [31:0] flush_z_w; // Ch357 — fragment coords + persp_z5
|
||||
int cap_ep; int cap_fh; int tex_fh; int issue_fh; // fragment capture (verify persp_z5 vs the clamp16 oracle)
|
||||
logic [10:0] cap_pu_q, cap_pv_q, cap_pu_qq, cap_pv_qq; // persp_u/v delayed from texture issue to flush/color
|
||||
logic [31:0] tex_issue_addr_q, tex_issue_raw_q; // debug: texture request that returns on the next cycle
|
||||
logic [10:0] tex_issue_u_q, tex_issue_v_q;
|
||||
// feeder staging WRITE PORT (streamed per epoch)
|
||||
logic stg_we; logic [11:0] stg_waddr; logic [63:0] stg_wdata;
|
||||
|
||||
top_psmct32_raster_demo_bram #(
|
||||
.H_ACTIVE(FBPXW), .V_ACTIVE(FBH),
|
||||
.VRAM_BYTES(VRAM_BYTES_P), .VRAM_ENABLE_READ2(1'b0), .PSMCT32_SWIZZLE(1'b0),
|
||||
.COMBINED_TAZ(1'b0), .TILE_LOCAL(1'b0), .TILE_COLS(1), .TILE_ROWS(1),
|
||||
.TILE_MULTIPRIM(1'b0), .TILE_PRIM_COUNT(1), .TILE_FIFO_DEPTH(8),
|
||||
.BIN_BUFFER_ENABLE(1'b0), .HEARTBEAT_SPLICE_ENABLE(1'b0),
|
||||
.FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(STG_WORDS), .FEEDER_AUTOSTART(1'b0),
|
||||
.PERSPECTIVE_CORRECT(1'b1), .PERSP_RECIP_IDX_BITS(11),
|
||||
.GRAD_SEQ_DIVIDER(1'b1), .GRAD_DIV_CYCLES(4),
|
||||
.GS_LPDDR_TEX(1'b1), .TEX_VRAM_BASE(TEX_VRAM_BASE), .TEX_CACHE_BYTES(TEX_BYTES),
|
||||
.CLUT_CSM1_ENABLE(1'b1), .FB_LPDDR_ONLY(1'b1)
|
||||
) dut (
|
||||
.clk(clk), .rst_n(rst_n), .core_go(core_go),
|
||||
.r(r), .g(g), .b(b), .hsync(hsync), .vsync(vsync_o), .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),
|
||||
.feeder_stg_we_i(stg_we), .feeder_stg_waddr_i(stg_waddr), .feeder_stg_wdata_i(stg_wdata),
|
||||
.feeder_go_i(feeder_go_tb), .feeder_ready_o(feeder_ready_tb),
|
||||
.feeder_records_o(feeder_records_w), .feeder_waits_o(feeder_waits_w),
|
||||
.flush_emit_o(flush_emit_w), .flush_addr_o(flush_addr_w),
|
||||
.flush_pix16_o(), .flush_color32_o(flush_color32_w), .flush_psm_o(flush_psm_w),
|
||||
.flush_x_o(flush_x_w), .flush_y_o(flush_y_w), .flush_z_o(flush_z_w),
|
||||
.gs_tex_rd_en_o(gs_tex_rd_en_o), .gs_tex_rd_addr_o(gs_tex_rd_addr_o),
|
||||
.tex_cache_data_i(tex_cache_data), .tex_cache_ready_i(tex_cache_ready),
|
||||
.tex_cache_hits_o(tex_cache_hits), .tex_bram_hits_o(tex_bram_hits)
|
||||
);
|
||||
|
||||
// Ch357 — capture every emitted fragment {epoch, x, y, persp_z, color}. An external Python check replays these through
|
||||
// the clamp16 persistent-Z model and compares owner/reject to the oracle -> validates persp_z5 end-to-end (the new RTL).
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst_n) begin
|
||||
if (dut.u_gs.persp_outvalid) begin
|
||||
cap_pu_qq <= cap_pu_q;
|
||||
cap_pv_qq <= cap_pv_q;
|
||||
cap_pu_q <= dut.u_gs.persp_u;
|
||||
cap_pv_q <= dut.u_gs.persp_v;
|
||||
end
|
||||
if (gs_tex_rd_en_o) begin
|
||||
tex_issue_addr_q <= gs_tex_rd_addr_o;
|
||||
tex_issue_raw_q <= dut.u_gs.u_tex.addr;
|
||||
tex_issue_u_q <= dut.u_gs.u_tex.u_eff;
|
||||
tex_issue_v_q <= dut.u_gs.u_tex.v_eff;
|
||||
end
|
||||
if (flush_emit_w && (flush_psm_w==6'h00) && cap_fh!=0)
|
||||
$fwrite(cap_fh, "%0d %0d %0d %0d %08x %0d %0d\n",
|
||||
cap_ep, flush_x_w, flush_y_w, flush_z_w, flush_color32_w, cap_pu_qq, cap_pv_qq);
|
||||
if (flush_emit_w && (flush_psm_w==6'h00) && tex_fh!=0)
|
||||
$fwrite(tex_fh, "%0d %0d %0d %0d %08x %08x %08x %0d %0d %08x %0d %02x %08x %08x %08x\n",
|
||||
cap_ep, flush_x_w, flush_y_w, flush_z_w, flush_color32_w,
|
||||
tex_issue_addr_q, tex_issue_raw_q, tex_issue_u_q, tex_issue_v_q,
|
||||
dut.u_gs.u_tex.tex_rd_data, dut.u_gs.u_tex.sel_lo, dut.u_gs.u_tex.clut_rd_idx,
|
||||
dut.u_gs.u_tex.clut_rd_data, dut.u_gs.u_tex.near_color, dut.u_gs.s1_tex_color);
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst_n) begin
|
||||
#1;
|
||||
if (dut.u_gs.persp_outvalid && issue_fh!=0)
|
||||
$fwrite(issue_fh, "%0d %0d %0d %0d %0d %0d %0d\n",
|
||||
cap_ep,
|
||||
dut.u_gs.g_persp_emit.u_persp_uv.uq_pipe[2],
|
||||
dut.u_gs.g_persp_emit.u_persp_uv.vq_pipe[2],
|
||||
dut.u_gs.g_persp_emit.u_persp_uv.w_recip,
|
||||
dut.u_gs.persp_u, dut.u_gs.persp_v,
|
||||
dut.u_gs.g_persp_emit.u_persp_uv.out_valid);
|
||||
end
|
||||
end
|
||||
|
||||
// texture cache + behavioral texture LPDDR (RELOADED between fills for each epoch's rebind)
|
||||
logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid; logic [7:0] arlen; logic [2:0] arsize;
|
||||
logic arvalid, arready; logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready;
|
||||
logic fill_start, fill_done; logic [31:0] fill_beats, fill_bytes, tex_rd_errs, fill_crc_w;
|
||||
gs_texture_cache #(.LPDDR_TEX_BASE(LPDDR_TEX_BASE), .TEX_VRAM_BASE(TEX_VRAM_BASE),
|
||||
.TEX_BYTES(TEX_BYTES), .N_BEATS(N_BEATS)) u_cache (
|
||||
.axi_clk(clk), .axi_rst_n(rst_n), .fill_start(fill_start), .fill_done(fill_done),
|
||||
.fill_beats(fill_beats), .fill_bytes(fill_bytes), .rd_errs(tex_rd_errs), .fill_crc(fill_crc_w),
|
||||
.araddr(araddr), .arburst(arburst), .arid(arid), .arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready),
|
||||
.rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready),
|
||||
.sample_clk(clk), .tex_rd_en(gs_tex_rd_en_o), .tex_rd_addr(gs_tex_rd_addr_o),
|
||||
.tex_rd_data(tex_cache_data), .tex_ready(tex_cache_ready)
|
||||
);
|
||||
logic [31:0] lpddr_mem [0:(TEX_BYTES/4)-1]; // reloaded per epoch: tex0, tex1, tex2
|
||||
typedef enum logic [1:0] { S_IDLE, S_WAIT, S_DATA } sstate_t; sstate_t sst; logic [3:0] tdly; int tbeat;
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin sst<=S_IDLE; arready<=0; rvalid<=0; rlast<=0; rresp<=0; rdata<=0; tdly<=0; end
|
||||
else begin arready<=0; rvalid<=0; rlast<=0;
|
||||
case (sst)
|
||||
S_IDLE: if (arvalid) begin arready<=1; tbeat<=(araddr-LPDDR_TEX_BASE)>>5; tdly<=4'd2; sst<=S_WAIT; end
|
||||
S_WAIT: if (tdly==0) sst<=S_DATA; else tdly<=tdly-1'b1;
|
||||
S_DATA: if (rready) begin for (int w=0;w<8;w++) rdata[w*32 +: 32]<=lpddr_mem[tbeat*8+w];
|
||||
rresp<=2'b00; rvalid<=1; rlast<=1; sst<=S_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// render epoch (per scene) + PSMCT32 writer + precleared LPDDR FB
|
||||
logic fb_flush=0, render_inflight=0, feeder_ready_q=0; int eof_count;
|
||||
wire feeder_ready_rise = feeder_ready_tb && !feeder_ready_q;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin render_inflight<=0; feeder_ready_q<=0; fb_flush<=0; eof_count<=0; end
|
||||
else begin feeder_ready_q<=feeder_ready_tb; fb_flush<=1'b0;
|
||||
if (feeder_go_tb && feeder_ready_tb) render_inflight<=1'b1;
|
||||
if (render_inflight && feeder_ready_rise) begin fb_flush<=1'b1; render_inflight<=1'b0; eof_count<=eof_count+1; end
|
||||
end
|
||||
end
|
||||
logic wr_arm=0; logic [255:0] fbw_wdata; logic [31:0] fbw_wstrb, fbw_awaddr; logic fbw_awvalid, fbw_wvalid;
|
||||
logic [31:0] fbw_beats, fbw_ovf, fbw_bresp_err; logic fbw_idle, fbw_drained;
|
||||
gs_lpddr_axi_master #(.FIFO_DEPTH(64), .PIX_BYTES(4)) u_wr (
|
||||
.gs_clk(clk), .gs_rst_n(rst_n), .enable(1'b1),
|
||||
.arm(wr_arm), .canary(1'b0), .fb_base(32'h0), .ctrl_commit(fb_commit),
|
||||
.px_emit(flush_emit_w && (flush_psm_w==6'h00)), .px_addr(flush_addr_w), .px_pix32(flush_color32_w), .flush(fb_flush),
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n),
|
||||
.awaddr(fbw_awaddr), .awlen(), .awsize(), .awburst(), .awid(), .awvalid(fbw_awvalid), .awready(1'b1),
|
||||
.wdata(fbw_wdata), .wstrb(fbw_wstrb), .wlast(), .wvalid(fbw_wvalid), .wready(1'b1),
|
||||
.bvalid(1'b1), .bready(), .bresp(2'b00),
|
||||
.beats_written(fbw_beats), .bursts_issued(), .bresp_err_count(fbw_bresp_err),
|
||||
.fifo_overflow_count(fbw_ovf), .idle(fbw_idle), .frame_drained(fbw_drained)
|
||||
);
|
||||
logic [7:0] fb [0:FB_BYTES-1]; logic [31:0] fb_awlat;
|
||||
always_ff @(posedge emif_clk) begin
|
||||
if (fbw_awvalid) fb_awlat<=fbw_awaddr;
|
||||
if (fbw_wvalid) for (int i=0;i<32;i++) if (fbw_wstrb[i]) begin
|
||||
int aa; aa=fb_awlat+i; if (aa>=0 && aa<FB_BYTES) fb[aa]<=fbw_wdata[i*8 +: 8]; end
|
||||
end
|
||||
logic [31:0] ideal [0:FB_WORDS-1];
|
||||
always_ff @(posedge clk) if (rst_n && flush_emit_w && (flush_psm_w==6'h00)) ideal[flush_addr_w>>2]<=flush_color32_w;
|
||||
|
||||
// FRESH-DRAIN observer: count frame_drained low->high and high->low edges (emif domain).
|
||||
logic fd_q; int fd_rises, fd_falls;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin fd_q<=0; fd_rises<=0; fd_falls<=0; end
|
||||
else begin fd_q<=fbw_drained;
|
||||
if (fbw_drained && !fd_q) fd_rises<=fd_rises+1;
|
||||
if (!fbw_drained && fd_q) fd_falls<=fd_falls+1;
|
||||
end
|
||||
end
|
||||
|
||||
// scanout (384x381), host-gated: enable = scan_en (video_src) AND frame_drained
|
||||
logic scan_en=0; wire so_enable = scan_en & fbw_drained;
|
||||
logic [11:0] px, py; logic vsync, in_win; logic [7:0] so_r, so_g, so_b;
|
||||
logic so_underflow; logic [31:0] so_rd_errs; logic so_line_valid;
|
||||
logic [29:0] so_araddr; logic [1:0] so_arburst; logic [6:0] so_arid; logic [7:0] so_arlen;
|
||||
logic [2:0] so_arsize; logic so_arvalid, so_arready;
|
||||
logic [255:0] so_rdata; logic [1:0] so_rresp; logic so_rlast, so_rvalid, so_rready;
|
||||
gs_lpddr_scanout_lb #(.FB_BASE(30'd0), .STRIDE_BYTES(STRIDE), .ROW_BEATS(ROW_BEATS),
|
||||
.N_ROWS(H), .PSMCT32(1'b1)) u_scan (
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n), .enable(so_enable),
|
||||
.video_clk(video_clk), .frame_start(vsync), .pixel_x(px), .pixel_y(py), .in_window(in_win),
|
||||
.r(so_r), .g(so_g), .b(so_b), .line_valid(so_line_valid), .underflow(so_underflow), .rd_errs(so_rd_errs),
|
||||
.araddr(so_araddr), .arburst(so_arburst), .arid(so_arid), .arlen(so_arlen), .arsize(so_arsize),
|
||||
.arvalid(so_arvalid), .arready(so_arready), .rdata(so_rdata), .rresp(so_rresp),
|
||||
.rlast(so_rlast), .rvalid(so_rvalid), .rready(so_rready)
|
||||
);
|
||||
logic [7:0] rlfsr=8'h3C; always_ff @(posedge emif_clk) rlfsr<={rlfsr[6:0], rlfsr[7]^rlfsr[5]^rlfsr[4]^rlfsr[3]};
|
||||
typedef enum logic [1:0] { R_IDLE, R_WAIT, R_DATA } rst_t; rst_t rst_state;
|
||||
logic [3:0] rdly; logic [29:0] rd_addr_l; int read_beats;
|
||||
logic [2:0] vs_e; wire vs_edge_e = vs_e[1] && !vs_e[2]; int fb_reads, fb_reads_last;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin rst_state<=R_IDLE; so_arready<=0; so_rvalid<=0; so_rlast<=0; so_rresp<=0; so_rdata<=0; rdly<=0;
|
||||
read_beats<=0; vs_e<=0; fb_reads<=0; fb_reads_last<=0; end
|
||||
else begin so_arready<=0; so_rvalid<=0; so_rlast<=0; vs_e<={vs_e[1:0], vsync};
|
||||
if (vs_edge_e) begin fb_reads_last<=fb_reads; fb_reads<=0; end
|
||||
case (rst_state)
|
||||
R_IDLE: if (so_arvalid) begin so_arready<=1; rd_addr_l<=so_araddr; rdly<=rlfsr[2:0]; rst_state<=R_WAIT; end
|
||||
R_WAIT: if (rdly==0) rst_state<=R_DATA; else rdly<=rdly-1'b1;
|
||||
R_DATA: if (so_rready) begin
|
||||
for (int w=0;w<8;w++) begin int aa; aa=rd_addr_l+w*4;
|
||||
so_rdata[w*32 +: 32] <= (aa+3<FB_BYTES) ? {fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]} : 32'd0; end
|
||||
so_rresp<=2'b00; so_rvalid<=1; so_rlast<=1; read_beats<=read_beats+1;
|
||||
if (!vs_edge_e) fb_reads<=fb_reads+1; rst_state<=R_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
logic vid_run=0; logic [11:0] rawx, rawy;
|
||||
always_ff @(posedge video_clk) begin
|
||||
if (!vid_run) begin rawx<=0; rawy<=0; end
|
||||
else if (rawx==H_TOT-1) begin rawx<=0; rawy<=(rawy==V_TOT-1)?12'd0:rawy+1'b1; end
|
||||
else rawx<=rawx+1'b1;
|
||||
end
|
||||
wire active = (rawx>=H_BP)&&(rawx<H_BP+H_ACT)&&(rawy>=V_BP)&&(rawy<V_BP+V_ACT);
|
||||
assign px=active?(rawx-H_BP):12'd0; assign py=(rawy>=V_BP&&rawy<V_BP+V_ACT)?(rawy-V_BP):12'd0;
|
||||
assign in_win=active; assign vsync=vid_run&&(rawx==0)&&(rawy==0);
|
||||
logic [11:0] px_q, py_q; logic inwin_q, run_q;
|
||||
always_ff @(posedge video_clk) begin px_q<=px; py_q<=py; inwin_q<=in_win; run_q<=vid_run; end
|
||||
int checked; initial checked=0; logic scoring=0;
|
||||
always_ff @(posedge video_clk) if (scoring && run_q) begin
|
||||
logic [7:0] er,eg,eb; logic [31:0] w;
|
||||
if (inwin_q) begin int aa; aa=py_q*STRIDE+px_q*4; w={fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]}; er=w[7:0]; eg=w[15:8]; eb=w[23:16]; end
|
||||
else begin er=0; eg=0; eb=0; end
|
||||
checked++;
|
||||
if (so_r!==er||so_g!==eg||so_b!==eb) begin
|
||||
if (errors<12) $error("[scan] px(%0d,%0d) got(%02x,%02x,%02x) exp(%02x,%02x,%02x)", px_q,py_q, so_r,so_g,so_b, er,eg,eb); errors++; end
|
||||
end
|
||||
|
||||
// per-epoch idx/pal (for the oracle) + composed reference (owner epoch in [26:24], multi in [28]) + per-epoch refmaps
|
||||
logic [31:0] idx [0:2][0:(512*512/4)-1]; logic [31:0] pal [0:2][0:255];
|
||||
logic [31:0] refmap [0:FB_WORDS-1]; logic [31:0] refmap_ep [0:2][0:FB_WORDS-1];
|
||||
logic [63:0] stg_buf [0:STG_WORDS-1]; // one epoch's staging list, streamed through the write port
|
||||
// temps: iverilog can't $readmemh into a 2D-array slice, so load flat then copy into the [e] plane
|
||||
logic [31:0] t_idx [0:(512*512/4)-1]; logic [31:0] t_pal [0:255]; logic [31:0] t_rm [0:FB_WORDS-1];
|
||||
|
||||
// ---- stream one epoch's list into the feeder staging via the WRITE PORT (word0 first, full STG reset) ----
|
||||
task automatic stream_list(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/feeder_sh3_zs640%0d.mem", k);
|
||||
for (int i=0;i<STG_WORDS;i++) stg_buf[i]=64'd0;
|
||||
$readmemh(fn, stg_buf);
|
||||
@(negedge clk);
|
||||
for (int i=0;i<STG_WORDS;i++) begin
|
||||
stg_we<=1'b1; stg_waddr<=i[11:0]; stg_wdata<=stg_buf[i]; @(negedge clk);
|
||||
end
|
||||
stg_we<=1'b0; stg_waddr<=12'd0; stg_wdata<=64'd0; @(negedge clk);
|
||||
// sanity: header word0 (ntris) landed correctly in the DUT staging
|
||||
if (dut.g_feeder.feeder_stg[0][31:0] !== stg_buf[0][31:0]) begin
|
||||
$error("[sched] epoch %0d: staged word0=%08x exp %08x (write-port mis-slot)", k, dut.g_feeder.feeder_stg[0][31:0], stg_buf[0][31:0]); errors++; end
|
||||
$display("[sched] epoch %0d: streamed %0d words via write port; word0(ntris)=%0d", k, STG_WORDS, dut.g_feeder.feeder_stg[0][31:0]);
|
||||
endtask
|
||||
|
||||
// ---- one-scene runner: GO, wait render+drain, REQUIRE fresh frame_drained (high->low->high, or low->high on first) ----
|
||||
task automatic run_scene(input int k, input int exp_records);
|
||||
int r0, f0, d=0; logic fd_before;
|
||||
r0=fd_rises; f0=fd_falls; fd_before=fbw_drained;
|
||||
@(negedge clk); feeder_go_tb=1'b1; @(negedge clk); feeder_go_tb=1'b0;
|
||||
d=0; while (feeder_ready_tb && d<4000) begin @(posedge clk); d++; end
|
||||
if (feeder_ready_tb!==1'b0) begin $error("[sched] epoch %0d: feeder_ready did not drop after GO", k); errors++; end
|
||||
if (fd_before) begin
|
||||
d=0; while (fd_falls==f0 && d<800000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_falls==f0) begin $error("[sched] epoch %0d: frame_drained never fell from stale high — STALE drain", k); errors++; end
|
||||
end
|
||||
d=0; while (fd_rises<=r0 && d<800000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_rises<=r0) begin $error("[sched] epoch %0d: frame_drained never rose — scene did not drain", k); errors++; end
|
||||
repeat(100) @(posedge clk);
|
||||
if (feeder_records_w!==exp_records) begin $error("[sched] epoch %0d: records_emitted=%0d exp %0d", k, feeder_records_w, exp_records); errors++; end
|
||||
if (fbw_ovf!==0 || fbw_bresp_err!==0) begin $error("[sched] epoch %0d: writer ovf=%0d bresp=%0d", k, fbw_ovf, fbw_bresp_err); errors++; end
|
||||
$display("[sched] epoch %0d: fresh drain (falls %0d->%0d, rises %0d->%0d), records=%0d, ovf=0", k, f0, fd_falls, r0, fd_rises, feeder_records_w);
|
||||
endtask
|
||||
|
||||
task automatic fill_cache(input int k, input logic [31:0] exp_crc);
|
||||
int d=0;
|
||||
@(posedge clk) fill_start<=1'b1; @(posedge clk) fill_start<=1'b0;
|
||||
d=0; while (fill_done && d<1000) begin @(posedge clk); d++; end // wait it drops (busy)
|
||||
d=0; while (!fill_done && d<400000) begin @(posedge clk); d++; end // then rises (done)
|
||||
if (!fill_done) begin $error("[sched] fill %0d: fill_done never rose (rearm failed)", k); errors++; end
|
||||
if (fill_crc_w!==exp_crc) begin $error("[sched] fill %0d: crc=%08x exp %08x", k, fill_crc_w, exp_crc); errors++; end
|
||||
if (fill_beats!==N_BEATS) begin $error("[sched] fill %0d: beats=%0d exp %0d", k, fill_beats, N_BEATS); errors++; end
|
||||
if (fill_bytes!==TEX_BYTES) begin $error("[sched] fill %0d: bytes=%0d exp %0d", k, fill_bytes, TEX_BYTES); errors++; end
|
||||
if (tex_rd_errs!==0) begin $error("[sched] fill %0d: rd_errs=%0d", k, tex_rd_errs); errors++; end
|
||||
$display("[sched] cache fill %0d: fresh done, crc=0x%08x (exp %08x), beats=%0d bytes=%0d rd_errs=0", k, fill_crc_w, exp_crc, fill_beats, fill_bytes);
|
||||
endtask
|
||||
|
||||
// texel lookup in epoch e's palette (module-level arrays, no per-call copy)
|
||||
function automatic logic [23:0] cell_e(input int e, input integer u, input integer v);
|
||||
integer lin; logic [31:0] w; logic [7:0] ix;
|
||||
lin=v*TW+u; w=idx[e][lin/4]; ix=w[(8*(lin%4)) +: 8]; cell_e=pal[e][ix][23:0];
|
||||
endfunction
|
||||
|
||||
// run ONE epoch fully (rebind tex -> fresh fill+CRC -> stream list -> GO -> fresh ordered drain)
|
||||
task automatic run_epoch(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/sh3_zs640%0d_tex_lpddr.mem", k);
|
||||
$readmemh(fn, lpddr_mem);
|
||||
fill_cache(k, EP_CRC[k]);
|
||||
stream_list(k);
|
||||
run_scene(k, EP_REC[k]);
|
||||
endtask
|
||||
|
||||
string only_m; string fdump; int only_k; // +ONLY=ALL | +ONLY=<k> ; +FBDUMP=<file>
|
||||
initial begin
|
||||
errors=0; feeder_go_tb=1'b0; fill_start=1'b0; wr_arm=0; scan_en=0; stg_we=0; stg_waddr=0; stg_wdata=0; cap_fh=0; tex_fh=0; issue_fh=0; cap_ep=0;
|
||||
tex_issue_addr_q=0; tex_issue_raw_q=0; tex_issue_u_q=0; tex_issue_v_q=0;
|
||||
if (!$value$plusargs("ONLY=%s", only_m)) only_m="ALL";
|
||||
only_k = (only_m=="ALL") ? -1 : only_m.atoi();
|
||||
for (int i=0;i<FB_WORDS;i++) ideal[i]=32'd0;
|
||||
for (int i=0;i<FB_BYTES;i++) fb[i]=8'h00; // PRECLEAR EXACTLY ONCE
|
||||
idx[0][0]='x;
|
||||
for (int e=0;e<NEP;e++) begin
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_zs640%0d_idx.mem", e), t_idx);
|
||||
for (int i=0;i<(512*512/4);i++) idx[e][i]=t_idx[i];
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_zs640%0d_pal.mem", e), t_pal);
|
||||
for (int i=0;i<256;i++) pal[e][i]=t_pal[i];
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_zs640%0d_refmap.mem", e), t_rm);
|
||||
for (int i=0;i<FB_WORDS;i++) refmap_ep[e][i]=t_rm[i];
|
||||
end
|
||||
$readmemh("../../data/top_psmct32_raster_demo/sh3_zs640_refmap.mem", refmap);
|
||||
$display("[sched] ONLY=%s (only_k=%0d), N_EPOCHS=%0d", only_m, only_k, NEP);
|
||||
if (idx[0][0]===32'bx) begin $display("[tb_top_psmct32_sh3_zs640_cap] SKIP — sh3_zs640*.mem absent (run gs_make_sh3_scheduler_fixture.py --authz --fb640 --tag zs640 --emit)"); $finish; end
|
||||
|
||||
rst_n=1'b0; core_go=1'b0; repeat(6) @(posedge clk); rst_n=1'b1; repeat(8) @(posedge clk);
|
||||
|
||||
// boot the bootlet (uploads ALL N relocated CLUTs to their distinct CBPs)
|
||||
@(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);
|
||||
wait (feeder_ready_tb==1'b1); repeat(50) @(posedge clk);
|
||||
wr_arm<=1'b1; repeat(40) @(posedge clk);
|
||||
|
||||
// ===== scheduler: iterate the epoch descriptors in dump order (or a single epoch for the isolation check) =====
|
||||
cap_fh = $fopen("zs640_frags.txt","w"); // Ch357 fragment capture for external persp_z5 verification
|
||||
tex_fh = $fopen("zs640_textrace.txt","w"); // sim-only texture lookup trace for perspective-color diagnosis
|
||||
issue_fh = $fopen("zs640_issue.txt","w"); // sim-only perspective divide output trace
|
||||
if (only_k<0) begin
|
||||
for (int k=0;k<NEP;k++) begin
|
||||
cap_ep = k;
|
||||
run_epoch(k);
|
||||
if (k<NEP-1 && scan_en!==1'b0) begin $error("[sched] scanout enabled before the last epoch"); errors++; end
|
||||
end
|
||||
end else begin
|
||||
run_epoch(only_k);
|
||||
end
|
||||
|
||||
// enable scanout only after the final fresh drain
|
||||
scan_en<=1'b1; repeat(20) @(posedge clk);
|
||||
if (only_k<0) begin
|
||||
if (fd_rises<NEP) begin $error("[sched] expected %0d ordered drains: rises=%0d", NEP, fd_rises); errors++; end
|
||||
if (fd_falls<NEP-1)begin $error("[sched] epochs after the first never cleared stale drains (falls=%0d)", fd_falls); errors++; end
|
||||
end else if (fd_rises<1) begin $error("[sched] isolated epoch did not drain: rises=%0d", fd_rises); errors++; end
|
||||
|
||||
// ===== scanout: score final FB vs the composed reference (per-pixel OWNER epoch), overlap separately =====
|
||||
vid_run=1;
|
||||
begin int d=0; while(!vsync && d<300000) begin @(posedge video_clk); d++; end end
|
||||
@(posedge video_clk); while(!vsync) @(posedge video_clk);
|
||||
begin scoring=1; @(posedge video_clk); while(!vsync) @(posedge video_clk); scoring=0; repeat(60) @(posedge emif_clk);
|
||||
if (fb_reads_last!==BEATS_PER_FRAME) begin $error("[sched] scanout beats/frame=%0d exp %0d", fb_reads_last, BEATS_PER_FRAME); errors++; end
|
||||
end
|
||||
if (so_underflow!==0) begin $error("[sched] scanout underflow"); errors++; end
|
||||
if (so_rd_errs !==0) begin $error("[sched] scanout rd_errs=%0d", so_rd_errs); errors++; end
|
||||
if (checked < H_ACT*V_ACT) begin $error("[sched] only %0d px checked (exp >= %0d)", checked, H_ACT*V_ACT); errors++; end
|
||||
|
||||
// oracle (multi-texture correctness): a covered pixel must equal ONE of the COVERING epochs' texels. Each epoch
|
||||
// stores its OWN (tu,tv) per pixel in refmap_ep[k]; we accept if the RTL colour matches any covering epoch's texel
|
||||
// in the <=1-texel neighbourhood (owner-first, then neighbours — handles coverage-edge owner ambiguity between
|
||||
// adjacent-order epochs, exactly the tolerance Ch355 used). multi-epoch pixels (>=2 covering) scored separately as
|
||||
// the accumulation composite proof. clut_bad = an RTL-written pixel (ideal!=0) whose colour is in NO epoch palette
|
||||
// (palettes are pairwise-distinct, so this still proves the pixel came from a real texture+CLUT, not garbage).
|
||||
begin
|
||||
int m_tot,m_ok,o_tot,o_ok,cb,D,ncov; bit mt; m_tot=0;m_ok=0;o_tot=0;o_ok=0;cb=0;
|
||||
for (int y=0;y<H;y++) for (int x=0;x<W;x++) begin
|
||||
int o; logic [23:0] fbc; bit cov; o=y*W+x;
|
||||
cov = (only_k<0) ? refmap[o][31] : refmap_ep[only_k][o][31];
|
||||
if (cov) begin
|
||||
fbc=ideal[o][23:0]; m_tot++; D=9; ncov=0;
|
||||
for (int e=0;e<NEP;e++) begin
|
||||
if (!(only_k>=0 && e!=only_k) && refmap_ep[e][o][31]) begin
|
||||
int tu,tv; tu=(refmap_ep[e][o]>>9)&9'h1FF; tv=refmap_ep[e][o]&9'h1FF; ncov++;
|
||||
for (int rad=0;rad<=1;rad++) for (int du=-rad;du<=rad;du++) for (int dv=-rad;dv<=rad;dv++) begin
|
||||
int ch; ch=(du<0?-du:du); if((dv<0?-dv:dv)>ch) ch=(dv<0?-dv:dv);
|
||||
if (ch==rad && (tu+du)>=0 && (tu+du)<TW && (tv+dv)>=0 && (tv+dv)<TH) begin
|
||||
mt=(fbc===cell_e(e,tu+du,tv+dv)); if (mt && rad<D) D=rad;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if (D<=1) m_ok++;
|
||||
if (only_k<0 && refmap[o][28]) begin o_tot++; if (D<=1) o_ok++; end // multi-epoch (>=2 covering)
|
||||
if (ideal[o]!==32'd0) begin bit f; f=1'b0;
|
||||
for (int e=0;e<NEP;e++) for (int i=0;i<256;i++) if (pal[e][i][23:0]===fbc) f=1'b1;
|
||||
if(!f) cb++; end
|
||||
end
|
||||
end
|
||||
$display("[sched][oracle] ONLY=%s <=1texel ALL=%0d/%0d (%.1f%%) MULTI(>=2)=%0d/%0d (%.1f%%) clut_bad=%0d",
|
||||
only_m, m_ok,m_tot,(m_tot>0)?100.0*m_ok/m_tot:0.0, o_ok,o_tot,(o_tot>0)?100.0*o_ok/o_tot:0.0, cb);
|
||||
if (cb!==0) begin $error("[sched][oracle] ONLY=%s: %0d covered px in NO epoch palette", only_m, cb); errors++; end
|
||||
if (only_k<0) begin
|
||||
if (o_tot<500) begin $error("[sched][oracle] only %0d multi-epoch px — accumulation not exercised", o_tot); errors++; end
|
||||
// MULTI <=1texel is TEXTURE FIDELITY (RTL 11-bit reciprocal LUT precision), NOT accumulation correctness.
|
||||
// It tracks the per-epoch isolated fidelity (ONLY=k gives 93.0/93.3/95.6%); the multi-epoch subset sits at
|
||||
// the same reciprocal floor (~92%). The ACCUMULATION proof is separate and EXACT: compose_sched.py shows
|
||||
// the joint ALL render == the composited ONLY=k isolation dumps 100% BIT-FOR-BIT. So gate this at the
|
||||
// documented reciprocal floor, not an accumulation-implying bar.
|
||||
if (o_tot>0 && (100.0*o_ok/o_tot)<90.0) begin $error("[sched][oracle] MULTI <=1texel %.1f%% < 90%% reciprocal floor", 100.0*o_ok/o_tot); errors++; end
|
||||
end
|
||||
end
|
||||
// FB dump for the composition==isolated check (per-epoch dumps composed externally -> compare vs ALL dump)
|
||||
if ($value$plusargs("FBDUMP=%s", fdump)) begin
|
||||
int fh; fh=$fopen(fdump,"w");
|
||||
for (int y=0;y<H;y++) for (int x=0;x<W;x++) begin int aa; aa=y*STRIDE+x*4; $fwrite(fh,"%08x\n",{fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]}); end
|
||||
$fclose(fh); $display("[sched] dumped RTL FB (%0dx%0d) -> %s", W, H, fdump);
|
||||
end
|
||||
|
||||
$display("[tb_top_psmct32_sh3_zs640_cap] checked=%0d beats/frame=%0d (exp %0d) fresh_drains(rise/fall)=%0d/%0d records=%0d underflow=%0b ovf=%0d errors=%0d",
|
||||
checked, fb_reads_last, BEATS_PER_FRAME, fd_rises, fd_falls, feeder_records_w, so_underflow, fbw_ovf, errors);
|
||||
if (cap_fh!=0) $fclose(cap_fh);
|
||||
if (tex_fh!=0) $fclose(tex_fh);
|
||||
if (issue_fh!=0) $fclose(issue_fh);
|
||||
if (errors==0) $display("[tb_top_psmct32_sh3_zs640_cap] PASS"); else $display("[tb_top_psmct32_sh3_zs640_cap] FAIL");
|
||||
$finish;
|
||||
end
|
||||
initial begin #320000000; $error("[tb_top_psmct32_sh3_zs640_cap] TIMEOUT"); $finish; end
|
||||
endmodule : tb_top_psmct32_sh3_zs640_cap
|
||||
@@ -0,0 +1,474 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_sh3_zs640_shared_cap (Ch360 — four-epoch shared-texture capture at native 640x480)
|
||||
//
|
||||
// Four authentic SH3 draw groups share one resident TEX0/CLUT and accumulate into one LPDDR framebuffer via the
|
||||
// C12 epoch descriptor table. Each group keeps the proven 204-triangle feeder-list capacity.
|
||||
// Also captures the fragment stream {epoch,x,y,persp_z,color} -> zs640c12_frags.txt. Proves the Ch356 integration acceptance:
|
||||
// * preclear EXACTLY once.
|
||||
// * N FRESH cache fills, each fill_done low->high, expected beats/bytes, 0 read errors, epoch CRC (EPk_CRC).
|
||||
// * N lists STREAMED through the feeder staging WRITE PORT (feeder_stg_we/waddr/wdata) — NOT the $readmemh backdoor
|
||||
// (closes the Ch355 sim gap where the write-port sequencing was untested). Each streams the full STG_WORDS so
|
||||
// staging is fully reset per epoch (no stale words), and word0 (the ntris header) is exercised first.
|
||||
// * N FRESH ORDERED drains: epoch 0 low->high (first render); epochs 1..N-1 high->low->high (stale cleared).
|
||||
// * scanout stays disabled until the LAST fresh drain; exact BEATS_PER_FRAME (=ROW_BEATS*H).
|
||||
// * final FB scored vs the independent composed reference using the PER-PIXEL OWNER epoch (exact palette per owner);
|
||||
// multi-epoch (>=2) overlap scored separately as the accumulation proof.
|
||||
// * +ONLY=<k> renders a SINGLE epoch (+FBDUMP dumps its FB) for the composition==isolated bit-for-bit check.
|
||||
// LOCAL/gitignored fixtures; skip-guard if absent.
|
||||
`ifndef SH3_SHARED_PARAMS
|
||||
`error "Define SH3_SHARED_PARAMS as a quoted generated params header"
|
||||
`endif
|
||||
`ifndef SH3_SHARED_TAG
|
||||
`error "Define SH3_SHARED_TAG as a quoted fixture tag"
|
||||
`endif
|
||||
`ifndef SH3_SHARED_EPOCH_TABLE
|
||||
`error "Define SH3_SHARED_EPOCH_TABLE as a quoted generated descriptor table"
|
||||
`endif
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_sh3_zs640_shared_cap;
|
||||
`include `SH3_SHARED_PARAMS
|
||||
localparam string FIXTURE_TAG = `SH3_SHARED_TAG;
|
||||
localparam int MAX_EPOCHS = 16;
|
||||
localparam int W = FBPXW, H = FBH;
|
||||
localparam int STRIDE = W*4; // 1536
|
||||
localparam int ROW_BEATS = STRIDE/32; // 48
|
||||
localparam int FB_BYTES = STRIDE*H; // 585216
|
||||
localparam int FB_WORDS = W*H;
|
||||
localparam int BEATS_PER_FRAME = ROW_BEATS*H; // 18288
|
||||
localparam int H_ACT=W, V_ACT=H, H_BP=32, H_FP=8, V_BP=16, V_FP=8;
|
||||
localparam int H_TOT=H_BP+H_ACT+H_FP, V_TOT=V_BP+V_ACT+V_FP;
|
||||
localparam int NEP = N_EPOCHS; // 4
|
||||
|
||||
// per-epoch expected CRC / records (from the descriptor params)
|
||||
// Ch360 — four epochs share one texture: EPk_REUSE=1 means the epoch runs on the resident cache without a fill.
|
||||
logic [31:0] EP_CRC [0:MAX_EPOCHS-1]; int EP_REC [0:MAX_EPOCHS-1]; bit EP_REUSE [0:MAX_EPOCHS-1]; int fills;
|
||||
initial begin
|
||||
`include `SH3_SHARED_EPOCH_TABLE
|
||||
fills=0;
|
||||
end
|
||||
|
||||
logic clk=0; always #5 clk=~clk;
|
||||
logic emif_clk=0; always #2 emif_clk=~emif_clk;
|
||||
logic video_clk=0; always #7 video_clk=~video_clk;
|
||||
logic rst_n; logic fb_commit=0; always #13 fb_commit=~fb_commit;
|
||||
int errors; initial errors=0;
|
||||
|
||||
// ===== bram-top (FB_LPDDR_ONLY, AUTOSTART off, STG 2048), H/V_ACTIVE = 384x381 =====
|
||||
logic core_go; logic [7:0] r,g,b; logic hsync,vsync_o,de;
|
||||
logic core_halt,dma_done_seen,frame_seen,raster_overflow,frame_toggle,dma_done_toggle;
|
||||
logic feeder_go_tb, feeder_ready_tb; logic [15:0] feeder_records_w; logic [31:0] feeder_waits_w;
|
||||
logic gs_tex_rd_en_o; logic [31:0] gs_tex_rd_addr_o; logic [31:0] tex_cache_data; logic tex_cache_ready;
|
||||
logic [31:0] tex_cache_hits, tex_bram_hits;
|
||||
logic flush_emit_w; logic [31:0] flush_addr_w; logic [31:0] flush_color32_w; logic [5:0] flush_psm_w;
|
||||
logic [11:0] flush_x_w, flush_y_w; logic [31:0] flush_z_w; // Ch357 — fragment coords + persp_z5
|
||||
int cap_ep; int cap_fh; int tex_fh; int issue_fh; // fragment capture (verify persp_z5 vs the clamp16 oracle)
|
||||
logic [10:0] cap_pu_q, cap_pv_q, cap_pu_qq, cap_pv_qq; // persp_u/v delayed from texture issue to flush/color
|
||||
logic [31:0] tex_issue_addr_q, tex_issue_raw_q; // debug: texture request that returns on the next cycle
|
||||
logic [10:0] tex_issue_u_q, tex_issue_v_q;
|
||||
// feeder staging WRITE PORT (streamed per epoch)
|
||||
logic stg_we; logic [11:0] stg_waddr; logic [63:0] stg_wdata;
|
||||
|
||||
top_psmct32_raster_demo_bram #(
|
||||
.H_ACTIVE(FBPXW), .V_ACTIVE(FBH),
|
||||
.VRAM_BYTES(VRAM_BYTES_P), .VRAM_ENABLE_READ2(1'b0), .PSMCT32_SWIZZLE(1'b0),
|
||||
.COMBINED_TAZ(1'b0), .TILE_LOCAL(1'b0), .TILE_COLS(1), .TILE_ROWS(1),
|
||||
.TILE_MULTIPRIM(1'b0), .TILE_PRIM_COUNT(1), .TILE_FIFO_DEPTH(8),
|
||||
.BIN_BUFFER_ENABLE(1'b0), .HEARTBEAT_SPLICE_ENABLE(1'b0),
|
||||
.FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(STG_WORDS), .FEEDER_AUTOSTART(1'b0),
|
||||
.PERSPECTIVE_CORRECT(1'b1), .PERSP_RECIP_IDX_BITS(11),
|
||||
.GRAD_SEQ_DIVIDER(1'b1), .GRAD_DIV_CYCLES(4),
|
||||
.GS_LPDDR_TEX(1'b1), .TEX_VRAM_BASE(TEX_VRAM_BASE), .TEX_CACHE_BYTES(TEX_BYTES),
|
||||
.CLUT_CSM1_ENABLE(1'b1), .RAM_SIZE_BYTES(32 * 1024), .FB_LPDDR_ONLY(1'b1)
|
||||
) dut (
|
||||
.clk(clk), .rst_n(rst_n), .core_go(core_go),
|
||||
.r(r), .g(g), .b(b), .hsync(hsync), .vsync(vsync_o), .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),
|
||||
.feeder_stg_we_i(stg_we), .feeder_stg_waddr_i(stg_waddr), .feeder_stg_wdata_i(stg_wdata),
|
||||
.feeder_go_i(feeder_go_tb), .feeder_ready_o(feeder_ready_tb),
|
||||
.feeder_records_o(feeder_records_w), .feeder_waits_o(feeder_waits_w),
|
||||
.flush_emit_o(flush_emit_w), .flush_addr_o(flush_addr_w),
|
||||
.flush_pix16_o(), .flush_color32_o(flush_color32_w), .flush_psm_o(flush_psm_w),
|
||||
.flush_x_o(flush_x_w), .flush_y_o(flush_y_w), .flush_z_o(flush_z_w),
|
||||
.gs_tex_rd_en_o(gs_tex_rd_en_o), .gs_tex_rd_addr_o(gs_tex_rd_addr_o),
|
||||
.tex_cache_data_i(tex_cache_data), .tex_cache_ready_i(tex_cache_ready),
|
||||
.tex_cache_hits_o(tex_cache_hits), .tex_bram_hits_o(tex_bram_hits)
|
||||
);
|
||||
|
||||
// Ch357 — capture every emitted fragment {epoch, x, y, persp_z, color}. An external Python check replays these through
|
||||
// the clamp16 persistent-Z model and compares owner/reject to the oracle -> validates persp_z5 end-to-end (the new RTL).
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst_n) begin
|
||||
if (dut.u_gs.persp_outvalid) begin
|
||||
cap_pu_qq <= cap_pu_q;
|
||||
cap_pv_qq <= cap_pv_q;
|
||||
cap_pu_q <= dut.u_gs.persp_u;
|
||||
cap_pv_q <= dut.u_gs.persp_v;
|
||||
end
|
||||
if (gs_tex_rd_en_o) begin
|
||||
tex_issue_addr_q <= gs_tex_rd_addr_o;
|
||||
tex_issue_raw_q <= dut.u_gs.u_tex.addr;
|
||||
tex_issue_u_q <= dut.u_gs.u_tex.u_eff;
|
||||
tex_issue_v_q <= dut.u_gs.u_tex.v_eff;
|
||||
end
|
||||
if (flush_emit_w && (flush_psm_w==6'h00) && cap_fh!=0)
|
||||
$fwrite(cap_fh, "%0d %0d %0d %0d %08x %0d %0d\n",
|
||||
cap_ep, flush_x_w, flush_y_w, flush_z_w, flush_color32_w, cap_pu_qq, cap_pv_qq);
|
||||
if (flush_emit_w && (flush_psm_w==6'h00) && tex_fh!=0)
|
||||
$fwrite(tex_fh, "%0d %0d %0d %0d %08x %08x %08x %0d %0d %08x %0d %02x %08x %08x %08x\n",
|
||||
cap_ep, flush_x_w, flush_y_w, flush_z_w, flush_color32_w,
|
||||
tex_issue_addr_q, tex_issue_raw_q, tex_issue_u_q, tex_issue_v_q,
|
||||
dut.u_gs.u_tex.tex_rd_data, dut.u_gs.u_tex.sel_lo, dut.u_gs.u_tex.clut_rd_idx,
|
||||
dut.u_gs.u_tex.clut_rd_data, dut.u_gs.u_tex.near_color, dut.u_gs.s1_tex_color);
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst_n) begin
|
||||
#1;
|
||||
if (dut.u_gs.persp_outvalid && issue_fh!=0)
|
||||
$fwrite(issue_fh, "%0d %0d %0d %0d %0d %0d %0d\n",
|
||||
cap_ep,
|
||||
dut.u_gs.g_persp_emit.u_persp_uv.uq_pipe[2],
|
||||
dut.u_gs.g_persp_emit.u_persp_uv.vq_pipe[2],
|
||||
dut.u_gs.g_persp_emit.u_persp_uv.w_recip,
|
||||
dut.u_gs.persp_u, dut.u_gs.persp_v,
|
||||
dut.u_gs.g_persp_emit.u_persp_uv.out_valid);
|
||||
end
|
||||
end
|
||||
|
||||
// texture cache + behavioral texture LPDDR (RELOADED between fills for each epoch's rebind)
|
||||
logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid; logic [7:0] arlen; logic [2:0] arsize;
|
||||
logic arvalid, arready; logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready;
|
||||
logic fill_start, fill_done; logic [31:0] fill_beats, fill_bytes, tex_rd_errs, fill_crc_w;
|
||||
gs_texture_cache #(.LPDDR_TEX_BASE(LPDDR_TEX_BASE), .TEX_VRAM_BASE(TEX_VRAM_BASE),
|
||||
.TEX_BYTES(TEX_BYTES), .N_BEATS(N_BEATS)) u_cache (
|
||||
.axi_clk(clk), .axi_rst_n(rst_n), .fill_start(fill_start), .fill_done(fill_done),
|
||||
.fill_beats(fill_beats), .fill_bytes(fill_bytes), .rd_errs(tex_rd_errs), .fill_crc(fill_crc_w),
|
||||
.araddr(araddr), .arburst(arburst), .arid(arid), .arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready),
|
||||
.rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready),
|
||||
.sample_clk(clk), .tex_rd_en(gs_tex_rd_en_o), .tex_rd_addr(gs_tex_rd_addr_o),
|
||||
.tex_rd_data(tex_cache_data), .tex_ready(tex_cache_ready)
|
||||
);
|
||||
logic [31:0] lpddr_mem [0:(TEX_BYTES/4)-1]; // reloaded per epoch: tex0, tex1, tex2
|
||||
typedef enum logic [1:0] { S_IDLE, S_WAIT, S_DATA } sstate_t; sstate_t sst; logic [3:0] tdly; int tbeat;
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin sst<=S_IDLE; arready<=0; rvalid<=0; rlast<=0; rresp<=0; rdata<=0; tdly<=0; end
|
||||
else begin arready<=0; rvalid<=0; rlast<=0;
|
||||
case (sst)
|
||||
S_IDLE: if (arvalid) begin arready<=1; tbeat<=(araddr-LPDDR_TEX_BASE)>>5; tdly<=4'd2; sst<=S_WAIT; end
|
||||
S_WAIT: if (tdly==0) sst<=S_DATA; else tdly<=tdly-1'b1;
|
||||
S_DATA: if (rready) begin for (int w=0;w<8;w++) rdata[w*32 +: 32]<=lpddr_mem[tbeat*8+w];
|
||||
rresp<=2'b00; rvalid<=1; rlast<=1; sst<=S_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// render epoch (per scene) + PSMCT32 writer + precleared LPDDR FB
|
||||
logic fb_flush=0, render_inflight=0, feeder_ready_q=0; int eof_count;
|
||||
wire feeder_ready_rise = feeder_ready_tb && !feeder_ready_q;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin render_inflight<=0; feeder_ready_q<=0; fb_flush<=0; eof_count<=0; end
|
||||
else begin feeder_ready_q<=feeder_ready_tb; fb_flush<=1'b0;
|
||||
if (feeder_go_tb && feeder_ready_tb) render_inflight<=1'b1;
|
||||
if (render_inflight && feeder_ready_rise) begin fb_flush<=1'b1; render_inflight<=1'b0; eof_count<=eof_count+1; end
|
||||
end
|
||||
end
|
||||
logic wr_arm=0; logic [255:0] fbw_wdata; logic [31:0] fbw_wstrb, fbw_awaddr; logic fbw_awvalid, fbw_wvalid;
|
||||
logic [31:0] fbw_beats, fbw_ovf, fbw_bresp_err; logic fbw_idle, fbw_drained;
|
||||
gs_lpddr_axi_master #(.FIFO_DEPTH(64), .PIX_BYTES(4)) u_wr (
|
||||
.gs_clk(clk), .gs_rst_n(rst_n), .enable(1'b1),
|
||||
.arm(wr_arm), .canary(1'b0), .fb_base(32'h0), .ctrl_commit(fb_commit),
|
||||
.px_emit(flush_emit_w && (flush_psm_w==6'h00)), .px_addr(flush_addr_w), .px_pix32(flush_color32_w), .flush(fb_flush),
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n),
|
||||
.awaddr(fbw_awaddr), .awlen(), .awsize(), .awburst(), .awid(), .awvalid(fbw_awvalid), .awready(1'b1),
|
||||
.wdata(fbw_wdata), .wstrb(fbw_wstrb), .wlast(), .wvalid(fbw_wvalid), .wready(1'b1),
|
||||
.bvalid(1'b1), .bready(), .bresp(2'b00),
|
||||
.beats_written(fbw_beats), .bursts_issued(), .bresp_err_count(fbw_bresp_err),
|
||||
.fifo_overflow_count(fbw_ovf), .idle(fbw_idle), .frame_drained(fbw_drained)
|
||||
);
|
||||
logic [7:0] fb [0:FB_BYTES-1]; logic [31:0] fb_awlat;
|
||||
always_ff @(posedge emif_clk) begin
|
||||
if (fbw_awvalid) fb_awlat<=fbw_awaddr;
|
||||
if (fbw_wvalid) for (int i=0;i<32;i++) if (fbw_wstrb[i]) begin
|
||||
int aa; aa=fb_awlat+i; if (aa>=0 && aa<FB_BYTES) fb[aa]<=fbw_wdata[i*8 +: 8]; end
|
||||
end
|
||||
logic [31:0] ideal [0:FB_WORDS-1];
|
||||
always_ff @(posedge clk) if (rst_n && flush_emit_w && (flush_psm_w==6'h00)) ideal[flush_addr_w>>2]<=flush_color32_w;
|
||||
|
||||
// FRESH-DRAIN observer: count frame_drained low->high and high->low edges (emif domain).
|
||||
logic fd_q; int fd_rises, fd_falls;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin fd_q<=0; fd_rises<=0; fd_falls<=0; end
|
||||
else begin fd_q<=fbw_drained;
|
||||
if (fbw_drained && !fd_q) fd_rises<=fd_rises+1;
|
||||
if (!fbw_drained && fd_q) fd_falls<=fd_falls+1;
|
||||
end
|
||||
end
|
||||
|
||||
// scanout (384x381), host-gated: enable = scan_en (video_src) AND frame_drained
|
||||
logic scan_en=0; wire so_enable = scan_en & fbw_drained;
|
||||
logic [11:0] px, py; logic vsync, in_win; logic [7:0] so_r, so_g, so_b;
|
||||
logic so_underflow; logic [31:0] so_rd_errs; logic so_line_valid;
|
||||
logic [29:0] so_araddr; logic [1:0] so_arburst; logic [6:0] so_arid; logic [7:0] so_arlen;
|
||||
logic [2:0] so_arsize; logic so_arvalid, so_arready;
|
||||
logic [255:0] so_rdata; logic [1:0] so_rresp; logic so_rlast, so_rvalid, so_rready;
|
||||
gs_lpddr_scanout_lb #(.FB_BASE(30'd0), .STRIDE_BYTES(STRIDE), .ROW_BEATS(ROW_BEATS),
|
||||
.N_ROWS(H), .PSMCT32(1'b1)) u_scan (
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n), .enable(so_enable),
|
||||
.video_clk(video_clk), .frame_start(vsync), .pixel_x(px), .pixel_y(py), .in_window(in_win),
|
||||
.r(so_r), .g(so_g), .b(so_b), .line_valid(so_line_valid), .underflow(so_underflow), .rd_errs(so_rd_errs),
|
||||
.araddr(so_araddr), .arburst(so_arburst), .arid(so_arid), .arlen(so_arlen), .arsize(so_arsize),
|
||||
.arvalid(so_arvalid), .arready(so_arready), .rdata(so_rdata), .rresp(so_rresp),
|
||||
.rlast(so_rlast), .rvalid(so_rvalid), .rready(so_rready)
|
||||
);
|
||||
logic [7:0] rlfsr=8'h3C; always_ff @(posedge emif_clk) rlfsr<={rlfsr[6:0], rlfsr[7]^rlfsr[5]^rlfsr[4]^rlfsr[3]};
|
||||
typedef enum logic [1:0] { R_IDLE, R_WAIT, R_DATA } rst_t; rst_t rst_state;
|
||||
logic [3:0] rdly; logic [29:0] rd_addr_l; int read_beats;
|
||||
logic [2:0] vs_e; wire vs_edge_e = vs_e[1] && !vs_e[2]; int fb_reads, fb_reads_last;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin rst_state<=R_IDLE; so_arready<=0; so_rvalid<=0; so_rlast<=0; so_rresp<=0; so_rdata<=0; rdly<=0;
|
||||
read_beats<=0; vs_e<=0; fb_reads<=0; fb_reads_last<=0; end
|
||||
else begin so_arready<=0; so_rvalid<=0; so_rlast<=0; vs_e<={vs_e[1:0], vsync};
|
||||
if (vs_edge_e) begin fb_reads_last<=fb_reads; fb_reads<=0; end
|
||||
case (rst_state)
|
||||
R_IDLE: if (so_arvalid) begin so_arready<=1; rd_addr_l<=so_araddr; rdly<=rlfsr[2:0]; rst_state<=R_WAIT; end
|
||||
R_WAIT: if (rdly==0) rst_state<=R_DATA; else rdly<=rdly-1'b1;
|
||||
R_DATA: if (so_rready) begin
|
||||
for (int w=0;w<8;w++) begin int aa; aa=rd_addr_l+w*4;
|
||||
so_rdata[w*32 +: 32] <= (aa+3<FB_BYTES) ? {fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]} : 32'd0; end
|
||||
so_rresp<=2'b00; so_rvalid<=1; so_rlast<=1; read_beats<=read_beats+1;
|
||||
if (!vs_edge_e) fb_reads<=fb_reads+1; rst_state<=R_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
logic vid_run=0; logic [11:0] rawx, rawy;
|
||||
always_ff @(posedge video_clk) begin
|
||||
if (!vid_run) begin rawx<=0; rawy<=0; end
|
||||
else if (rawx==H_TOT-1) begin rawx<=0; rawy<=(rawy==V_TOT-1)?12'd0:rawy+1'b1; end
|
||||
else rawx<=rawx+1'b1;
|
||||
end
|
||||
wire active = (rawx>=H_BP)&&(rawx<H_BP+H_ACT)&&(rawy>=V_BP)&&(rawy<V_BP+V_ACT);
|
||||
assign px=active?(rawx-H_BP):12'd0; assign py=(rawy>=V_BP&&rawy<V_BP+V_ACT)?(rawy-V_BP):12'd0;
|
||||
assign in_win=active; assign vsync=vid_run&&(rawx==0)&&(rawy==0);
|
||||
logic [11:0] px_q, py_q; logic inwin_q, run_q;
|
||||
always_ff @(posedge video_clk) begin px_q<=px; py_q<=py; inwin_q<=in_win; run_q<=vid_run; end
|
||||
int checked; initial checked=0; logic scoring=0;
|
||||
always_ff @(posedge video_clk) if (scoring && run_q) begin
|
||||
logic [7:0] er,eg,eb; logic [31:0] w;
|
||||
if (inwin_q) begin int aa; aa=py_q*STRIDE+px_q*4; w={fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]}; er=w[7:0]; eg=w[15:8]; eb=w[23:16]; end
|
||||
else begin er=0; eg=0; eb=0; end
|
||||
checked++;
|
||||
if (so_r!==er||so_g!==eg||so_b!==eb) begin
|
||||
if (errors<12) $error("[scan] px(%0d,%0d) got(%02x,%02x,%02x) exp(%02x,%02x,%02x)", px_q,py_q, so_r,so_g,so_b, er,eg,eb); errors++; end
|
||||
end
|
||||
|
||||
// per-epoch idx/pal (for the oracle) + composed reference (owner epoch in [26:24], multi in [28]) + per-epoch refmaps
|
||||
logic [31:0] idx [0:MAX_EPOCHS-1][0:(512*512/4)-1]; logic [31:0] pal [0:MAX_EPOCHS-1][0:255];
|
||||
logic [31:0] refmap [0:FB_WORDS-1]; logic [31:0] refmap_ep [0:MAX_EPOCHS-1][0:FB_WORDS-1];
|
||||
logic [63:0] stg_buf [0:STG_WORDS-1]; // one epoch's staging list, streamed through the write port
|
||||
// temps: iverilog can't $readmemh into a 2D-array slice, so load flat then copy into the [e] plane
|
||||
logic [31:0] t_idx [0:(512*512/4)-1]; logic [31:0] t_pal [0:255]; logic [31:0] t_rm [0:FB_WORDS-1];
|
||||
|
||||
// ---- stream one epoch's list into the feeder staging via the WRITE PORT (word0 first, full STG reset) ----
|
||||
task automatic stream_list(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/feeder_sh3_%s%0d.mem", FIXTURE_TAG, k);
|
||||
for (int i=0;i<STG_WORDS;i++) stg_buf[i]=64'd0;
|
||||
$readmemh(fn, stg_buf);
|
||||
@(negedge clk);
|
||||
for (int i=0;i<STG_WORDS;i++) begin
|
||||
stg_we<=1'b1; stg_waddr<=i[11:0]; stg_wdata<=stg_buf[i]; @(negedge clk);
|
||||
end
|
||||
stg_we<=1'b0; stg_waddr<=12'd0; stg_wdata<=64'd0; @(negedge clk);
|
||||
// sanity: header word0 (ntris) landed correctly in the DUT staging
|
||||
if (dut.g_feeder.feeder_stg[0][31:0] !== stg_buf[0][31:0]) begin
|
||||
$error("[sched] epoch %0d: staged word0=%08x exp %08x (write-port mis-slot)", k, dut.g_feeder.feeder_stg[0][31:0], stg_buf[0][31:0]); errors++; end
|
||||
$display("[sched] epoch %0d: streamed %0d words via write port; word0(ntris)=%0d", k, STG_WORDS, dut.g_feeder.feeder_stg[0][31:0]);
|
||||
endtask
|
||||
|
||||
// ---- one-scene runner: GO, wait render+drain, REQUIRE fresh frame_drained (high->low->high, or low->high on first) ----
|
||||
task automatic run_scene(input int k, input int exp_records);
|
||||
int r0, f0, d=0; logic fd_before;
|
||||
r0=fd_rises; f0=fd_falls; fd_before=fbw_drained;
|
||||
@(negedge clk); feeder_go_tb=1'b1; @(negedge clk); feeder_go_tb=1'b0;
|
||||
d=0; while (feeder_ready_tb && d<4000) begin @(posedge clk); d++; end
|
||||
if (feeder_ready_tb!==1'b0) begin $error("[sched] epoch %0d: feeder_ready did not drop after GO", k); errors++; end
|
||||
if (fd_before) begin
|
||||
d=0; while (fd_falls==f0 && d<4000000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_falls==f0) begin $error("[sched] epoch %0d: frame_drained never fell from stale high — STALE drain", k); errors++; end
|
||||
end
|
||||
d=0; while (fd_rises<=r0 && d<4000000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_rises<=r0) begin $error("[sched] epoch %0d: frame_drained never rose — scene did not drain", k); errors++; end
|
||||
repeat(100) @(posedge clk);
|
||||
if (feeder_records_w!==exp_records) begin $error("[sched] epoch %0d: records_emitted=%0d exp %0d", k, feeder_records_w, exp_records); errors++; end
|
||||
if (fbw_ovf!==0 || fbw_bresp_err!==0) begin $error("[sched] epoch %0d: writer ovf=%0d bresp=%0d", k, fbw_ovf, fbw_bresp_err); errors++; end
|
||||
$display("[sched] epoch %0d: fresh drain (falls %0d->%0d, rises %0d->%0d), records=%0d, ovf=0", k, f0, fd_falls, r0, fd_rises, feeder_records_w);
|
||||
endtask
|
||||
|
||||
task automatic fill_cache(input int k, input logic [31:0] exp_crc);
|
||||
int d=0;
|
||||
fills=fills+1;
|
||||
@(posedge clk) fill_start<=1'b1; @(posedge clk) fill_start<=1'b0;
|
||||
d=0; while (fill_done && d<1000) begin @(posedge clk); d++; end // wait it drops (busy)
|
||||
d=0; while (!fill_done && d<400000) begin @(posedge clk); d++; end // then rises (done)
|
||||
if (!fill_done) begin $error("[sched] fill %0d: fill_done never rose (rearm failed)", k); errors++; end
|
||||
if (fill_crc_w!==exp_crc) begin $error("[sched] fill %0d: crc=%08x exp %08x", k, fill_crc_w, exp_crc); errors++; end
|
||||
if (fill_beats!==N_BEATS) begin $error("[sched] fill %0d: beats=%0d exp %0d", k, fill_beats, N_BEATS); errors++; end
|
||||
if (fill_bytes!==TEX_BYTES) begin $error("[sched] fill %0d: bytes=%0d exp %0d", k, fill_bytes, TEX_BYTES); errors++; end
|
||||
if (tex_rd_errs!==0) begin $error("[sched] fill %0d: rd_errs=%0d", k, tex_rd_errs); errors++; end
|
||||
$display("[sched] cache fill %0d: fresh done, crc=0x%08x (exp %08x), beats=%0d bytes=%0d rd_errs=0", k, fill_crc_w, exp_crc, fill_beats, fill_bytes);
|
||||
endtask
|
||||
|
||||
// texel lookup in epoch e's palette (module-level arrays, no per-call copy)
|
||||
function automatic logic [23:0] cell_e(input int e, input integer u, input integer v);
|
||||
integer lin; logic [31:0] w; logic [7:0] ix;
|
||||
lin=v*TW+u; w=idx[e][lin/4]; ix=w[(8*(lin%4)) +: 8]; cell_e=pal[e][ix][23:0];
|
||||
endfunction
|
||||
|
||||
// run ONE epoch fully (rebind tex -> fresh fill+CRC -> stream list -> GO -> fresh ordered drain)
|
||||
task automatic run_epoch(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/sh3_%s%0d_tex_lpddr.mem", FIXTURE_TAG, k);
|
||||
if (!EP_REUSE[k] || only_k>=0) begin // isolation (+ONLY=k) always fills — nothing resident
|
||||
$readmemh(fn, lpddr_mem);
|
||||
fill_cache(k, EP_CRC[k]);
|
||||
end else begin
|
||||
// Ch359 — EXPLICIT REUSE: no reload, no fill pulse. Fail-closed residency: the fill CRC register is
|
||||
// only rewritten by a fill, so it must still hold the prior epoch's verified CRC (same shared texture).
|
||||
if (fill_crc_w!==EP_CRC[k]) begin $error("%s epoch %0d: REUSE but resident crc=%08x exp %08x — residency broken", "[sched]", k, fill_crc_w, EP_CRC[k]); errors++; end
|
||||
else $display("%s epoch %0d: REUSE resident texture (crc=0x%08x, no fill)", "[sched]", k, fill_crc_w);
|
||||
end
|
||||
stream_list(k);
|
||||
run_scene(k, EP_REC[k]);
|
||||
endtask
|
||||
|
||||
string only_m; string fdump; int only_k; // +ONLY=ALL | +ONLY=<k> ; +FBDUMP=<file>
|
||||
initial begin
|
||||
errors=0; feeder_go_tb=1'b0; fill_start=1'b0; wr_arm=0; scan_en=0; stg_we=0; stg_waddr=0; stg_wdata=0; cap_fh=0; tex_fh=0; issue_fh=0; cap_ep=0;
|
||||
tex_issue_addr_q=0; tex_issue_raw_q=0; tex_issue_u_q=0; tex_issue_v_q=0;
|
||||
if (!$value$plusargs("ONLY=%s", only_m)) only_m="ALL";
|
||||
only_k = (only_m=="ALL") ? -1 : only_m.atoi();
|
||||
for (int i=0;i<FB_WORDS;i++) ideal[i]=32'd0;
|
||||
for (int i=0;i<FB_BYTES;i++) fb[i]=8'h00; // PRECLEAR EXACTLY ONCE
|
||||
idx[0][0]='x;
|
||||
for (int e=0;e<NEP;e++) begin
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_%s%0d_idx.mem", FIXTURE_TAG, e), t_idx);
|
||||
for (int i=0;i<(512*512/4);i++) idx[e][i]=t_idx[i];
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_%s%0d_pal.mem", FIXTURE_TAG, e), t_pal);
|
||||
for (int i=0;i<256;i++) pal[e][i]=t_pal[i];
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_%s%0d_refmap.mem", FIXTURE_TAG, e), t_rm);
|
||||
for (int i=0;i<FB_WORDS;i++) refmap_ep[e][i]=t_rm[i];
|
||||
end
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_%s_refmap.mem", FIXTURE_TAG), refmap);
|
||||
$display("[sched] ONLY=%s (only_k=%0d), N_EPOCHS=%0d", only_m, only_k, NEP);
|
||||
if (idx[0][0]===32'bx) begin $display("[tb_top_psmct32_sh3_zs640_shared_cap] SKIP — sh3_%s*.mem absent", FIXTURE_TAG); $finish; end
|
||||
|
||||
rst_n=1'b0; core_go=1'b0; repeat(6) @(posedge clk); rst_n=1'b1; repeat(8) @(posedge clk);
|
||||
|
||||
// boot the bootlet (uploads ALL N relocated CLUTs to their distinct CBPs)
|
||||
@(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);
|
||||
wait (feeder_ready_tb==1'b1); repeat(50) @(posedge clk);
|
||||
wr_arm<=1'b1; repeat(40) @(posedge clk);
|
||||
|
||||
// ===== scheduler: iterate the epoch descriptors in dump order (or a single epoch for the isolation check) =====
|
||||
cap_fh = $fopen($sformatf("%s_frags.txt", FIXTURE_TAG),"w");
|
||||
tex_fh = $fopen($sformatf("%s_textrace.txt", FIXTURE_TAG),"w");
|
||||
issue_fh = $fopen($sformatf("%s_issue.txt", FIXTURE_TAG),"w");
|
||||
if (only_k<0) begin
|
||||
for (int k=0;k<NEP;k++) begin
|
||||
cap_ep = k;
|
||||
run_epoch(k);
|
||||
if (k<NEP-1 && scan_en!==1'b0) begin $error("[sched] scanout enabled before the last epoch"); errors++; end
|
||||
end
|
||||
end else begin
|
||||
run_epoch(only_k);
|
||||
end
|
||||
|
||||
// enable scanout only after the final fresh drain
|
||||
scan_en<=1'b1; repeat(20) @(posedge clk);
|
||||
if (only_k<0) begin
|
||||
if (fd_rises<NEP) begin $error("[sched] expected %0d ordered drains: rises=%0d", NEP, fd_rises); errors++; end
|
||||
if (fd_falls<NEP-1)begin $error("[sched] epochs after the first never cleared stale drains (falls=%0d)", fd_falls); errors++; end
|
||||
end else if (fd_rises<1) begin $error("[sched] isolated epoch did not drain: rises=%0d", fd_rises); errors++; end
|
||||
// A descriptor marked resident reuses the prior cache image; every non-resident descriptor must refill it.
|
||||
begin
|
||||
int exp_fills; exp_fills=0;
|
||||
if (only_k>=0) exp_fills=1;
|
||||
else for (int k=0;k<NEP;k++) if (!EP_REUSE[k]) exp_fills++;
|
||||
if (fills!==exp_fills) begin $error("[sched] fills=%0d exp=%0d from descriptor reuse flags", fills, exp_fills); errors++; end
|
||||
end
|
||||
|
||||
// ===== scanout: score final FB vs the composed reference (per-pixel OWNER epoch), overlap separately =====
|
||||
vid_run=1;
|
||||
begin int d=0; while(!vsync && d<300000) begin @(posedge video_clk); d++; end end
|
||||
@(posedge video_clk); while(!vsync) @(posedge video_clk);
|
||||
begin scoring=1; @(posedge video_clk); while(!vsync) @(posedge video_clk); scoring=0; repeat(60) @(posedge emif_clk);
|
||||
if (fb_reads_last!==BEATS_PER_FRAME) begin $error("[sched] scanout beats/frame=%0d exp %0d", fb_reads_last, BEATS_PER_FRAME); errors++; end
|
||||
end
|
||||
if (so_underflow!==0) begin $error("[sched] scanout underflow"); errors++; end
|
||||
if (so_rd_errs !==0) begin $error("[sched] scanout rd_errs=%0d", so_rd_errs); errors++; end
|
||||
if (checked < H_ACT*V_ACT) begin $error("[sched] only %0d px checked (exp >= %0d)", checked, H_ACT*V_ACT); errors++; end
|
||||
|
||||
// oracle (multi-texture correctness): a covered pixel must equal ONE of the COVERING epochs' texels. Each epoch
|
||||
// stores its OWN (tu,tv) per pixel in refmap_ep[k]; we accept if the RTL colour matches any covering epoch's texel
|
||||
// in the <=1-texel neighbourhood (owner-first, then neighbours — handles coverage-edge owner ambiguity between
|
||||
// adjacent-order epochs, exactly the tolerance Ch355 used). multi-epoch pixels (>=2 covering) scored separately as
|
||||
// the accumulation composite proof. clut_bad = an RTL-written pixel (ideal!=0) whose colour is in NO epoch palette
|
||||
// (palettes are pairwise-distinct, so this still proves the pixel came from a real texture+CLUT, not garbage).
|
||||
begin
|
||||
int m_tot,m_ok,o_tot,o_ok,cb,D,ncov; bit mt; m_tot=0;m_ok=0;o_tot=0;o_ok=0;cb=0;
|
||||
for (int y=0;y<H;y++) for (int x=0;x<W;x++) begin
|
||||
int o; logic [23:0] fbc; bit cov; o=y*W+x;
|
||||
cov = (only_k<0) ? refmap[o][31] : refmap_ep[only_k][o][31];
|
||||
if (cov) begin
|
||||
fbc=ideal[o][23:0]; m_tot++; D=9; ncov=0;
|
||||
for (int e=0;e<NEP;e++) begin
|
||||
if (!(only_k>=0 && e!=only_k) && refmap_ep[e][o][31]) begin
|
||||
int tu,tv; tu=(refmap_ep[e][o]>>9)&9'h1FF; tv=refmap_ep[e][o]&9'h1FF; ncov++;
|
||||
for (int rad=0;rad<=1;rad++) for (int du=-rad;du<=rad;du++) for (int dv=-rad;dv<=rad;dv++) begin
|
||||
int ch; ch=(du<0?-du:du); if((dv<0?-dv:dv)>ch) ch=(dv<0?-dv:dv);
|
||||
if (ch==rad && (tu+du)>=0 && (tu+du)<TW && (tv+dv)>=0 && (tv+dv)<TH) begin
|
||||
mt=(fbc===cell_e(e,tu+du,tv+dv)); if (mt && rad<D) D=rad;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if (D<=1) m_ok++;
|
||||
if (only_k<0 && refmap[o][28]) begin o_tot++; if (D<=1) o_ok++; end // multi-epoch (>=2 covering)
|
||||
if (ideal[o]!==32'd0) begin bit f; f=1'b0;
|
||||
for (int e=0;e<NEP;e++) for (int i=0;i<256;i++) if (pal[e][i][23:0]===fbc) f=1'b1;
|
||||
if(!f) cb++; end
|
||||
end
|
||||
end
|
||||
$display("[sched][oracle] ONLY=%s <=1texel ALL=%0d/%0d (%.1f%%) MULTI(>=2)=%0d/%0d (%.1f%%) clut_bad=%0d",
|
||||
only_m, m_ok,m_tot,(m_tot>0)?100.0*m_ok/m_tot:0.0, o_ok,o_tot,(o_tot>0)?100.0*o_ok/o_tot:0.0, cb);
|
||||
if (cb!==0) begin $error("[sched][oracle] ONLY=%s: %0d covered px in NO epoch palette", only_m, cb); errors++; end
|
||||
if (only_k<0) begin
|
||||
if (o_tot<500) begin $error("[sched][oracle] only %0d multi-epoch px — accumulation not exercised", o_tot); errors++; end
|
||||
// MULTI <=1texel is TEXTURE FIDELITY (RTL 11-bit reciprocal LUT precision), NOT accumulation correctness.
|
||||
// It tracks the per-epoch isolated fidelity (ONLY=k gives 93.0/93.3/95.6%); the multi-epoch subset sits at
|
||||
// the same reciprocal floor (~92%). The ACCUMULATION proof is separate and EXACT: compose_sched.py shows
|
||||
// the joint ALL render == the composited ONLY=k isolation dumps 100% BIT-FOR-BIT. So gate this at the
|
||||
// documented reciprocal floor, not an accumulation-implying bar.
|
||||
if (o_tot>0 && (100.0*o_ok/o_tot)<90.0) begin $error("[sched][oracle] MULTI <=1texel %.1f%% < 90%% reciprocal floor", 100.0*o_ok/o_tot); errors++; end
|
||||
end
|
||||
end
|
||||
// FB dump for the composition==isolated check (per-epoch dumps composed externally -> compare vs ALL dump)
|
||||
if ($value$plusargs("FBDUMP=%s", fdump)) begin
|
||||
int fh; fh=$fopen(fdump,"w");
|
||||
for (int y=0;y<H;y++) for (int x=0;x<W;x++) begin int aa; aa=y*STRIDE+x*4; $fwrite(fh,"%08x\n",{fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]}); end
|
||||
$fclose(fh); $display("[sched] dumped RTL FB (%0dx%0d) -> %s", W, H, fdump);
|
||||
end
|
||||
|
||||
$display("[tb_top_psmct32_sh3_zs640_shared_cap] checked=%0d beats/frame=%0d (exp %0d) fresh_drains(rise/fall)=%0d/%0d records=%0d underflow=%0b ovf=%0d errors=%0d",
|
||||
checked, fb_reads_last, BEATS_PER_FRAME, fd_rises, fd_falls, feeder_records_w, so_underflow, fbw_ovf, errors);
|
||||
if (cap_fh!=0) $fclose(cap_fh);
|
||||
if (tex_fh!=0) $fclose(tex_fh);
|
||||
if (issue_fh!=0) $fclose(issue_fh);
|
||||
if (errors==0) $display("[tb_top_psmct32_sh3_zs640_shared_cap] PASS"); else $display("[tb_top_psmct32_sh3_zs640_shared_cap] FAIL");
|
||||
$finish;
|
||||
end
|
||||
initial begin #320000000; $error("[tb_top_psmct32_sh3_zs640_shared_cap] TIMEOUT"); $finish; end
|
||||
endmodule : tb_top_psmct32_sh3_zs640_shared_cap
|
||||
@@ -0,0 +1,460 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_sh3_zs640c12_cap (Ch360 — four-epoch shared-texture capture at native 640x480)
|
||||
//
|
||||
// Four authentic SH3 draw groups share one resident TEX0/CLUT and accumulate into one LPDDR framebuffer via the
|
||||
// C12 epoch descriptor table. Each group keeps the proven 204-triangle feeder-list capacity.
|
||||
// Also captures the fragment stream {epoch,x,y,persp_z,color} -> zs640c12_frags.txt. Proves the Ch356 integration acceptance:
|
||||
// * preclear EXACTLY once.
|
||||
// * N FRESH cache fills, each fill_done low->high, expected beats/bytes, 0 read errors, epoch CRC (EPk_CRC).
|
||||
// * N lists STREAMED through the feeder staging WRITE PORT (feeder_stg_we/waddr/wdata) — NOT the $readmemh backdoor
|
||||
// (closes the Ch355 sim gap where the write-port sequencing was untested). Each streams the full STG_WORDS so
|
||||
// staging is fully reset per epoch (no stale words), and word0 (the ntris header) is exercised first.
|
||||
// * N FRESH ORDERED drains: epoch 0 low->high (first render); epochs 1..N-1 high->low->high (stale cleared).
|
||||
// * scanout stays disabled until the LAST fresh drain; exact BEATS_PER_FRAME (=ROW_BEATS*H).
|
||||
// * final FB scored vs the independent composed reference using the PER-PIXEL OWNER epoch (exact palette per owner);
|
||||
// multi-epoch (>=2) overlap scored separately as the accumulation proof.
|
||||
// * +ONLY=<k> renders a SINGLE epoch (+FBDUMP dumps its FB) for the composition==isolated bit-for-bit check.
|
||||
// LOCAL/gitignored fixtures; skip-guard if absent.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_sh3_zs640c12_cap;
|
||||
`include "sh3_zs640c12_params.vh" // FBPXW=640, FBH=480, N_EPOCHS=4, EPk_CRC/EPk_NTRIS/EPk_CBP, TEX_*, ...
|
||||
localparam int W = FBPXW, H = FBH;
|
||||
localparam int STRIDE = W*4; // 1536
|
||||
localparam int ROW_BEATS = STRIDE/32; // 48
|
||||
localparam int FB_BYTES = STRIDE*H; // 585216
|
||||
localparam int FB_WORDS = W*H;
|
||||
localparam int BEATS_PER_FRAME = ROW_BEATS*H; // 18288
|
||||
localparam int H_ACT=W, V_ACT=H, H_BP=32, H_FP=8, V_BP=16, V_FP=8;
|
||||
localparam int H_TOT=H_BP+H_ACT+H_FP, V_TOT=V_BP+V_ACT+V_FP;
|
||||
localparam int NEP = N_EPOCHS; // 4
|
||||
|
||||
// per-epoch expected CRC / records (from the descriptor params)
|
||||
// Ch360 — four epochs share one texture: EPk_REUSE=1 means the epoch runs on the resident cache without a fill.
|
||||
logic [31:0] EP_CRC [0:3]; int EP_REC [0:3]; bit EP_REUSE [0:3]; int fills;
|
||||
initial begin
|
||||
EP_CRC[0]=EP0_CRC; EP_CRC[1]=EP1_CRC; EP_CRC[2]=EP2_CRC; EP_CRC[3]=EP3_CRC;
|
||||
EP_REC[0]=EP0_NTRIS; EP_REC[1]=EP1_NTRIS; EP_REC[2]=EP2_NTRIS; EP_REC[3]=EP3_NTRIS;
|
||||
EP_REUSE[0]=EP0_REUSE; EP_REUSE[1]=EP1_REUSE; EP_REUSE[2]=EP2_REUSE; EP_REUSE[3]=EP3_REUSE;
|
||||
fills=0;
|
||||
end
|
||||
|
||||
logic clk=0; always #5 clk=~clk;
|
||||
logic emif_clk=0; always #2 emif_clk=~emif_clk;
|
||||
logic video_clk=0; always #7 video_clk=~video_clk;
|
||||
logic rst_n; logic fb_commit=0; always #13 fb_commit=~fb_commit;
|
||||
int errors; initial errors=0;
|
||||
|
||||
// ===== bram-top (FB_LPDDR_ONLY, AUTOSTART off, STG 2048), H/V_ACTIVE = 384x381 =====
|
||||
logic core_go; logic [7:0] r,g,b; logic hsync,vsync_o,de;
|
||||
logic core_halt,dma_done_seen,frame_seen,raster_overflow,frame_toggle,dma_done_toggle;
|
||||
logic feeder_go_tb, feeder_ready_tb; logic [15:0] feeder_records_w; logic [31:0] feeder_waits_w;
|
||||
logic gs_tex_rd_en_o; logic [31:0] gs_tex_rd_addr_o; logic [31:0] tex_cache_data; logic tex_cache_ready;
|
||||
logic [31:0] tex_cache_hits, tex_bram_hits;
|
||||
logic flush_emit_w; logic [31:0] flush_addr_w; logic [31:0] flush_color32_w; logic [5:0] flush_psm_w;
|
||||
logic [11:0] flush_x_w, flush_y_w; logic [31:0] flush_z_w; // Ch357 — fragment coords + persp_z5
|
||||
int cap_ep; int cap_fh; int tex_fh; int issue_fh; // fragment capture (verify persp_z5 vs the clamp16 oracle)
|
||||
logic [10:0] cap_pu_q, cap_pv_q, cap_pu_qq, cap_pv_qq; // persp_u/v delayed from texture issue to flush/color
|
||||
logic [31:0] tex_issue_addr_q, tex_issue_raw_q; // debug: texture request that returns on the next cycle
|
||||
logic [10:0] tex_issue_u_q, tex_issue_v_q;
|
||||
// feeder staging WRITE PORT (streamed per epoch)
|
||||
logic stg_we; logic [11:0] stg_waddr; logic [63:0] stg_wdata;
|
||||
|
||||
top_psmct32_raster_demo_bram #(
|
||||
.H_ACTIVE(FBPXW), .V_ACTIVE(FBH),
|
||||
.VRAM_BYTES(VRAM_BYTES_P), .VRAM_ENABLE_READ2(1'b0), .PSMCT32_SWIZZLE(1'b0),
|
||||
.COMBINED_TAZ(1'b0), .TILE_LOCAL(1'b0), .TILE_COLS(1), .TILE_ROWS(1),
|
||||
.TILE_MULTIPRIM(1'b0), .TILE_PRIM_COUNT(1), .TILE_FIFO_DEPTH(8),
|
||||
.BIN_BUFFER_ENABLE(1'b0), .HEARTBEAT_SPLICE_ENABLE(1'b0),
|
||||
.FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(STG_WORDS), .FEEDER_AUTOSTART(1'b0),
|
||||
.PERSPECTIVE_CORRECT(1'b1), .PERSP_RECIP_IDX_BITS(11),
|
||||
.GRAD_SEQ_DIVIDER(1'b1), .GRAD_DIV_CYCLES(4),
|
||||
.GS_LPDDR_TEX(1'b1), .TEX_VRAM_BASE(TEX_VRAM_BASE), .TEX_CACHE_BYTES(TEX_BYTES),
|
||||
.CLUT_CSM1_ENABLE(1'b1), .FB_LPDDR_ONLY(1'b1)
|
||||
) dut (
|
||||
.clk(clk), .rst_n(rst_n), .core_go(core_go),
|
||||
.r(r), .g(g), .b(b), .hsync(hsync), .vsync(vsync_o), .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),
|
||||
.feeder_stg_we_i(stg_we), .feeder_stg_waddr_i(stg_waddr), .feeder_stg_wdata_i(stg_wdata),
|
||||
.feeder_go_i(feeder_go_tb), .feeder_ready_o(feeder_ready_tb),
|
||||
.feeder_records_o(feeder_records_w), .feeder_waits_o(feeder_waits_w),
|
||||
.flush_emit_o(flush_emit_w), .flush_addr_o(flush_addr_w),
|
||||
.flush_pix16_o(), .flush_color32_o(flush_color32_w), .flush_psm_o(flush_psm_w),
|
||||
.flush_x_o(flush_x_w), .flush_y_o(flush_y_w), .flush_z_o(flush_z_w),
|
||||
.gs_tex_rd_en_o(gs_tex_rd_en_o), .gs_tex_rd_addr_o(gs_tex_rd_addr_o),
|
||||
.tex_cache_data_i(tex_cache_data), .tex_cache_ready_i(tex_cache_ready),
|
||||
.tex_cache_hits_o(tex_cache_hits), .tex_bram_hits_o(tex_bram_hits)
|
||||
);
|
||||
|
||||
// Ch357 — capture every emitted fragment {epoch, x, y, persp_z, color}. An external Python check replays these through
|
||||
// the clamp16 persistent-Z model and compares owner/reject to the oracle -> validates persp_z5 end-to-end (the new RTL).
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst_n) begin
|
||||
if (dut.u_gs.persp_outvalid) begin
|
||||
cap_pu_qq <= cap_pu_q;
|
||||
cap_pv_qq <= cap_pv_q;
|
||||
cap_pu_q <= dut.u_gs.persp_u;
|
||||
cap_pv_q <= dut.u_gs.persp_v;
|
||||
end
|
||||
if (gs_tex_rd_en_o) begin
|
||||
tex_issue_addr_q <= gs_tex_rd_addr_o;
|
||||
tex_issue_raw_q <= dut.u_gs.u_tex.addr;
|
||||
tex_issue_u_q <= dut.u_gs.u_tex.u_eff;
|
||||
tex_issue_v_q <= dut.u_gs.u_tex.v_eff;
|
||||
end
|
||||
if (flush_emit_w && (flush_psm_w==6'h00) && cap_fh!=0)
|
||||
$fwrite(cap_fh, "%0d %0d %0d %0d %08x %0d %0d\n",
|
||||
cap_ep, flush_x_w, flush_y_w, flush_z_w, flush_color32_w, cap_pu_qq, cap_pv_qq);
|
||||
if (flush_emit_w && (flush_psm_w==6'h00) && tex_fh!=0)
|
||||
$fwrite(tex_fh, "%0d %0d %0d %0d %08x %08x %08x %0d %0d %08x %0d %02x %08x %08x %08x\n",
|
||||
cap_ep, flush_x_w, flush_y_w, flush_z_w, flush_color32_w,
|
||||
tex_issue_addr_q, tex_issue_raw_q, tex_issue_u_q, tex_issue_v_q,
|
||||
dut.u_gs.u_tex.tex_rd_data, dut.u_gs.u_tex.sel_lo, dut.u_gs.u_tex.clut_rd_idx,
|
||||
dut.u_gs.u_tex.clut_rd_data, dut.u_gs.u_tex.near_color, dut.u_gs.s1_tex_color);
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst_n) begin
|
||||
#1;
|
||||
if (dut.u_gs.persp_outvalid && issue_fh!=0)
|
||||
$fwrite(issue_fh, "%0d %0d %0d %0d %0d %0d %0d\n",
|
||||
cap_ep,
|
||||
dut.u_gs.g_persp_emit.u_persp_uv.uq_pipe[2],
|
||||
dut.u_gs.g_persp_emit.u_persp_uv.vq_pipe[2],
|
||||
dut.u_gs.g_persp_emit.u_persp_uv.w_recip,
|
||||
dut.u_gs.persp_u, dut.u_gs.persp_v,
|
||||
dut.u_gs.g_persp_emit.u_persp_uv.out_valid);
|
||||
end
|
||||
end
|
||||
|
||||
// texture cache + behavioral texture LPDDR (RELOADED between fills for each epoch's rebind)
|
||||
logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid; logic [7:0] arlen; logic [2:0] arsize;
|
||||
logic arvalid, arready; logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready;
|
||||
logic fill_start, fill_done; logic [31:0] fill_beats, fill_bytes, tex_rd_errs, fill_crc_w;
|
||||
gs_texture_cache #(.LPDDR_TEX_BASE(LPDDR_TEX_BASE), .TEX_VRAM_BASE(TEX_VRAM_BASE),
|
||||
.TEX_BYTES(TEX_BYTES), .N_BEATS(N_BEATS)) u_cache (
|
||||
.axi_clk(clk), .axi_rst_n(rst_n), .fill_start(fill_start), .fill_done(fill_done),
|
||||
.fill_beats(fill_beats), .fill_bytes(fill_bytes), .rd_errs(tex_rd_errs), .fill_crc(fill_crc_w),
|
||||
.araddr(araddr), .arburst(arburst), .arid(arid), .arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready),
|
||||
.rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready),
|
||||
.sample_clk(clk), .tex_rd_en(gs_tex_rd_en_o), .tex_rd_addr(gs_tex_rd_addr_o),
|
||||
.tex_rd_data(tex_cache_data), .tex_ready(tex_cache_ready)
|
||||
);
|
||||
logic [31:0] lpddr_mem [0:(TEX_BYTES/4)-1]; // reloaded per epoch: tex0, tex1, tex2
|
||||
typedef enum logic [1:0] { S_IDLE, S_WAIT, S_DATA } sstate_t; sstate_t sst; logic [3:0] tdly; int tbeat;
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin sst<=S_IDLE; arready<=0; rvalid<=0; rlast<=0; rresp<=0; rdata<=0; tdly<=0; end
|
||||
else begin arready<=0; rvalid<=0; rlast<=0;
|
||||
case (sst)
|
||||
S_IDLE: if (arvalid) begin arready<=1; tbeat<=(araddr-LPDDR_TEX_BASE)>>5; tdly<=4'd2; sst<=S_WAIT; end
|
||||
S_WAIT: if (tdly==0) sst<=S_DATA; else tdly<=tdly-1'b1;
|
||||
S_DATA: if (rready) begin for (int w=0;w<8;w++) rdata[w*32 +: 32]<=lpddr_mem[tbeat*8+w];
|
||||
rresp<=2'b00; rvalid<=1; rlast<=1; sst<=S_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// render epoch (per scene) + PSMCT32 writer + precleared LPDDR FB
|
||||
logic fb_flush=0, render_inflight=0, feeder_ready_q=0; int eof_count;
|
||||
wire feeder_ready_rise = feeder_ready_tb && !feeder_ready_q;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin render_inflight<=0; feeder_ready_q<=0; fb_flush<=0; eof_count<=0; end
|
||||
else begin feeder_ready_q<=feeder_ready_tb; fb_flush<=1'b0;
|
||||
if (feeder_go_tb && feeder_ready_tb) render_inflight<=1'b1;
|
||||
if (render_inflight && feeder_ready_rise) begin fb_flush<=1'b1; render_inflight<=1'b0; eof_count<=eof_count+1; end
|
||||
end
|
||||
end
|
||||
logic wr_arm=0; logic [255:0] fbw_wdata; logic [31:0] fbw_wstrb, fbw_awaddr; logic fbw_awvalid, fbw_wvalid;
|
||||
logic [31:0] fbw_beats, fbw_ovf, fbw_bresp_err; logic fbw_idle, fbw_drained;
|
||||
gs_lpddr_axi_master #(.FIFO_DEPTH(64), .PIX_BYTES(4)) u_wr (
|
||||
.gs_clk(clk), .gs_rst_n(rst_n), .enable(1'b1),
|
||||
.arm(wr_arm), .canary(1'b0), .fb_base(32'h0), .ctrl_commit(fb_commit),
|
||||
.px_emit(flush_emit_w && (flush_psm_w==6'h00)), .px_addr(flush_addr_w), .px_pix32(flush_color32_w), .flush(fb_flush),
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n),
|
||||
.awaddr(fbw_awaddr), .awlen(), .awsize(), .awburst(), .awid(), .awvalid(fbw_awvalid), .awready(1'b1),
|
||||
.wdata(fbw_wdata), .wstrb(fbw_wstrb), .wlast(), .wvalid(fbw_wvalid), .wready(1'b1),
|
||||
.bvalid(1'b1), .bready(), .bresp(2'b00),
|
||||
.beats_written(fbw_beats), .bursts_issued(), .bresp_err_count(fbw_bresp_err),
|
||||
.fifo_overflow_count(fbw_ovf), .idle(fbw_idle), .frame_drained(fbw_drained)
|
||||
);
|
||||
logic [7:0] fb [0:FB_BYTES-1]; logic [31:0] fb_awlat;
|
||||
always_ff @(posedge emif_clk) begin
|
||||
if (fbw_awvalid) fb_awlat<=fbw_awaddr;
|
||||
if (fbw_wvalid) for (int i=0;i<32;i++) if (fbw_wstrb[i]) begin
|
||||
int aa; aa=fb_awlat+i; if (aa>=0 && aa<FB_BYTES) fb[aa]<=fbw_wdata[i*8 +: 8]; end
|
||||
end
|
||||
logic [31:0] ideal [0:FB_WORDS-1];
|
||||
always_ff @(posedge clk) if (rst_n && flush_emit_w && (flush_psm_w==6'h00)) ideal[flush_addr_w>>2]<=flush_color32_w;
|
||||
|
||||
// FRESH-DRAIN observer: count frame_drained low->high and high->low edges (emif domain).
|
||||
logic fd_q; int fd_rises, fd_falls;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin fd_q<=0; fd_rises<=0; fd_falls<=0; end
|
||||
else begin fd_q<=fbw_drained;
|
||||
if (fbw_drained && !fd_q) fd_rises<=fd_rises+1;
|
||||
if (!fbw_drained && fd_q) fd_falls<=fd_falls+1;
|
||||
end
|
||||
end
|
||||
|
||||
// scanout (384x381), host-gated: enable = scan_en (video_src) AND frame_drained
|
||||
logic scan_en=0; wire so_enable = scan_en & fbw_drained;
|
||||
logic [11:0] px, py; logic vsync, in_win; logic [7:0] so_r, so_g, so_b;
|
||||
logic so_underflow; logic [31:0] so_rd_errs; logic so_line_valid;
|
||||
logic [29:0] so_araddr; logic [1:0] so_arburst; logic [6:0] so_arid; logic [7:0] so_arlen;
|
||||
logic [2:0] so_arsize; logic so_arvalid, so_arready;
|
||||
logic [255:0] so_rdata; logic [1:0] so_rresp; logic so_rlast, so_rvalid, so_rready;
|
||||
gs_lpddr_scanout_lb #(.FB_BASE(30'd0), .STRIDE_BYTES(STRIDE), .ROW_BEATS(ROW_BEATS),
|
||||
.N_ROWS(H), .PSMCT32(1'b1)) u_scan (
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n), .enable(so_enable),
|
||||
.video_clk(video_clk), .frame_start(vsync), .pixel_x(px), .pixel_y(py), .in_window(in_win),
|
||||
.r(so_r), .g(so_g), .b(so_b), .line_valid(so_line_valid), .underflow(so_underflow), .rd_errs(so_rd_errs),
|
||||
.araddr(so_araddr), .arburst(so_arburst), .arid(so_arid), .arlen(so_arlen), .arsize(so_arsize),
|
||||
.arvalid(so_arvalid), .arready(so_arready), .rdata(so_rdata), .rresp(so_rresp),
|
||||
.rlast(so_rlast), .rvalid(so_rvalid), .rready(so_rready)
|
||||
);
|
||||
logic [7:0] rlfsr=8'h3C; always_ff @(posedge emif_clk) rlfsr<={rlfsr[6:0], rlfsr[7]^rlfsr[5]^rlfsr[4]^rlfsr[3]};
|
||||
typedef enum logic [1:0] { R_IDLE, R_WAIT, R_DATA } rst_t; rst_t rst_state;
|
||||
logic [3:0] rdly; logic [29:0] rd_addr_l; int read_beats;
|
||||
logic [2:0] vs_e; wire vs_edge_e = vs_e[1] && !vs_e[2]; int fb_reads, fb_reads_last;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin rst_state<=R_IDLE; so_arready<=0; so_rvalid<=0; so_rlast<=0; so_rresp<=0; so_rdata<=0; rdly<=0;
|
||||
read_beats<=0; vs_e<=0; fb_reads<=0; fb_reads_last<=0; end
|
||||
else begin so_arready<=0; so_rvalid<=0; so_rlast<=0; vs_e<={vs_e[1:0], vsync};
|
||||
if (vs_edge_e) begin fb_reads_last<=fb_reads; fb_reads<=0; end
|
||||
case (rst_state)
|
||||
R_IDLE: if (so_arvalid) begin so_arready<=1; rd_addr_l<=so_araddr; rdly<=rlfsr[2:0]; rst_state<=R_WAIT; end
|
||||
R_WAIT: if (rdly==0) rst_state<=R_DATA; else rdly<=rdly-1'b1;
|
||||
R_DATA: if (so_rready) begin
|
||||
for (int w=0;w<8;w++) begin int aa; aa=rd_addr_l+w*4;
|
||||
so_rdata[w*32 +: 32] <= (aa+3<FB_BYTES) ? {fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]} : 32'd0; end
|
||||
so_rresp<=2'b00; so_rvalid<=1; so_rlast<=1; read_beats<=read_beats+1;
|
||||
if (!vs_edge_e) fb_reads<=fb_reads+1; rst_state<=R_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
logic vid_run=0; logic [11:0] rawx, rawy;
|
||||
always_ff @(posedge video_clk) begin
|
||||
if (!vid_run) begin rawx<=0; rawy<=0; end
|
||||
else if (rawx==H_TOT-1) begin rawx<=0; rawy<=(rawy==V_TOT-1)?12'd0:rawy+1'b1; end
|
||||
else rawx<=rawx+1'b1;
|
||||
end
|
||||
wire active = (rawx>=H_BP)&&(rawx<H_BP+H_ACT)&&(rawy>=V_BP)&&(rawy<V_BP+V_ACT);
|
||||
assign px=active?(rawx-H_BP):12'd0; assign py=(rawy>=V_BP&&rawy<V_BP+V_ACT)?(rawy-V_BP):12'd0;
|
||||
assign in_win=active; assign vsync=vid_run&&(rawx==0)&&(rawy==0);
|
||||
logic [11:0] px_q, py_q; logic inwin_q, run_q;
|
||||
always_ff @(posedge video_clk) begin px_q<=px; py_q<=py; inwin_q<=in_win; run_q<=vid_run; end
|
||||
int checked; initial checked=0; logic scoring=0;
|
||||
always_ff @(posedge video_clk) if (scoring && run_q) begin
|
||||
logic [7:0] er,eg,eb; logic [31:0] w;
|
||||
if (inwin_q) begin int aa; aa=py_q*STRIDE+px_q*4; w={fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]}; er=w[7:0]; eg=w[15:8]; eb=w[23:16]; end
|
||||
else begin er=0; eg=0; eb=0; end
|
||||
checked++;
|
||||
if (so_r!==er||so_g!==eg||so_b!==eb) begin
|
||||
if (errors<12) $error("[scan] px(%0d,%0d) got(%02x,%02x,%02x) exp(%02x,%02x,%02x)", px_q,py_q, so_r,so_g,so_b, er,eg,eb); errors++; end
|
||||
end
|
||||
|
||||
// per-epoch idx/pal (for the oracle) + composed reference (owner epoch in [26:24], multi in [28]) + per-epoch refmaps
|
||||
logic [31:0] idx [0:3][0:(512*512/4)-1]; logic [31:0] pal [0:3][0:255];
|
||||
logic [31:0] refmap [0:FB_WORDS-1]; logic [31:0] refmap_ep [0:3][0:FB_WORDS-1];
|
||||
logic [63:0] stg_buf [0:STG_WORDS-1]; // one epoch's staging list, streamed through the write port
|
||||
// temps: iverilog can't $readmemh into a 2D-array slice, so load flat then copy into the [e] plane
|
||||
logic [31:0] t_idx [0:(512*512/4)-1]; logic [31:0] t_pal [0:255]; logic [31:0] t_rm [0:FB_WORDS-1];
|
||||
|
||||
// ---- stream one epoch's list into the feeder staging via the WRITE PORT (word0 first, full STG reset) ----
|
||||
task automatic stream_list(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/feeder_sh3_zs640c12%0d.mem", k);
|
||||
for (int i=0;i<STG_WORDS;i++) stg_buf[i]=64'd0;
|
||||
$readmemh(fn, stg_buf);
|
||||
@(negedge clk);
|
||||
for (int i=0;i<STG_WORDS;i++) begin
|
||||
stg_we<=1'b1; stg_waddr<=i[11:0]; stg_wdata<=stg_buf[i]; @(negedge clk);
|
||||
end
|
||||
stg_we<=1'b0; stg_waddr<=12'd0; stg_wdata<=64'd0; @(negedge clk);
|
||||
// sanity: header word0 (ntris) landed correctly in the DUT staging
|
||||
if (dut.g_feeder.feeder_stg[0][31:0] !== stg_buf[0][31:0]) begin
|
||||
$error("[sched] epoch %0d: staged word0=%08x exp %08x (write-port mis-slot)", k, dut.g_feeder.feeder_stg[0][31:0], stg_buf[0][31:0]); errors++; end
|
||||
$display("[sched] epoch %0d: streamed %0d words via write port; word0(ntris)=%0d", k, STG_WORDS, dut.g_feeder.feeder_stg[0][31:0]);
|
||||
endtask
|
||||
|
||||
// ---- one-scene runner: GO, wait render+drain, REQUIRE fresh frame_drained (high->low->high, or low->high on first) ----
|
||||
task automatic run_scene(input int k, input int exp_records);
|
||||
int r0, f0, d=0; logic fd_before;
|
||||
r0=fd_rises; f0=fd_falls; fd_before=fbw_drained;
|
||||
@(negedge clk); feeder_go_tb=1'b1; @(negedge clk); feeder_go_tb=1'b0;
|
||||
d=0; while (feeder_ready_tb && d<4000) begin @(posedge clk); d++; end
|
||||
if (feeder_ready_tb!==1'b0) begin $error("[sched] epoch %0d: feeder_ready did not drop after GO", k); errors++; end
|
||||
if (fd_before) begin
|
||||
d=0; while (fd_falls==f0 && d<4000000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_falls==f0) begin $error("[sched] epoch %0d: frame_drained never fell from stale high — STALE drain", k); errors++; end
|
||||
end
|
||||
d=0; while (fd_rises<=r0 && d<4000000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_rises<=r0) begin $error("[sched] epoch %0d: frame_drained never rose — scene did not drain", k); errors++; end
|
||||
repeat(100) @(posedge clk);
|
||||
if (feeder_records_w!==exp_records) begin $error("[sched] epoch %0d: records_emitted=%0d exp %0d", k, feeder_records_w, exp_records); errors++; end
|
||||
if (fbw_ovf!==0 || fbw_bresp_err!==0) begin $error("[sched] epoch %0d: writer ovf=%0d bresp=%0d", k, fbw_ovf, fbw_bresp_err); errors++; end
|
||||
$display("[sched] epoch %0d: fresh drain (falls %0d->%0d, rises %0d->%0d), records=%0d, ovf=0", k, f0, fd_falls, r0, fd_rises, feeder_records_w);
|
||||
endtask
|
||||
|
||||
task automatic fill_cache(input int k, input logic [31:0] exp_crc);
|
||||
int d=0;
|
||||
fills=fills+1;
|
||||
@(posedge clk) fill_start<=1'b1; @(posedge clk) fill_start<=1'b0;
|
||||
d=0; while (fill_done && d<1000) begin @(posedge clk); d++; end // wait it drops (busy)
|
||||
d=0; while (!fill_done && d<400000) begin @(posedge clk); d++; end // then rises (done)
|
||||
if (!fill_done) begin $error("[sched] fill %0d: fill_done never rose (rearm failed)", k); errors++; end
|
||||
if (fill_crc_w!==exp_crc) begin $error("[sched] fill %0d: crc=%08x exp %08x", k, fill_crc_w, exp_crc); errors++; end
|
||||
if (fill_beats!==N_BEATS) begin $error("[sched] fill %0d: beats=%0d exp %0d", k, fill_beats, N_BEATS); errors++; end
|
||||
if (fill_bytes!==TEX_BYTES) begin $error("[sched] fill %0d: bytes=%0d exp %0d", k, fill_bytes, TEX_BYTES); errors++; end
|
||||
if (tex_rd_errs!==0) begin $error("[sched] fill %0d: rd_errs=%0d", k, tex_rd_errs); errors++; end
|
||||
$display("[sched] cache fill %0d: fresh done, crc=0x%08x (exp %08x), beats=%0d bytes=%0d rd_errs=0", k, fill_crc_w, exp_crc, fill_beats, fill_bytes);
|
||||
endtask
|
||||
|
||||
// texel lookup in epoch e's palette (module-level arrays, no per-call copy)
|
||||
function automatic logic [23:0] cell_e(input int e, input integer u, input integer v);
|
||||
integer lin; logic [31:0] w; logic [7:0] ix;
|
||||
lin=v*TW+u; w=idx[e][lin/4]; ix=w[(8*(lin%4)) +: 8]; cell_e=pal[e][ix][23:0];
|
||||
endfunction
|
||||
|
||||
// run ONE epoch fully (rebind tex -> fresh fill+CRC -> stream list -> GO -> fresh ordered drain)
|
||||
task automatic run_epoch(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/sh3_zs640c12%0d_tex_lpddr.mem", k);
|
||||
if (!EP_REUSE[k] || only_k>=0) begin // isolation (+ONLY=k) always fills — nothing resident
|
||||
$readmemh(fn, lpddr_mem);
|
||||
fill_cache(k, EP_CRC[k]);
|
||||
end else begin
|
||||
// Ch359 — EXPLICIT REUSE: no reload, no fill pulse. Fail-closed residency: the fill CRC register is
|
||||
// only rewritten by a fill, so it must still hold the prior epoch's verified CRC (same shared texture).
|
||||
if (fill_crc_w!==EP_CRC[k]) begin $error("%s epoch %0d: REUSE but resident crc=%08x exp %08x — residency broken", "[sched]", k, fill_crc_w, EP_CRC[k]); errors++; end
|
||||
else $display("%s epoch %0d: REUSE resident texture (crc=0x%08x, no fill)", "[sched]", k, fill_crc_w);
|
||||
end
|
||||
stream_list(k);
|
||||
run_scene(k, EP_REC[k]);
|
||||
endtask
|
||||
|
||||
string only_m; string fdump; int only_k; // +ONLY=ALL | +ONLY=<k> ; +FBDUMP=<file>
|
||||
initial begin
|
||||
errors=0; feeder_go_tb=1'b0; fill_start=1'b0; wr_arm=0; scan_en=0; stg_we=0; stg_waddr=0; stg_wdata=0; cap_fh=0; tex_fh=0; issue_fh=0; cap_ep=0;
|
||||
tex_issue_addr_q=0; tex_issue_raw_q=0; tex_issue_u_q=0; tex_issue_v_q=0;
|
||||
if (!$value$plusargs("ONLY=%s", only_m)) only_m="ALL";
|
||||
only_k = (only_m=="ALL") ? -1 : only_m.atoi();
|
||||
for (int i=0;i<FB_WORDS;i++) ideal[i]=32'd0;
|
||||
for (int i=0;i<FB_BYTES;i++) fb[i]=8'h00; // PRECLEAR EXACTLY ONCE
|
||||
idx[0][0]='x;
|
||||
for (int e=0;e<NEP;e++) begin
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_zs640c12%0d_idx.mem", e), t_idx);
|
||||
for (int i=0;i<(512*512/4);i++) idx[e][i]=t_idx[i];
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_zs640c12%0d_pal.mem", e), t_pal);
|
||||
for (int i=0;i<256;i++) pal[e][i]=t_pal[i];
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_zs640c12%0d_refmap.mem", e), t_rm);
|
||||
for (int i=0;i<FB_WORDS;i++) refmap_ep[e][i]=t_rm[i];
|
||||
end
|
||||
$readmemh("../../data/top_psmct32_raster_demo/sh3_zs640c12_refmap.mem", refmap);
|
||||
$display("[sched] ONLY=%s (only_k=%0d), N_EPOCHS=%0d", only_m, only_k, NEP);
|
||||
if (idx[0][0]===32'bx) begin $display("[tb_top_psmct32_sh3_zs640c12_cap] SKIP — sh3_zs640c12*.mem absent (run gs_make_sh3_scheduler_fixture.py --authz --fb640 --tag zs640c12 --emit)"); $finish; end
|
||||
|
||||
rst_n=1'b0; core_go=1'b0; repeat(6) @(posedge clk); rst_n=1'b1; repeat(8) @(posedge clk);
|
||||
|
||||
// boot the bootlet (uploads ALL N relocated CLUTs to their distinct CBPs)
|
||||
@(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);
|
||||
wait (feeder_ready_tb==1'b1); repeat(50) @(posedge clk);
|
||||
wr_arm<=1'b1; repeat(40) @(posedge clk);
|
||||
|
||||
// ===== scheduler: iterate the epoch descriptors in dump order (or a single epoch for the isolation check) =====
|
||||
cap_fh = $fopen("zs640c12_frags.txt","w"); // Ch357 fragment capture for external persp_z5 verification
|
||||
tex_fh = $fopen("zs640c12_textrace.txt","w"); // sim-only texture lookup trace for perspective-color diagnosis
|
||||
issue_fh = $fopen("zs640c12_issue.txt","w"); // sim-only perspective divide output trace
|
||||
if (only_k<0) begin
|
||||
for (int k=0;k<NEP;k++) begin
|
||||
cap_ep = k;
|
||||
run_epoch(k);
|
||||
if (k<NEP-1 && scan_en!==1'b0) begin $error("[sched] scanout enabled before the last epoch"); errors++; end
|
||||
end
|
||||
end else begin
|
||||
run_epoch(only_k);
|
||||
end
|
||||
|
||||
// enable scanout only after the final fresh drain
|
||||
scan_en<=1'b1; repeat(20) @(posedge clk);
|
||||
if (only_k<0) begin
|
||||
if (fd_rises<NEP) begin $error("[sched] expected %0d ordered drains: rises=%0d", NEP, fd_rises); errors++; end
|
||||
if (fd_falls<NEP-1)begin $error("[sched] epochs after the first never cleared stale drains (falls=%0d)", fd_falls); errors++; end
|
||||
end else if (fd_rises<1) begin $error("[sched] isolated epoch did not drain: rises=%0d", fd_rises); errors++; end
|
||||
// Ch359 — the WHOLE run must perform exactly ONE verified texture fill (epoch 1 reuses the resident cache)
|
||||
if (fills!==1) begin $error("[sched] expected exactly ONE texture fill, got %0d (residency contract)", fills); errors++; end
|
||||
|
||||
// ===== scanout: score final FB vs the composed reference (per-pixel OWNER epoch), overlap separately =====
|
||||
vid_run=1;
|
||||
begin int d=0; while(!vsync && d<300000) begin @(posedge video_clk); d++; end end
|
||||
@(posedge video_clk); while(!vsync) @(posedge video_clk);
|
||||
begin scoring=1; @(posedge video_clk); while(!vsync) @(posedge video_clk); scoring=0; repeat(60) @(posedge emif_clk);
|
||||
if (fb_reads_last!==BEATS_PER_FRAME) begin $error("[sched] scanout beats/frame=%0d exp %0d", fb_reads_last, BEATS_PER_FRAME); errors++; end
|
||||
end
|
||||
if (so_underflow!==0) begin $error("[sched] scanout underflow"); errors++; end
|
||||
if (so_rd_errs !==0) begin $error("[sched] scanout rd_errs=%0d", so_rd_errs); errors++; end
|
||||
if (checked < H_ACT*V_ACT) begin $error("[sched] only %0d px checked (exp >= %0d)", checked, H_ACT*V_ACT); errors++; end
|
||||
|
||||
// oracle (multi-texture correctness): a covered pixel must equal ONE of the COVERING epochs' texels. Each epoch
|
||||
// stores its OWN (tu,tv) per pixel in refmap_ep[k]; we accept if the RTL colour matches any covering epoch's texel
|
||||
// in the <=1-texel neighbourhood (owner-first, then neighbours — handles coverage-edge owner ambiguity between
|
||||
// adjacent-order epochs, exactly the tolerance Ch355 used). multi-epoch pixels (>=2 covering) scored separately as
|
||||
// the accumulation composite proof. clut_bad = an RTL-written pixel (ideal!=0) whose colour is in NO epoch palette
|
||||
// (palettes are pairwise-distinct, so this still proves the pixel came from a real texture+CLUT, not garbage).
|
||||
begin
|
||||
int m_tot,m_ok,o_tot,o_ok,cb,D,ncov; bit mt; m_tot=0;m_ok=0;o_tot=0;o_ok=0;cb=0;
|
||||
for (int y=0;y<H;y++) for (int x=0;x<W;x++) begin
|
||||
int o; logic [23:0] fbc; bit cov; o=y*W+x;
|
||||
cov = (only_k<0) ? refmap[o][31] : refmap_ep[only_k][o][31];
|
||||
if (cov) begin
|
||||
fbc=ideal[o][23:0]; m_tot++; D=9; ncov=0;
|
||||
for (int e=0;e<NEP;e++) begin
|
||||
if (!(only_k>=0 && e!=only_k) && refmap_ep[e][o][31]) begin
|
||||
int tu,tv; tu=(refmap_ep[e][o]>>9)&9'h1FF; tv=refmap_ep[e][o]&9'h1FF; ncov++;
|
||||
for (int rad=0;rad<=1;rad++) for (int du=-rad;du<=rad;du++) for (int dv=-rad;dv<=rad;dv++) begin
|
||||
int ch; ch=(du<0?-du:du); if((dv<0?-dv:dv)>ch) ch=(dv<0?-dv:dv);
|
||||
if (ch==rad && (tu+du)>=0 && (tu+du)<TW && (tv+dv)>=0 && (tv+dv)<TH) begin
|
||||
mt=(fbc===cell_e(e,tu+du,tv+dv)); if (mt && rad<D) D=rad;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if (D<=1) m_ok++;
|
||||
if (only_k<0 && refmap[o][28]) begin o_tot++; if (D<=1) o_ok++; end // multi-epoch (>=2 covering)
|
||||
if (ideal[o]!==32'd0) begin bit f; f=1'b0;
|
||||
for (int e=0;e<NEP;e++) for (int i=0;i<256;i++) if (pal[e][i][23:0]===fbc) f=1'b1;
|
||||
if(!f) cb++; end
|
||||
end
|
||||
end
|
||||
$display("[sched][oracle] ONLY=%s <=1texel ALL=%0d/%0d (%.1f%%) MULTI(>=2)=%0d/%0d (%.1f%%) clut_bad=%0d",
|
||||
only_m, m_ok,m_tot,(m_tot>0)?100.0*m_ok/m_tot:0.0, o_ok,o_tot,(o_tot>0)?100.0*o_ok/o_tot:0.0, cb);
|
||||
if (cb!==0) begin $error("[sched][oracle] ONLY=%s: %0d covered px in NO epoch palette", only_m, cb); errors++; end
|
||||
if (only_k<0) begin
|
||||
if (o_tot<500) begin $error("[sched][oracle] only %0d multi-epoch px — accumulation not exercised", o_tot); errors++; end
|
||||
// MULTI <=1texel is TEXTURE FIDELITY (RTL 11-bit reciprocal LUT precision), NOT accumulation correctness.
|
||||
// It tracks the per-epoch isolated fidelity (ONLY=k gives 93.0/93.3/95.6%); the multi-epoch subset sits at
|
||||
// the same reciprocal floor (~92%). The ACCUMULATION proof is separate and EXACT: compose_sched.py shows
|
||||
// the joint ALL render == the composited ONLY=k isolation dumps 100% BIT-FOR-BIT. So gate this at the
|
||||
// documented reciprocal floor, not an accumulation-implying bar.
|
||||
if (o_tot>0 && (100.0*o_ok/o_tot)<90.0) begin $error("[sched][oracle] MULTI <=1texel %.1f%% < 90%% reciprocal floor", 100.0*o_ok/o_tot); errors++; end
|
||||
end
|
||||
end
|
||||
// FB dump for the composition==isolated check (per-epoch dumps composed externally -> compare vs ALL dump)
|
||||
if ($value$plusargs("FBDUMP=%s", fdump)) begin
|
||||
int fh; fh=$fopen(fdump,"w");
|
||||
for (int y=0;y<H;y++) for (int x=0;x<W;x++) begin int aa; aa=y*STRIDE+x*4; $fwrite(fh,"%08x\n",{fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]}); end
|
||||
$fclose(fh); $display("[sched] dumped RTL FB (%0dx%0d) -> %s", W, H, fdump);
|
||||
end
|
||||
|
||||
$display("[tb_top_psmct32_sh3_zs640c12_cap] checked=%0d beats/frame=%0d (exp %0d) fresh_drains(rise/fall)=%0d/%0d records=%0d underflow=%0b ovf=%0d errors=%0d",
|
||||
checked, fb_reads_last, BEATS_PER_FRAME, fd_rises, fd_falls, feeder_records_w, so_underflow, fbw_ovf, errors);
|
||||
if (cap_fh!=0) $fclose(cap_fh);
|
||||
if (tex_fh!=0) $fclose(tex_fh);
|
||||
if (issue_fh!=0) $fclose(issue_fh);
|
||||
if (errors==0) $display("[tb_top_psmct32_sh3_zs640c12_cap] PASS"); else $display("[tb_top_psmct32_sh3_zs640c12_cap] FAIL");
|
||||
$finish;
|
||||
end
|
||||
initial begin #320000000; $error("[tb_top_psmct32_sh3_zs640c12_cap] TIMEOUT"); $finish; end
|
||||
endmodule : tb_top_psmct32_sh3_zs640c12_cap
|
||||
@@ -0,0 +1,461 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_sh3_zs640c6_cap (Ch358 — authentic-Z capture at NATIVE 640x480, strong-reject scene)
|
||||
//
|
||||
// Generalizes Ch355's two-group flow to N authentic SH3 draw epochs, each with a DIFFERENT TEX0/CLUT, accumulating
|
||||
// into ONE LPDDR framebuffer via a data-driven scheduler over epoch descriptors (sh3_zs640c6_params.vh). Default
|
||||
// N_EPOCHS=3 strong-reject trio: E0=idx8634 E1=idx12757 E2=idx145742 at AUTHENTIC native coords x[407..619] y[97..306].
|
||||
// Also captures the fragment stream {epoch,x,y,persp_z,color} -> zs640c6_frags.txt. Proves the Ch356 integration acceptance:
|
||||
// * preclear EXACTLY once.
|
||||
// * N FRESH cache fills, each fill_done low->high, expected beats/bytes, 0 read errors, epoch CRC (EPk_CRC).
|
||||
// * N lists STREAMED through the feeder staging WRITE PORT (feeder_stg_we/waddr/wdata) — NOT the $readmemh backdoor
|
||||
// (closes the Ch355 sim gap where the write-port sequencing was untested). Each streams the full STG_WORDS so
|
||||
// staging is fully reset per epoch (no stale words), and word0 (the ntris header) is exercised first.
|
||||
// * N FRESH ORDERED drains: epoch 0 low->high (first render); epochs 1..N-1 high->low->high (stale cleared).
|
||||
// * scanout stays disabled until the LAST fresh drain; exact BEATS_PER_FRAME (=ROW_BEATS*H).
|
||||
// * final FB scored vs the independent composed reference using the PER-PIXEL OWNER epoch (exact palette per owner);
|
||||
// multi-epoch (>=2) overlap scored separately as the accumulation proof.
|
||||
// * +ONLY=<k> renders a SINGLE epoch (+FBDUMP dumps its FB) for the composition==isolated bit-for-bit check.
|
||||
// LOCAL/gitignored fixtures; skip-guard if absent.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_sh3_zs640c6_cap;
|
||||
`include "sh3_zs640c6_params.vh" // FBPXW=640, FBH=480, N_EPOCHS=3, EPk_CRC/EPk_NTRIS/EPk_CBP, TEX_*, ...
|
||||
localparam int W = FBPXW, H = FBH;
|
||||
localparam int STRIDE = W*4; // 1536
|
||||
localparam int ROW_BEATS = STRIDE/32; // 48
|
||||
localparam int FB_BYTES = STRIDE*H; // 585216
|
||||
localparam int FB_WORDS = W*H;
|
||||
localparam int BEATS_PER_FRAME = ROW_BEATS*H; // 18288
|
||||
localparam int H_ACT=W, V_ACT=H, H_BP=32, H_FP=8, V_BP=16, V_FP=8;
|
||||
localparam int H_TOT=H_BP+H_ACT+H_FP, V_TOT=V_BP+V_ACT+V_FP;
|
||||
localparam int NEP = N_EPOCHS; // 3
|
||||
|
||||
// per-epoch expected CRC / records (from the descriptor params)
|
||||
// Ch359 — TWO epochs sharing ONE texture: EPk_REUSE=1 means the epoch runs on the RESIDENT cache (no fill).
|
||||
logic [31:0] EP_CRC [0:1]; int EP_REC [0:1]; bit EP_REUSE [0:1]; int fills;
|
||||
initial begin
|
||||
EP_CRC[0]=EP0_CRC; EP_CRC[1]=EP1_CRC;
|
||||
EP_REC[0]=EP0_NTRIS; EP_REC[1]=EP1_NTRIS;
|
||||
EP_REUSE[0]=EP0_REUSE; EP_REUSE[1]=EP1_REUSE;
|
||||
fills=0;
|
||||
end
|
||||
|
||||
logic clk=0; always #5 clk=~clk;
|
||||
logic emif_clk=0; always #2 emif_clk=~emif_clk;
|
||||
logic video_clk=0; always #7 video_clk=~video_clk;
|
||||
logic rst_n; logic fb_commit=0; always #13 fb_commit=~fb_commit;
|
||||
int errors; initial errors=0;
|
||||
|
||||
// ===== bram-top (FB_LPDDR_ONLY, AUTOSTART off, STG 2048), H/V_ACTIVE = 384x381 =====
|
||||
logic core_go; logic [7:0] r,g,b; logic hsync,vsync_o,de;
|
||||
logic core_halt,dma_done_seen,frame_seen,raster_overflow,frame_toggle,dma_done_toggle;
|
||||
logic feeder_go_tb, feeder_ready_tb; logic [15:0] feeder_records_w; logic [31:0] feeder_waits_w;
|
||||
logic gs_tex_rd_en_o; logic [31:0] gs_tex_rd_addr_o; logic [31:0] tex_cache_data; logic tex_cache_ready;
|
||||
logic [31:0] tex_cache_hits, tex_bram_hits;
|
||||
logic flush_emit_w; logic [31:0] flush_addr_w; logic [31:0] flush_color32_w; logic [5:0] flush_psm_w;
|
||||
logic [11:0] flush_x_w, flush_y_w; logic [31:0] flush_z_w; // Ch357 — fragment coords + persp_z5
|
||||
int cap_ep; int cap_fh; int tex_fh; int issue_fh; // fragment capture (verify persp_z5 vs the clamp16 oracle)
|
||||
logic [10:0] cap_pu_q, cap_pv_q, cap_pu_qq, cap_pv_qq; // persp_u/v delayed from texture issue to flush/color
|
||||
logic [31:0] tex_issue_addr_q, tex_issue_raw_q; // debug: texture request that returns on the next cycle
|
||||
logic [10:0] tex_issue_u_q, tex_issue_v_q;
|
||||
// feeder staging WRITE PORT (streamed per epoch)
|
||||
logic stg_we; logic [11:0] stg_waddr; logic [63:0] stg_wdata;
|
||||
|
||||
top_psmct32_raster_demo_bram #(
|
||||
.H_ACTIVE(FBPXW), .V_ACTIVE(FBH),
|
||||
.VRAM_BYTES(VRAM_BYTES_P), .VRAM_ENABLE_READ2(1'b0), .PSMCT32_SWIZZLE(1'b0),
|
||||
.COMBINED_TAZ(1'b0), .TILE_LOCAL(1'b0), .TILE_COLS(1), .TILE_ROWS(1),
|
||||
.TILE_MULTIPRIM(1'b0), .TILE_PRIM_COUNT(1), .TILE_FIFO_DEPTH(8),
|
||||
.BIN_BUFFER_ENABLE(1'b0), .HEARTBEAT_SPLICE_ENABLE(1'b0),
|
||||
.FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(STG_WORDS), .FEEDER_AUTOSTART(1'b0),
|
||||
.PERSPECTIVE_CORRECT(1'b1), .PERSP_RECIP_IDX_BITS(11),
|
||||
.GRAD_SEQ_DIVIDER(1'b1), .GRAD_DIV_CYCLES(4),
|
||||
.GS_LPDDR_TEX(1'b1), .TEX_VRAM_BASE(TEX_VRAM_BASE), .TEX_CACHE_BYTES(TEX_BYTES),
|
||||
.CLUT_CSM1_ENABLE(1'b1), .FB_LPDDR_ONLY(1'b1)
|
||||
) dut (
|
||||
.clk(clk), .rst_n(rst_n), .core_go(core_go),
|
||||
.r(r), .g(g), .b(b), .hsync(hsync), .vsync(vsync_o), .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),
|
||||
.feeder_stg_we_i(stg_we), .feeder_stg_waddr_i(stg_waddr), .feeder_stg_wdata_i(stg_wdata),
|
||||
.feeder_go_i(feeder_go_tb), .feeder_ready_o(feeder_ready_tb),
|
||||
.feeder_records_o(feeder_records_w), .feeder_waits_o(feeder_waits_w),
|
||||
.flush_emit_o(flush_emit_w), .flush_addr_o(flush_addr_w),
|
||||
.flush_pix16_o(), .flush_color32_o(flush_color32_w), .flush_psm_o(flush_psm_w),
|
||||
.flush_x_o(flush_x_w), .flush_y_o(flush_y_w), .flush_z_o(flush_z_w),
|
||||
.gs_tex_rd_en_o(gs_tex_rd_en_o), .gs_tex_rd_addr_o(gs_tex_rd_addr_o),
|
||||
.tex_cache_data_i(tex_cache_data), .tex_cache_ready_i(tex_cache_ready),
|
||||
.tex_cache_hits_o(tex_cache_hits), .tex_bram_hits_o(tex_bram_hits)
|
||||
);
|
||||
|
||||
// Ch357 — capture every emitted fragment {epoch, x, y, persp_z, color}. An external Python check replays these through
|
||||
// the clamp16 persistent-Z model and compares owner/reject to the oracle -> validates persp_z5 end-to-end (the new RTL).
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst_n) begin
|
||||
if (dut.u_gs.persp_outvalid) begin
|
||||
cap_pu_qq <= cap_pu_q;
|
||||
cap_pv_qq <= cap_pv_q;
|
||||
cap_pu_q <= dut.u_gs.persp_u;
|
||||
cap_pv_q <= dut.u_gs.persp_v;
|
||||
end
|
||||
if (gs_tex_rd_en_o) begin
|
||||
tex_issue_addr_q <= gs_tex_rd_addr_o;
|
||||
tex_issue_raw_q <= dut.u_gs.u_tex.addr;
|
||||
tex_issue_u_q <= dut.u_gs.u_tex.u_eff;
|
||||
tex_issue_v_q <= dut.u_gs.u_tex.v_eff;
|
||||
end
|
||||
if (flush_emit_w && (flush_psm_w==6'h00) && cap_fh!=0)
|
||||
$fwrite(cap_fh, "%0d %0d %0d %0d %08x %0d %0d\n",
|
||||
cap_ep, flush_x_w, flush_y_w, flush_z_w, flush_color32_w, cap_pu_qq, cap_pv_qq);
|
||||
if (flush_emit_w && (flush_psm_w==6'h00) && tex_fh!=0)
|
||||
$fwrite(tex_fh, "%0d %0d %0d %0d %08x %08x %08x %0d %0d %08x %0d %02x %08x %08x %08x\n",
|
||||
cap_ep, flush_x_w, flush_y_w, flush_z_w, flush_color32_w,
|
||||
tex_issue_addr_q, tex_issue_raw_q, tex_issue_u_q, tex_issue_v_q,
|
||||
dut.u_gs.u_tex.tex_rd_data, dut.u_gs.u_tex.sel_lo, dut.u_gs.u_tex.clut_rd_idx,
|
||||
dut.u_gs.u_tex.clut_rd_data, dut.u_gs.u_tex.near_color, dut.u_gs.s1_tex_color);
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst_n) begin
|
||||
#1;
|
||||
if (dut.u_gs.persp_outvalid && issue_fh!=0)
|
||||
$fwrite(issue_fh, "%0d %0d %0d %0d %0d %0d %0d\n",
|
||||
cap_ep,
|
||||
dut.u_gs.g_persp_emit.u_persp_uv.uq_pipe[2],
|
||||
dut.u_gs.g_persp_emit.u_persp_uv.vq_pipe[2],
|
||||
dut.u_gs.g_persp_emit.u_persp_uv.w_recip,
|
||||
dut.u_gs.persp_u, dut.u_gs.persp_v,
|
||||
dut.u_gs.g_persp_emit.u_persp_uv.out_valid);
|
||||
end
|
||||
end
|
||||
|
||||
// texture cache + behavioral texture LPDDR (RELOADED between fills for each epoch's rebind)
|
||||
logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid; logic [7:0] arlen; logic [2:0] arsize;
|
||||
logic arvalid, arready; logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready;
|
||||
logic fill_start, fill_done; logic [31:0] fill_beats, fill_bytes, tex_rd_errs, fill_crc_w;
|
||||
gs_texture_cache #(.LPDDR_TEX_BASE(LPDDR_TEX_BASE), .TEX_VRAM_BASE(TEX_VRAM_BASE),
|
||||
.TEX_BYTES(TEX_BYTES), .N_BEATS(N_BEATS)) u_cache (
|
||||
.axi_clk(clk), .axi_rst_n(rst_n), .fill_start(fill_start), .fill_done(fill_done),
|
||||
.fill_beats(fill_beats), .fill_bytes(fill_bytes), .rd_errs(tex_rd_errs), .fill_crc(fill_crc_w),
|
||||
.araddr(araddr), .arburst(arburst), .arid(arid), .arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready),
|
||||
.rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready),
|
||||
.sample_clk(clk), .tex_rd_en(gs_tex_rd_en_o), .tex_rd_addr(gs_tex_rd_addr_o),
|
||||
.tex_rd_data(tex_cache_data), .tex_ready(tex_cache_ready)
|
||||
);
|
||||
logic [31:0] lpddr_mem [0:(TEX_BYTES/4)-1]; // reloaded per epoch: tex0, tex1, tex2
|
||||
typedef enum logic [1:0] { S_IDLE, S_WAIT, S_DATA } sstate_t; sstate_t sst; logic [3:0] tdly; int tbeat;
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin sst<=S_IDLE; arready<=0; rvalid<=0; rlast<=0; rresp<=0; rdata<=0; tdly<=0; end
|
||||
else begin arready<=0; rvalid<=0; rlast<=0;
|
||||
case (sst)
|
||||
S_IDLE: if (arvalid) begin arready<=1; tbeat<=(araddr-LPDDR_TEX_BASE)>>5; tdly<=4'd2; sst<=S_WAIT; end
|
||||
S_WAIT: if (tdly==0) sst<=S_DATA; else tdly<=tdly-1'b1;
|
||||
S_DATA: if (rready) begin for (int w=0;w<8;w++) rdata[w*32 +: 32]<=lpddr_mem[tbeat*8+w];
|
||||
rresp<=2'b00; rvalid<=1; rlast<=1; sst<=S_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// render epoch (per scene) + PSMCT32 writer + precleared LPDDR FB
|
||||
logic fb_flush=0, render_inflight=0, feeder_ready_q=0; int eof_count;
|
||||
wire feeder_ready_rise = feeder_ready_tb && !feeder_ready_q;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin render_inflight<=0; feeder_ready_q<=0; fb_flush<=0; eof_count<=0; end
|
||||
else begin feeder_ready_q<=feeder_ready_tb; fb_flush<=1'b0;
|
||||
if (feeder_go_tb && feeder_ready_tb) render_inflight<=1'b1;
|
||||
if (render_inflight && feeder_ready_rise) begin fb_flush<=1'b1; render_inflight<=1'b0; eof_count<=eof_count+1; end
|
||||
end
|
||||
end
|
||||
logic wr_arm=0; logic [255:0] fbw_wdata; logic [31:0] fbw_wstrb, fbw_awaddr; logic fbw_awvalid, fbw_wvalid;
|
||||
logic [31:0] fbw_beats, fbw_ovf, fbw_bresp_err; logic fbw_idle, fbw_drained;
|
||||
gs_lpddr_axi_master #(.FIFO_DEPTH(64), .PIX_BYTES(4)) u_wr (
|
||||
.gs_clk(clk), .gs_rst_n(rst_n), .enable(1'b1),
|
||||
.arm(wr_arm), .canary(1'b0), .fb_base(32'h0), .ctrl_commit(fb_commit),
|
||||
.px_emit(flush_emit_w && (flush_psm_w==6'h00)), .px_addr(flush_addr_w), .px_pix32(flush_color32_w), .flush(fb_flush),
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n),
|
||||
.awaddr(fbw_awaddr), .awlen(), .awsize(), .awburst(), .awid(), .awvalid(fbw_awvalid), .awready(1'b1),
|
||||
.wdata(fbw_wdata), .wstrb(fbw_wstrb), .wlast(), .wvalid(fbw_wvalid), .wready(1'b1),
|
||||
.bvalid(1'b1), .bready(), .bresp(2'b00),
|
||||
.beats_written(fbw_beats), .bursts_issued(), .bresp_err_count(fbw_bresp_err),
|
||||
.fifo_overflow_count(fbw_ovf), .idle(fbw_idle), .frame_drained(fbw_drained)
|
||||
);
|
||||
logic [7:0] fb [0:FB_BYTES-1]; logic [31:0] fb_awlat;
|
||||
always_ff @(posedge emif_clk) begin
|
||||
if (fbw_awvalid) fb_awlat<=fbw_awaddr;
|
||||
if (fbw_wvalid) for (int i=0;i<32;i++) if (fbw_wstrb[i]) begin
|
||||
int aa; aa=fb_awlat+i; if (aa>=0 && aa<FB_BYTES) fb[aa]<=fbw_wdata[i*8 +: 8]; end
|
||||
end
|
||||
logic [31:0] ideal [0:FB_WORDS-1];
|
||||
always_ff @(posedge clk) if (rst_n && flush_emit_w && (flush_psm_w==6'h00)) ideal[flush_addr_w>>2]<=flush_color32_w;
|
||||
|
||||
// FRESH-DRAIN observer: count frame_drained low->high and high->low edges (emif domain).
|
||||
logic fd_q; int fd_rises, fd_falls;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin fd_q<=0; fd_rises<=0; fd_falls<=0; end
|
||||
else begin fd_q<=fbw_drained;
|
||||
if (fbw_drained && !fd_q) fd_rises<=fd_rises+1;
|
||||
if (!fbw_drained && fd_q) fd_falls<=fd_falls+1;
|
||||
end
|
||||
end
|
||||
|
||||
// scanout (384x381), host-gated: enable = scan_en (video_src) AND frame_drained
|
||||
logic scan_en=0; wire so_enable = scan_en & fbw_drained;
|
||||
logic [11:0] px, py; logic vsync, in_win; logic [7:0] so_r, so_g, so_b;
|
||||
logic so_underflow; logic [31:0] so_rd_errs; logic so_line_valid;
|
||||
logic [29:0] so_araddr; logic [1:0] so_arburst; logic [6:0] so_arid; logic [7:0] so_arlen;
|
||||
logic [2:0] so_arsize; logic so_arvalid, so_arready;
|
||||
logic [255:0] so_rdata; logic [1:0] so_rresp; logic so_rlast, so_rvalid, so_rready;
|
||||
gs_lpddr_scanout_lb #(.FB_BASE(30'd0), .STRIDE_BYTES(STRIDE), .ROW_BEATS(ROW_BEATS),
|
||||
.N_ROWS(H), .PSMCT32(1'b1)) u_scan (
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n), .enable(so_enable),
|
||||
.video_clk(video_clk), .frame_start(vsync), .pixel_x(px), .pixel_y(py), .in_window(in_win),
|
||||
.r(so_r), .g(so_g), .b(so_b), .line_valid(so_line_valid), .underflow(so_underflow), .rd_errs(so_rd_errs),
|
||||
.araddr(so_araddr), .arburst(so_arburst), .arid(so_arid), .arlen(so_arlen), .arsize(so_arsize),
|
||||
.arvalid(so_arvalid), .arready(so_arready), .rdata(so_rdata), .rresp(so_rresp),
|
||||
.rlast(so_rlast), .rvalid(so_rvalid), .rready(so_rready)
|
||||
);
|
||||
logic [7:0] rlfsr=8'h3C; always_ff @(posedge emif_clk) rlfsr<={rlfsr[6:0], rlfsr[7]^rlfsr[5]^rlfsr[4]^rlfsr[3]};
|
||||
typedef enum logic [1:0] { R_IDLE, R_WAIT, R_DATA } rst_t; rst_t rst_state;
|
||||
logic [3:0] rdly; logic [29:0] rd_addr_l; int read_beats;
|
||||
logic [2:0] vs_e; wire vs_edge_e = vs_e[1] && !vs_e[2]; int fb_reads, fb_reads_last;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin rst_state<=R_IDLE; so_arready<=0; so_rvalid<=0; so_rlast<=0; so_rresp<=0; so_rdata<=0; rdly<=0;
|
||||
read_beats<=0; vs_e<=0; fb_reads<=0; fb_reads_last<=0; end
|
||||
else begin so_arready<=0; so_rvalid<=0; so_rlast<=0; vs_e<={vs_e[1:0], vsync};
|
||||
if (vs_edge_e) begin fb_reads_last<=fb_reads; fb_reads<=0; end
|
||||
case (rst_state)
|
||||
R_IDLE: if (so_arvalid) begin so_arready<=1; rd_addr_l<=so_araddr; rdly<=rlfsr[2:0]; rst_state<=R_WAIT; end
|
||||
R_WAIT: if (rdly==0) rst_state<=R_DATA; else rdly<=rdly-1'b1;
|
||||
R_DATA: if (so_rready) begin
|
||||
for (int w=0;w<8;w++) begin int aa; aa=rd_addr_l+w*4;
|
||||
so_rdata[w*32 +: 32] <= (aa+3<FB_BYTES) ? {fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]} : 32'd0; end
|
||||
so_rresp<=2'b00; so_rvalid<=1; so_rlast<=1; read_beats<=read_beats+1;
|
||||
if (!vs_edge_e) fb_reads<=fb_reads+1; rst_state<=R_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
logic vid_run=0; logic [11:0] rawx, rawy;
|
||||
always_ff @(posedge video_clk) begin
|
||||
if (!vid_run) begin rawx<=0; rawy<=0; end
|
||||
else if (rawx==H_TOT-1) begin rawx<=0; rawy<=(rawy==V_TOT-1)?12'd0:rawy+1'b1; end
|
||||
else rawx<=rawx+1'b1;
|
||||
end
|
||||
wire active = (rawx>=H_BP)&&(rawx<H_BP+H_ACT)&&(rawy>=V_BP)&&(rawy<V_BP+V_ACT);
|
||||
assign px=active?(rawx-H_BP):12'd0; assign py=(rawy>=V_BP&&rawy<V_BP+V_ACT)?(rawy-V_BP):12'd0;
|
||||
assign in_win=active; assign vsync=vid_run&&(rawx==0)&&(rawy==0);
|
||||
logic [11:0] px_q, py_q; logic inwin_q, run_q;
|
||||
always_ff @(posedge video_clk) begin px_q<=px; py_q<=py; inwin_q<=in_win; run_q<=vid_run; end
|
||||
int checked; initial checked=0; logic scoring=0;
|
||||
always_ff @(posedge video_clk) if (scoring && run_q) begin
|
||||
logic [7:0] er,eg,eb; logic [31:0] w;
|
||||
if (inwin_q) begin int aa; aa=py_q*STRIDE+px_q*4; w={fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]}; er=w[7:0]; eg=w[15:8]; eb=w[23:16]; end
|
||||
else begin er=0; eg=0; eb=0; end
|
||||
checked++;
|
||||
if (so_r!==er||so_g!==eg||so_b!==eb) begin
|
||||
if (errors<12) $error("[scan] px(%0d,%0d) got(%02x,%02x,%02x) exp(%02x,%02x,%02x)", px_q,py_q, so_r,so_g,so_b, er,eg,eb); errors++; end
|
||||
end
|
||||
|
||||
// per-epoch idx/pal (for the oracle) + composed reference (owner epoch in [26:24], multi in [28]) + per-epoch refmaps
|
||||
logic [31:0] idx [0:2][0:(512*512/4)-1]; logic [31:0] pal [0:2][0:255];
|
||||
logic [31:0] refmap [0:FB_WORDS-1]; logic [31:0] refmap_ep [0:2][0:FB_WORDS-1];
|
||||
logic [63:0] stg_buf [0:STG_WORDS-1]; // one epoch's staging list, streamed through the write port
|
||||
// temps: iverilog can't $readmemh into a 2D-array slice, so load flat then copy into the [e] plane
|
||||
logic [31:0] t_idx [0:(512*512/4)-1]; logic [31:0] t_pal [0:255]; logic [31:0] t_rm [0:FB_WORDS-1];
|
||||
|
||||
// ---- stream one epoch's list into the feeder staging via the WRITE PORT (word0 first, full STG reset) ----
|
||||
task automatic stream_list(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/feeder_sh3_zs640c6%0d.mem", k);
|
||||
for (int i=0;i<STG_WORDS;i++) stg_buf[i]=64'd0;
|
||||
$readmemh(fn, stg_buf);
|
||||
@(negedge clk);
|
||||
for (int i=0;i<STG_WORDS;i++) begin
|
||||
stg_we<=1'b1; stg_waddr<=i[11:0]; stg_wdata<=stg_buf[i]; @(negedge clk);
|
||||
end
|
||||
stg_we<=1'b0; stg_waddr<=12'd0; stg_wdata<=64'd0; @(negedge clk);
|
||||
// sanity: header word0 (ntris) landed correctly in the DUT staging
|
||||
if (dut.g_feeder.feeder_stg[0][31:0] !== stg_buf[0][31:0]) begin
|
||||
$error("[sched] epoch %0d: staged word0=%08x exp %08x (write-port mis-slot)", k, dut.g_feeder.feeder_stg[0][31:0], stg_buf[0][31:0]); errors++; end
|
||||
$display("[sched] epoch %0d: streamed %0d words via write port; word0(ntris)=%0d", k, STG_WORDS, dut.g_feeder.feeder_stg[0][31:0]);
|
||||
endtask
|
||||
|
||||
// ---- one-scene runner: GO, wait render+drain, REQUIRE fresh frame_drained (high->low->high, or low->high on first) ----
|
||||
task automatic run_scene(input int k, input int exp_records);
|
||||
int r0, f0, d=0; logic fd_before;
|
||||
r0=fd_rises; f0=fd_falls; fd_before=fbw_drained;
|
||||
@(negedge clk); feeder_go_tb=1'b1; @(negedge clk); feeder_go_tb=1'b0;
|
||||
d=0; while (feeder_ready_tb && d<4000) begin @(posedge clk); d++; end
|
||||
if (feeder_ready_tb!==1'b0) begin $error("[sched] epoch %0d: feeder_ready did not drop after GO", k); errors++; end
|
||||
if (fd_before) begin
|
||||
d=0; while (fd_falls==f0 && d<4000000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_falls==f0) begin $error("[sched] epoch %0d: frame_drained never fell from stale high — STALE drain", k); errors++; end
|
||||
end
|
||||
d=0; while (fd_rises<=r0 && d<4000000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_rises<=r0) begin $error("[sched] epoch %0d: frame_drained never rose — scene did not drain", k); errors++; end
|
||||
repeat(100) @(posedge clk);
|
||||
if (feeder_records_w!==exp_records) begin $error("[sched] epoch %0d: records_emitted=%0d exp %0d", k, feeder_records_w, exp_records); errors++; end
|
||||
if (fbw_ovf!==0 || fbw_bresp_err!==0) begin $error("[sched] epoch %0d: writer ovf=%0d bresp=%0d", k, fbw_ovf, fbw_bresp_err); errors++; end
|
||||
$display("[sched] epoch %0d: fresh drain (falls %0d->%0d, rises %0d->%0d), records=%0d, ovf=0", k, f0, fd_falls, r0, fd_rises, feeder_records_w);
|
||||
endtask
|
||||
|
||||
task automatic fill_cache(input int k, input logic [31:0] exp_crc);
|
||||
int d=0;
|
||||
fills=fills+1;
|
||||
@(posedge clk) fill_start<=1'b1; @(posedge clk) fill_start<=1'b0;
|
||||
d=0; while (fill_done && d<1000) begin @(posedge clk); d++; end // wait it drops (busy)
|
||||
d=0; while (!fill_done && d<400000) begin @(posedge clk); d++; end // then rises (done)
|
||||
if (!fill_done) begin $error("[sched] fill %0d: fill_done never rose (rearm failed)", k); errors++; end
|
||||
if (fill_crc_w!==exp_crc) begin $error("[sched] fill %0d: crc=%08x exp %08x", k, fill_crc_w, exp_crc); errors++; end
|
||||
if (fill_beats!==N_BEATS) begin $error("[sched] fill %0d: beats=%0d exp %0d", k, fill_beats, N_BEATS); errors++; end
|
||||
if (fill_bytes!==TEX_BYTES) begin $error("[sched] fill %0d: bytes=%0d exp %0d", k, fill_bytes, TEX_BYTES); errors++; end
|
||||
if (tex_rd_errs!==0) begin $error("[sched] fill %0d: rd_errs=%0d", k, tex_rd_errs); errors++; end
|
||||
$display("[sched] cache fill %0d: fresh done, crc=0x%08x (exp %08x), beats=%0d bytes=%0d rd_errs=0", k, fill_crc_w, exp_crc, fill_beats, fill_bytes);
|
||||
endtask
|
||||
|
||||
// texel lookup in epoch e's palette (module-level arrays, no per-call copy)
|
||||
function automatic logic [23:0] cell_e(input int e, input integer u, input integer v);
|
||||
integer lin; logic [31:0] w; logic [7:0] ix;
|
||||
lin=v*TW+u; w=idx[e][lin/4]; ix=w[(8*(lin%4)) +: 8]; cell_e=pal[e][ix][23:0];
|
||||
endfunction
|
||||
|
||||
// run ONE epoch fully (rebind tex -> fresh fill+CRC -> stream list -> GO -> fresh ordered drain)
|
||||
task automatic run_epoch(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/sh3_zs640c6%0d_tex_lpddr.mem", k);
|
||||
if (!EP_REUSE[k] || only_k>=0) begin // isolation (+ONLY=k) always fills — nothing resident
|
||||
$readmemh(fn, lpddr_mem);
|
||||
fill_cache(k, EP_CRC[k]);
|
||||
end else begin
|
||||
// Ch359 — EXPLICIT REUSE: no reload, no fill pulse. Fail-closed residency: the fill CRC register is
|
||||
// only rewritten by a fill, so it must still hold the prior epoch's verified CRC (same shared texture).
|
||||
if (fill_crc_w!==EP_CRC[k]) begin $error("%s epoch %0d: REUSE but resident crc=%08x exp %08x — residency broken", "[sched]", k, fill_crc_w, EP_CRC[k]); errors++; end
|
||||
else $display("%s epoch %0d: REUSE resident texture (crc=0x%08x, no fill)", "[sched]", k, fill_crc_w);
|
||||
end
|
||||
stream_list(k);
|
||||
run_scene(k, EP_REC[k]);
|
||||
endtask
|
||||
|
||||
string only_m; string fdump; int only_k; // +ONLY=ALL | +ONLY=<k> ; +FBDUMP=<file>
|
||||
initial begin
|
||||
errors=0; feeder_go_tb=1'b0; fill_start=1'b0; wr_arm=0; scan_en=0; stg_we=0; stg_waddr=0; stg_wdata=0; cap_fh=0; tex_fh=0; issue_fh=0; cap_ep=0;
|
||||
tex_issue_addr_q=0; tex_issue_raw_q=0; tex_issue_u_q=0; tex_issue_v_q=0;
|
||||
if (!$value$plusargs("ONLY=%s", only_m)) only_m="ALL";
|
||||
only_k = (only_m=="ALL") ? -1 : only_m.atoi();
|
||||
for (int i=0;i<FB_WORDS;i++) ideal[i]=32'd0;
|
||||
for (int i=0;i<FB_BYTES;i++) fb[i]=8'h00; // PRECLEAR EXACTLY ONCE
|
||||
idx[0][0]='x;
|
||||
for (int e=0;e<NEP;e++) begin
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_zs640c6%0d_idx.mem", e), t_idx);
|
||||
for (int i=0;i<(512*512/4);i++) idx[e][i]=t_idx[i];
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_zs640c6%0d_pal.mem", e), t_pal);
|
||||
for (int i=0;i<256;i++) pal[e][i]=t_pal[i];
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_zs640c6%0d_refmap.mem", e), t_rm);
|
||||
for (int i=0;i<FB_WORDS;i++) refmap_ep[e][i]=t_rm[i];
|
||||
end
|
||||
$readmemh("../../data/top_psmct32_raster_demo/sh3_zs640c6_refmap.mem", refmap);
|
||||
$display("[sched] ONLY=%s (only_k=%0d), N_EPOCHS=%0d", only_m, only_k, NEP);
|
||||
if (idx[0][0]===32'bx) begin $display("[tb_top_psmct32_sh3_zs640c6_cap] SKIP — sh3_zs640c6*.mem absent (run gs_make_sh3_scheduler_fixture.py --authz --fb640 --tag zs640c6 --emit)"); $finish; end
|
||||
|
||||
rst_n=1'b0; core_go=1'b0; repeat(6) @(posedge clk); rst_n=1'b1; repeat(8) @(posedge clk);
|
||||
|
||||
// boot the bootlet (uploads ALL N relocated CLUTs to their distinct CBPs)
|
||||
@(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);
|
||||
wait (feeder_ready_tb==1'b1); repeat(50) @(posedge clk);
|
||||
wr_arm<=1'b1; repeat(40) @(posedge clk);
|
||||
|
||||
// ===== scheduler: iterate the epoch descriptors in dump order (or a single epoch for the isolation check) =====
|
||||
cap_fh = $fopen("zs640c6_frags.txt","w"); // Ch357 fragment capture for external persp_z5 verification
|
||||
tex_fh = $fopen("zs640c6_textrace.txt","w"); // sim-only texture lookup trace for perspective-color diagnosis
|
||||
issue_fh = $fopen("zs640c6_issue.txt","w"); // sim-only perspective divide output trace
|
||||
if (only_k<0) begin
|
||||
for (int k=0;k<NEP;k++) begin
|
||||
cap_ep = k;
|
||||
run_epoch(k);
|
||||
if (k<NEP-1 && scan_en!==1'b0) begin $error("[sched] scanout enabled before the last epoch"); errors++; end
|
||||
end
|
||||
end else begin
|
||||
run_epoch(only_k);
|
||||
end
|
||||
|
||||
// enable scanout only after the final fresh drain
|
||||
scan_en<=1'b1; repeat(20) @(posedge clk);
|
||||
if (only_k<0) begin
|
||||
if (fd_rises<NEP) begin $error("[sched] expected %0d ordered drains: rises=%0d", NEP, fd_rises); errors++; end
|
||||
if (fd_falls<NEP-1)begin $error("[sched] epochs after the first never cleared stale drains (falls=%0d)", fd_falls); errors++; end
|
||||
end else if (fd_rises<1) begin $error("[sched] isolated epoch did not drain: rises=%0d", fd_rises); errors++; end
|
||||
// Ch359 — the WHOLE run must perform exactly ONE verified texture fill (epoch 1 reuses the resident cache)
|
||||
if (fills!==1) begin $error("[sched] expected exactly ONE texture fill, got %0d (residency contract)", fills); errors++; end
|
||||
|
||||
// ===== scanout: score final FB vs the composed reference (per-pixel OWNER epoch), overlap separately =====
|
||||
vid_run=1;
|
||||
begin int d=0; while(!vsync && d<300000) begin @(posedge video_clk); d++; end end
|
||||
@(posedge video_clk); while(!vsync) @(posedge video_clk);
|
||||
begin scoring=1; @(posedge video_clk); while(!vsync) @(posedge video_clk); scoring=0; repeat(60) @(posedge emif_clk);
|
||||
if (fb_reads_last!==BEATS_PER_FRAME) begin $error("[sched] scanout beats/frame=%0d exp %0d", fb_reads_last, BEATS_PER_FRAME); errors++; end
|
||||
end
|
||||
if (so_underflow!==0) begin $error("[sched] scanout underflow"); errors++; end
|
||||
if (so_rd_errs !==0) begin $error("[sched] scanout rd_errs=%0d", so_rd_errs); errors++; end
|
||||
if (checked < H_ACT*V_ACT) begin $error("[sched] only %0d px checked (exp >= %0d)", checked, H_ACT*V_ACT); errors++; end
|
||||
|
||||
// oracle (multi-texture correctness): a covered pixel must equal ONE of the COVERING epochs' texels. Each epoch
|
||||
// stores its OWN (tu,tv) per pixel in refmap_ep[k]; we accept if the RTL colour matches any covering epoch's texel
|
||||
// in the <=1-texel neighbourhood (owner-first, then neighbours — handles coverage-edge owner ambiguity between
|
||||
// adjacent-order epochs, exactly the tolerance Ch355 used). multi-epoch pixels (>=2 covering) scored separately as
|
||||
// the accumulation composite proof. clut_bad = an RTL-written pixel (ideal!=0) whose colour is in NO epoch palette
|
||||
// (palettes are pairwise-distinct, so this still proves the pixel came from a real texture+CLUT, not garbage).
|
||||
begin
|
||||
int m_tot,m_ok,o_tot,o_ok,cb,D,ncov; bit mt; m_tot=0;m_ok=0;o_tot=0;o_ok=0;cb=0;
|
||||
for (int y=0;y<H;y++) for (int x=0;x<W;x++) begin
|
||||
int o; logic [23:0] fbc; bit cov; o=y*W+x;
|
||||
cov = (only_k<0) ? refmap[o][31] : refmap_ep[only_k][o][31];
|
||||
if (cov) begin
|
||||
fbc=ideal[o][23:0]; m_tot++; D=9; ncov=0;
|
||||
for (int e=0;e<NEP;e++) begin
|
||||
if (!(only_k>=0 && e!=only_k) && refmap_ep[e][o][31]) begin
|
||||
int tu,tv; tu=(refmap_ep[e][o]>>9)&9'h1FF; tv=refmap_ep[e][o]&9'h1FF; ncov++;
|
||||
for (int rad=0;rad<=1;rad++) for (int du=-rad;du<=rad;du++) for (int dv=-rad;dv<=rad;dv++) begin
|
||||
int ch; ch=(du<0?-du:du); if((dv<0?-dv:dv)>ch) ch=(dv<0?-dv:dv);
|
||||
if (ch==rad && (tu+du)>=0 && (tu+du)<TW && (tv+dv)>=0 && (tv+dv)<TH) begin
|
||||
mt=(fbc===cell_e(e,tu+du,tv+dv)); if (mt && rad<D) D=rad;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if (D<=1) m_ok++;
|
||||
if (only_k<0 && refmap[o][28]) begin o_tot++; if (D<=1) o_ok++; end // multi-epoch (>=2 covering)
|
||||
if (ideal[o]!==32'd0) begin bit f; f=1'b0;
|
||||
for (int e=0;e<NEP;e++) for (int i=0;i<256;i++) if (pal[e][i][23:0]===fbc) f=1'b1;
|
||||
if(!f) cb++; end
|
||||
end
|
||||
end
|
||||
$display("[sched][oracle] ONLY=%s <=1texel ALL=%0d/%0d (%.1f%%) MULTI(>=2)=%0d/%0d (%.1f%%) clut_bad=%0d",
|
||||
only_m, m_ok,m_tot,(m_tot>0)?100.0*m_ok/m_tot:0.0, o_ok,o_tot,(o_tot>0)?100.0*o_ok/o_tot:0.0, cb);
|
||||
if (cb!==0) begin $error("[sched][oracle] ONLY=%s: %0d covered px in NO epoch palette", only_m, cb); errors++; end
|
||||
if (only_k<0) begin
|
||||
if (o_tot<500) begin $error("[sched][oracle] only %0d multi-epoch px — accumulation not exercised", o_tot); errors++; end
|
||||
// MULTI <=1texel is TEXTURE FIDELITY (RTL 11-bit reciprocal LUT precision), NOT accumulation correctness.
|
||||
// It tracks the per-epoch isolated fidelity (ONLY=k gives 93.0/93.3/95.6%); the multi-epoch subset sits at
|
||||
// the same reciprocal floor (~92%). The ACCUMULATION proof is separate and EXACT: compose_sched.py shows
|
||||
// the joint ALL render == the composited ONLY=k isolation dumps 100% BIT-FOR-BIT. So gate this at the
|
||||
// documented reciprocal floor, not an accumulation-implying bar.
|
||||
if (o_tot>0 && (100.0*o_ok/o_tot)<90.0) begin $error("[sched][oracle] MULTI <=1texel %.1f%% < 90%% reciprocal floor", 100.0*o_ok/o_tot); errors++; end
|
||||
end
|
||||
end
|
||||
// FB dump for the composition==isolated check (per-epoch dumps composed externally -> compare vs ALL dump)
|
||||
if ($value$plusargs("FBDUMP=%s", fdump)) begin
|
||||
int fh; fh=$fopen(fdump,"w");
|
||||
for (int y=0;y<H;y++) for (int x=0;x<W;x++) begin int aa; aa=y*STRIDE+x*4; $fwrite(fh,"%08x\n",{fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]}); end
|
||||
$fclose(fh); $display("[sched] dumped RTL FB (%0dx%0d) -> %s", W, H, fdump);
|
||||
end
|
||||
|
||||
$display("[tb_top_psmct32_sh3_zs640c6_cap] checked=%0d beats/frame=%0d (exp %0d) fresh_drains(rise/fall)=%0d/%0d records=%0d underflow=%0b ovf=%0d errors=%0d",
|
||||
checked, fb_reads_last, BEATS_PER_FRAME, fd_rises, fd_falls, feeder_records_w, so_underflow, fbw_ovf, errors);
|
||||
if (cap_fh!=0) $fclose(cap_fh);
|
||||
if (tex_fh!=0) $fclose(tex_fh);
|
||||
if (issue_fh!=0) $fclose(issue_fh);
|
||||
if (errors==0) $display("[tb_top_psmct32_sh3_zs640c6_cap] PASS"); else $display("[tb_top_psmct32_sh3_zs640c6_cap] FAIL");
|
||||
$finish;
|
||||
end
|
||||
initial begin #320000000; $error("[tb_top_psmct32_sh3_zs640c6_cap] TIMEOUT"); $finish; end
|
||||
endmodule : tb_top_psmct32_sh3_zs640c6_cap
|
||||
@@ -0,0 +1,447 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_sh3_zsched (Ch356 — N-TEXTURE SCHEDULER, data-driven epoch descriptors)
|
||||
//
|
||||
// Generalizes Ch355's two-group flow to N authentic SH3 draw epochs, each with a DIFFERENT TEX0/CLUT, accumulating
|
||||
// into ONE LPDDR framebuffer via a data-driven scheduler over epoch descriptors (sh3_zsched_params.vh). Default
|
||||
// N_EPOCHS=3: E0=idx11671 E1=idx19562 E2=idx89761. Proves Codex's Ch356 integration acceptance:
|
||||
// * preclear EXACTLY once.
|
||||
// * N FRESH cache fills, each fill_done low->high, expected beats/bytes, 0 read errors, epoch CRC (EPk_CRC).
|
||||
// * N lists STREAMED through the feeder staging WRITE PORT (feeder_stg_we/waddr/wdata) — NOT the $readmemh backdoor
|
||||
// (closes the Ch355 sim gap where the write-port sequencing was untested). Each streams the full STG_WORDS so
|
||||
// staging is fully reset per epoch (no stale words), and word0 (the ntris header) is exercised first.
|
||||
// * N FRESH ORDERED drains: epoch 0 low->high (first render); epochs 1..N-1 high->low->high (stale cleared).
|
||||
// * scanout stays disabled until the LAST fresh drain; exact BEATS_PER_FRAME (=ROW_BEATS*H).
|
||||
// * final FB scored vs the independent composed reference using the PER-PIXEL OWNER epoch (exact palette per owner);
|
||||
// multi-epoch (>=2) overlap scored separately as the accumulation proof.
|
||||
// * +ONLY=<k> renders a SINGLE epoch (+FBDUMP dumps its FB) for the composition==isolated bit-for-bit check.
|
||||
// LOCAL/gitignored fixtures; skip-guard if absent.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_sh3_zsched;
|
||||
`include "sh3_zsched_params.vh" // FBPXW=384, FBH=381, N_EPOCHS=3, EPk_CRC/EPk_NTRIS/EPk_CBP, TEX_*, ...
|
||||
localparam int W = FBPXW, H = FBH;
|
||||
localparam int STRIDE = W*4; // 1536
|
||||
localparam int ROW_BEATS = STRIDE/32; // 48
|
||||
localparam int FB_BYTES = STRIDE*H; // 585216
|
||||
localparam int FB_WORDS = W*H;
|
||||
localparam int BEATS_PER_FRAME = ROW_BEATS*H; // 18288
|
||||
localparam int H_ACT=W, V_ACT=H, H_BP=32, H_FP=8, V_BP=16, V_FP=8;
|
||||
localparam int H_TOT=H_BP+H_ACT+H_FP, V_TOT=V_BP+V_ACT+V_FP;
|
||||
localparam int NEP = N_EPOCHS; // 3
|
||||
|
||||
// per-epoch expected CRC / records (from the descriptor params)
|
||||
logic [31:0] EP_CRC [0:2]; int EP_REC [0:2];
|
||||
initial begin
|
||||
EP_CRC[0]=EP0_CRC; EP_CRC[1]=EP1_CRC; EP_CRC[2]=EP2_CRC;
|
||||
EP_REC[0]=EP0_NTRIS; EP_REC[1]=EP1_NTRIS; EP_REC[2]=EP2_NTRIS;
|
||||
end
|
||||
|
||||
logic clk=0; always #5 clk=~clk;
|
||||
logic emif_clk=0; always #2 emif_clk=~emif_clk;
|
||||
logic video_clk=0; always #7 video_clk=~video_clk;
|
||||
logic rst_n; logic fb_commit=0; always #13 fb_commit=~fb_commit;
|
||||
int errors; initial errors=0;
|
||||
|
||||
// ===== bram-top (FB_LPDDR_ONLY, AUTOSTART off, STG 2048), H/V_ACTIVE = 384x381 =====
|
||||
logic core_go; logic [7:0] r,g,b; logic hsync,vsync_o,de;
|
||||
logic core_halt,dma_done_seen,frame_seen,raster_overflow,frame_toggle,dma_done_toggle;
|
||||
logic feeder_go_tb, feeder_ready_tb; logic [15:0] feeder_records_w; logic [31:0] feeder_waits_w;
|
||||
logic gs_tex_rd_en_o; logic [31:0] gs_tex_rd_addr_o; logic [31:0] tex_cache_data; logic tex_cache_ready;
|
||||
logic [31:0] tex_cache_hits, tex_bram_hits;
|
||||
logic flush_emit_w; logic [31:0] flush_addr_w; logic [31:0] flush_color32_w; logic [5:0] flush_psm_w;
|
||||
logic [11:0] flush_x_w, flush_y_w; logic [31:0] flush_z_w; // Ch357 — fragment coords + persp_z5
|
||||
int cap_ep; int cap_fh; int tex_fh; int issue_fh; // fragment capture (verify persp_z5 vs the clamp16 oracle)
|
||||
logic [10:0] cap_pu_q, cap_pv_q, cap_pu_qq, cap_pv_qq; // persp_u/v delayed from texture issue to flush/color
|
||||
logic [31:0] tex_issue_addr_q, tex_issue_raw_q; // debug: texture request that returns on the next cycle
|
||||
logic [10:0] tex_issue_u_q, tex_issue_v_q;
|
||||
// feeder staging WRITE PORT (streamed per epoch)
|
||||
logic stg_we; logic [11:0] stg_waddr; logic [63:0] stg_wdata;
|
||||
|
||||
top_psmct32_raster_demo_bram #(
|
||||
.H_ACTIVE(FBPXW), .V_ACTIVE(FBH),
|
||||
.VRAM_BYTES(VRAM_BYTES_P), .VRAM_ENABLE_READ2(1'b0), .PSMCT32_SWIZZLE(1'b0),
|
||||
.COMBINED_TAZ(1'b0), .TILE_LOCAL(1'b0), .TILE_COLS(1), .TILE_ROWS(1),
|
||||
.TILE_MULTIPRIM(1'b0), .TILE_PRIM_COUNT(1), .TILE_FIFO_DEPTH(8),
|
||||
.BIN_BUFFER_ENABLE(1'b0), .HEARTBEAT_SPLICE_ENABLE(1'b0),
|
||||
.FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(STG_WORDS), .FEEDER_AUTOSTART(1'b0),
|
||||
.PERSPECTIVE_CORRECT(1'b1), .PERSP_RECIP_IDX_BITS(11),
|
||||
.GRAD_SEQ_DIVIDER(1'b1), .GRAD_DIV_CYCLES(4),
|
||||
.GS_LPDDR_TEX(1'b1), .TEX_VRAM_BASE(TEX_VRAM_BASE), .TEX_CACHE_BYTES(TEX_BYTES),
|
||||
.CLUT_CSM1_ENABLE(1'b1), .FB_LPDDR_ONLY(1'b1)
|
||||
) dut (
|
||||
.clk(clk), .rst_n(rst_n), .core_go(core_go),
|
||||
.r(r), .g(g), .b(b), .hsync(hsync), .vsync(vsync_o), .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),
|
||||
.feeder_stg_we_i(stg_we), .feeder_stg_waddr_i(stg_waddr), .feeder_stg_wdata_i(stg_wdata),
|
||||
.feeder_go_i(feeder_go_tb), .feeder_ready_o(feeder_ready_tb),
|
||||
.feeder_records_o(feeder_records_w), .feeder_waits_o(feeder_waits_w),
|
||||
.flush_emit_o(flush_emit_w), .flush_addr_o(flush_addr_w),
|
||||
.flush_pix16_o(), .flush_color32_o(flush_color32_w), .flush_psm_o(flush_psm_w),
|
||||
.flush_x_o(flush_x_w), .flush_y_o(flush_y_w), .flush_z_o(flush_z_w),
|
||||
.gs_tex_rd_en_o(gs_tex_rd_en_o), .gs_tex_rd_addr_o(gs_tex_rd_addr_o),
|
||||
.tex_cache_data_i(tex_cache_data), .tex_cache_ready_i(tex_cache_ready),
|
||||
.tex_cache_hits_o(tex_cache_hits), .tex_bram_hits_o(tex_bram_hits)
|
||||
);
|
||||
|
||||
// Ch357 — capture every emitted fragment {epoch, x, y, persp_z, color}. An external Python check replays these through
|
||||
// the clamp16 persistent-Z model and compares owner/reject to the oracle -> validates persp_z5 end-to-end (the new RTL).
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst_n) begin
|
||||
if (dut.u_gs.persp_outvalid) begin
|
||||
cap_pu_qq <= cap_pu_q;
|
||||
cap_pv_qq <= cap_pv_q;
|
||||
cap_pu_q <= dut.u_gs.persp_u;
|
||||
cap_pv_q <= dut.u_gs.persp_v;
|
||||
end
|
||||
if (gs_tex_rd_en_o) begin
|
||||
tex_issue_addr_q <= gs_tex_rd_addr_o;
|
||||
tex_issue_raw_q <= dut.u_gs.u_tex.addr;
|
||||
tex_issue_u_q <= dut.u_gs.u_tex.u_eff;
|
||||
tex_issue_v_q <= dut.u_gs.u_tex.v_eff;
|
||||
end
|
||||
if (flush_emit_w && (flush_psm_w==6'h00) && cap_fh!=0)
|
||||
$fwrite(cap_fh, "%0d %0d %0d %0d %08x %0d %0d\n",
|
||||
cap_ep, flush_x_w, flush_y_w, flush_z_w, flush_color32_w, cap_pu_qq, cap_pv_qq);
|
||||
if (flush_emit_w && (flush_psm_w==6'h00) && tex_fh!=0)
|
||||
$fwrite(tex_fh, "%0d %0d %0d %0d %08x %08x %08x %0d %0d %08x %0d %02x %08x %08x %08x\n",
|
||||
cap_ep, flush_x_w, flush_y_w, flush_z_w, flush_color32_w,
|
||||
tex_issue_addr_q, tex_issue_raw_q, tex_issue_u_q, tex_issue_v_q,
|
||||
dut.u_gs.u_tex.tex_rd_data, dut.u_gs.u_tex.sel_lo, dut.u_gs.u_tex.clut_rd_idx,
|
||||
dut.u_gs.u_tex.clut_rd_data, dut.u_gs.u_tex.near_color, dut.u_gs.s1_tex_color);
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst_n) begin
|
||||
#1;
|
||||
if (dut.u_gs.persp_outvalid && issue_fh!=0)
|
||||
$fwrite(issue_fh, "%0d %0d %0d %0d %0d %0d %0d\n",
|
||||
cap_ep,
|
||||
dut.u_gs.g_persp_emit.u_persp_uv.uq_pipe[2],
|
||||
dut.u_gs.g_persp_emit.u_persp_uv.vq_pipe[2],
|
||||
dut.u_gs.g_persp_emit.u_persp_uv.w_recip,
|
||||
dut.u_gs.persp_u, dut.u_gs.persp_v,
|
||||
dut.u_gs.g_persp_emit.u_persp_uv.out_valid);
|
||||
end
|
||||
end
|
||||
|
||||
// texture cache + behavioral texture LPDDR (RELOADED between fills for each epoch's rebind)
|
||||
logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid; logic [7:0] arlen; logic [2:0] arsize;
|
||||
logic arvalid, arready; logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready;
|
||||
logic fill_start, fill_done; logic [31:0] fill_beats, fill_bytes, tex_rd_errs, fill_crc_w;
|
||||
gs_texture_cache #(.LPDDR_TEX_BASE(LPDDR_TEX_BASE), .TEX_VRAM_BASE(TEX_VRAM_BASE),
|
||||
.TEX_BYTES(TEX_BYTES), .N_BEATS(N_BEATS)) u_cache (
|
||||
.axi_clk(clk), .axi_rst_n(rst_n), .fill_start(fill_start), .fill_done(fill_done),
|
||||
.fill_beats(fill_beats), .fill_bytes(fill_bytes), .rd_errs(tex_rd_errs), .fill_crc(fill_crc_w),
|
||||
.araddr(araddr), .arburst(arburst), .arid(arid), .arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready),
|
||||
.rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready),
|
||||
.sample_clk(clk), .tex_rd_en(gs_tex_rd_en_o), .tex_rd_addr(gs_tex_rd_addr_o),
|
||||
.tex_rd_data(tex_cache_data), .tex_ready(tex_cache_ready)
|
||||
);
|
||||
logic [31:0] lpddr_mem [0:(TEX_BYTES/4)-1]; // reloaded per epoch: tex0, tex1, tex2
|
||||
typedef enum logic [1:0] { S_IDLE, S_WAIT, S_DATA } sstate_t; sstate_t sst; logic [3:0] tdly; int tbeat;
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin sst<=S_IDLE; arready<=0; rvalid<=0; rlast<=0; rresp<=0; rdata<=0; tdly<=0; end
|
||||
else begin arready<=0; rvalid<=0; rlast<=0;
|
||||
case (sst)
|
||||
S_IDLE: if (arvalid) begin arready<=1; tbeat<=(araddr-LPDDR_TEX_BASE)>>5; tdly<=4'd2; sst<=S_WAIT; end
|
||||
S_WAIT: if (tdly==0) sst<=S_DATA; else tdly<=tdly-1'b1;
|
||||
S_DATA: if (rready) begin for (int w=0;w<8;w++) rdata[w*32 +: 32]<=lpddr_mem[tbeat*8+w];
|
||||
rresp<=2'b00; rvalid<=1; rlast<=1; sst<=S_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// render epoch (per scene) + PSMCT32 writer + precleared LPDDR FB
|
||||
logic fb_flush=0, render_inflight=0, feeder_ready_q=0; int eof_count;
|
||||
wire feeder_ready_rise = feeder_ready_tb && !feeder_ready_q;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin render_inflight<=0; feeder_ready_q<=0; fb_flush<=0; eof_count<=0; end
|
||||
else begin feeder_ready_q<=feeder_ready_tb; fb_flush<=1'b0;
|
||||
if (feeder_go_tb && feeder_ready_tb) render_inflight<=1'b1;
|
||||
if (render_inflight && feeder_ready_rise) begin fb_flush<=1'b1; render_inflight<=1'b0; eof_count<=eof_count+1; end
|
||||
end
|
||||
end
|
||||
logic wr_arm=0; logic [255:0] fbw_wdata; logic [31:0] fbw_wstrb, fbw_awaddr; logic fbw_awvalid, fbw_wvalid;
|
||||
logic [31:0] fbw_beats, fbw_ovf, fbw_bresp_err; logic fbw_idle, fbw_drained;
|
||||
gs_lpddr_axi_master #(.FIFO_DEPTH(64), .PIX_BYTES(4)) u_wr (
|
||||
.gs_clk(clk), .gs_rst_n(rst_n), .enable(1'b1),
|
||||
.arm(wr_arm), .canary(1'b0), .fb_base(32'h0), .ctrl_commit(fb_commit),
|
||||
.px_emit(flush_emit_w && (flush_psm_w==6'h00)), .px_addr(flush_addr_w), .px_pix32(flush_color32_w), .flush(fb_flush),
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n),
|
||||
.awaddr(fbw_awaddr), .awlen(), .awsize(), .awburst(), .awid(), .awvalid(fbw_awvalid), .awready(1'b1),
|
||||
.wdata(fbw_wdata), .wstrb(fbw_wstrb), .wlast(), .wvalid(fbw_wvalid), .wready(1'b1),
|
||||
.bvalid(1'b1), .bready(), .bresp(2'b00),
|
||||
.beats_written(fbw_beats), .bursts_issued(), .bresp_err_count(fbw_bresp_err),
|
||||
.fifo_overflow_count(fbw_ovf), .idle(fbw_idle), .frame_drained(fbw_drained)
|
||||
);
|
||||
logic [7:0] fb [0:FB_BYTES-1]; logic [31:0] fb_awlat;
|
||||
always_ff @(posedge emif_clk) begin
|
||||
if (fbw_awvalid) fb_awlat<=fbw_awaddr;
|
||||
if (fbw_wvalid) for (int i=0;i<32;i++) if (fbw_wstrb[i]) begin
|
||||
int aa; aa=fb_awlat+i; if (aa>=0 && aa<FB_BYTES) fb[aa]<=fbw_wdata[i*8 +: 8]; end
|
||||
end
|
||||
logic [31:0] ideal [0:FB_WORDS-1];
|
||||
always_ff @(posedge clk) if (rst_n && flush_emit_w && (flush_psm_w==6'h00)) ideal[flush_addr_w>>2]<=flush_color32_w;
|
||||
|
||||
// FRESH-DRAIN observer: count frame_drained low->high and high->low edges (emif domain).
|
||||
logic fd_q; int fd_rises, fd_falls;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin fd_q<=0; fd_rises<=0; fd_falls<=0; end
|
||||
else begin fd_q<=fbw_drained;
|
||||
if (fbw_drained && !fd_q) fd_rises<=fd_rises+1;
|
||||
if (!fbw_drained && fd_q) fd_falls<=fd_falls+1;
|
||||
end
|
||||
end
|
||||
|
||||
// scanout (384x381), host-gated: enable = scan_en (video_src) AND frame_drained
|
||||
logic scan_en=0; wire so_enable = scan_en & fbw_drained;
|
||||
logic [11:0] px, py; logic vsync, in_win; logic [7:0] so_r, so_g, so_b;
|
||||
logic so_underflow; logic [31:0] so_rd_errs; logic so_line_valid;
|
||||
logic [29:0] so_araddr; logic [1:0] so_arburst; logic [6:0] so_arid; logic [7:0] so_arlen;
|
||||
logic [2:0] so_arsize; logic so_arvalid, so_arready;
|
||||
logic [255:0] so_rdata; logic [1:0] so_rresp; logic so_rlast, so_rvalid, so_rready;
|
||||
gs_lpddr_scanout_lb #(.FB_BASE(30'd0), .STRIDE_BYTES(STRIDE), .ROW_BEATS(ROW_BEATS),
|
||||
.N_ROWS(H), .PSMCT32(1'b1)) u_scan (
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n), .enable(so_enable),
|
||||
.video_clk(video_clk), .frame_start(vsync), .pixel_x(px), .pixel_y(py), .in_window(in_win),
|
||||
.r(so_r), .g(so_g), .b(so_b), .line_valid(so_line_valid), .underflow(so_underflow), .rd_errs(so_rd_errs),
|
||||
.araddr(so_araddr), .arburst(so_arburst), .arid(so_arid), .arlen(so_arlen), .arsize(so_arsize),
|
||||
.arvalid(so_arvalid), .arready(so_arready), .rdata(so_rdata), .rresp(so_rresp),
|
||||
.rlast(so_rlast), .rvalid(so_rvalid), .rready(so_rready)
|
||||
);
|
||||
logic [7:0] rlfsr=8'h3C; always_ff @(posedge emif_clk) rlfsr<={rlfsr[6:0], rlfsr[7]^rlfsr[5]^rlfsr[4]^rlfsr[3]};
|
||||
typedef enum logic [1:0] { R_IDLE, R_WAIT, R_DATA } rst_t; rst_t rst_state;
|
||||
logic [3:0] rdly; logic [29:0] rd_addr_l; int read_beats;
|
||||
logic [2:0] vs_e; wire vs_edge_e = vs_e[1] && !vs_e[2]; int fb_reads, fb_reads_last;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin rst_state<=R_IDLE; so_arready<=0; so_rvalid<=0; so_rlast<=0; so_rresp<=0; so_rdata<=0; rdly<=0;
|
||||
read_beats<=0; vs_e<=0; fb_reads<=0; fb_reads_last<=0; end
|
||||
else begin so_arready<=0; so_rvalid<=0; so_rlast<=0; vs_e<={vs_e[1:0], vsync};
|
||||
if (vs_edge_e) begin fb_reads_last<=fb_reads; fb_reads<=0; end
|
||||
case (rst_state)
|
||||
R_IDLE: if (so_arvalid) begin so_arready<=1; rd_addr_l<=so_araddr; rdly<=rlfsr[2:0]; rst_state<=R_WAIT; end
|
||||
R_WAIT: if (rdly==0) rst_state<=R_DATA; else rdly<=rdly-1'b1;
|
||||
R_DATA: if (so_rready) begin
|
||||
for (int w=0;w<8;w++) begin int aa; aa=rd_addr_l+w*4;
|
||||
so_rdata[w*32 +: 32] <= (aa+3<FB_BYTES) ? {fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]} : 32'd0; end
|
||||
so_rresp<=2'b00; so_rvalid<=1; so_rlast<=1; read_beats<=read_beats+1;
|
||||
if (!vs_edge_e) fb_reads<=fb_reads+1; rst_state<=R_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
logic vid_run=0; logic [11:0] rawx, rawy;
|
||||
always_ff @(posedge video_clk) begin
|
||||
if (!vid_run) begin rawx<=0; rawy<=0; end
|
||||
else if (rawx==H_TOT-1) begin rawx<=0; rawy<=(rawy==V_TOT-1)?12'd0:rawy+1'b1; end
|
||||
else rawx<=rawx+1'b1;
|
||||
end
|
||||
wire active = (rawx>=H_BP)&&(rawx<H_BP+H_ACT)&&(rawy>=V_BP)&&(rawy<V_BP+V_ACT);
|
||||
assign px=active?(rawx-H_BP):12'd0; assign py=(rawy>=V_BP&&rawy<V_BP+V_ACT)?(rawy-V_BP):12'd0;
|
||||
assign in_win=active; assign vsync=vid_run&&(rawx==0)&&(rawy==0);
|
||||
logic [11:0] px_q, py_q; logic inwin_q, run_q;
|
||||
always_ff @(posedge video_clk) begin px_q<=px; py_q<=py; inwin_q<=in_win; run_q<=vid_run; end
|
||||
int checked; initial checked=0; logic scoring=0;
|
||||
always_ff @(posedge video_clk) if (scoring && run_q) begin
|
||||
logic [7:0] er,eg,eb; logic [31:0] w;
|
||||
if (inwin_q) begin int aa; aa=py_q*STRIDE+px_q*4; w={fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]}; er=w[7:0]; eg=w[15:8]; eb=w[23:16]; end
|
||||
else begin er=0; eg=0; eb=0; end
|
||||
checked++;
|
||||
if (so_r!==er||so_g!==eg||so_b!==eb) begin
|
||||
if (errors<12) $error("[scan] px(%0d,%0d) got(%02x,%02x,%02x) exp(%02x,%02x,%02x)", px_q,py_q, so_r,so_g,so_b, er,eg,eb); errors++; end
|
||||
end
|
||||
|
||||
// per-epoch idx/pal (for the oracle) + composed reference (owner epoch in [26:24], multi in [28]) + per-epoch refmaps
|
||||
logic [31:0] idx [0:2][0:(512*512/4)-1]; logic [31:0] pal [0:2][0:255];
|
||||
logic [31:0] refmap [0:FB_WORDS-1]; logic [31:0] refmap_ep [0:2][0:FB_WORDS-1];
|
||||
logic [63:0] stg_buf [0:STG_WORDS-1]; // one epoch's staging list, streamed through the write port
|
||||
// temps: iverilog can't $readmemh into a 2D-array slice, so load flat then copy into the [e] plane
|
||||
logic [31:0] t_idx [0:(512*512/4)-1]; logic [31:0] t_pal [0:255]; logic [31:0] t_rm [0:FB_WORDS-1];
|
||||
|
||||
// ---- stream one epoch's list into the feeder staging via the WRITE PORT (word0 first, full STG reset) ----
|
||||
task automatic stream_list(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/feeder_sh3_zsched%0d.mem", k);
|
||||
for (int i=0;i<STG_WORDS;i++) stg_buf[i]=64'd0;
|
||||
$readmemh(fn, stg_buf);
|
||||
@(negedge clk);
|
||||
for (int i=0;i<STG_WORDS;i++) begin
|
||||
stg_we<=1'b1; stg_waddr<=i[11:0]; stg_wdata<=stg_buf[i]; @(negedge clk);
|
||||
end
|
||||
stg_we<=1'b0; stg_waddr<=12'd0; stg_wdata<=64'd0; @(negedge clk);
|
||||
// sanity: header word0 (ntris) landed correctly in the DUT staging
|
||||
if (dut.g_feeder.feeder_stg[0][31:0] !== stg_buf[0][31:0]) begin
|
||||
$error("[sched] epoch %0d: staged word0=%08x exp %08x (write-port mis-slot)", k, dut.g_feeder.feeder_stg[0][31:0], stg_buf[0][31:0]); errors++; end
|
||||
$display("[sched] epoch %0d: streamed %0d words via write port; word0(ntris)=%0d", k, STG_WORDS, dut.g_feeder.feeder_stg[0][31:0]);
|
||||
endtask
|
||||
|
||||
// ---- one-scene runner: GO, wait render+drain, REQUIRE fresh frame_drained (high->low->high, or low->high on first) ----
|
||||
task automatic run_scene(input int k, input int exp_records);
|
||||
int r0, f0, d=0; logic fd_before;
|
||||
r0=fd_rises; f0=fd_falls; fd_before=fbw_drained;
|
||||
@(negedge clk); feeder_go_tb=1'b1; @(negedge clk); feeder_go_tb=1'b0;
|
||||
d=0; while (feeder_ready_tb && d<4000) begin @(posedge clk); d++; end
|
||||
if (feeder_ready_tb!==1'b0) begin $error("[sched] epoch %0d: feeder_ready did not drop after GO", k); errors++; end
|
||||
if (fd_before) begin
|
||||
d=0; while (fd_falls==f0 && d<800000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_falls==f0) begin $error("[sched] epoch %0d: frame_drained never fell from stale high — STALE drain", k); errors++; end
|
||||
end
|
||||
d=0; while (fd_rises<=r0 && d<800000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_rises<=r0) begin $error("[sched] epoch %0d: frame_drained never rose — scene did not drain", k); errors++; end
|
||||
repeat(100) @(posedge clk);
|
||||
if (feeder_records_w!==exp_records) begin $error("[sched] epoch %0d: records_emitted=%0d exp %0d", k, feeder_records_w, exp_records); errors++; end
|
||||
if (fbw_ovf!==0 || fbw_bresp_err!==0) begin $error("[sched] epoch %0d: writer ovf=%0d bresp=%0d", k, fbw_ovf, fbw_bresp_err); errors++; end
|
||||
$display("[sched] epoch %0d: fresh drain (falls %0d->%0d, rises %0d->%0d), records=%0d, ovf=0", k, f0, fd_falls, r0, fd_rises, feeder_records_w);
|
||||
endtask
|
||||
|
||||
task automatic fill_cache(input int k, input logic [31:0] exp_crc);
|
||||
int d=0;
|
||||
@(posedge clk) fill_start<=1'b1; @(posedge clk) fill_start<=1'b0;
|
||||
d=0; while (fill_done && d<1000) begin @(posedge clk); d++; end // wait it drops (busy)
|
||||
d=0; while (!fill_done && d<400000) begin @(posedge clk); d++; end // then rises (done)
|
||||
if (!fill_done) begin $error("[sched] fill %0d: fill_done never rose (rearm failed)", k); errors++; end
|
||||
if (fill_crc_w!==exp_crc) begin $error("[sched] fill %0d: crc=%08x exp %08x", k, fill_crc_w, exp_crc); errors++; end
|
||||
if (fill_beats!==N_BEATS) begin $error("[sched] fill %0d: beats=%0d exp %0d", k, fill_beats, N_BEATS); errors++; end
|
||||
if (fill_bytes!==TEX_BYTES) begin $error("[sched] fill %0d: bytes=%0d exp %0d", k, fill_bytes, TEX_BYTES); errors++; end
|
||||
if (tex_rd_errs!==0) begin $error("[sched] fill %0d: rd_errs=%0d", k, tex_rd_errs); errors++; end
|
||||
$display("[sched] cache fill %0d: fresh done, crc=0x%08x (exp %08x), beats=%0d bytes=%0d rd_errs=0", k, fill_crc_w, exp_crc, fill_beats, fill_bytes);
|
||||
endtask
|
||||
|
||||
// texel lookup in epoch e's palette (module-level arrays, no per-call copy)
|
||||
function automatic logic [23:0] cell_e(input int e, input integer u, input integer v);
|
||||
integer lin; logic [31:0] w; logic [7:0] ix;
|
||||
lin=v*TW+u; w=idx[e][lin/4]; ix=w[(8*(lin%4)) +: 8]; cell_e=pal[e][ix][23:0];
|
||||
endfunction
|
||||
|
||||
// run ONE epoch fully (rebind tex -> fresh fill+CRC -> stream list -> GO -> fresh ordered drain)
|
||||
task automatic run_epoch(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/sh3_zsched%0d_tex_lpddr.mem", k);
|
||||
$readmemh(fn, lpddr_mem);
|
||||
fill_cache(k, EP_CRC[k]);
|
||||
stream_list(k);
|
||||
run_scene(k, EP_REC[k]);
|
||||
endtask
|
||||
|
||||
string only_m; string fdump; int only_k; // +ONLY=ALL | +ONLY=<k> ; +FBDUMP=<file>
|
||||
initial begin
|
||||
errors=0; feeder_go_tb=1'b0; fill_start=1'b0; wr_arm=0; scan_en=0; stg_we=0; stg_waddr=0; stg_wdata=0; cap_fh=0; tex_fh=0; issue_fh=0; cap_ep=0;
|
||||
tex_issue_addr_q=0; tex_issue_raw_q=0; tex_issue_u_q=0; tex_issue_v_q=0;
|
||||
if (!$value$plusargs("ONLY=%s", only_m)) only_m="ALL";
|
||||
only_k = (only_m=="ALL") ? -1 : only_m.atoi();
|
||||
for (int i=0;i<FB_WORDS;i++) ideal[i]=32'd0;
|
||||
for (int i=0;i<FB_BYTES;i++) fb[i]=8'h00; // PRECLEAR EXACTLY ONCE
|
||||
idx[0][0]='x;
|
||||
for (int e=0;e<NEP;e++) begin
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_zsched%0d_idx.mem", e), t_idx);
|
||||
for (int i=0;i<(512*512/4);i++) idx[e][i]=t_idx[i];
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_zsched%0d_pal.mem", e), t_pal);
|
||||
for (int i=0;i<256;i++) pal[e][i]=t_pal[i];
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_zsched%0d_refmap.mem", e), t_rm);
|
||||
for (int i=0;i<FB_WORDS;i++) refmap_ep[e][i]=t_rm[i];
|
||||
end
|
||||
$readmemh("../../data/top_psmct32_raster_demo/sh3_zsched_refmap.mem", refmap);
|
||||
$display("[sched] ONLY=%s (only_k=%0d), N_EPOCHS=%0d", only_m, only_k, NEP);
|
||||
if (idx[0][0]===32'bx) begin $display("[tb_top_psmct32_sh3_zsched] SKIP — sh3_zsched*.mem absent (run gs_make_sh3_zscheduler_fixture.py --emit)"); $finish; end
|
||||
|
||||
rst_n=1'b0; core_go=1'b0; repeat(6) @(posedge clk); rst_n=1'b1; repeat(8) @(posedge clk);
|
||||
|
||||
// boot the bootlet (uploads ALL N relocated CLUTs to their distinct CBPs)
|
||||
@(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);
|
||||
wait (feeder_ready_tb==1'b1); repeat(50) @(posedge clk);
|
||||
wr_arm<=1'b1; repeat(40) @(posedge clk);
|
||||
|
||||
// ===== scheduler: iterate the epoch descriptors in dump order (or a single epoch for the isolation check) =====
|
||||
cap_fh = $fopen("zsched_frags.txt","w"); // Ch357 fragment capture for external persp_z5 verification
|
||||
tex_fh = $fopen("zsched_textrace.txt","w"); // sim-only texture lookup trace for perspective-color diagnosis
|
||||
issue_fh = $fopen("zsched_issue.txt","w"); // sim-only perspective divide output trace
|
||||
if (only_k<0) begin
|
||||
for (int k=0;k<NEP;k++) begin
|
||||
cap_ep = k;
|
||||
run_epoch(k);
|
||||
if (k<NEP-1 && scan_en!==1'b0) begin $error("[sched] scanout enabled before the last epoch"); errors++; end
|
||||
end
|
||||
end else begin
|
||||
run_epoch(only_k);
|
||||
end
|
||||
|
||||
// enable scanout only after the final fresh drain
|
||||
scan_en<=1'b1; repeat(20) @(posedge clk);
|
||||
if (only_k<0) begin
|
||||
if (fd_rises<NEP) begin $error("[sched] expected %0d ordered drains: rises=%0d", NEP, fd_rises); errors++; end
|
||||
if (fd_falls<NEP-1)begin $error("[sched] epochs after the first never cleared stale drains (falls=%0d)", fd_falls); errors++; end
|
||||
end else if (fd_rises<1) begin $error("[sched] isolated epoch did not drain: rises=%0d", fd_rises); errors++; end
|
||||
|
||||
// ===== scanout: score final FB vs the composed reference (per-pixel OWNER epoch), overlap separately =====
|
||||
vid_run=1;
|
||||
begin int d=0; while(!vsync && d<300000) begin @(posedge video_clk); d++; end end
|
||||
@(posedge video_clk); while(!vsync) @(posedge video_clk);
|
||||
begin scoring=1; @(posedge video_clk); while(!vsync) @(posedge video_clk); scoring=0; repeat(60) @(posedge emif_clk);
|
||||
if (fb_reads_last!==BEATS_PER_FRAME) begin $error("[sched] scanout beats/frame=%0d exp %0d", fb_reads_last, BEATS_PER_FRAME); errors++; end
|
||||
end
|
||||
if (so_underflow!==0) begin $error("[sched] scanout underflow"); errors++; end
|
||||
if (so_rd_errs !==0) begin $error("[sched] scanout rd_errs=%0d", so_rd_errs); errors++; end
|
||||
if (checked < H_ACT*V_ACT) begin $error("[sched] only %0d px checked (exp >= %0d)", checked, H_ACT*V_ACT); errors++; end
|
||||
|
||||
// oracle (multi-texture correctness): a covered pixel must equal ONE of the COVERING epochs' texels. Each epoch
|
||||
// stores its OWN (tu,tv) per pixel in refmap_ep[k]; we accept if the RTL colour matches any covering epoch's texel
|
||||
// in the <=1-texel neighbourhood (owner-first, then neighbours — handles coverage-edge owner ambiguity between
|
||||
// adjacent-order epochs, exactly the tolerance Ch355 used). multi-epoch pixels (>=2 covering) scored separately as
|
||||
// the accumulation composite proof. clut_bad = an RTL-written pixel (ideal!=0) whose colour is in NO epoch palette
|
||||
// (palettes are pairwise-distinct, so this still proves the pixel came from a real texture+CLUT, not garbage).
|
||||
begin
|
||||
int m_tot,m_ok,o_tot,o_ok,cb,D,ncov; bit mt; m_tot=0;m_ok=0;o_tot=0;o_ok=0;cb=0;
|
||||
for (int y=0;y<H;y++) for (int x=0;x<W;x++) begin
|
||||
int o; logic [23:0] fbc; bit cov; o=y*W+x;
|
||||
cov = (only_k<0) ? refmap[o][31] : refmap_ep[only_k][o][31];
|
||||
if (cov) begin
|
||||
fbc=ideal[o][23:0]; m_tot++; D=9; ncov=0;
|
||||
for (int e=0;e<NEP;e++) begin
|
||||
if (!(only_k>=0 && e!=only_k) && refmap_ep[e][o][31]) begin
|
||||
int tu,tv; tu=(refmap_ep[e][o]>>9)&9'h1FF; tv=refmap_ep[e][o]&9'h1FF; ncov++;
|
||||
for (int rad=0;rad<=1;rad++) for (int du=-rad;du<=rad;du++) for (int dv=-rad;dv<=rad;dv++) begin
|
||||
int ch; ch=(du<0?-du:du); if((dv<0?-dv:dv)>ch) ch=(dv<0?-dv:dv);
|
||||
if (ch==rad && (tu+du)>=0 && (tu+du)<TW && (tv+dv)>=0 && (tv+dv)<TH) begin
|
||||
mt=(fbc===cell_e(e,tu+du,tv+dv)); if (mt && rad<D) D=rad;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if (D<=1) m_ok++;
|
||||
if (only_k<0 && refmap[o][28]) begin o_tot++; if (D<=1) o_ok++; end // multi-epoch (>=2 covering)
|
||||
if (ideal[o]!==32'd0) begin bit f; f=1'b0;
|
||||
for (int e=0;e<NEP;e++) for (int i=0;i<256;i++) if (pal[e][i][23:0]===fbc) f=1'b1;
|
||||
if(!f) cb++; end
|
||||
end
|
||||
end
|
||||
$display("[sched][oracle] ONLY=%s <=1texel ALL=%0d/%0d (%.1f%%) MULTI(>=2)=%0d/%0d (%.1f%%) clut_bad=%0d",
|
||||
only_m, m_ok,m_tot,(m_tot>0)?100.0*m_ok/m_tot:0.0, o_ok,o_tot,(o_tot>0)?100.0*o_ok/o_tot:0.0, cb);
|
||||
if (cb!==0) begin $error("[sched][oracle] ONLY=%s: %0d covered px in NO epoch palette", only_m, cb); errors++; end
|
||||
if (only_k<0) begin
|
||||
if (o_tot<500) begin $error("[sched][oracle] only %0d multi-epoch px — accumulation not exercised", o_tot); errors++; end
|
||||
// MULTI <=1texel is TEXTURE FIDELITY (RTL 11-bit reciprocal LUT precision), NOT accumulation correctness.
|
||||
// It tracks the per-epoch isolated fidelity (ONLY=k gives 93.0/93.3/95.6%); the multi-epoch subset sits at
|
||||
// the same reciprocal floor (~92%). The ACCUMULATION proof is separate and EXACT: compose_sched.py shows
|
||||
// the joint ALL render == the composited ONLY=k isolation dumps 100% BIT-FOR-BIT. So gate this at the
|
||||
// documented reciprocal floor, not an accumulation-implying bar.
|
||||
if (o_tot>0 && (100.0*o_ok/o_tot)<90.0) begin $error("[sched][oracle] MULTI <=1texel %.1f%% < 90%% reciprocal floor", 100.0*o_ok/o_tot); errors++; end
|
||||
end
|
||||
end
|
||||
// FB dump for the composition==isolated check (per-epoch dumps composed externally -> compare vs ALL dump)
|
||||
if ($value$plusargs("FBDUMP=%s", fdump)) begin
|
||||
int fh; fh=$fopen(fdump,"w");
|
||||
for (int y=0;y<H;y++) for (int x=0;x<W;x++) begin int aa; aa=y*STRIDE+x*4; $fwrite(fh,"%08x\n",{fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]}); end
|
||||
$fclose(fh); $display("[sched] dumped RTL FB (%0dx%0d) -> %s", W, H, fdump);
|
||||
end
|
||||
|
||||
$display("[tb_top_psmct32_sh3_zsched] checked=%0d beats/frame=%0d (exp %0d) fresh_drains(rise/fall)=%0d/%0d records=%0d underflow=%0b ovf=%0d errors=%0d",
|
||||
checked, fb_reads_last, BEATS_PER_FRAME, fd_rises, fd_falls, feeder_records_w, so_underflow, fbw_ovf, errors);
|
||||
if (cap_fh!=0) $fclose(cap_fh);
|
||||
if (tex_fh!=0) $fclose(tex_fh);
|
||||
if (issue_fh!=0) $fclose(issue_fh);
|
||||
if (errors==0) $display("[tb_top_psmct32_sh3_zsched] PASS"); else $display("[tb_top_psmct32_sh3_zsched] FAIL");
|
||||
$finish;
|
||||
end
|
||||
initial begin #160000000; $error("[tb_top_psmct32_sh3_zsched] TIMEOUT"); $finish; end
|
||||
endmodule : tb_top_psmct32_sh3_zsched
|
||||
@@ -0,0 +1,178 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_sh3_ztrio (Ch357 — persistent-Z ROP acceptance gate, TRACE-based scoreboard)
|
||||
//
|
||||
// Codex option A: verify the LPDDR-Z ROP (gs_lpddr_zc_emit) against the raster's ACTUAL emitted fragments. This TB replays
|
||||
// the captured fragment trace (tb_top_psmct32_sh3_zsched -> ztrio_frags.txt: "epoch x y persp_z color" per line) through
|
||||
// zc_emit across two clock domains, with a per-epoch scene marker, and scores it with an INDEPENDENT software scoreboard
|
||||
// (clamp16 / GEQUAL / persistence). Proves Codex's Ch357 gates:
|
||||
// * every replayed fragment enters the ROP EXACTLY once (we feed each once and wait g_ready — no drop, no double).
|
||||
// * the scoreboard predicts every pass/reject and the final packed 16-bit Z; failed depth tests emit NO color.
|
||||
// * final color + Z LPDDR memories match the scoreboard EXACTLY.
|
||||
// * the ordered end-of-scene marker drain waits BOTH pipelines (color + Z BRESPs) before frame_drained.
|
||||
// This is the ROP-correctness gate; the raster's Z-interp fidelity (float-oracle 3971 / 87.6% owner agreement) is a SEPARATE
|
||||
// recorded limitation, not tested here. Primary fixture = sh3_zsched [8634,12757,145742], FB 256x210, Z base 0x140000.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_sh3_ztrio;
|
||||
localparam int FB_PXW=384, FB_H=381;
|
||||
localparam int NPX=FB_PXW*FB_H; // 53760
|
||||
localparam int ZBEATS=(NPX+15)/16; // 3360
|
||||
localparam int CBEATS=(NPX+7)/8; // 6720
|
||||
localparam [31:0] COLBASE=32'h0000_0000, ZBASE=32'h0014_0000;
|
||||
localparam int ZBW=$clog2(ZBEATS), CBW=$clog2(CBEATS);
|
||||
|
||||
logic gs_clk=0; always #5 gs_clk=~gs_clk;
|
||||
logic axi_clk=0; always #2 axi_clk=~axi_clk; // emif faster than design -> ROP drains faster than the trace feeds
|
||||
logic gs_rst_n, axi_rst_n; int errors; initial errors=0;
|
||||
|
||||
logic enable, clear_start, clear_done, frame_drained;
|
||||
logic g_valid, g_ready; logic [11:0] g_x, g_y; logic [15:0] g_zq; logic g_zmsk, g_ztest, g_scene; logic [31:0] g_color;
|
||||
logic [31:0] z_araddr; logic [7:0] z_arlen; logic [2:0] z_arsize; logic [1:0] z_arburst; logic z_arvalid, z_arready;
|
||||
logic [255:0] z_rdata; logic [1:0] z_rresp; logic z_rlast, z_rvalid, z_rready;
|
||||
logic [31:0] z_awaddr; logic [7:0] z_awlen; logic [2:0] z_awsize; logic [1:0] z_awburst; logic z_awvalid, z_awready;
|
||||
logic [255:0] z_wdata; logic [31:0] z_wstrb; logic z_wlast, z_wvalid, z_wready; logic z_bvalid, z_bready; logic [1:0] z_bresp;
|
||||
logic [31:0] c_awaddr; logic [7:0] c_awlen; logic [2:0] c_awsize; logic [1:0] c_awburst; logic c_awvalid, c_awready;
|
||||
logic [255:0] c_wdata; logic [31:0] c_wstrb; logic c_wlast, c_wvalid, c_wready; logic c_bvalid, c_bready; logic [1:0] c_bresp;
|
||||
logic [31:0] z_beats_read, z_beats_written, c_beats_written, col_ovf, bresp_err; logic idle;
|
||||
|
||||
gs_lpddr_zc_emit #(.COLBASE(COLBASE), .ZBASE(ZBASE), .FB_PXW(FB_PXW), .FB_H(FB_H), .REQ_DEPTH(32), .COL_DEPTH(64)) dut (
|
||||
.gs_clk(gs_clk), .gs_rst_n(gs_rst_n), .enable(enable),
|
||||
.g_valid(g_valid), .g_ready(g_ready), .g_x(g_x), .g_y(g_y), .g_zq(g_zq), .g_zmsk(g_zmsk),
|
||||
.g_ztest(g_ztest), .g_ztst(2'd2), .g_color(g_color), .g_be(4'hF), .g_scene(g_scene),
|
||||
.axi_clk(axi_clk), .axi_rst_n(axi_rst_n), .clear_start(clear_start), .clear_done(clear_done), .frame_drained(frame_drained),
|
||||
.z_araddr(z_araddr), .z_arlen(z_arlen), .z_arsize(z_arsize), .z_arburst(z_arburst), .z_arvalid(z_arvalid), .z_arready(z_arready),
|
||||
.z_rdata(z_rdata), .z_rresp(z_rresp), .z_rlast(z_rlast), .z_rvalid(z_rvalid), .z_rready(z_rready),
|
||||
.z_awaddr(z_awaddr), .z_awlen(z_awlen), .z_awsize(z_awsize), .z_awburst(z_awburst), .z_awvalid(z_awvalid), .z_awready(z_awready),
|
||||
.z_wdata(z_wdata), .z_wstrb(z_wstrb), .z_wlast(z_wlast), .z_wvalid(z_wvalid), .z_wready(z_wready),
|
||||
.z_bvalid(z_bvalid), .z_bready(z_bready), .z_bresp(z_bresp),
|
||||
.c_awaddr(c_awaddr), .c_awlen(c_awlen), .c_awsize(c_awsize), .c_awburst(c_awburst), .c_awvalid(c_awvalid), .c_awready(c_awready),
|
||||
.c_wdata(c_wdata), .c_wstrb(c_wstrb), .c_wlast(c_wlast), .c_wvalid(c_wvalid), .c_wready(c_wready),
|
||||
.c_bvalid(c_bvalid), .c_bready(c_bready), .c_bresp(c_bresp),
|
||||
.z_beats_read(z_beats_read), .z_beats_written(z_beats_written), .c_beats_written(c_beats_written),
|
||||
.col_ovf(col_ovf), .bresp_err(bresp_err), .idle(idle)
|
||||
);
|
||||
|
||||
logic [15:0] lf=16'hCAFE; always_ff @(posedge axi_clk) lf<={lf[14:0], lf[15]^lf[13]^lf[12]^lf[10]};
|
||||
|
||||
// ---- Z LPDDR slave (256b, random backpressure) ----
|
||||
logic [255:0] zmem [0:ZBEATS-1];
|
||||
logic [31:0] zra; logic zrp; logic [3:0] zrd; logic [31:0] zwa; logic [255:0] zwd; logic zaw,zw; logic [3:0] zbd; logic zbp;
|
||||
always_ff @(posedge axi_clk or negedge axi_rst_n) begin
|
||||
if(!axi_rst_n) begin z_arready<=0;z_rvalid<=0;z_rlast<=0;z_rresp<=0;z_rdata<=0;zrp<=0;zrd<=0;
|
||||
z_awready<=0;z_wready<=0;z_bvalid<=0;z_bresp<=0;zaw<=0;zw<=0;zbp<=0;zbd<=0; end
|
||||
else begin
|
||||
z_arready<=(!zrp&&!z_rvalid)?lf[0]:1'b0;
|
||||
if(z_arvalid&&z_arready) begin zra<=z_araddr;zrp<=1;zrd<={1'b0,lf[3:1]};z_arready<=0; end
|
||||
if(zrp&&!z_rvalid) begin if(zrd==0) begin z_rdata<=zmem[(zra-ZBASE)>>5];z_rresp<=0;z_rvalid<=1;z_rlast<=1;zrp<=0; end else zrd<=zrd-1; end
|
||||
if(z_rvalid&&z_rready) begin z_rvalid<=0;z_rlast<=0; end
|
||||
z_awready<=(!zaw)?lf[4]:1'b0; z_wready<=(!zw)?lf[5]:1'b0;
|
||||
if(z_awvalid&&z_awready) begin zwa<=z_awaddr;zaw<=1;z_awready<=0; end
|
||||
if(z_wvalid&&z_wready) begin zwd<=z_wdata;zw<=1;z_wready<=0; end
|
||||
if(zaw&&zw&&!zbp&&!z_bvalid) begin zmem[(zwa-ZBASE)>>5]<=zwd;zbp<=1;zbd<={1'b0,lf[8:6]}; end
|
||||
if(zbp&&!z_bvalid) begin if(zbd==0) begin z_bvalid<=1;z_bresp<=0;zbp<=0;zaw<=0;zw<=0; end else zbd<=zbd-1; end
|
||||
if(z_bvalid&&z_bready) z_bvalid<=0;
|
||||
end
|
||||
end
|
||||
// ---- Color LPDDR slave (256b, per-byte wstrb, random backpressure) ----
|
||||
logic [255:0] cmem [0:CBEATS-1];
|
||||
logic [31:0] cwa; logic [255:0] cwd; logic [31:0] cws; logic caw,cw; logic [3:0] cbd; logic cbp;
|
||||
always_ff @(posedge axi_clk or negedge axi_rst_n) begin
|
||||
if(!axi_rst_n) begin c_awready<=0;c_wready<=0;c_bvalid<=0;c_bresp<=0;caw<=0;cw<=0;cbp<=0;cbd<=0; end
|
||||
else begin
|
||||
c_awready<=(!caw)?lf[9]:1'b0; c_wready<=(!cw)?lf[11]:1'b0;
|
||||
if(c_awvalid&&c_awready) begin cwa<=c_awaddr;caw<=1;c_awready<=0; end
|
||||
if(c_wvalid&&c_wready) begin cwd<=c_wdata;cws<=c_wstrb;cw<=1;c_wready<=0; end
|
||||
if(caw&&cw&&!cbp&&!c_bvalid) begin
|
||||
for(int b=0;b<32;b++) if(cws[b]) cmem[(cwa-COLBASE)>>5][b*8+:8]<=cwd[b*8+:8];
|
||||
cbp<=1;cbd<={1'b0,lf[14:12]};
|
||||
end
|
||||
if(cbp&&!c_bvalid) begin if(cbd==0) begin c_bvalid<=1;c_bresp<=0;cbp<=0;caw<=0;cw<=0; end else cbd<=cbd-1; end
|
||||
if(c_bvalid&&c_bready) c_bvalid<=0;
|
||||
end
|
||||
end
|
||||
// disjoint-range monitors
|
||||
always_ff @(posedge axi_clk) if(axi_rst_n) begin
|
||||
if(z_arvalid && (z_araddr<ZBASE || z_araddr>=ZBASE+ZBEATS*32)) begin $error("Z AR %h OOR",z_araddr);errors++; end
|
||||
if(z_awvalid && (z_awaddr<ZBASE || z_awaddr>=ZBASE+ZBEATS*32)) begin $error("Z AW %h OOR",z_awaddr);errors++; end
|
||||
if(c_awvalid && (c_awaddr<COLBASE || c_awaddr>=COLBASE+CBEATS*32)) begin $error("C AW %h OOR",c_awaddr);errors++; end
|
||||
end
|
||||
|
||||
// ---- independent scoreboard (clamp16 persistent-Z over the fed fragments) ----
|
||||
logic [15:0] sb_z [0:NPX-1]; logic [31:0] sb_col [0:NPX-1]; logic sb_seen [0:NPX-1];
|
||||
function automatic logic [15:0] cl16(input logic [31:0] z); cl16=(|z[31:16])?16'hFFFF:z[15:0]; endfunction
|
||||
|
||||
// ---- feed one fragment: drive g_*, wait acceptance (exactly once), update the scoreboard ----
|
||||
int nfed;
|
||||
task automatic feed_frag(input int ep, input int x, input int y, input int z, input logic [31:0] col);
|
||||
int o; logic [15:0] zq; logic pass;
|
||||
@(negedge gs_clk);
|
||||
g_valid<=1; g_scene<=0; g_x<=x[11:0]; g_y<=y[11:0]; g_zq<=cl16(z); g_zmsk<=0; g_ztest<=1; g_color<=col;
|
||||
@(posedge gs_clk);
|
||||
while(!(g_valid && g_ready)) @(posedge gs_clk); // wait acceptance — NEVER drop a fragment
|
||||
@(negedge gs_clk); g_valid<=0;
|
||||
// scoreboard (in feed order == ROP processing order)
|
||||
o=y*FB_PXW+x; zq=cl16(z); pass=(zq>=sb_z[o]);
|
||||
if(pass) begin sb_col[o]=col; sb_seen[o]=1; sb_z[o]=zq; end
|
||||
nfed++;
|
||||
if(lf[7]) repeat(1+lf[1:0]) @(posedge gs_clk); // random inter-fragment gap
|
||||
endtask
|
||||
task automatic send_marker();
|
||||
@(negedge gs_clk); g_valid<=1; g_scene<=1; @(posedge gs_clk);
|
||||
while(!(g_valid && g_ready)) @(posedge gs_clk);
|
||||
@(negedge gs_clk); g_valid<=0; g_scene<=0;
|
||||
endtask
|
||||
|
||||
int fh, r, ep, x, y, z, cep, drains; logic [31:0] col;
|
||||
initial begin
|
||||
errors=0; enable=0; clear_start=0; g_valid=0; g_scene=0; g_x=0; g_y=0; g_zq=0; g_zmsk=0; g_ztest=1; g_color=0; nfed=0; drains=0;
|
||||
for(int i=0;i<NPX;i++) begin sb_z[i]=16'h0000; sb_col[i]=32'd0; sb_seen[i]=1'b0; end
|
||||
for(int b=0;b<CBEATS;b++) cmem[b]=256'd0; // host preclears the color FB
|
||||
gs_rst_n=0; axi_rst_n=0; repeat(6) @(posedge axi_clk); gs_rst_n=1; axi_rst_n=1; repeat(4) @(posedge axi_clk);
|
||||
enable=1;
|
||||
@(negedge axi_clk) clear_start=1; @(negedge axi_clk) clear_start=0; // preclear Z once
|
||||
begin int g=0; while(!clear_done && g<400000) begin @(posedge axi_clk); g++; end end
|
||||
if(!clear_done) begin $error("[zrop] Z preclear timeout"); errors++; end
|
||||
|
||||
fh=$fopen("ztrio_frags.txt","r");
|
||||
if(fh==0) begin $display("[tb_top_psmct32_sh3_ztrio] SKIP — ztrio_frags.txt absent (run make tb_top_psmct32_sh3_zsched)"); $finish; end
|
||||
cep=0;
|
||||
while(!$feof(fh)) begin
|
||||
r=$fscanf(fh, "%d %d %d %d %h\n", ep, x, y, z, col);
|
||||
if(r==5) begin
|
||||
if(ep!=cep) begin // epoch boundary -> ordered scene drain
|
||||
send_marker();
|
||||
begin int g=0; while(!frame_drained && g<800000) begin @(posedge axi_clk); g++; end end
|
||||
if(!frame_drained) begin $error("[zrop] epoch %0d: frame_drained never rose",cep); errors++; end
|
||||
else drains++;
|
||||
cep=ep;
|
||||
end
|
||||
feed_frag(ep, x, y, z, col);
|
||||
end else begin
|
||||
r=$fgetc(fh); // skip a char past a blank/partial line
|
||||
end
|
||||
end
|
||||
$fclose(fh);
|
||||
// final scene marker (last epoch)
|
||||
send_marker();
|
||||
begin int g=0; while(!frame_drained && g<800000) begin @(posedge axi_clk); g++; end end
|
||||
if(!frame_drained) begin $error("[zrop] final: frame_drained never rose"); errors++; end else drains++;
|
||||
repeat(60) @(posedge axi_clk);
|
||||
|
||||
// ---- gates: final Z + color LPDDR == scoreboard ; suppression ; counts ----
|
||||
for(int o=0;o<NPX;o++) begin
|
||||
logic [15:0] zs; zs=zmem[o>>4][(o[3:0])*16+:16];
|
||||
if(zs!==sb_z[o]) begin if(errors<12)$error("[zrop] Z px%0d=%04x exp %04x",o,zs,sb_z[o]);errors++; end
|
||||
end
|
||||
for(int o=0;o<NPX;o++) begin
|
||||
logic [31:0] cs; cs=cmem[o>>3][(o[2:0])*32+:32];
|
||||
if(sb_seen[o]) begin if(cs!==sb_col[o]) begin if(errors<12)$error("[zrop] COLOR px%0d=%08x exp %08x",o,cs,sb_col[o]);errors++; end end
|
||||
else begin if(cs!==32'd0) begin if(errors<12)$error("[zrop] px%0d never-passed but color=%08x (suppression fail)",o,cs);errors++; end end
|
||||
end
|
||||
if(col_ovf!==0) begin $error("[zrop] col_ovf=%0d",col_ovf);errors++; end
|
||||
if(bresp_err!==0)begin $error("[zrop] bresp_err=%0d",bresp_err);errors++; end
|
||||
$display("[zrop] fed=%0d fragments, scene-drains=%0d, z_wr=%0d c_wr=%0d col_ovf=%0d bresp_err=%0d errors=%0d",
|
||||
nfed, drains, z_beats_written, c_beats_written, col_ovf, bresp_err, errors);
|
||||
if(errors==0) $display("[tb_top_psmct32_sh3_ztrio] PASS"); else $display("[tb_top_psmct32_sh3_ztrio] FAIL");
|
||||
$finish;
|
||||
end
|
||||
initial begin #400000000; $error("[tb_top_psmct32_sh3_ztrio] TIMEOUT"); $finish; end
|
||||
endmodule : tb_top_psmct32_sh3_ztrio
|
||||
@@ -0,0 +1,402 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_sh3_ztrio_cap (Ch356 — N-TEXTURE SCHEDULER, data-driven epoch descriptors)
|
||||
//
|
||||
// Generalizes Ch355's two-group flow to N authentic SH3 draw epochs, each with a DIFFERENT TEX0/CLUT, accumulating
|
||||
// into ONE LPDDR framebuffer via a data-driven scheduler over epoch descriptors (sh3_ztrio_params.vh). Default
|
||||
// N_EPOCHS=3: E0=idx11671 E1=idx19562 E2=idx89761. Proves Codex's Ch356 integration acceptance:
|
||||
// * preclear EXACTLY once.
|
||||
// * N FRESH cache fills, each fill_done low->high, expected beats/bytes, 0 read errors, epoch CRC (EPk_CRC).
|
||||
// * N lists STREAMED through the feeder staging WRITE PORT (feeder_stg_we/waddr/wdata) — NOT the $readmemh backdoor
|
||||
// (closes the Ch355 sim gap where the write-port sequencing was untested). Each streams the full STG_WORDS so
|
||||
// staging is fully reset per epoch (no stale words), and word0 (the ntris header) is exercised first.
|
||||
// * N FRESH ORDERED drains: epoch 0 low->high (first render); epochs 1..N-1 high->low->high (stale cleared).
|
||||
// * scanout stays disabled until the LAST fresh drain; exact BEATS_PER_FRAME (=ROW_BEATS*H).
|
||||
// * final FB scored vs the independent composed reference using the PER-PIXEL OWNER epoch (exact palette per owner);
|
||||
// multi-epoch (>=2) overlap scored separately as the accumulation proof.
|
||||
// * +ONLY=<k> renders a SINGLE epoch (+FBDUMP dumps its FB) for the composition==isolated bit-for-bit check.
|
||||
// LOCAL/gitignored fixtures; skip-guard if absent.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_sh3_ztrio_cap;
|
||||
`include "sh3_ztrio_params.vh" // FBPXW=384, FBH=381, N_EPOCHS=3, EPk_CRC/EPk_NTRIS/EPk_CBP, TEX_*, ...
|
||||
localparam int W = FBPXW, H = FBH;
|
||||
localparam int STRIDE = W*4; // 1536
|
||||
localparam int ROW_BEATS = STRIDE/32; // 48
|
||||
localparam int FB_BYTES = STRIDE*H; // 585216
|
||||
localparam int FB_WORDS = W*H;
|
||||
localparam int BEATS_PER_FRAME = ROW_BEATS*H; // 18288
|
||||
localparam int H_ACT=W, V_ACT=H, H_BP=32, H_FP=8, V_BP=16, V_FP=8;
|
||||
localparam int H_TOT=H_BP+H_ACT+H_FP, V_TOT=V_BP+V_ACT+V_FP;
|
||||
localparam int NEP = N_EPOCHS; // 3
|
||||
|
||||
// per-epoch expected CRC / records (from the descriptor params)
|
||||
logic [31:0] EP_CRC [0:2]; int EP_REC [0:2];
|
||||
initial begin
|
||||
EP_CRC[0]=EP0_CRC; EP_CRC[1]=EP1_CRC; EP_CRC[2]=EP2_CRC;
|
||||
EP_REC[0]=EP0_NTRIS; EP_REC[1]=EP1_NTRIS; EP_REC[2]=EP2_NTRIS;
|
||||
end
|
||||
|
||||
logic clk=0; always #5 clk=~clk;
|
||||
logic emif_clk=0; always #2 emif_clk=~emif_clk;
|
||||
logic video_clk=0; always #7 video_clk=~video_clk;
|
||||
logic rst_n; logic fb_commit=0; always #13 fb_commit=~fb_commit;
|
||||
int errors; initial errors=0;
|
||||
|
||||
// ===== bram-top (FB_LPDDR_ONLY, AUTOSTART off, STG 2048), H/V_ACTIVE = 384x381 =====
|
||||
logic core_go; logic [7:0] r,g,b; logic hsync,vsync_o,de;
|
||||
logic core_halt,dma_done_seen,frame_seen,raster_overflow,frame_toggle,dma_done_toggle;
|
||||
logic feeder_go_tb, feeder_ready_tb; logic [15:0] feeder_records_w; logic [31:0] feeder_waits_w;
|
||||
logic gs_tex_rd_en_o; logic [31:0] gs_tex_rd_addr_o; logic [31:0] tex_cache_data; logic tex_cache_ready;
|
||||
logic [31:0] tex_cache_hits, tex_bram_hits;
|
||||
logic flush_emit_w; logic [31:0] flush_addr_w; logic [31:0] flush_color32_w; logic [5:0] flush_psm_w;
|
||||
logic [11:0] flush_x_w, flush_y_w; logic [31:0] flush_z_w; // Ch357 — fragment coords + persp_z5
|
||||
int cap_ep; int cap_fh; // fragment capture (verify persp_z5 vs the clamp16 oracle)
|
||||
// feeder staging WRITE PORT (streamed per epoch)
|
||||
logic stg_we; logic [11:0] stg_waddr; logic [63:0] stg_wdata;
|
||||
|
||||
top_psmct32_raster_demo_bram #(
|
||||
.H_ACTIVE(FBPXW), .V_ACTIVE(FBH),
|
||||
.VRAM_BYTES(VRAM_BYTES_P), .VRAM_ENABLE_READ2(1'b0), .PSMCT32_SWIZZLE(1'b0),
|
||||
.COMBINED_TAZ(1'b0), .TILE_LOCAL(1'b0), .TILE_COLS(1), .TILE_ROWS(1),
|
||||
.TILE_MULTIPRIM(1'b0), .TILE_PRIM_COUNT(1), .TILE_FIFO_DEPTH(8),
|
||||
.BIN_BUFFER_ENABLE(1'b0), .HEARTBEAT_SPLICE_ENABLE(1'b0),
|
||||
.FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(STG_WORDS), .FEEDER_AUTOSTART(1'b0),
|
||||
.PERSPECTIVE_CORRECT(1'b1), .PERSP_RECIP_IDX_BITS(11),
|
||||
.GRAD_SEQ_DIVIDER(1'b1), .GRAD_DIV_CYCLES(4),
|
||||
.GS_LPDDR_TEX(1'b1), .TEX_VRAM_BASE(TEX_VRAM_BASE), .TEX_CACHE_BYTES(TEX_BYTES),
|
||||
.CLUT_CSM1_ENABLE(1'b1), .FB_LPDDR_ONLY(1'b1)
|
||||
) dut (
|
||||
.clk(clk), .rst_n(rst_n), .core_go(core_go),
|
||||
.r(r), .g(g), .b(b), .hsync(hsync), .vsync(vsync_o), .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),
|
||||
.feeder_stg_we_i(stg_we), .feeder_stg_waddr_i(stg_waddr), .feeder_stg_wdata_i(stg_wdata),
|
||||
.feeder_go_i(feeder_go_tb), .feeder_ready_o(feeder_ready_tb),
|
||||
.feeder_records_o(feeder_records_w), .feeder_waits_o(feeder_waits_w),
|
||||
.flush_emit_o(flush_emit_w), .flush_addr_o(flush_addr_w),
|
||||
.flush_pix16_o(), .flush_color32_o(flush_color32_w), .flush_psm_o(flush_psm_w),
|
||||
.flush_x_o(flush_x_w), .flush_y_o(flush_y_w), .flush_z_o(flush_z_w),
|
||||
.gs_tex_rd_en_o(gs_tex_rd_en_o), .gs_tex_rd_addr_o(gs_tex_rd_addr_o),
|
||||
.tex_cache_data_i(tex_cache_data), .tex_cache_ready_i(tex_cache_ready),
|
||||
.tex_cache_hits_o(tex_cache_hits), .tex_bram_hits_o(tex_bram_hits)
|
||||
);
|
||||
|
||||
// Ch357 — capture every emitted fragment {epoch, x, y, persp_z, color}. An external Python check replays these through
|
||||
// the clamp16 persistent-Z model and compares owner/reject to the oracle -> validates persp_z5 end-to-end (the new RTL).
|
||||
always_ff @(posedge clk) if (rst_n && flush_emit_w && (flush_psm_w==6'h00) && cap_fh!=0)
|
||||
$fwrite(cap_fh, "%0d %0d %0d %0d %08x\n", cap_ep, flush_x_w, flush_y_w, flush_z_w, flush_color32_w);
|
||||
|
||||
// texture cache + behavioral texture LPDDR (RELOADED between fills for each epoch's rebind)
|
||||
logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid; logic [7:0] arlen; logic [2:0] arsize;
|
||||
logic arvalid, arready; logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready;
|
||||
logic fill_start, fill_done; logic [31:0] fill_beats, fill_bytes, tex_rd_errs, fill_crc_w;
|
||||
gs_texture_cache #(.LPDDR_TEX_BASE(LPDDR_TEX_BASE), .TEX_VRAM_BASE(TEX_VRAM_BASE),
|
||||
.TEX_BYTES(TEX_BYTES), .N_BEATS(N_BEATS)) u_cache (
|
||||
.axi_clk(clk), .axi_rst_n(rst_n), .fill_start(fill_start), .fill_done(fill_done),
|
||||
.fill_beats(fill_beats), .fill_bytes(fill_bytes), .rd_errs(tex_rd_errs), .fill_crc(fill_crc_w),
|
||||
.araddr(araddr), .arburst(arburst), .arid(arid), .arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready),
|
||||
.rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready),
|
||||
.sample_clk(clk), .tex_rd_en(gs_tex_rd_en_o), .tex_rd_addr(gs_tex_rd_addr_o),
|
||||
.tex_rd_data(tex_cache_data), .tex_ready(tex_cache_ready)
|
||||
);
|
||||
logic [31:0] lpddr_mem [0:(TEX_BYTES/4)-1]; // reloaded per epoch: tex0, tex1, tex2
|
||||
typedef enum logic [1:0] { S_IDLE, S_WAIT, S_DATA } sstate_t; sstate_t sst; logic [3:0] tdly; int tbeat;
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin sst<=S_IDLE; arready<=0; rvalid<=0; rlast<=0; rresp<=0; rdata<=0; tdly<=0; end
|
||||
else begin arready<=0; rvalid<=0; rlast<=0;
|
||||
case (sst)
|
||||
S_IDLE: if (arvalid) begin arready<=1; tbeat<=(araddr-LPDDR_TEX_BASE)>>5; tdly<=4'd2; sst<=S_WAIT; end
|
||||
S_WAIT: if (tdly==0) sst<=S_DATA; else tdly<=tdly-1'b1;
|
||||
S_DATA: if (rready) begin for (int w=0;w<8;w++) rdata[w*32 +: 32]<=lpddr_mem[tbeat*8+w];
|
||||
rresp<=2'b00; rvalid<=1; rlast<=1; sst<=S_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// render epoch (per scene) + PSMCT32 writer + precleared LPDDR FB
|
||||
logic fb_flush=0, render_inflight=0, feeder_ready_q=0; int eof_count;
|
||||
wire feeder_ready_rise = feeder_ready_tb && !feeder_ready_q;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin render_inflight<=0; feeder_ready_q<=0; fb_flush<=0; eof_count<=0; end
|
||||
else begin feeder_ready_q<=feeder_ready_tb; fb_flush<=1'b0;
|
||||
if (feeder_go_tb && feeder_ready_tb) render_inflight<=1'b1;
|
||||
if (render_inflight && feeder_ready_rise) begin fb_flush<=1'b1; render_inflight<=1'b0; eof_count<=eof_count+1; end
|
||||
end
|
||||
end
|
||||
logic wr_arm=0; logic [255:0] fbw_wdata; logic [31:0] fbw_wstrb, fbw_awaddr; logic fbw_awvalid, fbw_wvalid;
|
||||
logic [31:0] fbw_beats, fbw_ovf, fbw_bresp_err; logic fbw_idle, fbw_drained;
|
||||
gs_lpddr_axi_master #(.FIFO_DEPTH(64), .PIX_BYTES(4)) u_wr (
|
||||
.gs_clk(clk), .gs_rst_n(rst_n), .enable(1'b1),
|
||||
.arm(wr_arm), .canary(1'b0), .fb_base(32'h0), .ctrl_commit(fb_commit),
|
||||
.px_emit(flush_emit_w && (flush_psm_w==6'h00)), .px_addr(flush_addr_w), .px_pix32(flush_color32_w), .flush(fb_flush),
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n),
|
||||
.awaddr(fbw_awaddr), .awlen(), .awsize(), .awburst(), .awid(), .awvalid(fbw_awvalid), .awready(1'b1),
|
||||
.wdata(fbw_wdata), .wstrb(fbw_wstrb), .wlast(), .wvalid(fbw_wvalid), .wready(1'b1),
|
||||
.bvalid(1'b1), .bready(), .bresp(2'b00),
|
||||
.beats_written(fbw_beats), .bursts_issued(), .bresp_err_count(fbw_bresp_err),
|
||||
.fifo_overflow_count(fbw_ovf), .idle(fbw_idle), .frame_drained(fbw_drained)
|
||||
);
|
||||
logic [7:0] fb [0:FB_BYTES-1]; logic [31:0] fb_awlat;
|
||||
always_ff @(posedge emif_clk) begin
|
||||
if (fbw_awvalid) fb_awlat<=fbw_awaddr;
|
||||
if (fbw_wvalid) for (int i=0;i<32;i++) if (fbw_wstrb[i]) begin
|
||||
int aa; aa=fb_awlat+i; if (aa>=0 && aa<FB_BYTES) fb[aa]<=fbw_wdata[i*8 +: 8]; end
|
||||
end
|
||||
logic [31:0] ideal [0:FB_WORDS-1];
|
||||
always_ff @(posedge clk) if (rst_n && flush_emit_w && (flush_psm_w==6'h00)) ideal[flush_addr_w>>2]<=flush_color32_w;
|
||||
|
||||
// FRESH-DRAIN observer: count frame_drained low->high and high->low edges (emif domain).
|
||||
logic fd_q; int fd_rises, fd_falls;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin fd_q<=0; fd_rises<=0; fd_falls<=0; end
|
||||
else begin fd_q<=fbw_drained;
|
||||
if (fbw_drained && !fd_q) fd_rises<=fd_rises+1;
|
||||
if (!fbw_drained && fd_q) fd_falls<=fd_falls+1;
|
||||
end
|
||||
end
|
||||
|
||||
// scanout (384x381), host-gated: enable = scan_en (video_src) AND frame_drained
|
||||
logic scan_en=0; wire so_enable = scan_en & fbw_drained;
|
||||
logic [11:0] px, py; logic vsync, in_win; logic [7:0] so_r, so_g, so_b;
|
||||
logic so_underflow; logic [31:0] so_rd_errs; logic so_line_valid;
|
||||
logic [29:0] so_araddr; logic [1:0] so_arburst; logic [6:0] so_arid; logic [7:0] so_arlen;
|
||||
logic [2:0] so_arsize; logic so_arvalid, so_arready;
|
||||
logic [255:0] so_rdata; logic [1:0] so_rresp; logic so_rlast, so_rvalid, so_rready;
|
||||
gs_lpddr_scanout_lb #(.FB_BASE(30'd0), .STRIDE_BYTES(STRIDE), .ROW_BEATS(ROW_BEATS),
|
||||
.N_ROWS(H), .PSMCT32(1'b1)) u_scan (
|
||||
.axi_clk(emif_clk), .axi_rst_n(rst_n), .enable(so_enable),
|
||||
.video_clk(video_clk), .frame_start(vsync), .pixel_x(px), .pixel_y(py), .in_window(in_win),
|
||||
.r(so_r), .g(so_g), .b(so_b), .line_valid(so_line_valid), .underflow(so_underflow), .rd_errs(so_rd_errs),
|
||||
.araddr(so_araddr), .arburst(so_arburst), .arid(so_arid), .arlen(so_arlen), .arsize(so_arsize),
|
||||
.arvalid(so_arvalid), .arready(so_arready), .rdata(so_rdata), .rresp(so_rresp),
|
||||
.rlast(so_rlast), .rvalid(so_rvalid), .rready(so_rready)
|
||||
);
|
||||
logic [7:0] rlfsr=8'h3C; always_ff @(posedge emif_clk) rlfsr<={rlfsr[6:0], rlfsr[7]^rlfsr[5]^rlfsr[4]^rlfsr[3]};
|
||||
typedef enum logic [1:0] { R_IDLE, R_WAIT, R_DATA } rst_t; rst_t rst_state;
|
||||
logic [3:0] rdly; logic [29:0] rd_addr_l; int read_beats;
|
||||
logic [2:0] vs_e; wire vs_edge_e = vs_e[1] && !vs_e[2]; int fb_reads, fb_reads_last;
|
||||
always_ff @(posedge emif_clk or negedge rst_n) begin
|
||||
if (!rst_n) begin rst_state<=R_IDLE; so_arready<=0; so_rvalid<=0; so_rlast<=0; so_rresp<=0; so_rdata<=0; rdly<=0;
|
||||
read_beats<=0; vs_e<=0; fb_reads<=0; fb_reads_last<=0; end
|
||||
else begin so_arready<=0; so_rvalid<=0; so_rlast<=0; vs_e<={vs_e[1:0], vsync};
|
||||
if (vs_edge_e) begin fb_reads_last<=fb_reads; fb_reads<=0; end
|
||||
case (rst_state)
|
||||
R_IDLE: if (so_arvalid) begin so_arready<=1; rd_addr_l<=so_araddr; rdly<=rlfsr[2:0]; rst_state<=R_WAIT; end
|
||||
R_WAIT: if (rdly==0) rst_state<=R_DATA; else rdly<=rdly-1'b1;
|
||||
R_DATA: if (so_rready) begin
|
||||
for (int w=0;w<8;w++) begin int aa; aa=rd_addr_l+w*4;
|
||||
so_rdata[w*32 +: 32] <= (aa+3<FB_BYTES) ? {fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]} : 32'd0; end
|
||||
so_rresp<=2'b00; so_rvalid<=1; so_rlast<=1; read_beats<=read_beats+1;
|
||||
if (!vs_edge_e) fb_reads<=fb_reads+1; rst_state<=R_IDLE; end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
logic vid_run=0; logic [11:0] rawx, rawy;
|
||||
always_ff @(posedge video_clk) begin
|
||||
if (!vid_run) begin rawx<=0; rawy<=0; end
|
||||
else if (rawx==H_TOT-1) begin rawx<=0; rawy<=(rawy==V_TOT-1)?12'd0:rawy+1'b1; end
|
||||
else rawx<=rawx+1'b1;
|
||||
end
|
||||
wire active = (rawx>=H_BP)&&(rawx<H_BP+H_ACT)&&(rawy>=V_BP)&&(rawy<V_BP+V_ACT);
|
||||
assign px=active?(rawx-H_BP):12'd0; assign py=(rawy>=V_BP&&rawy<V_BP+V_ACT)?(rawy-V_BP):12'd0;
|
||||
assign in_win=active; assign vsync=vid_run&&(rawx==0)&&(rawy==0);
|
||||
logic [11:0] px_q, py_q; logic inwin_q, run_q;
|
||||
always_ff @(posedge video_clk) begin px_q<=px; py_q<=py; inwin_q<=in_win; run_q<=vid_run; end
|
||||
int checked; initial checked=0; logic scoring=0;
|
||||
always_ff @(posedge video_clk) if (scoring && run_q) begin
|
||||
logic [7:0] er,eg,eb; logic [31:0] w;
|
||||
if (inwin_q) begin int aa; aa=py_q*STRIDE+px_q*4; w={fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]}; er=w[7:0]; eg=w[15:8]; eb=w[23:16]; end
|
||||
else begin er=0; eg=0; eb=0; end
|
||||
checked++;
|
||||
if (so_r!==er||so_g!==eg||so_b!==eb) begin
|
||||
if (errors<12) $error("[scan] px(%0d,%0d) got(%02x,%02x,%02x) exp(%02x,%02x,%02x)", px_q,py_q, so_r,so_g,so_b, er,eg,eb); errors++; end
|
||||
end
|
||||
|
||||
// per-epoch idx/pal (for the oracle) + composed reference (owner epoch in [26:24], multi in [28]) + per-epoch refmaps
|
||||
logic [31:0] idx [0:2][0:(512*512/4)-1]; logic [31:0] pal [0:2][0:255];
|
||||
logic [31:0] refmap [0:FB_WORDS-1]; logic [31:0] refmap_ep [0:2][0:FB_WORDS-1];
|
||||
logic [63:0] stg_buf [0:STG_WORDS-1]; // one epoch's staging list, streamed through the write port
|
||||
// temps: iverilog can't $readmemh into a 2D-array slice, so load flat then copy into the [e] plane
|
||||
logic [31:0] t_idx [0:(512*512/4)-1]; logic [31:0] t_pal [0:255]; logic [31:0] t_rm [0:FB_WORDS-1];
|
||||
|
||||
// ---- stream one epoch's list into the feeder staging via the WRITE PORT (word0 first, full STG reset) ----
|
||||
task automatic stream_list(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/feeder_sh3_ztrio%0d.mem", k);
|
||||
for (int i=0;i<STG_WORDS;i++) stg_buf[i]=64'd0;
|
||||
$readmemh(fn, stg_buf);
|
||||
@(negedge clk);
|
||||
for (int i=0;i<STG_WORDS;i++) begin
|
||||
stg_we<=1'b1; stg_waddr<=i[11:0]; stg_wdata<=stg_buf[i]; @(negedge clk);
|
||||
end
|
||||
stg_we<=1'b0; stg_waddr<=12'd0; stg_wdata<=64'd0; @(negedge clk);
|
||||
// sanity: header word0 (ntris) landed correctly in the DUT staging
|
||||
if (dut.g_feeder.feeder_stg[0][31:0] !== stg_buf[0][31:0]) begin
|
||||
$error("[sched] epoch %0d: staged word0=%08x exp %08x (write-port mis-slot)", k, dut.g_feeder.feeder_stg[0][31:0], stg_buf[0][31:0]); errors++; end
|
||||
$display("[sched] epoch %0d: streamed %0d words via write port; word0(ntris)=%0d", k, STG_WORDS, dut.g_feeder.feeder_stg[0][31:0]);
|
||||
endtask
|
||||
|
||||
// ---- one-scene runner: GO, wait render+drain, REQUIRE fresh frame_drained (high->low->high, or low->high on first) ----
|
||||
task automatic run_scene(input int k, input int exp_records);
|
||||
int r0, f0, d=0; logic fd_before;
|
||||
r0=fd_rises; f0=fd_falls; fd_before=fbw_drained;
|
||||
@(negedge clk); feeder_go_tb=1'b1; @(negedge clk); feeder_go_tb=1'b0;
|
||||
d=0; while (feeder_ready_tb && d<4000) begin @(posedge clk); d++; end
|
||||
if (feeder_ready_tb!==1'b0) begin $error("[sched] epoch %0d: feeder_ready did not drop after GO", k); errors++; end
|
||||
if (fd_before) begin
|
||||
d=0; while (fd_falls==f0 && d<800000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_falls==f0) begin $error("[sched] epoch %0d: frame_drained never fell from stale high — STALE drain", k); errors++; end
|
||||
end
|
||||
d=0; while (fd_rises<=r0 && d<800000) begin @(posedge emif_clk); d++; end
|
||||
if (fd_rises<=r0) begin $error("[sched] epoch %0d: frame_drained never rose — scene did not drain", k); errors++; end
|
||||
repeat(100) @(posedge clk);
|
||||
if (feeder_records_w!==exp_records) begin $error("[sched] epoch %0d: records_emitted=%0d exp %0d", k, feeder_records_w, exp_records); errors++; end
|
||||
if (fbw_ovf!==0 || fbw_bresp_err!==0) begin $error("[sched] epoch %0d: writer ovf=%0d bresp=%0d", k, fbw_ovf, fbw_bresp_err); errors++; end
|
||||
$display("[sched] epoch %0d: fresh drain (falls %0d->%0d, rises %0d->%0d), records=%0d, ovf=0", k, f0, fd_falls, r0, fd_rises, feeder_records_w);
|
||||
endtask
|
||||
|
||||
task automatic fill_cache(input int k, input logic [31:0] exp_crc);
|
||||
int d=0;
|
||||
@(posedge clk) fill_start<=1'b1; @(posedge clk) fill_start<=1'b0;
|
||||
d=0; while (fill_done && d<1000) begin @(posedge clk); d++; end // wait it drops (busy)
|
||||
d=0; while (!fill_done && d<400000) begin @(posedge clk); d++; end // then rises (done)
|
||||
if (!fill_done) begin $error("[sched] fill %0d: fill_done never rose (rearm failed)", k); errors++; end
|
||||
if (fill_crc_w!==exp_crc) begin $error("[sched] fill %0d: crc=%08x exp %08x", k, fill_crc_w, exp_crc); errors++; end
|
||||
if (fill_beats!==N_BEATS) begin $error("[sched] fill %0d: beats=%0d exp %0d", k, fill_beats, N_BEATS); errors++; end
|
||||
if (fill_bytes!==TEX_BYTES) begin $error("[sched] fill %0d: bytes=%0d exp %0d", k, fill_bytes, TEX_BYTES); errors++; end
|
||||
if (tex_rd_errs!==0) begin $error("[sched] fill %0d: rd_errs=%0d", k, tex_rd_errs); errors++; end
|
||||
$display("[sched] cache fill %0d: fresh done, crc=0x%08x (exp %08x), beats=%0d bytes=%0d rd_errs=0", k, fill_crc_w, exp_crc, fill_beats, fill_bytes);
|
||||
endtask
|
||||
|
||||
// texel lookup in epoch e's palette (module-level arrays, no per-call copy)
|
||||
function automatic logic [23:0] cell_e(input int e, input integer u, input integer v);
|
||||
integer lin; logic [31:0] w; logic [7:0] ix;
|
||||
lin=v*TW+u; w=idx[e][lin/4]; ix=w[(8*(lin%4)) +: 8]; cell_e=pal[e][ix][23:0];
|
||||
endfunction
|
||||
|
||||
// run ONE epoch fully (rebind tex -> fresh fill+CRC -> stream list -> GO -> fresh ordered drain)
|
||||
task automatic run_epoch(input int k);
|
||||
string fn; fn=$sformatf("../../data/top_psmct32_raster_demo/sh3_ztrio%0d_tex_lpddr.mem", k);
|
||||
$readmemh(fn, lpddr_mem);
|
||||
fill_cache(k, EP_CRC[k]);
|
||||
stream_list(k);
|
||||
run_scene(k, EP_REC[k]);
|
||||
endtask
|
||||
|
||||
string only_m; string fdump; int only_k; // +ONLY=ALL | +ONLY=<k> ; +FBDUMP=<file>
|
||||
initial begin
|
||||
errors=0; feeder_go_tb=1'b0; fill_start=1'b0; wr_arm=0; scan_en=0; stg_we=0; stg_waddr=0; stg_wdata=0; cap_fh=0; cap_ep=0;
|
||||
if (!$value$plusargs("ONLY=%s", only_m)) only_m="ALL";
|
||||
only_k = (only_m=="ALL") ? -1 : only_m.atoi();
|
||||
for (int i=0;i<FB_WORDS;i++) ideal[i]=32'd0;
|
||||
for (int i=0;i<FB_BYTES;i++) fb[i]=8'h00; // PRECLEAR EXACTLY ONCE
|
||||
idx[0][0]='x;
|
||||
for (int e=0;e<NEP;e++) begin
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_ztrio%0d_idx.mem", e), t_idx);
|
||||
for (int i=0;i<(512*512/4);i++) idx[e][i]=t_idx[i];
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_ztrio%0d_pal.mem", e), t_pal);
|
||||
for (int i=0;i<256;i++) pal[e][i]=t_pal[i];
|
||||
$readmemh($sformatf("../../data/top_psmct32_raster_demo/sh3_ztrio%0d_refmap.mem", e), t_rm);
|
||||
for (int i=0;i<FB_WORDS;i++) refmap_ep[e][i]=t_rm[i];
|
||||
end
|
||||
$readmemh("../../data/top_psmct32_raster_demo/sh3_ztrio_refmap.mem", refmap);
|
||||
$display("[sched] ONLY=%s (only_k=%0d), N_EPOCHS=%0d", only_m, only_k, NEP);
|
||||
if (idx[0][0]===32'bx) begin $display("[tb_top_psmct32_sh3_ztrio_cap] SKIP — sh3_ztrio*.mem absent (run gs_make_sh3_ztriouler_fixture.py --emit)"); $finish; end
|
||||
|
||||
rst_n=1'b0; core_go=1'b0; repeat(6) @(posedge clk); rst_n=1'b1; repeat(8) @(posedge clk);
|
||||
|
||||
// boot the bootlet (uploads ALL N relocated CLUTs to their distinct CBPs)
|
||||
@(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);
|
||||
wait (feeder_ready_tb==1'b1); repeat(50) @(posedge clk);
|
||||
wr_arm<=1'b1; repeat(40) @(posedge clk);
|
||||
|
||||
// ===== scheduler: iterate the epoch descriptors in dump order (or a single epoch for the isolation check) =====
|
||||
cap_fh = $fopen("ztrio_frags.txt","w"); // Ch357 fragment capture for external persp_z5 verification
|
||||
if (only_k<0) begin
|
||||
for (int k=0;k<NEP;k++) begin
|
||||
cap_ep = k;
|
||||
run_epoch(k);
|
||||
if (k<NEP-1 && scan_en!==1'b0) begin $error("[sched] scanout enabled before the last epoch"); errors++; end
|
||||
end
|
||||
end else begin
|
||||
run_epoch(only_k);
|
||||
end
|
||||
|
||||
// enable scanout only after the final fresh drain
|
||||
scan_en<=1'b1; repeat(20) @(posedge clk);
|
||||
if (only_k<0) begin
|
||||
if (fd_rises<NEP) begin $error("[sched] expected %0d ordered drains: rises=%0d", NEP, fd_rises); errors++; end
|
||||
if (fd_falls<NEP-1)begin $error("[sched] epochs after the first never cleared stale drains (falls=%0d)", fd_falls); errors++; end
|
||||
end else if (fd_rises<1) begin $error("[sched] isolated epoch did not drain: rises=%0d", fd_rises); errors++; end
|
||||
|
||||
// ===== scanout: score final FB vs the composed reference (per-pixel OWNER epoch), overlap separately =====
|
||||
vid_run=1;
|
||||
begin int d=0; while(!vsync && d<300000) begin @(posedge video_clk); d++; end end
|
||||
@(posedge video_clk); while(!vsync) @(posedge video_clk);
|
||||
begin scoring=1; @(posedge video_clk); while(!vsync) @(posedge video_clk); scoring=0; repeat(60) @(posedge emif_clk);
|
||||
if (fb_reads_last!==BEATS_PER_FRAME) begin $error("[sched] scanout beats/frame=%0d exp %0d", fb_reads_last, BEATS_PER_FRAME); errors++; end
|
||||
end
|
||||
if (so_underflow!==0) begin $error("[sched] scanout underflow"); errors++; end
|
||||
if (so_rd_errs !==0) begin $error("[sched] scanout rd_errs=%0d", so_rd_errs); errors++; end
|
||||
if (checked < H_ACT*V_ACT) begin $error("[sched] only %0d px checked (exp >= %0d)", checked, H_ACT*V_ACT); errors++; end
|
||||
|
||||
// oracle (multi-texture correctness): a covered pixel must equal ONE of the COVERING epochs' texels. Each epoch
|
||||
// stores its OWN (tu,tv) per pixel in refmap_ep[k]; we accept if the RTL colour matches any covering epoch's texel
|
||||
// in the <=1-texel neighbourhood (owner-first, then neighbours — handles coverage-edge owner ambiguity between
|
||||
// adjacent-order epochs, exactly the tolerance Ch355 used). multi-epoch pixels (>=2 covering) scored separately as
|
||||
// the accumulation composite proof. clut_bad = an RTL-written pixel (ideal!=0) whose colour is in NO epoch palette
|
||||
// (palettes are pairwise-distinct, so this still proves the pixel came from a real texture+CLUT, not garbage).
|
||||
begin
|
||||
int m_tot,m_ok,o_tot,o_ok,cb,D,ncov; bit mt; m_tot=0;m_ok=0;o_tot=0;o_ok=0;cb=0;
|
||||
for (int y=0;y<H;y++) for (int x=0;x<W;x++) begin
|
||||
int o; logic [23:0] fbc; bit cov; o=y*W+x;
|
||||
cov = (only_k<0) ? refmap[o][31] : refmap_ep[only_k][o][31];
|
||||
if (cov) begin
|
||||
fbc=ideal[o][23:0]; m_tot++; D=9; ncov=0;
|
||||
for (int e=0;e<NEP;e++) begin
|
||||
if (!(only_k>=0 && e!=only_k) && refmap_ep[e][o][31]) begin
|
||||
int tu,tv; tu=(refmap_ep[e][o]>>9)&9'h1FF; tv=refmap_ep[e][o]&9'h1FF; ncov++;
|
||||
for (int rad=0;rad<=1;rad++) for (int du=-rad;du<=rad;du++) for (int dv=-rad;dv<=rad;dv++) begin
|
||||
int ch; ch=(du<0?-du:du); if((dv<0?-dv:dv)>ch) ch=(dv<0?-dv:dv);
|
||||
if (ch==rad && (tu+du)>=0 && (tu+du)<TW && (tv+dv)>=0 && (tv+dv)<TH) begin
|
||||
mt=(fbc===cell_e(e,tu+du,tv+dv)); if (mt && rad<D) D=rad;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if (D<=1) m_ok++;
|
||||
if (only_k<0 && refmap[o][28]) begin o_tot++; if (D<=1) o_ok++; end // multi-epoch (>=2 covering)
|
||||
if (ideal[o]!==32'd0) begin bit f; f=1'b0;
|
||||
for (int e=0;e<NEP;e++) for (int i=0;i<256;i++) if (pal[e][i][23:0]===fbc) f=1'b1;
|
||||
if(!f) cb++; end
|
||||
end
|
||||
end
|
||||
$display("[sched][oracle] ONLY=%s <=1texel ALL=%0d/%0d (%.1f%%) MULTI(>=2)=%0d/%0d (%.1f%%) clut_bad=%0d",
|
||||
only_m, m_ok,m_tot,(m_tot>0)?100.0*m_ok/m_tot:0.0, o_ok,o_tot,(o_tot>0)?100.0*o_ok/o_tot:0.0, cb);
|
||||
if (cb!==0) begin $error("[sched][oracle] ONLY=%s: %0d covered px in NO epoch palette", only_m, cb); errors++; end
|
||||
if (only_k<0) begin
|
||||
if (o_tot<500) begin $error("[sched][oracle] only %0d multi-epoch px — accumulation not exercised", o_tot); errors++; end
|
||||
// MULTI <=1texel is TEXTURE FIDELITY (RTL 11-bit reciprocal LUT precision), NOT accumulation correctness.
|
||||
// It tracks the per-epoch isolated fidelity (ONLY=k gives 93.0/93.3/95.6%); the multi-epoch subset sits at
|
||||
// the same reciprocal floor (~92%). The ACCUMULATION proof is separate and EXACT: compose_sched.py shows
|
||||
// the joint ALL render == the composited ONLY=k isolation dumps 100% BIT-FOR-BIT. So gate this at the
|
||||
// documented reciprocal floor, not an accumulation-implying bar.
|
||||
if (o_tot>0 && (100.0*o_ok/o_tot)<90.0) begin $error("[sched][oracle] MULTI <=1texel %.1f%% < 90%% reciprocal floor", 100.0*o_ok/o_tot); errors++; end
|
||||
end
|
||||
end
|
||||
// FB dump for the composition==isolated check (per-epoch dumps composed externally -> compare vs ALL dump)
|
||||
if ($value$plusargs("FBDUMP=%s", fdump)) begin
|
||||
int fh; fh=$fopen(fdump,"w");
|
||||
for (int y=0;y<H;y++) for (int x=0;x<W;x++) begin int aa; aa=y*STRIDE+x*4; $fwrite(fh,"%08x\n",{fb[aa+3],fb[aa+2],fb[aa+1],fb[aa]}); end
|
||||
$fclose(fh); $display("[sched] dumped RTL FB (%0dx%0d) -> %s", W, H, fdump);
|
||||
end
|
||||
|
||||
$display("[tb_top_psmct32_sh3_ztrio_cap] checked=%0d beats/frame=%0d (exp %0d) fresh_drains(rise/fall)=%0d/%0d records=%0d underflow=%0b ovf=%0d errors=%0d",
|
||||
checked, fb_reads_last, BEATS_PER_FRAME, fd_rises, fd_falls, feeder_records_w, so_underflow, fbw_ovf, errors);
|
||||
if (cap_fh!=0) $fclose(cap_fh);
|
||||
if (errors==0) $display("[tb_top_psmct32_sh3_ztrio_cap] PASS"); else $display("[tb_top_psmct32_sh3_ztrio_cap] FAIL");
|
||||
$finish;
|
||||
end
|
||||
initial begin #160000000; $error("[tb_top_psmct32_sh3_ztrio_cap] TIMEOUT"); $finish; end
|
||||
endmodule : tb_top_psmct32_sh3_ztrio_cap
|
||||
Reference in New Issue
Block a user