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,183 @@
|
||||
// retroDE_ps2 — tb_ee_dmac_ctrl_stub
|
||||
//
|
||||
// Ch287 — Focused TB for ee_dmac_ctrl_stub. The DMAC global
|
||||
// control/status registers at 0x1000_E000..0x1000_E0FF — D_CTRL at
|
||||
// 0x00, D_STAT at 0x10 (W1C semantics on the low half), D_PCR at
|
||||
// 0x20, D_SQWC at 0x30, D_RBSR at 0x40, D_RBOR at 0x50.
|
||||
//
|
||||
// Test sequence:
|
||||
// 1. Reset-init: every named offset reads 0.
|
||||
// 2. D_CTRL latch round-trip (write/read at 0x00).
|
||||
// 3. D_STAT W1C semantics. Since nothing in the stub *sets*
|
||||
// d_stat bits (real PS2 channels would), we hierarchically
|
||||
// poke d_stat to a known value, then issue a W1C write and
|
||||
// verify only the requested CIS bits clear (low half) while
|
||||
// CIM bits in the high half are *unconditionally* written.
|
||||
// 4. D_PCR / D_SQWC / D_RBSR / D_RBOR round-trip (latched).
|
||||
// 5. Unknown offset (0x80) read returns 0; write doesn't crash
|
||||
// anything and the next valid read still works.
|
||||
//
|
||||
// Strict mode irrelevant here — the stub is driven directly, not
|
||||
// through ee_core_stub.
|
||||
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_ee_dmac_ctrl_stub;
|
||||
|
||||
logic clk;
|
||||
logic rst_n;
|
||||
|
||||
initial clk = 1'b0;
|
||||
always #5 clk = ~clk;
|
||||
|
||||
// DUT ports
|
||||
logic reg_wr_en;
|
||||
logic [7:0] reg_offset;
|
||||
logic [31:0] reg_wr_data;
|
||||
logic reg_rd_en;
|
||||
logic [31:0] reg_rd_data;
|
||||
logic reg_rd_valid;
|
||||
|
||||
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;
|
||||
|
||||
ee_dmac_ctrl_stub dut (
|
||||
.clk(clk), .rst_n(rst_n),
|
||||
.reg_wr_en(reg_wr_en),
|
||||
.reg_offset(reg_offset),
|
||||
.reg_wr_data(reg_wr_data),
|
||||
.reg_rd_en(reg_rd_en),
|
||||
.reg_rd_data(reg_rd_data),
|
||||
.reg_rd_valid(reg_rd_valid),
|
||||
.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)
|
||||
);
|
||||
|
||||
int errors = 0;
|
||||
|
||||
task automatic do_write(input logic [7:0] off, input logic [31:0] data);
|
||||
@(negedge clk);
|
||||
reg_wr_en = 1'b1;
|
||||
reg_offset = off;
|
||||
reg_wr_data = data;
|
||||
@(negedge clk);
|
||||
reg_wr_en = 1'b0;
|
||||
reg_offset = 8'd0;
|
||||
reg_wr_data = 32'd0;
|
||||
endtask
|
||||
|
||||
task automatic do_read(input logic [7:0] off,
|
||||
output logic [31:0] data_out);
|
||||
@(negedge clk);
|
||||
reg_rd_en = 1'b1;
|
||||
reg_offset = off;
|
||||
@(negedge clk);
|
||||
reg_rd_en = 1'b0;
|
||||
reg_offset = 8'd0;
|
||||
@(posedge clk);
|
||||
data_out = reg_rd_data;
|
||||
endtask
|
||||
|
||||
task automatic check(input string tag,
|
||||
input logic [31:0] got,
|
||||
input logic [31:0] exp);
|
||||
if (got !== exp) begin
|
||||
$display("[tb_ee_dmac_ctrl_stub] FAIL %s got=0x%08h exp=0x%08h",
|
||||
tag, got, exp);
|
||||
errors++;
|
||||
end else begin
|
||||
$display("[tb_ee_dmac_ctrl_stub] ok %s = 0x%08h", tag, got);
|
||||
end
|
||||
endtask
|
||||
|
||||
logic [31:0] rd0;
|
||||
|
||||
initial begin
|
||||
rst_n = 1'b0;
|
||||
reg_wr_en = 1'b0;
|
||||
reg_offset = 8'd0;
|
||||
reg_wr_data = 32'd0;
|
||||
reg_rd_en = 1'b0;
|
||||
|
||||
repeat (4) @(posedge clk);
|
||||
rst_n = 1'b1;
|
||||
@(posedge clk);
|
||||
|
||||
// ===== 1) Reset-init: every named offset reads 0 =====
|
||||
do_read(8'h00, rd0); check("reset_d_ctrl", rd0, 32'd0);
|
||||
do_read(8'h10, rd0); check("reset_d_stat", rd0, 32'd0);
|
||||
do_read(8'h20, rd0); check("reset_d_pcr", rd0, 32'd0);
|
||||
do_read(8'h30, rd0); check("reset_d_sqwc", rd0, 32'd0);
|
||||
do_read(8'h40, rd0); check("reset_d_rbsr", rd0, 32'd0);
|
||||
do_read(8'h50, rd0); check("reset_d_rbor", rd0, 32'd0);
|
||||
|
||||
// ===== 2) D_CTRL latch round-trip =====
|
||||
do_write(8'h00, 32'h1234_5678);
|
||||
do_read (8'h00, rd0); check("d_ctrl_write_read", rd0, 32'h1234_5678);
|
||||
|
||||
// ===== 3) D_STAT W1C semantics =====
|
||||
// Pre-load d_stat hierarchically (CIS = 0x00AB, CIM = 0xFFFF
|
||||
// → full word 0xFFFF_00AB). Verify the read sees it.
|
||||
@(negedge clk);
|
||||
dut.d_stat = 32'hFFFF_00AB;
|
||||
@(negedge clk);
|
||||
do_read(8'h10, rd0); check("d_stat_preload", rd0, 32'hFFFF_00AB);
|
||||
|
||||
// W1C the low byte: write 0x0000_00A0 — should clear bits 7,5,3 in
|
||||
// the low half (0x00AB & ~0x00A0 = 0x000B) and overwrite the high
|
||||
// half (CIM) with the write value (0x0000_0000).
|
||||
do_write(8'h10, 32'h0000_00A0);
|
||||
do_read (8'h10, rd0); check("d_stat_w1c_lo_cim_zero",
|
||||
rd0, 32'h0000_000B);
|
||||
|
||||
// Re-pre-load to test that high-half write doesn't W1C the low.
|
||||
@(negedge clk);
|
||||
dut.d_stat = 32'h0000_00FF;
|
||||
@(negedge clk);
|
||||
do_write(8'h10, 32'hDEAD_0000); // CIM = 0xDEAD, CIS write = 0 (no clear)
|
||||
do_read (8'h10, rd0); check("d_stat_cim_write_cis_unchanged",
|
||||
rd0, 32'hDEAD_00FF);
|
||||
|
||||
// ===== 4) D_PCR / D_SQWC / D_RBSR / D_RBOR latch =====
|
||||
do_write(8'h20, 32'hCAFE_BABE);
|
||||
do_write(8'h30, 32'h1357_9BDF);
|
||||
do_write(8'h40, 32'h2468_ACE0);
|
||||
do_write(8'h50, 32'hFEED_FACE);
|
||||
do_read (8'h20, rd0); check("d_pcr_rt", rd0, 32'hCAFE_BABE);
|
||||
do_read (8'h30, rd0); check("d_sqwc_rt", rd0, 32'h1357_9BDF);
|
||||
do_read (8'h40, rd0); check("d_rbsr_rt", rd0, 32'h2468_ACE0);
|
||||
do_read (8'h50, rd0); check("d_rbor_rt", rd0, 32'hFEED_FACE);
|
||||
|
||||
// D_CTRL untouched by D_PCR write.
|
||||
do_read (8'h00, rd0); check("d_ctrl_distinct", rd0, 32'h1234_5678);
|
||||
|
||||
// ===== 5) Unknown offset read returns 0; write doesn't break =====
|
||||
do_read (8'h80, rd0); check("unknown_offset_reads_zero", rd0, 32'd0);
|
||||
do_write(8'h80, 32'hAAAA_5555); // dropped
|
||||
do_read (8'h80, rd0); check("unknown_offset_still_zero", rd0, 32'd0);
|
||||
// Next valid read still works after an unknown write.
|
||||
do_read (8'h00, rd0); check("d_ctrl_after_unknown_write",
|
||||
rd0, 32'h1234_5678);
|
||||
|
||||
if (errors == 0)
|
||||
$display("[tb_ee_dmac_ctrl_stub] PASS");
|
||||
else
|
||||
$display("[tb_ee_dmac_ctrl_stub] FAIL errors=%0d", errors);
|
||||
|
||||
$finish;
|
||||
end
|
||||
|
||||
initial begin
|
||||
#5_000_000;
|
||||
$display("[tb_ee_dmac_ctrl_stub] TIMEOUT");
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule : tb_ee_dmac_ctrl_stub
|
||||
@@ -0,0 +1,176 @@
|
||||
// retroDE_ps2 — tb_ee_dmac_passive_chan_stub
|
||||
//
|
||||
// Ch288 — Focused TB for ee_dmac_passive_chan_stub. The lightweight
|
||||
// per-channel register surface covering DMAC channels 0/1/3/4/5
|
||||
// (ch2 GIF stays on its dedicated dmac_reg_stub). Four registers
|
||||
// per channel (CHCR/MADR/QWC/TADR), latched, no FSM.
|
||||
//
|
||||
// Test cases:
|
||||
// 1. Reset reads zero for all 4 regs of the qbert-blocking channel
|
||||
// ch4 (chan_addr[15:12] == 0xC).
|
||||
// 2. Round-trip write/read of CHCR/MADR/QWC/TADR on ch4.
|
||||
// 3. Channel independence: write distinct values to ch4 and ch5,
|
||||
// verify ch4 readback is unchanged after ch5 writes.
|
||||
// 4. ch2 (chan_addr[15:12] == 0xA) is OUTSIDE this stub's channel
|
||||
// set — verify reads return 0 (invalid-channel path).
|
||||
// 5. Unknown register offset within a valid channel: read returns 0,
|
||||
// next valid read still works.
|
||||
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_ee_dmac_passive_chan_stub;
|
||||
|
||||
logic clk;
|
||||
logic rst_n;
|
||||
|
||||
initial clk = 1'b0;
|
||||
always #5 clk = ~clk;
|
||||
|
||||
// DUT ports
|
||||
logic reg_wr_en;
|
||||
logic [15:0] chan_addr;
|
||||
logic [31:0] reg_wr_data;
|
||||
logic reg_rd_en;
|
||||
logic [31:0] reg_rd_data;
|
||||
logic reg_rd_valid;
|
||||
|
||||
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;
|
||||
|
||||
ee_dmac_passive_chan_stub dut (
|
||||
.clk(clk), .rst_n(rst_n),
|
||||
.reg_wr_en(reg_wr_en),
|
||||
.chan_addr(chan_addr),
|
||||
.reg_wr_data(reg_wr_data),
|
||||
.reg_rd_en(reg_rd_en),
|
||||
.reg_rd_data(reg_rd_data),
|
||||
.reg_rd_valid(reg_rd_valid),
|
||||
.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)
|
||||
);
|
||||
|
||||
int errors = 0;
|
||||
|
||||
// Helpers — chan_idx in {0x8, 0x9, 0xB, 0xC, 0xD}, reg_offset is the
|
||||
// 12-bit register offset within that channel (0x000 CHCR, 0x010
|
||||
// MADR, 0x020 QWC, 0x030 TADR).
|
||||
task automatic do_write(input logic [3:0] chan_nibble,
|
||||
input logic [11:0] reg_off,
|
||||
input logic [31:0] data);
|
||||
@(negedge clk);
|
||||
reg_wr_en = 1'b1;
|
||||
chan_addr = {chan_nibble, reg_off};
|
||||
reg_wr_data = data;
|
||||
@(negedge clk);
|
||||
reg_wr_en = 1'b0;
|
||||
chan_addr = 16'd0;
|
||||
reg_wr_data = 32'd0;
|
||||
endtask
|
||||
|
||||
task automatic do_read(input logic [3:0] chan_nibble,
|
||||
input logic [11:0] reg_off,
|
||||
output logic [31:0] data_out);
|
||||
@(negedge clk);
|
||||
reg_rd_en = 1'b1;
|
||||
chan_addr = {chan_nibble, reg_off};
|
||||
@(negedge clk);
|
||||
reg_rd_en = 1'b0;
|
||||
chan_addr = 16'd0;
|
||||
@(posedge clk);
|
||||
data_out = reg_rd_data;
|
||||
endtask
|
||||
|
||||
task automatic check(input string tag,
|
||||
input logic [31:0] got,
|
||||
input logic [31:0] exp);
|
||||
if (got !== exp) begin
|
||||
$display("[tb_ee_dmac_passive_chan_stub] FAIL %s got=0x%08h exp=0x%08h",
|
||||
tag, got, exp);
|
||||
errors++;
|
||||
end else begin
|
||||
$display("[tb_ee_dmac_passive_chan_stub] ok %s = 0x%08h",
|
||||
tag, got);
|
||||
end
|
||||
endtask
|
||||
|
||||
logic [31:0] rd0;
|
||||
|
||||
initial begin
|
||||
rst_n = 1'b0;
|
||||
reg_wr_en = 1'b0;
|
||||
chan_addr = 16'd0;
|
||||
reg_wr_data = 32'd0;
|
||||
reg_rd_en = 1'b0;
|
||||
|
||||
repeat (4) @(posedge clk);
|
||||
rst_n = 1'b1;
|
||||
@(posedge clk);
|
||||
|
||||
// ===== 1) ch4 reset reads zero =====
|
||||
do_read(4'hC, 12'h000, rd0); check("ch4_chcr_reset", rd0, 32'd0);
|
||||
do_read(4'hC, 12'h010, rd0); check("ch4_madr_reset", rd0, 32'd0);
|
||||
do_read(4'hC, 12'h020, rd0); check("ch4_qwc_reset", rd0, 32'd0);
|
||||
do_read(4'hC, 12'h030, rd0); check("ch4_tadr_reset", rd0, 32'd0);
|
||||
|
||||
// ===== 2) ch4 round-trip =====
|
||||
do_write(4'hC, 12'h000, 32'h1234_5678);
|
||||
do_write(4'hC, 12'h010, 32'h89AB_CDEF);
|
||||
do_write(4'hC, 12'h020, 32'hCAFE_BABE);
|
||||
do_write(4'hC, 12'h030, 32'hDEAD_BEEF);
|
||||
do_read (4'hC, 12'h000, rd0); check("ch4_chcr_rt", rd0, 32'h1234_5678);
|
||||
do_read (4'hC, 12'h010, rd0); check("ch4_madr_rt", rd0, 32'h89AB_CDEF);
|
||||
do_read (4'hC, 12'h020, rd0); check("ch4_qwc_rt", rd0, 32'hCAFE_BABE);
|
||||
do_read (4'hC, 12'h030, rd0); check("ch4_tadr_rt", rd0, 32'hDEAD_BEEF);
|
||||
|
||||
// ===== 3) Channel independence (ch4 vs ch5) =====
|
||||
do_write(4'hD, 12'h000, 32'hAAAA_5555); // ch5 CHCR
|
||||
do_write(4'hD, 12'h010, 32'h5555_AAAA); // ch5 MADR
|
||||
// ch4 values must be unchanged.
|
||||
do_read (4'hC, 12'h000, rd0); check("ch4_chcr_after_ch5_wr",
|
||||
rd0, 32'h1234_5678);
|
||||
do_read (4'hC, 12'h010, rd0); check("ch4_madr_after_ch5_wr",
|
||||
rd0, 32'h89AB_CDEF);
|
||||
// ch5 reads its own values.
|
||||
do_read (4'hD, 12'h000, rd0); check("ch5_chcr_rt", rd0, 32'hAAAA_5555);
|
||||
do_read (4'hD, 12'h010, rd0); check("ch5_madr_rt", rd0, 32'h5555_AAAA);
|
||||
|
||||
// ===== 4) ch2 (chan_nibble=0xA) is OUTSIDE the passive set =====
|
||||
// Reads return 0 (invalid channel); writes are dropped. The
|
||||
// real ch2 GIF surface lives in dmac_reg_stub on dedicated
|
||||
// ports — this stub must not shadow it.
|
||||
do_write(4'hA, 12'h000, 32'hF00D_F00D); // dropped
|
||||
do_read (4'hA, 12'h000, rd0); check("ch2_chcr_unmapped_returns_zero",
|
||||
rd0, 32'd0);
|
||||
|
||||
// Also confirm ch0 (0x8), ch1 (0x9), ch3 (0xB) reset to 0.
|
||||
do_read (4'h8, 12'h000, rd0); check("ch0_chcr_reset", rd0, 32'd0);
|
||||
do_read (4'h9, 12'h000, rd0); check("ch1_chcr_reset", rd0, 32'd0);
|
||||
do_read (4'hB, 12'h000, rd0); check("ch3_chcr_reset", rd0, 32'd0);
|
||||
|
||||
// ===== 5) Unknown register offset on a valid channel =====
|
||||
do_read (4'hC, 12'h040, rd0); check("ch4_unknown_offset_reads_zero",
|
||||
rd0, 32'd0);
|
||||
do_write(4'hC, 12'h040, 32'hFEED_FACE); // dropped
|
||||
do_read (4'hC, 12'h000, rd0); check("ch4_chcr_after_unknown_wr",
|
||||
rd0, 32'h1234_5678);
|
||||
|
||||
if (errors == 0)
|
||||
$display("[tb_ee_dmac_passive_chan_stub] PASS");
|
||||
else
|
||||
$display("[tb_ee_dmac_passive_chan_stub] FAIL errors=%0d", errors);
|
||||
|
||||
$finish;
|
||||
end
|
||||
|
||||
initial begin
|
||||
#5_000_000;
|
||||
$display("[tb_ee_dmac_passive_chan_stub] TIMEOUT");
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule : tb_ee_dmac_passive_chan_stub
|
||||
Reference in New Issue
Block a user