CLAUDE.md/README/tutorial: optional BS_ENABLE_DIGILENT backend, why SMT2 modules need libdjtg, and the new jtag_open_probe index. Mark phases 2 and 2.5 done.
7.7 KiB
Project guide for Claude Code
This file is loaded automatically when working on bs_explorer in any
Claude Code session, on any machine where the repo is cloned. Keep
machine-specific or user-specific facts out of here — they belong in
~/.claude/ memory, not in the repo.
Project status, decisions, and roadmap below are durable: update them when reality changes, not for every transient task.
What this project is
bs_explorer is an application layer on top of Viveris's
jtag-boundary-scanner
library (LGPL). End goal: program SPI configuration memories attached
to FPGAs (Xilinx KU15P first, then others) over JTAG, from a CLI tool
running on a host with an FTDI probe.
The Viveris library itself lives unchanged in modules/. Everything
new is in bs/ (the REPL) and future modules (fpga/, bscan_spi/,
spi_flash/) sitting alongside the Viveris ones.
Architecture
bs/ Application (readline REPL, no business logic)
modules/ — Viveris's library (LGPL, unchanged) —
├── jtag_core/ TAP state machine, IR/DR shifts
├── bsdl_parser/ .bsd loader
├── bus_over_jtag/ SPI/I²C/MDIO/parallel mem bit-bang over EXTEST
├── drivers/ FTDI, J-Link, Linux GPIO, LPT, Digilent (optional)
├── script/ Script engine (40+ commands, the real UI)
├── config/ Built-in config.script
├── os_interface/ Portable fs/network wrappers
└── natsort/ Natural pin-name sorting
bsdl_files/ BSDL files for target FPGAs
scripts/ Example scripts
doc/ Tutorial and longer-form docs (doc/tutorial.md is the end-to-end walkthrough)
libs/libftd2xx/ Vendored FTDI SDK
User interacts with the project through the script engine commands
(jtag_*, set, if, …), exposed via the REPL or piped script files.
Adding a feature usually means adding a new script command in
modules/script/script.c and the supporting C in a new module.
Roadmap
| Phase | Module | Status | Summary |
|---|---|---|---|
| 1 | bs/ cleanup, REPL polish, README |
done (commit 7cb3627) |
Fix format-strings, delete dead code, tab-completion, banner |
| 2 | fpga/ |
done (commit 545fe09) |
Per-target descriptor (IDCODE, BSDL, IR codes, proxy path, quirks). Compile-time registry. |
| 2.5 | bscan_spi/ |
done (commit dec0d14) |
Load BSCAN proxy bitstream via CFG_IN, expose fast bscan_spi_xfer() via USER1. Required for realistic flashing speeds. |
| 3 | spi_flash/ |
planned | Chip database (JEDEC ID → page/sector/cmd set). Generic read/erase/program/verify over either backend. |
| 4 | script commands | planned | flash_detect, flash_read/write/erase/verify. |
Move forward phase by phase: validate one with the user before starting
the next. Don't break the validated path
jtag_open_probe → jtag_autoinit → jtag_set_mode 0 EXTEST while
refactoring.
Key technical decisions
Flashing path: BSCAN proxy, not EXTEST
The Viveris library has bus_over_jtag/spi_over_jtag.c which bit-bangs
SPI on FPGA pins placed in EXTEST. This is not usable for actual
flashing. Each SPI bit costs 2 full boundary-scan-register shifts
through JTAG, plus USB latency on the FTDI side. Order of magnitude:
~30 bytes/s effective, i.e. weeks to flash a 128 MB part. Useful
only for one-shot operations (read JEDEC ID, check wiring).
Real flashing path: load a small "BSCAN proxy" bitstream into the FPGA
fabric via standard JTAG configuration (CFG_IN). The proxy uses a
BSCANE2 primitive to bridge the JTAG USER1 instruction's DR shift
to the physical SPI pins, running at fabric speed (CCLK driven
internally — the STARTUPE3 problem disappears). Realistic throughput
50–200 KB/s, so ~10–40 min for 128 MB. This is what Vivado, OpenOCD's
jtagspi driver, and xc3sprog all do.
Per-FPGA descriptor
fpga_target struct (Phase 2) holds the per-target facts that can't be
derived from the BSDL alone:
idcode+idcode_mask— match the device on the chainbsdl_filename— BSDL to auto-loadcfg_in_ir_code,user1_ir_code,jprogram_ir_code— Xilinx-specific private IR opcodes (read from BSDL when available)proxy_bitstream_path— path to the BSCAN proxy.bitfor this partquirks— flags for known caveats (e.g. CCLK via STARTUPE3)
Registry is a compile-time array. Adding a part = one entry + its
.bsd in bsdl_files/ + its proxy .bit in bscan_proxies/.
Digilent SMT2 modules need libdjtg, not raw MPSSE
Several Xilinx dev boards (KCU105, ZCU102, …) embed a Digilent JTAG-SMT2 / SMT2-NC for USB-JTAG. Even though it presents a stock FTDI FT232H over USB (VID:PID 0403:6014), it runs a proprietary firmware that does not respond to plain MPSSE commands — TCK toggles but the level-shifters/buffers stay disabled, so TDO floats high ("all ones" symptom). Standard FTDI driver path is dead on these boards.
Workaround: modules/drivers/digilent_jtag/ wraps libdjtg/libdmgr
(Digilent Adept Runtime). Built only when -DBS_ENABLE_DIGILENT=ON,
loaded via dlopen at runtime — no Digilent binary or header in the
repo. End-user just needs the Adept Runtime package installed.
Xilinx caveats
On 7-Series / UltraScale / UltraScale+, CCLK is not a regular I/O
pin — it goes through the STARTUPE3 primitive. Cannot be driven
directly in EXTEST. Non-issue once we use the BSCAN proxy (CCLK is
driven by the fabric clock inside the proxy).
The FPGA must be in a configurable state before loading the proxy.
Issue JPROGRAM first to reset, then CFG_IN + shift the bitstream,
then JSTART and check DONE.
External references
- BSCAN proxy bitstreams:
quartiq/bscan_spi_bitstreams(BSD-2). Pre-built.bitfor most Xilinx parts; Migen sources to rebuild any part that's missing (needs Vivado). - Reference host-side implementation:
openocd/src/flash/nor/jtagspi.c. Defines the proxy protocol (header with bit count + CS state, then payload). Don't reinvent — match what OpenOCD does so we share the same bitstreams. - Viveris library docs:
modules/jtag_core/headers are well commented.modules/config/config.scriptdocuments every runtime variable.
Build & test
mkdir build && cd build && cmake .. && make
./bs/bs # interactive REPL
For Digilent SMT2-based boards, configure with
cmake -DBS_ENABLE_DIGILENT=ON .. and install the Adept Runtime
system-wide (provides libdjtg.so + libdmgr.so).
No automated tests yet. Smoke test = banner appears, exit works.
After changes touching jtag_core, drivers/ftdi_jtag, or the
autoinit flow, manual hardware test required: probe + KU15P board
should scan and load xcku15p_ffve1517.bsd.
The FTDI driver compiles with int-to-pointer-cast warnings (Viveris's code on 64-bit Linux). Don't fix in this repo — that's upstream.
Commit conventions
- Messages in English, lowercase first word, imperative or short descriptive (match existing style: "phase 1: cleanup, REPL polish, README", "translate README to English").
- Title ≤ ~70 chars. Body wrapped ~80 chars, with bullets for what changed. Mention the why for non-obvious choices.
- Sign-off block:
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> - One logical change per commit. Don't bundle README + bugfix + refactor.
What does NOT belong here
- User preferences (tone, language used in chat, "always do X first")
→
~/.claude/ - Machine-local facts (which probes are physically attached, paths
outside the repo, validated-on-this-machine notes) →
~/.claude/ - Transient task state, in-progress decisions → conversation, not file
- Generated artifacts (
build/, downloaded proxy.bitif we choose not to vendor) →.gitignore