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

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

327 lines
14 KiB
Bash
Executable File
Raw 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.
#!/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 02 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 100140)\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 02 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