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:
2026-07-20 19:56:46 -04:00
parent ec82764bef
commit ba74bbd5aa
476 changed files with 696247 additions and 130119 deletions
+423
View File
@@ -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