Files
retroDE_ps2/sim/tb/gif_gs/tb_gs_scanout_psm16.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

270 lines
9.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 — tb_gs_scanout_psm16 (Ch94)
//
// Locks the contract for DISPFB1.PSM=PSMCT16 (RGB5A1) scanout.
// gs_pcrtc_stub now selects byte-offset shift and color-decode
// path based on the PSM field in DISPFB1: PSMCT32 (=0) reads 4
// bytes/pixel and uses {A, B, G, R} byte order; PSMCT16 (=2)
// reads 2 bytes/pixel and unpacks RGB5A1 with 5→8 bit-replicate
// expansion.
//
// gs_stub's raster channel still emits PSMCT32 only, so this TB
// bypasses gs_stub entirely — it instantiates only `vram_stub`
// (so we have a real RAM) and `gs_pcrtc_stub` (so we exercise
// the scanout PSM path), and drives privileged-register-shaped
// inputs from TB-side regs. That isolates the test to the
// scanout PSM decode.
//
// Setup:
// VRAM populated with a 4×4 RGB5A1 sprite, color
// (R5, G5, B5) = (0x10, 0x18, 0x08) → 16-bit pixel 0x2310,
// row stride 128 bytes (FBW=1 → 64 pixels/row × 2 bytes/pixel).
// PSM = PSMCT16 (DISPFB1[19:15] = 0x02).
// DISPFB1.FBP=0, FBW=1. DBX=DBY=0.
// DISPLAY1 covers full 16×8 active area, MAGH=MAGV=0 (1×).
//
// Expected:
// R8 = {0x10, 0x10[4:2]} = {0x10, 3'b100} = 8'h84
// G8 = {0x18, 0x18[4:2]} = {0x18, 3'b110} = 8'hC6
// B8 = {0x08, 0x08[4:2]} = {0x08, 3'b010} = 8'h42
// At displayed (0..3, 0..3): (R, G, B) = (0x84, 0xC6, 0x42).
// At every other displayed pixel: (0, 0, 0).
`timescale 1ns/1ps
module tb_gs_scanout_psm16;
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;
// Privileged-register-shaped TB regs feeding pcrtc directly.
logic [63:0] pmode_q;
logic [63:0] dispfb1_q;
logic [63:0] display1_q;
// VRAM write side: TB-driven (no gs_stub raster source).
logic vram_we;
logic [31:0] vram_waddr;
logic [31:0] vram_wdata;
// VRAM read side: pcrtc drives the address.
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 (4'b1111),
.write_mask(32'hFFFF_FFFF),
.read_addr (vram_raddr),
.read_data (vram_rdata),
.read2_addr(32'd0),
.read2_data()
);
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 (1'b0),
.clut_csa (5'd0),
.clut_read_idx (),
.clut_read_data(32'd0),
.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;
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 vram_write32(input logic [31:0] addr, input logic [31:0] data);
@(negedge clk);
vram_we = 1'b1;
vram_waddr = addr;
vram_wdata = data;
@(posedge clk);
@(negedge clk);
vram_we = 1'b0;
vram_waddr = 32'd0;
vram_wdata = 32'd0;
endtask
// Build a packed RGB5A1 16-bit value: A[15] B[14:10] G[9:5] R[4:0].
function automatic logic [15:0] psm16_pack(input logic [4:0] r5,
input logic [4:0] g5,
input logic [4:0] b5,
input logic a1);
return {a1, b5, g5, r5};
endfunction
// Sprite color.
localparam logic [4:0] R5 = 5'h10;
localparam logic [4:0] G5 = 5'h18;
localparam logic [4:0] B5 = 5'h08;
// Expected expanded 8-bit values (5→8 bit-replicate).
localparam logic [7:0] EXP_R = {R5, R5[4:2]};
localparam logic [7:0] EXP_G = {G5, G5[4:2]};
localparam logic [7:0] EXP_B = {B5, B5[4:2]};
localparam int SPRITE_W = 4;
localparam int SPRITE_H = 4;
// DISPFB1 PSM = PSMCT16 (5'h02), FBP=0, FBW=1.
// FBP[8:0] = 0
// FBW[14:9] = 1 → 0x200
// PSM[19:15] = 2 → 0x10000
localparam logic [63:0] DISPFB1_VAL = 64'h0000_0000_0001_0200;
// DISPLAY1 covers full active area, MAGH=MAGV=0.
localparam logic [63:0] DISPLAY1_VAL =
(64'(PCRTC_H_ACTIVE - 1) << 32) // DW
| (64'(PCRTC_V_ACTIVE - 1) << 44); // DH
localparam logic [63:0] PMODE_EN1 = 64'h0000_0000_0000_0001;
initial begin
rst_n = 1'b0;
pmode_q = 64'd0;
dispfb1_q = 64'd0;
display1_q = 64'd0;
vram_we = 1'b0;
vram_waddr = 32'd0;
vram_wdata = 32'd0;
repeat (4) @(posedge clk);
rst_n = 1'b1;
repeat (2) @(posedge clk);
// Populate VRAM with the 4×4 PSMCT16 sprite. FBW=1
// means 64 pixels/row × 2 bytes = 128 bytes/row stride.
// Row Y starts at byte Y*128. Each row stores 4 PSMCT16
// pixels = 8 bytes = 2 32-bit writes.
for (int y = 0; y < SPRITE_H; y++) begin
logic [15:0] pix;
logic [31:0] pair;
int row_base;
pix = psm16_pack(R5, G5, B5, 1'b0);
pair = {pix, pix}; // two PSMCT16 pixels packed
row_base = y * 128;
// Pixels (0,1) at byte row_base..row_base+3.
vram_write32(row_base, pair);
// Pixels (2,3) at byte row_base+4..row_base+7.
vram_write32(row_base + 32'd4, pair);
end
// Configure scanout. Reset has dispfb1_q=display1_q=0, so
// we must explicitly write both before enabling EN1.
dispfb1_q = DISPFB1_VAL;
display1_q = DISPLAY1_VAL;
@(posedge clk);
// Cross-check the PSM field landed at [19:15]=2.
if (dispfb1_q[19:15] !== 5'h02) begin
$error("DISPFB1.PSM=%0d (expected 2 = PSMCT16)", dispfb1_q[19:15]);
errors = errors + 1;
end
pmode_q = PMODE_EN1;
@(posedge clk);
@(posedge u_pcrtc.end_of_frame);
@(posedge clk);
capture_armed = 1'b1;
@(posedge u_pcrtc.end_of_frame);
@(posedge clk);
capture_armed = 1'b0;
// Per-pixel verification.
for (int y = 0; y < PCRTC_V_ACTIVE; y++) begin
for (int x = 0; x < PCRTC_H_ACTIVE; x++) begin
logic [7:0] er, eg, eb;
if (x < SPRITE_W && y < SPRITE_H) begin
er = EXP_R; eg = EXP_G; eb = EXP_B;
end else begin
er = 8'd0; eg = 8'd0; eb = 8'd0;
end
if (!cap_de[y][x]) begin
$error("(%0d,%0d) DE never asserted", x, y);
errors = errors + 1;
end
if (cap_r[y][x] !== er || cap_g[y][x] !== eg || cap_b[y][x] !== eb) begin
$error("(%0d,%0d) got (%02x,%02x,%02x) expected (%02x,%02x,%02x)",
x, y, cap_r[y][x], cap_g[y][x], cap_b[y][x], er, eg, eb);
errors = errors + 1;
end
end
end
$display("[tb_gs_scanout_psm16] sprite=%0dx%0d RGB5A1=(%02x,%02x,%02x) → RGB8=(%02x,%02x,%02x)",
SPRITE_W, SPRITE_H, R5, G5, B5, EXP_R, EXP_G, EXP_B);
if (errors == 0) $display("[tb_gs_scanout_psm16] PASS");
else $display("[tb_gs_scanout_psm16] FAIL");
$finish;
end
initial begin
#5000000;
$error("[tb_gs_scanout_psm16] timeout");
$finish;
end
endmodule : tb_gs_scanout_psm16