Ch443f: coherent Gray readiness CDC + qualified scanout underflow detector

Harden the LPDDR scanout underflow diagnostic (Codex direction). Keeps the
Ch443e 4-buffer + lead-2 fix; adds no 5th buffer.

RTL (gs_lpddr_scanout_lb):
- Replace the raw-binary next_fetch readiness sync with a reset-aware GRAY
  code. next_fetch is monotonic between frames, so one Gray bit changes per
  increment; the 2-FF-synced + decoded nf_v is always a real prior frontier
  (monotone, burst-safe), never a torn multi-bit combination. (A plain
  toggle-per-change handshake dropped bursts when two increments landed in one
  sync window; that is why the earlier attempt under-read nf_v and false-tripped.)
  fs_edge_v overrides the lone multi-bit reset transient.
- Qualify the underflow: only a miss persisting >= QUAL_CYCLES (4) sets sticky
  underflow. uf_pmax_q records the longest streak and uf_qual_q whether any
  qualified, so a host can distinguish a 1-cycle CDC lag from a real late row.
- Atomic snapshot: scan_y/nf_v/pmax/causes/vphase/line_valid all latched the
  same video cycle on the first qualified miss.

ABI: 0x120 adds [15:12]=live pmax (2-FF synced via scan_diag_pmax_i). 0x124
[29:20] now carries pmax-at-capture (was nf_s0). Bridge dst reg kept 10-bit.

SDC: scanout diag bundle source count 37 -> 31 (nf_s0[10] -> pmax[4]); new
async-in cut + max_skew/net_delay for the next_fetch Gray CDC; stage-0 cut for
the live pmax sync.

Tests: new tb_gs_scanout_cdc_qual (async-clock focused: no false event from the
readiness transition or ordinary sync latency; sub-QUAL transient does not
qualify; genuine late row qualifies with a self-consistent atomic snapshot;
frame reset + mod-4 reuse re-arm the detector). tb_gs_scanout_diag updated for
the pmax field. Tie off scan_diag_pmax_i (+ pre-existing clut_* gap from the
fog baseline) in the four .* bridge/integration TBs.

Regressions green: scanout (cdc_qual, binomial_lookahead, diag, restart,
lpddr_scanout_lb x3), regbuf (r/aw/w), bridge + 3 pad integration TBs, and the
f52 top-level golden FB (Z 0/307200, COLOR 0/245760 mismatch, drops=0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-23 23:17:41 -04:00
parent 2e2c1e9ca6
commit 064484c50d
11 changed files with 418 additions and 63 deletions
@@ -369,13 +369,15 @@ set_false_path -from $osd_cfg_src -to $osd_cfg_dst
# Cut the async INTO stage 0 only (setup+hold); the sync[0]->sync[1..] MTBF
# leg stays timed. Nonzero-check WARN (the whole path is absent in non-LPDDR
# profiles), not fatal.
# (b) the 37-bit STABLE first-failure bundle {scan_y[10], nf_v[10], nf_s0[10],
# vphase[4], base, lookahead, line_valid}. It is written once per enabled
# session and held quiescent until the bridge captures it on the synced-valid
# edge -> the raw HOLD check is false; cut HOLD only + bound bit spread
# (set_max_skew 2ns) + bound net arrival (set_net_delay -max 2ns), same recipe
# as the tile_ram_cdc bundle above. FATAL count checks so a rename can't
# silently orphan the exception (src==37 exact; dst nonzero).
# (b) the 31-src STABLE first-failure bundle {scan_y[10], nf_v[10], pmax[4],
# vphase[4], base, lookahead, line_valid} (Ch443f: nf_s0[10] -> pmax[4];
# diag_first[29:20] now carries the qualified-miss persistence max, upper 6
# bits const 0 so the bridge dst reg stays 10-bit / 37-keeper). It is written
# once per enabled session and held quiescent until the bridge captures it on
# the synced-valid edge -> the raw HOLD check is false; cut HOLD only + bound
# bit spread (set_max_skew 2ns) + bound net arrival (set_net_delay -max 2ns),
# same recipe as the tile_ram_cdc bundle above. FATAL count checks so a rename
# can't silently orphan the exception (src==31 exact; dst nonzero).
foreach s {scan_diag_uf_sync scan_diag_rderr_sync scan_diag_valid_sync} {
set s0 [get_keepers -nowarn "*u_hps_bridge|$s\[0\]"]
if { [get_collection_size $s0] == 0 } {
@@ -384,14 +386,41 @@ foreach s {scan_diag_uf_sync scan_diag_rderr_sync scan_diag_valid_sync} {
set_false_path -to $s0
}
}
set scan_diag_src [get_keepers -nowarn {*u_lpddr_scan_lb|diag_scan_y_q[*] *u_lpddr_scan_lb|diag_nf_v_q[*] *u_lpddr_scan_lb|diag_nf_s0_q[*] *u_lpddr_scan_lb|diag_vphase_q[*] *u_lpddr_scan_lb|diag_base_q *u_lpddr_scan_lb|diag_look_q *u_lpddr_scan_lb|diag_lv_q}]
# Ch443f: live per-frame pmax magnitude (4-bit bus) is a plain advisory 2-FF sync
# (video_clk uf_pmax_q -> bridge scan_diag_pmax_s0[*]); cut the async INTO stage 0
# only, leaving s0->s1 timed. Bus bits may be momentarily incoherent — harmless,
# it feeds 0x120[15:12] read across many frames, not the coherent capture bundle.
set pmax_s0 [get_keepers -nowarn {*u_hps_bridge|scan_diag_pmax_s0[*]}]
if { [get_collection_size $pmax_s0] == 0 } {
post_message -type warning "Ch443f SDC WARN: scan_diag_pmax_s0 matched 0 keepers (renamed / no LPDDR scanout) — stage-0 cut NOT applied"
} else {
set_false_path -to $pmax_s0
}
# Ch443f: coherent readiness GRAY code (axi next_fetch -> next_fetch_gray comb ->
# video nf_gray_s0[*]). Async into stage 0 -> cut setup+hold into nf_gray_s0; the
# s0->s1 MTBF leg stays timed. Gray changes ONE bit per increment, but bound the
# source->stage0 bit spread (max_skew + net_delay 2ns) so the lone multi-bit event
# (frame-restart reset to V_SOURCE_START, which fs_edge_v overrides on the video
# side anyway) cannot spread across more than one video sample. FATAL count guard.
set nfg_dst [get_keepers -nowarn {*u_lpddr_scan_lb|nf_gray_s0[*]}]
set nfg_src [get_keepers -nowarn {*u_lpddr_scan_lb|next_fetch[*]}]
if { [get_collection_size $nfg_dst] == 0 } {
post_message -type info "Ch443f SDC: next_fetch Gray readiness CDC inactive in this profile (0 stage-0 dst)"
} else {
if { [get_collection_size $nfg_src] == 0 } { error "Ch443f SDC FATAL: nf_gray_s0 present but u_lpddr_scan_lb|next_fetch[*] matched 0 keepers (renamed? Gray CDC source orphaned)" }
set_false_path -to $nfg_dst
set_max_skew -from $nfg_src -to $nfg_dst 2.0
set_net_delay -max -from $nfg_src -to $nfg_dst 2.0
post_message -type info "Ch443f SDC: next_fetch Gray readiness CDC async-in cut + 2ns max_skew/net_delay ([get_collection_size $nfg_src] src -> [get_collection_size $nfg_dst] stage-0 dst)"
}
set scan_diag_src [get_keepers -nowarn {*u_lpddr_scan_lb|diag_scan_y_q[*] *u_lpddr_scan_lb|diag_nf_v_q[*] *u_lpddr_scan_lb|diag_pmax_q[*] *u_lpddr_scan_lb|diag_vphase_q[*] *u_lpddr_scan_lb|diag_base_q *u_lpddr_scan_lb|diag_look_q *u_lpddr_scan_lb|diag_lv_q}]
set scan_diag_dst [get_keepers -nowarn {*u_hps_bridge|scan_diag_scan_y_q[*] *u_hps_bridge|scan_diag_nf_v_q[*] *u_hps_bridge|scan_diag_nf_s0_q[*] *u_hps_bridge|scan_diag_vphase_q[*] *u_hps_bridge|scan_diag_base_q *u_hps_bridge|scan_diag_look_q *u_hps_bridge|scan_diag_lv_q}]
set scan_diag_src_n [get_collection_size $scan_diag_src]
set scan_diag_dst_n [get_collection_size $scan_diag_dst]
if { $scan_diag_src_n == 0 } {
post_message -type info "Ch442 SDC: scanout diagnostic CDC inactive in this profile (0 src -> no LPDDR scanout)"
} else {
if { $scan_diag_src_n != 37 } { error "Ch442 SDC FATAL: scanout diag bundle source count $scan_diag_src_n != 37 (10+10+10+4+1+1+1; renamed/optimized? hold cut + net_delay would orphan)" }
if { $scan_diag_src_n != 31 } { error "Ch442/443f SDC FATAL: scanout diag bundle source count $scan_diag_src_n != 31 (10+10+4+4+1+1+1 = scan_y+nf_v+pmax+vphase+base+look+lv; renamed/optimized? hold cut + net_delay would orphan)" }
if { $scan_diag_dst_n == 0 } { error "Ch442 SDC FATAL: scanout diag bundle dest u_hps_bridge|scan_diag_*_q EMPTY (renamed? hold cut would orphan)" }
set_false_path -hold -from $scan_diag_src -to $scan_diag_dst
set_max_skew -from $scan_diag_src -to $scan_diag_dst 2.0