Files
bs_explorer/doc/tutorial.md
François 37efccaf50 bscan: rename from bscan_spi, add TAP primitives for the SVF player
The module outgrew its "SPI bridge" name — it's mostly generic
single-device JTAG TAP primitives now. Rename modules/bscan_spi ->
modules/bscan (dir, files, library target, includes, doc paths);
bscan_* function names and bscan_spi_xfer() kept.

Add the two primitives the SVF player needs beyond shift_dr:
- bscan_shift_ir: general IR scan with TDO capture (bscan_set_ir is
  opcode-only, no readback)
- bscan_tap_reset: force Test-Logic-Reset, land in Run-Test/Idle

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 12:00:38 +02:00

388 lines
15 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.
# Tutorial — from probe detection to SPI flash
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.
## Prerequisites
- A JTAG probe physically wired to the target's TCK/TDI/TDO/TMS/TRST.
- `libftd2xx` reachable at runtime (already vendored under
`libs/libftd2xx/`).
- The target's BSDL in `bsdl_files/` (KU15P: `xcku15p_ffve1517.bsd` is
bundled).
- An entry for the target in `fpga_registry.yaml` (KU15P is bundled).
See [Adding a new FPGA](#6-add-a-new-fpga-target) below.
- For SPI flashing, eventually: a BSCAN proxy bitstream — see the
[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.
## Build & launch
```sh
mkdir build && cd build
cmake .. && make
./bs/bs
```
You should see:
```
Boundary Scan Explorer v2.6.7.1
Based on Viveris jtag-boundary-scanner
3 probe driver(s) available.
Type 'help' or '?' to list commands, 'exit' or Ctrl-D to quit.
<Tab> completes commands.
bs_explorer>
```
`<Tab>` completes commands; `help <cmd>` shows per-command help;
Ctrl-D or `exit` quits.
## 1. Detect and open the probe
```
bs_explorer> jtag_probes
[0] 0x00000000 Digilent USB Device 210308AB06A6
[1] 0x00000300 Digilent: JtagSmt2NC
```
Open a probe by the index in brackets:
```
bs_explorer> jtag_open 1
```
The `0x…` value next to each index is the raw probe id and is also
accepted (`jtag_open 0x300`) — handy in scripts where you'd rather pin
the exact backend than rely on enumeration order.
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`).
## 2. Scan the JTAG chain
The fastest path is `jtag_autoinit`: it scans the chain *and*
auto-loads every BSDL in `bsdl_files/` whose IDCODE matches a device.
```
bs_explorer> jtag_autoinit
```
Expected output for a single-FPGA board:
```
1 device(s) found
Device 0 (04A56093 - XCKU15P_FFVE1517) - BSDL Loaded : ...xcku15p_ffve1517.bsd
```
If the device count is 0 or the IDCODE is `0xFFFFFFFF`/`0x00000000`:
TDI/TDO are likely swapped, TRST not released, or the probe is
mis-wired. Power-cycle and re-check the harness before going further.
## 3. Identify the FPGA against the registry
`fpga_info` walks the chain and matches each IDCODE against the
registry in `fpga_registry.yaml`:
```
bs_explorer> fpga_info
Device 0 IDCODE 0x04A56093 -> Xilinx Kintex UltraScale+ XCKU15P [Xilinx UltraScale+]
caveat: CCLK routed via STARTUP primitive (not drivable in EXTEST)
```
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.
## 4. (Optional) Sanity-check the low-level JTAG primitives
Before doing anything fancy, you can verify that `bscan_set_ir` and
`bscan_shift_dr` actually move bits correctly on your hardware. Drop
the BSDL state (so `jtag_core` doesn't fight us on IR caching) and
shift IDCODE manually:
```
bs_explorer> jtag_open 0 # index from jtag_probes
bs_explorer> jtag_scan # detects devices, does NOT load BSDL
bs_explorer> bscan_set_ir 9 6 # IDCODE opcode (KU15P: 0x09, IR=6 bits)
bs_explorer> bscan_shift_dr 32
DR = 04 A5 60 93 # bytes printed MSB-first
```
Match → primitives are healthy. Mismatch → wiring or clock issue, fix
that before moving on. The opcode and IR length come from the
`fpga_target` (and ultimately the BSDL `INSTRUCTION_OPCODE` block).
## 5. Read the SPI flash JEDEC ID via EXTEST (validation only)
This is the *slow* path — useful to confirm the SPI pins are wired
correctly to the flash, **not** a viable way to flash megabytes. See
[Phase 2.5](#phase-25-spi-through-the-bscan-proxy-bridge-bitstream) for the production path.
Put the FPGA in EXTEST, then map the four SPI signals onto the FPGA's
BSDL pin names:
```
bs_explorer> jtag_autoinit
bs_explorer> jtag_mode 0 EXTEST
bs_explorer> jtag_spi_cs 0 <PIN_CS> 0
bs_explorer> jtag_spi_clk 0 <PIN_CLK> 0
bs_explorer> jtag_spi_mosi 0 <PIN_MOSI> 0
bs_explorer> jtag_spi_miso 0 <PIN_MISO> 0
```
Pin names depend on the board: dump `jtag_pins 0` to discover
them. On Xilinx FPGAs, the SPI flash is typically wired to the
configuration bank (e.g. `D00_MOSI_0`, `D01_DIN_0`, `FCS_B_0`) —
**except** `CCLK`, which goes through the `STARTUPE3` primitive and is
not drivable in EXTEST (the `CCLK_VIA_STARTUP` caveat on the target).
Send the JEDEC ID command (`0x9F` + 3 dummy bytes):
```
bs_explorer> jtag_spi_xfer 9F000000
SPI TX: 9F 00 00 00
SPI RX: FF XX YY ZZ # XX YY ZZ identifies the flash vendor/part
```
This takes several seconds even for 4 bytes — that's the
~30 bytes/sec EXTEST ceiling. Don't try to read the whole flash this
way; you'd be there for weeks.
## 6. Add a new FPGA target
The registry — `fpga_registry.yaml` at the repo root — holds the
per-part facts that can't be derived from the BSDL alone (or are tedious
to). It's parsed at runtime, so adding a part is **editing YAML, no
rebuild**. The XCKU040 entry already there was added exactly with the
steps below — use it as your template.
### a. Drop the BSDL
Put the part's `.bsd` in `bsdl_files/`. Source: Xilinx/AMD device page
under "Design Files / BSDL", Intel in the Quartus install, Lattice per
part, Microsemi/Microchip via Libero. `jtag_autoinit` will then
auto-load it by IDCODE.
### b. Pull the facts out of the BSDL
Everything you need is in the file:
```sh
grep -iE "INSTRUCTION_LENGTH|IDCODE_REGISTER|\b(USER1|CFG_IN|JPROGRAM|JSTART|JSHUTDOWN|ISC_DISABLE)\b" \
bsdl_files/xcku040_ffva1156.bsd
```
For the XCKU040 this yields IR length 6, and the private opcodes
`USER1=000010` (0x02), `CFG_IN=000101` (0x05), `JPROGRAM=001011`
(0x0B), `JSTART=001100` (0x0C), etc. The `IDCODE_REGISTER` string is
`XXXX...0010 0000 1001 0011` — the top four bits are the silicon
**revision** and read as `X` (don't-care), which is why the registry
masks them off.
### c. Add a YAML entry
Append a list item under `fpgas:` in `fpga_registry.yaml`:
| Key | What it is | XCKU040 |
|-----|-----------|---------|
| `name` | human-readable label (quoted) | "Xilinx Kintex UltraScale XCKU040" |
| `idcode` | IDCODE pattern (version nibble as 0) | `0x03822093` |
| `idcode_mask` | bits that must match; `0x0FFFFFFF` ignores the Xilinx revision nibble (default `0xFFFFFFFF`) | `0x0FFFFFFF` |
| `family` | `xilinx_7/us/usp`, `microsemi_igloo2/smartfusion2`, `lattice_machxo2/3` | `xilinx_us` |
| `bsdl` | basename in `bsdl_files/` | `xcku040_ffva1156.bsd` |
| `ir_length` | IR width in bits | `6` |
| `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` |
The resulting entry (verbatim from `fpga_registry.yaml`):
```yaml
- name: "Xilinx Kintex UltraScale XCKU040"
idcode: 0x03822093
idcode_mask: 0x0FFFFFFF
family: xilinx_us
bsdl: xcku040_ffva1156.bsd
ir_length: 6
ir_cfg_in: 0x05
ir_user1: 0x02
ir_jprogram: 0x0B
ir_jstart: 0x0C
ir_jshutdown: 0x0D
ir_isc_disable: 0x16
proxy_bitstream: bscan_spi_xcku040.bit
caveats: cclk_via_startup
```
Omit any field that doesn't apply: a missing `proxy_bitstream` means
"none yet", a missing `caveats` means none, a missing `idcode_mask`
defaults to exact match (`0xFFFFFFFF`).
### What `caveats` means
`caveats` is a space/comma-separated list of flag names, each backed by
an `FPGA_CAVEAT_*` bit in `fpga.h`, marking **known hardware gotchas
that change how the tool must drive the part**. It is *not* a free-text
note — each flag is something the code (or you) can branch on. Omit the
field when the part has none. `fpga_info` prints any flag that is set,
as a human-readable line.
Currently one flag exists:
- `cclk_via_startup` (`FPGA_CAVEAT_CCLK_VIA_STARTUP`) — on Xilinx
7-Series / UltraScale / UltraScale+, the SPI configuration clock
**CCLK is not a normal I/O pin**: it is routed through the
`STARTUP`/`STARTUPE3` primitive and therefore **cannot be toggled in
EXTEST** boundary scan. Practical effect: the slow EXTEST SPI bit-bang
can't clock the flash on these parts — you must use the BSCAN proxy
(Phase 2.5), where CCLK is driven by the fabric internally and the
problem disappears. Set this for any 7-Series/US/US+ part.
To introduce a *new* caveat: add a `#define FPGA_CAVEAT_xxx (1u << n)`
in `fpga.h`, teach `parse_caveats()` in `fpga.c` its YAML name, use that
name in the YAML, and (if it should be visible) print it in
`cmd_fpga_info` in `script.c`.
### d. Verify — no rebuild
The registry is loaded at runtime, so just (re)start bs_explorer from
the repo root and check:
```sh
./build/bs/bs
bs_explorer> fpga_list # your part should appear, with its source file
bs_explorer> jtag_autoinit
bs_explorer> fpga_info # should show your part, family, and any caveats
```
(`fpga_list` reads the registry without needing a probe.)
## Phase 2.5: SPI through the BSCAN proxy (bridge bitstream)
Talking to the SPI flash via EXTEST is fine for a JEDEC ID but useless
for real flashing (~30 B/s, days to weeks for a config part). The
production path loads a tiny **BSCAN proxy** bitstream into the FPGA
fabric, then runs SPI through the `USER1` instruction at fabric speed
(~50200 KB/s). The proxy uses a `BSCANE2` primitive to bridge the
`USER1` DR shift to the flash pins, and drives `CCLK` from the fabric
internally — so the `STARTUPE3`/CCLK problem of EXTEST disappears.
### Get the bridge bitstream
Pre-built proxies live in `quartiq/bscan_spi_bitstreams` (MIT). Drop
the one for your part in `bscan_proxies/`:
```sh
curl -L -o bscan_proxies/bscan_spi_xcku040.bit \
https://raw.githubusercontent.com/quartiq/bscan_spi_bitstreams/master/bscan_spi_xcku040.bit
```
The registry entry for the part points at this file via its
`proxy_bitstream` field (e.g. the XCKU040 entry → `bscan_spi_xcku040.bit`).
#### When your part isn't pre-built (e.g. the KU15P)
quartiq ships `.bit` only for the parts its generator knows — it has
**no UltraScale+** proxy (its single UltraScale entry is the KU040), so
the KU15P has to be built from source. You need (o)Migen + Vivado
(2022.2; ISE 14.7 for older parts). From a clone of the quartiq repo,
per its README:
```sh
python3 -m venv --system-site-packages .venv
./.venv/bin/pip install -r requirements.txt
PATH=$PATH:/opt/Xilinx/Vivado/2022.2/bin \
./.venv/bin/python3 xilinx_bscan_spi.py ...
```
The XCKU15P first has to be **added to the generator's device table**
(a Migen platform entry) — it's not just a command-line part flag.
Once built, drop `bscan_spi_xcku15p.bit` into `bscan_proxies/` (it's
MIT, like the KU040 — keep `bscan_proxies/LICENSE.quartiq`) and set the
`proxy_bitstream` field on the KU15P entry in `fpga_registry.yaml`
(currently omitted).
### Load the bridge and talk SPI
```
bs_explorer> jtag_open 1
bs_explorer> jtag_autoinit
bs_explorer> bscan_load_bitstream 0 bscan_proxies/bscan_spi_xcku040.bit
bs_explorer> bscan_jedec 0
JEDEC ID: 20 BB 19 (manufacturer 0x20, device 0xBB19)
```
(Validated on a KCU105: `0x20` = Micron, `0xBB19` = MT25QU256, the
board's 1.8 V 256 Mbit config flash. Other boards will show different
device bytes.)
`bscan_load_bitstream` runs JPROGRAM → CFG_IN → shift → JSTART, which
**reconfigures the FPGA fabric**: the design currently running on the
part is wiped and replaced by the proxy. This is undone by a
power-cycle (the configuration flash reloads the original design at the
next boot), but be aware the board stops doing whatever it was doing.
`bscan_jedec` issues the SPI **Read Identification** command (`0x9F`,
also called RDID) and reads back 3 bytes — the chip's *JEDEC ID*. JEDEC
is the standards body (JESD216 / JEP106) that defines this identifier;
every serial flash answers `0x9F` the same way:
- **byte 0** — manufacturer, a code assigned by JEDEC (`0x20` Micron,
`0xEF` Winbond, `0xC2` Macronix, `0x01` Cypress/Infineon, `0x9D`
ISSI, …);
- **bytes 12** — device ID (memory type + capacity), vendor-specific.
So the JEDEC ID is how you discover *which* flash is wired up without
prior knowledge — Phase 3 will use it to look the part up in a chip
database and pull its page/sector sizes and command set.
A sane answer here (manufacturer `0x20` = Micron, the KCU105's config
flash) also confirms the whole proxy path end to end: the
`bscan_spi_xfer()` framing, the MSB-first bit order, and the TDO
read-latency skew.
### The transfer primitive
`bscan_spi_xfer(jc, t, tx, txlen, rx, rxlen)` in
`modules/bscan/bscan.c` performs one CS-framed transaction:
clock out `txlen` MOSI bytes, then read `rxlen` MISO bytes. It builds
the quartiq/OpenOCD jtagspi DR frame
(`marker | bit-count | MOSI | latency-skip | MISO`) and matches
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.
## Troubleshooting cheat sheet
| Symptom | Likely cause |
|---------|--------------|
| `jtag_probes` returns nothing | FTDI not enumerated. Check `lsusb`, udev permissions, conflicting process. |
| `jtag_autoinit` finds 0 devices | TDI/TDO swap, TRST held low, voltage mismatch, or chain broken. |
| All IDCODEs read `0xFFFFFFFF` | TDO floats high — broken TDO link, wrong voltage reference, or a Digilent SMT2 module being driven via raw FTDI MPSSE (use the Digilent backend instead). |
| All IDCODEs read `0x00000000` | TDO tied low or no clock reaching the target. |
| `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). |
## Where to go from here
- `help` in the REPL lists all commands; `help <cmd>` gives details.
- `CLAUDE.md` at the repo root captures the architecture, roadmap and
technical decisions (machine-independent).
- `README.md` is the user-facing summary.
- The original Viveris library and its docs live untouched under
`modules/jtag_core/`, `modules/bsdl_parser/`, `modules/bus_over_jtag/`.