Files
retroDE_ps2/docs/hardware/ps2_lpddr_test.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

130 lines
5.8 KiB
Bash
Executable File

#!/bin/sh
# retroDE_ps2 LPDDR framebuffer write/readback test — Ch318 operator helper.
#
# Drives the runtime LPDDR test controls in `ps2_hps_bridge` and verifies the
# tile-flush writer reached real LPDDR. ONE bitstream (GS_TILE_PSMCT16FB_DEMO +
# GS_LPDDR_FB); all control is at runtime — no rebuild between stages.
#
# Same style/contract as ps2_status.sh: PS2 HPS-bridge base + busybox devmem
# (busybox avoids the devmem2 "Bus error" quirk on 0x?4-suffixed offsets — and
# LPDDR_BURSTS sits at 0x...34). See rtl/platform/ps2_hps_bridge.sv and
# docs/ch318-lpddr-fb-bringup.md.
#
# Usage (run from HPS Linux after loading the .core.rbf):
# ./ps2_lpddr_test.sh # read-only LPDDR status (safe; no arming)
# ./ps2_lpddr_test.sh --canary # arm canary (1 line), re-render, prove via counters
# ./ps2_lpddr_test.sh --full # arm full frame, re-render, prove via counters
# ./ps2_lpddr_test.sh --disarm # write LPDDR_CTRL = 0x2 (disarmed, canary)
#
# PROOF METHOD = bridge counters, NOT /dev/mem. The Ch318 writer targets the HPS
# LPDDR (f2sdram) at 0x80000000, which is a firmware-RESERVED region: reading it
# with `dd /dev/mem` HARD-CRASHES the fabric (needs a power cycle). So this script
# NEVER touches /dev/mem. It proves the write reached LPDDR by reading LPDDR_BYTES/
# LPDDR_BURSTS/LPDDR_STATUS over the HPS bridge (safe register reads). Byte-level
# CONTENT verification needs the Ch318b bridge-register readback path (ported from
# ao486 lpddr4b_loader.sv) — until that lands, content is not checked here.
#
# ONE-SHOT FIX: the EE bootlet renders once at boot (before you can arm), then
# halts -> BYTES stays 0. So --canary/--full arm FIRST, then pulse the core reset
# (CORE_CTRL[0]) to re-run the bootlet and flush a frame WHILE ARMED.
#
# Defaults are SAFE: the bitstream boots disarmed; this script only writes when
# you pass --canary/--full, and always leaves the writer disarmed on exit.
set -u
BASE="${PS2_BRIDGE_BASE:-0x40000000}"
DEVMEM="${DEVMEM:-busybox devmem}"
MODE="${1:-status}"
# Register offsets (see ps2_hps_bridge.sv).
OFF_CORE_CTRL=0x010 # RW [0]=core reset (pulse 1->0 re-runs the EE bootlet)
OFF_LPDDR_CTRL=0x018 # RW [0]=arm [1]=canary (reset 0x2)
OFF_LPDDR_FB_BASE=0x01C # RW LPDDR byte base (reset 0x80000000)
OFF_LPDDR_STATUS=0x02C # R [0]=idle [1]=bresp_err [2]=fifo_ovf
OFF_LPDDR_BYTES=0x030 # R total bytes written
OFF_LPDDR_BURSTS=0x034 # R total 32-byte bursts
OFF_LPDDR_BRESP_ERRS=0x038 # R count of bursts with non-OKAY response (1=reset-race phantom; 256=all refused)
# Expected byte counts (canary = 1 top line = 32 B; full = 64x64 PSMCT16 = 8 KiB).
EXP_CANARY_BYTES=32
EXP_FULL_BYTES=8192
read_reg() { $DEVMEM "$(printf '0x%08x' $(( BASE + $1 )) )" w; }
write_reg() { $DEVMEM "$(printf '0x%08x' $(( BASE + $1 )) )" w "$2"; }
bit_set() { if [ $(( ($1 >> $2) & 1 )) -eq 1 ]; then echo 1; else echo 0; fi; }
show_status() {
local ctrl base st by bu
ctrl=$(read_reg $OFF_LPDDR_CTRL); base=$(read_reg $OFF_LPDDR_FB_BASE)
st=$(read_reg $OFF_LPDDR_STATUS); by=$(read_reg $OFF_LPDDR_BYTES); bu=$(read_reg $OFF_LPDDR_BURSTS)
printf "LPDDR writer status\n"
printf " LPDDR_CTRL : %s (arm=%d canary=%d)\n" "$ctrl" "$(bit_set $((ctrl)) 0)" "$(bit_set $((ctrl)) 1)"
printf " LPDDR_FB_BASE: %s\n" "$base"
printf " LPDDR_STATUS : %s (idle=%d bresp_err=%d fifo_ovf=%d)\n" \
"$st" "$(bit_set $((st)) 0)" "$(bit_set $((st)) 1)" "$(bit_set $((st)) 2)"
printf " LPDDR_BYTES : %s\n LPDDR_BURSTS : %s\n" "$by" "$bu"
printf " LPDDR_BRESP_ERRS: %s\n" "$(read_reg $OFF_LPDDR_BRESP_ERRS)"
}
err_bits_clear() { # 1 if bresp_err and fifo_ovf both 0
local st=$(( $(read_reg $OFF_LPDDR_STATUS) ))
[ "$(bit_set $st 1)" = "0" ] && [ "$(bit_set $st 2)" = "0" ]
}
# Re-run the EE bootlet so it renders a frame WHILE the writer is armed.
# (The bootlet is one-shot; it renders once at boot, before you can arm.)
rerender_pulse() {
write_reg $OFF_CORE_CTRL 0x1 # assert core reset
sleep 1
write_reg $OFF_CORE_CTRL 0x0 # release -> bootlet re-runs, flushes a frame
sleep 3 # ~2 s DMAC-drain render cadence + margin
}
# Arm, re-render, and prove the write reached LPDDR via the bridge counters.
# $1 = LPDDR_CTRL arm value (0x3 canary / 0x1 full), $2 = expected byte count, $3 = label
prove_via_counters() {
local armval=$1 expbytes=$2 label=$3 by bu
write_reg $OFF_LPDDR_CTRL "$armval" # arm
rerender_pulse # render a frame while armed
by=$(( $(read_reg $OFF_LPDDR_BYTES) ))
bu=$(( $(read_reg $OFF_LPDDR_BURSTS) ))
be=$(( $(read_reg $OFF_LPDDR_BRESP_ERRS) ))
write_reg $OFF_LPDDR_CTRL 0x2 # DISARM
printf "after re-render: LPDDR_BYTES=%d (expect %d) LPDDR_BURSTS=%d BRESP_ERRS=%d\n" "$by" "$expbytes" "$bu" "$be"
if [ "$by" -ge "$expbytes" ] && err_bits_clear; then
printf "%s: PASS (fabric delivered %d B to LPDDR; no AXI/FIFO errors)\n" "$label" "$by"
return 0
else
printf "%s: FAIL (BYTES=%d < %d, or error bits set)\n" "$label" "$by" "$expbytes"
show_status; return 1
fi
}
case "$MODE" in
status)
show_status
;;
--canary)
printf "== LPDDR CANARY (32-byte top-line write, counter proof) ==\n"
printf "defaults: LPDDR_CTRL=%s (expect 0x00000002) LPDDR_FB_BASE=%s (expect 0x80000000)\n" \
"$(read_reg $OFF_LPDDR_CTRL)" "$(read_reg $OFF_LPDDR_FB_BASE)"
prove_via_counters 0x3 "$EXP_CANARY_BYTES" CANARY; exit $?
;;
--full)
printf "== LPDDR FULL FRAME (%d B, counter proof) ==\n" "$EXP_FULL_BYTES"
prove_via_counters 0x1 "$EXP_FULL_BYTES" FULL; exit $?
;;
--disarm)
write_reg $OFF_LPDDR_CTRL 0x2
printf "disarmed (LPDDR_CTRL=0x2)\n"
;;
*)
printf "usage: %s [--canary|--full|--disarm] (no arg = status)\n" "$0"; exit 2
;;
esac