Files
bs_explorer/doc/tutorial.md
francois 1febae7377 doc: add end-to-end tutorial
doc/tutorial.md walks from probe detection to JEDEC ID over EXTEST and
forward-references the BSCAN proxy path. Includes:
- prerequisites and build/launch
- chain scan, FPGA identification, registry lookup
- IR/DR primitive sanity check via the IDCODE register
- SPI JEDEC ID over EXTEST (with the speed caveat)
- recipe to add a new FPGA target
- troubleshooting cheat sheet

README and CLAUDE.md updated to point at it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 23:11:15 +02:00

232 lines
8.3 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 `modules/fpga/fpga.c` (KU15P is bundled).
See [Adding a new FPGA](#adding-a-new-fpga-target) below.
- For SPI flashing, eventually: a BSCAN proxy bitstream — see the
[Phase 2.5 caveat](#phase-25-bscan-proxy) at the end.
## 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_get_probes_list
```
Lists each probe the loaded drivers can see. With an FTDI cable
plugged in, you should get at least one entry. Pick its index (1-based
in the output) and open it:
```
bs_explorer> jtag_open_probe 1
```
If `jtag_open_probe` fails: check `lsusb` for the FTDI 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
compile-time registry in `modules/fpga/fpga.c`:
```
bs_explorer> fpga_info
Device 0 IDCODE 0x04A56093 -> Xilinx Kintex UltraScale+ XCKU15P [Xilinx UltraScale+]
quirk: CCLK routed via STARTUP primitive (not drivable in EXTEST)
```
If you get `not in registry`, add an entry — see
[Adding a new FPGA](#adding-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_probe 1
bs_explorer> jtag_init_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-bscan-proxy) 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_set_mode 0 EXTEST
bs_explorer> jtag_set_spi_cs_pin 0 <PIN_CS> 0
bs_explorer> jtag_set_spi_clk_pin 0 <PIN_CLK> 0
bs_explorer> jtag_set_spi_mosi_pin 0 <PIN_MOSI> 0
bs_explorer> jtag_set_spi_miso_pin 0 <PIN_MISO> 0
```
Pin names depend on the board: dump `jtag_get_pins_list 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` quirk on the target).
Send the JEDEC ID command (`0x9F` + 3 dummy bytes):
```
bs_explorer> jtag_spi_rd_wr 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
For an FPGA that's not in the registry yet:
1. **Drop the BSDL** in `bsdl_files/`. The file you want is on the
vendor's site (Xilinx: in the device download under
"BSDL files"; Intel: in Quartus install dir; Lattice: per part).
2. **Read the facts you need** from the BSDL:
```
attribute IDCODE_REGISTER ... -> IDCODE pattern (4-bit version
masked, lower 28 bits matter)
attribute INSTRUCTION_LENGTH ... -> ir_length
attribute INSTRUCTION_OPCODE ... -> opcodes for IDCODE, EXTEST,
SAMPLE, BYPASS, and private
instructions for the family
(USER1, CFG_IN, JPROGRAM,
JSTART, JSHUTDOWN, ISC_DISABLE
on Xilinx)
```
3. **Add an entry** to `fpga_registry[]` in `modules/fpga/fpga.c`,
mirroring the existing KU15P entry. Set `proxy_bitstream` to
`NULL` for now; wire it up when you have one. Set quirks as
appropriate (e.g. `FPGA_QUIRK_CCLK_VIA_STARTUP` for any
Xilinx 7-Series/UltraScale/UltraScale+).
4. **Rebuild**. The registry is compile-time, no runtime registration.
5. **Verify** with `fpga_info` after `jtag_autoinit`.
## Phase 2.5: BSCAN proxy
Flashing tens of megabytes via the EXTEST SPI bridge is not feasible
(~30 B/s, days to weeks). The realistic path is to load a tiny "BSCAN
proxy" bitstream into the FPGA fabric, then talk SPI through the
`USER1` instruction at fabric speed (~50200 KB/s).
The infrastructure is in `modules/bscan_spi/`:
```
bs_explorer> jtag_autoinit
bs_explorer> bscan_load_bitstream 0 bscan_spi_xcku15p.bit
```
This runs JPROGRAM → CFG_IN → shift → JSTART. The bitstream itself is
**not yet bundled**. Sources:
- `quartiq/bscan_spi_bitstreams` on GitHub (BSD-2). Pre-built `.bit`
files for most Xilinx parts; Migen sources to rebuild any missing
part using Vivado.
- OpenOCD reference: `src/flash/nor/jtagspi.c` documents the host
side protocol.
Once a proxy is loaded, the matching `bscan_spi_xfer()` function (and
the script command that will wrap it) will run real SPI transfers. The
proxy protocol detail is still a TODO in `modules/bscan_spi/bscan_spi.c`
— it will be wired against an actual `.bit` so we can validate
end-to-end.
## Troubleshooting cheat sheet
| Symptom | Likely cause |
|---------|--------------|
| `jtag_get_probes_list` 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 or wrong voltage reference. |
| All IDCODEs read `0x00000000` | TDO tied low or no clock reaching the target. |
| `fpga_info` says "not in registry" | Add the part to `fpga_registry[]`. |
| `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_rd_wr` 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/`.