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

262 lines
9.7 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_vram_writeback (Ch89)
//
// White-box TB pinning down the Ch89 VRAM persistence contract:
// every raster_pixel_emit pulse must land 4 bytes of pixel data
// (PSMCT32 ABGR) at raster_pixel_fb_addr_q in vram_stub, and a
// later read-back at the same address must return EXACTLY the
// emitted color.
//
// Setup: gs_stub + vram_stub instantiated together; gif_reg_*
// driven directly (no DMA). Drive a single 4×4 SPRITE so 16
// pixels emit. Capture the (fb_addr, color) of each emit at the
// posedge it fires; after raster completes, walk each captured
// address and assert vram_stub.read_data == captured color.
//
// Stream:
// PRIM=SPRITE, FRAME_1 (FBP=2 FBW=10 PSMCT32), RGBAQ
// v1 = (0, 0)
// v2 = (3, 3) — close S1, sprite is 4×4 = 16 pixels
`timescale 1ns/1ps
module tb_gs_vram_writeback;
logic clk;
logic rst_n;
initial clk = 1'b0;
always #5 clk = ~clk;
logic gif_reg_wr_en;
logic [7:0] gif_reg_num;
logic [63:0] gif_reg_data;
logic [7:0] bg_r, bg_g, bg_b;
logic [63:0] prim_q, rgbaq_q, xyz2_q, xyzf2_q, frame_1_q, zbuf_1_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 ev_valid;
trace_pkg::subsys_e ev_subsys;
trace_pkg::event_e ev_event;
logic [63:0] ev_arg0, ev_arg1, ev_arg2, ev_arg3;
logic [31:0] ev_flags;
gs_stub u_gs (
.clk(clk), .rst_n(rst_n),
.reg_wr_en(1'b0), .reg_wr_addr(16'd0), .reg_wr_data(64'd0),
.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),
.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),
.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(ev_valid), .ev_subsys(ev_subsys), .ev_event(ev_event),
.ev_arg0(ev_arg0), .ev_arg1(ev_arg1),
.ev_arg2(ev_arg2), .ev_arg3(ev_arg3),
.ev_flags(ev_flags)
);
logic [31:0] vram_read_addr;
logic [31:0] vram_read_data;
vram_stub u_vram (
.clk(clk), .rst_n(rst_n),
.write_en (raster_pixel_emit),
.write_addr(raster_pixel_fb_addr_q),
.write_data(raster_pixel_color_q[31:0]),
.write_be (raster_pixel_be_q),
.write_mask(32'hFFFF_FFFF),
.read_addr (vram_read_addr),
.read_data (vram_read_data),
.read2_addr(32'd0),
.read2_data()
);
// Capture the (fb_addr, color, x, y) of every emit pulse.
int emit_count;
logic [31:0] emit_fb_addr [0:31];
logic [31:0] emit_color [0:31];
logic [11:0] emit_x [0:31];
logic [11:0] emit_y [0:31];
initial begin
emit_count = 0;
end
always_ff @(posedge clk) begin
if (rst_n && raster_pixel_emit && emit_count < 32) begin
emit_fb_addr[emit_count] <= raster_pixel_fb_addr_q;
emit_color [emit_count] <= raster_pixel_color_q[31:0];
emit_x [emit_count] <= raster_pixel_x_q;
emit_y [emit_count] <= raster_pixel_y_q;
emit_count <= emit_count + 1;
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
function automatic logic [63:0] xyz2_data(input logic [11:0] x_int,
input logic [11:0] y_int);
return {32'd0, y_int, 4'd0, x_int, 4'd0};
endfunction
localparam logic [7:0] R_PRIM = 8'h00;
localparam logic [7:0] R_RGBAQ = 8'h01;
localparam logic [7:0] R_XYZ2 = 8'h05;
localparam logic [7:0] R_FRAME_1 = 8'h4C;
localparam logic [63:0] PRIM_SPRITE = 64'd6;
localparam logic [63:0] FRAME_1_VAL = 64'h0000_0000_000A_0002;
// Color: ABGR = 0x_00_00_FF_30 → A=0x00, B=0x00, G=0xFF, R=0x30.
// raster_pixel_color_q[31:0] should mirror this.
localparam logic [63:0] RGBAQ_VAL = 64'h0000_0000_0000_FF30;
localparam logic [31:0] EXPECTED_PIX = 32'h0000_FF30;
int errors;
initial begin
rst_n = 1'b0;
gif_reg_wr_en = 1'b0;
gif_reg_num = 8'd0;
gif_reg_data = 64'd0;
errors = 0;
vram_read_addr = 32'd0;
repeat (4) @(posedge clk);
rst_n = 1'b1;
repeat (2) @(posedge clk);
drive_reg(R_PRIM, PRIM_SPRITE);
drive_reg(R_FRAME_1, FRAME_1_VAL);
drive_reg(R_RGBAQ, RGBAQ_VAL);
// 4×4 SPRITE — 16 pixels.
drive_reg(R_XYZ2, xyz2_data(12'd0, 12'd0)); // v1
drive_reg(R_XYZ2, xyz2_data(12'd3, 12'd3)); // v2 — close S1
drive_idle();
repeat (40) @(posedge clk);
$display("[tb_gs_vram_writeback] emit_count=%0d raster_pixel_emit_count=%0d raster_overflow=%b",
emit_count, raster_pixel_emit_count, raster_overflow);
if (emit_count != 16) begin
$error("emit_count=%0d (expected 16)", emit_count);
errors = errors + 1;
end
if (raster_overflow !== 1'b0) begin
$error("raster_overflow=%b (expected 0)", raster_overflow);
errors = errors + 1;
end
// Walk each captured emit and verify VRAM read-back matches.
for (int i = 0; i < emit_count; i++) begin
vram_read_addr = emit_fb_addr[i];
#1; // let comb read settle
$display("[tb_gs_vram_writeback] cap[%0d] (%0d,%0d) fb=0x%08x vram=0x%08x expect=0x%08x",
i, emit_x[i], emit_y[i], emit_fb_addr[i],
vram_read_data, emit_color[i]);
if (vram_read_data !== emit_color[i]) begin
$error("VRAM mismatch @ fb=0x%08x: got 0x%08x, expected 0x%08x",
emit_fb_addr[i], vram_read_data, emit_color[i]);
errors = errors + 1;
end
if (emit_color[i] !== EXPECTED_PIX) begin
$error("emit color mismatch @ fb=0x%08x: emitted 0x%08x, expected 0x%08x",
emit_fb_addr[i], emit_color[i], EXPECTED_PIX);
errors = errors + 1;
end
end
// Sanity probe: an address NOT touched by any emit must
// still read as zero (initial mem state).
vram_read_addr = 32'h0000_0000;
#1;
if (vram_read_data !== 32'd0) begin
$error("VRAM addr 0 should be 0 (untouched), got 0x%08x", vram_read_data);
errors = errors + 1;
end
if (errors == 0) $display("[tb_gs_vram_writeback] PASS");
else $display("[tb_gs_vram_writeback] FAIL");
$finish;
end
initial begin
#5000000;
$error("[tb_gs_vram_writeback] timeout");
$finish;
end
endmodule : tb_gs_vram_writeback