// 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