ec82764bef
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression (272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps, and all dump-derived textures/traces) is excluded via .gitignore and stays local. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
131 lines
6.3 KiB
Systemverilog
131 lines
6.3 KiB
Systemverilog
// 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
|