doc: refresh README/tutorial/CLAUDE for profiles, clock, SVF

Bring the docs up to date and keep each in its lane:
- README (overview): both programming paths (Xilinx proxy flash + SVF),
  probe profiles, neutral JTAG clock + per-device cap, runtime YAML
  registry, IGLOO2 bundled; run-from-repo-root fixed
- tutorial (user view): probe profiles + jtag_close, the prog tag, a
  JTAG-clock section, a new "Programming via SVF" section, prog/max_tck
  in the add-a-target table, troubleshooting rows
- CLAUDE.md (design): architecture tree lists the project modules + YAML
  data files; roadmap gains phases 5 (probes/JTAG-link) and 6 (SVF)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 14:50:02 +02:00
parent c77d86efd0
commit cc2ee5d92c
3 changed files with 199 additions and 47 deletions

View File

@@ -12,28 +12,40 @@ when reality changes, not for every transient task.
`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.
library (LGPL). End goal: program FPGAs/CPLDs over JTAG from a CLI tool
on a host with an FTDI/Digilent/J-Link probe — Xilinx external SPI
configuration flash via a BSCAN proxy (started with the KU15P), and
other families (Lattice, Microsemi, …) by playing a vendor-exported SVF.
The Viveris library itself lives unchanged in `modules/`. Everything
new is in `bs/` (the REPL) and future modules (`fpga/`, `bscan/`,
`spi_flash/`) sitting alongside the Viveris ones.
new is in `bs/` (the REPL) and the project modules (`fpga/`, `bscan/`,
`spi_flash/`, `svf/`, `probes/`) sitting alongside the Viveris ones.
## Architecture
```
bs/ Application (readline REPL, no business logic)
modules/ — Viveris's library (LGPL, unchanged) —
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)
├── drivers/ FTDI, J-Link, Linux GPIO, LPT, Digilent (optional, dlopen)
├── script/ Script engine (the real UI)
├── config/ Built-in config.script
├── os_interface/ Portable fs/network wrappers
── natsort/ Natural pin-name sorting
── natsort/ Natural pin-name sorting
— new (this project) —
├── fpga/ Registry loader (parses fpga_registry.yaml, libyaml)
├── bscan/ JTAG TAP primitives (set_ir/shift_ir/shift_dr/tap_reset/
│ idle_cycles) + BSCAN proxy (bitstream load, SPI-over-USER1)
├── spi_flash/ SPI NOR chip DB + read/erase/program/verify over a callback
├── svf/ SVF player (svf_play): SIR/SDR/RUNTEST/STATE, masked compare
└── probes/ Probe-config profiles loader (parses probes.yaml, libyaml)
fpga_registry.yaml FPGA registry (IDCODE → BSDL, IR opcodes, proxy, caveats, prog, max_tck)
probes.yaml Probe-config profiles (defaults + per-probe overrides)
bsdl_files/ BSDL files for target FPGAs
bscan_proxies/ BSCAN proxy bitstreams (MIT, from quartiq)
scripts/ Example scripts
doc/ Tutorial and longer-form docs (doc/tutorial.md is the end-to-end walkthrough)
libs/libftd2xx/ Vendored FTDI SDK
@@ -49,10 +61,12 @@ Adding a feature usually means adding a new script command in
| 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 | `fpga/` | **done** (commit `545fe09`) | Per-target descriptor (IDCODE, BSDL, IR codes, proxy path, caveats). Now a **runtime YAML** registry (`fpga_registry.yaml`, libyaml), later gaining `prog` method + `max_tck_khz`. |
| 2.5 | `bscan/` | **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. |
| 5 | `probes/` + JTAG-link | **done** | `probes.yaml` probe-config profiles (`jtag_open <idx> <profile>`, `jtag_profiles`, `jtag_close`); driver-neutral `JTAG_TCK_FREQ_KHZ`/`JTAG_RTCK`; device `max_tck_khz` clock cap resolved at `jtag_autoinit`; `prog` method tag. See the config-strategy design note. Validated on the IGLOO2 (FlashPro). |
| 6 | `svf/` | **done** (subset, commit `c77d86e`) | SVF player + `svf_play`: SIR/SDR with masked TDO compare, RUNTEST, STATE — single-device. Validated on the IGLOO2 IDCODE; a real Libero SVF and a generic `program` dispatch off the `prog` tag are still TODO. |
Move forward phase by phase: validate one with the user before starting
the next. Don't break the validated path

View File

@@ -1,10 +1,14 @@
# bs_explorer — Boundary Scan Explorer
Command-line tool to explore a JTAG chain, drive an FPGA's pins through
boundary scan (BSDL), and program the SPI configuration flash attached
to an FPGA (Xilinx and others) over JTAG — fast, via a BSCAN proxy
boundary scan (BSDL), and **program** parts over JTAG, from a host with
an FTDI / Digilent / J-Link probe. Two programming paths:
- **Xilinx external SPI configuration flash** — fast, via a BSCAN proxy
bitstream loaded into the fabric (~100 KB/s), or slowly via EXTEST pin
bit-bang for one-shot checks.
bit-bang for one-shot checks;
- **everything else** (Lattice, Microsemi, CPLDs, …) — by playing a
vendor-exported **SVF** file (`svf_play`), one near-universal backend.
Based on the [jtag-boundary-scanner](https://github.com/viveris/jtag-boundary-scanner)
library by Viveris (LGPL).
@@ -14,15 +18,17 @@ library by Viveris (LGPL).
- JTAG chain detection through FTDI / J-Link / Linux GPIO / Digilent SMT2 probes: OK
- Automatic BSDL loading by IDCODE: OK
- Pin control in SAMPLE / EXTEST, incl. slow SPI bit-bang: OK
- Per-FPGA registry (IDCODE → BSDL, IR opcodes, proxy, caveats): OK
- FPGA registry (runtime YAML: IDCODE → BSDL, IR opcodes, proxy, caveats, programming method): OK
- Probe-config profiles (`probes.yaml`) + driver-neutral JTAG clock with per-device cap: OK
- BSCAN proxy SPI bridge (load proxy bitstream, talk SPI via `USER1`): OK
- SPI flash detect / read / erase / program / verify: OK (~100 KB/s via the proxy)
- SVF player (`svf_play`) — program any device from a vendor-exported SVF: OK (single-device subset)
Bundled BSDLs: Xilinx Kintex UltraScale+ KU15P
(`bsdl_files/xcku15p_ffve1517.bsd`) and Kintex UltraScale KU040
(`bsdl_files/xcku040_ffva1156.bsd`). Add more by dropping `.bsd` files
in `bsdl_files/` (see [`doc/tutorial.md`](doc/tutorial.md) for adding a
target).
(`xcku15p_ffve1517.bsd`), Kintex UltraScale KU040 (`xcku040_ffva1156.bsd`),
and Microsemi IGLOO2 M2GL010T (`m2gl010t-fg484.bsd`). Add more by dropping
`.bsd` files in `bsdl_files/` plus an entry in `fpga_registry.yaml` (see
[`doc/tutorial.md`](doc/tutorial.md) for adding a target).
## Dependencies
@@ -56,15 +62,23 @@ cmake -DBS_ENABLE_DIGILENT=OFF ..
## Run
Run from the repository root so the runtime data files are found — they
are looked up relative to the current directory:
```sh
cd build
./bs/bs
./build/bs/bs
```
At startup, `bs_explorer` looks for a `config.script` file in the
current directory to override default settings (FTDI clock, TRST/SRST
pin mapping, etc.). See `modules/config/config.script` for the full
list of variables.
`bs_explorer` reads, when present in that directory:
- `config.script` — overrides built-in probe variables (FTDI clock,
TRST/SRST pin mapping, …); see `modules/config/config.script` for the
full list. Loaded at startup.
- `probes.yaml` — probe-config profiles, applied with
`jtag_open <idx> <profile>` (`$BS_PROBES` overrides the path).
- `fpga_registry.yaml` — the FPGA target registry
(`$BS_FPGA_REGISTRY` overrides the path).
- `bsdl_files/`, `bscan_proxies/` — BSDLs and proxy bitstreams.
## REPL
@@ -76,31 +90,39 @@ list of variables.
## Typical flow
**Xilinx external SPI flash — via the BSCAN proxy:**
```sh
# 1. List probes, open one by its index (the [N] in the list)
# 1. List probes, open one by its index ([N]). A probe that needs tweaks
# (e.g. an embedded FlashPro) takes a profile from probes.yaml:
bs_explorer> jtag_probes
[0] 0x00000000 <probe description>
bs_explorer> jtag_open 0 # or the raw 0x id shown next to it
bs_explorer> jtag_profiles # available profiles
bs_explorer> jtag_open 0 # or: jtag_open 0 <profile>
# 2. Scan the chain and auto-load matching BSDLs
bs_explorer> jtag_autoinit
bs_explorer> jtag_autoinit # fpga_info then shows the prog method
# 3. Load the BSCAN proxy into the fabric (fast SPI bridge)
bs_explorer> bscan_load_bitstream 0 bscan_proxies/bscan_spi_xcku040.bit
# 4. Talk to the SPI flash through the proxy
bs_explorer> flash_detect 0 # JEDEC ID -> chip name / size
bs_explorer> flash_read 0 0x0 256 # hex dump
bs_explorer> flash_erase 0 0x10000 4096
bs_explorer> flash_write 0 0x10000 image.bin
bs_explorer> flash_verify 0 0x10000 image.bin
```
**Anything else (Lattice, Microsemi, …) — play a vendor-exported SVF:**
```sh
bs_explorer> jtag_open 0 <profile> # e.g. flashpro for a Microsemi kit
bs_explorer> jtag_autoinit # identify; prog method should be 'svf'
bs_explorer> svf_play design.svf # exported from Libero / Diamond / Radiant
```
The slow EXTEST path (bit-bang SPI on boundary-scan pins, `jtag_mode 0
EXTEST` + `jtag_spi_*`) is only useful for one-shot checks — see the
tutorial. A minimal example script is in `scripts/example_script.txt`;
the full walkthrough (probe → proxy → flash) lives in
[`doc/tutorial.md`](doc/tutorial.md).
EXTEST` + `jtag_spi_*`) is only useful for one-shot checks. A minimal
example script is in `scripts/example_script.txt`; the full walkthrough
lives in [`doc/tutorial.md`](doc/tutorial.md).
## Main commands
@@ -159,6 +181,8 @@ modules/
├── fpga/ Registry loader (parses fpga_registry.yaml at runtime)
├── bscan/ JTAG TAP primitives + BSCAN proxy (bitstream, SPI-over-USER1)
├── spi_flash/ SPI NOR chip database + read/erase/program/verify
├── svf/ SVF player (program from a vendor-exported SVF)
├── probes/ Probe-config profiles loader (probes.yaml)
├── script/ Script engine
├── config/ Built-in config.script
├── os_interface/ Portable fs/network wrappers

View File

@@ -1,9 +1,16 @@
# Tutorial — from probe detection to SPI flash
# Tutorial — from probe to programmed part
This walks through the full `bs_explorer` flow on a Xilinx Kintex
UltraScale+ KU15P board connected via an FTDI MPSSE probe. The
commands are identical for any FPGA registered in `modules/fpga/`; only
the IDCODE and BSDL filename change.
This walks through the full `bs_explorer` flow: detect a probe, scan the
chain, identify the device, and program it. There are two programming
paths and the tutorial covers both:
- **Xilinx external SPI configuration flash**, via the BSCAN proxy —
shown on a Kintex UltraScale+ KU15P / UltraScale KU040;
- **any other part** (Lattice, Microsemi, …), by playing a
vendor-exported **SVF** — shown on a Microsemi IGLOO2 M2GL010T.
The early steps are identical for any device in `fpga_registry.yaml`;
only the IDCODE and BSDL filename change.
## Prerequisites
@@ -18,17 +25,17 @@ the IDCODE and BSDL filename change.
[Phase 2.5 caveat](#phase-25-spi-through-the-bscan-proxy-bridge-bitstream) at the end.
If your board uses a Digilent JTAG-SMT2 / SMT2-NC module (KCU105,
ZCU102, …), you need the optional Digilent backend: install the Adept
Runtime system-wide and configure with `cmake -DBS_ENABLE_DIGILENT=ON
..`. Plain MPSSE does not work on those modules — see the README and
the `Digilent SMT2` block in `CLAUDE.md` for the why.
ZCU102, …), the Digilent backend is built in by default on Linux — just
install the Adept Runtime system-wide so `libdjtg.so`/`libdmgr.so` are
present at runtime. Plain MPSSE does not work on those modules — see the
README and the `Digilent SMT2` block in `CLAUDE.md` for the why.
## Build & launch
```sh
mkdir build && cd build
cmake .. && make
./bs/bs
mkdir build && cd build && cmake .. && make && cd ..
./build/bs/bs # run from the repo root: probes.yaml, fpga_registry.yaml,
# bsdl_files/ and bscan_proxies/ are looked up in the CWD
```
You should see:
@@ -69,6 +76,29 @@ If `jtag_open` fails: check `lsusb` for the probe VID:PID, make
sure the user has access to the USB device (udev rule or group), and
confirm no other process holds the probe (e.g. `openocd`).
### Probe profiles
Some probes need pin tweaks the built-in defaults don't cover — e.g. the
embedded **FlashPro** on Microsemi eval kits (an FT4232H whose JTAG sits
on channel A = index 0 and needs `ADBUS4` left high-Z, or the chain stays
silent). `probes.yaml` captures these as named profiles; apply one when
opening:
```
bs_explorer> jtag_profiles
2 probe profile(s) in probes.yaml:
flashpro
ft2232h
bs_explorer> jtag_open 0 flashpro
Applied probe profile 'flashpro'.
Probe Ok !
```
`jtag_close` releases the current probe (frees its USB handle) — use it
to hand the probe to another tool, or to program two boards on different
probes in turn: `jtag_close`, then `jtag_open` the next one and
`jtag_autoinit` to rescan.
## 2. Scan the JTAG chain
The fastest path is `jtag_autoinit`: it scans the chain *and*
@@ -97,14 +127,38 @@ registry in `fpga_registry.yaml`:
```
bs_explorer> fpga_info
Device 0 IDCODE 0x04A56093 -> Xilinx Kintex UltraScale+ XCKU15P [Xilinx UltraScale+]
prog: proxy_spi
caveat: CCLK routed via STARTUP primitive (not drivable in EXTEST)
```
`prog:` is the programming backend the registry assigns the part —
`proxy_spi` (Xilinx external flash, [§Phase 2.5](#phase-25-spi-through-the-bscan-proxy-bridge-bitstream))
or `svf` ([§Programming via SVF](#programming-via-svf-lattice-microsemi-)).
If you get `not in registry`, add an entry — see
[Adding a new FPGA](#6-add-a-new-fpga-target).
`fpga_list` prints the whole registry without needing a probe.
## JTAG clock (optional)
Each driver has its own default TCK (FTDI 1 MHz, Digilent 4 MHz). To set
a single driver-neutral clock, before `jtag_open`:
```
bs_explorer> set JTAG_TCK_FREQ_KHZ 1000
```
It's applied at open (mapped to the FTDI driver's variable, or read
directly by the Digilent one). A registry entry may declare a
`max_tck_khz`; if your requested clock exceeds it, `jtag_autoinit` clamps
it and re-opens the probe at the safe rate:
```
WARNING : JTAG clock 2000 kHz exceeds the device max 1000 kHz; clamping.
Re-opening at 1000 kHz and re-scanning.
```
## 4. (Optional) Sanity-check the low-level JTAG primitives
Before doing anything fancy, you can verify that `bscan_set_ir` and
@@ -206,6 +260,8 @@ Append a list item under `fpgas:` in `fpga_registry.yaml`:
| `ir_cfg_in` / `ir_user1` / `ir_jprogram` / `ir_jstart` / `ir_jshutdown` / `ir_isc_disable` | private IR opcodes (omit = 0/N/A) | from the BSDL |
| `proxy_bitstream` | BSCAN proxy `.bit` in `bscan_proxies/` (omit if none) | `bscan_spi_xcku040.bit` |
| `caveats` | space/comma-separated flag names (omit if none) | `cclk_via_startup` |
| `max_tck_khz` | max safe JTAG TCK in kHz; `jtag_autoinit` clamps + re-opens if exceeded (omit = unspecified) | — |
| `prog` | programming backend `proxy_spi`/`svf`/`none` (omit → inferred: a proxy ⇒ `proxy_spi`, Microsemi/Lattice ⇒ `svf`) | `proxy_spi` |
The resulting entry (verbatim from `fpga_registry.yaml`):
@@ -224,6 +280,7 @@ The resulting entry (verbatim from `fpga_registry.yaml`):
ir_isc_disable: 0x16
proxy_bitstream: bscan_spi_xcku040.bit
caveats: cclk_via_startup
prog: proxy_spi
```
Omit any field that doesn't apply: a missing `proxy_bitstream` means
@@ -365,6 +422,60 @@ OpenOCD's `src/flash/nor/jtagspi.c` so the same bitstreams work. Generic
flash `read`/`erase`/`program`/`verify` (Phase 3) will be built on top
of this primitive.
## Programming via SVF (Lattice, Microsemi, …)
The BSCAN-proxy path above is Xilinx-specific (external SPI config flash).
For everything else, the universal path is to play an **SVF** file
exported by the vendor tool — Libero / FlashPro Express (Microsemi),
Diamond / Radiant (Lattice), Vivado (Xilinx fabric), Quartus, … The
vendor bakes the programming *algorithm* into the SVF; `bs_explorer` just
replays its `SIR`/`SDR`/`RUNTEST` vectors and checks the masked `TDO`
compares that flag a failed erase/program.
```
bs_explorer> jtag_open 0 flashpro # the probe profile for your kit
bs_explorer> jtag_autoinit # fpga_info should show 'prog: svf'
bs_explorer> svf_play design.svf
...
SVF done: 1342 commands, 1338 scans, 71 compares
```
A `TDO` mismatch stops play and points at the failing vector:
```
ERROR : line 842: SDR TDO mismatch at bit 17 (len 696)
```
— usually a wrong device, a too-fast clock, or a part that isn't
erased/unlocked.
You can sanity-check the player without a programming file using a tiny
hand-written IDCODE check (after `STATE RESET` the TAP auto-loads IDCODE
into DR):
```
! idcode.svf — masked IDCODE check (top nibble = revision)
STATE RESET;
SDR 32 TDO (0F8031CF) MASK (0FFFFFFF);
```
```
bs_explorer> svf_play idcode.svf
SVF done: 2 commands, 1 scans, 1 compares
```
**Supported subset (single-device chain):** `SIR`/`SDR` with
`TDI`/`TDO`/`MASK`/`SMASK` and the masked compare; `RUNTEST` (TCK/SCK
counts and SEC delays); `STATE` (RESET/IDLE); `ENDIR`/`ENDDR` (IDLE only);
`HIR`/`HDR`/`TIR`/`TDR` (length 0 only); `TRST`; `FREQUENCY`. SMASK is
parsed but not applied. Multi-device headers/trailers (non-zero) and
non-IDLE end states are rejected with a clear error.
**Generating the SVF is the closed step.** There is no open-source
bitstream/SVF generator for Microsemi (or most vendors) — you need the
vendor tool to *produce* the SVF (Libero has a free tier covering the
small IGLOO2 parts). `bs_explorer` only *plays* it, which is fully open.
## Troubleshooting cheat sheet
| Symptom | Likely cause |
@@ -376,6 +487,9 @@ of this primitive.
| `fpga_info` says "not in registry" | Add an entry to `fpga_registry.yaml`. |
| `bscan_shift_dr 32` doesn't return the expected IDCODE | Wrong IR opcode/length, wrong device index, or a multi-device chain (current primitives assume single device). |
| `jtag_spi_xfer` is hopelessly slow | That's expected via EXTEST — switch to BSCAN proxy (Phase 2.5). |
| Detected fine, then reads turn to garbage / `0x00000000` mid-session | Target board lost power — JTAG floats (the USB probe stays enumerated regardless). Re-power the board. |
| FT4232H FlashPro: `jtag_scan` finds 0 devices | JTAG is on channel A (index 0) and needs `ADBUS4` high-Z — open with the profile: `jtag_open 0 flashpro`. |
| `svf_play` mismatches only on the very first compare | FTDI link warm-up; `svf_play` handles it, but a bare `bscan_shift_dr` straight after `jtag_open` may need a `jtag_scan` first. |
## Where to go from here