The FTDI MPSSE xfer ignored TMS on data bits, so bscan_set_ir never latched the IR — the bscan exit needs the last bit to clock Shift->Exit1 so the following Update latches. It only ever worked on the Digilent driver. Now the final TMS-flagged bit is clocked through the TMS pin (carrying TDI/TDO), so bscan_set_ir/bscan_shift_dr reach Exit1->Update correctly. Implement ARM7TDMI EmbeddedICE access (SCAN_N + INTEST, 38-bit scan chain 2 register R/W with pipelined read) and halt (force DBGRQ, poll DBGACK) / resume (clear DBGRQ + RESTART). New cpu_halt / cpu_resume commands; arm_debug links bscan. Validated on an LPC2103 over the ARM-USB-OCD: set_ir(IDCODE) reads 0x4F1F0F0F, EmbeddedICE registers round-trip, cpu_halt -> DBGACK, cpu_resume releases the core. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
bs_explorer — Boundary Scan Explorer
Command-line tool to explore a JTAG chain, drive an FPGA's pins through 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 library by Viveris (LGPL).
Status
- 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
- Target registry (runtime YAML, FPGAs + CPUs: IDCODE → BSDL/debug, IR opcodes, proxy, caveats, programming method): OK
- Probe-config profiles (
data/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) program <dev> <file>— dispatches to the right backend by the target'sprogmethod: OK- ARM7/9 CPU flashing (EmbeddedICE, ARM-USB-OCD): structure in place; debug/flash backend not implemented yet
Bundled BSDLs: Xilinx Kintex UltraScale+ KU15P
(xcku15p_ffve1517.bsd), Kintex UltraScale KU040 (xcku040_ffva1156.bsd),
and Microsemi IGLOO2 M2GL010T (m2gl010t-fg484.bsd). Add more by dropping
.bsd files in data/bsdl_files/ plus an entry in data/targets.yaml (see
doc/tutorial.md for adding a target).
Dependencies
- CMake ≥ 3.10, gcc/clang
readline(Arch:readline, Debian/Ubuntu:libreadline-dev)libyamlfor the FPGA registry, found via pkg-configyaml-0.1(Arch:libyaml, Debian/Ubuntu:libyaml-dev)libftdi1+libusb-1.0for FTDI/Olimex probes, via pkg-configlibftdi1(Arch:libftdi, Debian/Ubuntu:libftdi1-dev). Open source — no vendored proprietary SDK; works with any VID:PID and auto-detaches the kernelftdi_siodriver.- To drive a Digilent SMT2/SMT2-NC probe: the Digilent
Adept Runtime
installed system-wide (provides
libdjtg.so+libdmgr.so). Nothing from Digilent is vendored — the backend isdlopen'd at runtime, so it's built in by default and simply reports no probe if the libs are missing.
Build
mkdir build && cd build
cmake ..
make
The binary is produced at build/bs.
The Digilent SMT2 backend is built by default on Linux. To leave it out:
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:
./build/bs
bs_explorer reads, when present in that directory:
data/config.script— overrides built-in probe variables (FTDI clock, TRST/SRST pin mapping, …); seesrc/modules/config/config.scriptfor the full list. Loaded at startup.data/probes.yaml— probe-config profiles, applied withjtag_open <idx> <profile>($BS_PROBESoverrides the path).data/targets.yaml— the FPGA target registry ($BS_TARGETSoverrides the path).data/bsdl_files/,data/bscan_proxies/— BSDLs and proxy bitstreams.
REPL
<Tab>completes command names.- Persistent history (GNU readline) in
~/.bs_explorer_history: up/down arrows and Ctrl-R recall commands across sessions. helpor?lists commands;help <cmd>shows details.exit,quit, or Ctrl-D to leave.
Typical flow
Xilinx external SPI flash — via the BSCAN proxy:
# 1. List probes, open one by its index ([N]). A probe that needs tweaks
# (e.g. an embedded FlashPro) takes a profile from data/probes.yaml:
bs_explorer> jtag_probes
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 # target_info then shows the prog method
# 3. Load the BSCAN proxy into the fabric (fast SPI bridge)
bs_explorer> bscan_load_bitstream 0 data/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_write 0 0x10000 image.bin
bs_explorer> flash_verify 0 0x10000 image.bin
Anything else (Lattice, Microsemi, …) — play a vendor-exported SVF:
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. A minimal
example script is in data/scripts/example_script.txt; the full walkthrough
lives in doc/tutorial.md.
Main commands
| Category | Commands |
|---|---|
| Script control | set, print, print_env_var, if, goto, call, return, rand, init_array, system, pause |
| Probe / chain | jtag_probes, jtag_open, jtag_close, jtag_profiles, jtag_scan, jtag_autoinit, jtag_ndev, jtag_devices |
| BSDL / pins | jtag_bsdl, jtag_pins, jtag_mode, jtag_pin_dir, jtag_pin_set, jtag_pin_get, jtag_push_pop |
| I²C / MDIO / SPI over BS pins (EXTEST) | jtag_i2c_scl, jtag_i2c_sda, jtag_i2c_rd, jtag_i2c_wr, jtag_mdio_mdc, jtag_mdio_io, jtag_mdio_rd, jtag_mdio_wr, jtag_spi_cs/mosi/miso/clk, jtag_spi_xfer |
| Targets (FPGA/CPU) | target_list, target_info |
| BSCAN proxy | bscan_load_bitstream, bscan_jedec, bscan_set_ir, bscan_shift_dr |
| SPI flash (via proxy) | flash_detect, flash_read, flash_erase, flash_write, flash_verify |
| Program | program (dispatch by target), svf_play |
| Misc | help, ?, version, exit |
Use help <command> for per-command help.
Supported probes
- FTDI MPSSE (FT2232D/H, FT4232H, …) — see the
PROBE_FTDI_*block insrc/modules/config/config.scriptfor pin mapping and TCK frequency. Boards that wire the FT4232H differently (e.g. the embedded FlashPro on Microsemi eval kits, which needs ADBUS4 left high-Z) are handled by a probe profile indata/probes.yaml:jtag_profileslists them,jtag_open <idx> <profile>applies one (e.g.jtag_open 0 flashpro). The Olimex ARM-USB-OCD (also an FT2232, for ARM CPU targets) has anarm-usb-ocdprofile slot — pending its control-pin map. - SEGGER J-Link
- Linux GPIO (sysfs; deprecated on recent kernels, libgpiod migration TBD)
- Digilent JTAG-SMT2 / SMT2-NC — built in by default on Linux
(
-DBS_ENABLE_DIGILENT=OFFto drop it). Required for the USB-JTAG on Xilinx eval boards like the KCU105: those modules ship a Digilent-proprietary firmware that does not respond to plain MPSSE, so the FTDI driver appears to enumerate them but the JTAG chain stays silent.
Known Xilinx caveats
On 7-Series / UltraScale / UltraScale+, CCLK is not a regular I/O pin
and goes through the STARTUPE3 primitive, so it cannot be driven
directly in EXTEST — the slow EXTEST SPI path therefore can't clock the
flash on these parts.
The BSCAN proxy sidesteps this entirely: it drives CCLK from the
fabric internally, so flashing runs at full speed. Parts affected are
flagged with the CCLK_VIA_STARTUP caveat in the registry (target_info
shows it).
Repository layout
src/
├── bs/ Application (readline REPL)
└── modules/
├── jtag_core/ TAP state machine, IR/DR shifts
├── bsdl_parser/ .bsd loader
├── bus_over_jtag/ SPI / I²C / MDIO / parallel mem bit-bang (EXTEST)
├── drivers/ FTDI, J-Link, Linux GPIO, LPT, Digilent (optional)
├── target/ Target registry loader: FPGAs + CPUs (data/targets.yaml)
├── 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 (data/probes.yaml)
├── program/ `program` dispatch: routes a target to its backend by prog
├── arm_debug/ ARM (EmbeddedICE) debug + flash backend (not implemented yet)
├── script/ Script engine
├── config/ Built-in config.script
├── os_interface/ Portable fs/network wrappers
└── natsort/ Natural-order pin-name sorting
data/ — runtime resources, looked up from the CWD —
├── targets.yaml Target registry (FPGAs + CPUs)
├── 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
License
src/modules/jtag_core/ and the original Viveris files are under LGPL 2.1.
See LICENSE and src/modules/jtag_core/COPYING.LESSER. The proxy
bitstreams in data/bscan_proxies/ are from quartiq (MIT) — see
data/bscan_proxies/LICENSE.quartiq.