Files
retroDE_ps2/sim/tb/integration/tb_bridge_iop_pad_input.sv
T
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

489 lines
20 KiB
Systemverilog

// retroDE_ps2 — tb_bridge_iop_pad_input (Ch235)
// ============================================================================
// First end-to-end integration TB for the HPS → bridge → IOP-fabric
// input path. Wires `ps2_hps_bridge` (Ch222 INPUT_P1/P2 store) into
// `iop_memory_map_stub` (Ch234 sio2_input_stub region) across two
// distinct clocks so the bridge-clk → IOP-clk CDC is exercised end-to-end:
//
// HPS AXI write @ 0x040 (INPUT_P1) →
// ps2_hps_bridge.input_p1_q latch (bclk @ 100 MHz) →
// ps2_hps_bridge.input_p1_o output →
// iop_memory_map_stub.input_p1 →
// u_sio2_input.input_p1 →
// 2-FF sync into iclk @ 33 MHz →
// PAD_P1_STATE readable at IOP phys 0x1F80_8500
//
// Verifies:
// §1. Reset: IOP-side PAD_P1/P2 = 0xFFFF, PAD_STATUS = 1.
// §2. AXI write INPUT_P1 single bit → after CDC latency, IOP read
// shows the corresponding Sony bit cleared.
// §3. AXI write INPUT_P2 with a different pattern → P1 unchanged,
// P2 reflects new state (independence).
// §4. Multi-bit AXI write → IOP sees the matching combo word.
// §5. AXI clear → IOP reads back to 0xFFFF.
// §6. Bridge reset cycle → IOP sees 0xFFFF (Ch222 latches clear),
// even if iclk reset stays asserted.
// ============================================================================
`timescale 1ns/1ps
module tb_bridge_iop_pad_input;
// ------------------------------------------------------------------
// Two clocks — bridge on bclk @ 100 MHz, IOP on iclk @ 33 MHz.
// Different periods deliberately exercise the bclk → iclk CDC.
// ------------------------------------------------------------------
logic bclk;
logic iclk;
initial bclk = 1'b0;
initial iclk = 1'b0;
always #5 bclk = ~bclk; // 100 MHz
always #15 iclk = ~iclk; // 33 MHz
logic breset_n;
logic ireset_n;
// ------------------------------------------------------------------
// Bridge status inputs (tied LOW — we don't exercise CORE_STATUS).
// ------------------------------------------------------------------
logic core_halt;
logic dma_done_seen;
logic frame_seen;
logic hdmi_init_done;
logic hdmi_i2c_error;
logic raster_overflow;
logic frame_toggle;
logic dma_done_toggle;
// ------------------------------------------------------------------
// Bridge AXI slave (TB drives master).
// ------------------------------------------------------------------
logic [3:0] s_axi_awid;
logic [37:0] s_axi_awaddr;
logic [7:0] s_axi_awlen;
logic [2:0] s_axi_awsize;
logic [1:0] s_axi_awburst;
logic s_axi_awlock;
logic [3:0] s_axi_awcache;
logic [2:0] s_axi_awprot;
logic s_axi_awvalid;
logic s_axi_awready;
logic [127:0] s_axi_wdata;
logic [15:0] s_axi_wstrb;
logic s_axi_wlast;
logic s_axi_wvalid;
logic s_axi_wready;
logic [3:0] s_axi_bid;
logic [1:0] s_axi_bresp;
logic s_axi_bvalid;
logic s_axi_bready;
logic [3:0] s_axi_arid;
logic [37:0] s_axi_araddr;
logic [7:0] s_axi_arlen;
logic [2:0] s_axi_arsize;
logic [1:0] s_axi_arburst;
logic s_axi_arlock;
logic [3:0] s_axi_arcache;
logic [2:0] s_axi_arprot;
logic s_axi_arvalid;
logic s_axi_arready;
logic [3:0] s_axi_rid;
logic [127:0] s_axi_rdata;
logic [1:0] s_axi_rresp;
logic s_axi_rlast;
logic s_axi_rvalid;
logic s_axi_rready;
// ------------------------------------------------------------------
// Bridge outputs we ignore (Ch176/Ch229/Ch230).
// ------------------------------------------------------------------
logic core_reset_req;
logic tile_wr_toggle;
logic [9:0] tile_wr_index;
logic [31:0] tile_wr_data;
// Ch245 — platform OSD register-surface ports (unused by this
// integration TB; declared so the `.*` wildcard binds them).
wire [31:0] osd_ctrl_o;
wire [31:0] osd_cfg0_o;
wire [31:0] osd_cfg1_o;
logic osd_active_i = 1'b0;
logic [4:0] osd_cursor_row_i = 5'd0;
logic osd_set_trigger_i = 1'b0;
logic osd_back_trigger_i = 1'b0;
logic osd_scroll_down_trigger_i = 1'b0;
logic osd_scroll_up_trigger_i = 1'b0;
logic osd_open_trigger_i = 1'b0;
logic [4:0] osd_trigger_row_i = 5'd0;
// Ch248 DS2 wired-controller inputs (unused by this TB; tied
// unplugged so the bridge's DS2_STATUS reads as such).
logic [31:0] ds2_buttons_i = 32'd0;
logic ds2_connected_i = 1'b0;
logic ds2_error_i = 1'b0;
// ------------------------------------------------------------------
// The new Ch235 broadcast — bridge → IOP map.
// ------------------------------------------------------------------
wire [31:0] bridge_input_p1;
wire [31:0] bridge_input_p2;
// ------------------------------------------------------------------
// ps2_hps_bridge instance.
// ------------------------------------------------------------------
// Ch318 — LPDDR test ports (this TB doesn't exercise them; nets exist so the
// bridge's .* connects, status inputs tied safe).
logic lpddr_arm_o, lpddr_canary_o;
logic lpddr_ctrl_commit_o;
logic [31:0] lpddr_fb_base_o;
logic [31:0] lpddr_bytes_i = 32'd0, lpddr_bursts_i = 32'd0, lpddr_bresp_err_i = 32'd0, lpddr_fifo_ovf_i = 32'd0;
logic lpddr_idle_i = 1'b1;
// Ch319 — read-probe nets (tie inputs safe; .* binds them).
logic [31:0] lpddr_rd_addr_o; logic lpddr_rd_pulse_o;
logic [31:0] lpddr_rd_data_i = 32'd0; logic lpddr_rd_done_i = 1'b0;
logic lpddr_video_src_o;
logic lpddr_scanout_lb_o;
logic lpddr_scan_valid_i = 1'b0, lpddr_scan_err_i = 1'b0;
// Ch322 — write-probe + texture-cache fill nets (matched by the .* below).
logic [31:0] lpddr_wr_addr_o, lpddr_wr_data_o; logic lpddr_wr_pulse_o;
logic lpddr_wr_busy_i = 1'b0, lpddr_wr_done_i = 1'b0; logic [31:0] lpddr_wr_bresp_err_i = 32'd0;
logic tex_fill_start_o, tex_fill_done_i = 1'b0;
logic [31:0] tex_fill_beats_i = 32'd0, tex_fill_bytes_i = 32'd0, tex_rd_errs_i = 32'd0, tex_fill_crc_i = 32'd0;
logic [31:0] tex_cache_hits_i = 32'd0, tex_bram_hits_i = 32'd0;
// Ch323 — tile COLOR+Z spill/reload counters (unused here; 0 so the .* connect resolves).
logic [31:0] spill_color_beats_i = 32'd0, spill_z_beats_i = 32'd0;
logic [31:0] reload_color_beats_i = 32'd0, reload_z_beats_i = 32'd0;
logic [31:0] reload_rd_errs_i = 32'd0, spill_color_errs_i = 32'd0, spill_z_errs_i = 32'd0;
logic spill_color_ovf_i = 1'b0, spill_z_ovf_i = 1'b0;
logic [1:0] diag_ctrl_o; // Ch323 diag DIAG_CTRL output
logic [31:0] color_spill_awaddr_i=0, color_spill_wdata0_i=0;
logic [31:0] dbg_c_first_awaddr_i=0, dbg_c_first_wdata0_i=0, dbg_c_first_wstrb_i=0, dbg_c_last_wstrb_i=0, dbg_c_beat_count_i=0;
logic [31:0] dbg_c_emit_count_i=0, dbg_c_push_count_i=0, dbg_c_pop_count_i=0; // Ch323 pipeline-split (.* tie-off)
logic [31:0] dbg_z_beat_count_i=0, dbg_z_emit_count_i=0, dbg_z_push_count_i=0, dbg_z_pop_count_i=0; // Ch324 (.* tie-off)
logic [31:0] dbg_m_first_awaddr_i=0, dbg_m_first_wdata0_i=0, dbg_m_first_wstrb_i=0;
logic [31:0] ev_tp_flush_i=0, ev_tp_zflush_i=0, ev_tp_reload_i=0, ev_tp_render_i=0;
logic [31:0] ev_flush_emit_i=0, ev_zflush_emit_i=0, ev_reload_start_i=0, ev_reload_ready_i=0;
// Ch330 Brick 4 — feeder control ports (.* tie-off; this TB doesn't exercise the feeder).
logic feeder_stg_we_tgl_o, feeder_go_tgl_o;
logic [11:0] feeder_stg_waddr_o;
logic [63:0] feeder_stg_wdata_o;
logic feeder_ready_i = 1'b0;
logic [15:0] feeder_records_i = 16'd0;
logic [31:0] feeder_waits_i = 32'd0;
ps2_hps_bridge u_bridge (
.clk (bclk),
.reset_n (breset_n),
.h2f_reset (1'b0),
.core_halt (core_halt),
.dma_done_seen (dma_done_seen),
.frame_seen (frame_seen),
.hdmi_init_done (hdmi_init_done),
.hdmi_i2c_error (hdmi_i2c_error),
.raster_overflow(raster_overflow),
.frame_toggle (frame_toggle),
.dma_done_toggle(dma_done_toggle),
.core_reset_req (core_reset_req),
.tile_wr_toggle (tile_wr_toggle),
.tile_wr_index (tile_wr_index),
.tile_wr_data (tile_wr_data),
.input_p1_o (bridge_input_p1),
.input_p2_o (bridge_input_p2),
.input_p1_raw_o (),
.* // AXI signals
);
// ------------------------------------------------------------------
// IOP map signals (TB drives the CPU-side read; all other ports
// tied off since we only care about the pad path).
// ------------------------------------------------------------------
logic iop_rd_en;
logic [31:0] iop_rd_addr;
wire [31:0] iop_rd_data;
wire iop_rd_valid;
logic [7:0] iop_master_id;
// Trace outputs (ignored).
wire tr_valid;
wire [3:0] tr_subsys;
wire [3:0] tr_event;
wire [63:0] tr_arg0, tr_arg1, tr_arg2, tr_arg3;
wire [31:0] tr_flags;
iop_memory_map_stub u_map (
.clk(iclk), .rst_n(ireset_n),
// CPU side — TB drives reads only.
.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(1'b0), .iop_wr_addr(32'd0),
.iop_wr_data(32'd0), .iop_wr_be(4'd0),
.master_id(iop_master_id),
// Bridge-side write 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-side read port — unused.
.dma_rd_en(1'b0), .dma_rd_addr(32'd0),
.dma_master_id(8'd0),
.dma_rd_data(), .dma_rd_valid(),
// RAM port — tied off; CPU reads of RAM aren't exercised.
.ram_rd_en(), .ram_rd_addr(),
.ram_rd_data(32'd0), .ram_rd_valid(1'b0),
.ram_wr_en(), .ram_wr_addr(),
.ram_wr_data(), .ram_wr_be(),
.ram_master_id(),
// SIF port — tied off.
.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 — tied off.
.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 — tied off.
.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(),
// Ch235 — bridge → IOP wiring (the whole point of this TB).
.input_p1(bridge_input_p1),
.input_p2(bridge_input_p2),
// BIOS port — tied off.
.bios_rd_en(), .bios_rd_addr(),
.bios_rd_data(32'd0), .bios_rd_valid(1'b0),
// Trace outputs.
.ev_valid(tr_valid), .ev_subsys(tr_subsys), .ev_event(tr_event),
.ev_arg0(tr_arg0), .ev_arg1(tr_arg1),
.ev_arg2(tr_arg2), .ev_arg3(tr_arg3),
.ev_flags(tr_flags)
);
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
// ------------------------------------------------------------------
// AXI master tasks (mirror tb_ps2_hps_bridge.sv).
// ------------------------------------------------------------------
task automatic axi_write32(input logic [37:0] addr,
input logic [31:0] data);
@(posedge bclk);
s_axi_awid <= 4'd0;
s_axi_awaddr <= addr;
s_axi_awlen <= 8'd0;
s_axi_awsize <= 3'd2;
s_axi_awburst <= 2'b01;
s_axi_awlock <= 1'b0;
s_axi_awcache <= 4'd0;
s_axi_awprot <= 3'd0;
s_axi_awvalid <= 1'b1;
case (addr[3:2])
2'b00: s_axi_wdata <= {96'd0, data};
2'b01: s_axi_wdata <= {64'd0, data, 32'd0};
2'b10: s_axi_wdata <= {32'd0, data, 64'd0};
default: s_axi_wdata <= {data, 96'd0};
endcase
s_axi_wstrb <= 16'h000F << ({addr[3:2], 2'b00});
s_axi_wlast <= 1'b1;
s_axi_wvalid <= 1'b1;
s_axi_bready <= 1'b1;
wait (s_axi_awready);
@(posedge bclk);
s_axi_awvalid <= 1'b0;
wait (s_axi_wready);
@(posedge bclk);
s_axi_wvalid <= 1'b0;
wait (s_axi_bvalid);
@(posedge bclk);
s_axi_bready <= 1'b0;
endtask
// ------------------------------------------------------------------
// IOP read helper. Follows the existing tb_iop_memory_map_stub
// pattern: drive on @(negedge iclk) with BLOCKING assigns so the
// address is stable through the next posedge that registers
// rd_pending. Sample iop_rd_data on the following negedge — by
// then the map's read response (and the stub's pad_rd_data) have
// both been registered, and iop_rd_valid is HIGH for one cycle.
// ------------------------------------------------------------------
task automatic iop_read32(input logic [31:0] addr,
output logic [31:0] data);
@(negedge iclk);
iop_rd_en = 1'b1;
iop_rd_addr = addr;
@(negedge iclk);
iop_rd_en = 1'b0;
iop_rd_addr = 32'd0;
data = iop_rd_data;
if (iop_rd_valid !== 1'b1) begin
$error("[iop_read32] iop_rd_valid not high at sample (addr=0x%08x)", addr);
errors = errors + 1;
end
endtask
// ------------------------------------------------------------------
// Sony pad-word expected-value helper (mirrors sio2_input_stub).
// ------------------------------------------------------------------
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
// ------------------------------------------------------------------
// Wait long enough for an AXI-written value to propagate through
// the bridge latch + the 2-FF sync inside sio2_input_stub.
// Conservative — 20 iclk cycles is many sync windows.
// ------------------------------------------------------------------
task automatic settle_cdc();
repeat (20) @(posedge iclk);
endtask
logic [31:0] rd;
localparam logic [31:0] PAD_P1_ADDR = 32'h1F80_8500;
localparam logic [31:0] PAD_P2_ADDR = 32'h1F80_8504;
localparam logic [31:0] PAD_ST_ADDR = 32'h1F80_8508;
initial begin
errors = 0;
breset_n = 1'b0;
ireset_n = 1'b0;
// AXI defaults.
s_axi_arvalid = 1'b0;
s_axi_awvalid = 1'b0;
s_axi_wvalid = 1'b0;
s_axi_rready = 1'b0;
s_axi_bready = 1'b0;
// Bridge status (all LOW — we don't exercise CORE_STATUS).
core_halt = 1'b0;
dma_done_seen = 1'b0;
frame_seen = 1'b0;
hdmi_init_done = 1'b0;
hdmi_i2c_error = 1'b0;
raster_overflow = 1'b0;
frame_toggle = 1'b0;
dma_done_toggle = 1'b0;
// IOP-side defaults.
iop_rd_en = 1'b0;
iop_rd_addr = 32'd0;
iop_master_id = 8'd0;
repeat (4) @(posedge bclk);
@(posedge iclk);
breset_n = 1'b1;
ireset_n = 1'b1;
// Settle both domains.
repeat (8) @(posedge bclk);
repeat (8) @(posedge iclk);
// ----------------------------------------------------------
// §1. Reset state — bridge latches at 0 → Sony word 0xFFFF.
// PAD_STATUS = 1.
// ----------------------------------------------------------
iop_read32(PAD_P1_ADDR, rd); check_eq("rst_P1", rd, 32'h0000_FFFF);
iop_read32(PAD_P2_ADDR, rd); check_eq("rst_P2", rd, 32'h0000_FFFF);
iop_read32(PAD_ST_ADDR, rd); check_eq("rst_ST", rd, 32'h0000_0001);
// ----------------------------------------------------------
// §2. AXI write JOY_RIGHT (bit 0) into INPUT_P1 @ 0x040.
// After CDC settle, IOP-side P1 has byte3.5 (RIGHT)
// cleared.
// ----------------------------------------------------------
axi_write32(38'h040, 32'h0000_0001);
settle_cdc();
iop_read32(PAD_P1_ADDR, rd);
check_eq("P1_RIGHT_only",
rd, {16'd0, expected_sony(32'h0000_0001)});
// P2 unchanged.
iop_read32(PAD_P2_ADDR, rd);
check_eq("P2_unaffected_by_P1_write", rd, 32'h0000_FFFF);
// ----------------------------------------------------------
// §3. AXI write a different pattern into INPUT_P2 @ 0x044
// (JOY_DOWN | JOY_A = bits 2 | 9). P1 retains its
// previous state.
// ----------------------------------------------------------
axi_write32(38'h044, 32'h0000_0204);
settle_cdc();
iop_read32(PAD_P2_ADDR, rd);
check_eq("P2_DOWN_A",
rd, {16'd0, expected_sony(32'h0000_0204)});
iop_read32(PAD_P1_ADDR, rd);
check_eq("P1_persist_after_P2_write",
rd, {16'd0, expected_sony(32'h0000_0001)});
// ----------------------------------------------------------
// §4. Multi-bit combo. All D-pad + START + SELECT pressed
// on P1 (bits 0..5).
// ----------------------------------------------------------
axi_write32(38'h040, 32'h0000_003F);
settle_cdc();
iop_read32(PAD_P1_ADDR, rd);
check_eq("P1_dpad_start_sel_combo",
rd, {16'd0, expected_sony(32'h0000_003F)});
// ----------------------------------------------------------
// §5. AXI clear INPUT_P1 → IOP reads back to 0xFFFF.
// INPUT_P2 unchanged.
// ----------------------------------------------------------
axi_write32(38'h040, 32'h0000_0000);
settle_cdc();
iop_read32(PAD_P1_ADDR, rd);
check_eq("P1_cleared", rd, 32'h0000_FFFF);
iop_read32(PAD_P2_ADDR, rd);
check_eq("P2_persist_after_P1_clear",
rd, {16'd0, expected_sony(32'h0000_0204)});
// ----------------------------------------------------------
// §6. Bridge reset cycle — Ch222 latches clear; IOP sees
// 0xFFFF for both pads regardless of any prior state.
// ----------------------------------------------------------
@(posedge bclk); breset_n = 1'b0;
// Hold reset long enough to cycle through the sync chain.
repeat (8) @(posedge bclk);
repeat (8) @(posedge iclk);
@(posedge bclk); breset_n = 1'b1;
// Need more iclk settle: bridge clears at bclk, sio2_input
// sync takes 2 iclk to propagate the new value.
repeat (8) @(posedge bclk);
settle_cdc();
iop_read32(PAD_P1_ADDR, rd); check_eq("rst_clr_P1", rd, 32'h0000_FFFF);
iop_read32(PAD_P2_ADDR, rd); check_eq("rst_clr_P2", rd, 32'h0000_FFFF);
iop_read32(PAD_ST_ADDR, rd); check_eq("rst_clr_ST", rd, 32'h0000_0001);
// ----------------------------------------------------------
// Done.
// ----------------------------------------------------------
$display("[tb_bridge_iop_pad_input] errors=%0d", errors);
if (errors == 0) $display("[tb_bridge_iop_pad_input] PASS");
else $display("[tb_bridge_iop_pad_input] FAIL");
$finish;
end
initial begin
#5_000_000;
$error("[tb_bridge_iop_pad_input] TIMEOUT");
$finish;
end
endmodule : tb_bridge_iop_pad_input