#!/bin/sh # retroDE_ps2 status block — Ch219 operator helper. # # Reads the retroDE ABI v1.0 + ps2-specific diagnostic registers # exposed by `ps2_hps_bridge` on the hps2fpga bridge and prints a # one-screen status block. Run from the HPS Linux on the DE25-Nano # board after `core_loader.sh load /home/terasic/cores/retroDE_ps2.core.rbf`. # # Uses `busybox devmem` rather than `devmem2` to avoid a known # devmem2 access-size quirk on `0x?4`-suffixed offsets (the older # devmem2 build throws "Bus error" on ABI_VERSION @ 0x40000004 and # DMA_DONE_COUNT @ 0x40000024 — busybox devmem reads them cleanly). # # Usage: # ./ps2_status.sh # one-shot snapshot # ./ps2_status.sh --delta # take two snapshots 500ms apart, show counter deltas # # Exits 0 if CORE_ID matches "PS2\0" and CORE_STATUS bit [5] # (hdmi_i2c_error) is clear, else nonzero. Suitable for automation. set -u BASE="${PS2_BRIDGE_BASE:-0x40000000}" DEVMEM="${DEVMEM:-busybox devmem}" MODE="${1:-one-shot}" # Register offsets (matches retroDE ABI v1.0 + ps2 diagnostics; see # rtl/platform/ps2_hps_bridge.sv and docs/hardware/de25_nano_bringup.md). OFF_CORE_ID=0x000 OFF_ABI_VERSION=0x004 OFF_CORE_STATUS=0x008 OFF_CORE_CAPS=0x00C OFF_CORE_CTRL=0x010 OFF_CORE_PULSE=0x014 OFF_FRAME_COUNT=0x020 OFF_DMA_DONE=0x024 OFF_RASTER_OF=0x028 # Ch222 input latches + Ch226 DS2 stub (added to the snapshot in Ch236 # so operators can confirm the input-latch landing on real silicon). OFF_INPUT_P1=0x040 OFF_INPUT_P2=0x044 OFF_INPUT_P1_RAW=0x048 OFF_DS2_STATUS=0x0F0 OFF_DS2_BUTTONS=0x0F4 # Ch245 — OSD registers (sibling-ABI offsets, now wired to the # platform osd_overlay + osd_menu_fsm). OFF_OSD_CTRL=0x100 OFF_OSD_STATUS=0x104 OFF_OSD_TRIGGER=0x108 OFF_OSD_CFG0=0x110 OFF_OSD_CFG1=0x114 read_reg() { # $1 = offset (hex string with 0x prefix) # Returns the 32-bit value as 0x%08x via stdout. local addr=$(printf "0x%08x" $(( BASE + $1 )) ) $DEVMEM "$addr" w } bit_set() { # $1 = value, $2 = bit index → echo "1" if bit set, "0" if not. local v=$1 b=$2 if [ $(( (v >> b) & 1 )) -eq 1 ]; then echo "1"; else echo "0"; fi } print_status() { local label=$1 local id abi caps status ctrl frame dma raster id=$(read_reg $OFF_CORE_ID) abi=$(read_reg $OFF_ABI_VERSION) caps=$(read_reg $OFF_CORE_CAPS) status=$(read_reg $OFF_CORE_STATUS) ctrl=$(read_reg $OFF_CORE_CTRL) frame=$(read_reg $OFF_FRAME_COUNT) dma=$(read_reg $OFF_DMA_DONE) raster=$(read_reg $OFF_RASTER_OF) printf "retroDE_ps2 core status [%s] @ %s\n" "$label" "$(date)" printf "================================================\n" printf " CORE_ID : %s " "$id" if [ "$id" = "0x50533200" ]; then printf '"PS2\\0" ✓ ps2 fabric loaded\n' else printf " ✗ wrong/no fabric loaded (expected 0x50533200)\n" fi printf " ABI_VERSION : %s " "$abi" if [ "$abi" = "0x00000100" ]; then printf "(v1.0) ✓ retroDE ABI v1.0\n" else printf " ✗ unexpected ABI\n" fi # Ch246 — CORE_CAPS now advertises OSD geometry per the sibling-ABI # bit layout (matches nes_hps_bridge.sv). Decode the fields so # operators can spot a misadvertisement at a glance. local caps_cols caps_rows caps_2p caps_analog caps_savestates caps_save caps_save=$(( caps & 0x1 )) caps_savestates=$(( (caps >> 1) & 0x1 )) caps_2p=$(( (caps >> 2) & 0x1 )) caps_analog=$(( (caps >> 3) & 0x1 )) caps_cols=$(( (caps >> 8) & 0xff )) caps_rows=$(( (caps >> 16) & 0x1f )) printf " CORE_CAPS : %s (osd=%dx%d save=%d ss=%d 2p=%d analog=%d)\n" \ "$caps" "$caps_cols" "$caps_rows" "$caps_save" "$caps_savestates" "$caps_2p" "$caps_analog" printf "\n" # CORE_STATUS bit decode local sv=$((status)) printf " CORE_STATUS : %s\n" "$status" printf " [0] loaded : %s\n" "$(bit_set $sv 0)" # Ch251 inverted core_halt semantics: pre-Ch251 the bootlet halted # after a single paint (lit = healthy); the animated demo now loops # forever (unlit = healthy, lit = EE stuck on an unexpected SYSCALL). printf " [1] core_halt : %s " "$(bit_set $sv 1)" if [ "$(bit_set $sv 1)" = "0" ]; then printf "✓ Ch251 animated demo running (expected)\n" else printf "✗ EE halted — pre-Ch251 sentinel, or animated loop crashed/SYSCALL'd\n" fi printf " [2] dma_done_seen : %s\n" "$(bit_set $sv 2)" printf " [3] frame_seen : %s (lit = PCRTC delivered ≥1 frame)\n" "$(bit_set $sv 3)" printf " [4] hdmi_init_done : %s (lit = ADV7513 LUT walk complete)\n" "$(bit_set $sv 4)" printf " [5] hdmi_i2c_error : %s " "$(bit_set $sv 5)" if [ "$(bit_set $sv 5)" = "0" ]; then printf "✓ no I²C NACKs\n"; else printf "✗ NACK watchdog LATCHED — see runbook triage\n"; fi printf " [6] raster_overflow : %s " "$(bit_set $sv 6)" if [ "$(bit_set $sv 6)" = "0" ]; then printf "✓ raster healthy\n"; else printf "✗ raster FIFO overflowed — Ch172 backpressure broken?\n"; fi printf "\n" # CORE_CTRL bit decode local cv=$((ctrl)) printf " CORE_CTRL : %s\n" "$ctrl" printf " [0] reset : %s (1 = PS2 design held in reset)\n" "$(bit_set $cv 0)" printf " [1] rom_loaded : %s (ABI-shape latch, no functional effect yet)\n" "$(bit_set $cv 1)" printf " [2] pause : %s (ABI-shape latch, no functional effect yet)\n" "$(bit_set $cv 2)" printf "\n" printf " Counters:\n" printf " FRAME_COUNT : %s (advances at ~60Hz once PCRTC is alive)\n" "$frame" printf " DMA_DONE_COUNT : %s\n" "$dma" printf " RASTER_OVERFLOW_COUNT : %s (should be 0 under Ch172 backpressure)\n" "$raster" printf "\n" # Ch222 / Ch226 / Ch235 input-latch readbacks. These prove the # HPS-to-bridge half of the input path is alive on real silicon. # PS2-fabric consumption (Ch234 sio2_input_stub via IOP) is sim- # validated by tb_bridge_iop_pad_input but not wired into the # synth top yet — so non-zero values here mean "the bridge latch # landed" rather than "an in-fabric consumer saw it." local p1 p2 p1raw ds2s ds2b p1=$(read_reg $OFF_INPUT_P1) p2=$(read_reg $OFF_INPUT_P2) p1raw=$(read_reg $OFF_INPUT_P1_RAW) ds2s=$(read_reg $OFF_DS2_STATUS) ds2b=$(read_reg $OFF_DS2_BUTTONS) printf " Input latches (Ch222) + DS2 mirror (Ch226):\n" printf " INPUT_P1 : %s\n" "$p1" printf " INPUT_P2 : %s\n" "$p2" printf " INPUT_P1_RAW : %s\n" "$p1raw" # Ch248 — DS2_STATUS is now driven by the platform ds2_controller # (via the bridge's new ds2_connected_i / ds2_error_i input ports). # Bit layout: [0]=connected, [1]=error, [2]=1 (PS2-local legacy bit). local ds2_conn ds2_err ds2_conn=$(( $ds2s & 0x1 )) ds2_err=$(( ($ds2s >> 1) & 0x1 )) printf " DS2_STATUS : %s ([0]=connected=%d [1]=error=%d [2]=reserved=1)\n" \ "$ds2s" "$ds2_conn" "$ds2_err" # DS2_BUTTONS is now the live wired-controller bitmap (Ch248); the # Ch226 INPUT_P1 mirror was removed because it blocked retrodesd's # ds2_poll_thread from seeing real button state. printf " DS2_BUTTONS : %s (live ds2_controller readback — Ch248)\n" "$ds2b" if [ "$ds2_conn" = "1" ]; then printf " → controller plugged in; ds2_poll_thread should be writing INPUT_P1/RAW from this.\n" else printf " → controller unplugged (DS2_STATUS[0]=0). Plug a DS2 wired pad into\n" printf " the DE25 GPIO header (CLK/CMD/DATA/ATTN = PIN_H16/Y1/C2/P1) to wake the path.\n" fi # Ch245 — platform OSD register surface. Sibling-ABI layout; the # shared retroDE_splash overlay + menu FSM is wired to these. local octrl ostatus otrig ocfg0 ocfg1 octrl=$(read_reg $OFF_OSD_CTRL) ostatus=$(read_reg $OFF_OSD_STATUS) otrig=$(read_reg $OFF_OSD_TRIGGER) ocfg0=$(read_reg $OFF_OSD_CFG0) ocfg1=$(read_reg $OFF_OSD_CFG1) printf "\n OSD registers (Ch245 platform OSD):\n" printf " OSD_CTRL : %s ([0]=enable [2]=force_open [3]=force_close,self-clear)\n" "$octrl" printf " OSD_STATUS : %s ([0]=osd_active [12:8]=cursor_row)\n" "$ostatus" printf " OSD_TRIGGER : %s ([4]=A [5]=B [6]=down [7]=up [16]=open [12:8]=row)\n" "$otrig" printf " OSD_CFG0 : %s ([5:0]=cols [12:8]=rows [23:16]=origin_x_chars [31:24]=origin_y_chars)\n" "$ocfg0" printf " OSD_CFG1 : %s ([4:0]=menu_first_row [12:8]=menu_last_row [23:16]=cursor_attr)\n" "$ocfg1" # Decode CFG0 fields for human readability. local cols rows origx origy origx_pix origy_pix cols=$(( ocfg0 & 0x3f )) rows=$(( (ocfg0 >> 8 ) & 0x1f )) origx=$(((ocfg0 >> 16) & 0xff )) origy=$(((ocfg0 >> 24) & 0xff )) origx_pix=$(( origx * 16 )) origy_pix=$(( origy * 16 )) printf " → cols=%d rows=%d origin=(%d,%d) chars = (%d,%d) px at 2× scale\n" \ "$cols" "$rows" "$origx" "$origy" "$origx_pix" "$origy_pix" } print_delta() { # Ch253 — two snapshots 2 s apart so a Ch251.5 ~1 Hz heartbeat # toggle is captured reliably regardless of phase (was 500 ms in # Ch219 when the bootlet was one-shot and counter Δ didn't matter # for blink rate). local f1 d1 r1 f2 d2 r2 f1=$(read_reg $OFF_FRAME_COUNT) d1=$(read_reg $OFF_DMA_DONE) r1=$(read_reg $OFF_RASTER_OF) sleep 2 f2=$(read_reg $OFF_FRAME_COUNT) d2=$(read_reg $OFF_DMA_DONE) r2=$(read_reg $OFF_RASTER_OF) local df dd dr df=$(( f2 - f1 )) dd=$(( d2 - d1 )) dr=$(( r2 - r1 )) printf "\n Counter Δ over 2 s:\n" printf " FRAME_COUNT : %s → %s Δ=%d (≈ 120 if PCRTC is alive @ 60 Hz)\n" "$f1" "$f2" "$df" printf " DMA_DONE_COUNT : %s → %s Δ=%d (validated band 0–2 per 2 s — liveness cue, not a clock)\n" "$d1" "$d2" "$dd" printf " RASTER_OVERFLOW_COUNT : %s → %s Δ=%d (MUST stay 0 — Ch172 backpressure)\n" "$r1" "$r2" "$dr" # Ch253 — Ch251+ animated-demo health verdict. Rolls up the # individual signals into a single PASS/FAIL block so the # operator can run `ps2_status.sh --delta` and know in <3 s # whether the field unit is healthy. local status_final halt_bit i2c_bit raster_bit ds2_conn_bit status_final=$(read_reg $OFF_CORE_STATUS) halt_bit=$(bit_set $((status_final)) 1) i2c_bit=$(bit_set $((status_final)) 5) raster_bit=$(bit_set $((status_final)) 6) ds2_conn_bit=$(( $(read_reg $OFF_DS2_STATUS) & 0x1 )) local verdict_ok=1 printf "\n Ch251+ animated-demo health verdict:\n" if [ "$df" -ge 100 ] && [ "$df" -le 140 ]; then printf " [ ✓ ] PCRTC alive (FRAME_COUNT Δ=%d ≈ 120 @ 60 Hz)\n" "$df" else printf " [ ✗ ] PCRTC stalled/off-rate (FRAME_COUNT Δ=%d, expected 100–140)\n" "$df" verdict_ok=0 fi if [ "$raster_bit" = "0" ] && [ "$dr" -eq 0 ]; then printf " [ ✓ ] Raster healthy (RASTER_OVERFLOW_COUNT stable + bit[6] clear)\n" else printf " [ ✗ ] Raster overflowing (Δ=%d / status bit[6]=%s — Ch172 broken?)\n" "$dr" "$raster_bit" verdict_ok=0 fi # Ch253 / Ch254 — this counter is a LIVENESS CUE, not a precision # timer. The bootlet ties DMAC DONE 1:1 to the visible heartbeat # toggle, so DMA_DONE Δ over 2 s is a faithful proxy for the blink. # Hardware-validated Δ band is {0, 1, 2}; ~1.2 s fixed per-iter # overhead (DMAC drain + 17-SPRITE GS rasterize) puts the cadence # around 0.5 Hz with natural ±0.5 s jitter — see bake.py comment # block and de25_nano_bringup.md Ch254 for the empirical model. # Δ=0 is a 2 s phase-miss not a failure; rerun protocol. if [ "$dd" -ge 1 ] && [ "$dd" -le 2 ]; then printf " [ ✓ ] DMAC repaint liveness (DMA_DONE Δ=%d over 2 s; bootlet animating)\n" "$dd" elif [ "$dd" -eq 0 ]; then printf " [ ? ] DMAC repaint window miss (Δ=0 — likely 2 s phase miss; rerun. Persistent Δ=0 = bootlet stuck)\n" # Don't fail the verdict on a single Δ=0; rerun is the right # protocol. Persistent stuck-state shows up as the operator # running --delta twice in a row and getting Δ=0 both times. else printf " [ ? ] DMAC repaint off-rate (Δ=%d, validated band is 0–2 over 2 s — investigate)\n" "$dd" verdict_ok=0 fi if [ "$halt_bit" = "0" ]; then printf " [ ✓ ] EE core not halted (CORE_STATUS[1]=0)\n" else printf " [ ✗ ] EE halted (CORE_STATUS[1]=1 — animated loop crashed/SYSCALL)\n" verdict_ok=0 fi if [ "$i2c_bit" = "0" ]; then printf " [ ✓ ] HDMI I²C clean (CORE_STATUS[5]=0)\n" else printf " [ ✗ ] HDMI I²C NACKed (CORE_STATUS[5]=1 — see runbook triage)\n" verdict_ok=0 fi if [ "$ds2_conn_bit" = "1" ]; then printf " [ ✓ ] DS2 controller plugged (DS2_STATUS[0]=1)\n" else printf " [ — ] DS2 controller unplugged (informational — Select+Start menu needs a pad)\n" fi if [ "$verdict_ok" -eq 1 ]; then printf "\n ──> Ch251+ field health: PASS\n" else printf "\n ──> Ch251+ field health: FAIL (see flags above)\n" fi # Build-time profile reminder. The script cannot read VRAM_ENABLE_READ2 # at runtime (it is baked into the bitstream); the sim-side tripwire # in vram_bram_stub.sv catches a bad profile during iverilog/verilator # elaboration, but Quartus does not honour it (the guard sits inside # `translate_off`). Real synth-side protection is the explicit # parameter override in de25_nano_psmct32_raster_demo_top.sv. printf "\n Profile note: hardware build assumes VRAM_ENABLE_READ2=0 (Ch252).\n" printf " Re-enabling read2 at VRAM_BYTES=512 KiB will blow the\n" printf " Agilex 5 M20K budget at fit time. See\n" printf " docs/decisions/0006-vram-roadmap.md for triggers.\n" } print_status "snapshot" if [ "$MODE" = "--delta" ]; then print_delta fi # Exit status: 0 if CORE_ID + clean I²C, nonzero otherwise. id_ok=$(read_reg $OFF_CORE_ID) status_check=$(read_reg $OFF_CORE_STATUS) i2c_err=$(bit_set $((status_check)) 5) if [ "$id_ok" = "0x50533200" ] && [ "$i2c_err" = "0" ]; then exit 0 else exit 1 fi