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>
509 lines
19 KiB
Systemverilog
509 lines
19 KiB
Systemverilog
// retroDE_ps2 — tb_gs_tex0_clut (Ch98)
|
|
//
|
|
// Locks the contract for gs_stub's TEX0_1 latch + CLUT-side
|
|
// field decoders (CBP/CPSM/CSM/CSA/CLD). Ch97 added the CLUT
|
|
// lookup path through pcrtc with a TB-driven sideband
|
|
// `clut_csa`; Ch98 wires that field through the GIF-A+D TEX0_1
|
|
// register the way a real PS2 driver would.
|
|
//
|
|
// Two phases:
|
|
// Phase 1 — register-decode verification. Drive a known TEX0_1
|
|
// pattern via gs_stub.gif_reg_*, then assert that
|
|
// tex0_1_q[63:0] holds the same payload AND the
|
|
// sub-field decoder outputs (CBP/CPSM/CSM/CSA/CLD)
|
|
// match the bit ranges in the contract.
|
|
// Phase 2 — end-to-end CSA-from-TEX0 → pcrtc lookup. Wire
|
|
// pcrtc.clut_csa from gs_stub.tex0_1_csa_q (instead
|
|
// of a TB-side reg). Drive TEX0_1 with CSA=1, set
|
|
// VRAM[0] = 0x00, program CLUT[0x10] with a known
|
|
// entry, and verify pcrtc r/g/b at displayed (0,0)
|
|
// matches CLUT[0x10] — proves CSA flowed from the
|
|
// GIF reg into the CLUT lookup math.
|
|
//
|
|
// Out of scope (deferred): CBP/CPSM are extracted but pcrtc
|
|
// doesn't yet load from VRAM[CBP] — the TB still programs the
|
|
// clut_stub directly. CLD is latched but doesn't trigger any
|
|
// load. Those are Ch99+ work.
|
|
|
|
`timescale 1ns/1ps
|
|
|
|
module tb_gs_tex0_clut;
|
|
|
|
localparam int PCRTC_H_ACTIVE = 16;
|
|
localparam int PCRTC_V_ACTIVE = 8;
|
|
|
|
logic clk;
|
|
logic rst_n;
|
|
initial clk = 1'b0;
|
|
always #5 clk = ~clk;
|
|
|
|
// gs_stub IO
|
|
logic gif_reg_wr_en;
|
|
logic [7:0] gif_reg_num;
|
|
logic [63:0] gif_reg_data;
|
|
logic priv_reg_wr_en;
|
|
logic [15:0] priv_reg_wr_addr;
|
|
logic [63:0] priv_reg_wr_data;
|
|
|
|
logic [7:0] bg_r, bg_g, bg_b;
|
|
logic [63:0] pmode_q, dispfb1_q, display1_q;
|
|
logic [63:0] prim_q, rgbaq_q, xyz2_q, xyzf2_q, frame_1_q, zbuf_1_q;
|
|
logic [63:0] tex0_1_q;
|
|
logic [13:0] tex0_1_cbp_q;
|
|
logic [3:0] tex0_1_cpsm_q;
|
|
logic tex0_1_csm_q;
|
|
logic [4:0] tex0_1_csa_q;
|
|
logic [2:0] tex0_1_cld_q;
|
|
logic prim_complete;
|
|
logic [31:0] prim_complete_count;
|
|
logic [63:0] prim_v0_q, prim_v1_q, prim_v2_q;
|
|
logic [63:0] prim_color_q;
|
|
logic [63:0] prim_color_v0_q, prim_color_v1_q, prim_color_v2_q;
|
|
trace_pkg::vertex_t prim_v0_decoded_q, prim_v1_decoded_q, prim_v2_decoded_q;
|
|
trace_pkg::color_t prim_v0_color_decoded_q, prim_v1_color_decoded_q, prim_v2_color_decoded_q;
|
|
logic pixel_emit;
|
|
logic [31:0] pixel_emit_count;
|
|
logic [11:0] pixel_x_q, pixel_y_q;
|
|
logic [63:0] pixel_color_q;
|
|
logic [8:0] pixel_fbp_q;
|
|
logic [5:0] pixel_fbw_q, pixel_psm_q;
|
|
logic [31:0] pixel_fb_addr_q;
|
|
logic raster_pixel_emit;
|
|
logic [31:0] raster_pixel_emit_count;
|
|
logic [11:0] raster_pixel_x_q, raster_pixel_y_q;
|
|
logic [63:0] raster_pixel_color_q;
|
|
logic [31:0] raster_pixel_fb_addr_q;
|
|
logic [3:0] raster_pixel_be_q;
|
|
logic [5:0] raster_pixel_psm_q;
|
|
logic raster_active;
|
|
logic raster_overflow;
|
|
logic raster_degenerate;
|
|
logic gs_ev_valid;
|
|
trace_pkg::subsys_e gs_ev_subsys;
|
|
trace_pkg::event_e gs_ev_event;
|
|
logic [63:0] gs_ev_arg0, gs_ev_arg1, gs_ev_arg2, gs_ev_arg3;
|
|
logic [31:0] gs_ev_flags;
|
|
|
|
gs_stub u_gs (
|
|
.clk(clk), .rst_n(rst_n),
|
|
.reg_wr_en (priv_reg_wr_en),
|
|
.reg_wr_addr(priv_reg_wr_addr),
|
|
.reg_wr_data(priv_reg_wr_data),
|
|
.gif_reg_wr_en(gif_reg_wr_en),
|
|
.gif_reg_num(gif_reg_num),
|
|
.gif_reg_data(gif_reg_data),
|
|
.bg_r(bg_r), .bg_g(bg_g), .bg_b(bg_b),
|
|
.pmode_q(pmode_q), .dispfb1_q(dispfb1_q), .display1_q(display1_q),
|
|
.prim_q(prim_q), .rgbaq_q(rgbaq_q),
|
|
.xyz2_q(xyz2_q), .xyzf2_q(xyzf2_q),
|
|
.frame_1_q(frame_1_q), .zbuf_1_q(zbuf_1_q),
|
|
.tex0_1_q(tex0_1_q),
|
|
.tex0_1_cbp_q(tex0_1_cbp_q),
|
|
.tex0_1_cpsm_q(tex0_1_cpsm_q),
|
|
.tex0_1_csm_q(tex0_1_csm_q),
|
|
.tex0_1_csa_q(tex0_1_csa_q),
|
|
.tex0_1_cld_q(tex0_1_cld_q),
|
|
.prim_complete(prim_complete),
|
|
.prim_complete_count(prim_complete_count),
|
|
.prim_v0_q(prim_v0_q), .prim_v1_q(prim_v1_q), .prim_v2_q(prim_v2_q),
|
|
.prim_color_q(prim_color_q),
|
|
.prim_color_v0_q(prim_color_v0_q),
|
|
.prim_color_v1_q(prim_color_v1_q),
|
|
.prim_color_v2_q(prim_color_v2_q),
|
|
.prim_v0_decoded_q(prim_v0_decoded_q),
|
|
.prim_v1_decoded_q(prim_v1_decoded_q),
|
|
.prim_v2_decoded_q(prim_v2_decoded_q),
|
|
.prim_v0_color_decoded_q(prim_v0_color_decoded_q),
|
|
.prim_v1_color_decoded_q(prim_v1_color_decoded_q),
|
|
.prim_v2_color_decoded_q(prim_v2_color_decoded_q),
|
|
.pixel_emit(pixel_emit),
|
|
.pixel_emit_count(pixel_emit_count),
|
|
.pixel_x_q(pixel_x_q), .pixel_y_q(pixel_y_q),
|
|
.pixel_color_q(pixel_color_q),
|
|
.pixel_fbp_q(pixel_fbp_q),
|
|
.pixel_fbw_q(pixel_fbw_q),
|
|
.pixel_psm_q(pixel_psm_q),
|
|
.pixel_fb_addr_q(pixel_fb_addr_q),
|
|
.raster_pixel_emit(raster_pixel_emit),
|
|
.raster_pixel_emit_count(raster_pixel_emit_count),
|
|
.raster_pixel_x_q(raster_pixel_x_q),
|
|
.raster_pixel_y_q(raster_pixel_y_q),
|
|
.raster_pixel_color_q(raster_pixel_color_q),
|
|
.raster_pixel_fb_addr_q(raster_pixel_fb_addr_q),
|
|
.raster_pixel_be_q(raster_pixel_be_q),
|
|
.raster_pixel_psm_q(raster_pixel_psm_q),
|
|
.raster_active(raster_active),
|
|
.raster_overflow(raster_overflow),
|
|
.raster_degenerate(raster_degenerate),
|
|
.ev_valid(gs_ev_valid),
|
|
.ev_subsys(gs_ev_subsys),
|
|
.ev_event(gs_ev_event),
|
|
.ev_arg0(gs_ev_arg0), .ev_arg1(gs_ev_arg1),
|
|
.ev_arg2(gs_ev_arg2), .ev_arg3(gs_ev_arg3),
|
|
.ev_flags(gs_ev_flags)
|
|
);
|
|
|
|
// VRAM (TB-direct write port; not wired to gs_stub raster).
|
|
logic vram_we;
|
|
logic [31:0] vram_waddr;
|
|
logic [31:0] vram_wdata;
|
|
logic [3:0] vram_wbe;
|
|
logic [31:0] vram_raddr;
|
|
logic [31:0] vram_rdata;
|
|
|
|
vram_stub #(.BYTES(4096)) u_vram (
|
|
.clk(clk), .rst_n(rst_n),
|
|
.write_en (vram_we),
|
|
.write_addr(vram_waddr),
|
|
.write_data(vram_wdata),
|
|
.write_be (vram_wbe),
|
|
.write_mask(32'hFFFF_FFFF),
|
|
.read_addr (vram_raddr),
|
|
.read_data (vram_rdata),
|
|
.read2_addr(32'd0),
|
|
.read2_data()
|
|
);
|
|
|
|
// CLUT (TB-direct write port — Ch99 candidate is wiring this
|
|
// to a VRAM→CLUT load triggered by TEX0_1.CLD).
|
|
logic clut_we;
|
|
logic [7:0] clut_widx;
|
|
logic [31:0] clut_wdata;
|
|
logic [7:0] clut_ridx;
|
|
logic [31:0] clut_rdata;
|
|
|
|
clut_stub u_clut (
|
|
.clk(clk), .rst_n(rst_n),
|
|
.write_en (clut_we),
|
|
.write_idx (clut_widx),
|
|
.write_data(clut_wdata),
|
|
.read_idx (clut_ridx),
|
|
.read_data (clut_rdata)
|
|
);
|
|
|
|
// pcrtc — clut_csa is wired from gs_stub.tex0_1_csa_q (Ch98)
|
|
// instead of a TB-side reg. clut_enable stays TB-driven for
|
|
// now (Ch99 candidate: derive from PSM + CLD + load done).
|
|
logic clut_enable;
|
|
logic hsync_o, vsync_o, de_o;
|
|
logic [7:0] r_o, g_o, b_o;
|
|
logic pcrtc_ev_valid;
|
|
trace_pkg::subsys_e pcrtc_ev_subsys;
|
|
trace_pkg::event_e pcrtc_ev_event;
|
|
logic [63:0] pcrtc_ev_arg0, pcrtc_ev_arg1;
|
|
logic [63:0] pcrtc_ev_arg2, pcrtc_ev_arg3;
|
|
logic [31:0] pcrtc_ev_flags;
|
|
|
|
gs_pcrtc_stub #(
|
|
.H_ACTIVE(PCRTC_H_ACTIVE), .H_FRONT(1), .H_SYNC(1), .H_BACK(1),
|
|
.V_ACTIVE(PCRTC_V_ACTIVE), .V_FRONT(1), .V_SYNC(1), .V_BACK(1)
|
|
) u_pcrtc (
|
|
.clk(clk), .rst_n(rst_n),
|
|
.pmode_q (pmode_q),
|
|
.dispfb1_q (dispfb1_q),
|
|
.display1_q (display1_q),
|
|
.vram_read_addr(vram_raddr),
|
|
.vram_read_data(vram_rdata),
|
|
.clut_enable (clut_enable),
|
|
.clut_csa (tex0_1_csa_q), // ← gs_stub TEX0_1 decoded field
|
|
.clut_read_idx (clut_ridx),
|
|
.clut_read_data(clut_rdata),
|
|
.hsync(hsync_o), .vsync(vsync_o), .de(de_o),
|
|
.r(r_o), .g(g_o), .b(b_o),
|
|
.ev_valid(pcrtc_ev_valid),
|
|
.ev_subsys(pcrtc_ev_subsys),
|
|
.ev_event(pcrtc_ev_event),
|
|
.ev_arg0(pcrtc_ev_arg0), .ev_arg1(pcrtc_ev_arg1),
|
|
.ev_arg2(pcrtc_ev_arg2), .ev_arg3(pcrtc_ev_arg3),
|
|
.ev_flags(pcrtc_ev_flags)
|
|
);
|
|
|
|
logic [7:0] cap_r [0:PCRTC_V_ACTIVE-1][0:PCRTC_H_ACTIVE-1];
|
|
logic [7:0] cap_g [0:PCRTC_V_ACTIVE-1][0:PCRTC_H_ACTIVE-1];
|
|
logic [7:0] cap_b [0:PCRTC_V_ACTIVE-1][0:PCRTC_H_ACTIVE-1];
|
|
logic cap_de[0:PCRTC_V_ACTIVE-1][0:PCRTC_H_ACTIVE-1];
|
|
|
|
int errors;
|
|
bit capture_armed;
|
|
|
|
// Ch98 audit-low — capture EV_WRITE events from gs_stub so
|
|
// we can assert TEX0_1 lands with the documented stable
|
|
// selector (= 7) instead of the default-0 fall-through.
|
|
int tex0_ev_write_count;
|
|
logic [63:0] last_tex0_ev_arg0;
|
|
logic [63:0] last_tex0_ev_arg1;
|
|
logic [63:0] last_tex0_ev_arg2;
|
|
|
|
initial begin
|
|
tex0_ev_write_count = 0;
|
|
last_tex0_ev_arg0 = 64'd0;
|
|
last_tex0_ev_arg1 = 64'd0;
|
|
last_tex0_ev_arg2 = 64'd0;
|
|
end
|
|
|
|
always_ff @(posedge clk) begin
|
|
if (rst_n && gs_ev_valid
|
|
&& gs_ev_event == trace_pkg::EV_WRITE
|
|
&& gs_ev_arg0[7:0] == 8'h06) begin
|
|
tex0_ev_write_count <= tex0_ev_write_count + 1;
|
|
last_tex0_ev_arg0 <= gs_ev_arg0;
|
|
last_tex0_ev_arg1 <= gs_ev_arg1;
|
|
last_tex0_ev_arg2 <= gs_ev_arg2;
|
|
end
|
|
end
|
|
|
|
initial begin
|
|
for (int y = 0; y < PCRTC_V_ACTIVE; y++)
|
|
for (int x = 0; x < PCRTC_H_ACTIVE; x++) begin
|
|
cap_r[y][x] = 8'd0;
|
|
cap_g[y][x] = 8'd0;
|
|
cap_b[y][x] = 8'd0;
|
|
cap_de[y][x] = 1'b0;
|
|
end
|
|
errors = 0;
|
|
capture_armed = 1'b0;
|
|
end
|
|
|
|
always_ff @(posedge clk) begin
|
|
if (rst_n && capture_armed && de_o
|
|
&& (u_pcrtc.vcnt < PCRTC_V_ACTIVE)
|
|
&& (u_pcrtc.hcnt < PCRTC_H_ACTIVE)) begin
|
|
cap_r [u_pcrtc.vcnt][u_pcrtc.hcnt] <= r_o;
|
|
cap_g [u_pcrtc.vcnt][u_pcrtc.hcnt] <= g_o;
|
|
cap_b [u_pcrtc.vcnt][u_pcrtc.hcnt] <= b_o;
|
|
cap_de[u_pcrtc.vcnt][u_pcrtc.hcnt] <= 1'b1;
|
|
end
|
|
end
|
|
|
|
task automatic step_drive(input logic wr_en,
|
|
input logic [7:0] num,
|
|
input logic [63:0] data);
|
|
@(negedge clk);
|
|
gif_reg_wr_en = wr_en;
|
|
gif_reg_num = num;
|
|
gif_reg_data = data;
|
|
@(posedge clk);
|
|
endtask
|
|
|
|
task automatic drive_reg(input logic [7:0] num, input logic [63:0] data);
|
|
step_drive(1'b1, num, data);
|
|
endtask
|
|
|
|
task automatic drive_idle();
|
|
step_drive(1'b0, 8'd0, 64'd0);
|
|
endtask
|
|
|
|
task automatic drive_priv(input logic [15:0] addr,
|
|
input logic [63:0] data);
|
|
@(negedge clk);
|
|
priv_reg_wr_en = 1'b1;
|
|
priv_reg_wr_addr = addr;
|
|
priv_reg_wr_data = data;
|
|
@(posedge clk);
|
|
@(negedge clk);
|
|
priv_reg_wr_en = 1'b0;
|
|
priv_reg_wr_addr = 16'd0;
|
|
priv_reg_wr_data = 64'd0;
|
|
endtask
|
|
|
|
task automatic vram_write32(input logic [31:0] addr,
|
|
input logic [31:0] data,
|
|
input logic [3:0] be);
|
|
@(negedge clk);
|
|
vram_we = 1'b1;
|
|
vram_waddr = addr;
|
|
vram_wdata = data;
|
|
vram_wbe = be;
|
|
@(posedge clk);
|
|
@(negedge clk);
|
|
vram_we = 1'b0;
|
|
vram_waddr = 32'd0;
|
|
vram_wdata = 32'd0;
|
|
vram_wbe = 4'b0000;
|
|
endtask
|
|
|
|
task automatic clut_write(input logic [7:0] idx,
|
|
input logic [31:0] entry);
|
|
@(negedge clk);
|
|
clut_we = 1'b1;
|
|
clut_widx = idx;
|
|
clut_wdata = entry;
|
|
@(posedge clk);
|
|
@(negedge clk);
|
|
clut_we = 1'b0;
|
|
clut_widx = 8'd0;
|
|
clut_wdata = 32'd0;
|
|
endtask
|
|
|
|
function automatic logic [31:0] clut_entry(input logic [7:0] i);
|
|
// Reuse the Ch97 contract: A=0xFF, B=i+0x80, G=i+0x40, R=i.
|
|
logic [7:0] r8, g8, b8, a8;
|
|
r8 = i;
|
|
g8 = i + 8'h40;
|
|
b8 = i + 8'h80;
|
|
a8 = 8'hFF;
|
|
return {a8, b8, g8, r8};
|
|
endfunction
|
|
|
|
function automatic logic [63:0] tex0_pack(
|
|
input logic [13:0] cbp,
|
|
input logic [3:0] cpsm,
|
|
input logic csm,
|
|
input logic [4:0] csa,
|
|
input logic [2:0] cld);
|
|
logic [63:0] v;
|
|
v = 64'd0;
|
|
v[50:37] = cbp;
|
|
v[54:51] = cpsm;
|
|
v[55] = csm;
|
|
v[60:56] = csa;
|
|
v[63:61] = cld;
|
|
return v;
|
|
endfunction
|
|
|
|
localparam logic [7:0] GIF_TEX0_1 = 8'h06;
|
|
|
|
// Privileged offsets.
|
|
localparam logic [15:0] PMODE_OFF = 16'h0000;
|
|
localparam logic [15:0] DISPFB1_OFF = 16'h0070;
|
|
localparam logic [15:0] DISPLAY1_OFF = 16'h0080;
|
|
|
|
// Phase-1 pattern: distinct values per field.
|
|
localparam logic [13:0] EXP_CBP = 14'h1234;
|
|
localparam logic [3:0] EXP_CPSM = 4'h0; // PSMCT32
|
|
localparam logic EXP_CSM = 1'b1; // CSM2 (linear)
|
|
localparam logic [4:0] EXP_CSA = 5'h05;
|
|
localparam logic [2:0] EXP_CLD = 3'h2; // load mode 2
|
|
|
|
// Phase-2 config.
|
|
localparam logic [63:0] DISPFB1_VAL = 64'h0000_0000_0009_8200; // FBP=0, FBW=1, PSM=PSMT8
|
|
localparam logic [63:0] DISPLAY1_VAL =
|
|
(64'(PCRTC_H_ACTIVE - 1) << 32)
|
|
| (64'(PCRTC_V_ACTIVE - 1) << 44);
|
|
localparam logic [63:0] PMODE_EN1 = 64'h0000_0000_0000_0001;
|
|
|
|
initial begin
|
|
rst_n = 1'b0;
|
|
gif_reg_wr_en = 1'b0;
|
|
gif_reg_num = 8'd0;
|
|
gif_reg_data = 64'd0;
|
|
priv_reg_wr_en = 1'b0;
|
|
priv_reg_wr_addr = 16'd0;
|
|
priv_reg_wr_data = 64'd0;
|
|
vram_we = 1'b0;
|
|
vram_waddr = 32'd0;
|
|
vram_wdata = 32'd0;
|
|
vram_wbe = 4'b0000;
|
|
clut_we = 1'b0;
|
|
clut_widx = 8'd0;
|
|
clut_wdata = 32'd0;
|
|
clut_enable = 1'b0;
|
|
|
|
repeat (4) @(posedge clk);
|
|
rst_n = 1'b1;
|
|
repeat (2) @(posedge clk);
|
|
|
|
// ----------------------------------------------------------------
|
|
// Phase 1 — TEX0_1 register decode verification.
|
|
// ----------------------------------------------------------------
|
|
begin
|
|
logic [63:0] payload;
|
|
payload = tex0_pack(EXP_CBP, EXP_CPSM, EXP_CSM, EXP_CSA, EXP_CLD);
|
|
drive_reg(GIF_TEX0_1, payload);
|
|
drive_idle();
|
|
|
|
if (tex0_1_q !== payload) begin $error("tex0_1_q=0x%016x (expected 0x%016x)", tex0_1_q, payload); errors = errors + 1; end
|
|
if (tex0_1_cbp_q !== EXP_CBP) begin $error("CBP=0x%04x (expected 0x%04x)", tex0_1_cbp_q, EXP_CBP); errors = errors + 1; end
|
|
if (tex0_1_cpsm_q !== EXP_CPSM) begin $error("CPSM=0x%01x (expected 0x%01x)", tex0_1_cpsm_q, EXP_CPSM); errors = errors + 1; end
|
|
if (tex0_1_csm_q !== EXP_CSM) begin $error("CSM=%b (expected %b)", tex0_1_csm_q, EXP_CSM); errors = errors + 1; end
|
|
if (tex0_1_csa_q !== EXP_CSA) begin $error("CSA=0x%02x (expected 0x%02x)", tex0_1_csa_q, EXP_CSA); errors = errors + 1; end
|
|
if (tex0_1_cld_q !== EXP_CLD) begin $error("CLD=0x%01x (expected 0x%01x)", tex0_1_cld_q, EXP_CLD); errors = errors + 1; end
|
|
end
|
|
|
|
$display("[tb_gs_tex0_clut] phase 1 register decode: tex0_1_q=0x%016x CBP=0x%04x CPSM=%01x CSM=%b CSA=%02x CLD=%01x",
|
|
tex0_1_q, tex0_1_cbp_q, tex0_1_cpsm_q, tex0_1_csm_q, tex0_1_csa_q, tex0_1_cld_q);
|
|
|
|
// Settle one more cycle so the EV_WRITE pulse from the
|
|
// drive_reg posedge has propagated into the TB-side
|
|
// observer's NB-updated count.
|
|
@(posedge clk);
|
|
|
|
// Ch98 audit-low: TEX0_1 writes must produce EV_WRITE
|
|
// events with the documented per-register selector value
|
|
// (7), not the default-0 unknown fall-through.
|
|
if (tex0_ev_write_count < 1) begin
|
|
$error("EV_WRITE for TEX0_1 (reg# 0x06) never observed");
|
|
errors = errors + 1;
|
|
end else begin
|
|
if (last_tex0_ev_arg0[7:0] !== 8'h06) begin
|
|
$error("TEX0_1 EV_WRITE arg0[7:0]=0x%02x (expected 0x06)", last_tex0_ev_arg0[7:0]);
|
|
errors = errors + 1;
|
|
end
|
|
if (last_tex0_ev_arg2 !== 64'd7) begin
|
|
$error("TEX0_1 EV_WRITE selector arg2=0x%016x (expected 7)", last_tex0_ev_arg2);
|
|
errors = errors + 1;
|
|
end
|
|
end
|
|
|
|
// ----------------------------------------------------------------
|
|
// Phase 2 — TEX0_1.CSA flows into pcrtc.clut_csa, and a
|
|
// VRAM index of 0x00 with CSA=1 looks up CLUT[0x10].
|
|
// ----------------------------------------------------------------
|
|
// Re-program TEX0_1 with CSA=1 (other CLUT fields zero).
|
|
drive_reg(GIF_TEX0_1, tex0_pack(14'd0, 4'd0, 1'b1, 5'd1, 3'd0));
|
|
drive_idle();
|
|
|
|
if (tex0_1_csa_q !== 5'd1) begin
|
|
$error("phase 2: tex0_1_csa_q=0x%02x (expected 0x01)", tex0_1_csa_q);
|
|
errors = errors + 1;
|
|
end
|
|
|
|
// Program CLUT[0x10] so the lookup has something to find.
|
|
clut_write(8'h10, clut_entry(8'h10));
|
|
|
|
// VRAM[0] = 0x00 (PSMT8 index byte 0). FBW=1 → 64 bytes/row.
|
|
vram_write32(32'd0, 32'h00_00_00_00, 4'b0001);
|
|
|
|
// Configure scanout (no gs_stub raster involved).
|
|
drive_priv(DISPFB1_OFF, DISPFB1_VAL);
|
|
drive_priv(DISPLAY1_OFF, DISPLAY1_VAL);
|
|
clut_enable = 1'b1;
|
|
@(posedge clk);
|
|
drive_priv(PMODE_OFF, PMODE_EN1);
|
|
|
|
@(posedge u_pcrtc.end_of_frame);
|
|
@(posedge clk);
|
|
capture_armed = 1'b1;
|
|
@(posedge u_pcrtc.end_of_frame);
|
|
@(posedge clk);
|
|
capture_armed = 1'b0;
|
|
|
|
begin
|
|
logic [31:0] entry_p2;
|
|
entry_p2 = clut_entry(8'h10);
|
|
if (cap_r[0][0] !== entry_p2[7:0]
|
|
|| cap_g[0][0] !== entry_p2[15:8]
|
|
|| cap_b[0][0] !== entry_p2[23:16]) begin
|
|
$error("[phase 2] (0,0) got (%02x,%02x,%02x) expected CLUT[0x10]=(%02x,%02x,%02x) — TEX0.CSA flow into pcrtc broken",
|
|
cap_r[0][0], cap_g[0][0], cap_b[0][0],
|
|
entry_p2[7:0], entry_p2[15:8], entry_p2[23:16]);
|
|
errors = errors + 1;
|
|
end
|
|
end
|
|
|
|
$display("[tb_gs_tex0_clut] phase 2 TEX0.CSA=1 → pcrtc.clut_csa=1 → idx 0x00 reads CLUT[0x10]: cap=(%02x,%02x,%02x)",
|
|
cap_r[0][0], cap_g[0][0], cap_b[0][0]);
|
|
|
|
if (errors == 0) $display("[tb_gs_tex0_clut] PASS");
|
|
else $display("[tb_gs_tex0_clut] FAIL");
|
|
$finish;
|
|
end
|
|
|
|
initial begin
|
|
#5000000;
|
|
$error("[tb_gs_tex0_clut] timeout");
|
|
$finish;
|
|
end
|
|
|
|
endmodule : tb_gs_tex0_clut
|