Files
retroDE_ps2/sim/tb/gif_gs/tb_clut_loader_csm1.sv
thejayman77 ec82764bef Initial commit: retroDE_ps2 — first-of-its-kind PS2 GS FPGA core (DE25-Nano / Agilex 5)
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression
(272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps,
and all dump-derived textures/traces) is excluded via .gitignore and stays local.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 20:10:50 -04:00

131 lines
6.3 KiB
Systemverilog
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// retroDE_ps2 — Ch350 synthetic TB: CSM1 16×16 CT32 grid CLUT load.
//
// Proves the parameter-gated CSM1-grid path in clut_loader_stub maps palette index i to the VRAM word the
// real GS grid order points at — NOT the linear i*4 word. A deliberately permuted layout makes the two
// orders disagree for most entries, so a pass can only happen if the grid swizzle is correct.
//
// Two instances share one behavioural VRAM model:
// u_csm1 : CLUT_CSM1_ENABLE=1, driven with csm=0 (CSM1) → must read via the grid offset.
// u_csm2 : CLUT_CSM1_ENABLE=1, driven with csm=1 (CSM2) → must STILL read linear i*4 (regression guard:
// enabling the param must not perturb CSM2 behaviour).
// A third check: u_csm1 driven with csm=0 but CLUT_CSM1_ENABLE=0 (separate instance u_off) must NOT load at
// all (load_busy never asserts) — byte-identical-to-legacy proof.
`timescale 1ns/1ps
module tb_clut_loader_csm1;
logic clk = 0, rst_n = 0;
always #5 clk = ~clk;
// ---- behavioural VRAM: 256 PSMCT32 words at CBP=0 (offsets 0..1020) ----
logic [31:0] vram [0:255];
// CSM1 grid offset (bytes) for entry i — MUST match clut_loader_stub.addr_offset_csm1 exactly.
function automatic logic [31:0] off_csm1(input logic [7:0] i);
logic [31:0] blk, by32, bx4;
blk = ({30'd0, i[7], i[3]}) << 8; // block * 256
by32 = ({29'd0, i[6:4]}) << 5; // iy[2:0] * 32
bx4 = ({29'd0, i[2:0]}) << 2; // ix[2:0] * 4
return blk + by32 + bx4;
endfunction
// The "logical palette" we want index i to resolve to. Place it at the GRID address for i, so that
// reading linearly (word i) lands on a DIFFERENT logical entry for every permuted index.
logic [31:0] expect_pal [0:255]; // grid expectation
integer k;
initial begin
for (k = 0; k < 256; k = k + 1) expect_pal[k] = 32'hA0000000 | k; // distinct per index
// scatter expect_pal[i] into VRAM at the grid byte-offset for i
for (k = 0; k < 256; k = k + 1) vram[off_csm1(k[7:0]) >> 2] = expect_pal[k];
end
// ---- DUT ports (shared stimulus) ----
logic tex0_wr_pulse;
logic [13:0] tex0_cbp;
logic [3:0] tex0_cpsm;
logic tex0_csm;
logic [4:0] tex0_csa;
logic [2:0] tex0_cld;
logic [31:0] a_csm1, a_csm2, a_off;
logic we_csm1, we_csm2, we_off;
logic [7:0] wi_csm1, wi_csm2, wi_off;
logic [31:0] wd_csm1, wd_csm2, wd_off;
logic busy_csm1, busy_csm2, busy_off;
// combinational VRAM read for each instance's addr
wire [31:0] rd_csm1 = vram[a_csm1[9:2]];
wire [31:0] rd_csm2 = vram[a_csm2[9:2]];
wire [31:0] rd_off = vram[a_off [9:2]];
clut_loader_stub #(.CLUT_CSM1_ENABLE(1'b1)) u_csm1 (
.clk(clk), .rst_n(rst_n), .tex0_wr_pulse(tex0_wr_pulse), .tex0_cbp(tex0_cbp),
.tex0_cpsm(tex0_cpsm), .tex0_csm(1'b0), .tex0_csa(tex0_csa), .tex0_cld(tex0_cld),
.vram_read_addr(a_csm1), .vram_read_data(rd_csm1),
.clut_write_en(we_csm1), .clut_write_idx(wi_csm1), .clut_write_data(wd_csm1), .load_busy(busy_csm1));
clut_loader_stub #(.CLUT_CSM1_ENABLE(1'b1)) u_csm2 (
.clk(clk), .rst_n(rst_n), .tex0_wr_pulse(tex0_wr_pulse), .tex0_cbp(tex0_cbp),
.tex0_cpsm(tex0_cpsm), .tex0_csm(1'b1), .tex0_csa(tex0_csa), .tex0_cld(tex0_cld),
.vram_read_addr(a_csm2), .vram_read_data(rd_csm2),
.clut_write_en(we_csm2), .clut_write_idx(wi_csm2), .clut_write_data(wd_csm2), .load_busy(busy_csm2));
clut_loader_stub #(.CLUT_CSM1_ENABLE(1'b0)) u_off (
.clk(clk), .rst_n(rst_n), .tex0_wr_pulse(tex0_wr_pulse), .tex0_cbp(tex0_cbp),
.tex0_cpsm(tex0_cpsm), .tex0_csm(1'b0), .tex0_csa(tex0_csa), .tex0_cld(tex0_cld),
.vram_read_addr(a_off), .vram_read_data(rd_off),
.clut_write_en(we_off), .clut_write_idx(wi_off), .clut_write_data(wd_off), .load_busy(busy_off));
// capture each instance's writes
logic [31:0] got_csm1 [0:255];
logic [31:0] got_csm2 [0:255];
integer wr_off_count = 0;
always_ff @(posedge clk) begin
if (we_csm1) got_csm1[wi_csm1] <= wd_csm1;
if (we_csm2) got_csm2[wi_csm2] <= wd_csm2;
if (we_off) wr_off_count <= wr_off_count + 1; // must stay 0
end
integer i, errors = 0, permuted = 0;
initial begin
tex0_wr_pulse = 0; tex0_cbp = 14'd0; tex0_cpsm = 4'd0; tex0_csm = 1'b0;
tex0_csa = 5'd0; tex0_cld = 3'd1; // CLD=1 = always full load
repeat (4) @(posedge clk);
rst_n = 1;
@(posedge clk);
// fire one TEX0 commit pulse to all three loaders
tex0_wr_pulse <= 1'b1; @(posedge clk); tex0_wr_pulse <= 1'b0;
// a full load is 256 cycles; wait comfortably past it. (Can't `wait(busy==0)` — busy is already 0
// in S_IDLE before the load starts, so that returns immediately.)
repeat (300) @(posedge clk);
// (1) CSM1 grid: got_csm1[i] must equal expect_pal[i]
for (i = 0; i < 256; i = i + 1) begin
if (got_csm1[i] !== expect_pal[i]) begin
if (errors < 8) $display(" CSM1 MISMATCH idx %0d: got %08x exp %08x", i, got_csm1[i], expect_pal[i]);
errors = errors + 1;
end
end
// (2) CSM2 linear: got_csm2[i] must equal vram[i] (the raw linear word). Count how many indices
// where linear != grid — proves the test is non-trivial (the orders genuinely disagree).
for (i = 0; i < 256; i = i + 1) begin
if (got_csm2[i] !== vram[i]) begin
if (errors < 16) $display(" CSM2 LINEAR BROKEN idx %0d: got %08x exp(linear) %08x", i, got_csm2[i], vram[i]);
errors = errors + 1;
end
if (vram[i] !== expect_pal[i]) permuted = permuted + 1;
end
// (3) param OFF: csm=0 must have produced NO writes at all
if (wr_off_count != 0) begin
$display(" PARAM-OFF BROKEN: csm=0 produced %0d writes (expected 0)", wr_off_count);
errors = errors + 1;
end
$display("[tb_clut_loader_csm1] grid-vs-linear disagreeing indices=%0d/256 (test non-triviality)", permuted);
if (errors == 0 && permuted > 64)
$display("[tb_clut_loader_csm1] PASS");
else
$display("[tb_clut_loader_csm1] FAIL errors=%0d permuted=%0d", errors, permuted);
$finish;
end
endmodule