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>
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_textured_demo (Brick 1)
|
||||
//
|
||||
// TOP-LEVEL textured-sprite demo TB. Proves the synthesizable textured
|
||||
// SPRITE path renders end-to-end through the REAL hardware top
|
||||
// (top_psmct32_raster_demo), exactly as a board load would:
|
||||
//
|
||||
// EE bootlet (bios_textured.mem) configures DISPFB1/DISPLAY1/PMODE
|
||||
// and kicks DMAC ch2 to stream the GIF payload (payload_textured.mem):
|
||||
// U1 BITBLT/TRX -> uploads an 8x8 PSMCT32 texture to VRAM (DBP=8)
|
||||
// U2 IMAGE -> 16 qwords = the 64 texels
|
||||
// U3/U4 PACKED -> PRIM(SPRITE+TME) + TEX0 + UV + XYZ2: a textured
|
||||
// 8x8 sprite at screen (0,0)..(7,7)
|
||||
// U5 PACKED -> a FLAT green control sprite at (8,0)..(15,7)
|
||||
//
|
||||
// The top is built with PSMCT32_SWIZZLE=0 so the LINEAR gs_texel_addr
|
||||
// fetch and the BITBLT upload share one VRAM layout (v1 scope; the
|
||||
// swizzle reconciliation is the gs_stub TODO, out of scope here).
|
||||
//
|
||||
// Verification is via the PCRTC scanout RGB (the same r/g/b/de a board
|
||||
// wires to a video PHY) — so a PASS means the board would SHOW the
|
||||
// texture:
|
||||
// - Textured region x in [0..7]: scanout pixel == the uploaded texel
|
||||
// for (x,y). Texel(x,y) ABGR = {A=FF, B=y, G=x, R=0x40}, and PCRTC
|
||||
// PSMCT32 emits r=texel[7:0]=0x40, g=texel[15:8]=x, b=texel[23:16]=y.
|
||||
// - Flat region x in [8..15]: scanout pixel == the flat green color
|
||||
// (proves the TME=0 path still works alongside texturing).
|
||||
// - The textured region must NOT be uniform (it's a real sampled
|
||||
// gradient, not a flat fill) — guards against "texturing silently
|
||||
// collapsed to flat."
|
||||
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_textured_demo;
|
||||
|
||||
localparam int H_ACTIVE = 16;
|
||||
localparam int V_ACTIVE = 8;
|
||||
|
||||
logic clk;
|
||||
logic rst_n;
|
||||
initial clk = 1'b0;
|
||||
always #5 clk = ~clk;
|
||||
|
||||
logic core_go;
|
||||
logic [7:0] r, g, b;
|
||||
logic hsync, vsync, de;
|
||||
logic core_halt;
|
||||
logic dma_done_seen;
|
||||
logic frame_seen;
|
||||
logic raster_overflow;
|
||||
logic frame_toggle;
|
||||
logic dma_done_toggle;
|
||||
|
||||
// PSMCT32_SWIZZLE=0 so texture-upload (BITBLT) and texel-fetch
|
||||
// (gs_texel_addr, linear) share one VRAM layout.
|
||||
top_psmct32_raster_demo #(
|
||||
.H_ACTIVE (H_ACTIVE),
|
||||
.V_ACTIVE (V_ACTIVE),
|
||||
.PSMCT32_SWIZZLE(1'b0)
|
||||
) dut (
|
||||
.clk(clk), .rst_n(rst_n),
|
||||
.core_go(core_go),
|
||||
.r(r), .g(g), .b(b),
|
||||
.hsync(hsync), .vsync(vsync), .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)
|
||||
);
|
||||
|
||||
// ----- Expected texel/scanout for the textured region -----
|
||||
// Bold 2x2-quadrant texture (mirrors bake.py tex_demo_texel):
|
||||
// (x<4,y<4)=RED (x>=4,y<4)=GREEN (x<4,y>=4)=BLUE (x>=4,y>=4)=YELLOW
|
||||
// PCRTC PSMCT32 -> r=texel[7:0], g=texel[15:8], b=texel[23:16].
|
||||
function automatic logic [7:0] tex_exp_r(input int x, input int y); return ((x<4)==(y<4)) ? 8'hFF : 8'h00; endfunction
|
||||
function automatic logic [7:0] tex_exp_g(input int x, input int y); return (x>=4) ? 8'hFF : 8'h00; endfunction
|
||||
function automatic logic [7:0] tex_exp_b(input int x, input int y); return ((x<4)&&(y>=4)) ? 8'hFF : 8'h00; endfunction
|
||||
|
||||
// Flat control sprite color: rgbaq_data(0x20, 0xC0, 0x40).
|
||||
localparam logic [7:0] FLAT_R = 8'h20;
|
||||
localparam logic [7:0] FLAT_G = 8'hC0;
|
||||
localparam logic [7:0] FLAT_B = 8'h40;
|
||||
|
||||
// ----- Frame capture -----
|
||||
logic [7:0] cap_r [0:V_ACTIVE-1][0:H_ACTIVE-1];
|
||||
logic [7:0] cap_g [0:V_ACTIVE-1][0:H_ACTIVE-1];
|
||||
logic [7:0] cap_b [0:V_ACTIVE-1][0:H_ACTIVE-1];
|
||||
logic cap_de[0:V_ACTIVE-1][0:H_ACTIVE-1];
|
||||
bit capture_armed;
|
||||
|
||||
initial begin
|
||||
for (int y = 0; y < V_ACTIVE; y++)
|
||||
for (int x = 0; x < 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
|
||||
capture_armed = 1'b0;
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst_n && capture_armed && de
|
||||
&& (dut.u_pcrtc.vcnt < V_ACTIVE)
|
||||
&& (dut.u_pcrtc.hcnt < H_ACTIVE)) begin
|
||||
cap_r [dut.u_pcrtc.vcnt][dut.u_pcrtc.hcnt] <= r;
|
||||
cap_g [dut.u_pcrtc.vcnt][dut.u_pcrtc.hcnt] <= g;
|
||||
cap_b [dut.u_pcrtc.vcnt][dut.u_pcrtc.hcnt] <= b;
|
||||
cap_de[dut.u_pcrtc.vcnt][dut.u_pcrtc.hcnt] <= 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
int errors;
|
||||
int tex_pixels_ok;
|
||||
int distinct_tex_values;
|
||||
logic [7:0] first_tex_g;
|
||||
initial begin
|
||||
errors = 0;
|
||||
tex_pixels_ok = 0;
|
||||
distinct_tex_values = 0;
|
||||
end
|
||||
|
||||
initial begin
|
||||
rst_n = 1'b0;
|
||||
core_go = 1'b0;
|
||||
repeat (4) @(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);
|
||||
if (dut.u_gs.raster_active == 1'b1)
|
||||
wait (dut.u_gs.raster_active == 1'b0);
|
||||
repeat (10) @(posedge clk);
|
||||
|
||||
@(posedge dut.u_pcrtc.end_of_frame);
|
||||
@(posedge clk);
|
||||
capture_armed = 1'b1;
|
||||
@(posedge dut.u_pcrtc.end_of_frame);
|
||||
@(posedge clk);
|
||||
capture_armed = 1'b0;
|
||||
|
||||
// --- Textured region (x in [0..7]) must match the uploaded texel. ---
|
||||
for (int y = 0; y < 8; y++) begin
|
||||
for (int x = 0; x < 8; x++) begin
|
||||
logic [7:0] er, eg, eb;
|
||||
er = tex_exp_r(x, y);
|
||||
eg = tex_exp_g(x, y);
|
||||
eb = tex_exp_b(x, y);
|
||||
if (!cap_de[y][x]) begin
|
||||
$error("textured (%0d,%0d) DE never asserted", x, y);
|
||||
errors = errors + 1;
|
||||
end else if (cap_r[y][x] !== er || cap_g[y][x] !== eg || cap_b[y][x] !== eb) begin
|
||||
$error("[textured] (%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 else begin
|
||||
tex_pixels_ok = tex_pixels_ok + 1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// --- The textured region must be a real gradient, not flat. ---
|
||||
// (Guards against "texturing collapsed to a single texel / flat
|
||||
// color".) Count how many distinct G values (= sampled u) appear
|
||||
// across row 0.
|
||||
first_tex_g = cap_g[0][0];
|
||||
for (int x = 1; x < 8; x++)
|
||||
if (cap_g[0][x] !== first_tex_g)
|
||||
distinct_tex_values = distinct_tex_values + 1;
|
||||
if (distinct_tex_values == 0) begin
|
||||
$error("textured region is uniform (G all = %02x) — texturing collapsed to flat",
|
||||
first_tex_g);
|
||||
errors = errors + 1;
|
||||
end
|
||||
|
||||
// --- Flat control region (x in [8..15]) must be the flat color. ---
|
||||
for (int y = 0; y < 8; y++) begin
|
||||
for (int x = 8; x < 16; x++) begin
|
||||
if (!cap_de[y][x]) begin
|
||||
$error("flat (%0d,%0d) DE never asserted", x, y);
|
||||
errors = errors + 1;
|
||||
end else if (cap_r[y][x] !== FLAT_R || cap_g[y][x] !== FLAT_G || cap_b[y][x] !== FLAT_B) begin
|
||||
$error("[flat] (%0d,%0d) got (%02x,%02x,%02x) expected (%02x,%02x,%02x)",
|
||||
x, y, cap_r[y][x], cap_g[y][x], cap_b[y][x], FLAT_R, FLAT_G, FLAT_B);
|
||||
errors = errors + 1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// --- Status bundle. ---
|
||||
if (!core_halt) begin $error("core_halt low at end"); errors = errors + 1; end
|
||||
if (!dma_done_seen) begin $error("dma_done_seen never latched"); errors = errors + 1; end
|
||||
if (!frame_seen) begin $error("frame_seen never latched"); errors = errors + 1; end
|
||||
if (raster_overflow) begin $error("raster_overflow set"); errors = errors + 1; end
|
||||
|
||||
$display("[tb_top_psmct32_textured_demo] tex_ok=%0d/64 gradient_distinct=%0d raster_emits=%0d errors=%0d",
|
||||
tex_pixels_ok, distinct_tex_values,
|
||||
dut.u_gs.raster_pixel_emit_count, errors);
|
||||
if (errors == 0)
|
||||
$display("[tb_top_psmct32_textured_demo] PASS");
|
||||
else
|
||||
$display("[tb_top_psmct32_textured_demo] FAIL");
|
||||
$finish;
|
||||
end
|
||||
|
||||
initial begin
|
||||
#20000000;
|
||||
$error("[tb_top_psmct32_textured_demo] TIMEOUT");
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule : tb_top_psmct32_textured_demo
|
||||
Reference in New Issue
Block a user