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,282 @@
|
||||
// retroDE_ps2 — tb_iop_memory_map_collision (Ch261)
|
||||
// ============================================================================
|
||||
// Focused arbitration test for the iop_memory_map_stub deferred-CPU-RAM-read
|
||||
// slot landed alongside Ch261. The Ch261 SIF-landing TB surfaced a real bug:
|
||||
// when CPU and DMA both wanted the iop_ram_stub read port in the same cycle,
|
||||
// the map prioritized the CPU's address but routed `ram_rd_data` to BOTH
|
||||
// the CPU and the DMA paths — silent DMA data corruption.
|
||||
//
|
||||
// This TB:
|
||||
// 1. Preloads two distinct sentinels at two distinct IOP RAM word offsets.
|
||||
// 2. Drives `iop_rd_en` (CPU) and `dma_rd_en` (DMA) on the SAME cycle, with
|
||||
// DIFFERENT addresses (one each).
|
||||
// 3. Asserts:
|
||||
// - `dma_rd_data` returns the DMA-target sentinel on the next cycle
|
||||
// (DMA wins the bus immediately).
|
||||
// - `iop_rd_data` returns the CPU-target sentinel one cycle LATER
|
||||
// (deferred CPU read services on the next non-DMA cycle).
|
||||
// - `iop_rd_valid` stays LOW during the collision-deferral cycle and
|
||||
// pulses only when the deferred read actually finishes.
|
||||
//
|
||||
// No CPU or DMAC core here — the TB drives the map's CPU- and DMA-read ports
|
||||
// directly, so any future change to those clients can't mask the regression.
|
||||
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_iop_memory_map_collision;
|
||||
|
||||
localparam int IOP_RAM_BYTES = 4 * 1024;
|
||||
localparam int IOP_RAM_ADDR_W = $clog2(IOP_RAM_BYTES);
|
||||
|
||||
localparam logic [31:0] CPU_RAM_ADDR = 32'h0000_0100; // word 0x40
|
||||
localparam logic [31:0] DMA_RAM_ADDR = 32'h0000_0200; // word 0x80
|
||||
localparam logic [31:0] CPU_SENTINEL = 32'hCAFE_C0DE;
|
||||
localparam logic [31:0] DMA_SENTINEL = 32'hDEAD_BEEF;
|
||||
|
||||
logic clk;
|
||||
logic rst_n;
|
||||
initial clk = 1'b0;
|
||||
always #5 clk = ~clk;
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// DUT ports
|
||||
// ------------------------------------------------------------------
|
||||
logic cpu_rd_en;
|
||||
logic [31:0] cpu_rd_addr;
|
||||
logic [31:0] cpu_rd_data;
|
||||
logic cpu_rd_valid;
|
||||
|
||||
logic cpu_wr_en;
|
||||
logic [31:0] cpu_wr_addr;
|
||||
logic [31:0] cpu_wr_data;
|
||||
logic [3:0] cpu_wr_be;
|
||||
|
||||
logic tb_br_wr_en;
|
||||
logic [31:0] tb_br_wr_addr;
|
||||
logic [31:0] tb_br_wr_data;
|
||||
logic [3:0] tb_br_wr_be;
|
||||
|
||||
logic dma_rd_en;
|
||||
logic [31:0] dma_rd_addr;
|
||||
logic [7:0] dma_master_id;
|
||||
logic [31:0] dma_rd_data;
|
||||
logic dma_rd_valid;
|
||||
|
||||
logic ram_rd_en;
|
||||
logic [20:0] ram_rd_addr;
|
||||
logic [31:0] ram_rd_data;
|
||||
logic ram_rd_valid;
|
||||
logic ram_wr_en;
|
||||
logic [20:0] ram_wr_addr;
|
||||
logic [31:0] ram_wr_data;
|
||||
logic [3:0] ram_wr_be;
|
||||
logic [7:0] ram_master_id;
|
||||
|
||||
iop_memory_map_stub u_map (
|
||||
.clk(clk), .rst_n(rst_n),
|
||||
.iop_rd_en(cpu_rd_en), .iop_rd_addr(cpu_rd_addr),
|
||||
.iop_rd_data(cpu_rd_data), .iop_rd_valid(cpu_rd_valid),
|
||||
.iop_wr_en(cpu_wr_en), .iop_wr_addr(cpu_wr_addr),
|
||||
.iop_wr_data(cpu_wr_data), .iop_wr_be(cpu_wr_be),
|
||||
.master_id(8'd2),
|
||||
.bridge_wr_en(tb_br_wr_en), .bridge_wr_addr(tb_br_wr_addr),
|
||||
.bridge_wr_data(tb_br_wr_data), .bridge_wr_be(tb_br_wr_be),
|
||||
.bridge_master_id(8'd0),
|
||||
.dma_rd_en(dma_rd_en), .dma_rd_addr(dma_rd_addr),
|
||||
.dma_master_id(dma_master_id),
|
||||
.dma_rd_data(dma_rd_data), .dma_rd_valid(dma_rd_valid),
|
||||
.sif_rd_en(), .sif_rd_addr(),
|
||||
.sif_rd_data(32'd0), .sif_rd_valid(1'b0),
|
||||
.sif_wr_en(), .sif_wr_addr(), .sif_wr_data(),
|
||||
.iop_dmac_rd_en(), .iop_dmac_rd_addr(),
|
||||
.iop_dmac_rd_data(32'd0), .iop_dmac_rd_valid(1'b0),
|
||||
.iop_dmac_wr_en(), .iop_dmac_wr_addr(), .iop_dmac_wr_data(),
|
||||
.iop_intc_rd_en(), .iop_intc_rd_addr(),
|
||||
.iop_intc_rd_data(32'd0), .iop_intc_rd_valid(1'b0),
|
||||
.iop_intc_wr_en(), .iop_intc_wr_addr(), .iop_intc_wr_data(),
|
||||
.input_p1(32'd0), .input_p2(32'd0),
|
||||
.bios_rd_en(), .bios_rd_addr(),
|
||||
.bios_rd_data(32'd0), .bios_rd_valid(1'b0),
|
||||
.ram_rd_en(ram_rd_en), .ram_rd_addr(ram_rd_addr),
|
||||
.ram_rd_data(ram_rd_data), .ram_rd_valid(ram_rd_valid),
|
||||
.ram_wr_en(ram_wr_en), .ram_wr_addr(ram_wr_addr),
|
||||
.ram_wr_data(ram_wr_data), .ram_wr_be(ram_wr_be),
|
||||
.ram_master_id(ram_master_id),
|
||||
.ev_valid(), .ev_subsys(), .ev_event(),
|
||||
.ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
|
||||
);
|
||||
|
||||
iop_ram_stub #(.SIZE_BYTES(IOP_RAM_BYTES)) u_ram (
|
||||
.clk(clk), .rst_n(rst_n),
|
||||
.rd_en(ram_rd_en),
|
||||
.rd_addr(ram_rd_addr[IOP_RAM_ADDR_W-1:0]),
|
||||
.rd_data(ram_rd_data), .rd_valid(ram_rd_valid),
|
||||
.wr_en(ram_wr_en),
|
||||
.wr_addr(ram_wr_addr[IOP_RAM_ADDR_W-1:0]),
|
||||
.wr_data(ram_wr_data), .wr_be(ram_wr_be),
|
||||
.master_id(ram_master_id),
|
||||
.ev_valid(), .ev_subsys(), .ev_event(),
|
||||
.ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
|
||||
);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ------------------------------------------------------------------
|
||||
task automatic bridge_write(input logic [31:0] addr,
|
||||
input logic [31:0] data);
|
||||
@(negedge clk);
|
||||
tb_br_wr_en = 1'b1;
|
||||
tb_br_wr_addr = addr;
|
||||
tb_br_wr_data = data;
|
||||
tb_br_wr_be = 4'b1111;
|
||||
@(negedge clk);
|
||||
tb_br_wr_en = 1'b0;
|
||||
tb_br_wr_addr = 32'd0;
|
||||
tb_br_wr_data = 32'd0;
|
||||
tb_br_wr_be = 4'd0;
|
||||
endtask
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Stimulus + assertions
|
||||
// ------------------------------------------------------------------
|
||||
int errors;
|
||||
initial errors = 0;
|
||||
|
||||
initial begin
|
||||
rst_n = 1'b0;
|
||||
cpu_rd_en = 1'b0;
|
||||
cpu_rd_addr = 32'd0;
|
||||
cpu_wr_en = 1'b0;
|
||||
cpu_wr_addr = 32'd0;
|
||||
cpu_wr_data = 32'd0;
|
||||
cpu_wr_be = 4'd0;
|
||||
tb_br_wr_en = 1'b0;
|
||||
tb_br_wr_addr = 32'd0;
|
||||
tb_br_wr_data = 32'd0;
|
||||
tb_br_wr_be = 4'd0;
|
||||
dma_rd_en = 1'b0;
|
||||
dma_rd_addr = 32'd0;
|
||||
dma_master_id = 8'd5;
|
||||
|
||||
repeat (4) @(posedge clk);
|
||||
rst_n = 1'b1;
|
||||
repeat (2) @(posedge clk);
|
||||
|
||||
// Preload the two distinct sentinels.
|
||||
bridge_write(CPU_RAM_ADDR, CPU_SENTINEL);
|
||||
bridge_write(DMA_RAM_ADDR, DMA_SENTINEL);
|
||||
repeat (2) @(posedge clk);
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// The collision cycle. Drive both reads on the same posedge.
|
||||
// Sample valids/data at the negedge between each posedge —
|
||||
// that mirrors the existing tb_iop_memory_map_stub do_read_expect
|
||||
// pattern (rd_pending pulses for one cycle, the negedge sits
|
||||
// inside that cycle so the valid is observable).
|
||||
// --------------------------------------------------------------
|
||||
@(negedge clk);
|
||||
cpu_rd_en = 1'b1;
|
||||
cpu_rd_addr = CPU_RAM_ADDR;
|
||||
dma_rd_en = 1'b1;
|
||||
dma_rd_addr = DMA_RAM_ADDR;
|
||||
|
||||
@(negedge clk);
|
||||
// Pulses go low — single-cycle requests. The posedge between
|
||||
// these two negedges latched cpu_pend_addr (deferred) and
|
||||
// serviced DMA's read.
|
||||
cpu_rd_en = 1'b0;
|
||||
cpu_rd_addr = 32'd0;
|
||||
dma_rd_en = 1'b0;
|
||||
dma_rd_addr = 32'd0;
|
||||
|
||||
// Sample now (negedge just after the cycle that serviced DMA
|
||||
// and deferred CPU). DMA's data should be on dma_rd_data with
|
||||
// dma_rd_valid high; CPU's valid still LOW.
|
||||
if (dma_rd_valid !== 1'b1) begin
|
||||
$error("[tb_iop_memory_map_collision] cycle+1: dma_rd_valid not asserted (got %0b)", dma_rd_valid);
|
||||
errors = errors + 1;
|
||||
end
|
||||
if (dma_rd_data !== DMA_SENTINEL) begin
|
||||
$error("[tb_iop_memory_map_collision] cycle+1: dma_rd_data=0x%08h expected DMA_SENTINEL=0x%08h",
|
||||
dma_rd_data, DMA_SENTINEL);
|
||||
errors = errors + 1;
|
||||
end
|
||||
if (cpu_rd_valid !== 1'b0) begin
|
||||
$error("[tb_iop_memory_map_collision] cycle+1: cpu_rd_valid asserted prematurely (should be deferred)");
|
||||
errors = errors + 1;
|
||||
end
|
||||
|
||||
// Next negedge: the cycle that serviced the deferred CPU read
|
||||
// (no DMA contending) is now visible. cpu_rd_valid pulses with
|
||||
// CPU_SENTINEL; dma_rd_valid back to 0.
|
||||
@(negedge clk);
|
||||
if (cpu_rd_valid !== 1'b1) begin
|
||||
$error("[tb_iop_memory_map_collision] cycle+2: cpu_rd_valid not asserted (deferred read should land)");
|
||||
errors = errors + 1;
|
||||
end
|
||||
if (cpu_rd_data !== CPU_SENTINEL) begin
|
||||
$error("[tb_iop_memory_map_collision] cycle+2: cpu_rd_data=0x%08h expected CPU_SENTINEL=0x%08h",
|
||||
cpu_rd_data, CPU_SENTINEL);
|
||||
errors = errors + 1;
|
||||
end
|
||||
if (dma_rd_valid !== 1'b0) begin
|
||||
$error("[tb_iop_memory_map_collision] cycle+2: dma_rd_valid stuck high (should have de-asserted)");
|
||||
errors = errors + 1;
|
||||
end
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// Sanity 2 — solo CPU read (no DMA contention) returns its
|
||||
// sentinel one cycle later with no deferral.
|
||||
// --------------------------------------------------------------
|
||||
repeat (3) @(posedge clk);
|
||||
@(negedge clk);
|
||||
cpu_rd_en = 1'b1;
|
||||
cpu_rd_addr = CPU_RAM_ADDR;
|
||||
@(negedge clk);
|
||||
cpu_rd_en = 1'b0;
|
||||
cpu_rd_addr = 32'd0;
|
||||
if (cpu_rd_valid !== 1'b1) begin
|
||||
$error("[tb_iop_memory_map_collision] solo CPU read: cpu_rd_valid not asserted");
|
||||
errors = errors + 1;
|
||||
end
|
||||
if (cpu_rd_data !== CPU_SENTINEL) begin
|
||||
$error("[tb_iop_memory_map_collision] solo CPU read: cpu_rd_data=0x%08h expected 0x%08h",
|
||||
cpu_rd_data, CPU_SENTINEL);
|
||||
errors = errors + 1;
|
||||
end
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// Sanity 3 — solo DMA read returns its sentinel one cycle later
|
||||
// with no deferral and no CPU interference.
|
||||
// --------------------------------------------------------------
|
||||
repeat (3) @(posedge clk);
|
||||
@(negedge clk);
|
||||
dma_rd_en = 1'b1;
|
||||
dma_rd_addr = DMA_RAM_ADDR;
|
||||
@(negedge clk);
|
||||
dma_rd_en = 1'b0;
|
||||
dma_rd_addr = 32'd0;
|
||||
if (dma_rd_valid !== 1'b1) begin
|
||||
$error("[tb_iop_memory_map_collision] solo DMA read: dma_rd_valid not asserted");
|
||||
errors = errors + 1;
|
||||
end
|
||||
if (dma_rd_data !== DMA_SENTINEL) begin
|
||||
$error("[tb_iop_memory_map_collision] solo DMA read: dma_rd_data=0x%08h expected 0x%08h",
|
||||
dma_rd_data, DMA_SENTINEL);
|
||||
errors = errors + 1;
|
||||
end
|
||||
|
||||
repeat (4) @(posedge clk);
|
||||
$display("[tb_iop_memory_map_collision] errors=%0d", errors);
|
||||
if (errors == 0) $display("[tb_iop_memory_map_collision] PASS");
|
||||
else $display("[tb_iop_memory_map_collision] FAIL");
|
||||
$finish;
|
||||
end
|
||||
|
||||
initial begin
|
||||
#100_000;
|
||||
$error("[tb_iop_memory_map_collision] TIMEOUT");
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule : tb_iop_memory_map_collision
|
||||
@@ -0,0 +1,280 @@
|
||||
// retroDE_ps2 — tb_iop_memory_map_stub
|
||||
//
|
||||
// Unit test for iop_memory_map_stub wired to iop_ram_stub. TB drives the
|
||||
// map's CPU-side port and verifies that RAM-region accesses land in the
|
||||
// backing store while non-RAM accesses deterministically fault.
|
||||
//
|
||||
// Scenarios:
|
||||
// 1. Word write into IOP RAM region, read back — round-trip
|
||||
// 2. Same write/read using a kseg1-aliased address (0xA0000000|offset)
|
||||
// — confirms kseg stripping routes to the same physical slot
|
||||
// 3. Unmapped read — returns 0xDEADBEEF, emits IOP UNMAPPED trace
|
||||
// 4. Unmapped write — no effect on RAM, emits IOP UNMAPPED trace
|
||||
// 5. Trace tagging: region=IOP_RAM on routed accesses, region=0xFF on
|
||||
// unmapped, master_id preserved through the map
|
||||
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_iop_memory_map_stub;
|
||||
|
||||
localparam int RAM_BYTES = 4 * 1024;
|
||||
localparam int RAM_ADDR_W = $clog2(RAM_BYTES);
|
||||
|
||||
logic clk;
|
||||
logic rst_n;
|
||||
|
||||
initial clk = 1'b0;
|
||||
always #5 clk = ~clk;
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// DUTs
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
// Map <-> IOP CPU (TB-driven)
|
||||
logic iop_rd_en;
|
||||
logic [31:0] iop_rd_addr;
|
||||
logic [31:0] iop_rd_data;
|
||||
logic iop_rd_valid;
|
||||
logic iop_wr_en;
|
||||
logic [31:0] iop_wr_addr;
|
||||
logic [31:0] iop_wr_data;
|
||||
logic [3:0] iop_wr_be;
|
||||
logic [7:0] master_id;
|
||||
|
||||
// Map <-> RAM
|
||||
logic ram_rd_en;
|
||||
logic [20:0] ram_rd_addr;
|
||||
logic [31:0] ram_rd_data;
|
||||
logic ram_rd_valid;
|
||||
logic ram_wr_en;
|
||||
logic [20:0] ram_wr_addr;
|
||||
logic [31:0] ram_wr_data;
|
||||
logic [3:0] ram_wr_be;
|
||||
logic [7:0] ram_master_id;
|
||||
|
||||
// Map trace
|
||||
logic map_ev_valid;
|
||||
trace_pkg::subsys_e map_ev_subsys;
|
||||
trace_pkg::event_e map_ev_event;
|
||||
logic [63:0] map_ev_arg0, map_ev_arg1, map_ev_arg2, map_ev_arg3;
|
||||
logic [31:0] map_ev_flags;
|
||||
|
||||
// RAM trace
|
||||
logic ram_ev_valid;
|
||||
trace_pkg::subsys_e ram_ev_subsys;
|
||||
trace_pkg::event_e ram_ev_event;
|
||||
logic [63:0] ram_ev_arg0, ram_ev_arg1, ram_ev_arg2, ram_ev_arg3;
|
||||
logic [31:0] ram_ev_flags;
|
||||
|
||||
iop_memory_map_stub u_map (
|
||||
.clk(clk), .rst_n(rst_n),
|
||||
.iop_rd_en(iop_rd_en), .iop_rd_addr(iop_rd_addr),
|
||||
.iop_rd_data(iop_rd_data), .iop_rd_valid(iop_rd_valid),
|
||||
.iop_wr_en(iop_wr_en), .iop_wr_addr(iop_wr_addr),
|
||||
.iop_wr_data(iop_wr_data), .iop_wr_be(iop_wr_be),
|
||||
.master_id(master_id),
|
||||
// Bridge port — unused by this TB
|
||||
.bridge_wr_en(1'b0), .bridge_wr_addr(32'd0),
|
||||
.bridge_wr_data(32'd0), .bridge_wr_be(4'd0),
|
||||
.bridge_master_id(8'd0),
|
||||
// DMA read-master port — unused by this TB
|
||||
.dma_rd_en(1'b0), .dma_rd_addr(32'd0),
|
||||
.dma_master_id(8'd0),
|
||||
.dma_rd_data(), .dma_rd_valid(),
|
||||
.ram_rd_en(ram_rd_en), .ram_rd_addr(ram_rd_addr),
|
||||
.ram_rd_data(ram_rd_data), .ram_rd_valid(ram_rd_valid),
|
||||
.ram_wr_en(ram_wr_en), .ram_wr_addr(ram_wr_addr),
|
||||
.ram_wr_data(ram_wr_data), .ram_wr_be(ram_wr_be),
|
||||
.ram_master_id(ram_master_id),
|
||||
// SIF register-shell port — unused by this TB
|
||||
.sif_rd_en(), .sif_rd_addr(),
|
||||
.sif_rd_data(32'd0), .sif_rd_valid(1'b0),
|
||||
.sif_wr_en(), .sif_wr_addr(), .sif_wr_data(),
|
||||
// IOP DMAC port — unused by this TB
|
||||
.iop_dmac_rd_en(), .iop_dmac_rd_addr(),
|
||||
.iop_dmac_rd_data(32'd0), .iop_dmac_rd_valid(1'b0),
|
||||
.iop_dmac_wr_en(), .iop_dmac_wr_addr(), .iop_dmac_wr_data(),
|
||||
// IOP INTC port — unused by this TB
|
||||
.iop_intc_rd_en(), .iop_intc_rd_addr(),
|
||||
.iop_intc_rd_data(32'd0), .iop_intc_rd_valid(1'b0),
|
||||
.iop_intc_wr_en(), .iop_intc_wr_addr(), .iop_intc_wr_data(),
|
||||
.input_p1(32'd0), .input_p2(32'd0),
|
||||
// BIOS ROM port — unused by this TB
|
||||
.bios_rd_en(), .bios_rd_addr(),
|
||||
.bios_rd_data(32'd0), .bios_rd_valid(1'b0),
|
||||
.ev_valid(map_ev_valid), .ev_subsys(map_ev_subsys),
|
||||
.ev_event(map_ev_event),
|
||||
.ev_arg0(map_ev_arg0), .ev_arg1(map_ev_arg1),
|
||||
.ev_arg2(map_ev_arg2), .ev_arg3(map_ev_arg3),
|
||||
.ev_flags(map_ev_flags)
|
||||
);
|
||||
|
||||
iop_ram_stub #(.SIZE_BYTES(RAM_BYTES)) u_ram (
|
||||
.clk(clk), .rst_n(rst_n),
|
||||
.rd_en(ram_rd_en), .rd_addr(ram_rd_addr[RAM_ADDR_W-1:0]),
|
||||
.rd_data(ram_rd_data), .rd_valid(ram_rd_valid),
|
||||
.wr_en(ram_wr_en), .wr_addr(ram_wr_addr[RAM_ADDR_W-1:0]),
|
||||
.wr_data(ram_wr_data), .wr_be(ram_wr_be),
|
||||
.master_id(ram_master_id),
|
||||
.ev_valid(ram_ev_valid), .ev_subsys(ram_ev_subsys),
|
||||
.ev_event(ram_ev_event),
|
||||
.ev_arg0(ram_ev_arg0), .ev_arg1(ram_ev_arg1),
|
||||
.ev_arg2(ram_ev_arg2), .ev_arg3(ram_ev_arg3),
|
||||
.ev_flags(ram_ev_flags)
|
||||
);
|
||||
|
||||
trace_sink_stub #(.FILENAME("iop_map.trace"), .SINK_LABEL("iop_map"))
|
||||
u_trace_map (.clk(clk), .rst_n(rst_n),
|
||||
.ev_valid(map_ev_valid), .ev_subsys(map_ev_subsys),
|
||||
.ev_event(map_ev_event), .ev_arg0(map_ev_arg0),
|
||||
.ev_arg1(map_ev_arg1), .ev_arg2(map_ev_arg2),
|
||||
.ev_arg3(map_ev_arg3), .ev_flags(map_ev_flags));
|
||||
|
||||
trace_sink_stub #(.FILENAME("iop_map_ram.trace"), .SINK_LABEL("iop_ram"))
|
||||
u_trace_ram (.clk(clk), .rst_n(rst_n),
|
||||
.ev_valid(ram_ev_valid), .ev_subsys(ram_ev_subsys),
|
||||
.ev_event(ram_ev_event), .ev_arg0(ram_ev_arg0),
|
||||
.ev_arg1(ram_ev_arg1), .ev_arg2(ram_ev_arg2),
|
||||
.ev_arg3(ram_ev_arg3), .ev_flags(ram_ev_flags));
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Counters (map side)
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
int map_reads_ram;
|
||||
int map_writes_ram;
|
||||
int map_unmapped;
|
||||
int map_master_mismatches;
|
||||
int errors;
|
||||
|
||||
initial begin
|
||||
map_reads_ram = 0;
|
||||
map_writes_ram = 0;
|
||||
map_unmapped = 0;
|
||||
map_master_mismatches = 0;
|
||||
errors = 0;
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst_n && map_ev_valid && map_ev_subsys == trace_pkg::SUBSYS_IOP) begin
|
||||
// Master_id preserved through the map.
|
||||
if (map_ev_arg2[7:0] !== master_id)
|
||||
map_master_mismatches <= map_master_mismatches + 1;
|
||||
|
||||
if (map_ev_event == trace_pkg::EV_READ &&
|
||||
map_ev_arg3[7:0] == 8'd2)
|
||||
map_reads_ram <= map_reads_ram + 1;
|
||||
if (map_ev_event == trace_pkg::EV_WRITE &&
|
||||
map_ev_arg3[7:0] == 8'd2)
|
||||
map_writes_ram <= map_writes_ram + 1;
|
||||
if (map_ev_event == trace_pkg::EV_UNMAPPED)
|
||||
map_unmapped <= map_unmapped + 1;
|
||||
end
|
||||
end
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
task automatic do_write(input logic [31:0] addr,
|
||||
input logic [31:0] data,
|
||||
input logic [3:0] be);
|
||||
@(negedge clk);
|
||||
iop_wr_en = 1'b1;
|
||||
iop_wr_addr = addr;
|
||||
iop_wr_data = data;
|
||||
iop_wr_be = be;
|
||||
@(negedge clk);
|
||||
iop_wr_en = 1'b0;
|
||||
iop_wr_addr = 32'd0;
|
||||
iop_wr_data = 32'd0;
|
||||
iop_wr_be = 4'd0;
|
||||
endtask
|
||||
|
||||
task automatic do_read_expect(input logic [31:0] addr,
|
||||
input logic [31:0] expected,
|
||||
input string label);
|
||||
@(negedge clk);
|
||||
iop_rd_en = 1'b1;
|
||||
iop_rd_addr = addr;
|
||||
@(negedge clk);
|
||||
iop_rd_en = 1'b0;
|
||||
iop_rd_addr = 32'd0;
|
||||
if (iop_rd_data !== expected || iop_rd_valid !== 1'b1) begin
|
||||
$error("[tb_iop_memory_map_stub] read %s: got 0x%08h valid=%0b expected 0x%08h",
|
||||
label, iop_rd_data, iop_rd_valid, expected);
|
||||
errors = errors + 1;
|
||||
end
|
||||
endtask
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Stimulus
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
initial begin
|
||||
rst_n = 1'b0;
|
||||
iop_rd_en = 1'b0;
|
||||
iop_rd_addr = 32'd0;
|
||||
iop_wr_en = 1'b0;
|
||||
iop_wr_addr = 32'd0;
|
||||
iop_wr_data = 32'd0;
|
||||
iop_wr_be = 4'd0;
|
||||
master_id = 8'd2; // IOP_CPU
|
||||
|
||||
repeat (4) @(posedge clk);
|
||||
rst_n = 1'b1;
|
||||
repeat (2) @(posedge clk);
|
||||
|
||||
// 1. RAM-region write + read-back (physical address).
|
||||
do_write(32'h0000_0100, 32'hCAFE_BABE, 4'b1111);
|
||||
do_read_expect(32'h0000_0100, 32'hCAFE_BABE, "RAM phys 0x100");
|
||||
|
||||
// 2. kseg1-aliased access to the same physical slot.
|
||||
do_read_expect(32'hA000_0100, 32'hCAFE_BABE, "RAM kseg1 alias 0xA0000100");
|
||||
|
||||
// 3. RAM write through a kseg0 alias; read-back via physical.
|
||||
do_write(32'h8000_0200, 32'h1234_5678, 4'b1111);
|
||||
do_read_expect(32'h0000_0200, 32'h1234_5678, "kseg0-aliased write landed at phys 0x200");
|
||||
|
||||
// 4. Unmapped read — not in RAM window.
|
||||
do_read_expect(32'h1000_0000, 32'hDEAD_BEEF, "unmapped phys 0x10000000");
|
||||
|
||||
// 5. Unmapped write — shouldn't affect any backing. No easy direct
|
||||
// readback (since there's no backing), but verify trace below.
|
||||
do_write(32'h1F80_0000, 32'h0BAD_F00D, 4'b1111);
|
||||
|
||||
// 6. RAM stays intact after unmapped write.
|
||||
do_read_expect(32'h0000_0100, 32'hCAFE_BABE, "RAM survives nearby unmapped write");
|
||||
|
||||
repeat (4) @(posedge clk);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
$display("[tb_iop_memory_map_stub] map_reads_ram=%0d map_writes_ram=%0d map_unmapped=%0d master_mismatches=%0d errors=%0d",
|
||||
map_reads_ram, map_writes_ram, map_unmapped,
|
||||
map_master_mismatches, errors);
|
||||
|
||||
if (map_reads_ram < 4) $error("expected >= 4 RAM-routed reads, got %0d", map_reads_ram);
|
||||
if (map_writes_ram < 2) $error("expected >= 2 RAM-routed writes, got %0d", map_writes_ram);
|
||||
if (map_unmapped < 2) $error("expected >= 2 UNMAPPED events, got %0d", map_unmapped);
|
||||
if (map_master_mismatches != 0)
|
||||
$error("master_id mismatch at map trace: %0d occurrences", map_master_mismatches);
|
||||
|
||||
if (errors == 0 &&
|
||||
map_reads_ram >= 4 &&
|
||||
map_writes_ram >= 2 &&
|
||||
map_unmapped >= 2 &&
|
||||
map_master_mismatches == 0)
|
||||
$display("[tb_iop_memory_map_stub] PASS");
|
||||
else
|
||||
$display("[tb_iop_memory_map_stub] FAIL");
|
||||
|
||||
$finish;
|
||||
end
|
||||
|
||||
initial begin
|
||||
#200000;
|
||||
$error("[tb_iop_memory_map_stub] timeout");
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule : tb_iop_memory_map_stub
|
||||
@@ -0,0 +1,194 @@
|
||||
// retroDE_ps2 — tb_iop_ram_stub
|
||||
//
|
||||
// Unit test for iop_ram_stub. Mirrors the style of other primitive tests
|
||||
// (bios_rom_stub / ee_ram_stub) — drive reads and writes from the TB,
|
||||
// verify round-trip data, check trace emission and region/master tagging.
|
||||
//
|
||||
// Scenarios:
|
||||
// 1. Post-reset read returns zero (zero-init)
|
||||
// 2. Byte-wide writes with partial byte enables
|
||||
// 3. Word-wide writes
|
||||
// 4. Read-back matches write pattern
|
||||
// 5. Trace events tagged as SUBSYS_IOP with region=IOP_RAM and
|
||||
// caller-provided master_id
|
||||
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_iop_ram_stub;
|
||||
|
||||
localparam int SIZE_BYTES = 4 * 1024;
|
||||
localparam int ADDR_W = $clog2(SIZE_BYTES);
|
||||
|
||||
logic clk;
|
||||
logic rst_n;
|
||||
|
||||
initial clk = 1'b0;
|
||||
always #5 clk = ~clk;
|
||||
|
||||
logic rd_en;
|
||||
logic [ADDR_W-1:0] rd_addr;
|
||||
logic [31:0] rd_data;
|
||||
logic rd_valid;
|
||||
|
||||
logic wr_en;
|
||||
logic [ADDR_W-1:0] wr_addr;
|
||||
logic [31:0] wr_data;
|
||||
logic [3:0] wr_be;
|
||||
|
||||
logic [7:0] master_id;
|
||||
|
||||
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;
|
||||
|
||||
iop_ram_stub #(.SIZE_BYTES(SIZE_BYTES)) u_iop_ram (
|
||||
.clk(clk), .rst_n(rst_n),
|
||||
.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), .wr_be(wr_be),
|
||||
.master_id(master_id),
|
||||
.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)
|
||||
);
|
||||
|
||||
trace_sink_stub #(.FILENAME("iop_ram.trace"), .SINK_LABEL("iop_ram"))
|
||||
u_trace (.clk(clk), .rst_n(rst_n),
|
||||
.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));
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Counters
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
int iop_reads;
|
||||
int iop_writes;
|
||||
int iop_region_hits;
|
||||
int errors;
|
||||
|
||||
initial begin
|
||||
iop_reads = 0;
|
||||
iop_writes = 0;
|
||||
iop_region_hits = 0;
|
||||
errors = 0;
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst_n && ev_valid && ev_subsys == trace_pkg::SUBSYS_IOP) begin
|
||||
if (ev_event == trace_pkg::EV_READ) iop_reads <= iop_reads + 1;
|
||||
if (ev_event == trace_pkg::EV_WRITE) iop_writes <= iop_writes + 1;
|
||||
if (ev_arg3[7:0] == 8'd2) iop_region_hits <= iop_region_hits + 1;
|
||||
end
|
||||
end
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
task automatic do_write(input logic [ADDR_W-1:0] addr,
|
||||
input logic [31:0] data,
|
||||
input logic [3:0] be);
|
||||
@(negedge clk);
|
||||
wr_en = 1'b1;
|
||||
wr_addr = addr;
|
||||
wr_data = data;
|
||||
wr_be = be;
|
||||
@(negedge clk);
|
||||
wr_en = 1'b0;
|
||||
wr_addr = '0;
|
||||
wr_data = 32'd0;
|
||||
wr_be = 4'd0;
|
||||
endtask
|
||||
|
||||
task automatic do_read_expect(input logic [ADDR_W-1:0] addr,
|
||||
input logic [31:0] expected,
|
||||
input string label);
|
||||
@(negedge clk);
|
||||
rd_en = 1'b1;
|
||||
rd_addr = addr;
|
||||
@(negedge clk);
|
||||
rd_en = 1'b0;
|
||||
rd_addr = '0;
|
||||
if (rd_data !== expected || rd_valid !== 1'b1) begin
|
||||
$error("[tb_iop_ram_stub] read %s: got 0x%08h valid=%0b expected 0x%08h",
|
||||
label, rd_data, rd_valid, expected);
|
||||
errors = errors + 1;
|
||||
end
|
||||
endtask
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Stimulus
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
logic [31:0] before_read;
|
||||
|
||||
initial begin
|
||||
rst_n = 1'b0;
|
||||
rd_en = 1'b0;
|
||||
rd_addr = '0;
|
||||
wr_en = 1'b0;
|
||||
wr_addr = '0;
|
||||
wr_data = 32'd0;
|
||||
wr_be = 4'd0;
|
||||
master_id = 8'd0;
|
||||
|
||||
repeat (4) @(posedge clk);
|
||||
rst_n = 1'b1;
|
||||
repeat (2) @(posedge clk);
|
||||
|
||||
// 1. Zero-init check
|
||||
do_read_expect({ADDR_W{1'b0}}, 32'h0000_0000, "addr 0x0 after reset");
|
||||
|
||||
// 2. Word-wide write + read-back
|
||||
master_id = 8'd0; // TB direct
|
||||
do_write({ADDR_W{1'b0}}, 32'hCAFE_BABE, 4'b1111);
|
||||
do_read_expect({ADDR_W{1'b0}}, 32'hCAFE_BABE, "word write round-trip");
|
||||
|
||||
// 3. Partial byte-enable write: preserve upper half, overwrite lower
|
||||
// (start from known word value, write only be=0x3 with new data)
|
||||
do_write({ADDR_W{1'b0}}, 32'h1234_0000, 4'b0011);
|
||||
do_read_expect({ADDR_W{1'b0}}, 32'hCAFE_0000, "partial byte-enable low");
|
||||
|
||||
// 4. Non-zero master_id — IOP CPU hypothetical
|
||||
master_id = 8'd2;
|
||||
do_write(ADDR_W'(32'h0010), 32'hDEAD_BEEF, 4'b1111);
|
||||
do_read_expect(ADDR_W'(32'h0010), 32'hDEAD_BEEF, "IOP-tagged write");
|
||||
|
||||
// 5. Address-distinct read — confirm storage independence
|
||||
do_read_expect(ADDR_W'(32'h0020), 32'h0000_0000, "untouched slot");
|
||||
|
||||
repeat (4) @(posedge clk);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
$display("[tb_iop_ram_stub] iop_reads=%0d iop_writes=%0d iop_region_hits=%0d errors=%0d",
|
||||
iop_reads, iop_writes, iop_region_hits, errors);
|
||||
|
||||
if (iop_reads < 4) $error("expected >= 4 IOP read events, got %0d", iop_reads);
|
||||
if (iop_writes < 3) $error("expected >= 3 IOP write events, got %0d", iop_writes);
|
||||
if (iop_region_hits != iop_reads + iop_writes)
|
||||
$error("region tag inconsistent: reads+writes=%0d region_hits=%0d",
|
||||
iop_reads + iop_writes, iop_region_hits);
|
||||
|
||||
if (errors == 0 && iop_reads >= 4 && iop_writes >= 3 &&
|
||||
iop_region_hits == iop_reads + iop_writes)
|
||||
$display("[tb_iop_ram_stub] PASS");
|
||||
else
|
||||
$display("[tb_iop_ram_stub] FAIL");
|
||||
|
||||
$finish;
|
||||
end
|
||||
|
||||
initial begin
|
||||
#200000;
|
||||
$error("[tb_iop_ram_stub] timeout");
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule : tb_iop_ram_stub
|
||||
@@ -0,0 +1,237 @@
|
||||
// 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
|
||||
Reference in New Issue
Block a user