ec82764bef
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression (272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps, and all dump-derived textures/traces) is excluded via .gitignore and stays local. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
327 lines
14 KiB
Bash
Executable File
327 lines
14 KiB
Bash
Executable File
#!/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
|