#!/bin/sh
# retroDE_ps2 — Ch327b 16x16 MULTI-TILE spill/reload silicon proof (256x256 raster FB in LPDDR, line-buffer scanout).
#
# Renders the 16x16 two-batch scene (P1 near color1 + P2 mid color2, cross-seam), reads the
# 256x256 color framebuffer back out of LPDDR via the HPS read-probe (subsampled), dumps a PPM,
# categorizes the regions. Proves grid spill/reload + per-tile depth survival on hardware.
#
# Acceptance (matches the sim tb_gs_tile_spill_grid8x8_lpddr scaled + tb_gs_lpddr_scanout_lb_psm32_256):
#   - overlap (P1 region, x+y<256) keeps color1 (red) across MANY tiles  => depth survival
#   - region B (264<x+y<312, P2 only) accepts color2 (blue)
#   - empty tiles (top rows, bottom-right) stay CLEAR (0x008000 green clear), not painted
#   - ovf=0, errs=0; spill/reload counters sane (sim ref: spill_color~16384, reloaded_tiles~121)
#
# REQUIRES the Ch327b bitstream:  ./scripts/select_de25_profile.sh tile_spill  (then re-fit)
# Writes /tmp/ps2_tile8x8.ppm — scp it off and view to SEE the red/blue/green scene.

set -u
BASE="${PS2_BRIDGE_BASE:-0x40000000}"
DEVMEM="${DEVMEM:-busybox devmem}"
PPM="${PPM:-/tmp/ps2_tile8x8.ppm}"

OFF_CORE_CTRL=0x010
OFF_LPDDR_STATUS=0x02C
OFF_LPDDR_RDADDR=0x03C
OFF_SPILL_COLOR_BEATS=0x080; OFF_SPILL_Z_BEATS=0x084
OFF_RELOAD_COLOR_BEATS=0x088; OFF_RELOAD_Z_BEATS=0x08C
OFF_RELOAD_RD_ERRS=0x090; OFF_SPILL_COLOR_ERRS=0x094; OFF_SPILL_Z_ERRS=0x098; OFF_SPILL_OVF=0x09C
OFF_EV_TP_FLUSH=0x0A0; OFF_EV_TP_ZFLUSH=0x0A4; OFF_EV_TP_RELOAD=0x0A8; OFF_EV_TP_RENDER=0x0AC
OFF_EV_FLUSH_EMIT=0x0B0; OFF_EV_ZFLUSH_EMIT=0x0B4; OFF_EV_RELOAD_START=0x0B8; OFF_EV_RELOAD_READY=0x0BC
OFF_DIAG_CTRL=0x0C0      # [6]=trace_clear
OFF_LPDDR_CTRL=0x018     # [2]=video_src (1=LPDDR scanout, 0=BRAM)  [3]=scanout_lb (0=frame-cache)
# color-writer pipeline counters (0xCC/0xF8/0xFC/0xE0) + Ch324 Z-writer counters (0xC4/0xC8/0xD0/0xD4)
OFF_C_EMIT=0x0CC; OFF_C_PUSH=0x0F8; OFF_C_POP=0x0FC; OFF_C_BEATS=0x0E0
OFF_Z_BEATS=0x0C4; OFF_Z_EMIT=0x0C8; OFF_Z_PUSH=0x0D0; OFF_Z_POP=0x0D4

COLOR_SPILL_BASE=0x00400000; Z_SPILL_BASE=0x00500000; ROW_STRIDE=1024  # Ch327b 256 px * 4 B = 1024
FBW_PX=256; FBH_PX=256
STEP="${STEP:-2}"   # Ch327b — subsample the readback sweep (256x256=65536 reads is ~15 min at STEP=1)
RED=0xFF0000FF; BLUE=0xFFFF0000; CLEARC=0xFF008000   # color1 / color2 / tile clear (green, opaque)

w() { $DEVMEM $(printf "0x%X" $(( BASE + $1 ))) w "$2" >/dev/null; }
r() { $DEVMEM $(printf "0x%X" $(( BASE + $1 ))) w; }
rdprobe() { # $1 = EMIF byte addr -> 32-bit word
    w $OFF_LPDDR_RDADDR "$1"
    i=0; while [ $i -lt 1000 ]; do st=$(r $OFF_LPDDR_STATUS); [ $(( st & 0x8 )) -eq 0 ] && break; i=$(( i + 1 )); done
    r $OFF_LPDDR_RDADDR
}

echo "=== Ch327b 16x16 multi-tile spill/reload silicon proof (256x256, line-buffer scanout) ==="

# ---- (1) hold core in reset; one render (no preseed — clean-Z bootstrap) ----
echo "[1] holding core in reset, then one render ..."
w $OFF_CORE_CTRL 0x1
scb0=$(( $(r $OFF_SPILL_COLOR_BEATS) )); szb0=$(( $(r $OFF_SPILL_Z_BEATS) ))
rcb0=$(( $(r $OFF_RELOAD_COLOR_BEATS) )); rzb0=$(( $(r $OFF_RELOAD_Z_BEATS) ))
rs0=$(( $(r $OFF_EV_RELOAD_START) ))
w $OFF_DIAG_CTRL 0x40; w $OFF_DIAG_CTRL 0x00   # trace_clear pulse
w $OFF_CORE_CTRL 0x0; sleep 4

# ---- (2) counters ----
dC=$(( $(r $OFF_SPILL_COLOR_BEATS) - scb0 )); dZ=$(( $(r $OFF_SPILL_Z_BEATS) - szb0 ))
dRC=$(( $(r $OFF_RELOAD_COLOR_BEATS) - rcb0 )); dRZ=$(( $(r $OFF_RELOAD_Z_BEATS) - rzb0 ))
dRS=$(( $(r $OFF_EV_RELOAD_START) - rs0 ))
ovf=$(( $(r $OFF_SPILL_OVF) ))
errs=$(( $(r $OFF_SPILL_COLOR_ERRS) + $(r $OFF_SPILL_Z_ERRS) + $(r $OFF_RELOAD_RD_ERRS) ))
printf "[2] spill_color=%d spill_z=%d reload_color=%d reload_z=%d  reloaded_tiles=%d  ovf=0x%X errs=%d\n" \
       "$dC" "$dZ" "$dRC" "$dRZ" "$dRS" "$ovf" "$errs"
printf "    FSM evts: TP_FLUSH=%d TP_ZFLUSH=%d TP_RELOAD=%d TP_RENDER=%d  (sim ref: spill_color~16384 reloaded_tiles~121)\n" \
       "$(( $(r $OFF_EV_TP_FLUSH) ))" "$(( $(r $OFF_EV_TP_ZFLUSH) ))" "$(( $(r $OFF_EV_TP_RELOAD) ))" "$(( $(r $OFF_EV_TP_RENDER) ))"
# pipeline split — compare COLOR vs Z in the SAME run to localize the spill_z over-production.
cemit=$(( $(r $OFF_C_EMIT) )); cpush=$(( $(r $OFF_C_PUSH) )); cpop=$(( $(r $OFF_C_POP) )); cbeat=$(( $(r $OFF_C_BEATS) ))
zemit=$(( $(r $OFF_Z_EMIT) )); zpush=$(( $(r $OFF_Z_PUSH) )); zpop=$(( $(r $OFF_Z_POP) )); zbeat=$(( $(r $OFF_Z_BEATS) ))
printf "    PIPELINE color: emit=%d push=%d pop=%d beats=%d\n" "$cemit" "$cpush" "$cpop" "$cbeat"
printf "    PIPELINE   Z  : emit=%d push=%d pop=%d beats=%d   (color==Z expected; ovf bit1=Z)\n" "$zemit" "$zpush" "$zpop" "$zbeat"
if   [ "$zemit" -gt "$cemit" ];                 then echo "    -> DIAG(Z): emit>color  TP_ZFLUSH/tile-iteration emits too many (upstream)"
elif [ "$zpush" -gt "$cpush" ];                 then echo "    -> DIAG(Z): push>color  PACKER over-produces partial Z beats"
elif [ "$zpop" -gt "$zpush" ] || [ "$zbeat" -gt "$zpush" ]; then echo "    -> DIAG(Z): pop/beats>push  Z async-FIFO/CDC phantom drain"
else echo "    -> Z pipeline matches color"; fi

# ---- (3) sweep the 256x256 color FB (subsampled by STEP) -> PPM + categorize ----
sw=$(( FBW_PX / STEP )); sh=$(( FBH_PX / STEP ))
echo "[3] reading 256x256 color FB from LPDDR (STEP=$STEP -> $((sw*sh)) reads) -> $PPM ..."
printf "P3\n%d %d\n255\n" "$sw" "$sh" > "$PPM"
nred=0; nblue=0; nclear=0; nother=0
ovl_red=0; regB_blue=0; empty_painted=0
y=0
while [ $y -lt $FBH_PX ]; do
    x=0
    while [ $x -lt $FBW_PX ]; do
        a=$(( COLOR_SPILL_BASE + y*ROW_STRIDE + x*4 )); v=$(( $(rdprobe "$(printf "0x%X" $a)") ))
        cr=$(( v & 0xFF )); cg=$(( (v>>8) & 0xFF )); cb=$(( (v>>16) & 0xFF ))
        printf "%d %d %d\n" "$cr" "$cg" "$cb" >> "$PPM"
        if   [ $v -eq $(( RED )) ];    then nred=$(( nred+1 ))
        elif [ $v -eq $(( BLUE )) ];   then nblue=$(( nblue+1 ))
        elif [ $v -eq $(( CLEARC )) ]; then nclear=$(( nclear+1 ))
        else nother=$(( nother+1 )); fi
        s=$(( x + y ))
        # P1 (80,80)-(176,80)-(80,176): overlap red where x+y<240 (inside the P1 hypotenuse x+y=256)
        [ $x -ge 80 ] && [ $y -ge 80 ] && [ $s -lt 240 ] && [ $v -eq $(( RED )) ]  && ovl_red=$(( ovl_red+1 ))
        # P2-only band (between P1 hyp 256 and P2 hyp 320): blue
        [ $x -ge 80 ] && [ $y -ge 80 ] && [ $s -gt 264 ] && [ $s -lt 312 ] && [ $v -eq $(( BLUE )) ] && regB_blue=$(( regB_blue+1 ))
        # empty top tile-rows (gy<64, above the triangles which start at y=80): must not be painted
        [ $y -lt 64 ] && { [ $v -eq $(( RED )) ] || [ $v -eq $(( BLUE )) ]; } && empty_painted=$(( empty_painted+1 ))
        x=$(( x+STEP ))
    done
    y=$(( y+STEP ))
done
printf "    color census: red=%d blue=%d clear=%d other=%d  (subsampled STEP=%d)\n" "$nred" "$nblue" "$nclear" "$nother" "$STEP"
printf "    overlap-red(depth-survived)=%d  regionB-blue=%d  empty-toprow-painted=%d\n" "$ovl_red" "$regB_blue" "$empty_painted"
printf "    sample px: ovl(96,96)=0x%06X (144,96)=0x%06X (96,144)=0x%06X | regB(160,160)=0x%06X | empty(16,16)=0x%06X (224,224)=0x%06X\n" \
       "$(( $(rdprobe "$(printf "0x%X" $((COLOR_SPILL_BASE+96*ROW_STRIDE+96*4)))") & 0xFFFFFF ))" \
       "$(( $(rdprobe "$(printf "0x%X" $((COLOR_SPILL_BASE+96*ROW_STRIDE+144*4)))") & 0xFFFFFF ))" \
       "$(( $(rdprobe "$(printf "0x%X" $((COLOR_SPILL_BASE+144*ROW_STRIDE+96*4)))") & 0xFFFFFF ))" \
       "$(( $(rdprobe "$(printf "0x%X" $((COLOR_SPILL_BASE+160*ROW_STRIDE+160*4)))") & 0xFFFFFF ))" \
       "$(( $(rdprobe "$(printf "0x%X" $((COLOR_SPILL_BASE+16*ROW_STRIDE+16*4)))") & 0xFFFFFF ))" \
       "$(( $(rdprobe "$(printf "0x%X" $((COLOR_SPILL_BASE+224*ROW_STRIDE+224*4)))") & 0xFFFFFF ))"

# ---- (4) Ch327a — HDMI is the LPDDR FB via the SCALABLE LINE-BUFFER scanout ----
echo "[4] HDMI source = LPDDR framebuffer via LINE-BUFFER scanout (Ch327a: O(width) BRAM, not the"
echo "    O(w*h) frame-cache). FB_LPDDR_ONLY external FB, ~120 KiB reclaimed. On-screen == $PPM."
# scanout error/underflow: LPDDR_STATUS bit [5] = scan_rd_err (line-buffer underflow OR rresp err).
scan_st=$(( $(r $OFF_LPDDR_STATUS) ))
scan_err=$(( (scan_st >> 5) & 1 ))
printf "    LINE-BUFFER scanout: LPDDR_STATUS=0x%X  scan_err/underflow(bit5)=%d  (MUST be 0)\n" "$scan_st" "$scan_err"
if [ "$scan_err" -ne 0 ]; then echo "    !! FAIL: line-buffer underflow/read-error — scanout missed its real-time refill deadline"; fi

# ---- diagnosis ----
# PASS is judged on the LPDDR FRAMEBUFFER CONTENT (the real proof — whichever render
# populated it, including the boot auto-render), not the per-render spill DELTA. The
# delta is supplementary: if the measured render this pass re-fired it should be
# color==z==1024, but a 0 delta just means the FB was already correct (e.g. the script
# raced the boot auto-render) — the content + LPDDR scanout still prove correctness.
echo "=== DIAGNOSIS ==="
if [ $ovl_red -ge 3 ] && [ $regB_blue -ge 3 ] && [ $empty_painted -eq 0 ] && [ $ovf -eq 0 ] && [ $errs -eq 0 ]; then
    echo "PASS: grid spill/reload + LPDDR scanout works. color1 SURVIVED in the overlap across"
    echo "  multiple tiles (depth survival via per-tile reload), region B took color2, empty tiles"
    echo "  stayed clear, no overflow/errors. HDMI [4] now reads this FB from LPDDR scanout."
    if [ $(( dC )) -eq 0 ]; then
        echo "  NOTE: this pass's spill DELTA was 0 — the FB was already rendered (boot auto-render);"
        echo "  the content above is the proof. Re-run for a fresh spill_color==spill_z==1024 snapshot."
    fi
else
    echo "PARTIAL/FAIL: ovl_red=$ovl_red regB_blue=$regB_blue empty_painted=$empty_painted ovf=0x$ovf errs=$errs"
    echo "  expected ovl_red>=3, regB_blue>=3, empty_painted=0, ovf=0, errs=0. Inspect $PPM + the [2] counters."
fi
