From cc2ee5d92c67cdbd1f6a40a48facb76127b1d821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Sun, 24 May 2026 14:50:02 +0200 Subject: [PATCH] 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 --- CLAUDE.md | 34 ++++++++---- README.md | 74 +++++++++++++++++--------- doc/tutorial.md | 138 +++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 199 insertions(+), 47 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 75254dd..a50e03c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 `, `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 diff --git a/README.md b/README.md index 80c2ddc..69686a4 100644 --- a/README.md +++ b/README.md @@ -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 -bitstream loaded into the fabric (~100 KB/s), or slowly via EXTEST pin -bit-bang for one-shot checks. +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; +- **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 ` (`$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 -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 # 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 # 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 diff --git a/doc/tutorial.md b/doc/tutorial.md index 841d803..e0dea4c 100644 --- a/doc/tutorial.md +++ b/doc/tutorial.md @@ -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