Read CPU memory over JTAG via system-speed LDM. Validated on the LPC2103: reads the real ARM reset vectors and contiguous multi-block code. The core only advances on Run-Test/Idle debug clocks (not Update-DR), so the trick is keeping that clock count exact: - "quiet" TAP ops (quiet_set_ir / quiet_shift_dr / quiet_chain_select / quiet_eice_read / quiet_latch_chain1) pass through Update but park in Pause, never RTI -> they switch chains and read EmbeddedICE WITHOUT clocking the core, so they can't clobber the registers a sys-speed LDM just loaded. - clock_core(n) is the only thing that advances the core (n RTI clocks). - execute_sys_speed: RESTART, then drive the access one clock at a time with a quiet DBG_STATUS check between, stopping the instant SYSCOMP & DBGACK appear (no over-clock past re-entry). - after sys-speed: quiet-switch to chain 1, quiet-latch a NOP to displace the stale LDMIA, then read_core_regs. - pre-read pipeline normalization: change_to_arm (17 clocked instrs) for a Thumb halt; 17 ARM NOPs for an ARM halt. WIP: not yet reliable across all halt states - the first read after some halts times out (SYSCOMP never appears) and leaves the core running. Within one good halt, reads are consistent and correct. Diagnosis and next steps in the arm7-debug-dclk-timing note. 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.