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>
This commit is contained in:
2026-05-24 12:00:38 +02:00
parent 2a03cb1145
commit 37efccaf50
8 changed files with 97 additions and 19 deletions

View File

@@ -17,7 +17,7 @@ to FPGAs (Xilinx KU15P first, then others) over JTAG, from a CLI tool
running on a host with an FTDI probe. running on a host with an FTDI probe.
The Viveris library itself lives unchanged in `modules/`. Everything The Viveris library itself lives unchanged in `modules/`. Everything
new is in `bs/` (the REPL) and future modules (`fpga/`, `bscan_spi/`, new is in `bs/` (the REPL) and future modules (`fpga/`, `bscan/`,
`spi_flash/`) sitting alongside the Viveris ones. `spi_flash/`) sitting alongside the Viveris ones.
## Architecture ## Architecture
@@ -50,7 +50,7 @@ Adding a feature usually means adding a new script command in
|-------|--------|--------|---------| |-------|--------|--------|---------|
| 1 | `bs/` cleanup, REPL polish, README | **done** (commit `7cb3627`) | Fix format-strings, delete dead code, tab-completion, banner | | 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). Compile-time registry. |
| 2.5 | `bscan_spi/` | **done** (commit `dec0d14`) | Load BSCAN proxy bitstream via `CFG_IN`, expose fast `bscan_spi_xfer()` via `USER1`. Required for realistic flashing speeds. | | 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. | | 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. | | 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. |
@@ -234,7 +234,7 @@ progress and partial ops. The equivalent SVF ("indirect flash") is huge
| Target | Recommended path | | Target | Recommended path |
|--------|------------------| |--------|------------------|
| Xilinx external SPI config flash | **native proxy** (`bscan_spi/`+`spi_flash/`, done). SVF works but is bloated. | | Xilinx external SPI config flash | **native proxy** (`bscan/`+`spi_flash/`, done). SVF works but is bloated. |
| Xilinx fabric config (volatile) | SVF, or our `bscan_load_bitstream` (equivalent) | | Xilinx fabric config (volatile) | SVF, or our `bscan_load_bitstream` (equivalent) |
| Lattice MachXO2/3 (internal flash) | **SVF** (Diamond/Radiant export). Native IEEE-1532 ISC optional, only for a self-contained `.jed` flow. | | Lattice MachXO2/3 (internal flash) | **SVF** (Diamond/Radiant export). Native IEEE-1532 ISC optional, only for a self-contained `.jed` flow. |
| Microsemi IGLOO2 / SmartFusion2 | **SVF/STAPL** (Libero/FlashPro export). Native algorithm too complex/proprietary. | | Microsemi IGLOO2 / SmartFusion2 | **SVF/STAPL** (Libero/FlashPro export). Native algorithm too complex/proprietary. |

View File

@@ -156,7 +156,7 @@ modules/
├── bus_over_jtag/ SPI / I²C / MDIO / parallel mem bit-bang (EXTEST) ├── bus_over_jtag/ SPI / I²C / MDIO / parallel mem bit-bang (EXTEST)
├── drivers/ FTDI, J-Link, Linux GPIO, LPT, Digilent (optional) ├── drivers/ FTDI, J-Link, Linux GPIO, LPT, Digilent (optional)
├── fpga/ Registry loader (parses fpga_registry.yaml at runtime) ├── fpga/ Registry loader (parses fpga_registry.yaml at runtime)
├── bscan_spi/ BSCAN proxy loader + fast SPI-over-USER1 bridge ├── bscan/ JTAG TAP primitives + BSCAN proxy (bitstream, SPI-over-USER1)
├── spi_flash/ SPI NOR chip database + read/erase/program/verify ├── spi_flash/ SPI NOR chip database + read/erase/program/verify
├── script/ Script engine ├── script/ Script engine
├── config/ Built-in config.script ├── config/ Built-in config.script

View File

@@ -7,7 +7,7 @@ enabling fast SPI flashing — see `doc/tutorial.md`, Phase 2.5.
These `.bit` files are **not** built here. They come from These `.bit` files are **not** built here. They come from
[quartiq/bscan_spi_bitstreams](https://github.com/quartiq/bscan_spi_bitstreams), [quartiq/bscan_spi_bitstreams](https://github.com/quartiq/bscan_spi_bitstreams),
© QUARTIQ GmbH, MIT-licensed — see `LICENSE.quartiq`. The host-side © QUARTIQ GmbH, MIT-licensed — see `LICENSE.quartiq`. The host-side
framing in `modules/bscan_spi/` matches OpenOCD's `jtagspi` so the same framing in `modules/bscan/` matches OpenOCD's `jtagspi` so the same
bitstreams work. bitstreams work.
| File | Part | Used by | | File | Part | Used by |

View File

@@ -357,7 +357,7 @@ read-latency skew.
### The transfer primitive ### The transfer primitive
`bscan_spi_xfer(jc, t, tx, txlen, rx, rxlen)` in `bscan_spi_xfer(jc, t, tx, txlen, rx, rxlen)` in
`modules/bscan_spi/bscan_spi.c` performs one CS-framed transaction: `modules/bscan/bscan.c` performs one CS-framed transaction:
clock out `txlen` MOSI bytes, then read `rxlen` MISO bytes. It builds clock out `txlen` MOSI bytes, then read `rxlen` MISO bytes. It builds
the quartiq/OpenOCD jtagspi DR frame the quartiq/OpenOCD jtagspi DR frame
(`marker | bit-count | MOSI | latency-skip | MISO`) and matches (`marker | bit-count | MOSI | latency-skip | MISO`) and matches

View File

@@ -4,4 +4,4 @@ file(GLOB_RECURSE ALL_SOURCES "*.c")
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)
add_library(bscan_spi ${ALL_SOURCES}) add_library(bscan ${ALL_SOURCES})

View File

@@ -2,7 +2,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "bscan_spi.h" #include "bscan.h"
/* JTAG byte format expected by drv_TXRX_DATA / drv_TX_TMS: /* JTAG byte format expected by drv_TXRX_DATA / drv_TX_TMS:
* bit 0 (JTAG_STR_DOUT) = TDI value * bit 0 (JTAG_STR_DOUT) = TDI value
@@ -108,6 +108,78 @@ int bscan_shift_dr(jtag_core *jc, const uint8_t *tdi, uint8_t *tdo, int nbits)
return 0; return 0;
} }
int bscan_shift_ir(jtag_core *jc, const uint8_t *tdi, uint8_t *tdo, int nbits)
{
unsigned char tms_buf[8];
unsigned char *buf_out, *buf_in;
int i;
if (!drv_ok(jc) || nbits <= 0) {
return -1;
}
/* Idle -> Select-DR -> Select-IR -> Capture-IR -> Shift-IR */
tms_buf[0] = JTAG_STR_TMS;
tms_buf[1] = JTAG_STR_TMS;
tms_buf[2] = 0;
tms_buf[3] = 0;
jc->io_functions.drv_TX_TMS(jc, tms_buf, 4);
buf_out = malloc(nbits);
if (!buf_out) return -1;
buf_in = tdo ? malloc(nbits) : NULL;
if (tdo && !buf_in) { free(buf_out); return -1; }
for (i = 0; i < nbits; i++) {
uint8_t bit = 0;
if (tdi) {
bit = (tdi[i / 8] >> (i & 7)) & 1u;
}
buf_out[i] = bit ? JTAG_STR_DOUT : 0;
if (i == nbits - 1) {
buf_out[i] |= JTAG_STR_TMS; /* last bit -> Exit1-IR */
}
}
jc->io_functions.drv_TXRX_DATA(jc, buf_out, buf_in, nbits);
if (tdo && buf_in) {
memset(tdo, 0, (size_t)((nbits + 7) / 8));
for (i = 0; i < nbits; i++) {
if (buf_in[i]) {
tdo[i / 8] |= (uint8_t)(1u << (i & 7));
}
}
}
free(buf_out);
free(buf_in);
/* Exit1-IR -> Update-IR -> Idle */
tms_buf[0] = JTAG_STR_TMS;
tms_buf[1] = 0;
jc->io_functions.drv_TX_TMS(jc, tms_buf, 2);
return 0;
}
int bscan_tap_reset(jtag_core *jc)
{
unsigned char tms_buf[8];
if (!drv_ok(jc)) return -1;
/* 5 TMS=1 forces Test-Logic-Reset from any state, then 1 TMS=0
* lands in Run-Test/Idle (where the shift primitives start). */
tms_buf[0] = JTAG_STR_TMS;
tms_buf[1] = JTAG_STR_TMS;
tms_buf[2] = JTAG_STR_TMS;
tms_buf[3] = JTAG_STR_TMS;
tms_buf[4] = JTAG_STR_TMS;
tms_buf[5] = 0;
jc->io_functions.drv_TX_TMS(jc, tms_buf, 6);
return 0;
}
int bscan_idle_cycles(jtag_core *jc, int ncycles) int bscan_idle_cycles(jtag_core *jc, int ncycles)
{ {
unsigned char *buf; unsigned char *buf;

View File

@@ -1,22 +1,23 @@
#ifndef _BSCAN_SPI_H #ifndef _BSCAN_H
#define _BSCAN_SPI_H #define _BSCAN_H
/* /*
* BSCAN-proxy SPI bridge (Phase 2.5). * Single-device JTAG primitives and BSCAN-proxy operations.
* *
* Provides: * Provides:
* - low-level JTAG primitives (set_ir, shift_dr, idle_cycles) that * - low-level JTAG TAP primitives (set_ir, shift_ir, shift_dr,
* operate directly on jc->io_functions, leaving jtag_core untouched; * idle_cycles, tap_reset) that operate directly on jc->io_functions,
* leaving jtag_core untouched. These are the building blocks the SVF
* player and the proxy paths share;
* - bitstream loading via CFG_IN to install a BSCAN proxy in the FPGA * - bitstream loading via CFG_IN to install a BSCAN proxy in the FPGA
* fabric; * fabric (Xilinx);
* - a fast SPI transfer routine via USER1 once the proxy is loaded. * - a fast SPI transfer routine via USER1 once the proxy is loaded.
* *
* (Was modules/bscan_spi/ renamed once it grew past the SPI bridge
* into general TAP primitives.)
*
* Current assumption: single device on the JTAG chain. Multi-device * Current assumption: single device on the JTAG chain. Multi-device
* support requires knowing the IR length of bypassed devices; defer. * support requires knowing the IR length of bypassed devices; defer.
*
* The SPI transfer entry point is wired against the quartiq jtagspi
* proxy convention but the protocol header still needs to be confirmed
* against an actual proxy bitstream (see openocd src/flash/nor/jtagspi.c).
*/ */
#include <stddef.h> #include <stddef.h>
@@ -34,6 +35,11 @@ int bscan_set_ir(jtag_core *jc, unsigned int opcode, int ir_length);
* `tdo` may be NULL (write only). Both buffers are LSB-first per byte. */ * `tdo` may be NULL (write only). Both buffers are LSB-first per byte. */
int bscan_shift_dr(jtag_core *jc, const uint8_t *tdi, uint8_t *tdo, int nbits); int bscan_shift_dr(jtag_core *jc, const uint8_t *tdi, uint8_t *tdo, int nbits);
/* Like bscan_shift_dr but through Shift-IR — a general IR scan with TDO
* capture (bscan_set_ir is opcode-only). Single-device chain; buffers
* LSB-first per byte. Both ends in Run-Test/Idle. */
int bscan_shift_ir(jtag_core *jc, const uint8_t *tdi, uint8_t *tdo, int nbits);
/* Emit `ncycles` TCK cycles while staying in Run-Test/Idle. */ /* Emit `ncycles` TCK cycles while staying in Run-Test/Idle. */
int bscan_idle_cycles(jtag_core *jc, int ncycles); int bscan_idle_cycles(jtag_core *jc, int ncycles);

View File

@@ -39,7 +39,7 @@
#include "os_interface/os_interface.h" #include "os_interface/os_interface.h"
#include "fpga/fpga.h" #include "fpga/fpga.h"
#include "probes/probes.h" #include "probes/probes.h"
#include "bscan_spi/bscan_spi.h" #include "bscan/bscan.h"
#include "spi_flash/spi_flash.h" #include "spi_flash/spi_flash.h"
#include "env.h" #include "env.h"