Files
thejayman77 ec82764bef Initial commit: retroDE_ps2 — first-of-its-kind PS2 GS FPGA core (DE25-Nano / Agilex 5)
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression
(272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps,
and all dump-derived textures/traces) is excluded via .gitignore and stays local.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 20:10:50 -04:00

1345 lines
78 KiB
Systemverilog
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2025-2026 retroDE contributors
// ============================================================================
// ps2_hps_bridge — Ch173/Ch174/Ch176 AXI4 register surface for the PS2 core
// ============================================================================
//
// Replaces ps2_hps_bridge_null (Ch170 placeholder). Adds live status
// registers the HPS-side Linux userspace can read to confirm what the
// fabric is doing without watching the HDMI output. All identity +
// status registers are read-only; writes ACK with OKAY and discard
// (matches the Codex Ch173 caution to keep Ch173 read-only unless
// the write path is trivial).
//
// Port signature is a strict superset of ps2_hps_bridge_null so the
// top wrapper can swap the instantiation without re-plumbing AXI.
//
// Register map (read-only, single-beat AXI4, 128-bit data with 32-bit
// lane selection via {awaddr[3:2] / araddr[3:2]}). 0x000-0x01F is the
// retroDE common ABI v1.0 window (matches splash/nes/coco2 layout per
// retroDE_splash/software/input_common.h); the PS2-specific counters
// land at 0x020+ to avoid colliding with shared-ABI semantics.
//
// 0x000 CORE_ID 32'h50533200 ("PS2\0")
// 0x004 ABI_VERSION 32'h00000100 (v1.0)
// 0x008 CORE_STATUS
// bit 0: loaded (always 1 after configuration)
// bit 1: core_halt (EE core syscall'd / halted)
// bit 2: dma_done_seen (DMAC channel 2 reached done)
// bit 3: frame_seen (PCRTC produced end-of-frame)
// bit 4: hdmi_init_done (ADV7513 I²C config FSM complete)
// bit 5: hdmi_i2c_error (Ch166 NACK watchdog latched)
// bit 6: raster_overflow (Ch87 FIFO push_drop ever fired
// — should stay 0 under Ch172
// backpressure)
// bits[31:7]: reserved-zero
// 0x00C CORE_CAPS 32'h00000000 (no caps advertised
// yet; ROM/savestate/etc capability
// bits land here when wired)
// 0x010 CORE_CTRL Ch176 writable + readable. Bit layout
// matches shared retroDE ABI:
// [0] RESET — functional. Held
// high = PS2 design
// domain in reset.
// Exposed on the
// `core_reset_req`
// port; the board
// top 2-FF syncs it
// into design_clk
// and gates the
// existing reset.
// [1] ROM_LOADED — latch only. PS2
// has no ROM
// lifecycle today
// (we ship as
// backend=splash).
// Written/read for
// ABI shape only.
// [2] PAUSE — latch only. No
// clean stall point
// in this design
// yet; later chapter
// wires the gate.
// Unused bits readback as 0.
// 0x014 CORE_PULSE Ch176 write-only side-effect register.
// Self-clearing (always reads 0).
// Bit layout matches shared ABI:
// [3] HDMI_CLR — clears FRAME_COUNT,
// DMA_DONE_COUNT,
// RASTER_OVERFLOW_COUNT.
// PS2 widens the
// shared "HDMI
// diagnostic clear"
// to "video/bridge
// diagnostic clear"
// per Codex Ch176
// naming note.
// [0,1,2] DIRTY_CLR / SS_DONE_CLR /
// VIDEO_REC — ACK'd
// and ignored (PS2
// has no savestate
// or HDMI re-init
// path yet).
// 0x018 reserved 32'h00000000
// 0x01C reserved 32'h00000000
//
// PS2-specific diagnostics (Ch173, retroDE-ps2 extension to the shared
// ABI; safe because the shared map only reserves 0x000-0x01F):
//
// 0x020 FRAME_COUNT Ch174: increments on each edge of
// `frame_toggle` (the event-toggle
// primitive — flips on every PCRTC
// end-of-frame pulse in design_clk).
// Replaces Ch173's edge-of-sticky
// source so the counter actually
// tracks frames in hardware.
// 0x024 DMA_DONE_COUNT Ch174: increments on each edge of
// `dma_done_toggle` (event-toggle —
// flips on every DMAC EV_DMA_DONE).
// 0x028 RASTER_OVERFLOW_COUNT counts raster_overflow cycles
// (cycle-by-cycle, not edge — a
// non-zero value = the backpressure
// path missed somewhere)
// 0x02C reserved 32'h00000000
//
// Ch222 — input latch block. HPS-visible write/read registers at the
// shared-ABI input offsets. No SIO2 / DualShock wiring yet; retrodesd
// writes here so future PS2-side controller emulation has a stable
// read port. Reset clears all three to 0.
//
// 0x040 INPUT_P1 Ch222: 32-bit RW latch. retrodesd's
// `input_thread` writes the remapped
// gamepad bitmap (JOY_* bits per
// input_common.h).
// 0x044 INPUT_P2 Ch222: 32-bit RW latch (player 2).
// 0x048 INPUT_P1_RAW Ch222: 32-bit RW latch. Un-remapped
// mirror of the player-1 buttons,
// used by the OSD navigation FSM in
// other cores. PS2 stores it without
// consumers today.
// 0x04C reserved 32'h00000000
//
// Ch225 — VIDEO_STATUS / HDMI_DIAG diagnostic read surface. Both are
// pure-read views into the synchronized status signals the bridge
// already tracks; writes are ack'd-and-ignored. retrodesd / operator
// tooling can poll these to answer "is the display path alive?"
// without depending on LEDs or `ps2_status.sh`.
//
// 0x060 VIDEO_STATUS Ch225: read-only diagnostic bits.
// [0] frame_seen (live sticky from
// PCRTC end-of-frame)
// [1] scanout_alive (= frame_count!=0,
// i.e. ≥ 1 frame
// completed since
// last HDMI_CLR)
// [2] raster_overflow (live — drops if
// the input dies)
// [3] dma_done_seen (live sticky)
// [31:4] reserved-zero.
// 0x064 HDMI_DIAG Ch225: read-only diagnostic bits.
// [0] hdmi_init_done (ADV7513 LUT walk
// complete)
// [1] hdmi_i2c_error (Ch166 NACK
// watchdog latched)
// [31:2] reserved-zero.
//
// Ch226 — DS2 wired-controller stub at the shared-ABI offsets. PS2
// has no DS2 platform path today, so the stub honors the sibling-ABI
// contract consumed by retrodesd's `ds2_poll_thread.c`:
// `connected = status & 0x1` and `error = (status >> 1) & 0x1`. With
// connected = 0, retrodesd's poll thread treats DS2 as absent and
// never calls `osd_input_update_ds2(buttons)` — the right behavior.
// Bit [2] is a PS2-local diagnostic (`input_latches_valid`) confirming
// that the Ch222 latch surface exists; it deliberately sits above the
// shared bits to avoid colliding with the sibling-consumed
// `[0]=connected / [1]=error` semantics. DS2_BUTTONS mirrors
// INPUT_P1 so HPS can confirm the Ch222 store landed via the same
// bridge offset the sibling cores expose.
//
// 0x0F0 DS2_STATUS Ch226: read-only sibling-ABI stub.
// [0] connected = 1'b0
// (no DS2 wired path)
// [1] error = 1'b0
// (not an error state)
// [2] input_latches_valid = 1'b1
// (PS2-local: Ch222 OK)
// [31:3] reserved-zero.
// Reads = 32'h00000004.
// 0x0F4 DS2_BUTTONS Ch226: read-only mirror of INPUT_P1
// (Ch222). Reads return whatever
// retrodesd last wrote to 0x040.
// With `connected=0` retrodesd
// never consumes this, but operator
// tooling can use it to confirm the
// Ch222 store via the DS2 path.
//
// Ch227 — tile RAM storage surface at 0x1000..0x1FFF (4 KB window).
// The shared-ABI software view is "2048 × 16-bit cells packed 2/word"
// (see input_common.h's `osd_setup` + `osd_putchar`), but at the bus
// level the bridge stores plain 32-bit words and returns exactly
// what HPS wrote. Sibling cores forward tile writes to an external
// overlay engine via `tile_wr_addr/data/en` output ports; PS2 has
// no overlay engine yet (Ch228 will wire one), so for Ch227 the
// bridge owns the storage internally as a 1024 × 32-bit memory
// array. No PCRTC overlay composition; reads simply mirror the
// last write. Reset semantics: contents are *retained* across warm
// reset (no sibling precedent for clearing — siblings don't even
// own the storage). Sim initializes to 0 for determinism; power-up
// value on hardware is undefined until the overlay-engine chapter
// pins a reset contract.
//
// 0x1000..0x1FFF Tile RAM Ch227: 1024 × 32-bit RW. Decode
// guard `addr[37:12] == 26'h1`;
// word index = addr[11:2]. WSTRB
// ignored (full-word writes only,
// same contract as the rest of
// the bridge).
//
// Ch223 — OSD compatibility sink. retrodesd's splash backend writes
// OSD_CTRL / OSD_CFG0 / OSD_CFG1 at these offsets during start(),
// followed by tile RAM writes at 0x1000+. PS2 has no overlay path
// yet, so these three latches just store-and-readback — they exist
// so retrodesd's writes land in real registers instead of being
// silently dropped on the side-effect map (Ch221 boundary).
//
// 0x100 OSD_CTRL Ch223: 32-bit RW latch. Splash sets
// ENABLE | INPUT_LOCK | FORCE_OPEN
// here; the bridge doesn't act on
// those bits yet but the value is
// stored and readable for software
// introspection.
// 0x104 OSD_STATUS Ch224: always-zero source. Reads
// return 0; writes accepted with
// BRESP=OKAY and ignored. The FSM
// that drives [0]=active and
// [12:8]=cursor_row exists in other
// cores' bridges but PS2 has no
// overlay engine yet, so the status
// bits stay 0.
// 0x108 OSD_TRIGGER Ch224: W1C-shape sink. Reads
// return 0; writes accepted with
// BRESP=OKAY and ignored. In other
// cores the FSM SETs the action /
// back / scroll bits, retrodesd
// reads, processes, and writes-1
// to clear. PS2 has no FSM source
// today so the bits stay 0 and the
// W1C semantics collapse to a sink.
// Behavior matches the ABI v1.0
// contract — retrodesd's poll loop
// sees `trig=0` and never invokes
// `osd_action`.
// 0x10C OSD_INPUT reserved — reads 0. Sibling cores
// expose this as a "debug override"
// for injecting simulated button
// events; PS2 has no compelling use
// for it until an overlay engine
// exists.
// 0x110 OSD_CFG0 Ch223: 32-bit RW latch. cols/rows/
// x_chars/y_chars layout fields per
// input_common.h's `osd_setup`.
// 0x114 OSD_CFG1 Ch223: 32-bit RW latch. first_row/
// last_row + highlight/normal attrs.
// 0x118..0x11F reserved 32'h00000000
//
// CDC: status signals arrive on the design_clk (Ch169 IOPLL @ 25 MHz)
// domain or the CLOCK2_50 domain (HDMI I²C FSM). The bridge runs on
// clk (= CLOCK2_50, the qsys bridge clock per Ch170). Every signal
// is passed through a 2-stage synchronizer before being consumed by
// register logic.
//
// Ch174 — event-counter CDC contract: a 1-cycle pulse in design_clk
// (40 ns @ 25 MHz) is borderline against a 50 MHz 2-FF synchronizer
// and would occasionally miss frames on hardware. Counters here are
// fed by *event toggles* (`frame_toggle` / `dma_done_toggle`), which
// flip on every event in the design domain and stay at the new value
// until the next event (~16.7 ms for frames). The synchronizer has
// megacycles of slack and cannot miss an edge; the counter increments
// on each XOR between sync_d[1] and sync_d[2]. The sticky-latch
// signals `frame_seen` / `dma_done_seen` remain the *status-bit*
// sources (CORE_STATUS[3] / [2]) — do not "simplify" this back into
// raw pulse synchronizers.
//
// Write contract: the bridge accepts every AXI write addressed inside
// its 38-bit window (`s_axi_awready` rises in W_IDLE regardless of
// address) and always returns `BRESP=OKAY`. The 32-bit register slot
// is picked out of `s_axi_wdata[127:0]` by `aw_addr_q[3:2]` (the lane
// select). **`s_axi_wstrb` is intentionally ignored** — every accepted
// write is treated as a full 32-bit-word write into the targeted
// register. Partial-byte writes are not supported; the corresponding
// register field will be overwritten in full. This matches the AXI4
// usage that retroDE userspace produces (`devmem2 ... w` / native
// 32-bit stores from retrodesd's bridge mmap) and the sibling-core
// bridges, all of which also ignore WSTRB. Writes to undecoded
// offsets inside or outside the side-effect window are still ACK'd
// but have no register effect.
// ============================================================================
`timescale 1ns/1ps
module ps2_hps_bridge (
input logic clk, // qsys clk_100_clk (=CLOCK2_50)
input logic reset_n,
input logic h2f_reset, // HPS-driven fabric reset (unused; reserved)
// ---- Ch173 status inputs (asynchronous to clk; sync'd internally) ----
input logic core_halt,
input logic dma_done_seen,
input logic frame_seen,
input logic hdmi_init_done,
input logic hdmi_i2c_error,
input logic raster_overflow,
// ---- Ch174 event-toggle counter inputs (async; sync'd internally) ----
input logic frame_toggle,
input logic dma_done_toggle,
// ---- Ch318 LPDDR-framebuffer test control/status (CLOCK2_50 — SAME domain as
// the gs_lpddr_axi_master AXI side, so no synchronizers). HPS-armed runtime
// controls + status counters; defaults safe (arm=0, canary=1, base=0x80000000).
output logic lpddr_arm_o, // 0x018[0] — permit AXI writes (default 0)
output logic lpddr_canary_o, // 0x018[1] — write only the 32-byte top line (default 1)
output logic [31:0] lpddr_fb_base_o, // 0x01C — LPDDR byte base (default 0x8000_0000)
output logic lpddr_ctrl_commit_o,// Ch352 — toggles on any arm/canary/fb_base write. The EMIF
// domain syncs this and latches {arm,canary,fb_base} on its edge,
// so the multi-bit control crosses COHERENTLY (no raw fb_base bus).
input logic [31:0] lpddr_bytes_i, // 0x030 — bytes written
input logic [31:0] lpddr_bursts_i, // 0x034 — bursts issued
input logic [31:0] lpddr_bresp_err_i, // (folded into 0x02C status)
input logic [31:0] lpddr_fifo_ovf_i, // (folded into 0x02C status)
input logic lpddr_idle_i, // 0x02C[0] — writer idle/done
// ---- Ch319 Brick 3: LPDDR4B read-probe (HPS reads FB content back through the
// bridge — no /dev/mem). 0x03C: WRITE sets the byte address + triggers a read;
// READ returns the 32-bit word. rd_done/rd_data arrive on emif_clk → synced here.
output logic [31:0] lpddr_rd_addr_o, // 0x03C write — read byte address (write triggers a read)
output logic lpddr_rd_pulse_o, // toggles on each 0x03C write (the read trigger)
input logic [31:0] lpddr_rd_data_i, // read-probe result word (emif_clk; latched here)
input logic lpddr_rd_done_i, // read-probe done toggle (emif_clk; synced here)
// ---- Ch320: LPDDR4B scanout source-select + status ----
output logic lpddr_video_src_o, // 0x018[2] — 1 = HDMI sourced from LPDDR4B scanout (default 0 = BRAM)
output logic lpddr_scanout_lb_o, // 0x018[3] — 1 = line-buffer scanout, 0 = frame-cache (Ch321; default 0)
input logic lpddr_scan_valid_i, // scanout frame cache loaded (emif_clk; synced -> 0x02C[4])
input logic lpddr_scan_err_i, // scanout read errors seen (emif_clk; synced -> 0x02C[5])
// ---- Ch322: LPDDR write-probe (HPS stages texture words) + texture-cache fill ----
// 0x040 LPDDR_WRADDR (W): set the LPDDR byte address (auto-increments +4 per data write).
// 0x044 LPDDR_WRDATA (W): a 32-bit word -> latches data + toggles wr_pulse (one write).
// 0x048 TEX_FILL_CTRL (W bit0): arm the texture-cache fill. 0x048 (R): tex status.
output logic [31:0] lpddr_wr_addr_o, // 0x040 — write byte address (auto-inc per 0x044 write)
output logic [31:0] lpddr_wr_data_o, // 0x044 — write data word (stable when wr_pulse flips)
output logic lpddr_wr_pulse_o, // toggles on each 0x044 write (write trigger)
input logic lpddr_wr_busy_i, // write-probe busy (emif_clk; synced)
input logic lpddr_wr_done_i, // write-probe done toggle (emif_clk; synced)
input logic [31:0] lpddr_wr_bresp_err_i,// 0x058 — write-probe non-OKAY responses
output logic [7:0] diag_ctrl_o, // 0x0C0 — Ch323 diag: [0]wr-probe full-beat [1]spill force-lane0
// [3:2]probe-port(0=s3,1=s1,2=s2) [5:4]spill-awid(0=6,1=5,2=0)
output logic tex_fill_start_o, // 0x048[0] — arm the prefilled texture-cache fill
input logic tex_fill_done_i, // texture fully resident (emif_clk; synced -> 0x048[0])
input logic [31:0] tex_fill_beats_i, // 0x058 — fill beats completed
input logic [31:0] tex_fill_bytes_i, // 0x05C — fill bytes completed
input logic [31:0] tex_rd_errs_i, // 0x068 — texture-fill non-OKAY read responses
input logic [31:0] tex_fill_crc_i, // 0x070 — Ch352 sum32 of words written to tex_mem (integrity proof)
// Ch322 — texel-source proof counters (design_clk; reset per render). After a render,
// tex_cache_hits_i > 0 proves the triangle's texels came from the LPDDR cache.
input logic [31:0] tex_cache_hits_i, // 0x078 — texel reads served from the LPDDR cache
input logic [31:0] tex_bram_hits_i, // 0x07C — texel reads served from BRAM (fallback)
// Ch323 — tile COLOR+Z spill/reload status counters (R-only; 0 when GS_TILE_SPILL off).
// The HPS board script reads these to prove the spill/reload round-trip on silicon:
// both spill writers advance, the reload reads them back, no write/read errors.
input logic [31:0] spill_color_beats_i, // 0x080 — color flush words written to LPDDR
input logic [31:0] spill_z_beats_i, // 0x084 — Z flush words written to LPDDR
input logic [31:0] reload_color_beats_i,// 0x088 — color beats read back per reload
input logic [31:0] reload_z_beats_i, // 0x08C — Z beats read back per reload
input logic [31:0] reload_rd_errs_i, // 0x090 — reload non-OKAY read responses
input logic [31:0] spill_color_errs_i, // 0x094 — color flush non-OKAY write responses
input logic [31:0] spill_z_errs_i, // 0x098 — Z flush non-OKAY write responses
input logic spill_color_ovf_i, // 0x09C[0] — color spill FIFO overflow (sticky)
input logic spill_z_ovf_i, // 0x09C[1] — Z spill FIFO overflow (sticky)
// Ch323 diag — upstream tile-spill/reload event counters (reset per-render; 0 off-spill).
input logic [31:0] ev_tp_flush_i, // 0x0A0 — entries into TP_FLUSH
input logic [31:0] ev_tp_zflush_i, // 0x0A4 — entries into TP_ZFLUSH
input logic [31:0] ev_tp_reload_i, // 0x0A8 — entries into TP_RELOAD
input logic [31:0] ev_tp_render_i, // 0x0AC — entries into TP_RENDER
input logic [31:0] ev_flush_emit_i, // 0x0B0 — color flush emit pulses
input logic [31:0] ev_zflush_emit_i, // 0x0B4 — Z flush emit pulses
input logic [31:0] ev_reload_start_i, // 0x0B8 — reload-arm pulses
input logic [31:0] ev_reload_ready_i, // 0x0BC — reload-ready rising edges
// Color-writer spill PIPELINE counters (localize any spill divergence): beats=B handshakes,
// emit/push=GS side, pop=EMIF side. Healthy two-batch render = 512/64/64/64.
input logic [31:0] dbg_c_beat_count_i, // 0x0E0 — beats committed since trace_clear
input logic [31:0] dbg_c_emit_count_i, // 0x0CC — GS: emits accepted (expect 512)
input logic [31:0] dbg_c_push_count_i, // 0x0F8 — GS: beats pushed to FIFO (expect 64)
input logic [31:0] dbg_c_pop_count_i, // 0x0FC — EMIF: beats popped from FIFO (expect == beats)
// Ch324 Z-writer pipeline counters (same split): compare against color in one run to localize
// the grid spill_z over-production. emit too high=TP_ZFLUSH/iteration; push high=packer; pop/beats high=FIFO/CDC.
input logic [31:0] dbg_z_beat_count_i, // 0x0C4 — Z beats committed since trace_clear
input logic [31:0] dbg_z_emit_count_i, // 0x0C8 — Z GS emits accepted
input logic [31:0] dbg_z_push_count_i, // 0x0D0 — Z GS beats pushed to FIFO
input logic [31:0] dbg_z_pop_count_i, // 0x0D4 — Z EMIF beats popped from FIFO
// ---- Ch330 Brick 4 — runtime command-list feeder control ----
// The HPS writes a staging RAM entry as two 32-bit halves (0x0DC LO, 0x0E4 HI);
// writing HI commits {hi,lo} -> staging[addr] and auto-increments addr. 0x0E8 GO
// retriggers. The we/go outputs are TOGGLES (the feeder lives in design_clk; the
// de25 top syncs+edge-detects them into pulses) with stable level addr/data.
output logic feeder_stg_we_tgl_o, // toggles on each staging commit (HI write)
output logic [11:0] feeder_stg_waddr_o, // staging word index for the committed word (level)
output logic [63:0] feeder_stg_wdata_o, // {hi,lo} committed word (level)
output logic feeder_go_tgl_o, // toggles on each 0x0E8 GO write (bit0=1)
input logic feeder_ready_i, // 0x0D8[0] feeder control FSM in C_READY (async; synced)
input logic [15:0] feeder_records_i, // 0x0E4 records_emitted (async counter)
input logic [31:0] feeder_waits_i, // 0x0E8 fifo_wait_cycles (async counter)
// ---- AXI4 slave (signature matches ps2_hps_bridge_null) ----
input logic [3:0] s_axi_awid,
input logic [37:0] s_axi_awaddr,
input logic [7:0] s_axi_awlen,
input logic [2:0] s_axi_awsize,
input logic [1:0] s_axi_awburst,
input logic s_axi_awlock,
input logic [3:0] s_axi_awcache,
input logic [2:0] s_axi_awprot,
input logic s_axi_awvalid,
output logic s_axi_awready,
input logic [127:0] s_axi_wdata,
input logic [15:0] s_axi_wstrb,
input logic s_axi_wlast,
input logic s_axi_wvalid,
output logic s_axi_wready,
output logic [3:0] s_axi_bid,
output logic [1:0] s_axi_bresp,
output logic s_axi_bvalid,
input logic s_axi_bready,
input logic [3:0] s_axi_arid,
input logic [37:0] s_axi_araddr,
input logic [7:0] s_axi_arlen,
input logic [2:0] s_axi_arsize,
input logic [1:0] s_axi_arburst,
input logic s_axi_arlock,
input logic [3:0] s_axi_arcache,
input logic [2:0] s_axi_arprot,
input logic s_axi_arvalid,
output logic s_axi_arready,
output logic [3:0] s_axi_rid,
output logic [127:0] s_axi_rdata,
output logic [1:0] s_axi_rresp,
output logic s_axi_rlast,
output logic s_axi_rvalid,
input logic s_axi_rready,
// ---- Ch176 outputs ---------------------------------------------------
// core_reset_req: latched CORE_CTRL[0]. Active-high. Asynchronous to
// the design domain — the board top is responsible for 2-FF syncing
// this into design_clk and gating the design's reset.
output logic core_reset_req,
// ---- Ch229 tile write-event broadcast --------------------------------
// On every accepted AXI write into the Ch227 tile RAM window, latch
// the index + data and toggle a 1-bit "event" signal. A design-clock
// CDC (`tile_ram_cdc`) detects edges on the synchronized toggle to
// gate writes into its shadow RAM. The bridge still owns the
// HPS-side `tile_mem` for AXI readback — Ch229 only adds the
// broadcast outputs; it does not change the Ch227 storage path.
// Toggle starts at 0 after reset (matched on the receiver side, so
// no spurious post-reset edge if both domains reset together).
output logic tile_wr_toggle, // toggles per tile write
output logic [9:0] tile_wr_index, // word index (= addr[11:2])
output logic [31:0] tile_wr_data, // 32-bit word data
// ---- Ch235 input latch broadcast (bridge clock domain) ---------------
// Ch222 `INPUT_P1`/`INPUT_P2` latches (@ 0x040/0x044) surfaced for
// downstream PS2-fabric consumers. The Ch234 `sio2_input_stub`
// (instantiated inside `iop_memory_map_stub`) is the first
// consumer; it owns the bridge-clk → design-clk CDC via its
// internal 2-FF synchronizer, so this port is plain combinational
// out of the bridge — no extra sync stage here.
output logic [31:0] input_p1_o, // = input_p1_q
output logic [31:0] input_p2_o, // = input_p2_q
// Ch245 — INPUT_P1_RAW (un-remapped retroDE bitmap) surfaced for
// the platform osd_menu_fsm. retrodesd may per-game-remap
// INPUT_P1 (A↔B swap etc.) — the OSD navigation uses the raw
// bitmap directly so D-pad / Start / Select / A / B map to the
// FSM's expected bit positions regardless of per-game remaps.
// Matches NES at retroDE_nes.sv:1235 (".joy0_buttons(bridge_joy0_osd)").
output logic [31:0] input_p1_raw_o, // = input_p1_raw_q
// ---- Ch245 platform OSD register surface -----------------------------
// Mirror sibling-ABI bridge ports so the shared
// retroDE_splash osd_overlay + osd_menu_fsm slot in with no
// PS2-local cell-format assumptions. The whole 32-bit register
// value is exposed; the top extracts fields per the sibling
// layout (cols=bits[5:0], rows=bits[12:8], origin_x_chars=
// bits[23:16], origin_y_chars=bits[31:24]).
output logic [31:0] osd_ctrl_o, // = osd_ctrl_q
output logic [31:0] osd_cfg0_o, // = osd_cfg0_q
output logic [31:0] osd_cfg1_o, // = osd_cfg1_q
// Menu-FSM outputs (sys_clk = CLOCK2_50 domain). Compose
// OSD_STATUS read + OSD_TRIGGER set-bits.
input logic osd_active_i,
input logic [4:0] osd_cursor_row_i,
input logic osd_set_trigger_i, // A button (one-cycle pulse)
input logic osd_back_trigger_i, // B button (one-cycle pulse)
input logic osd_scroll_down_trigger_i,// cursor down past end
input logic osd_scroll_up_trigger_i, // cursor up past start
input logic osd_open_trigger_i, // OSD just became visible
input logic [4:0] osd_trigger_row_i, // active row at trigger
// ---- Ch248 DS2 wired-controller inputs (from the platform
// `ds2_controller` instantiated in the top, polling the
// DE25-Nano GPIO header at the PSX SPI-like protocol rate).
// These replace the Ch226 hardcoded stub that always reported
// "no controller plugged in." Same clock domain as the
// bridge (both ride sys_clk = CLOCK2_50), so no synchronizer
// needed at this end.
input logic [31:0] ds2_buttons_i,
input logic ds2_connected_i,
input logic ds2_error_i
);
// ----------------------------------------------------------------
// 2-stage synchronizers for the async status signals.
// ----------------------------------------------------------------
logic [1:0] core_halt_sync;
logic [1:0] dma_done_sync;
logic [1:0] frame_seen_sync;
logic [1:0] hdmi_init_sync;
logic [1:0] hdmi_err_sync;
logic [1:0] raster_of_sync;
logic [1:0] frame_tgl_sync; // Ch174
logic [1:0] dma_tgl_sync; // Ch174
always_ff @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
core_halt_sync <= 2'b00;
dma_done_sync <= 2'b00;
frame_seen_sync <= 2'b00;
hdmi_init_sync <= 2'b00;
hdmi_err_sync <= 2'b00;
raster_of_sync <= 2'b00;
frame_tgl_sync <= 2'b00;
dma_tgl_sync <= 2'b00;
end else begin
core_halt_sync <= {core_halt_sync[0], core_halt};
dma_done_sync <= {dma_done_sync[0], dma_done_seen};
frame_seen_sync <= {frame_seen_sync[0], frame_seen};
hdmi_init_sync <= {hdmi_init_sync[0], hdmi_init_done};
hdmi_err_sync <= {hdmi_err_sync[0], hdmi_i2c_error};
raster_of_sync <= {raster_of_sync[0], raster_overflow};
frame_tgl_sync <= {frame_tgl_sync[0], frame_toggle};
dma_tgl_sync <= {dma_tgl_sync[0], dma_done_toggle};
end
end
wire core_halt_q = core_halt_sync[1];
wire dma_done_q = dma_done_sync[1];
wire frame_seen_q = frame_seen_sync[1];
wire hdmi_init_q = hdmi_init_sync[1];
wire hdmi_err_q = hdmi_err_sync[1];
wire raster_of_q = raster_of_sync[1];
wire frame_tgl_q = frame_tgl_sync[1];
wire dma_tgl_q = dma_tgl_sync[1];
// ----------------------------------------------------------------
// Ch176 — writable control register at 0x010.
// bit 0: RESET (functional, drives core_reset_req)
// bit 1: ROM_LOADED (latch only)
// bit 2: PAUSE (latch only)
// bits[31:3]: writable but ignored (readback as 0 — only the
// three ABI-defined bits survive a write/read
// round-trip).
// Write path: the write FSM signals `core_ctrl_we` for one clk
// cycle when AXI accepts a write addressed to 0x010 and pulls
// the new value out of the AXI wstrb-aligned lane.
// ----------------------------------------------------------------
logic [2:0] core_ctrl_q;
logic core_ctrl_we;
logic [2:0] core_ctrl_d;
always_ff @(posedge clk or negedge reset_n) begin
if (!reset_n)
core_ctrl_q <= 3'b000;
else if (core_ctrl_we)
core_ctrl_q <= core_ctrl_d;
end
assign core_reset_req = core_ctrl_q[0];
// ----------------------------------------------------------------
// Ch176 — CORE_PULSE write decode. Self-clearing pulses live in
// CLOCK2_50 (bridge clock) only; HDMI_CLR (bit 3) zeros the
// diagnostic counters. Other bits ACK and discard.
// ----------------------------------------------------------------
logic core_pulse_we;
logic hdmi_clr_pulse;
// ----------------------------------------------------------------
// Ch222 — INPUT_P1 / INPUT_P2 / INPUT_P1_RAW write/read latches
// at 0x040 / 0x044 / 0x048. HPS-visible state only — retrodesd's
// `input_thread.c` writes the active controller bitmap here and
// the supervisor menu reads INPUT_P1_RAW. PS2 has no SIO2 /
// DualShock pipeline yet, so the values are stored and read back,
// nothing else. Reset clears unconditionally. Matches the shared
// ABI v1.0 input block used by retroDE_{nes,gb,Atari2600,coco2}.
// ----------------------------------------------------------------
logic [31:0] input_p1_q;
logic [31:0] input_p2_q;
logic [31:0] input_p1_raw_q;
// ----------------------------------------------------------------
// Ch223 — OSD compatibility sink at 0x100 / 0x110 / 0x114. Same
// "store-and-readback" pattern as Ch222: retrodesd's splash
// backend writes these latches during `osd_setup`; the bridge
// doesn't render an overlay yet, but the writes now land in real
// registers instead of being silently dropped. **Nothing in the
// PS2 fabric consumes these bits** — readback returns whatever
// userspace wrote, not a confirmation that an overlay engine
// observed it. Reset clears unconditionally.
// ----------------------------------------------------------------
logic [31:0] osd_ctrl_q;
// Ch318 — LPDDR test control registers (declared here; reset/write below).
logic lpddr_arm_q;
logic lpddr_canary_q;
logic [31:0] lpddr_fb_base_q;
// Ch319 Brick 3 — LPDDR4B read-probe registers + return-path CDC.
logic [31:0] lpddr_rd_addr_q;
logic lpddr_rd_pulse_q;
logic [1:0] lpddr_rd_done_sync;
logic [31:0] lpddr_rd_data_lat;
wire lpddr_rd_pending = (lpddr_rd_pulse_q != lpddr_rd_done_sync[1]);
assign lpddr_rd_addr_o = lpddr_rd_addr_q;
assign lpddr_rd_pulse_o = lpddr_rd_pulse_q;
// Ch323 diag — write-path commit experiment control (0x0C0).
logic [7:0] diag_ctrl_q;
assign diag_ctrl_o = diag_ctrl_q;
// Ch320 — scanout source-select + synced status.
logic lpddr_video_src_q;
logic lpddr_scanout_lb_q;
logic [1:0] lpddr_scan_valid_sync, lpddr_scan_err_sync;
assign lpddr_video_src_o = lpddr_video_src_q;
assign lpddr_scanout_lb_o = lpddr_scanout_lb_q;
// Ch322 — LPDDR write-probe + texture-cache fill registers + return-path CDC.
logic [31:0] lpddr_wr_addr_q; // "next write" pointer (auto-increments)
logic [31:0] lpddr_wr_addr_present_q;// the address that goes WITH the current data word
logic [31:0] lpddr_wr_data_q;
logic lpddr_wr_pulse_q;
logic tex_fill_start_q;
logic [1:0] tex_fill_done_sync;
logic [1:0] lpddr_wr_busy_sync;
// Ch352 CDC hardening (Codex) — the EMIF-domain status no longer crosses raw into reg_read:
// * lpddr_wr_done_sync : 3-FF sync of the write-probe done TOGGLE -> a STABLE write-pending bit
// (write_pending = issued_pulse ^ done_synced). Stop polling the transient wr_busy level.
// * tex_fill_*_snap_q : the multi-bit fill counters latched into THIS (clk) domain on the synced
// fill_done rising edge — they are stable then (fill complete), so the sample is coherent. reg_read
// returns these snapshots, never the raw emif counters.
logic [2:0] lpddr_wr_done_sync;
logic [31:0] tex_fill_beats_snap_q;
logic [31:0] tex_fill_bytes_snap_q;
logic [31:0] tex_rd_errs_snap_q;
logic [31:0] tex_fill_crc_snap_q;
logic lpddr_ctrl_commit_q; // toggles on any arm/canary/fb_base write (-> EMIF control snapshot)
wire write_pending = lpddr_wr_pulse_q ^ lpddr_wr_done_sync[2];
assign lpddr_ctrl_commit_o = lpddr_ctrl_commit_q;
// Ch352 (Codex) — the remaining raw emif->bridge status signals. lpddr_idle is single-bit (2-FF sync).
// The FB-writer counters (bytes/bursts/bresp_err/fifo_ovf) are multi-bit and only stable while the writer
// is IDLE, so latch them into clk-domain snapshots WHILE the synced idle is high (data stable -> coherent).
// The write-probe error counter latches on the synced write-done edge (count stable when a write completes).
logic [1:0] lpddr_idle_sync;
logic [31:0] lpddr_bytes_snap_q;
logic [31:0] lpddr_bursts_snap_q;
logic [31:0] lpddr_bresp_err_snap_q;
logic [31:0] lpddr_fifo_ovf_snap_q;
logic [31:0] lpddr_wr_bresp_err_snap_q;
// Present the SNAPSHOT taken when the data word was written — NOT lpddr_wr_addr_q,
// which auto-increments the same cycle the pulse toggles. The write-probe samples
// wr_addr asynchronously across the CDC, so it must stay stable between data writes
// (like wr_data). Presenting the live (incrementing) addr made every texel land +4B
// past its slot (Ch322 board bug).
assign lpddr_wr_addr_o = lpddr_wr_addr_present_q;
assign lpddr_wr_data_o = lpddr_wr_data_q;
assign lpddr_wr_pulse_o = lpddr_wr_pulse_q;
assign tex_fill_start_o = tex_fill_start_q;
logic [31:0] osd_cfg0_q;
logic [31:0] osd_cfg1_q;
// Ch245 — OSD_TRIGGER is a R/W register: HPS clears bits with W1C,
// the menu FSM (sys_clk domain) sets bits on action edges.
logic [31:0] osd_trigger_q;
// ----------------------------------------------------------------
// Ch227 — tile RAM storage at 0x1000..0x1FFF (4 KB window).
// 1024 × 32-bit words; HPS writes land here, reads return the
// last value written. No overlay engine yet (Ch228). The sim
// initial block zero-fills for deterministic TBs; on hardware
// the power-up value is undefined until the overlay chapter
// pins a reset contract (BRAM blocks on Agilex 5 don't typically
// sync-reset every word, and there is no sibling precedent for
// bridge-side tile-RAM clearing).
//
// Ch232 hotfix: explicit `ramstyle = "M20K"` attribute forces
// Quartus to put the 32 Kbit storage into a single M20K block
// instead of distributed LABs. Without the hint the AXI read
// path (combinational lookup via a function call) confused the
// inferencer.
// ----------------------------------------------------------------
(* ramstyle = "M20K" *) logic [31:0] tile_mem [0:1023];
initial begin
for (int i = 0; i < 1024; i++)
tile_mem[i] = 32'd0;
end
// ----------------------------------------------------------------
// Counters. Ch174: FRAME_COUNT / DMA_DONE_COUNT increment on each
// edge (rising OR falling) of the synchronized event-toggle —
// because the toggle flips per event in the design domain, every
// edge in the bridge domain is exactly one event. See the
// event-counter CDC contract in the header comment.
//
// RASTER_OVERFLOW_COUNT counts every clk cycle the synced
// raster_overflow stays high (cheap stickyness + approximate
// severity in one register).
//
// Ch176: a CORE_PULSE write with HDMI_CLR set takes priority over
// the increment in the same cycle and zeros all three counters
// synchronously in CLOCK2_50.
// ----------------------------------------------------------------
logic frame_tgl_q1;
logic dma_tgl_q1;
logic [31:0] frame_count;
logic [31:0] dma_done_count;
logic [31:0] raster_overflow_count;
always_ff @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
frame_tgl_q1 <= 1'b0;
dma_tgl_q1 <= 1'b0;
frame_count <= 32'd0;
dma_done_count <= 32'd0;
raster_overflow_count <= 32'd0;
end else begin
frame_tgl_q1 <= frame_tgl_q;
dma_tgl_q1 <= dma_tgl_q;
if (hdmi_clr_pulse) begin
frame_count <= 32'd0;
dma_done_count <= 32'd0;
raster_overflow_count <= 32'd0;
end else begin
if (frame_tgl_q ^ frame_tgl_q1)
frame_count <= frame_count + 32'd1;
if (dma_tgl_q ^ dma_tgl_q1)
dma_done_count <= dma_done_count + 32'd1;
if (raster_of_q)
raster_overflow_count <= raster_overflow_count + 32'd1;
end
end
end
// ----------------------------------------------------------------
// Register-window decode (read side). Returns the 32-bit register
// value for the given byte address; out-of-range reads return 0.
// ----------------------------------------------------------------
localparam logic [31:0] CORE_ID = 32'h50533200; // "PS2\0"
localparam logic [31:0] ABI_VERSION = 32'h00000100;
// Ch246 — advertise OSD geometry via CORE_CAPS following the
// sibling-ABI bit layout (matches nes_hps_bridge.sv:1024-1031).
// bit 0 has_save_ram = 0 (PS2 memory card not yet wired)
// bit 1 has_savestates = 0 (not yet implemented)
// bit 2 two_player = 0 (P2 input wiring is bringup-only)
// bit 3 analog_input = 0 (DS2 analog sticks not yet plumbed)
// [7:4] max_savestate_slots = 0
// [15:8] osd_columns = 40
// [20:16] osd_rows = 16
// = (16<<16) | (40<<8) = 0x00102800
//
// The runtime fix for PS2's current 640×480 picture lives in
// retrodesd's backend (it still computes origin from a hardcoded
// 1280×720 assumption, which clips on us). This advertisement
// is forward-looking metadata so a CORE_CAPS-aware retrodesd can
// use it directly; Ch246's hardware-side change is intentionally
// minimal per Codex's framing.
localparam logic [31:0] CORE_CAPS = 32'h00102800;
function automatic logic [31:0] reg_read(input logic [37:0] addr);
logic [31:0] core_status;
logic [31:0] video_status;
logic [31:0] hdmi_diag;
core_status = '0;
core_status[0] = 1'b1; // loaded
core_status[1] = core_halt_q;
core_status[2] = dma_done_q;
core_status[3] = frame_seen_q;
core_status[4] = hdmi_init_q;
core_status[5] = hdmi_err_q;
core_status[6] = raster_of_q;
// Ch225 — diagnostic views into the same synchronized signals.
video_status = '0;
video_status[0] = frame_seen_q;
video_status[1] = (frame_count != 32'd0); // scanout_alive
video_status[2] = raster_of_q;
video_status[3] = dma_done_q;
hdmi_diag = '0;
hdmi_diag[0] = hdmi_init_q;
hdmi_diag[1] = hdmi_err_q;
// Decode is split into two windows:
// 1. 0x000..0x0FF — shared-ABI prefix (0x000-0x01F), Ch174
// diagnostics (0x020-0x02F), Ch222 input latches
// (0x040-0x048), Ch225 diagnostic reads (0x060/0x064),
// Ch226 DS2 stub (0x0F0/0x0F4). Selected by
// addr[37:8]=='0 (Ch226 widened the window from 128 B
// → 256 B; existing slot indices keep their values
// since the high bit is implicitly 0 for offsets ≤ 0x07F).
// 2. 0x100..0x11F — Ch223 OSD compatibility sink. Selected
// by addr[37:5]==33'h08 (= 0x100/32).
// Anything else reads 0.
reg_read = 32'd0;
if (addr[37:8] == '0) begin
case (addr[7:2])
6'h00: reg_read = CORE_ID; // 0x000
6'h01: reg_read = ABI_VERSION; // 0x004
6'h02: reg_read = core_status; // 0x008
6'h03: reg_read = CORE_CAPS; // 0x00C
6'h04: reg_read = {29'd0, core_ctrl_q}; // 0x010 CORE_CTRL (Ch176)
6'h05: reg_read = 32'd0; // 0x014 CORE_PULSE — self-clearing, reads 0
6'h06: reg_read = {28'd0, lpddr_scanout_lb_q, lpddr_video_src_q, lpddr_canary_q, lpddr_arm_q}; // 0x018 LPDDR_CTRL: [0]arm [1]canary [2]video_src [3]scanout_lb (Ch318/320/321 RW)
6'h07: reg_read = lpddr_fb_base_q; // 0x01C LPDDR_FB_BASE (Ch318 RW)
6'h08: reg_read = frame_count; // 0x020
6'h09: reg_read = dma_done_count; // 0x024
6'h0A: reg_read = raster_overflow_count; // 0x028
// 0x02C LPDDR_STATUS (R): [0]idle [1]bresp_err [2]fifo_ovf [3]rd_pending [4]scan_cache_valid [5]scan_rd_err.
// Ch352 — idle via 2-FF sync; bresp_err/fifo_ovf reduced from the clk-domain snapshots (coherent).
6'h0B: reg_read = {26'd0, lpddr_scan_err_sync[1], lpddr_scan_valid_sync[1], lpddr_rd_pending, (|lpddr_fifo_ovf_snap_q), (|lpddr_bresp_err_snap_q), lpddr_idle_sync[1]};
6'h0C: reg_read = lpddr_bytes_snap_q; // 0x030 LPDDR_BYTES (Ch352 — clk-domain snapshot)
6'h0D: reg_read = lpddr_bursts_snap_q; // 0x034 LPDDR_BURSTS (Ch352 — clk-domain snapshot)
6'h0E: reg_read = lpddr_bresp_err_snap_q; // 0x038 LPDDR_BRESP_ERRS (Ch352 — clk-domain snapshot)
6'h0F: reg_read = lpddr_rd_data_lat; // 0x03C LPDDR_RDATA (Ch319 R): last read-probe word. WRITE this offset to set addr + trigger a read.
6'h10: reg_read = input_p1_q; // 0x040 INPUT_P1 (Ch222)
6'h11: reg_read = input_p2_q; // 0x044 INPUT_P2 (Ch222)
6'h12: reg_read = input_p1_raw_q; // 0x048 INPUT_P1_RAW (Ch222)
6'h13: reg_read = lpddr_wr_addr_q; // 0x04C LPDDR_WRADDR (Ch322 RW; auto-inc on 0x050 write)
6'h14: reg_read = 32'd0; // 0x050 LPDDR_WRDATA (Ch322 W-only: data+pulse; read 0)
// 0x054 TEX_FILL_CTRL(W[0]) / TEX_STATUS(R): [0]fill_done [1]wr_busy [2]write_pending (Ch352 — the
// STABLE write-done flag; poll [2]==0 between writes instead of catching the transient [1]wr_busy).
6'h15: reg_read = {29'd0, write_pending, lpddr_wr_busy_sync[1], tex_fill_done_sync[1]};
6'h16: reg_read = tex_fill_beats_snap_q; // 0x058 TEX_FILL_BEATS (Ch352 — clk-domain snapshot)
6'h17: reg_read = tex_fill_bytes_snap_q; // 0x05C TEX_FILL_BYTES (Ch352 — clk-domain snapshot)
6'h1A: reg_read = tex_rd_errs_snap_q; // 0x068 TEX_RD_ERRS (Ch352 — clk-domain snapshot)
6'h1B: reg_read = lpddr_wr_bresp_err_snap_q;// 0x06C LPDDR_WR_BRESP_ERRS (Ch352 — clk-domain snapshot)
6'h1C: reg_read = tex_fill_crc_snap_q; // 0x070 TEX_FILL_CRC (Ch352 — sum32 of tex_mem words; integrity proof)
6'h1E: reg_read = tex_cache_hits_i; // 0x078 TEX_CACHE_HITS (Ch322 R): texels served from LPDDR cache
6'h1F: reg_read = tex_bram_hits_i; // 0x07C TEX_BRAM_HITS (Ch322 R): texels served from BRAM
// Ch323 — tile COLOR+Z spill/reload counters (all R; 0 when GS_TILE_SPILL off).
6'h20: reg_read = spill_color_beats_i; // 0x080 SPILL_COLOR_WR_BEATS: color flush words → LPDDR
6'h21: reg_read = spill_z_beats_i; // 0x084 SPILL_Z_WR_BEATS: Z flush words → LPDDR
6'h22: reg_read = reload_color_beats_i; // 0x088 RELOAD_COLOR_BEATS: color beats read back per reload
6'h23: reg_read = reload_z_beats_i; // 0x08C RELOAD_Z_BEATS: Z beats read back per reload
6'h24: reg_read = reload_rd_errs_i; // 0x090 RELOAD_RD_ERRS: reload non-OKAY read responses
6'h25: reg_read = spill_color_errs_i; // 0x094 SPILL_COLOR_WR_ERRS: color flush non-OKAY write responses
6'h26: reg_read = spill_z_errs_i; // 0x098 SPILL_Z_WR_ERRS: Z flush non-OKAY write responses
6'h27: reg_read = {30'd0, spill_z_ovf_i, spill_color_ovf_i}; // 0x09C SPILL_OVF: [0]color FIFO ovf [1]Z FIFO ovf
// Ch323 diag — UPSTREAM tile-spill/reload EVENT counters (reset per-render). Prove
// the rasterizer entered the phases + emitted BEFORE diagnosing LPDDR commit.
6'h28: reg_read = ev_tp_flush_i; // 0x0A0 EV_TP_FLUSH : entries into TP_FLUSH
6'h29: reg_read = ev_tp_zflush_i; // 0x0A4 EV_TP_ZFLUSH : entries into TP_ZFLUSH
6'h2A: reg_read = ev_tp_reload_i; // 0x0A8 EV_TP_RELOAD : entries into TP_RELOAD
6'h2B: reg_read = ev_tp_render_i; // 0x0AC EV_TP_RENDER : entries into TP_RENDER
6'h2C: reg_read = ev_flush_emit_i; // 0x0B0 EV_FLUSH_EMIT : color flush emit pulses
6'h2D: reg_read = ev_zflush_emit_i; // 0x0B4 EV_ZFLUSH_EMIT: Z flush emit pulses
6'h2E: reg_read = ev_reload_start_i; // 0x0B8 EV_RELOAD_START: reload-arm pulses
6'h2F: reg_read = ev_reload_ready_i; // 0x0BC EV_RELOAD_READY: reload-ready rising edges
6'h30: reg_read = {24'd0, diag_ctrl_q}; // 0x0C0 DIAG_CTRL (RW): [6]=trace_clear (per-render counter reset)
6'h31: reg_read = dbg_z_beat_count_i; // 0x0C4 — Z writer beats since trace_clear
6'h32: reg_read = dbg_z_emit_count_i; // 0x0C8 — Z writer GS emits accepted
6'h33: reg_read = dbg_c_emit_count_i; // 0x0CC — color writer GS emits accepted (expect 512)
6'h34: reg_read = dbg_z_push_count_i; // 0x0D0 — Z writer GS beats pushed to FIFO
6'h35: reg_read = dbg_z_pop_count_i; // 0x0D4 — Z writer EMIF beats popped from FIFO
6'h38: reg_read = dbg_c_beat_count_i; // 0x0E0 — color writer beats since trace_clear
// Ch330 Brick 4 — feeder status reads.
6'h36: reg_read = {31'd0, feeder_ready_sync[1]}; // 0x0D8 FEEDER_STATUS [0]=ready (synced)
6'h37: reg_read = {20'd0, feeder_addr_q}; // 0x0DC FEEDER_STG_ADDR (current write index)
6'h39: reg_read = {16'd0, feeder_records_i}; // 0x0E4 FEEDER_RECORDS (records_emitted)
6'h3A: reg_read = feeder_waits_i; // 0x0E8 FEEDER_WAITS (fifo_wait_cycles)
6'h18: reg_read = video_status; // 0x060 VIDEO_STATUS (Ch225)
6'h19: reg_read = hdmi_diag; // 0x064 HDMI_DIAG (Ch225)
// Ch248 — replaced the Ch226 fake DS2 with the real
// platform `ds2_controller` outputs. Bit layout matches
// the sibling-ABI shape that retrodesd's
// `ds2_poll_thread` consumes:
// [0] connected (1 = wired controller present + IDed)
// [1] error (1 = comms timeout / bad framing)
// [2] reserved (PS2-local "input_latches_valid" bit
// from the Ch226 stub kept at 1 so
// operators-of-prior-tooling still
// see a familiar pattern when the
// controller is unplugged)
6'h3C: reg_read = {29'd0, 1'b1, ds2_error_i, ds2_connected_i}; // 0x0F0 DS2_STATUS
6'h3D: reg_read = ds2_buttons_i; // 0x0F4 DS2_BUTTONS
6'h3E: reg_read = dbg_c_push_count_i; // 0x0F8 — color writer GS beats pushed to FIFO (expect 64)
6'h3F: reg_read = dbg_c_pop_count_i; // 0x0FC — color writer EMIF beats popped (expect == beats)
default: reg_read = 32'd0;
endcase
end else if (addr[37:5] == 33'h08) begin
case (addr[4:2])
3'h0: reg_read = osd_ctrl_q; // 0x100 OSD_CTRL
// Ch245 — OSD_STATUS in sibling-ABI layout:
// bit 0 = osd_active (from menu FSM)
// bits 12:8 = cursor_row (from menu FSM)
// sys_clk → bridge_clk are the SAME clock here
// (both run from CLOCK2_50), so no synchronizer needed.
3'h1: reg_read = {19'd0, osd_cursor_row_i, 7'd0, osd_active_i};
3'h2: reg_read = osd_trigger_q; // 0x108 OSD_TRIGGER (Ch245 R/W)
3'h3: reg_read = 32'd0; // 0x10C OSD_INPUT (reserved)
3'h4: reg_read = osd_cfg0_q; // 0x110 OSD_CFG0
3'h5: reg_read = osd_cfg1_q; // 0x114 OSD_CFG1
default: reg_read = 32'd0; // 0x118 / 0x11C reserved-zero
endcase
end else if (addr[37:12] == 26'h1) begin
// Ch227 — tile RAM 0x1000..0x1FFF, 1024 × 32-bit.
reg_read = tile_mem[addr[11:2]];
end
endfunction
// ----------------------------------------------------------------
// Write FSM. Single-beat: accept aw + w together, emit b, return.
// Ch176: writes to 0x010 / 0x014 now take effect (CORE_CTRL latch
// + CORE_PULSE self-clearing). Writes to any other address are
// ACK'd-and-discarded (the read-only identity / counter window).
// ----------------------------------------------------------------
typedef enum logic [1:0] { W_IDLE, W_DATA, W_RESP } w_state_t;
w_state_t w_state;
logic [3:0] aw_id_q;
logic [37:0] aw_addr_q;
// Pull the 32-bit register value out of the 128-bit AXI wdata via
// the awaddr[3:2] lane select (matches the read-side encoding).
logic [31:0] wdata_lane;
always_comb begin
case (aw_addr_q[3:2])
2'b00: wdata_lane = s_axi_wdata[31:0];
2'b01: wdata_lane = s_axi_wdata[63:32];
2'b10: wdata_lane = s_axi_wdata[95:64];
default: wdata_lane = s_axi_wdata[127:96];
endcase
end
// Address decode for write side. Same 6-bit word index inside the
// first 256 bytes (Ch226 widened from 128 B) that the read side
// uses. DS2 stub at 0x0F0/0x0F4 is read-only — no write_is_*
// signals; writes there fall through to the no-side-effect path.
wire write_in_window = (aw_addr_q[37:8] == '0);
wire write_is_ctrl = write_in_window && (aw_addr_q[7:2] == 6'h04);
wire write_is_pulse = write_in_window && (aw_addr_q[7:2] == 6'h05);
// Ch222 — input-latch decode (offsets 0x040 / 0x044 / 0x048).
wire write_is_p1 = write_in_window && (aw_addr_q[7:2] == 6'h10);
wire write_is_p2 = write_in_window && (aw_addr_q[7:2] == 6'h11);
wire write_is_p1_raw = write_in_window && (aw_addr_q[7:2] == 6'h12);
// Ch318 — LPDDR test control writes (0x018 LPDDR_CTRL, 0x01C LPDDR_FB_BASE).
wire write_is_lpddr_ctrl = write_in_window && (aw_addr_q[7:2] == 6'h06);
wire write_is_lpddr_base = write_in_window && (aw_addr_q[7:2] == 6'h07);
// Ch319 Brick 3 — LPDDR4B read-probe address write (0x03C); the write also triggers a read.
wire write_is_lpddr_rdaddr = write_in_window && (aw_addr_q[7:2] == 6'h0F);
// Ch323 diag — write-commit experiment control (0x0C0 = 6'h30).
wire write_is_diag_ctrl = write_in_window && (aw_addr_q[7:2] == 6'h30);
// Ch322 — LPDDR write-probe + texture-cache fill writes.
wire write_is_lpddr_wraddr = write_in_window && (aw_addr_q[7:2] == 6'h13); // 0x04C set write addr
wire write_is_lpddr_wrdata = write_in_window && (aw_addr_q[7:2] == 6'h14); // 0x050 data -> write + pulse
wire write_is_tex_fill = write_in_window && (aw_addr_q[7:2] == 6'h15); // 0x054 bit0 arm fill
// Ch330 Brick 4 — feeder staging/control writes (0x0D8/0x0DC/0x0E4/0x0E8).
wire write_is_feeder_addr = write_in_window && (aw_addr_q[7:2] == 6'h36); // 0x0D8 set staging word index
wire write_is_feeder_lo = write_in_window && (aw_addr_q[7:2] == 6'h37); // 0x0DC low 32 bits (latched)
wire write_is_feeder_hi = write_in_window && (aw_addr_q[7:2] == 6'h39); // 0x0E4 high 32 -> commit {hi,lo}, addr++
wire write_is_feeder_go = write_in_window && (aw_addr_q[7:2] == 6'h3A); // 0x0E8 bit0 -> retrigger pulse
// Ch223 — OSD compatibility-sink decode at 0x100..0x11F. The OSD
// block is a 32-byte window outside the first-128-byte side-effect
// region, so it gets its own `addr[37:5] == 33'h08` guard
// (addr[37:5]==0x08 ⇔ addr ∈ [0x100, 0x120)).
wire write_in_osd = (aw_addr_q[37:5] == 33'h08);
wire write_is_osd_ctrl = write_in_osd && (aw_addr_q[4:2] == 3'h0); // 0x100
wire write_is_osd_trigger= write_in_osd && (aw_addr_q[4:2] == 3'h2); // 0x108 (Ch245 W1C)
wire write_is_osd_cfg0 = write_in_osd && (aw_addr_q[4:2] == 3'h4); // 0x110
wire write_is_osd_cfg1 = write_in_osd && (aw_addr_q[4:2] == 3'h5); // 0x114
// Ch227 — tile RAM write decode at 0x1000..0x1FFF (4 KB window).
// 1024 × 32-bit words; addr[11:2] picks the slot.
wire write_in_tile = (aw_addr_q[37:12] == 26'h1);
always_ff @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
w_state <= W_IDLE;
aw_id_q <= '0;
aw_addr_q <= '0;
s_axi_bvalid <= 1'b0;
end else begin
case (w_state)
W_IDLE: begin
s_axi_bvalid <= 1'b0;
if (s_axi_awvalid && s_axi_awready) begin
aw_id_q <= s_axi_awid;
aw_addr_q <= s_axi_awaddr;
w_state <= W_DATA;
end
end
W_DATA: begin
if (s_axi_wvalid && s_axi_wready) begin
s_axi_bvalid <= 1'b1;
w_state <= W_RESP;
end
end
W_RESP: begin
if (s_axi_bready) begin
s_axi_bvalid <= 1'b0;
w_state <= W_IDLE;
end
end
default: w_state <= W_IDLE;
endcase
end
end
assign s_axi_awready = (w_state == W_IDLE);
assign s_axi_wready = (w_state == W_DATA);
assign s_axi_bid = aw_id_q;
assign s_axi_bresp = 2'b00; // OKAY
// Ch176 — one-cycle write enable when the W beat lands on a write
// addressed at CORE_CTRL or CORE_PULSE. Use the W_DATA accept edge
// so the side effect happens exactly once per AXI write.
wire write_accept = (w_state == W_DATA) && s_axi_wvalid && s_axi_wready;
assign core_ctrl_we = write_accept && write_is_ctrl;
assign core_ctrl_d = wdata_lane[2:0];
assign core_pulse_we = write_accept && write_is_pulse;
assign hdmi_clr_pulse = core_pulse_we && wdata_lane[3];
// Ch222 — input-latch register file. One always_ff covers all three
// 32-bit latches; the lane-aligned `wdata_lane` (selected by
// aw_addr_q[3:2] earlier) supplies the data for each. Reset clears
// to 0; non-decoded writes leave existing values untouched.
always_ff @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
input_p1_q <= 32'd0;
input_p2_q <= 32'd0;
input_p1_raw_q <= 32'd0;
end else if (write_accept) begin
if (write_is_p1) input_p1_q <= wdata_lane;
if (write_is_p2) input_p2_q <= wdata_lane;
if (write_is_p1_raw) input_p1_raw_q <= wdata_lane;
end
end
// Ch318 — LPDDR test control registers. SAFE reset defaults: arm OFF, canary ON,
// base = 0x8000_0000 (the HPS-physical reserved region). The HPS arms via 0x018.
assign lpddr_arm_o = lpddr_arm_q;
assign lpddr_canary_o = lpddr_canary_q;
assign lpddr_fb_base_o = lpddr_fb_base_q;
always_ff @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
lpddr_arm_q <= 1'b0;
lpddr_canary_q <= 1'b1;
lpddr_fb_base_q <= 32'h8000_0000;
lpddr_rd_addr_q <= 32'd0;
lpddr_rd_pulse_q <= 1'b0;
lpddr_rd_done_sync <= 2'b00;
lpddr_rd_data_lat <= 32'd0;
lpddr_video_src_q <= 1'b0; // Ch320 — default BRAM scanout
lpddr_scanout_lb_q <= 1'b0; // Ch321 — default frame-cache (line-buffer = opt-in)
lpddr_scan_valid_sync <= 2'b00;
lpddr_scan_err_sync <= 2'b00;
lpddr_wr_addr_q <= 32'd0;
lpddr_wr_addr_present_q <= 32'd0;
lpddr_wr_data_q <= 32'd0;
lpddr_wr_pulse_q <= 1'b0;
tex_fill_start_q <= 1'b0;
diag_ctrl_q <= 8'd0;
tex_fill_done_sync <= 2'b00;
lpddr_wr_busy_sync <= 2'b00;
lpddr_wr_done_sync <= 3'b000;
tex_fill_crc_snap_q <= 32'd0;
tex_fill_beats_snap_q <= 32'd0;
tex_fill_bytes_snap_q <= 32'd0;
tex_rd_errs_snap_q <= 32'd0;
lpddr_ctrl_commit_q <= 1'b0;
lpddr_idle_sync <= 2'b00;
lpddr_bytes_snap_q <= 32'd0;
lpddr_bursts_snap_q <= 32'd0;
lpddr_bresp_err_snap_q<= 32'd0;
lpddr_fifo_ovf_snap_q <= 32'd0;
lpddr_wr_bresp_err_snap_q <= 32'd0;
end else begin
// Ch319 — read-probe return path CDC (emif_clk -> clk). rd_data is stable in
// the probe before rd_done toggles, so latching on the synced edge is safe.
lpddr_rd_done_sync <= {lpddr_rd_done_sync[0], lpddr_rd_done_i};
if (lpddr_rd_done_sync[0] != lpddr_rd_done_sync[1])
lpddr_rd_data_lat <= lpddr_rd_data_i;
// Ch320 — scanout status sync (emif_clk -> clk).
lpddr_scan_valid_sync <= {lpddr_scan_valid_sync[0], lpddr_scan_valid_i};
lpddr_scan_err_sync <= {lpddr_scan_err_sync[0], lpddr_scan_err_i};
// Ch322 — texture-fill / write-probe status sync (emif_clk -> clk).
tex_fill_done_sync <= {tex_fill_done_sync[0], tex_fill_done_i};
lpddr_wr_busy_sync <= {lpddr_wr_busy_sync[0], lpddr_wr_busy_i};
// Ch352 — 3-FF sync of the write-probe done toggle (feeds the stable write_pending bit).
lpddr_wr_done_sync <= {lpddr_wr_done_sync[1:0], lpddr_wr_done_i};
// Ch352 — latch the fill counters into clk domain on the synced fill_done RISING edge. The fill
// FSM raises fill_done with the final beat, so the counters are stable when the synced edge lands
// (2 FF later) — coherent multi-bit capture, same idea as the rd_data latch above.
if (tex_fill_done_sync[0] && !tex_fill_done_sync[1]) begin
tex_fill_beats_snap_q <= tex_fill_beats_i;
tex_fill_bytes_snap_q <= tex_fill_bytes_i;
tex_rd_errs_snap_q <= tex_rd_errs_i;
tex_fill_crc_snap_q <= tex_fill_crc_i;
end
// Ch352 — remaining status CDC. idle: 2-FF sync. FB-writer counters: capture WHILE idle (stable).
lpddr_idle_sync <= {lpddr_idle_sync[0], lpddr_idle_i};
if (lpddr_idle_sync[1]) begin
lpddr_bytes_snap_q <= lpddr_bytes_i;
lpddr_bursts_snap_q <= lpddr_bursts_i;
lpddr_bresp_err_snap_q <= lpddr_bresp_err_i;
lpddr_fifo_ovf_snap_q <= lpddr_fifo_ovf_i;
end
// write-probe error count: stable when a write completes -> latch on the synced write-done edge.
if (lpddr_wr_done_sync[2] != lpddr_wr_done_sync[1])
lpddr_wr_bresp_err_snap_q <= lpddr_wr_bresp_err_i;
if (write_accept) begin
if (write_is_lpddr_ctrl) begin
lpddr_arm_q <= wdata_lane[0];
lpddr_canary_q <= wdata_lane[1];
lpddr_video_src_q <= wdata_lane[2]; // Ch320 — select LPDDR4B scanout
lpddr_scanout_lb_q <= wdata_lane[3]; // Ch321 — line-buffer vs frame-cache
end
if (write_is_lpddr_base) lpddr_fb_base_q <= wdata_lane;
// Ch352 — flip the commit toggle on any control/base write so the EMIF domain re-latches
// {arm,canary,fb_base} coherently. (arm/canary/video_src/scanout_lb all live in the ctrl word.)
if (write_is_lpddr_ctrl || write_is_lpddr_base)
lpddr_ctrl_commit_q <= ~lpddr_ctrl_commit_q;
// 0x03C write: latch the read address AND pulse the read trigger.
if (write_is_lpddr_rdaddr) begin
lpddr_rd_addr_q <= wdata_lane;
lpddr_rd_pulse_q <= ~lpddr_rd_pulse_q;
end
// Ch322 — write-probe: 0x04C sets the byte address; 0x050 writes a data
// word (toggles the write trigger) and auto-increments the address by 4 so
// the HPS can stream a texture without re-writing the address each word.
if (write_is_lpddr_wraddr) lpddr_wr_addr_q <= wdata_lane;
if (write_is_lpddr_wrdata) begin
lpddr_wr_data_q <= wdata_lane;
lpddr_wr_pulse_q <= ~lpddr_wr_pulse_q;
lpddr_wr_addr_present_q <= lpddr_wr_addr_q; // addr for THIS word (snapshot)
lpddr_wr_addr_q <= lpddr_wr_addr_q + 32'd4; // advance for NEXT word
end
// arm (bit0=1) TOGGLES the cache fill_start — the cache edge-detects it and
// (re)fills, so re-staging a new texture + re-arming reloads the cache.
if (write_is_tex_fill && wdata_lane[0]) tex_fill_start_q <= ~tex_fill_start_q;
if (write_is_diag_ctrl) diag_ctrl_q <= wdata_lane[7:0];
end
end
end
// ----------------------------------------------------------------
// Ch330 Brick 4 — feeder staging-write + retrigger control.
// A staging entry is written as two halves (0x0DC LO then 0x0E4 HI);
// the HI write commits {hi,lo} -> staging[addr], TOGGLES the we strobe,
// and auto-increments addr so the HPS streams a whole list without
// re-writing the address. 0x0E8 GO bit0 toggles the retrigger. we/go are
// toggles (not pulses) because the feeder runs in design_clk; the de25 top
// syncs+edge-detects them. feeder_ready_i is synced for the 0x0D8 read.
// ----------------------------------------------------------------
logic [11:0] feeder_addr_q;
logic [31:0] feeder_lo_q;
logic [63:0] feeder_wdata_q;
logic [11:0] feeder_waddr_q;
logic feeder_we_tgl_q;
logic feeder_go_tgl_q;
logic [1:0] feeder_ready_sync;
always_ff @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
feeder_addr_q <= 12'd0;
feeder_lo_q <= 32'd0;
feeder_wdata_q <= 64'd0;
feeder_waddr_q <= 12'd0;
feeder_we_tgl_q <= 1'b0;
feeder_go_tgl_q <= 1'b0;
feeder_ready_sync <= 2'b00;
end else begin
feeder_ready_sync <= {feeder_ready_sync[0], feeder_ready_i};
if (write_accept) begin
if (write_is_feeder_addr) feeder_addr_q <= wdata_lane[11:0];
if (write_is_feeder_lo) feeder_lo_q <= wdata_lane;
if (write_is_feeder_hi) begin
feeder_wdata_q <= {wdata_lane, feeder_lo_q}; // {hi,lo}
feeder_waddr_q <= feeder_addr_q; // addr for THIS word (snapshot)
feeder_we_tgl_q <= ~feeder_we_tgl_q; // commit strobe (toggle)
feeder_addr_q <= feeder_addr_q + 12'd1; // advance for NEXT word
end
if (write_is_feeder_go && wdata_lane[0]) feeder_go_tgl_q <= ~feeder_go_tgl_q;
end
end
end
assign feeder_stg_we_tgl_o = feeder_we_tgl_q;
assign feeder_stg_waddr_o = feeder_waddr_q;
assign feeder_stg_wdata_o = feeder_wdata_q;
assign feeder_go_tgl_o = feeder_go_tgl_q;
// Ch223 — OSD-sink register file. Same pattern as Ch222.
// Ch245 — OSD_TRIGGER added with sibling-ABI semantics: bits are
// SET by single-cycle pulses from the menu FSM and CLEARED by HPS
// via write-1-to-clear. The bridge clock IS sys_clk (both ride
// CLOCK2_50), so the FSM-side set pulses are same-cycle inputs —
// no synchronizer required. OSD_CTRL[3] is a self-clearing
// request bit (matches nes_hps_bridge.sv:741).
always_ff @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
osd_ctrl_q <= 32'd0;
osd_cfg0_q <= 32'd0;
osd_cfg1_q <= 32'd0;
osd_trigger_q <= 32'd0;
end else begin
// OSD_CTRL[3] self-clear (HPS-write below overrides if it
// wrote a fresh 1 on the same cycle, NBA last-write-wins).
if (osd_ctrl_q[3]) osd_ctrl_q[3] <= 1'b0;
// OSD_TRIGGER: HPS W1C clear AND/OR FSM set pulses. Sets
// take priority over clears on the same bit (the set-bit
// assignments are lexically later in this block, so NBA
// semantics route them to win for any common bit).
if (write_accept && write_is_osd_trigger) begin
osd_trigger_q <= osd_trigger_q & ~wdata_lane;
end
if (osd_set_trigger_i) begin
osd_trigger_q[4] <= 1'b1; // action_pending (A)
osd_trigger_q[12:8] <= osd_trigger_row_i;
end
if (osd_back_trigger_i) begin
osd_trigger_q[5] <= 1'b1; // back_pending (B)
osd_trigger_q[12:8] <= osd_trigger_row_i;
end
if (osd_scroll_down_trigger_i) osd_trigger_q[6] <= 1'b1;
if (osd_scroll_up_trigger_i) osd_trigger_q[7] <= 1'b1;
if (osd_open_trigger_i) osd_trigger_q[16] <= 1'b1;
// HPS writes to OSD_CTRL/CFG0/CFG1 — full-register overwrite
// (intentionally last so it wins over the self-clear above).
if (write_accept) begin
if (write_is_osd_ctrl) osd_ctrl_q <= wdata_lane;
if (write_is_osd_cfg0) osd_cfg0_q <= wdata_lane;
if (write_is_osd_cfg1) osd_cfg1_q <= wdata_lane;
end
end
end
// Ch249 — Ch230 osd_ctrl_enable retired. The platform OSD path
// reads OSD_CTRL[0] out of `osd_ctrl_o[0]` (one of the three new
// 32-bit register exports), so the single-bit Ch230 broadcast
// had become redundant when the stub was unwired in Ch245 and
// fully obsolete after Ch249 removed the stub instantiation.
// Ch245 — surface full OSD register values for the platform OSD
// stack. Sibling cores (NES, etc.) extract field bits at the top:
// osd_cfg0[5:0] = cols
// osd_cfg0[12:8] = rows
// osd_cfg0[23:16] = origin_x_chars (× 16 for 2x scale)
// osd_cfg0[31:24] = origin_y_chars (× 16 for 2x scale)
// osd_cfg1[23:16] = cursor_attr
assign osd_ctrl_o = osd_ctrl_q;
assign osd_cfg0_o = osd_cfg0_q;
assign osd_cfg1_o = osd_cfg1_q;
// Ch235 — surface Ch222 INPUT_P1/P2 latches as bridge-clock-domain
// outputs for the downstream sio2_input_stub (which owns the CDC).
assign input_p1_o = input_p1_q;
assign input_p2_o = input_p2_q;
assign input_p1_raw_o = input_p1_raw_q;
// Ch227 — tile RAM write port. No reset clear (see header note —
// retained across warm reset, sim-initialized to 0 via the
// `initial` block above).
always_ff @(posedge clk) begin
if (write_accept && write_in_tile)
tile_mem[aw_addr_q[11:2]] <= wdata_lane;
end
// Ch229 — broadcast tile writes to the design-domain CDC. Toggle
// bit flips on every write; index + data latch at the same edge
// and remain stable until the next write. CDC primitive on the
// receiver detects toggle edges and samples index/data once they
// are guaranteed stable (2 dclk synchronizer stages = ≥ 2 dclk
// periods of holdover, more than enough at any reasonable HPS
// tile-write rate). Reset clears toggle + latches so a co-reset
// of bridge and design domains starts both sides at the same
// value (no spurious post-reset edge).
always_ff @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
tile_wr_toggle <= 1'b0;
tile_wr_index <= 10'd0;
tile_wr_data <= 32'd0;
end else if (write_accept && write_in_tile) begin
tile_wr_toggle <= ~tile_wr_toggle;
tile_wr_index <= aw_addr_q[11:2];
tile_wr_data <= wdata_lane;
end
end
// ----------------------------------------------------------------
// Read FSM. Same shape — accept arvalid, drive rdata + rvalid,
// hold until rready. rdata is the 32-bit reg value replicated
// into the matching 32-bit lane of the 128-bit response.
// ----------------------------------------------------------------
typedef enum logic [0:0] { R_IDLE, R_RESP } r_state_t;
r_state_t r_state;
logic [3:0] ar_id_q;
logic [37:0] ar_addr_q;
logic [127:0] rdata_q;
always_ff @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
r_state <= R_IDLE;
ar_id_q <= '0;
ar_addr_q <= '0;
rdata_q <= '0;
s_axi_rvalid <= 1'b0;
end else begin
case (r_state)
R_IDLE: begin
s_axi_rvalid <= 1'b0;
if (s_axi_arvalid && s_axi_arready) begin
ar_id_q <= s_axi_arid;
ar_addr_q <= s_axi_araddr;
case (s_axi_araddr[3:2])
2'b00: rdata_q <= {96'd0, reg_read(s_axi_araddr)};
2'b01: rdata_q <= {64'd0, reg_read(s_axi_araddr), 32'd0};
2'b10: rdata_q <= {32'd0, reg_read(s_axi_araddr), 64'd0};
default: rdata_q <= {reg_read(s_axi_araddr), 96'd0};
endcase
s_axi_rvalid <= 1'b1;
r_state <= R_RESP;
end
end
R_RESP: begin
if (s_axi_rready) begin
s_axi_rvalid <= 1'b0;
r_state <= R_IDLE;
end
end
default: r_state <= R_IDLE;
endcase
end
end
assign s_axi_arready = (r_state == R_IDLE);
assign s_axi_rid = ar_id_q;
assign s_axi_rdata = rdata_q;
assign s_axi_rresp = 2'b00; // OKAY
assign s_axi_rlast = 1'b1; // single-beat
// ----------------------------------------------------------------
// Tie off unused AXI4 fields to silence lint warnings.
// ----------------------------------------------------------------
// verilator lint_off UNUSED
wire _unused_ok = &{ 1'b0,
s_axi_awlen, s_axi_awsize, s_axi_awburst,
s_axi_awlock, s_axi_awcache, s_axi_awprot,
s_axi_wdata, s_axi_wstrb, s_axi_wlast,
s_axi_arlen, s_axi_arsize, s_axi_arburst,
s_axi_arlock, s_axi_arcache, s_axi_arprot,
h2f_reset,
1'b0 };
// verilator lint_on UNUSED
endmodule : ps2_hps_bridge