a2mega 1.0a3 DP bring-up: closed-loop training, boot-draw characterization, flash reliability, recovery tooling - #2
Open
edanuff wants to merge 134 commits into
Open
a2mega 1.0a3 DP bring-up: closed-loop training, boot-draw characterization, flash reliability, recovery tooling#2edanuff wants to merge 134 commits into
edanuff wants to merge 134 commits into
Conversation
- hdl/displayport/: dp_transmitter + core/video/audio/auxch/gowin from DisplayPort_Verilog @ 87cd538 (MIT), provenance README with a2mega lane/refclk facts - boards/a2mega/hdl/gowin/60B/dp_serdes/: generated GW5AT-60B Customized PHY (Q0 L2+L3 bonded, REFCLK1 135 MHz, QPLL0, HBR, tx_pol_invert) incl. .csr sidecar and .ipc regen config - boards/a2mega/hdl/dp_gowin_defines.v: DP_VENDOR_GOWIN/GOWIN_SERDES_IP/ GOWIN_PLL_IP/DP_SERDES_LANES_23 - ESP32 firmware: vendor portable USB-C PD stack (fusb302, usbc_port, esp32s3_integration HAL skeleton) into the sketch - docs: BRINGUP_1_0A3_PLAN.md (phased bring-up plan for the 1.0a3 board)
1.0a3 reshuffled the ESP32-S3 pinout: OPI D0-7 moved to IO11-18, JTAG TDO IO45->IO39, I2C (FUSB302B+TUSB1046A) on IO1/2 with INT on IO3, PPO narrowed to 4 bits, VBUS_SRC_EN on strapping pin IO46, six spare GPIO lines to the FPGA (GPIO0=DP HPD out, GPIO5=JTAG-bridge TRST dummy — IO3 now belongs to the FUSB302B and IO45 is the VDD_SPI strap). - board_pins.h: all pins for both revs, -DA2MEGA_BOARD_REV selects (default 103 on this branch; Makefile BOARD_REV=102 for older boards) - SD-card code compiled out on 1.0a3 (no slot); mount_sd() stub keeps the no-card path; storage returns later as LittleFS at the same /sdcard VFS prefix - setup(): pin VBUS_SRC_EN + HPD low, FUSB INT_N input, before subsystems - fpga_jtag.c/a2fpga_jtag.h take pins from board_pins.h (+_Static_assert the out1/in1 bank assumption) Both revs compile green (arduino-cli, esp32s3).
On 1.0a3 the USB-C port faces the DP monitor, so WiFi telnet becomes the primary console. Port of the BL616 telnetd (port 23, single client): console view streams osd_log lines live (ring tee, never blocks logging threads); menu view mirrors the 40x24 OSD page as ANSI and maps keys to pad buttons via a new thread-safe menu_inject/menu_request_menu_view, so the whole menu is drivable with no gamepad. Enhanced-only bus-capture diagnostics not ported (no such fabric in the a2mega gateware). Support APIs added: fpga_screen shadow row accessor + generation counter, osd_console_snapshot backlog replay, osd_log tee hook. New CLI: 'wifi [<ssid> [psk]]' persists credentials to NVS and starts the bridge immediately when booted unconfigured (replaces wifi.txt on SD-less 1.0a3), plus 'restart'. Both board revs compile green.
usbc_glue.cpp binds the vendored usbc_port/fusb302 policy core to the 1.0a3 hardware. The vendored esp32s3_integration skeleton assumes a GPIO-mode TUSB1046A; the board straps I2C mode, so this glue provides its own HAL: - shared Wire bus (IO1/IO2): FUSB302B @0x22, TUSB1046A @0x12 - DP enable = general reg 0x0A CTLSEL=10 | FLIPSEL | HPDIN_OVRRIDE, disable = CTLSEL=01 (as-built decisions from usb-c/SPEC.md §6) - HPD level driven on PIN_DP_HPD_OUT (ESP32_GPIO0 -> FPGA hotplug_decode); IRQ_HPD = 0.75 ms low pulse generated in firmware - VBUS sourcing via TPS2553 EN (strapping-safe), 5 V / 1 A single PDO - 1 kHz policy task on core 1, woken early by FUSB302B INT_N (IO3) CLI gains 'pd' (state/orientation/HPD/VBUS/mux status). PD init runs at setup(), before the FPGA link — the PD contract is time-bound and must complete regardless of FPGA state. Both board revs compile green.
Mount a LittleFS (1.5 MB 'spiffs' partition, format-on-first-use) at the /sdcard VFS prefix on 1.0a3, so disk.c, ftpd.c and fpgaupdate.c — all plain POSIX on that prefix — serve disk images and FPGA update files from flash unchanged. Images arrive over FTP once WiFi is up. Both revs compile green.
The 1.0a3 board carries the same module as the a2p25 — N4R2 (4 MB flash, 2 MB quad PSRAM) — not the N8 (8 MB, no PSRAM) of earlier revs. Makefile now selects the FQBN per BOARD_REV: 103 = FlashSize=4M, PSRAM=enabled, PartitionScheme=no_ota (2 MB app + 1.875 MB LittleFS; no OTA pair — the ESP32 reflashes over USB-C); 102 keeps 8M/no-PSRAM/default_8MB. Consequence noted in Makefile + plan: an uncompressed GW5AT-60 bitstream (~2.6 MB) no longer fits the flash FS, so menu FPGA-update-from-file will need compression or streaming; PC-driven JTAG is unaffected. PSRAM is now available for large buffers. Both revs compile green (103 app = 48% of the 2 MB slot).
The MINI footprint accepts either ESP32-S3 variant (N4R2/N8 are pin-compatible). N4R2 was selected when DOC-on-ESP32 was anticipated — now known unnecessary — but its 2 MB PSRAM may still be needed for the much larger buffers WiFi throughput wants. Either module supports the current bring-up path. FPGA update-from-file: decided to stream the bitstream to JTAG (USB stick / network source) rather than stage ~2.6 MB in MCU flash — wasteful on any module, impossible on the N4R2 FS.
… pin map Phase 2 step 1 of the 1.0a3 plan, built before hardware arrival. DP test project (a2mega_dp_test.gprj -> impl/pnr/a2mega_dp_test.fs): - a2mega_dp_test_top.sv: vendored dp_transmitter in the timing-verified config (1080p59.94, 2-lane HBR, Q0 L2+L3 bonded on REFCLK1), SMPTE-ish colorbars + frame border, quiet 48 kHz test tone, AUX pseudo-diff pair on G15/G16, HPD in from ESP32_GPIO0 (A19, pull-down), LED bring-up ladder: heartbeat / HPD / link_established / video_live (active low) - Timing CLEAN: 0 setup+hold violations, TNS 0.000 all five clocks, Fmax >= constraint (clk_sym 135.352 vs 135.007 MHz) - Gowin project gotchas captured in the plan doc: per-project impl/<name>_process_config.json must carry Verilog_Standard=Sysv2017, TopModule, and a DISTINCT OUTPUT_BASE_NAME (default silently overwrote the main a2mega outputs; restored from git). The .gprj.user is force-added past .gitignore: it registers RES.serdes.csr, without which PnR fails CM2031. - bitstream sha256: 6f1346dbb5c68c0971465aa174b9b351c8835178bc81cc0808136fbf1c0aee91 (base 44ba210 + this tree, gw_sh @ GowinIDE.app) Master pin map (user-requested): - docs/a2mega_pinmap_1_0a3.csv: signal / ESP32-S3 GPIO / BTB position / FPGA ball for ALL of J1 (Apple bus), J2 (ESP32+DP), J3 (GS), plus SERDES rows with die-true 60K vs 138K lane naming (balls identical on PG484; schematics use 138K names). Notes that 1.0a4 moves pins again. - tools/btb_pinmap.py: regenerable extractor (KiCad pcb pad nets + BTB-sheet pin-name texts, coordinate-matched). Validated: 1.0a2a netlist reproduces the working a2mega.cst 12/12 (incl. the deliberate USB D+/D- swap), and J1 spot checks (phi1 H19, d0 N18, a0 T20, a15 G17) match exactly. - docs/btb_pinout_1_0a3.md: narrative + the FPGA-side pin MOVES on 1.0a3 (OSPI/UART/PPO all relocated; UART now on old USB balls G13/H13; GS RDY split with RDY_OUT=V15)
… schematics The io_60k/io_138k columns were previously empty except for SERDES lane rows (the a2-mega sheets only carry 138K names). Now sourced from the Sipeed SOM schematic PDFs (tang_mega_60k_30353 / tang_mega_138k_30354): - docs/tang_mega_die_names.json: ball -> per-die BANKn_IOxxx dictionary (60K: 107 full + 24 bank-only of 135 rows; 138K: 134/135 — the 138K PDF labels fewer balls, fallback = a2-mega annotation). The 138K PDF's subsetted font emits text shifted by -0x1D; decoder noted in the tool. - Cross-checks: 0 mismatches between Sipeed-138K names and the a2-mega schematic annotations on every overlapping ball; per-die names really do differ (e.g. FPGA_A0 T20 = BANK7_IOB111B on 60K vs BANK4_IOB102B on 138K). - Confirmed at the source: the Sipeed 60K sheet labels the BTB DP pins with 138K-style lane names (Q0_LN1 on the die-true lane-3 pins) — supports the a2-mega die-true lane note; CSV lane columns stay die-true (what the timing-verified SERDES IP implements).
The Apple II core now outputs 720x480p59.94 over 2-lane HBR DisplayPort
(USB-C) instead of HDMI. Bitstream sha256
307fc24dfa9f1746595b4872fd62043a4032458bb225c5868a4bc09b9a26d112,
0 setup + 0 hold violations, TNS 0.000, Fmax >= constraint on all eight
clocks (clk_sym 135.055 vs 135.007 - met, thin; see margin note below).
top.sv:
- hdmi encoder + OSER10 serializer + ELVDS replaced by dp_transmitter at
the framebuffer_480p seam: the DP core owns clk_pixel_w (27 MHz =
135 MHz symbol clock / 5 from its pixel PLL) and pulls rgb via the
same hdl-util-style cx/cy contract, so framebuffer/OSD/DebugOverlay
connect unchanged. Board PLLs untouched - the board 27 MHz tap remains
solely pll_ddr3's source (do NOT re-source; DDR3 calib fragility).
- USB HID host + report CDC removed (no USB-A on 1.0a3); ESP32 readback
regs tied to no-device. AUX pseudo-diff pair, HPD in, 4 LEDs with the
bring-up ladder (heartbeat / calib / link_established / video_live).
cst/sdc: 1.0a3 ball moves (OSPI A14..B17/A18, UART on old USB balls
G13/H13, AUX G15/G16, HPD A19, 4 LEDs), TMDS/USB pins gone; DP clock set
(clk100/clk_sym/clk_pix + async groups), TMDS x5 and clk_usb dropped.
DP core timing closure - IN RTL, not floorplanning (vendored core,
upstreamable; sim-verified cycle-exact against in-module shadow models
at both 480p and 1080p geometries, 3 frames each):
- video_stream_packer: the whole two-slot TU walk (Bresenham valid
count, phase, FS/FE, word loads, FIFO pop) is computed one cycle early
from a self-contained registered state copy and REGISTERED; the live
datapath is flag muxes only. Region flags additionally pre-registered
from exactly-advanced two-step-ahead line counters. The original walk
survives as a translate_off shadow with equivalence checks.
- sdp_engine: the four per-cycle wire-byte lookups (deep mux over the
256-bit payload) precomputed one cycle early into registers; b-index
derived as {ins_cyc,1} (algebraic identity) to avoid a subtractor -
the design's final failing path.
- gowin_plls/dp_transmitter: pixel PLL ODIV0 parameterized off the fixed
1188 MHz VCO (8 -> 148.5 MHz, 44 -> 27 MHz), derived from
PIXEL_CLK_MULT/DIV with an elab check.
- process config: Place_Option=2, Route_Option=1 (part of the recipe).
Margin note: clk_sym worst slack +0.003 ns. GW5AT PnR variance can
wobble this on re-runs (docs/gotchas.md); the RTL now has no deep cones,
so a wobble is a re-run, not a redesign. Do NOT reach for GROUP/GRP_LOC
regions: two attempts thrashed the placer for 50 min and 9+ hours (new
gotchas entry). tools/build.sh now enforces BUILD_TIMEOUT (default 30
min) so a pathological PnR fails loudly.
Pin map: SERDES rows corrected from the Sipeed 60K U1L die symbol
(matches Gowin UG1222) + BTB nets - ALL FOUR DP pairs are P/N swapped
(TXM on the lower pin, uniformly); lane map DP0-L3/DP1-L2/DP2-L1/DP3-L0
confirmed; correct per-pair balls C7/D7 A6/B6 C5/D5 A4/B4. The 2-lane
IP's tx_pol_invert on L2+L3 is correct as shipped; 4-lane needs invert
on L0+L1 too. The a2-mega BTB1 schematic note (DP3 not swapped + ball
orderings) needs correcting in the hardware repo.
Artifacts now match the restructured hdl/displayport sources (registered early-walk packer, sdp wire-byte precompute, parameterized pixel PLL). Timing clean: 0 violations, TNS 0.000, clk_sym Fmax 136.077 vs 135.007 constraint — the RTL closure work IMPROVED standalone margin over the original build's 135.352. Bitstream sha256: 30f04a1d77736eda3226fd138335aa46e7241acd9ba1689ff81738ad165477b2
The full core now scans out at 1920x1080p59.94 over 2-lane HBR DP.
Bitstream sha256
2270857e7e55d05dc20af2138a668718e4924b65868d10c17b6c8893bff19451:
0 setup + 0 hold violations, all eight clocks meet constraint
(clk_sym 136.03 vs 135.01, clk_pix 148.67 vs 148.50).
hdl/video/framebuffer_1080p.sv (new, derived from framebuffer_480p
which stays the 480p/HDMI variant): the framebuffer still stores
SOURCE resolution (560x192 Apple II / 640x200 SHR); the read side
scales x3 horizontal / x5 vertical into 1080p with centered borders
(1680x960 / 1920x1000). No dividers: both axes use incremental phase
trackers off the DP core's monotonic cx/cy; the clk-domain fetch pacing
consumes a CDC'd {approach,active,fb_line} bundle computed in the pixel
domain. Same 8-bank line buffer (source-res lines) — zero BSRAM growth.
Registered input coords (+1 lookahead compensation) and a pre-registered
left-border trigger keep the address cone shallow at 148.5 MHz.
top.sv: dp_transmitter at the timing-verified 1080p config (2200/1125,
MULT 11 / DIV 10); audio strobe rebuilt as a sign-bit phase accumulator
(148.5M/48k is not integer); OSD window centered (680,348).
148.5 MHz closure of the 27 MHz-era pixel pipeline:
- osd_text_overlay: 4Kx8 font ROM forced to block ROM (pure sync read;
glyph inversion moved to the consumer — ~rom[addr] blocked inference)
- DebugOverlay: 5-stage pipeline (input regs -> region compares ->
region-relative subtracts + hex mux -> char ROM row -> output), local
registration of the quasi-static hex/bit values, syn_keep against
cross-module register merging. Overlay shifts a few px — cosmetic.
- video_stream_packer: VB-ID vsel() compares pre-registered from the
two-step-ahead counters (pf_vb*); redundant zero-gate dropped from the
sym decisions (stage B already gates on running) removing RESET arcs
- sdp_engine: GF parity tree split at the 2-byte midpoint (2-stage
pipeline, shadow-checked against pb_group in sim); packet arming =
pending for >=4 CONSECUTIVE cycles — the shadow wire-byte assertion
caught that a plain shift register retained stale armed state across
packet boundaries and back-to-back packets would have inserted the
PREVIOUS packet's parity bytes (real functional bug, killed in sim)
All packer/sdp changes verified cycle-exact against in-module shadow
models (3 frames x 480p + 1080p geometries, iverilog).
tools/build.sh: gw_sh runs under caffeinate on macOS (two silent
30-minute startup hangs; App Nap suspected, one recurrence caffeinated
— cause still open, BUILD_TIMEOUT contains the damage either way).
Margin note: clk_sym/clk_pix are met with thin margin; GW5AT PnR
variance can wobble marginal paths run-to-run (observed band this
session: 0-72 violations on identical-or-near sources). The RTL now has
no deep cones — a wobble is a re-run, not a redesign.
Section 5 added to BRINGUP_1_0A3_PLAN.md: staged first-article runbook built around the one-port constraint (J4 = power + programming + monitor on the bench; flash-boot for bench swaps, in-slot SRAM iteration once trusted). Stages: electrical (no SOM) -> ESP32/WiFi/telnet -> SOM+JTAG -> PD/Alt-Mode entry -> link training + colorbars soak -> full core on bench -> IIgs slot regression -> second-unit replication. Includes both LED ladders, console checkpoints, and failure plays per stage.
Found on first hardware boot of board #1 (Stage 2, no SOM fitted): start_subsystems() bailed out when fpga_link_init() failed, BEFORE settings/WiFi/ftpd/telnetd ever initialized — so with no SOM (or with the colorbars bring-up bitstream, which has no OSPI service in fabric) the firmware sat in the OSPI retry loop forever and telnet never came up. That inverts the 1.0a3 design intent: WiFi/telnet is the PRIMARY console once a monitor occupies the USB-C port, and Stages 4-5 of the bring-up runbook depend on watching 'pd' over telnet while colorbars (OSPI-less) is loaded. - setup() now calls start_network() before start_subsystems(): settings_init + FS mount + WiFi-from-NVS + ftpd + telnetd, unconditionally. FPGA-side subsystems (OSD show, disk, menu, w5100, DDR3 telemetry, service tasks) stay behind the link probe and still late-bind when the link appears. - osd_console repaint() bails until fpga_link_ok(): boot-time osd_log lines buffer (and tee to telnet) instead of writing through an uninitialized SPI device; osd_console_show() replays the buffer when the link comes up. All other a2spi entry points already guard on a2spi_is_ready(). - OSPI retry message rate-limited (1 per ~30 s) — permanent-miss is the normal, expected state under bring-up bitstreams. usbc_pd_init() was already FPGA-independent (runs first in setup) — PD/Alt-Mode + HPD work with any bitstream. Compiles clean at 48% flash. Bring-up log started: boards/a2mega/docs/bringup_1_0a3_log.md (board #1 Stage 1 PASS: VBUS 5.06 V, +5V 4.92 V, +3V3 3.31 V, 38 mA).
With no OSPI device (no SOM, or a bring-up bitstream without the OSPI service) register reads complete at the SPI-driver level but return floating-bus junk — live-observed on board #1 as 'DDR3: CALIBRATED (retries=254 seq=0xFE)' with NO SOM fitted (0xFE = floating pattern). status now prints 'FPGA link: UP/DOWN' and only shows DDR3 telemetry over a verified link, so bring-up readings can't masquerade as real.
Board #1 bring-up, flash-write saga (full narrative in docs/bringup_1_0a3_log.md). Root causes fixed: 1. Bit-bang JTAG vs USB-JTAG bridge pad ownership (GENERAL bug): while route_usb_jtag_to_gpio() has the pads matrix-routed to the USB-JTAG peripheral, gpio_config() does NOT reclaim them — every bit-banged edge is lost. fpgaerase read garbage IDCODE; menu self-update would have failed the same way with a PC attached. fpga_jtag_init_pins() now releases the bridge first (extern-C shims in a2fpga_jtag.cpp) and release_pins() restores it. 2. fpgaerase CLI never called fpga_jtag_init_pins() — bit-banged pins left as INPUTs by the bridge disconnect handler ('block 0x00000 TIMEOUT'). Now delegates to fpgaupdate_erase_bitstream_region() (new): keepsram SPI entry, header-block erase (128 KB, validated semantic), blank verify, leave WITHOUT reload so the SRAM fabric survives. fpga_jtag_flash_leave() added alongside reload(). 3. route_usb_jtag_to_gpio() now runs FIRST in setup(), before the slow network bring-up: with a corrupt flash image the GW5A MSPI retry loop kills the chain, and the only way in is JTAG activity during the boot window — an openFPGALoader retry loop at replug captures the TAP (SRAM-load payload proven: attempts 115/15/1). 4. telnet console: 'p' key = condensed PD status through the osd_log tee (usbc_pd_status_log) — the only live PD view once the monitor occupies the USB-C port. Removed a duplicate fpgaerase branch. Board #1 state: colorbars verified in SPI flash, heartbeat after power cycle PASS. Stages 1-3 closed.
…bility The vendored PD driver only implemented the source role (SPEC scoped out monitors that present Rp to charge their partner — which is what real USB-C monitors do). Board #1 stalled in 'device' with the VDM ladder never running. Implemented per the SPEC's own sketch: - Sink engine: PD RX enabled on sink attach (the FUSB302 driver never enabled the BMC TX driver in sink role either — a sink was mute), Source_Capabilities -> RDO Request (5 V, bounded by offer) -> Accept/PS_RDY -> SINK_READY. - DR_Swap both directions (we request it; we Accept the partner's) -> data role DFP -> the existing discover/enter/status/configure ladder runs unchanged. fusb302_set_data_role() flips the GoodCRC DATAROLE bit post-swap. Role-correct TX headers throughout (were hardcoded source/DFP). Sink-side Soft_Reset/hard-reset recovery, Get_Sink_Cap. - HW-verified on board #1: DP-ACTIVE SNK/DFP, both cable orientations, mux configured, HPD delivered (led ladder led[1]). Observability for the monitor-occupied port (telnet is the console): - telnet 'p' = usbc_pd_status_log (state + roles + CC/HPD/MUX). - telnet 'x' = TUSB1046A register dump. The DCI AUX snooper parses our DPCD writes off the wire: reg 0x12 read back LANE_COUNT_SET=2 — TI-silicon proof the FPGA's AUX transmit path works end-to-end. (Known issue: dump races the 1 kHz PD poller on the I2C bus — reads intermittently fail 0xEE; sample repeatedly. Mutex TODO.)
a2mega 1.0a3 hardware truth (board #1, first HW contact of the DP core): the AUX pair is AC-coupled per the TUSB1046A reference circuit (TI Fig 28) with mux-side bias per the datasheet's mandated AUX CM levels (AUXP 0-0.4 V, AUXN 2.7-3.6 V). The reference assumes a GPU-class AUX PHY with internal receive bias behind its own caps; a bare FPGA LVCMOS pin has none, so AUX RECEIVE is electrically unreachable (a <=1.38 Vpp reply can't cross LVCMOS thresholds, and the pair never crosses zero differentially at the mux-side CM). Transmit works — proven end-to-end by the mux's DCI AUX snooper decoding our LANE_COUNT_SET write (reg 0x12 = 0x02). BLIND_SINK=1 (parameter, default 0 = spec flow, chained dp_transmitter -> channel_management -> aux_channel): - FSM advances on a 100 us dwell after each request drains, instead of on reply bytes; reply timeouts don't reset it; training status assumed good; sink lane count assumed = source's. - aux_interface REPLY_TIMEOUT_TICKS param: blind callers use 400 us (spec-ish) instead of the historical 39999 ticks = 20 ms (the '400us' comment was stale) — sim caught the 20 ms/transaction walk colliding with the 0.5 s retry watchdog and never completing. - HPD gate: blind ladder holds in reset until hpd_present and restarts on every HPD rise (live-hit: ladder walked to 'established' against an empty connector, so a later-attached monitor never saw training). - New DPCD 0x600=D0 wake write before link config (state 0x31): the stock flow NEVER woke the sink (snooper: SET_POWER_STATE stayed 00); a monitor idling in low power post-Alt-Mode-entry ignores training. Snooper readback (0x12[6:5]==01) doubles as a per-build AUX wire-verification loop. Sim: tb_blind_aux (scratchpad) — held while HPD low, full walk with both training phases in ~12 ms, stable link, teardown on HPD loss. Status on hardware: PD/HPD/AUX-TX all verified; main-link lock still not achieved (monitor black) — suspects are SERDES serialization bit-order/polarity semantics (CSR blob unverifiable by inspection) and per-monitor tolerance. Fabric lane crossover (lane0->LN3, lane1->LN2) and .ipc invert config (both lanes) verified correct by inspection.
…folding DP core / colorbars top: - clk_symbol_out port on dp_transmitter; colorbars led[2] repurposed as an in-fabric line-rate check (clk_sym counted against the 50 MHz crystal over 1 s windows). HW-VERIFIED LIT: the GTR12 truly runs 2.7 Gb/s — clocking/QPLL/refclk eliminated as no-lock causes. - Blind-mode per-state dwell extended to ~1.3 ms (blind_dwell) so sinks get tens of ms of each training pattern. Tested: still no lock. - TX_WORD_MODE A/B scaffold in transceiver_bank_gowin: 4 serializer word conventions; modes 0 (straight) and 1 (full reversal) tested on hardware, no lock; modes 2 (symbol swap) and 3 (per-symbol reverse) remain. Default back to 0. ESP32 firmware: - REVERTED early route_usb_jtag_to_gpio() in setup() (broke the bridge for the whole boot: Serial.begin's CDC init reconfigures the USB peripheral over an early-routed bridge). - fpgaupdate_erase_bitstream_region: full 4 MB region with PER-BLOCK SPI-mode re-entry — empirically only the first block erase per entry completes; per-block entry converges (HW-verified: 64/64 blank, then flash.sh works first-try again). Slow (~4-8 min) but it is the corrupt-flash recovery of record together with the SRAM-load race. Stage 5 status (see bringup log): eliminated = PD/HPD/mux, AUX TX (TI-snooper-proven), D0 wake, line rate, training dwell, full-word bit order. Remaining = word modes 2/3, tx polarity-invert semantics (IP regen), swing/EQ/SI. Monitor+cable+sink proven good via MacBook.
ROOT-CAUSE CANDIDATE for the Stage 5 no-lock (overnight I2C investigation, user's hunch): in I2C mode the DPEQ strap pins double as address pins and are floated per SPEC — the chip latches the F/F 4-level default at reset, which is EQ setting 10 = 12.3 dB of DP receiver equalization (datasheet Table 7) on a few-cm trace needing ~1 dB. The linear redriver then re-drives a wrecked eye to the connector; the monitor never locks. Register dump evidence: 0x10/0x11 read 0xAA (setting 10, all four lanes) with EQ_OVERRIDE=0. Explains the MacBook control test (no mux in that path). The firmware never programmed EQ; SPEC.md always said I2C-mode EQ is register-settable. - hal_set_tusb1046: program DP0-3EQ_SEL = 0 (1.0 dB) BEFORE enabling lanes; set EQ_OVERRIDE in the General write. - telnet 'e': cycle EQ presets 0/3/6/10 (1.0/6.5/9.5/12.3 dB) live on the running link — A/B without reflashing. - I2C bus mutex in the glue's i2c_read/write (PD task vs CLI vs telnet dumps raced; the 'x' dump's 0xEE noise). - PD: auto-retry DP discovery 2 s after a VDM-flake fallback to usb-only (sink+DFP keeps its contract; no physical replug needed). Not yet uploaded to board #1 (asleep-shift: board is on the monitor, serial upload needs the PC). Morning: upload + flash mode-0 colorbars + monitor test; then 'x' should show 0x10/0x11 = 0x00 and General bit4 set.
Per the approved debugging plan (plans/quirky-toasting-meteor.md):
- tx_data packing: {60'b0, code} -> {4{code}} (Sipeed SFP+ idiom). The
width-20 GTR12 slice selection of the 80-bit bus was never verified
(sims used the behavioral stub, not the IP netlist); if the macro
reads any slice other than [19:0] we have been transmitting constant
zeros through every experiment — which would explain the total
invariance of all eight content-side A/Bs and the monitor never once
reacting. Replication covers every possible static slice. CSR
restored to generator-original (polarity invert bits back to 0x408).
- serdes_status export: {pll_lock, lane_ready[1:0], ~pcs_tx_rst,
tx_running[1:0]} from transceiver_bank_gowin through dp_transmitter
(ties 0x3F on non-Gowin builds). These reset-sequencer gates have
never been observed — clk_sym at 135 MHz does NOT imply the lane
transmits (PCS clock runs with TX still in reset).
- Debug UART (colorbars top, H13 -> ESP32 Serial1): one ASCII status
line ~2x/s (serdes_status, dp debug byte, frame counter, HPD/link/
video flags); firmware tees Serial1 lines into the telnet console.
FPGA-side status finally readable over WiFi with the monitor on the
port. Word-mode scaffold left at 0 (canonical).
Both bitstreams timing-clean. TX_WORD_MODE eliminations and the
polarity A/B are CONDITIONAL results — if the slice or reset gating
was the fault, they tested a dead line (noted in the plan).
…ecoder THE finding of the bring-up (debug-plan Phase A2, first-ever live SERDES status readback over the new FPGA-UART->telnet channel): 'DP S:20' = pll_lock=1, lane_ready=00, pcs_tx_rst STILL ASSERTED. The transmitter was held in reset for the entire bring-up — the lanes never carried one bit while every upstream indicator (PD, HPD, AUX TX, 135 MHz symbol clock, blind-ladder LEDs) looked healthy. The reset sequencer gated pcs_tx_rst release on lane_ready, which the GTR12 never asserts in this TX-only config (the working Sipeed SFP+ example releases both resets statically and ignores ready_o entirely — it is wired to nothing there). Fixes/changes: - transceiver_bank_gowin: ungated static reset release (~330 us after powerup); tx_running is status-only. Post-fix readback: S:24 (PCS out of reset; lane_ready stays 0 = status-only, matching the example). - TX FIFO wren gated on ~afull (example idiom; was constant-1). - TX_PROBE parameter (bank + dp_transmitter + test top): forces the quad powered and transmits a raw ~4.2 MHz square on both lanes for breakout-board + basic-scope lane presence/mapping/polarity checks. Probe bitstream stashed in scratchpad; test top back at TX_PROBE=0. - tools/aux_decode.py: Manchester-II decoder for AD2 CSV captures of the AUX channel — reads the monitor's replies (ACK/NAK, DPCD link status) that the FPGA electrically cannot; labels known DPCD addrs. All content-side eliminations from before this fix are VOID (dead line). Still no picture after the reset fix alone — remaining suspects: slice packing (4x replication now in), polarity semantics, content; the probe + AUX captures discriminate next.
…A/B prep
- usbc_glue: telnet 'f' toggles the CC-orientation->FLIPSEL convention
live and rewrites mux General in place. FLIPSEL steers BOTH the
SBU/AUX crossbar and the SS-lane crosspoint: an inverted convention
crosses AUX polarity at the sink AND parks the lanes on unwatched
connector pins — while every register readback, the AUX snooper
(which sits UPSTREAM of the crossbar), PD, and HPD all stay green.
Candidate explanation for the remaining no-lock; TI's USB-C DP
debugging guide (user-supplied) motivated the crossbar focus.
- serdes_status widened to 8 bits: TX FIFO afull/full visible on the
debug UART line (is the PCS consuming symbols?).
- Word modes 1-3 rebuilt WITH the reset-deadlock fix (all prior
word-mode results were against dead lanes) — stashed in scratchpad
as mode{1,2,3}_live.fs for slot-powered SRAM A/B.
- Polarity CSR restored to generator-original (0x408 both lanes);
live-lane polarity A/B completed: both settings no-lock (caveat:
bit identity still inferential — IDE regen pending).
- tools/aux_decode.py minor: (added earlier) AUX Manchester decoder.
- In-slot SRAM iteration workflow proven: IIgs slot power holds the
fabric through USB swaps; blank flash makes SRAM loads first-try.
…cause Two days of JTAG 'wedges' (works first-op-after-replug, dies on op two, retry-races eventually win, ~50%+ failure) were one line of over-clever code: bridge routing was edge-triggered on usb_serial_jtag_is_connected(), an SOF-activity heuristic that macOS USB autosuspend defeats whenever no host process holds a port open — suspend -> unroute -> the next openFPGALoader open races the polled re-route and loses. Correlations explained: successes tracked whatever held a serial port open (esptool, cat sessions); the 1.0a2a-era jtag_flash_reliability.md 'cold boot ritual' was very likely the same disease. Fix: route once in setup() after USB init, never unroute (bit-bang path does its own pad handoff). Acid test: 10/10 detects with 4 s idle gaps — previously ~1-2/10. Also audited per user challenge: firmware JTAG pins verified against the 1.0a3 netlist (TDO=39/TCK=40/TMS=41/TDI=42, module pads 35-38, all match); eFuses read (STRAP_JTAG_SEL=0 — the IO3 strap is inert for JTAG; pad-JTAG contention theory retired). Rides along: aux_channel debug_pmod now = ladder FSM state; colorbars status line gains clk100-liveness bit (HLVC) — every future test self-certifies its clocking and ladder progress.
Session results (every test now self-certifies via the status line: D=ladder FSM state, S=SERDES+FIFO, P=hpd gate, E=HPD edges, C=clk100): - Serializer msg_idx widened 5->6 bits (37-char line truncated at 5 — 'DP S:' fragments); hpd_present + raw-HPD-edge-count instrumented through cm/dp_transmitter to the status line. - CERTIFIED eliminations on live, established links (D:2E observed during every test): word modes 0,1,2,3; FLIPSEL both directions (live toggle, twice); with EQ=1.0dB, D0 wake, long dwells, 4x bus replication all active. Screen asleep throughout. - Intermittent ladder-freeze root progress: PROVEN a per-boot race (same bitstream boots to D:00-pinned or D:2E across power cycles; gate open P:1, clk100 alive, no HPD edges when stuck). Open issue — detectable now; reboot clears. - PD ladder auto-retry observed working in the field (ALT MODE RESPONSE TIMEOUT -> DP-ACTIVE with no replug). Remaining hypothesis space (unreachable from software): TX polarity bit semantics (IDE regen A/B), analog/swing, deeper GTR12 config semantics. Next: AD3 + USB-C/DP breakouts — first capture reads the monitor's LANE0_1_STATUS off SBU: CR_DONE=1/SYM_LOCK=0 => polarity; CR_DONE=0 => electrical. tools/aux_decode.py ready.
Verified design-arounds for real AD3 limits: - AUX must be captured on the ANALOG channels: the digital inputs' ~1.4 V threshold can read both AUX lines as constant (P idles 0.3 V swinging up <=1.38 V; N idles 3.0 V dipping to >=1.6 V). 14-bit analog + software thresholding instead. - Whole conversations exceed the capture buffer: Record mode streaming at 4 MS/s (4 samples per 1 us Manchester cell). tools/ad3_aux_capture.py — WaveForms SDK (ctypes, framework path inside WaveForms.app verified, dwf 3.25.1 loads + enumerates): Record-mode 1-2 channel capture to CSV. tools/ad3_lane_probe.py — buffer capture with automated verdict: lane ALIVE/DEAD (Vpp), probe-pattern period check, and POLARITY from duty cycle. aux_decode.py — differential 3-column CSV support + adaptive threshold. Probe pattern reworked for flywire probes + polarity readout: 128-word frame, 96 high / 32 low = ~1.05 MHz at 75% duty (P-true leg reads 75%, inverted reads 25%). dp_lane_probe_v2.fs stashed; test top back at TX_PROBE=0.
…LOSED — GTR12 TX pads silent - usbc_glue: disable AUX snooper lane power-gating on DP entry (datasheet 8.3.2: snoop-enabled mux mutes all lanes until LANE_COUNT_SET is snooped — any build without a completed AUX ladder transmitted into a muted mux) - AD3 evidence: mux DP0 output pair drives 1.83 V TX CM with zero data on a fresh verified TX_PROBE=1 build -> FPGA SERDES pads have never driven; every prior content A/B was void. Critical path: IP regen (task C). - ad3_lane_probe.py: 3 s range-relay settle, DC-mean reporting (the measurement that split mux-alive from FPGA-dead) - top: TX_PROBE=1 (matches flashed bitstream)
…; provenance settled - 0x809x68 bit6 patch (tang parity) flashed+probed: pads still silent; reverted - IPUG1024 archived in boards/a2mega/docs/: TX_ONLY = legit operation mode; ready_o = TX channel status; DRP timing documented (readback bridge viable) - Provenance: entire DP stack incl. both SERDES configs is first-silicon; a2_mega CSR is authentic GUI-generator output (2026-07-19 session)
…f-design validates fabric idiom; UG718 archived
…d) — draw is sub-word, no fabric cancellation target; B (ESP32-JTAG reload fw) now main line
…doc (thesis: reference-design framing dissolves the marginality). Key finds: USB-C/PD/mux already Pin-C/DP4 = zero changes; DP core is param-only 1/2/4-lane + rate (LINK_RATE_MBPS/270 auto RBR byte); real work = transceiver_bank 4-lane extension + pixel-PLL re-source off the now-81MHz symbol clock
… branch (die ln3/2/1/0 <- ML0/1/2/3, clock master stays ln2 so the SDC anchor pin is unchanged), encoder generate loop, MSA 4ch branch wired (was a silent gap — LANE_COUNT=4 would have used the 2ch inserter), pixel PLL ratio parameterized for the 81MHz symbol clock (IDIV1/MDIV11/ODIV6 -> VCO 891 -> 148.5MHz, DP synchronous-clock mode PRESERVED — earlier scope doc claim to re-source it was wrong), dp_test params + SDC 12.346ns. All 103 4-lane port bindings verified against a generated stub: zero errors. Awaiting IDE regen of the PHY for 4 lanes @1.62G
…x_pol_invert x4, txlev 13, un-bonded (self-mastered x4), REFCLK1@135/QPLL0, DRP on. Emission verified lane-by-lane
…s build compiled the STALE 2-lane ROM — watchdog replay would have rewritten 2-lane/2.7G values onto the 4-lane config; caught before flash)
…s 525 writes, overflowing the 9-bit index (entries 512-524 would alias to 0-12); generator emits [9:0] indices now
…ymbol clock, pixel PLL 891MHz VCO, 525-write replay ROM w/ 10-bit index. Timing 0/0
…mask correctly refused 4-lane vs the hub's honest 2-lane DPCD — Anker A8365 teardown confirms 2-lane architecture: IT6563 fed 2 lanes, USB3 runs alongside; chip is 4-lane capable but hub wiring isn't). Test vehicle = monitor-direct 4-lane RBR blind. Timing 0/0
…nks never show readable SINK_STATUS — v1 tore down the healthy blind link every 8s, W cycling on hw, invalidating the monitor run). Timing 0/0
… hub class is 2-lane+USB3; 4-lane systematically dark on monitor AND on a native-DP USB2 adapter = our 4-lane datapath implicated, maiden flight never sim-audited). IP/defines/SDC/top restored byte-for-byte from tag a2mega-2lane-planc-4of5; replay ROM back to 357; kept: blind-gated watchdog, parameterized PLL (identical defaults), dormant 4-lane RTL branch, 10-bit replay idx
…(no user-facing reconfig ever; 2-lane HBR production; tier ladder), revert complete
…ven 2-lane HBR config (monitor-direct gauntlet: was the blind streak luck?)
…6ns on the debug freq-checker path sym_delta->freq_ok — GW5AT PnR variance, non-functional path; rebuilding)
…two registered 28-bit compares then AND — the single-cycle pair missed 135MHz by ~70ps on some seeds); timing 0/0
…eudo-diff 2Vpp pad, cst reverted to separate LVCMOS33 pins; TLVDS-pad monitor confound identified: monitor never lit on ANY TLVDS build, all monitor successes were 2Vpp-era; 0/2 dark on the TLVDS-blind build was ambiguous)
…actual streak-era config) + pseudo-diff pad + blind. Hypothesis under test: chained-vs-unchained interlane profile is SINK-dependent (monitor lit only on chained; hub measured 4/5 on un-chained)
…(FPGA .fs byte-identical across Friday's good period; only ESP32 firmware changed, incl PD source-role rewrite). SOM#2/Saturday-firmware fully confounded. Faithful test = rebuild+upload end-of-Friday firmware 98461af (its .fs=5d8e15f0 already flashed)
…lights the monitor on SOM#2 — HARDWARE EXONERATED, monitor-direct regression is Saturday ESP32 firmware. Orientation-dependent (one plug flip works) => CC/FLIPSEL/mux-flip suspect. Bisect 98461af..HEAD next session
…eplug = full reconfig each time) — streak was REAL not luck; reframes boot-lottery as possibly hub/firmware-specific not fixed silicon tax. Re-test hub w/ Friday fw next session
edanuff
added a commit
that referenced
this pull request
Sep 4, 2026
…r) for the schedule-ROM address and the pf_* precompute compares.
nc2/s0n2/s1n2 were two adders + two wrap muxes deep off the line_cycle
flops and fed BOTH the BSRAM schedule-ROM address and the pf_* compare
chains: 'line_cycle -> sched_rom AD[13]' (-0.060) and 'line_cycle ->
pf_vb1/RESET' (+0.016) were the two worst clk_sym paths left in the 60K
after the FIFO skid. nc2_r is now a counter in its own right sharing
line_cycle's zero/increment control (lc_zero/lc_inc — the same terms that
already drive line_cycle's own reset), with its own shallow wrap compare
and a reload to 2 whenever line_cycle reloads to 0; s0n2/s1n2 are pure
wiring off it ({nc2_r,0} / {nc2_r,1}). A first formulation that
recomputed nc2 from line_cycle's next value put a five-way mux + two
wrap-adders on the flop's reset pin and drew -0.055 in a full-core roll —
hence the counter form. Sim-only $fatal proves nc2_r/s0n2_r/s1n2_r equal
the combinational values at every clock. Full-chain harness both packer
configs: identical to baseline (3,037,500 cycles no underrun, 720 rows
pixel-exact, ALL AUDIO CHECKS PASSED); the assertion never fired.
edanuff
added a commit
that referenced
this pull request
Sep 4, 2026
…k_sym +0.36/+0.41/+0.10) and (b); remaining-cone list updated (floor = wr_fifo DI clk_logic; overlays/framebuffer clk_pix; DOC; AUX; sdp)
edanuff
added a commit
that referenced
this pull request
Sep 4, 2026
… (nc2_r counter); dp_test 0/0, 60K rolls 0/0/0 Timing round 2 #2: packer registered two-step lookahead (nc2_r counter) — dp_test 0/0, 60K rolls 0/0/0
edanuff
added a commit
that referenced
this pull request
Sep 4, 2026
…ares split into registered 14-bit half results, combined a cycle later. sym_delta -> freq_ok was dp_test's last violating family after cones #1/#2 (-0.52 .. -1.62 across deterministic draws): two 28-bit compares in one 135 MHz cycle on a value that is static for a full second. Now each compare is A>B <=> Ah>Bh || (Ah==Bh && Al>Bl) with the half results registered, combined next cycle — three cycles of latency on a 1 Hz instrument (led[2], telemetry F:), zero behavior change. dp_test-only; verified by the deterministic gate (0 freq_ok endpoints, 0/0 overall).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
The complete a2mega 1.0a3 DisplayPort bring-up campaign (134 commits): from AUX closed-loop training first light through boot-reliability characterization, flash tooling hardening, and the recovery/diagnostic infrastructure. The branch preserves granular history of the debugging arc; the bringup log (
boards/a2mega/docs/bringup_1_0a3_log.md) is the narrative index.Highlights
DP link (2-lane HBR, production config)
Boot-draw characterization (the residual 1-in-5)
Flash reliability (GW5A + esp_usb_jtag)
flash_rescueSRAM core: MSPI-as-user-IO probe/erase with telnet telemetry — deterministic ~3 min recovery path, used throughouttools/openfpgaloader-a2mega.patch): honest empty-chain reporting, freq-honored flash phase, detect retriesDiagnostics preserved for reference
docs/gowin_outreach.mdKnown open items (queued as tasks, documented in the log)
98461af3..HEAD)Reviewer notes
impl/pnr/*.fs) are committed intentionally — bitstream provenance (sha256 in commit messages/log) was load-bearing for the flash forensics and the Friday-binary bisectionhdl/displayport/is the vendored DisplayPort_Verilog core with local modifications;boards/a2mega/hdl/gowin/60B/dp_serdes/is IDE-generated IP (regenerate via the IDE, sidecars must stay together)98461af3), not HEAD — see the two-persona note in the log