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>
148 lines
6.5 KiB
Systemverilog
148 lines
6.5 KiB
Systemverilog
// ============================================================================
|
|
// tb_gs_lpddr_scanout — Ch320 Brick 1
|
|
//
|
|
// Preloads an AXI read-slave with a known 64x64 PSMCT16 frame (pixel n carries
|
|
// the value n), pulses frame_start, waits for the cache to fill, then presents
|
|
// PCRTC-style vram_read_addr values and checks the decoded r/g/b match the
|
|
// known frame (5->8 bit-replication) and that out-of-range reads return black.
|
|
// ============================================================================
|
|
`timescale 1ns/1ps
|
|
|
|
module tb_gs_lpddr_scanout;
|
|
logic axi_clk = 0, video_clk = 0;
|
|
always #2 axi_clk = ~axi_clk; // ~250 MHz
|
|
always #10 video_clk = ~video_clk; // 50 MHz
|
|
|
|
logic axi_rst_n;
|
|
logic enable, frame_start;
|
|
logic [31:0] vram_read_addr;
|
|
logic [7:0] r, g, b;
|
|
logic cache_valid;
|
|
logic [31:0] rd_beats, rd_errs;
|
|
|
|
logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid;
|
|
logic [7:0] arlen; logic [2:0] arsize; logic arvalid, arready;
|
|
logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready;
|
|
|
|
gs_lpddr_scanout #(.FB_BASE(30'd0), .CACHE_BEATS(256)) dut (
|
|
.axi_clk(axi_clk), .axi_rst_n(axi_rst_n),
|
|
.enable(enable), .frame_start(frame_start),
|
|
.video_clk(video_clk), .vram_read_addr(vram_read_addr),
|
|
.r(r), .g(g), .b(b),
|
|
.cache_valid(cache_valid), .rd_beats(rd_beats), .rd_errs(rd_errs),
|
|
.araddr(araddr), .arburst(arburst), .arid(arid), .arlen(arlen),
|
|
.arsize(arsize), .arvalid(arvalid), .arready(arready),
|
|
.rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready)
|
|
);
|
|
|
|
// ---- known frame: pixel n (16-bit) carries value n ----
|
|
function automatic [255:0] beat_data(input [7:0] beat);
|
|
integer i;
|
|
for (i = 0; i < 16; i = i + 1)
|
|
beat_data[16*i +: 16] = (beat * 16 + i); // pixel index = value
|
|
endfunction
|
|
function automatic [15:0] pixel_val(input [31:0] byte_addr);
|
|
pixel_val = byte_addr[16:1]; // pixel index = addr>>1
|
|
endfunction
|
|
|
|
// ---- AXI read-slave model ----
|
|
localparam [1:0] S_IDLE=0, S_LAT=1, S_RESP=2;
|
|
logic [1:0] sst; logic [2:0] sdly; logic [7:0] sbeat;
|
|
always_ff @(posedge axi_clk or negedge axi_rst_n) begin
|
|
if (!axi_rst_n) begin
|
|
sst<=S_IDLE; arready<=0; rvalid<=0; rlast<=0; rdata<='0; rresp<=2'b00; sdly<=0; sbeat<=0;
|
|
end else begin
|
|
arready <= 1'b0;
|
|
case (sst)
|
|
S_IDLE: if (arvalid) begin
|
|
arready <= 1'b1; sbeat <= araddr[12:5]; sdly <= 3'd3; sst <= S_LAT;
|
|
end
|
|
S_LAT: if (sdly==0) begin
|
|
rdata<=beat_data(sbeat); rresp<=2'b00; rlast<=1'b1; rvalid<=1'b1; sst<=S_RESP;
|
|
end else sdly <= sdly-1;
|
|
S_RESP: if (rready) begin rvalid<=0; rlast<=0; sst<=S_IDLE; end
|
|
default: sst <= S_IDLE;
|
|
endcase
|
|
end
|
|
end
|
|
|
|
// ---- checks ----
|
|
integer errors = 0;
|
|
task automatic check_pixel(input [31:0] addr);
|
|
logic [15:0] px; logic [4:0] r5,g5,b5; logic [7:0] er,eg,eb;
|
|
begin
|
|
vram_read_addr = addr;
|
|
@(posedge video_clk); @(posedge video_clk); // registered (sync) read latency
|
|
px = pixel_val(addr);
|
|
r5 = px[4:0]; g5 = px[9:5]; b5 = px[14:10];
|
|
er = {r5, r5[4:2]}; eg = {g5, g5[4:2]}; eb = {b5, b5[4:2]};
|
|
if (r!==er || g!==eg || b!==eb) begin
|
|
errors = errors + 1;
|
|
$display("[tb] PIXEL MISMATCH @0x%0h: got(%0h,%0h,%0h) exp(%0h,%0h,%0h)",
|
|
addr, r, g, b, er, eg, eb);
|
|
end
|
|
end
|
|
endtask
|
|
|
|
task automatic check_black(input [31:0] addr);
|
|
begin
|
|
vram_read_addr = addr;
|
|
@(posedge video_clk); @(posedge video_clk);
|
|
if (r!==8'd0 || g!==8'd0 || b!==8'd0) begin
|
|
errors = errors + 1;
|
|
$display("[tb] EXPECTED BLACK @0x%0h: got(%0h,%0h,%0h)", addr, r, g, b);
|
|
end
|
|
end
|
|
endtask
|
|
|
|
// Ch352 (Codex) — register-contract witness: while disabled (the de25 top gates this with video_src=0),
|
|
// the scanout reader must issue ZERO AXI AR traffic. Proves video_src=0 -> no LPDDR scanout reads.
|
|
logic saw_ar_dis; initial saw_ar_dis=0;
|
|
always_ff @(posedge axi_clk) if (!enable && arvalid) saw_ar_dis <= 1'b1;
|
|
|
|
initial begin
|
|
enable=0; frame_start=0; vram_read_addr=0;
|
|
axi_rst_n=0; repeat(8) @(posedge axi_clk); axi_rst_n=1;
|
|
repeat(4) @(posedge video_clk);
|
|
|
|
// out-of-range before any fill -> black (cache_valid=0)
|
|
check_black(32'h0000_0000);
|
|
// pulse frame_start while DISABLED — must NOT trigger any AR (contract: video_src=0 => no reads)
|
|
@(posedge video_clk); frame_start=1'b1; @(posedge video_clk); frame_start=1'b0;
|
|
repeat(40) @(posedge axi_clk);
|
|
if (saw_ar_dis) begin errors=errors+1; $display("[tb] AR issued while disabled (video_src=0 contract violated)"); end
|
|
|
|
// trigger a frame fill
|
|
enable = 1'b1;
|
|
@(posedge video_clk); frame_start = 1'b1; @(posedge video_clk); frame_start = 1'b0;
|
|
|
|
// wait for the cache to load
|
|
begin integer to; to=0;
|
|
while (!cache_valid && to<100000) begin @(posedge axi_clk); to=to+1; end
|
|
if (!cache_valid) begin errors=errors+1; $display("[tb] cache_valid TIMEOUT"); end
|
|
end
|
|
|
|
if (rd_beats !== 32'd256) begin errors=errors+1; $display("[tb] rd_beats=%0d exp 256", rd_beats); end
|
|
if (rd_errs !== 32'd0) begin errors=errors+1; $display("[tb] rd_errs=%0d exp 0", rd_errs); end
|
|
|
|
// in-range pixels across beats + halfword lanes
|
|
check_pixel(32'h0000_0000); // pixel 0, beat 0 lane 0
|
|
check_pixel(32'h0000_0002); // pixel 1
|
|
check_pixel(32'h0000_001E); // pixel 15, beat 0 lane 15
|
|
check_pixel(32'h0000_0020); // pixel 16, beat 1 lane 0
|
|
check_pixel(32'h0000_0080); // pixel 64 (row 1, col 0)
|
|
check_pixel(32'h0000_1FFE); // pixel 4095, last in-range
|
|
// out-of-range -> black
|
|
check_black(32'h0000_2000); // byte 8192 = first past the frame
|
|
|
|
$display("[tb_gs_lpddr_scanout] errors=%0d", errors);
|
|
if (errors==0) $display("[tb_gs_lpddr_scanout] PASS");
|
|
else $display("[tb_gs_lpddr_scanout] FAIL");
|
|
$finish;
|
|
end
|
|
|
|
initial begin
|
|
#500000; $display("[tb_gs_lpddr_scanout] TIMEOUT"); $display("[tb_gs_lpddr_scanout] FAIL"); $finish;
|
|
end
|
|
endmodule
|