#!/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