Files
bs_explorer/CLAUDE.md
François 4329030ab9 doc: design note for extending to Lattice and Microsemi IGLOO2
Both hold config in internal flash (programmed directly over JTAG), so
the Xilinx external-flash+proxy path doesn't apply. Records two backend
strategies — native per-family (Lattice ISC / IEEE 1532) vs a generic
SVF/STAPL player (recommended for IGLOO2, whose algorithm is
proprietary) — what already generalises, and the registry/dispatch
changes needed.
2026-05-24 01:15:32 +02:00

245 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](https://github.com/viveris/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, caveats). 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/` | **done** (commit `c4afe87`) | Chip DB (JEDEC ID → page/sector/cmd set) + generic `read/erase/program/verify` over an `xfer` callback. detect+read validated on KCU105; erase/program implemented but not yet hardware-tested. |
| 4 | script commands | **done** (commit `d6f843e`) | `flash_detect`, `flash_read` (+file), `flash_erase`, `flash_write`, `flash_verify`. Full set validated on KCU105 (save/erase/write-random/verify/restore round-trip). ~100 KB/s write once the proxy is loaded. |
Move forward phase by phase: validate one with the user before starting
the next. Don't break the validated path
`jtag_open → jtag_autoinit → jtag_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
50200 KB/s, so ~1040 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 chain
- `bsdl_filename` — BSDL to auto-load
- `cfg_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 `.bit` for this part
- `caveats` — flags for known hardware gotchas (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), loaded via `dlopen` at runtime — no Digilent
binary or header in the repo. Because it's dlopen'd (degrades to "no
probe" if the libs are absent), it costs nothing to build in:
`BS_ENABLE_DIGILENT` defaults **ON** on UNIX (`<dlfcn.h>` required),
disable with `-DBS_ENABLE_DIGILENT=OFF`. Adept Runtime is only needed
at runtime to actually drive such a probe.
### 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`.
## Extending to other FPGA families (design note)
Not yet implemented — captured here so the design is ready when needed.
Targets in mind: small Lattice MachXO2/MachXO3 (PSU sequencing/glue) and
Microsemi/Microchip IGLOO2 / SmartFusion2.
**Key difference from Xilinx.** Our Xilinx path programs an *external*
SPI flash via a BSCAN proxy bitstream. Both Lattice MachXO2/3 and
Microsemi IGLOO2 instead hold their config in *internal* flash,
programmed *directly over JTAG* — no external flash, no proxy. So
`bscan_spi/` and `spi_flash/` do **not** apply to them; they need a
different programming backend.
**What already generalises (reuse as-is):**
- probe drivers, `jtag_core`, and the `bscan_set_ir` / `bscan_shift_dr`
primitives in `bscan_spi/` — vendor-neutral, and exactly what any
JTAG programming sequence drives;
- IDCODE detection + BSDL load (BSDL is a standard);
- the `fpga_target` registry concept and the script-command framework.
**Two strategies for the programming backend:**
1. **Native per-family backend.** Implement the vendor's JTAG flow on
top of our IR/DR primitives.
- *Lattice MachXO2/3*: the IEEE 1532 / ISC sequence — `ISC_ENABLE`
`ISC_ERASE``LSC_INIT_ADDRESS``LSC_PROG_INCR_NV` (page loop) →
`ISC_PROGRAM_DONE`, with BUSY polling. Well documented (Lattice
TN1204, IEEE 1532 BSDL `ISC_*` attributes) → very feasible. Needs a
`.jed` parser for the payload.
- *Microsemi IGLOO2*: the on-chip programming algorithm is
proprietary and complex (sys-services, eNVM/fabric) — reimplementing
it natively is a large, fragile effort. Not recommended.
2. **Generic SVF / STAPL player (higher leverage).** Execute the JTAG
programming file the vendor tool exports, instead of re-deriving the
algorithm. The vendor's algorithm is *baked into the file*.
- **SVF** = a flat list of `SIR`/`SDR`/`RUNTEST` ops → a small player
on `bscan_set_ir`/`bscan_shift_dr` covers it; vendor-neutral
(Lattice, Microsemi, Altera all export SVF).
- **STAPL** (`.stp`/`.jam`) = a full language (loops, conditionals) →
needs an interpreter, more work, but it's Microsemi's/Lattice's
native portable format.
- **For IGLOO2 this is the pragmatic route**: let Libero/FlashPro
export SVF (or STAPL) and just play it. One player → many vendors.
Recommendation: an **SVF player** is the single highest-value
addition — it unlocks IGLOO2 *and* most other JTAG parts at once,
with the vendor owning the algorithm. Add native Lattice ISC only if
a self-contained `.jed` flow (no vendor export step) is wanted.
**Registry / dispatch adjustments:**
1. `fpga_target` is Xilinx-flavoured today (`ir_cfg_in`, `ir_user1`,
`ir_jprogram`, …). Generalise it — per-family opcode sets, or a small
"programming method" tag (`proxy_spi` / `lattice_isc` / `svf`) that
selects the backend.
2. The existing `family` enum is the natural dispatch point
(`FPGA_FAMILY_XILINX_*`, add `FPGA_FAMILY_LATTICE_*`,
`FPGA_FAMILY_MICROSEMI_*`). An SVF player is family-agnostic and
needs no per-part opcodes at all.
**Scope caveat.** Lattice MachXO2/3 and IGLOO2/SmartFusion2 are genuinely
JTAG-programmable. iCE40 is usually programmed over SPI directly (or
one-time NVCM) with minimal JTAG — out of scope for this JTAG-centric
tool.
## External references
- **BSCAN proxy bitstreams**: `quartiq/bscan_spi_bitstreams` (MIT).
Pre-built `.bit` for many Xilinx parts; Migen sources to rebuild any
part that's missing (needs Vivado). Building a proxy that isn't
pre-built (e.g. the KU15P) is covered in `doc/tutorial.md`, Phase 2.5.
- **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.script` documents every runtime
variable.
## Build & test
```sh
mkdir build && cd build && cmake .. && make
./bs/bs # interactive REPL
```
The Digilent SMT2 backend is built by default on UNIX (disable with
`-DBS_ENABLE_DIGILENT=OFF`). To actually use such a probe, 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 `.bit` if we choose
not to vendor) → `.gitignore`