Enable in-container USB flashing + flash-re-bba-rb skill; find U7 EEPROM undersized

Devcontainer now grants libusb access to the FT2232H so iceprog (bitstream) and
pyftdi (FT2232H EEPROM) run inside the container:
- devcontainer.json: --privileged + /dev/bus/usb bind mount
- Dockerfile: sudo (NOPASSWD for vscode; USB nodes are root-owned), libusb-1.0-0,
  libftdi1-2, pyftdi

Add the flash-re-bba-rb skill documenting the procedure (bitstream via best swept
seed, optional EEPROM string, troubleshooting, guardrails) and
flash_ftdi_eeprom.py (pyftdi, backup-first, dry-run default) + ftdi_eeprom.conf
value spec.

Bring-up on the repaired V1 unit: bitstream flash VERIFY OK (iceprog
build/seed4/top.bin, capture 58.36 MHz). EEPROM product string CANNOT be
programmed on V1 — U7 is a 93LC46B (128 B), too small for the FT2232H, which
needs a 93LC56B (256 B); the 128 B chip mirrors and the config write fails
verify. This overturns the old REVIEW note "93LC46B = correct for FT2232H"
(that only checked ORG, not size). Board still works blank -> ROM defaults;
iceprog + UART unaffected. Documented in REVIEW.md + TODO.md (swap U7->93LC56B
for V2); flash_ftdi_eeprom.py detects the mirroring and refuses the write.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-22 14:44:43 +00:00
parent 64123014bf
commit 1a7f0689ab
9 changed files with 404 additions and 6 deletions
+21 -3
View File
@@ -74,8 +74,23 @@ datasheet extraction, WebSearch/WebFetch for parts and reference designs.
change); TEST→GND, AGND→GND.
- **EEPROM 93LC46B (U7)**: interface **was wrong** (DO+DI both direct to
EEDATA) → fixed per DS Table 3.4: EE_DO net, R30 2.2k (C25879) to EE_DATA,
R31 10k (C25744) pull-up to 3V3, DI direct. 93LC46B = fixed 16-bit org =
correct for FT2232H. Verified in netlist.
R31 10k (C25744) pull-up to 3V3, DI direct. 93LC46B = fixed 16-bit org (that
part of the earlier verdict stands). **Interface wiring verified in netlist.**
- **⚠️ U7 PART IS UNDERSIZED — found at bring-up 2026-08-22 (overturns the old
"93LC46B = correct for FT2232H" note, which only checked ORG, not size).**
The 93LC46B is **1 Kbit = 128 bytes**; the FT2232H addresses a **256-byte**
EEPROM (`max_eeprom_size=256`) and its config does not fit in 128 B. FTDI
specs a **93LC56B (256 B)** or 93LC66B for the H-series — the 93LC46 is for the
FT232R / FT2232**D**. Empirical evidence (pyftdi on the repaired V1 unit): the
128-byte chip mirrors into the upper half (`has_mirroring=True`); a config
write mirror-clobbers word 0x80→0x00 and **fails verify at word 0x02**; and
`set_property` indexes past byte 128. **Consequence:** the "re-BBA-rb" product
string CANNOT be stored on V1. **Not a functional defect** — with U7 left
blank the FT2232H uses ROM defaults ("Dual RS232-HS", 0403:6010), and iceprog
(bitstream flash — verified OK on this unit) + the channel-B UART work
normally. **Fix for a future rev:** fit a 93LC56B at U7 (same SOIC-8, fixed
16-bit org, pin-compatible). Tooling: `hardware/re-bba-rb/flash_ftdi_eeprom.py`
detects the mirroring and refuses the write; `--erase` blanks U7.
- **EE_CS pull-down (R43, 10 kΩ) — ADDED 2026-07-31.** FT2232H DS Table 3.4
states EECS/EECLK/EEDATA are all **"Tri-State during device reset"**, so CS
floats while the FT2232H is in reset. The Microchip 93LC46B DS is explicit:
@@ -102,7 +117,10 @@ datasheet extraction, WebSearch/WebFetch for parts and reference designs.
CDONE=ADBUS6, CRESET=ADBUS7 (verified against iceprog.c; old ADBUS3/ACBUS0/
ACBUS1 now NC). R11/R12 10k pull-ups on CRESET/CDONE intact.
- **EEPROM contents decision**: ship blank (default 0403:6010 = what iceprog
opens). Documented in TODO.
opens). Documented in TODO. **Update 2026-08-22:** on V1 this is now *forced*
blank, not just a choice — U7 (93LC46B) is too small to hold an FT2232H config
(see the U7-undersized note above), so the product string can't be programmed
until a 93LC56B is fitted. Blank is the correct, working state.
## 4. FPGA sheet (`Fpga.kicad_sch`) — FULLY REVIEWED, PASS
+9
View File
@@ -358,6 +358,15 @@ Major work this session:
## Minor / BOM
- [ ] **U7 EEPROM undersized — swap 93LC46B → 93LC56B for V2 (found at bring-up
2026-08-22).** The 93LC46B is 128 B; the FT2232H needs ≥256 B (93LC56B /
93LC66B) to hold its config, so the "re-BBA-rb" USB product string can't be
programmed on V1 (write mirrors + fails verify — see REVIEW.md "U7 PART IS
UNDERSIZED"). **Not a functional blocker:** V1 ships with U7 blank → FT2232H
ROM defaults ("Dual RS232-HS", 0403:6010); iceprog bitstream flash (VERIFY
OK on the repaired unit) and the channel-B UART are unaffected. 93LC56B is
the same SOIC-8 / fixed-16-bit-org, pin-compatible drop-in. Pick a JLC part
(e.g. 93LC56BT-I/SN) when respinning.
- [x] RSET_BG 12.4 k — ACCEPTED 2026-07-18 (closed, no change): datasheet
says 12.3 kΩ, but that is an E192 value with no JLC Basic part —
WIZnet's own reference synthesizes it as 12 k + 300 Ω. The 0.8 %
+183
View File
@@ -0,0 +1,183 @@
#!/usr/bin/env python3
"""Program the re-bba-rb FT2232H EEPROM so the unit enumerates as "re-BBA-rb".
Uses pyftdi (libusb) — the in-container tool, since debian's libftdi1-2 ships
only the runtime lib, not the `ftdi_eeprom` binary. The intended descriptor
values mirror hardware/re-bba-rb/ftdi_eeprom.conf.
SAFE BY DESIGN:
* Keeps the stock VID/PID 0403:6010 and the dual-channel config, so iceprog
(MPSSE on channel A) and the channel-B UART keep working unchanged — only
the descriptor STRINGS change.
* DRY-RUN BY DEFAULT: prints the diff and writes a raw backup, but does NOT
touch the EEPROM unless you pass --commit.
* Always dumps the current EEPROM to a backup file first.
Usage (inside the rebuilt devcontainer, FT2232H usbipd-attached):
sudo /opt/venv/bin/python hardware/re-bba-rb/flash_ftdi_eeprom.py # dry-run + backup
sudo /opt/venv/bin/python hardware/re-bba-rb/flash_ftdi_eeprom.py --commit # write strings
sudo /opt/venv/bin/python hardware/re-bba-rb/flash_ftdi_eeprom.py --erase --commit # blank U7
⚠️ re-bba-rb V1 HARDWARE LIMITATION: U7 is a 93LC46B (128-byte / 1 Kbit) EEPROM,
which is TOO SMALL for the FT2232H — the H-series needs a 93LC56B (256 B) or
93LC66B (the 93LC46 is for the FT232R / FT2232D). The FT2232H config mirrors in
the 128-byte chip and the string write fails verify, so the "re-BBA-rb" product
string CANNOT be programmed on V1. This script therefore REFUSES the write when
it detects mirroring (use --force to override, --erase to blank U7). The board
is fully functional without it: it enumerates on ROM defaults ("Dual RS232-HS",
0403:6010) and iceprog + the channel-B UART work normally. Fix in a future rev
by fitting a 93LC56B at U7.
Flash the FPGA bitstream FIRST and confirm iceprog works — the string is purely
cosmetic. Do NOT rely on reset_device(): it re-enumerates the FT2232H and drops
the usbipd attachment on WSL2 — re-attach with usbipd instead.
"""
import argparse
import sys
from pathlib import Path
VID = 0x0403
PID = 0x6010
URL = f"ftdi://0x{VID:04x}:0x{PID:04x}/1"
MANUFACTURER = "hashru"
PRODUCT = "re-BBA-rb"
SERIAL = "RBBARB001"
BACKUP = Path(__file__).with_name("eeprom-backup.bin")
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--commit", action="store_true",
help="actually write the EEPROM (default: dry-run only)")
ap.add_argument("--erase", action="store_true",
help="blank U7 to 0xFF (FT2232H falls back to ROM defaults)")
ap.add_argument("--force", action="store_true",
help="attempt the string write even if U7 reports mirroring "
"(undersized 93LC46B) — will likely fail/corrupt")
ap.add_argument("--url", default=URL, help=f"pyftdi device URL (default {URL})")
args = ap.parse_args()
try:
from pyftdi.eeprom import FtdiEeprom
except ImportError:
print("pyftdi not installed. In the devcontainer it is preinstalled; "
"otherwise: pip install pyftdi", file=sys.stderr)
return 2
eeprom = FtdiEeprom()
try:
eeprom.open(args.url)
except Exception as exc: # noqa: BLE001 - surface the real libusb error
print(f"Could not open {args.url}: {exc}", file=sys.stderr)
print("Is the FT2232H usbipd-attached, and are you running under sudo?",
file=sys.stderr)
return 1
# 1) Backup the current EEPROM image before any change.
try:
BACKUP.write_bytes(bytes(eeprom.data))
print(f"Backed up current EEPROM ({len(eeprom.data)} bytes) -> {BACKUP}")
except Exception as exc: # noqa: BLE001
print(f"WARNING: could not write backup ({exc}); aborting.", file=sys.stderr)
eeprom.close()
return 1
# --- Erase mode: blank U7 back to 0xFF (FT2232H then uses ROM defaults). ---
if args.erase:
return _erase(eeprom, args.commit)
# ------------------------------------------------------------------
# HARDWARE NOTE — U7 on re-bba-rb V1 is a 93LC46B (1 Kbit = 128 bytes).
# The FT2232H addresses a 256-byte EEPROM and its config does NOT fit in
# 128 bytes: pyftdi sees the 128-byte chip mirror into the upper half
# (has_mirroring=True), the config write mirror-clobbers word 0x80->0x00,
# and verify fails. FTDI specs a 93LC56B (256 B) / 93LC66B for the H-series
# (the 93LC46 is for the FT232R / FT2232D). So the product string CANNOT be
# stored on V1 — the board still works fine on ROM defaults ("Dual RS232-HS",
# 0403:6010; iceprog + the UART are unaffected). Refuse rather than
# half-write (which corrupts U7 and needs an --erase recovery).
# ------------------------------------------------------------------
if eeprom.has_mirroring and not args.force:
print("\nREFUSING: U7 reports EEPROM mirroring — it is a 128-byte chip "
"(93LC46B), too small for the FT2232H's 256-byte config.\n"
"The 're-BBA-rb' product string cannot be stored on this board "
"(see the header comment / REVIEW.md). The board works fine on ROM "
"defaults. Use --erase to (re)blank U7; --force to attempt anyway.",
file=sys.stderr)
eeprom.close()
return 3
# Stage a COMPLETE FT2232H default config, then overlay our strings.
print(f"\nEEPROM currently blank: {eeprom.is_empty} — staging FT2232H defaults.")
eeprom.initialize()
cfg = eeprom._config # decoded config dict (no public accessor in pyftdi 0.57)
print("\nCurrent (staged-base) descriptors:")
print(f" vendor_id = 0x{cfg.get('vendor_id', 0):04x}")
print(f" product_id = 0x{cfg.get('product_id', 0):04x}")
# Stage the new strings (VID/PID/channel config untouched).
eeprom.set_manufacturer_name(MANUFACTURER)
eeprom.set_product_name(PRODUCT)
eeprom.set_serial_number(SERIAL)
print("\nStaged descriptors:")
print(f" manufacturer = {MANUFACTURER!r}")
print(f" product = {PRODUCT!r}")
print(f" serial = {SERIAL!r}")
if not args.commit:
print("\nDRY-RUN — nothing written. Re-run with --commit to program.")
eeprom.close()
return 0
try:
eeprom.commit(dry_run=False)
except Exception as exc: # noqa: BLE001
print(f"\nWRITE FAILED ({exc}). U7 may be partially written — run with "
"--erase to blank it back to ROM-default fallback.", file=sys.stderr)
eeprom.close()
return 1
eeprom.close()
# NOTE: do NOT call reset_device() — it re-enumerates the FT2232H, which
# drops the usbipd attachment on WSL2. Re-plug / re-attach manually instead.
print("\nEEPROM written. Re-attach USB (usbipd) so the host shows 're-BBA-rb'.")
return 0
def _erase(eeprom, commit: bool) -> int:
"""Blank U7 to 0xFF so the FT2232H falls back to ROM defaults.
On the mirrored 128-byte chip pyftdi's 256-byte verify trips even though the
0xFF writes land, so we loop and check the actual content instead of trusting
commit()'s verify.
"""
if not commit:
print("\n--erase DRY-RUN: would write 0xFF over U7. Add --commit to do it.")
eeprom.close()
return 0
for attempt in range(5):
data = bytes(eeprom.data)
nonff = sum(1 for b in data if b != 0xFF)
print(f"erase attempt {attempt}: non-0xFF bytes = {nonff}")
if nonff == 0:
print("U7 is fully blank — FT2232H will use ROM defaults.")
eeprom.close()
return 0
eeprom.erase(0xFF)
try:
eeprom.commit(dry_run=False)
except Exception as exc: # noqa: BLE001 - mirrored-chip verify trips; writes still land
print(f" (commit verify raised, expected on mirrored chip: {exc})")
# Re-read fresh next loop without reset_device() (which drops usbip).
eeprom.sync()
print("Could not fully blank U7 after retries — re-run --erase after re-attach.",
file=sys.stderr)
eeprom.close()
return 1
if __name__ == "__main__":
raise SystemExit(main())
+45
View File
@@ -0,0 +1,45 @@
# FT2232H EEPROM config for the re-bba-rb board (libftdi `ftdi_eeprom`).
#
# NOTE: the devcontainer does NOT ship the `ftdi_eeprom` binary (debian's
# libftdi1-2 is the runtime lib only). Inside the container program the EEPROM
# with the pyftdi script instead — it applies the SAME values as this file:
# sudo /opt/venv/bin/python hardware/re-bba-rb/flash_ftdi_eeprom.py --commit
# This .conf remains the value spec, and works as-is with `ftdi_eeprom` on a host
# that has the libftdi tools, or as a reference when using FT_PROG on Windows.
#
# Purpose: give the on-board FT2232H a product string so the unit shows up as
# "re-BBA-rb" instead of the generic "Dual RS232-HS".
#
# SAFE BY DESIGN — this KEEPS the stock VID/PID (0403:6010) and the default
# dual-channel config, so `iceprog` (MPSSE on interface A) and the channel-B
# UART keep working exactly as before. Only the descriptor strings change.
#
# ⚠️ Before writing: back up whatever is there (it should be blank/default):
# ftdi_eeprom --device i:0x0403:0x6010 --read-eeprom --to-file eeprom-backup.bin
# Then flash the bitstream FIRST and confirm iceprog works; program this
# EEPROM only after that's proven (the string is cosmetic).
#
# Program with:
# ftdi_eeprom --device i:0x0403:0x6010 --flash-eeprom ftdi_eeprom.conf
# (re-plug the USB afterwards so the host re-reads the descriptors.)
vendor_id=0x0403 # KEEP 0403 — iceprog/D2XX look for this
product_id=0x6010 # KEEP 6010 — FT2232H default; iceprog looks for this
max_power=500 # mA; board draws well under this over USB
manufacturer=hashru # <- your name/org; free text
product=re-BBA-rb # <- this is what makes it show as "re-BBA-rb"
use_serial=true
serial=RBBARB001 # <- optional; bump per unit if you want unique serials
# Bus-powered while flashing over USB (GC not attached). If you ever program
# it while powered from SP1 only, this is still fine — it's a hint to the host.
self_powered=false
remote_wakeup=false
# Leave the two channels at their FT2232H defaults (UART/serial). MPSSE (used
# by iceprog) is a runtime mode on interface A and works regardless, so there
# is no need to set cha_type/chb_type here.
filename=re-bba-rb-eeprom.bin # ftdi_eeprom writes the built image here too