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>
238 lines
9.0 KiB
Systemverilog
238 lines
9.0 KiB
Systemverilog
// retroDE_ps2 — tb_sio2_input_stub (Ch234)
|
|
// ============================================================================
|
|
// Focused unit-test TB for sio2_input_stub. Instantiates the stub
|
|
// directly (no IOP map) and drives synthetic input_p1/input_p2 bitmaps,
|
|
// then reads back PAD_P1_STATE / PAD_P2_STATE / PAD_STATUS through the
|
|
// stub's IOP-map-style read port.
|
|
//
|
|
// Verifies:
|
|
// 1. Reset: stub responds 32'd0 for any read.
|
|
// 2. No buttons (INPUT_P1 = INPUT_P2 = 0) → Sony word = 0xFFFF.
|
|
// 3. Single buttons (each of the 16 retroDE bits) map to the right
|
|
// Sony pad bit (active-low: pressed = bit cleared).
|
|
// 4. Combos (multiple buttons) → combined active-low
|
|
// pattern.
|
|
// 5. P1 and P2 are independent (writing P1 doesn't change P2 readback
|
|
// and vice versa).
|
|
// 6. PAD_STATUS reads `32'h0000_0001` (bit 0 = present/valid).
|
|
// 7. Writes accepted-and-ignored (no mutation of any state).
|
|
// 8. Out-of-range word offsets inside the region read 0.
|
|
//
|
|
// CDC note: this TB drives a single clock, so the stub's 2-FF
|
|
// synchronizer is functionally a 2-cycle delay. The TB inserts
|
|
// `repeat (3) @(posedge clk)` after every INPUT_P1/P2 change to let
|
|
// the sync settle + the read pipeline produce valid data.
|
|
// ============================================================================
|
|
|
|
`timescale 1ns/1ps
|
|
|
|
module tb_sio2_input_stub;
|
|
|
|
logic clk;
|
|
logic rst_n;
|
|
initial clk = 1'b0;
|
|
always #5 clk = ~clk;
|
|
|
|
logic [31:0] input_p1;
|
|
logic [31:0] input_p2;
|
|
|
|
logic rd_en;
|
|
logic [3:0] rd_addr;
|
|
wire [31:0] rd_data;
|
|
wire rd_valid;
|
|
|
|
logic wr_en;
|
|
logic [3:0] wr_addr;
|
|
logic [31:0] wr_data;
|
|
|
|
sio2_input_stub u_dut (
|
|
.clk (clk),
|
|
.rst_n (rst_n),
|
|
.input_p1 (input_p1),
|
|
.input_p2 (input_p2),
|
|
.rd_en (rd_en),
|
|
.rd_addr (rd_addr),
|
|
.rd_data (rd_data),
|
|
.rd_valid (rd_valid),
|
|
.wr_en (wr_en),
|
|
.wr_addr (wr_addr),
|
|
.wr_data (wr_data)
|
|
);
|
|
|
|
int errors;
|
|
|
|
task automatic check_eq(input string label,
|
|
input logic [31:0] got,
|
|
input logic [31:0] expected);
|
|
if (got !== expected) begin
|
|
$error("[%s] got 0x%08x expected 0x%08x", label, got, expected);
|
|
errors = errors + 1;
|
|
end
|
|
endtask
|
|
|
|
// Issue a read at the given word offset and sample rd_data the
|
|
// cycle rd_valid asserts.
|
|
task automatic do_read(input logic [3:0] addr, output logic [31:0] data);
|
|
@(posedge clk);
|
|
rd_en <= 1'b1;
|
|
rd_addr <= addr;
|
|
@(posedge clk);
|
|
rd_en <= 1'b0;
|
|
wait (rd_valid);
|
|
data = rd_data;
|
|
@(posedge clk);
|
|
endtask
|
|
|
|
// Apply new pad state and let the 2-FF sync settle.
|
|
task automatic apply_pads(input logic [31:0] new_p1, input logic [31:0] new_p2);
|
|
@(posedge clk);
|
|
input_p1 <= new_p1;
|
|
input_p2 <= new_p2;
|
|
repeat (4) @(posedge clk); // 2-FF sync + slack
|
|
endtask
|
|
|
|
// Sony pad-word expectation helper. Mirrors the stub's sony_word()
|
|
// function — bit-for-bit copy from `docs/contracts/sio2_pad.md`.
|
|
function automatic logic [15:0] expected_sony(input logic [31:0] joy);
|
|
logic [7:0] b3, b4;
|
|
b3 = ~{joy[1], joy[2], joy[0], joy[3], joy[4], joy[15], joy[14], joy[5]};
|
|
b4 = ~{joy[8], joy[7], joy[9], joy[6], joy[11], joy[10], joy[13], joy[12]};
|
|
expected_sony = {b4, b3};
|
|
endfunction
|
|
|
|
logic [31:0] rd;
|
|
logic [31:0] pat;
|
|
|
|
initial begin
|
|
errors = 0;
|
|
rst_n = 1'b0;
|
|
rd_en = 1'b0;
|
|
rd_addr = 4'd0;
|
|
wr_en = 1'b0;
|
|
wr_addr = 4'd0;
|
|
wr_data = 32'd0;
|
|
input_p1 = 32'd0;
|
|
input_p2 = 32'd0;
|
|
|
|
repeat (4) @(posedge clk);
|
|
rst_n = 1'b1;
|
|
repeat (4) @(posedge clk);
|
|
|
|
// ----------------------------------------------------------
|
|
// §1. Reset state — all pad bits LOW → Sony word 0xFFFF.
|
|
// STATUS = 1.
|
|
// ----------------------------------------------------------
|
|
do_read(4'h0, rd); check_eq("rst_P1_word", rd, 32'h0000_FFFF);
|
|
do_read(4'h1, rd); check_eq("rst_P2_word", rd, 32'h0000_FFFF);
|
|
do_read(4'h2, rd); check_eq("rst_STATUS", rd, 32'h0000_0001);
|
|
|
|
// Out-of-range reads inside the 4-bit addr field → 0.
|
|
do_read(4'h3, rd); check_eq("rst_oob_3", rd, 32'd0);
|
|
do_read(4'hF, rd); check_eq("rst_oob_F", rd, 32'd0);
|
|
|
|
// ----------------------------------------------------------
|
|
// §2. Single-button mapping — for each of the 16 retroDE
|
|
// bits, press only that bit and verify the Sony word
|
|
// has exactly the corresponding bit cleared.
|
|
// ----------------------------------------------------------
|
|
for (int i = 0; i < 16; i = i + 1) begin
|
|
pat = (32'd1 << i);
|
|
apply_pads(pat, 32'd0);
|
|
do_read(4'h0, rd);
|
|
check_eq($sformatf("single_bit_%0d_P1", i),
|
|
rd, {16'd0, expected_sony(pat)});
|
|
do_read(4'h1, rd);
|
|
check_eq($sformatf("single_bit_%0d_P2_unaffected", i),
|
|
rd, 32'h0000_FFFF);
|
|
end
|
|
|
|
// ----------------------------------------------------------
|
|
// §3. JOY_OSD (bit 16) is intentionally NOT forwarded —
|
|
// retrodesd consumes it before the bridge. Pressing it
|
|
// should leave the Sony word at 0xFFFF.
|
|
// ----------------------------------------------------------
|
|
apply_pads(32'h0001_0000, 32'd0);
|
|
do_read(4'h0, rd); check_eq("OSD_bit_not_forwarded",
|
|
rd, 32'h0000_FFFF);
|
|
|
|
// ----------------------------------------------------------
|
|
// §4. Combos. The classic "Konami code" final two — Start +
|
|
// Select pressed together → byte3 bits 0 and 3 both
|
|
// cleared, all other bits HIGH (released).
|
|
// ----------------------------------------------------------
|
|
pat = (32'd1 << 4) | (32'd1 << 5); // START | SELECT
|
|
apply_pads(pat, 32'd0);
|
|
do_read(4'h0, rd);
|
|
check_eq("combo_START_SELECT", rd, {16'd0, expected_sony(pat)});
|
|
|
|
// All face + D-pad pressed at once. Both bytes have visible
|
|
// clears.
|
|
pat = (32'd1 << 6) | (32'd1 << 7) | (32'd1 << 8) | (32'd1 << 9)
|
|
| (32'd1 << 0) | (32'd1 << 1) | (32'd1 << 2) | (32'd1 << 3);
|
|
apply_pads(pat, 32'd0);
|
|
do_read(4'h0, rd);
|
|
check_eq("combo_face_plus_dpad", rd, {16'd0, expected_sony(pat)});
|
|
|
|
// ----------------------------------------------------------
|
|
// §5. P1/P2 independence. Different patterns on each
|
|
// channel, verify reads track each port independently.
|
|
// ----------------------------------------------------------
|
|
apply_pads(32'hAAAA_5555, 32'h5555_AAAA);
|
|
do_read(4'h0, rd);
|
|
check_eq("indep_P1_word",
|
|
rd, {16'd0, expected_sony(32'hAAAA_5555)});
|
|
do_read(4'h1, rd);
|
|
check_eq("indep_P2_word",
|
|
rd, {16'd0, expected_sony(32'h5555_AAAA)});
|
|
|
|
// ----------------------------------------------------------
|
|
// §6. Writes accepted-and-ignored. Drive write strobes at
|
|
// each of the three mapped addresses, then verify the
|
|
// readback values are unchanged.
|
|
// ----------------------------------------------------------
|
|
@(posedge clk);
|
|
wr_en <= 1'b1;
|
|
wr_addr <= 4'h0;
|
|
wr_data <= 32'hF00DBABE;
|
|
@(posedge clk);
|
|
wr_addr <= 4'h1;
|
|
wr_data <= 32'hCAFEF00D;
|
|
@(posedge clk);
|
|
wr_addr <= 4'h2;
|
|
wr_data <= 32'h12345678;
|
|
@(posedge clk);
|
|
wr_en <= 1'b0;
|
|
repeat (2) @(posedge clk);
|
|
|
|
do_read(4'h0, rd);
|
|
check_eq("wr_ignored_P1", rd, {16'd0, expected_sony(32'hAAAA_5555)});
|
|
do_read(4'h1, rd);
|
|
check_eq("wr_ignored_P2", rd, {16'd0, expected_sony(32'h5555_AAAA)});
|
|
do_read(4'h2, rd);
|
|
check_eq("wr_ignored_STATUS", rd, 32'h0000_0001);
|
|
|
|
// ----------------------------------------------------------
|
|
// §7. Clearing both pads returns to all-released = 0xFFFF.
|
|
// ----------------------------------------------------------
|
|
apply_pads(32'd0, 32'd0);
|
|
do_read(4'h0, rd); check_eq("clr_P1", rd, 32'h0000_FFFF);
|
|
do_read(4'h1, rd); check_eq("clr_P2", rd, 32'h0000_FFFF);
|
|
do_read(4'h2, rd); check_eq("clr_STATUS", rd, 32'h0000_0001);
|
|
|
|
// ----------------------------------------------------------
|
|
// Done.
|
|
// ----------------------------------------------------------
|
|
$display("[tb_sio2_input_stub] errors=%0d", errors);
|
|
if (errors == 0) $display("[tb_sio2_input_stub] PASS");
|
|
else $display("[tb_sio2_input_stub] FAIL");
|
|
$finish;
|
|
end
|
|
|
|
initial begin
|
|
#5_000_000;
|
|
$error("[tb_sio2_input_stub] TIMEOUT");
|
|
$finish;
|
|
end
|
|
|
|
endmodule : tb_sio2_input_stub
|