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>
1202 lines
63 KiB
Systemverilog
1202 lines
63 KiB
Systemverilog
// retroDE_ps2 — tb_ps2_hps_bridge (Ch173/Ch174/Ch176)
|
||
// ============================================================================
|
||
// Focused unit-test TB for ps2_hps_bridge. Drives the AXI4 slave port
|
||
// directly from the testbench, runs read transactions at each register
|
||
// address in the Ch173/Ch174 map, and verifies the response data.
|
||
//
|
||
// The bridge is self-contained — the AXI master in this TB is a tiny
|
||
// SV task `axi_read32(addr)` that follows the same single-beat, 128-bit
|
||
// data, byte-lane-via-{addr[3:2]} contract used by qsys_top.hps2fpga.
|
||
// No qsys or full-system involvement; the test is "for this AXI input,
|
||
// does the bridge produce the right read response."
|
||
//
|
||
// What we prove:
|
||
// 1. Identity reads:
|
||
// - CORE_ID @ 0x000 = 0x50533200
|
||
// - ABI_VERSION @ 0x004 = 0x00000100
|
||
// - CORE_CAPS @ 0x00C = 0x00102800 (Ch246: osd_cols=40, osd_rows=16)
|
||
// 2. CORE_STATUS bits track the synchronized status inputs.
|
||
// 3. CORE_CTRL/CORE_PULSE @ 0x010/0x014 read 0 (shared-ABI stubs).
|
||
// 4. Ch174: FRAME_COUNT increments on each *edge* (rising or falling)
|
||
// of `frame_toggle`. Sticky `frame_seen` rising does NOT advance
|
||
// FRAME_COUNT — it only drives CORE_STATUS bit 3.
|
||
// 5. Ch174: DMA_DONE_COUNT increments on each edge of `dma_done_toggle`,
|
||
// same pattern.
|
||
// 6. RASTER_OVERFLOW_COUNT counts every cycle the input stays high.
|
||
// 7. Reads outside the mapped window return 0.
|
||
// 8. Writes to read-only addresses are ACK'd with OKAY and ignored
|
||
// (Ch176: only CORE_CTRL @ 0x010 and CORE_PULSE @ 0x014 actually
|
||
// take effect; all other addresses are still read-only).
|
||
// 9. Ch176: CORE_CTRL @ 0x010 latches the three ABI-shaped bits
|
||
// (RESET / ROM_LOADED / PAUSE), readback returns the latched
|
||
// value, and bit 0 drives the `core_reset_req` output port.
|
||
// 10. Ch176: CORE_PULSE @ 0x014 self-clears (always reads 0). A
|
||
// write with HDMI_CLR (bit 3) high zeros the diagnostic
|
||
// counters (FRAME_COUNT / DMA_DONE_COUNT / RASTER_OVERFLOW_COUNT)
|
||
// synchronously in the bridge clock domain. Writes that DON'T
|
||
// set HDMI_CLR leave the counters alone.
|
||
// 11. Ch222: INPUT_P1 / INPUT_P2 / INPUT_P1_RAW @ 0x040/0x044/0x048
|
||
// are HPS-visible write/read latches. Reset clears all three.
|
||
// Each register is independently writable (writes to one don't
|
||
// touch the others). Existing CTRL/STATUS/counter regs and
|
||
// `core_reset_req` are unaffected by these writes — proves the
|
||
// Ch222 decode is additive, not destructive.
|
||
// 12. Ch223: OSD_CTRL / OSD_CFG0 / OSD_CFG1 @ 0x100/0x110/0x114 are
|
||
// HPS-visible write/read latches (the splash backend's writes
|
||
// now hit real registers instead of being silently dropped).
|
||
// Reset clears all three. Reads at the unmapped slots inside
|
||
// the OSD window (0x104 / 0x108 / 0x10C / 0x118 / 0x11C)
|
||
// return 0; writes there don't bleed into the mapped latches.
|
||
// Other regs (Ch222 inputs + CTRL/STATUS/PULSE/counters) are
|
||
// unchanged across the OSD writes.
|
||
// 13. Ch224: OSD_STATUS @ 0x104 always reads 0 (no FSM source);
|
||
// OSD_TRIGGER @ 0x108 is a W1C-shape sink — reads 0, writes
|
||
// accepted-and-ignored. W1C patterns (0xFFFFFFFF, single-bit,
|
||
// `OSD_TRIG_ACTION | row<<8`) all leave the register at 0 and
|
||
// don't disturb OSD_CTRL/CFG0/CFG1. 0x10C also stays read-zero
|
||
// (OSD_INPUT reserved). 0x120 is just past the OSD window and
|
||
// reads 0 (the audit-requested out-of-window sentinel).
|
||
// 14. Ch225: VIDEO_STATUS @ 0x060 and HDMI_DIAG @ 0x064 are
|
||
// read-only diagnostic views into the synchronized status
|
||
// signals. Reset values trace the live inputs; flipping
|
||
// frame_toggle raises VIDEO_STATUS[1] (scanout_alive) via the
|
||
// frame_count!=0 derivation; pulsing raster_overflow raises +
|
||
// drops VIDEO_STATUS[2] live. Writes are accepted-and-ignored.
|
||
// Ch222–Ch224 latches are unchanged across the Ch225 writes.
|
||
// 15. Ch226: DS2 stub @ 0x0F0 / 0x0F4. DS2_STATUS = 0x00000004
|
||
// (sibling-ABI [0]=connected=0, [1]=error=0, plus PS2-local
|
||
// [2]=input_latches_valid=1). DS2_BUTTONS mirrors INPUT_P1.
|
||
// Both are read-only — writes ack and don't mutate. Existing
|
||
// Ch222 input latches and Ch223 OSD latches are unaffected
|
||
// by Ch226 reads or write-attempts. §7 also picks up the
|
||
// 0x0E0 / 0x0F8 audit sentinels (just-before / just-after
|
||
// DS2) confirming the widened 256-byte window's decode is
|
||
// tight.
|
||
// 16. Ch227: tile RAM storage @ 0x1000..0x1FFF, 1024 × 32-bit.
|
||
// Writes land in the in-bridge memory; reads return the last
|
||
// value written at the same byte offset. Boundary sentinels
|
||
// at 0x0FFC (just below window) and 0x2000 (just above)
|
||
// read 0. Other regs (CTRL/STATUS/inputs/OSD/DS2) are
|
||
// unchanged across the tile writes. Reset semantics: tile
|
||
// RAM is *retained* across warm reset (sim's `initial` block
|
||
// pre-zeros it; the TB writes, resets, and verifies the
|
||
// written values survive).
|
||
// ============================================================================
|
||
|
||
`timescale 1ns/1ps
|
||
|
||
module tb_ps2_hps_bridge;
|
||
|
||
logic clk;
|
||
logic reset_n;
|
||
initial clk = 1'b0;
|
||
always #5 clk = ~clk; // 100 MHz TB clock
|
||
|
||
// ---- Status inputs ----
|
||
logic core_halt;
|
||
logic dma_done_seen;
|
||
logic frame_seen;
|
||
logic hdmi_init_done;
|
||
logic hdmi_i2c_error;
|
||
logic raster_overflow;
|
||
// ---- Ch174 event-toggle counter inputs ----
|
||
logic frame_toggle;
|
||
logic dma_done_toggle;
|
||
// ---- Ch176 outputs ----
|
||
logic core_reset_req;
|
||
// ---- Ch229 tile-write broadcast outputs (unused in this TB; the
|
||
// Ch229 receiver-side coverage lives in tb_tile_ram_cdc) ----
|
||
logic tile_wr_toggle;
|
||
logic [9:0] tile_wr_index;
|
||
logic [31:0] tile_wr_data;
|
||
// ---- Ch235 INPUT_P1/P2 broadcast outputs (unused here; the
|
||
// Ch235 integration TB lives in sim/tb/integration) ----
|
||
logic [31:0] input_p1_o;
|
||
logic [31:0] input_p2_o;
|
||
logic [31:0] input_p1_raw_o;
|
||
|
||
// ---- Ch245 platform OSD register surface (consumed by the
|
||
// top-level platform OSD migration; this focused bridge TB
|
||
// only verifies register read/write semantics).
|
||
logic [31:0] osd_ctrl_o;
|
||
logic [31:0] osd_cfg0_o;
|
||
logic [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 (TB-driven). The bridge
|
||
// surfaces these on DS2_STATUS/DS2_BUTTONS reads instead of
|
||
// the Ch226 hardcoded stub.
|
||
logic [31:0] ds2_buttons_i = 32'd0;
|
||
logic ds2_connected_i = 1'b0;
|
||
logic ds2_error_i = 1'b0;
|
||
|
||
// ---- AXI master signals driven by the TB ----
|
||
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;
|
||
|
||
// Ch318 — LPDDR test control/status nets (matched by the .* below).
|
||
logic lpddr_arm_o, lpddr_canary_o;
|
||
logic lpddr_ctrl_commit_o; // Ch352 — control-commit toggle for the EMIF-domain snapshot
|
||
logic [31:0] lpddr_fb_base_o;
|
||
logic [31:0] lpddr_bytes_i, lpddr_bursts_i, lpddr_bresp_err_i, lpddr_fifo_ovf_i;
|
||
logic lpddr_idle_i;
|
||
// Ch319 — read-probe nets.
|
||
logic [31:0] lpddr_rd_addr_o; logic lpddr_rd_pulse_o;
|
||
logic [31:0] lpddr_rd_data_i; logic lpddr_rd_done_i;
|
||
// Ch320 — scanout source-select + status nets.
|
||
logic lpddr_video_src_o;
|
||
logic lpddr_scanout_lb_o; // Ch321 — line-buffer vs frame-cache select
|
||
logic lpddr_scan_valid_i, lpddr_scan_err_i;
|
||
// 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, lpddr_wr_done_i; logic [31:0] lpddr_wr_bresp_err_i;
|
||
logic tex_fill_start_o, tex_fill_done_i;
|
||
logic [31:0] tex_fill_beats_i, tex_fill_bytes_i, tex_rd_errs_i, tex_fill_crc_i;
|
||
logic [31:0] tex_cache_hits_i, tex_bram_hits_i;
|
||
// Ch323 — tile COLOR+Z spill/reload counters (read-only regs at 0x080..0x09C).
|
||
logic [31:0] spill_color_beats_i, spill_z_beats_i, reload_color_beats_i, reload_z_beats_i;
|
||
logic [31:0] reload_rd_errs_i, spill_color_errs_i, spill_z_errs_i;
|
||
logic spill_color_ovf_i, spill_z_ovf_i;
|
||
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;
|
||
// Ch323 diag — upstream tile-spill/reload event counters (0x0A0..0x0BC).
|
||
logic [31:0] ev_tp_flush_i, ev_tp_zflush_i, ev_tp_reload_i, ev_tp_render_i;
|
||
logic [31:0] ev_flush_emit_i, ev_zflush_emit_i, ev_reload_start_i, ev_reload_ready_i;
|
||
// Ch330 Brick 4 — feeder control ports (.* match). Outputs observed by the feeder test;
|
||
// status inputs driven by the test.
|
||
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_dut (
|
||
.clk (clk),
|
||
.reset_n (reset_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),
|
||
.*
|
||
);
|
||
|
||
int errors;
|
||
|
||
// ----------------------------------------------------------------
|
||
// AXI master tasks. Single-beat, 32-bit-lane reads/writes.
|
||
// Address bits [3:2] pick which 32-bit slot inside the 128-bit
|
||
// response carries the register value.
|
||
// ----------------------------------------------------------------
|
||
task automatic axi_read32(input logic [37:0] addr, output logic [31:0] data);
|
||
// Issue AR
|
||
@(posedge clk);
|
||
s_axi_arid <= 4'd0;
|
||
s_axi_araddr <= addr;
|
||
s_axi_arlen <= 8'd0;
|
||
s_axi_arsize <= 3'd2;
|
||
s_axi_arburst <= 2'b01;
|
||
s_axi_arlock <= 1'b0;
|
||
s_axi_arcache <= 4'd0;
|
||
s_axi_arprot <= 3'd0;
|
||
s_axi_arvalid <= 1'b1;
|
||
s_axi_rready <= 1'b1;
|
||
wait (s_axi_arready);
|
||
@(posedge clk);
|
||
s_axi_arvalid <= 1'b0;
|
||
|
||
// Wait for R
|
||
wait (s_axi_rvalid);
|
||
// Lane select: addr[3:2] picks which 32-bit slot.
|
||
case (addr[3:2])
|
||
2'b00: data = s_axi_rdata[31:0];
|
||
2'b01: data = s_axi_rdata[63:32];
|
||
2'b10: data = s_axi_rdata[95:64];
|
||
default: data = s_axi_rdata[127:96];
|
||
endcase
|
||
@(posedge clk);
|
||
s_axi_rready <= 1'b0;
|
||
endtask
|
||
|
||
task automatic axi_write32(input logic [37:0] addr, input logic [31:0] data);
|
||
// Issue AW + W together, then wait B.
|
||
@(posedge clk);
|
||
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 clk);
|
||
s_axi_awvalid <= 1'b0;
|
||
wait (s_axi_wready);
|
||
@(posedge clk);
|
||
s_axi_wvalid <= 1'b0;
|
||
wait (s_axi_bvalid);
|
||
@(posedge clk);
|
||
s_axi_bready <= 1'b0;
|
||
endtask
|
||
|
||
// ----------------------------------------------------------------
|
||
// Raise a sticky status-signal level and HOLD it (real hardware
|
||
// sticky rises once and stays — the inner wrapper's
|
||
// frame_seen_q / dma_done_seen_q never fall back to 0).
|
||
// Drives the CORE_STATUS bit, NOT the Ch174 counter (those use
|
||
// separate `*_toggle` inputs).
|
||
// ----------------------------------------------------------------
|
||
task automatic set_dma_done_seen();
|
||
@(posedge clk); dma_done_seen <= 1'b1;
|
||
repeat (5) @(posedge clk);
|
||
endtask
|
||
task automatic set_frame_seen();
|
||
@(posedge clk); frame_seen <= 1'b1;
|
||
repeat (5) @(posedge clk);
|
||
endtask
|
||
|
||
// ----------------------------------------------------------------
|
||
// Ch174 — flip an event toggle once. The toggle stays at its new
|
||
// value (no return); the bridge XOR-detects each edge as one event.
|
||
// Holds the new level long enough for the 2-FF synchronizer to
|
||
// settle and the edge detector to fire on the next clk.
|
||
// ----------------------------------------------------------------
|
||
task automatic flip_frame_toggle();
|
||
@(posedge clk); frame_toggle <= ~frame_toggle;
|
||
repeat (8) @(posedge clk);
|
||
endtask
|
||
task automatic flip_dma_done_toggle();
|
||
@(posedge clk); dma_done_toggle <= ~dma_done_toggle;
|
||
repeat (8) @(posedge clk);
|
||
endtask
|
||
|
||
// ----------------------------------------------------------------
|
||
// Assertion helper.
|
||
// ----------------------------------------------------------------
|
||
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
|
||
|
||
logic [31:0] rd;
|
||
|
||
initial begin
|
||
errors = 0;
|
||
// Default AXI bus state.
|
||
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;
|
||
// Status defaults.
|
||
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;
|
||
// Ch318 LPDDR status inputs (fixed test values).
|
||
lpddr_bytes_i = 32'h0000_2000; // 8192
|
||
lpddr_bursts_i = 32'd256;
|
||
lpddr_bresp_err_i = 32'd0;
|
||
lpddr_fifo_ovf_i = 32'd0;
|
||
lpddr_idle_i = 1'b1;
|
||
lpddr_rd_data_i = 32'd0; // Ch319 read-probe return path (TB models the probe)
|
||
lpddr_rd_done_i = 1'b0;
|
||
lpddr_scan_valid_i = 1'b0; // Ch320 scanout status (TB models the scanout reader)
|
||
lpddr_scan_err_i = 1'b0;
|
||
lpddr_wr_busy_i = 1'b0; // Ch322 write-probe + texture-cache fill status
|
||
lpddr_wr_done_i = 1'b0;
|
||
lpddr_wr_bresp_err_i = 32'd0;
|
||
tex_fill_done_i = 1'b0;
|
||
tex_fill_beats_i = 32'd0;
|
||
tex_fill_bytes_i = 32'd0;
|
||
tex_rd_errs_i = 32'd0;
|
||
tex_cache_hits_i = 32'd0;
|
||
tex_bram_hits_i = 32'd0;
|
||
spill_color_beats_i = 32'd0; spill_z_beats_i = 32'd0;
|
||
reload_color_beats_i = 32'd0; reload_z_beats_i = 32'd0;
|
||
reload_rd_errs_i = 32'd0; spill_color_errs_i= 32'd0; spill_z_errs_i = 32'd0;
|
||
spill_color_ovf_i = 1'b0; spill_z_ovf_i = 1'b0;
|
||
ev_tp_flush_i = 0; ev_tp_zflush_i = 0; ev_tp_reload_i = 0; ev_tp_render_i = 0;
|
||
ev_flush_emit_i = 0; ev_zflush_emit_i = 0; ev_reload_start_i = 0; ev_reload_ready_i = 0;
|
||
reset_n = 1'b0;
|
||
repeat (10) @(posedge clk);
|
||
reset_n = 1'b1;
|
||
repeat (4) @(posedge clk);
|
||
|
||
// --------------------------------------------------------
|
||
// 1. Identity reads.
|
||
// --------------------------------------------------------
|
||
axi_read32(38'h000, rd); check_eq("CORE_ID", rd, 32'h50533200);
|
||
axi_read32(38'h004, rd); check_eq("ABI_VERSION", rd, 32'h00000100);
|
||
axi_read32(38'h00C, rd); check_eq("CORE_CAPS", rd, 32'h00102800);
|
||
|
||
// --------------------------------------------------------
|
||
// 2. CORE_STATUS at reset — only `loaded` bit set.
|
||
// --------------------------------------------------------
|
||
axi_read32(38'h008, rd); check_eq("CORE_STATUS@rst", rd, 32'h00000001);
|
||
|
||
// Raise core_halt, wait for sync to propagate (~3 clk),
|
||
// read again — bit 1 should be HIGH.
|
||
@(posedge clk); core_halt = 1'b1;
|
||
repeat (5) @(posedge clk);
|
||
axi_read32(38'h008, rd);
|
||
check_eq("CORE_STATUS@halt", rd, 32'h00000003); // loaded | core_halt
|
||
|
||
// Raise hdmi_init_done. Should set bit 4 too.
|
||
@(posedge clk); hdmi_init_done = 1'b1;
|
||
repeat (5) @(posedge clk);
|
||
axi_read32(38'h008, rd);
|
||
check_eq("CORE_STATUS@hdmi_init", rd, 32'h00000013); // loaded | halt | hdmi_init
|
||
|
||
// hdmi_i2c_error bit 5.
|
||
@(posedge clk); hdmi_i2c_error = 1'b1;
|
||
repeat (5) @(posedge clk);
|
||
axi_read32(38'h008, rd);
|
||
check_eq("CORE_STATUS@hdmi_err", rd, 32'h00000033); // + hdmi_i2c_error
|
||
|
||
// --------------------------------------------------------
|
||
// 3. CORE_CTRL / CORE_PULSE — Ch173 stubs, read 0 (shared
|
||
// retroDE ABI reserves 0x010/0x014; writable in Ch174+).
|
||
// --------------------------------------------------------
|
||
axi_read32(38'h010, rd); check_eq("CORE_CTRL@rst", rd, 32'd0);
|
||
axi_read32(38'h014, rd); check_eq("CORE_PULSE@rst", rd, 32'd0);
|
||
|
||
// --------------------------------------------------------
|
||
// 3b. Ch318 — LPDDR test registers (RW control + R status).
|
||
// --------------------------------------------------------
|
||
axi_read32(38'h018, rd); check_eq("LPDDR_CTRL@rst (arm=0,canary=1)", rd, 32'h00000002);
|
||
axi_read32(38'h01C, rd); check_eq("LPDDR_FB_BASE@rst", rd, 32'h80000000);
|
||
axi_write32(38'h018, 32'h00000001); // arm=1, canary=0
|
||
axi_read32(38'h018, rd); check_eq("LPDDR_CTRL after wr", rd, 32'h00000001);
|
||
check_eq("lpddr_arm_o", {31'd0, lpddr_arm_o}, 32'd1);
|
||
check_eq("lpddr_canary_o", {31'd0, lpddr_canary_o}, 32'd0);
|
||
axi_write32(38'h01C, 32'h8000_0000); // restore safe base
|
||
axi_read32(38'h01C, rd); check_eq("LPDDR_FB_BASE RW", rd, 32'h80000000);
|
||
check_eq("lpddr_fb_base_o", lpddr_fb_base_o, 32'h80000000);
|
||
axi_read32(38'h02C, rd); check_eq("LPDDR_STATUS (idle,noerr)", rd, 32'h00000001);
|
||
axi_read32(38'h030, rd); check_eq("LPDDR_BYTES", rd, 32'h0000_2000);
|
||
axi_read32(38'h034, rd); check_eq("LPDDR_BURSTS", rd, 32'd256);
|
||
|
||
// 3c. Ch319 — LPDDR4B read-probe (0x03C). WRITE sets the byte address +
|
||
// triggers a read; STATUS[3]=rd_pending stays high until the probe
|
||
// (modeled here in the emif domain) toggles done; then 0x03C reads
|
||
// back the returned word. Exercises the full return-path CDC.
|
||
axi_write32(38'h03C, 32'h0000_0040); // request read @ byte 0x40
|
||
check_eq("lpddr_rd_addr_o", lpddr_rd_addr_o, 32'h0000_0040);
|
||
axi_read32(38'h02C, rd); check_eq("rd_pending after trigger", {31'd0, rd[3]}, 32'd1);
|
||
lpddr_rd_data_i = 32'hCAFE_F00D; // probe returns a word ...
|
||
lpddr_rd_done_i = ~lpddr_rd_done_i; // ... then toggles done
|
||
repeat (6) @(posedge clk); // 2-FF sync + latch settle
|
||
axi_read32(38'h02C, rd); check_eq("rd_pending after done", {31'd0, rd[3]}, 32'd0);
|
||
axi_read32(38'h03C, rd); check_eq("LPDDR_RDATA", rd, 32'hCAFE_F00D);
|
||
|
||
// 3d. Ch320 — video-source select (0x018[2]) + scanout status (0x02C[4]/[5]).
|
||
axi_write32(38'h018, 32'h00000004); // video_src=1 (arm=0,canary=0)
|
||
check_eq("lpddr_video_src_o", {31'd0, lpddr_video_src_o}, 32'd1);
|
||
axi_read32(38'h018, rd); check_eq("LPDDR_CTRL[2] video_src", {31'd0, rd[2]}, 32'd1);
|
||
axi_write32(38'h018, 32'h0000000C); // Ch321: video_src=1 + scanout_lb=1
|
||
check_eq("lpddr_scanout_lb_o", {31'd0, lpddr_scanout_lb_o}, 32'd1);
|
||
axi_read32(38'h018, rd); check_eq("LPDDR_CTRL[3] scanout_lb", {31'd0, rd[3]}, 32'd1);
|
||
axi_write32(38'h018, 32'h00000004); // back to video_src=1, frame-cache
|
||
lpddr_scan_valid_i = 1'b1; lpddr_scan_err_i = 1'b0; // model: cache loaded, no errors
|
||
repeat (5) @(posedge clk);
|
||
axi_read32(38'h02C, rd);
|
||
check_eq("STATUS[4] scan_cache_valid", {31'd0, rd[4]}, 32'd1);
|
||
check_eq("STATUS[5] scan_rd_err", {31'd0, rd[5]}, 32'd0);
|
||
axi_write32(38'h018, 32'h00000002); // restore default (BRAM scanout)
|
||
|
||
// --------------------------------------------------------
|
||
// 4. Ch174 — FRAME_COUNT @ 0x020 advances on `frame_toggle`
|
||
// edges (rising OR falling), NOT on the sticky
|
||
// `frame_seen` rise. Sticky drives CORE_STATUS bit 3
|
||
// only; counter uses the event-toggle CDC primitive.
|
||
// --------------------------------------------------------
|
||
axi_read32(38'h020, rd); check_eq("FRAME_COUNT@rst", rd, 32'd0);
|
||
|
||
// Rising the sticky `frame_seen` MUST NOT increment FRAME_COUNT.
|
||
// This is the Ch174 CDC-safety regression: if a future
|
||
// refactor accidentally rewires the counter onto the sticky
|
||
// source, the count would jump to 1 here and this check fires.
|
||
set_frame_seen();
|
||
axi_read32(38'h020, rd); check_eq("FRAME_COUNT@stickyrise", rd, 32'd0);
|
||
|
||
// But the sticky DOES set CORE_STATUS bit 3 (frame_seen).
|
||
axi_read32(38'h008, rd);
|
||
check_eq("CORE_STATUS@frame_seen", rd, 32'h0000003B);
|
||
// loaded(0) | halt(1) | frame_seen(3) | hdmi_init(4) | hdmi_err(5)
|
||
|
||
// Now flip the toggle once — counter advances by 1.
|
||
flip_frame_toggle();
|
||
axi_read32(38'h020, rd); check_eq("FRAME_COUNT@toggle1", rd, 32'd1);
|
||
|
||
// Two more flips → +2 (both the rising and falling toggle
|
||
// edge each count as one event; 2 flips = 2 edges = 2 events).
|
||
flip_frame_toggle();
|
||
flip_frame_toggle();
|
||
axi_read32(38'h020, rd); check_eq("FRAME_COUNT@toggle3", rd, 32'd3);
|
||
|
||
// --------------------------------------------------------
|
||
// 5. Ch174 — DMA_DONE_COUNT @ 0x024 advances on
|
||
// `dma_done_toggle` edges, not on sticky `dma_done_seen`.
|
||
// --------------------------------------------------------
|
||
axi_read32(38'h024, rd); check_eq("DMA_DONE_COUNT@rst", rd, 32'd0);
|
||
|
||
set_dma_done_seen();
|
||
axi_read32(38'h024, rd); check_eq("DMA_DONE_COUNT@stickyrise", rd, 32'd0);
|
||
|
||
// Sticky set CORE_STATUS bit 2 (dma_done_seen).
|
||
axi_read32(38'h008, rd);
|
||
check_eq("CORE_STATUS@dma_done", rd, 32'h0000003F);
|
||
// loaded(0) | halt(1) | dma_done(2) | frame_seen(3) | hdmi_init(4) | hdmi_err(5)
|
||
|
||
// Toggle path increments the counter.
|
||
flip_dma_done_toggle();
|
||
flip_dma_done_toggle();
|
||
axi_read32(38'h024, rd); check_eq("DMA_DONE_COUNT@toggle2", rd, 32'd2);
|
||
|
||
// --------------------------------------------------------
|
||
// 6. RASTER_OVERFLOW_COUNT @ 0x028 counts every cycle high.
|
||
// --------------------------------------------------------
|
||
axi_read32(38'h028, rd); check_eq("RASTER_OVERFLOW_COUNT@rst", rd, 32'd0);
|
||
|
||
@(posedge clk); raster_overflow = 1'b1;
|
||
// 2-FF sync delays the count start by ~2 clks; hold high for
|
||
// a known window and check the count grows by approximately
|
||
// that window minus the sync delay.
|
||
repeat (20) @(posedge clk);
|
||
raster_overflow = 1'b0;
|
||
repeat (5) @(posedge clk);
|
||
axi_read32(38'h028, rd);
|
||
// Allow some slack — sync delay + read-pipeline timing.
|
||
if (rd < 32'd15 || rd > 32'd25) begin
|
||
$error("[RASTER_OVERFLOW_COUNT] got %0d, expected ~18..22", rd);
|
||
errors = errors + 1;
|
||
end
|
||
|
||
// --------------------------------------------------------
|
||
// 7. Read outside the mapped window → returns 0.
|
||
// Sentinels probe both window boundaries:
|
||
// - 0x200: past every decoded region; truly OOB.
|
||
// - 0x120: first byte past the OSD window (Ch223).
|
||
// - 0x0F8: between DS2_BUTTONS and OSD_CTRL — inside
|
||
// the Ch226-widened 256-byte window but
|
||
// unmapped (CoCo2-specific DS2_ANALOG slot
|
||
// that PS2 does not implement).
|
||
// - 0x0E0: just before DS2_STATUS — inside the widened
|
||
// window but unmapped, confirms the upper
|
||
// half of the window decodes tightly.
|
||
// - 0x074: an undecoded word in the lower 128 B; falls
|
||
// through case-default. (0x078/0x07C are now the
|
||
// Ch322 TEX_CACHE_HITS/TEX_BRAM_HITS counters.)
|
||
// (Ch222 mapped 0x040/0x044/0x048; Ch223 mapped 0x100/
|
||
// 0x110/0x114; Ch226 mapped 0x0F0/0x0F4, so prior probes
|
||
// avoid those addresses.)
|
||
// --------------------------------------------------------
|
||
// --------------------------------------------------------
|
||
// 8b. Ch323 — tile COLOR+Z spill/reload counters (R) at 0x080..0x09C.
|
||
// Drive distinct sentinels and read each back through the regmap.
|
||
// --------------------------------------------------------
|
||
spill_color_beats_i = 32'h0000_0101; spill_z_beats_i = 32'h0000_0202;
|
||
reload_color_beats_i = 32'h0000_0303; reload_z_beats_i = 32'h0000_0404;
|
||
reload_rd_errs_i = 32'h0000_0505; spill_color_errs_i = 32'h0000_0606;
|
||
spill_z_errs_i = 32'h0000_0707; spill_color_ovf_i = 1'b1; spill_z_ovf_i = 1'b1;
|
||
repeat (2) @(posedge clk);
|
||
axi_read32(38'h080, rd); check_eq("SPILL_COLOR_WR_BEATS@0x080", rd, 32'h0000_0101);
|
||
axi_read32(38'h084, rd); check_eq("SPILL_Z_WR_BEATS@0x084", rd, 32'h0000_0202);
|
||
axi_read32(38'h088, rd); check_eq("RELOAD_COLOR_BEATS@0x088", rd, 32'h0000_0303);
|
||
axi_read32(38'h08C, rd); check_eq("RELOAD_Z_BEATS@0x08C", rd, 32'h0000_0404);
|
||
axi_read32(38'h090, rd); check_eq("RELOAD_RD_ERRS@0x090", rd, 32'h0000_0505);
|
||
axi_read32(38'h094, rd); check_eq("SPILL_COLOR_WR_ERRS@0x094", rd, 32'h0000_0606);
|
||
axi_read32(38'h098, rd); check_eq("SPILL_Z_WR_ERRS@0x098", rd, 32'h0000_0707);
|
||
axi_read32(38'h09C, rd); check_eq("SPILL_OVF@0x09C", rd, 32'h0000_0003);
|
||
|
||
// 8c. Ch323 diag — upstream event counters at 0x0A0..0x0BC.
|
||
ev_tp_flush_i=32'h0A0; ev_tp_zflush_i=32'h0A4; ev_tp_reload_i=32'h0A8; ev_tp_render_i=32'h0AC;
|
||
ev_flush_emit_i=32'h0B0; ev_zflush_emit_i=32'h0B4; ev_reload_start_i=32'h0B8; ev_reload_ready_i=32'h0BC;
|
||
repeat (2) @(posedge clk);
|
||
axi_read32(38'h0A0, rd); check_eq("EV_TP_FLUSH@0x0A0", rd, 32'h0A0);
|
||
axi_read32(38'h0A4, rd); check_eq("EV_TP_ZFLUSH@0x0A4", rd, 32'h0A4);
|
||
axi_read32(38'h0A8, rd); check_eq("EV_TP_RELOAD@0x0A8", rd, 32'h0A8);
|
||
axi_read32(38'h0AC, rd); check_eq("EV_TP_RENDER@0x0AC", rd, 32'h0AC);
|
||
axi_read32(38'h0B0, rd); check_eq("EV_FLUSH_EMIT@0x0B0", rd, 32'h0B0);
|
||
axi_read32(38'h0B4, rd); check_eq("EV_ZFLUSH_EMIT@0x0B4", rd, 32'h0B4);
|
||
axi_read32(38'h0B8, rd); check_eq("EV_RELOAD_START@0x0B8",rd, 32'h0B8);
|
||
axi_read32(38'h0BC, rd); check_eq("EV_RELOAD_READY@0x0BC",rd, 32'h0BC);
|
||
|
||
axi_read32(38'h200, rd); check_eq("oob_read@0x200", rd, 32'd0);
|
||
axi_read32(38'h120, rd); check_eq("oob_read@0x120", rd, 32'd0);
|
||
axi_read32(38'h0F8, rd); check_eq("oob_read@0x0F8", rd, 32'd0);
|
||
axi_read32(38'h0E0, rd); check_eq("oob_read@0x0E0", rd, 32'd0);
|
||
axi_read32(38'h074, rd); check_eq("oob_read@0x074", rd, 32'd0);
|
||
|
||
// --------------------------------------------------------
|
||
// 9. Ch176 — CORE_CTRL @ 0x010 is writable + readable.
|
||
// Bit 0 (RESET) drives core_reset_req; bits 1/2 are
|
||
// pure latches; bits[31:3] are ignored on write.
|
||
// --------------------------------------------------------
|
||
// Round-trip latch check: write 0x06 (PAUSE | ROM_LOADED,
|
||
// RESET clear). Read back exactly 0x06; core_reset_req=0.
|
||
axi_write32(38'h010, 32'h00000006);
|
||
axi_read32(38'h010, rd); check_eq("CORE_CTRL@latch_06", rd, 32'h00000006);
|
||
repeat (3) @(posedge clk);
|
||
check_eq("core_reset_req@latch_06", core_reset_req, 1'b0);
|
||
|
||
// Assert RESET: write 0x05 (RESET | ROM_LOADED). Read back
|
||
// 0x05; core_reset_req=1 within ~1 clk of the write accept.
|
||
axi_write32(38'h010, 32'h00000005);
|
||
axi_read32(38'h010, rd); check_eq("CORE_CTRL@reset_held", rd, 32'h00000005);
|
||
check_eq("core_reset_req@assert", core_reset_req, 1'b1);
|
||
|
||
// Ignore-high-bits check: write 0xFFFFFFFF. Only bits[2:0]
|
||
// survive; readback = 0x07.
|
||
axi_write32(38'h010, 32'hFFFFFFFF);
|
||
axi_read32(38'h010, rd); check_eq("CORE_CTRL@hibits_dropped", rd, 32'h00000007);
|
||
|
||
// Release reset: write 0x00. core_reset_req goes low.
|
||
axi_write32(38'h010, 32'h00000000);
|
||
axi_read32(38'h010, rd); check_eq("CORE_CTRL@released", rd, 32'h00000000);
|
||
check_eq("core_reset_req@release", core_reset_req, 1'b0);
|
||
|
||
// --------------------------------------------------------
|
||
// 10. Ch176 — CORE_PULSE @ 0x014. Self-clearing; always
|
||
// reads 0. HDMI_CLR (bit 3) zeros the diagnostic
|
||
// counters. Other bits are ACK'd-and-ignored.
|
||
// --------------------------------------------------------
|
||
// Re-arm the counters so we have non-zero values to clear.
|
||
// (Section 6 left RASTER_OVERFLOW_COUNT in the 15..25
|
||
// range; sections 4-5 left FRAME_COUNT=3, DMA_DONE_COUNT=2.)
|
||
// First confirm the pre-clear state.
|
||
axi_read32(38'h020, rd); check_eq("FRAME_COUNT@pre_clr", rd, 32'd3);
|
||
axi_read32(38'h024, rd); check_eq("DMA_DONE_COUNT@pre_clr", rd, 32'd2);
|
||
// RASTER_OVERFLOW_COUNT already validated as nonzero in §6.
|
||
|
||
// Write a no-op pulse (bits[2:0] set but not HDMI_CLR).
|
||
// Counters should be untouched.
|
||
axi_write32(38'h014, 32'h00000007);
|
||
axi_read32(38'h014, rd); check_eq("CORE_PULSE@self_clear_noop", rd, 32'd0);
|
||
axi_read32(38'h020, rd); check_eq("FRAME_COUNT@noop_pulse", rd, 32'd3);
|
||
axi_read32(38'h024, rd); check_eq("DMA_DONE_COUNT@noop_pulse", rd, 32'd2);
|
||
|
||
// Now fire HDMI_CLR (bit 3 = 0x08). All three counters
|
||
// synchronously zero in the bridge clock domain.
|
||
axi_write32(38'h014, 32'h00000008);
|
||
axi_read32(38'h014, rd); check_eq("CORE_PULSE@self_clear_hdmi", rd, 32'd0);
|
||
axi_read32(38'h020, rd); check_eq("FRAME_COUNT@hdmi_clr", rd, 32'd0);
|
||
axi_read32(38'h024, rd); check_eq("DMA_DONE_COUNT@hdmi_clr", rd, 32'd0);
|
||
axi_read32(38'h028, rd); check_eq("RASTER_OVERFLOW_COUNT@hdmi_clr", rd, 32'd0);
|
||
|
||
// After clear, the toggle path still advances the counter
|
||
// — proves HDMI_CLR didn't break the increment logic.
|
||
flip_frame_toggle();
|
||
axi_read32(38'h020, rd); check_eq("FRAME_COUNT@post_clr_resume", rd, 32'd1);
|
||
|
||
// Final HDMI_CLR to leave counters at 0 for section 8's
|
||
// FRAME_COUNT-write-ignored check.
|
||
axi_write32(38'h014, 32'h00000008);
|
||
|
||
// --------------------------------------------------------
|
||
// 8. Writes to read-only addresses are ACK'd with OKAY and
|
||
// don't modify state. Ch176: this now means everything
|
||
// EXCEPT CORE_CTRL @ 0x010 and CORE_PULSE @ 0x014.
|
||
// --------------------------------------------------------
|
||
axi_write32(38'h000, 32'hDEAD_BEEF);
|
||
axi_read32(38'h000, rd); check_eq("CORE_ID@write-ignored", rd, 32'h50533200);
|
||
|
||
axi_write32(38'h020, 32'hDEAD_BEEF);
|
||
axi_read32(38'h020, rd); check_eq("FRAME_COUNT@write-ignored", rd, 32'd0);
|
||
// FRAME_COUNT was zeroed by the trailing HDMI_CLR in §10.
|
||
|
||
// --------------------------------------------------------
|
||
// 11. Ch222 — INPUT_P1 / INPUT_P2 / INPUT_P1_RAW write/read
|
||
// latches at 0x040 / 0x044 / 0x048. HPS-visible state
|
||
// only; reset clears; independent latches; CTRL/STATUS/
|
||
// counter regs unchanged.
|
||
//
|
||
// Entry conditions inherited from prior sections:
|
||
// - reset_n = 1 since the very first deassertion.
|
||
// - core_halt, dma_done_seen, frame_seen, hdmi_init_done,
|
||
// hdmi_i2c_error are all asserted HIGH and stable.
|
||
// - CORE_CTRL = 0 (§9 last write released it).
|
||
// - FRAME_COUNT / DMA_DONE_COUNT / RASTER_OVERFLOW_COUNT all
|
||
// 0 (§10 trailing HDMI_CLR + §8 write-ignored check).
|
||
// - raster_overflow input is LOW (deasserted in §6).
|
||
// --------------------------------------------------------
|
||
// Reset state — all three latches read back as 0.
|
||
axi_read32(38'h040, rd); check_eq("INPUT_P1@rst", rd, 32'd0);
|
||
axi_read32(38'h044, rd); check_eq("INPUT_P2@rst", rd, 32'd0);
|
||
axi_read32(38'h048, rd); check_eq("INPUT_P1_RAW@rst", rd, 32'd0);
|
||
|
||
// Round-trip P1; siblings remain 0.
|
||
axi_write32(38'h040, 32'hDEADBEEF);
|
||
axi_read32(38'h040, rd); check_eq("INPUT_P1@rt", rd, 32'hDEADBEEF);
|
||
axi_read32(38'h044, rd); check_eq("INPUT_P2@p1_indep", rd, 32'd0);
|
||
axi_read32(38'h048, rd); check_eq("INPUT_P1_RAW@p1_indep",rd, 32'd0);
|
||
|
||
// Round-trip P2; P1 sticks, P1_RAW still 0.
|
||
axi_write32(38'h044, 32'hCAFEBABE);
|
||
axi_read32(38'h044, rd); check_eq("INPUT_P2@rt", rd, 32'hCAFEBABE);
|
||
axi_read32(38'h040, rd); check_eq("INPUT_P1@p2_indep", rd, 32'hDEADBEEF);
|
||
axi_read32(38'h048, rd); check_eq("INPUT_P1_RAW@p2_indep",rd, 32'd0);
|
||
|
||
// Round-trip P1_RAW; P1 + P2 stick.
|
||
axi_write32(38'h048, 32'h12345678);
|
||
axi_read32(38'h048, rd); check_eq("INPUT_P1_RAW@rt", rd, 32'h12345678);
|
||
axi_read32(38'h040, rd); check_eq("INPUT_P1@raw_indep", rd, 32'hDEADBEEF);
|
||
axi_read32(38'h044, rd); check_eq("INPUT_P2@raw_indep", rd, 32'hCAFEBABE);
|
||
|
||
// Decode-tightness: 0x070 is an undecoded slot inside the 128-byte
|
||
// side-effect window (0x04C..0x06C are now Ch322 LPDDR write-probe /
|
||
// texture-fill regs). Writes must not bleed into the input latches,
|
||
// and reads must return 0.
|
||
axi_write32(38'h070, 32'hFEEDF00D);
|
||
axi_read32(38'h070, rd); check_eq("oob_in_window@0x070", rd, 32'd0);
|
||
axi_read32(38'h048, rd); check_eq("INPUT_P1_RAW@070_nobleed", rd, 32'h12345678);
|
||
axi_read32(38'h040, rd); check_eq("INPUT_P1@070_nobleed", rd, 32'hDEADBEEF);
|
||
axi_read32(38'h044, rd); check_eq("INPUT_P2@070_nobleed", rd, 32'hCAFEBABE);
|
||
|
||
// Existing regs unchanged.
|
||
axi_read32(38'h010, rd); check_eq("CORE_CTRL@post_ch222", rd, 32'd0);
|
||
check_eq("core_reset_req@post_ch222", core_reset_req, 1'b0);
|
||
// CORE_STATUS still driven by held-high inputs: loaded(0) |
|
||
// halt(1) | dma_done(2) | frame_seen(3) | hdmi_init(4) |
|
||
// hdmi_err(5) = 0x3F.
|
||
axi_read32(38'h008, rd); check_eq("CORE_STATUS@post_ch222",rd, 32'h0000003F);
|
||
axi_read32(38'h014, rd); check_eq("CORE_PULSE@post_ch222", rd, 32'd0);
|
||
axi_read32(38'h020, rd); check_eq("FRAME_COUNT@post_ch222",rd, 32'd0);
|
||
axi_read32(38'h024, rd); check_eq("DMA_DONE_COUNT@post_ch222", rd, 32'd0);
|
||
axi_read32(38'h028, rd); check_eq("RASTER_OVERFLOW_COUNT@post_ch222", rd, 32'd0);
|
||
|
||
// Counters still advance via toggle inputs after Ch222 writes.
|
||
flip_frame_toggle();
|
||
axi_read32(38'h020, rd); check_eq("FRAME_COUNT@post_ch222_inc", rd, 32'd1);
|
||
|
||
// Reset clears all three latches (and the rest of the bridge,
|
||
// but only the Ch222 regs are asserted here — the other regs
|
||
// already have separate coverage in §1..§10).
|
||
@(posedge clk); reset_n = 1'b0;
|
||
repeat (4) @(posedge clk);
|
||
reset_n = 1'b1;
|
||
repeat (4) @(posedge clk);
|
||
axi_read32(38'h040, rd); check_eq("INPUT_P1@reset_clr", rd, 32'd0);
|
||
axi_read32(38'h044, rd); check_eq("INPUT_P2@reset_clr", rd, 32'd0);
|
||
axi_read32(38'h048, rd); check_eq("INPUT_P1_RAW@reset_clr",rd, 32'd0);
|
||
|
||
// --------------------------------------------------------
|
||
// 12. Ch223 — OSD compatibility sink @ 0x100 / 0x110 / 0x114.
|
||
// Store-and-readback latches; reset clears; unmapped
|
||
// slots inside the OSD window read 0 and don't accept
|
||
// register state. retrodesd's splash backend now writes
|
||
// these without being dropped on the side-effect map.
|
||
//
|
||
// Entry state inherited from the §11 trailing reset:
|
||
// - reset_n = 1, all latches and counters at 0.
|
||
// - Status inputs (core_halt etc.) still asserted; the
|
||
// 2-FF synchronizer re-fills within ~2 clks of reset
|
||
// release, so CORE_STATUS reads back 0x3F.
|
||
// - frame_toggle and dma_done_toggle at their last value
|
||
// (from §11 last flip); no further edges produced here.
|
||
// --------------------------------------------------------
|
||
// Reset state — all three OSD latches read 0.
|
||
axi_read32(38'h100, rd); check_eq("OSD_CTRL@rst", rd, 32'd0);
|
||
axi_read32(38'h110, rd); check_eq("OSD_CFG0@rst", rd, 32'd0);
|
||
axi_read32(38'h114, rd); check_eq("OSD_CFG1@rst", rd, 32'd0);
|
||
|
||
// Round-trip OSD_CTRL; siblings stay 0.
|
||
axi_write32(38'h100, 32'hA5A5A5A5);
|
||
axi_read32(38'h100, rd); check_eq("OSD_CTRL@rt", rd, 32'hA5A5A5A5);
|
||
axi_read32(38'h110, rd); check_eq("OSD_CFG0@ctrl_indep", rd, 32'd0);
|
||
axi_read32(38'h114, rd); check_eq("OSD_CFG1@ctrl_indep", rd, 32'd0);
|
||
|
||
// Round-trip OSD_CFG0; OSD_CTRL sticks, OSD_CFG1 still 0.
|
||
axi_write32(38'h110, 32'h11223344);
|
||
axi_read32(38'h110, rd); check_eq("OSD_CFG0@rt", rd, 32'h11223344);
|
||
axi_read32(38'h100, rd); check_eq("OSD_CTRL@cfg0_indep", rd, 32'hA5A5A5A5);
|
||
axi_read32(38'h114, rd); check_eq("OSD_CFG1@cfg0_indep", rd, 32'd0);
|
||
|
||
// Round-trip OSD_CFG1; OSD_CTRL + OSD_CFG0 stick.
|
||
axi_write32(38'h114, 32'h55667788);
|
||
axi_read32(38'h114, rd); check_eq("OSD_CFG1@rt", rd, 32'h55667788);
|
||
axi_read32(38'h100, rd); check_eq("OSD_CTRL@cfg1_indep", rd, 32'hA5A5A5A5);
|
||
axi_read32(38'h110, rd); check_eq("OSD_CFG0@cfg1_indep", rd, 32'h11223344);
|
||
|
||
// Decode-tightness inside the OSD window. 0x104/0x108/0x10C
|
||
// are reserved-for-Ch224; 0x118/0x11C are reserved-zero.
|
||
// Writes to any of those addresses must not bleed into the
|
||
// mapped CTRL/CFG0/CFG1 latches, and reads must return 0.
|
||
axi_read32(38'h104, rd); check_eq("oob_in_osd@0x104_rst", rd, 32'd0);
|
||
axi_read32(38'h108, rd); check_eq("oob_in_osd@0x108_rst", rd, 32'd0);
|
||
axi_read32(38'h10C, rd); check_eq("oob_in_osd@0x10C_rst", rd, 32'd0);
|
||
axi_read32(38'h118, rd); check_eq("oob_in_osd@0x118_rst", rd, 32'd0);
|
||
axi_read32(38'h11C, rd); check_eq("oob_in_osd@0x11C_rst", rd, 32'd0);
|
||
|
||
axi_write32(38'h104, 32'hFEEDF00D);
|
||
axi_write32(38'h108, 32'hFEEDF00D);
|
||
axi_write32(38'h10C, 32'hFEEDF00D);
|
||
axi_write32(38'h118, 32'hFEEDF00D);
|
||
axi_write32(38'h11C, 32'hFEEDF00D);
|
||
axi_read32(38'h104, rd); check_eq("oob_in_osd@0x104", rd, 32'd0);
|
||
axi_read32(38'h108, rd); check_eq("oob_in_osd@0x108", rd, 32'd0);
|
||
axi_read32(38'h10C, rd); check_eq("oob_in_osd@0x10C", rd, 32'd0);
|
||
axi_read32(38'h118, rd); check_eq("oob_in_osd@0x118", rd, 32'd0);
|
||
axi_read32(38'h11C, rd); check_eq("oob_in_osd@0x11C", rd, 32'd0);
|
||
// Mapped OSD latches unchanged by the tight-decode writes.
|
||
axi_read32(38'h100, rd); check_eq("OSD_CTRL@oob_nobleed", rd, 32'hA5A5A5A5);
|
||
axi_read32(38'h110, rd); check_eq("OSD_CFG0@oob_nobleed", rd, 32'h11223344);
|
||
axi_read32(38'h114, rd); check_eq("OSD_CFG1@oob_nobleed", rd, 32'h55667788);
|
||
|
||
// Other regs unchanged across the Ch223 writes. The toggle
|
||
// synchronizers re-fill after the §11 reset and can emit a
|
||
// single phantom edge in FRAME_COUNT / DMA_DONE_COUNT before
|
||
// steady state — that's a CDC-refill artifact, not a Ch223
|
||
// bug. Issue an HDMI_CLR pulse here to zero the counters
|
||
// after the refill has settled; then prove the subsequent
|
||
// OSD writes don't disturb them.
|
||
axi_write32(38'h014, 32'h00000008); // CORE_PULSE.HDMI_CLR
|
||
repeat (4) @(posedge clk);
|
||
|
||
axi_read32(38'h040, rd); check_eq("INPUT_P1@post_ch223", rd, 32'd0);
|
||
axi_read32(38'h044, rd); check_eq("INPUT_P2@post_ch223", rd, 32'd0);
|
||
axi_read32(38'h048, rd); check_eq("INPUT_P1_RAW@post_ch223", rd, 32'd0);
|
||
axi_read32(38'h010, rd); check_eq("CORE_CTRL@post_ch223", rd, 32'd0);
|
||
axi_read32(38'h008, rd); check_eq("CORE_STATUS@post_ch223", rd, 32'h0000003F);
|
||
axi_read32(38'h014, rd); check_eq("CORE_PULSE@post_ch223", rd, 32'd0);
|
||
axi_read32(38'h020, rd); check_eq("FRAME_COUNT@post_ch223", rd, 32'd0);
|
||
axi_read32(38'h024, rd); check_eq("DMA_DONE_COUNT@post_ch223",rd, 32'd0);
|
||
axi_read32(38'h028, rd); check_eq("RASTER_OVERFLOW_COUNT@post_ch223", rd, 32'd0);
|
||
|
||
// Reset clears all three OSD latches.
|
||
@(posedge clk); reset_n = 1'b0;
|
||
repeat (4) @(posedge clk);
|
||
reset_n = 1'b1;
|
||
repeat (4) @(posedge clk);
|
||
axi_read32(38'h100, rd); check_eq("OSD_CTRL@reset_clr", rd, 32'd0);
|
||
axi_read32(38'h110, rd); check_eq("OSD_CFG0@reset_clr", rd, 32'd0);
|
||
axi_read32(38'h114, rd); check_eq("OSD_CFG1@reset_clr", rd, 32'd0);
|
||
|
||
// --------------------------------------------------------
|
||
// 13. Ch224 — OSD_STATUS / OSD_TRIGGER / OSD_INPUT contracts.
|
||
//
|
||
// OSD_STATUS @ 0x104: always-zero source. No FSM driving it
|
||
// in PS2 today; reads must return 0 regardless of any
|
||
// writes that landed at that address.
|
||
// OSD_TRIGGER @ 0x108: W1C-shape sink. The contract sibling
|
||
// cores expose is "FSM sets ACTION/BACK/SCROLL bits,
|
||
// retrodesd reads, processes, writes-1-to-clear". With no
|
||
// FSM source, bits never go HIGH, so reads always return 0
|
||
// and *any* W1C pattern (including 0xFFFFFFFF and the real
|
||
// OSD_TRIG_ACTION|row<<8 shape retrodesd writes) is a
|
||
// no-op against an already-zero register.
|
||
// OSD_INPUT @ 0x10C: reserved-zero. Sibling cores use this
|
||
// as a debug-override; PS2 keeps it dormant.
|
||
//
|
||
// Existing OSD_CTRL/CFG0/CFG1 latches must survive every
|
||
// poke at 0x104/0x108/0x10C.
|
||
// --------------------------------------------------------
|
||
// First arm OSD_CTRL/CFG0/CFG1 to known non-zero values so
|
||
// the "unchanged" checks below have signal to lose.
|
||
axi_write32(38'h100, 32'hA5A5A5A5);
|
||
axi_write32(38'h110, 32'h11223344);
|
||
axi_write32(38'h114, 32'h55667788);
|
||
|
||
// OSD_STATUS — always 0, any write is a no-op.
|
||
axi_read32(38'h104, rd); check_eq("OSD_STATUS@rst", rd, 32'd0);
|
||
axi_write32(38'h104, 32'hFFFFFFFF);
|
||
axi_read32(38'h104, rd); check_eq("OSD_STATUS@write_all", rd, 32'd0);
|
||
axi_write32(38'h104, 32'h00000001); // would be the "active" bit if FSM existed
|
||
axi_read32(38'h104, rd); check_eq("OSD_STATUS@write_active", rd, 32'd0);
|
||
|
||
// OSD_TRIGGER — W1C sink. All shapes (broad, real W1C
|
||
// mask, action+row encoding) read back 0.
|
||
axi_read32(38'h108, rd); check_eq("OSD_TRIGGER@rst", rd, 32'd0);
|
||
axi_write32(38'h108, 32'hFFFFFFFF);
|
||
axi_read32(38'h108, rd); check_eq("OSD_TRIGGER@w1c_all", rd, 32'd0);
|
||
// OSD_TRIG_ACTION = 1<<4 = 0x00000010; ROW=3 → 0x00000300; combined.
|
||
axi_write32(38'h108, 32'h00000310);
|
||
axi_read32(38'h108, rd); check_eq("OSD_TRIGGER@w1c_action_row3", rd, 32'd0);
|
||
// OSD_TRIG_BACK = 1<<5 = 0x00000020.
|
||
axi_write32(38'h108, 32'h00000020);
|
||
axi_read32(38'h108, rd); check_eq("OSD_TRIGGER@w1c_back", rd, 32'd0);
|
||
|
||
// OSD_INPUT — reserved-zero, any write is a no-op.
|
||
axi_read32(38'h10C, rd); check_eq("OSD_INPUT@rst", rd, 32'd0);
|
||
axi_write32(38'h10C, 32'hDEADBEEF);
|
||
axi_read32(38'h10C, rd); check_eq("OSD_INPUT@write", rd, 32'd0);
|
||
|
||
// OSD_CTRL/CFG0/CFG1 unchanged by every Ch224 poke above.
|
||
axi_read32(38'h100, rd); check_eq("OSD_CTRL@post_ch224", rd, 32'hA5A5A5A5);
|
||
axi_read32(38'h110, rd); check_eq("OSD_CFG0@post_ch224", rd, 32'h11223344);
|
||
axi_read32(38'h114, rd); check_eq("OSD_CFG1@post_ch224", rd, 32'h55667788);
|
||
|
||
// Audit-tail: 0x120 is the first byte past the OSD window
|
||
// and reads 0 (the decode guard `addr[37:5] == 33'h08`
|
||
// rejects it). Confirms the Ch223 window boundary is tight.
|
||
axi_read32(38'h120, rd); check_eq("OSD_window_tail@0x120", rd, 32'd0);
|
||
|
||
// --------------------------------------------------------
|
||
// 14. Ch225 — VIDEO_STATUS @ 0x060 + HDMI_DIAG @ 0x064.
|
||
// Read-only diagnostic views; writes accepted-and-ignored.
|
||
//
|
||
// Entry state (inherited from §13):
|
||
// - core_halt = dma_done_seen = frame_seen = hdmi_init_done
|
||
// = hdmi_i2c_error = 1 (held HIGH since §2/§4/§5).
|
||
// - raster_overflow = 0 (deasserted in §6).
|
||
// - frame_count = dma_done_count = 1 (the §12 trailing
|
||
// reset re-fired the toggle synchronizer phantom edge
|
||
// that the §12 HDMI_CLR previously absorbed; §13 didn't
|
||
// re-clear). scanout_alive is therefore already SET at
|
||
// entry — useful, since the test then exercises both
|
||
// transitions (HIGH → HDMI_CLR → LOW → toggle → HIGH).
|
||
// - OSD_CTRL=0xA5A5A5A5, OSD_CFG0=0x11223344,
|
||
// OSD_CFG1=0x55667788 (from §13's arming write).
|
||
//
|
||
// Expected entry reads:
|
||
// VIDEO_STATUS[3:0] = {dma_done(1), raster_of(0),
|
||
// scanout_alive(1), frame_seen(1)}
|
||
// = 4'b1011 = 0xB
|
||
// HDMI_DIAG[1:0] = {hdmi_err(1), hdmi_init(1)} = 0x3
|
||
// --------------------------------------------------------
|
||
axi_read32(38'h060, rd); check_eq("VIDEO_STATUS@entry", rd, 32'h0000000B);
|
||
axi_read32(38'h064, rd); check_eq("HDMI_DIAG@entry", rd, 32'h00000003);
|
||
|
||
// scanout_alive falling edge: HDMI_CLR zeros frame_count.
|
||
axi_write32(38'h014, 32'h00000008);
|
||
repeat (4) @(posedge clk);
|
||
axi_read32(38'h020, rd); check_eq("FRAME_COUNT@ch225_clr", rd, 32'd0);
|
||
axi_read32(38'h060, rd); check_eq("VIDEO_STATUS@scanout_lo", rd, 32'h00000009);
|
||
// [0]=frame_seen | [3]=dma_done, [1]=0 = 0x9
|
||
|
||
// scanout_alive rising edge: one toggle flip bumps frame_count
|
||
// to 1, scanout_alive re-rises.
|
||
flip_frame_toggle();
|
||
axi_read32(38'h020, rd); check_eq("FRAME_COUNT@ch225_inc", rd, 32'd1);
|
||
axi_read32(38'h060, rd); check_eq("VIDEO_STATUS@scanout_hi", rd, 32'h0000000B);
|
||
|
||
// raster_overflow live tracking: pulse HIGH, then LOW.
|
||
@(posedge clk); raster_overflow = 1'b1;
|
||
repeat (5) @(posedge clk);
|
||
axi_read32(38'h060, rd); check_eq("VIDEO_STATUS@raster_hi", rd, 32'h0000000F);
|
||
// [0..3] all set = 0xF
|
||
|
||
@(posedge clk); raster_overflow = 1'b0;
|
||
repeat (5) @(posedge clk);
|
||
axi_read32(38'h060, rd); check_eq("VIDEO_STATUS@raster_lo", rd, 32'h0000000B);
|
||
|
||
// Writes to 0x060 / 0x064 are accepted-and-ignored; the
|
||
// read-only diagnostic values must remain driven by status.
|
||
axi_write32(38'h060, 32'hFFFFFFFF);
|
||
axi_read32(38'h060, rd); check_eq("VIDEO_STATUS@write_all", rd, 32'h0000000B);
|
||
axi_write32(38'h060, 32'h00000000);
|
||
axi_read32(38'h060, rd); check_eq("VIDEO_STATUS@write_zero",rd, 32'h0000000B);
|
||
|
||
axi_write32(38'h064, 32'hFFFFFFFF);
|
||
axi_read32(38'h064, rd); check_eq("HDMI_DIAG@write_all", rd, 32'h00000003);
|
||
axi_write32(38'h064, 32'hDEADBEEF);
|
||
axi_read32(38'h064, rd); check_eq("HDMI_DIAG@write_pat", rd, 32'h00000003);
|
||
|
||
// Ch222–Ch224 latches unchanged across the Ch225 writes.
|
||
axi_read32(38'h040, rd); check_eq("INPUT_P1@post_ch225", rd, 32'd0);
|
||
axi_read32(38'h044, rd); check_eq("INPUT_P2@post_ch225", rd, 32'd0);
|
||
axi_read32(38'h048, rd); check_eq("INPUT_P1_RAW@post_ch225",rd, 32'd0);
|
||
axi_read32(38'h100, rd); check_eq("OSD_CTRL@post_ch225", rd, 32'hA5A5A5A5);
|
||
axi_read32(38'h110, rd); check_eq("OSD_CFG0@post_ch225", rd, 32'h11223344);
|
||
axi_read32(38'h114, rd); check_eq("OSD_CFG1@post_ch225", rd, 32'h55667788);
|
||
|
||
// --------------------------------------------------------
|
||
// 15. Ch248 — DS2 wired-controller live readback at 0x0F0 / 0x0F4.
|
||
// Replaces the Ch226 hardcoded stub. The bridge now
|
||
// composes DS2_STATUS from the (ds2_connected_i,
|
||
// ds2_error_i) ports driven by the platform
|
||
// `ds2_controller` in the top, and DS2_BUTTONS is the
|
||
// ds2_buttons_i input (NOT a mirror of INPUT_P1).
|
||
//
|
||
// DS2_STATUS bit layout (sibling-ABI + PS2-local):
|
||
// [0] connected — from ds2_connected_i
|
||
// [1] error — from ds2_error_i
|
||
// [2] reserved — 1 (PS2-local legacy bit kept stable)
|
||
// [31:3] reserved — 0
|
||
//
|
||
// DS2_BUTTONS [31:0] = ds2_buttons_i (controller's
|
||
// decoded button bitmap). Writes to 0x0F0/0x0F4 are
|
||
// still accepted-and-ignored — both are read-only.
|
||
// --------------------------------------------------------
|
||
// Entry state: TB-driven inputs all at reset defaults
|
||
// (ds2_buttons_i=0, ds2_connected_i=0, ds2_error_i=0).
|
||
axi_read32(38'h0F0, rd); check_eq("DS2_STATUS@unplugged", rd, 32'h00000004);
|
||
axi_read32(38'h0F4, rd); check_eq("DS2_BUTTONS@unplugged", rd, 32'd0);
|
||
|
||
// Plug in the controller (no errors).
|
||
ds2_connected_i = 1'b1;
|
||
@(posedge clk);
|
||
axi_read32(38'h0F0, rd); check_eq("DS2_STATUS@connected", rd, 32'h00000005);
|
||
|
||
// Drive a button bitmap.
|
||
ds2_buttons_i = 32'hF00DBABE;
|
||
@(posedge clk);
|
||
axi_read32(38'h0F4, rd); check_eq("DS2_BUTTONS@live", rd, 32'hF00DBABE);
|
||
|
||
// INPUT_P1 mirror behavior is gone — writing INPUT_P1 must
|
||
// NOT change DS2_BUTTONS now (Ch248 cuts the Ch226 mirror).
|
||
axi_write32(38'h040, 32'h13579BDF);
|
||
axi_read32(38'h0F4, rd); check_eq("DS2_BUTTONS@p1_no_mirror", rd, 32'hF00DBABE);
|
||
|
||
// INPUT_P2 / INPUT_P1_RAW writes don't touch DS2_BUTTONS either.
|
||
axi_write32(38'h044, 32'hDEADC0DE);
|
||
axi_write32(38'h048, 32'h0BADF00D);
|
||
axi_read32(38'h0F4, rd); check_eq("DS2_BUTTONS@p2raw_no_mirror", rd, 32'hF00DBABE);
|
||
|
||
// Update the button bitmap → readback follows.
|
||
ds2_buttons_i = 32'h12345678;
|
||
@(posedge clk);
|
||
axi_read32(38'h0F4, rd); check_eq("DS2_BUTTONS@update", rd, 32'h12345678);
|
||
|
||
// Comms-error condition: error=1 → DS2_STATUS[1]=1.
|
||
ds2_error_i = 1'b1;
|
||
@(posedge clk);
|
||
axi_read32(38'h0F0, rd); check_eq("DS2_STATUS@error", rd, 32'h00000007);
|
||
ds2_error_i = 1'b0;
|
||
@(posedge clk);
|
||
|
||
// Disconnect → status drops connected bit but ds2_buttons_i
|
||
// remains whatever the controller last reported (the
|
||
// controller-RTL latches it; the bridge passes through).
|
||
ds2_connected_i = 1'b0;
|
||
@(posedge clk);
|
||
axi_read32(38'h0F0, rd); check_eq("DS2_STATUS@disc", rd, 32'h00000004);
|
||
|
||
// Writes to 0x0F0 / 0x0F4 are accepted-and-ignored.
|
||
axi_write32(38'h0F0, 32'hFFFFFFFF);
|
||
axi_read32(38'h0F0, rd); check_eq("DS2_STATUS@write_all", rd, 32'h00000004);
|
||
axi_write32(38'h0F4, 32'hFFFFFFFF);
|
||
axi_read32(38'h0F4, rd); check_eq("DS2_BUTTONS@write_all", rd, 32'h12345678);
|
||
|
||
// Other regs unchanged across the Ch248 reads (sibling-ABI
|
||
// additivity unchanged from Ch226).
|
||
axi_read32(38'h040, rd); check_eq("INPUT_P1@post_ch248", rd, 32'h13579BDF);
|
||
axi_read32(38'h044, rd); check_eq("INPUT_P2@post_ch248", rd, 32'hDEADC0DE);
|
||
axi_read32(38'h048, rd); check_eq("INPUT_P1_RAW@post_ch248",rd, 32'h0BADF00D);
|
||
axi_read32(38'h100, rd); check_eq("OSD_CTRL@post_ch248", rd, 32'hA5A5A5A5);
|
||
axi_read32(38'h110, rd); check_eq("OSD_CFG0@post_ch248", rd, 32'h11223344);
|
||
axi_read32(38'h114, rd); check_eq("OSD_CFG1@post_ch248", rd, 32'h55667788);
|
||
axi_read32(38'h010, rd); check_eq("CORE_CTRL@post_ch248", rd, 32'd0);
|
||
|
||
// Reset clears INPUT_P1 latch; DS2 inputs are TB-side so they
|
||
// stay at whatever the TB last drove. Set them to a known
|
||
// unplugged state across the reset.
|
||
ds2_buttons_i = 32'd0;
|
||
ds2_connected_i = 1'b0;
|
||
ds2_error_i = 1'b0;
|
||
@(posedge clk); reset_n = 1'b0;
|
||
repeat (4) @(posedge clk);
|
||
reset_n = 1'b1;
|
||
repeat (4) @(posedge clk);
|
||
axi_read32(38'h0F0, rd); check_eq("DS2_STATUS@reset", rd, 32'h00000004);
|
||
axi_read32(38'h0F4, rd); check_eq("DS2_BUTTONS@reset", rd, 32'd0);
|
||
|
||
// --------------------------------------------------------
|
||
// 16. Ch227 — tile RAM storage @ 0x1000..0x1FFF.
|
||
//
|
||
// Entry state (inherited from §15 trailing reset):
|
||
// - Tile RAM was sim-initialized to 0 by the bridge's
|
||
// `initial` block; no writes have hit it yet, so every
|
||
// slot reads 0.
|
||
// - INPUT_P1 = 0 (cleared by §15 reset), but INPUT_P2
|
||
// and INPUT_P1_RAW survive at 0xDEADC0DE / 0x0BADF00D
|
||
// after §15's pre-reset arms… *unless* they were also
|
||
// cleared by the reset. Ch222 latches DO reset (§11
|
||
// reset path showed it), so the §15 reset cleared them
|
||
// too.
|
||
// - OSD_CTRL/CFG0/CFG1 = 0 (cleared by §15 reset).
|
||
// - Counters = 0 with potential 1-edge sync-refill from
|
||
// the §15 reset — irrelevant for tile-RAM testing.
|
||
// --------------------------------------------------------
|
||
// Reset state: every probed tile slot reads 0.
|
||
axi_read32(38'h1000, rd); check_eq("TILE_RAM@base_rst", rd, 32'd0);
|
||
axi_read32(38'h1800, rd); check_eq("TILE_RAM@mid_rst", rd, 32'd0);
|
||
axi_read32(38'h1FFC, rd); check_eq("TILE_RAM@end_rst", rd, 32'd0);
|
||
|
||
// Round-trip at base / middle / end.
|
||
axi_write32(38'h1000, 32'hCAFEF00D);
|
||
axi_read32(38'h1000, rd); check_eq("TILE_RAM@base_rt", rd, 32'hCAFEF00D);
|
||
|
||
axi_write32(38'h1800, 32'h12345678);
|
||
axi_read32(38'h1800, rd); check_eq("TILE_RAM@mid_rt", rd, 32'h12345678);
|
||
|
||
axi_write32(38'h1FFC, 32'hDEADBEEF);
|
||
axi_read32(38'h1FFC, rd); check_eq("TILE_RAM@end_rt", rd, 32'hDEADBEEF);
|
||
|
||
// Independent-slot check: each write hit only its target.
|
||
axi_read32(38'h1000, rd); check_eq("TILE_RAM@base_indep", rd, 32'hCAFEF00D);
|
||
axi_read32(38'h1800, rd); check_eq("TILE_RAM@mid_indep", rd, 32'h12345678);
|
||
axi_read32(38'h1FFC, rd); check_eq("TILE_RAM@end_indep", rd, 32'hDEADBEEF);
|
||
|
||
// Adjacent-slot tightness: a slot we never wrote stays 0.
|
||
axi_read32(38'h1004, rd); check_eq("TILE_RAM@base+4_zero", rd, 32'd0);
|
||
axi_read32(38'h17FC, rd); check_eq("TILE_RAM@mid-4_zero", rd, 32'd0);
|
||
axi_read32(38'h1FF8, rd); check_eq("TILE_RAM@end-4_zero", rd, 32'd0);
|
||
|
||
// Boundary sentinels: 0x0FFC is the last word *below* the
|
||
// tile window (between OSD at 0x11C and tile at 0x1000), and
|
||
// 0x2000 is the first word *above* the window. Both are
|
||
// outside every decoded region.
|
||
axi_read32(38'h0FFC, rd); check_eq("TILE_RAM_bound_lo@0x0FFC", rd, 32'd0);
|
||
axi_read32(38'h2000, rd); check_eq("TILE_RAM_bound_hi@0x2000", rd, 32'd0);
|
||
|
||
// Writes to those boundary addresses must not bleed into the
|
||
// tile RAM. Use distinct patterns to make a bleed visible.
|
||
axi_write32(38'h0FFC, 32'hAAAA5555);
|
||
axi_write32(38'h2000, 32'h5555AAAA);
|
||
axi_read32(38'h0FFC, rd); check_eq("TILE_RAM_bound_lo_w@0x0FFC", rd, 32'd0);
|
||
axi_read32(38'h2000, rd); check_eq("TILE_RAM_bound_hi_w@0x2000", rd, 32'd0);
|
||
// Inside-window slots near the boundaries didn't pick up the
|
||
// boundary writes either.
|
||
axi_read32(38'h1000, rd); check_eq("TILE_RAM@base_nobleed", rd, 32'hCAFEF00D);
|
||
axi_read32(38'h1FFC, rd); check_eq("TILE_RAM@end_nobleed", rd, 32'hDEADBEEF);
|
||
|
||
// Other regs unchanged across the Ch227 writes.
|
||
axi_read32(38'h008, rd); check_eq("CORE_STATUS@post_ch227", rd, 32'h0000003F);
|
||
axi_read32(38'h010, rd); check_eq("CORE_CTRL@post_ch227", rd, 32'd0);
|
||
axi_read32(38'h040, rd); check_eq("INPUT_P1@post_ch227", rd, 32'd0);
|
||
axi_read32(38'h100, rd); check_eq("OSD_CTRL@post_ch227", rd, 32'd0);
|
||
axi_read32(38'h110, rd); check_eq("OSD_CFG0@post_ch227", rd, 32'd0);
|
||
axi_read32(38'h114, rd); check_eq("OSD_CFG1@post_ch227", rd, 32'd0);
|
||
axi_read32(38'h0F0, rd); check_eq("DS2_STATUS@post_ch227", rd, 32'h00000004);
|
||
|
||
// Reset retention: tile RAM is *retained* across warm reset
|
||
// (no sibling precedent for clearing, and the bridge owns
|
||
// the storage). Pulse reset and verify the previously
|
||
// written tile slots still hold their values.
|
||
@(posedge clk); reset_n = 1'b0;
|
||
repeat (4) @(posedge clk);
|
||
reset_n = 1'b1;
|
||
repeat (4) @(posedge clk);
|
||
axi_read32(38'h1000, rd); check_eq("TILE_RAM@base_post_rst", rd, 32'hCAFEF00D);
|
||
axi_read32(38'h1800, rd); check_eq("TILE_RAM@mid_post_rst", rd, 32'h12345678);
|
||
axi_read32(38'h1FFC, rd); check_eq("TILE_RAM@end_post_rst", rd, 32'hDEADBEEF);
|
||
|
||
// --------------------------------------------------------
|
||
// Ch330 Brick 4 — feeder staging-write + retrigger regmap.
|
||
// --------------------------------------------------------
|
||
begin
|
||
logic we_b4, go_b4;
|
||
we_b4 = feeder_stg_we_tgl_o; go_b4 = feeder_go_tgl_o;
|
||
// write staging[5] = 0x12345678_CAFEF00D via ADDR/LO/HI
|
||
axi_write32(38'h0D8, 32'd5); // FEEDER_STG_ADDR = 5
|
||
axi_write32(38'h0DC, 32'hCAFEF00D); // FEEDER_STG_LO
|
||
axi_write32(38'h0E4, 32'h12345678); // FEEDER_STG_HI -> commit + addr++
|
||
repeat (2) @(posedge clk);
|
||
check_eq("FEEDER_we_toggled", {31'd0, (feeder_stg_we_tgl_o ^ we_b4)}, 32'd1);
|
||
check_eq("FEEDER_waddr", {20'd0, feeder_stg_waddr_o}, 32'd5);
|
||
check_eq("FEEDER_wdata_lo", feeder_stg_wdata_o[31:0], 32'hCAFEF00D);
|
||
check_eq("FEEDER_wdata_hi", feeder_stg_wdata_o[63:32], 32'h12345678);
|
||
axi_read32(38'h0DC, rd); check_eq("FEEDER_addr_autoinc", rd, 32'd6); // advanced 5->6
|
||
|
||
// a second word WITHOUT re-writing ADDR streams into staging[6]
|
||
axi_write32(38'h0DC, 32'h0000AAAA);
|
||
axi_write32(38'h0E4, 32'h0000BBBB); // commit -> waddr should be 6
|
||
repeat (2) @(posedge clk);
|
||
check_eq("FEEDER_waddr_stream", {20'd0, feeder_stg_waddr_o}, 32'd6);
|
||
axi_read32(38'h0DC, rd); check_eq("FEEDER_addr_autoinc2", rd, 32'd7);
|
||
|
||
// GO pulse toggles
|
||
axi_write32(38'h0E8, 32'd1); // FEEDER_GO bit0
|
||
repeat (2) @(posedge clk);
|
||
check_eq("FEEDER_go_toggled", {31'd0, (feeder_go_tgl_o ^ go_b4)}, 32'd1);
|
||
// GO with bit0=0 must NOT toggle
|
||
go_b4 = feeder_go_tgl_o;
|
||
axi_write32(38'h0E8, 32'd0);
|
||
repeat (2) @(posedge clk);
|
||
check_eq("FEEDER_go_no_toggle", {31'd0, (feeder_go_tgl_o ^ go_b4)}, 32'd0);
|
||
|
||
// status readbacks
|
||
feeder_ready_i = 1'b1; feeder_records_i = 16'd4; feeder_waits_i = 32'd7;
|
||
repeat (3) @(posedge clk); // let ready sync through
|
||
axi_read32(38'h0D8, rd); check_eq("FEEDER_status_ready", rd[0], 1'b1);
|
||
axi_read32(38'h0E4, rd); check_eq("FEEDER_records", rd, 32'd4);
|
||
axi_read32(38'h0E8, rd); check_eq("FEEDER_waits", rd, 32'd7);
|
||
end
|
||
|
||
// --------------------------------------------------------
|
||
// Done.
|
||
// --------------------------------------------------------
|
||
$display("[tb_ps2_hps_bridge] errors=%0d", errors);
|
||
if (errors == 0) $display("[tb_ps2_hps_bridge] PASS");
|
||
else $display("[tb_ps2_hps_bridge] FAIL");
|
||
$finish;
|
||
end
|
||
|
||
initial begin
|
||
#2_000_000;
|
||
$error("[tb_ps2_hps_bridge] TIMEOUT");
|
||
$finish;
|
||
end
|
||
|
||
endmodule : tb_ps2_hps_bridge
|