From dec0d14a064743eb6ab59a95a97b8fd0b088577f Mon Sep 17 00:00:00 2001 From: francois Date: Wed, 20 May 2026 23:10:54 +0200 Subject: [PATCH] =?UTF-8?q?phase=202.5:=20add=20bscan=5Fspi/=20=E2=80=94?= =?UTF-8?q?=20BSCAN=20proxy=20infrastructure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Low-level JTAG primitives operating directly on jc->io_functions (single-device chain assumed), independent of jtag_core: - bscan_set_ir - bscan_shift_dr (TDI/TDO, LSB-first packing) - bscan_idle_cycles High-level operations driven by an fpga_target descriptor: - bscan_load_bitstream: JPROGRAM -> CFG_IN -> shift (bit-reversed for Xilinx) -> JSTART -> idle -> BYPASS - bscan_load_bitstream_file: parses the Xilinx .bit container header (sections a/b/c/d/e), falls back to raw .bin bscan_spi_xfer is stubbed: the quartiq jtagspi protocol details will be wired once we have a proxy .bit to validate against (OpenOCD src/flash/nor/jtagspi.c is the host-side reference). Three new script commands: - bscan_set_ir - bscan_shift_dr (writes zeros, prints captured TDO) - bscan_load_bitstream The sanity check for a healthy primitive on KU15P: jtag_init_scan; bscan_set_ir 9 6; bscan_shift_dr 32 -> 04 A5 60 93 Co-Authored-By: Claude Opus 4.7 --- modules/bscan_spi/CMakeLists.txt | 7 + modules/bscan_spi/bscan_spi.c | 277 +++++++++++++++++++++++++++++++ modules/bscan_spi/bscan_spi.h | 59 +++++++ modules/script/script.c | 114 +++++++++++++ 4 files changed, 457 insertions(+) create mode 100644 modules/bscan_spi/CMakeLists.txt create mode 100644 modules/bscan_spi/bscan_spi.c create mode 100644 modules/bscan_spi/bscan_spi.h diff --git a/modules/bscan_spi/CMakeLists.txt b/modules/bscan_spi/CMakeLists.txt new file mode 100644 index 0000000..598da03 --- /dev/null +++ b/modules/bscan_spi/CMakeLists.txt @@ -0,0 +1,7 @@ +set(SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + +file(GLOB_RECURSE ALL_SOURCES "*.c") + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_library(bscan_spi ${ALL_SOURCES}) diff --git a/modules/bscan_spi/bscan_spi.c b/modules/bscan_spi/bscan_spi.c new file mode 100644 index 0000000..cec6638 --- /dev/null +++ b/modules/bscan_spi/bscan_spi.c @@ -0,0 +1,277 @@ +#include +#include +#include + +#include "bscan_spi.h" + +/* JTAG byte format expected by drv_TXRX_DATA / drv_TX_TMS: + * bit 0 (JTAG_STR_DOUT) = TDI value + * bit 1 (JTAG_STR_TMS) = TMS value + * bit 4 (JTAG_STR_DIN) = TDO returned by the driver (1 if TDO was high). + * In practice, jtag_core treats the input byte as "non-zero if TDO=1", + * so we just check buf_in[i] != 0 on read. */ + +static int drv_ok(jtag_core *jc) +{ + return jc && jc->io_functions.drv_TX_TMS && jc->io_functions.drv_TXRX_DATA; +} + +/* --- Low-level primitives ----------------------------------------- */ + +int bscan_set_ir(jtag_core *jc, unsigned int opcode, int ir_length) +{ + unsigned char tms_buf[8]; + unsigned char *data_buf; + int i; + + if (!drv_ok(jc) || ir_length <= 0 || ir_length > 32) { + 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); + + /* Shift IR LSB first; raise TMS on the last bit (-> Exit1-IR) */ + data_buf = malloc(ir_length); + if (!data_buf) return -1; + for (i = 0; i < ir_length; i++) { + data_buf[i] = ((opcode >> i) & 1u) ? JTAG_STR_DOUT : 0; + if (i == ir_length - 1) { + data_buf[i] |= JTAG_STR_TMS; + } + } + jc->io_functions.drv_TXRX_DATA(jc, data_buf, NULL, ir_length); + free(data_buf); + + /* 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_shift_dr(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 -> Capture-DR -> Shift-DR */ + tms_buf[0] = JTAG_STR_TMS; + tms_buf[1] = 0; + tms_buf[2] = 0; + jc->io_functions.drv_TX_TMS(jc, tms_buf, 3); + + 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; + } + } + 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-DR -> Update-DR -> 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_idle_cycles(jtag_core *jc, int ncycles) +{ + unsigned char *buf; + int i; + + if (!drv_ok(jc) || ncycles <= 0) { + return -1; + } + buf = malloc(ncycles); + if (!buf) return -1; + for (i = 0; i < ncycles; i++) buf[i] = 0; + jc->io_functions.drv_TX_TMS(jc, buf, ncycles); + free(buf); + return 0; +} + +/* --- High-level operations ---------------------------------------- */ + +static uint8_t reverse_bits(uint8_t b) +{ + b = (uint8_t)(((b & 0xF0u) >> 4) | ((b & 0x0Fu) << 4)); + b = (uint8_t)(((b & 0xCCu) >> 2) | ((b & 0x33u) << 2)); + b = (uint8_t)(((b & 0xAAu) >> 1) | ((b & 0x55u) << 1)); + return b; +} + +int bscan_load_bitstream(jtag_core *jc, const fpga_target *t, + const uint8_t *data, size_t nbytes) +{ + uint8_t *reversed; + unsigned int bypass; + size_t i; + + if (!drv_ok(jc) || !t || !data || nbytes == 0) return -1; + if (!t->ir_jprogram || !t->ir_cfg_in || !t->ir_jstart) { + /* No configuration opcodes known for this family. */ + return -1; + } + + /* JPROGRAM clears the configuration memory. Min ~10k TCK cycles + * to wait for INIT_B to go high before CFG_IN. + * TODO: poll INIT_B via SAMPLE instead of fixed wait. */ + if (bscan_set_ir(jc, t->ir_jprogram, t->ir_length) < 0) return -1; + bscan_idle_cycles(jc, 10000); + + /* CFG_IN routes DR shifts to the configuration interface. */ + if (bscan_set_ir(jc, t->ir_cfg_in, t->ir_length) < 0) return -1; + + /* Xilinx bitstream bytes must be bit-reversed before JTAG shift + * (configuration interface latches MSB first, JTAG shifts LSB first). */ + reversed = malloc(nbytes); + if (!reversed) return -1; + for (i = 0; i < nbytes; i++) { + reversed[i] = reverse_bits(data[i]); + } + if (bscan_shift_dr(jc, reversed, NULL, (int)(nbytes * 8)) < 0) { + free(reversed); + return -1; + } + free(reversed); + + /* JSTART triggers the fabric startup. UG470/UG570: ≥12 cycles in + * Idle to complete the sequence. Use 2000 for margin. */ + if (bscan_set_ir(jc, t->ir_jstart, t->ir_length) < 0) return -1; + bscan_idle_cycles(jc, 2000); + + /* Park on BYPASS (all 1s) so other operations don't trip on a + * lingering instruction. */ + bypass = (t->ir_length >= 32) ? 0xFFFFFFFFu : ((1u << t->ir_length) - 1u); + bscan_set_ir(jc, bypass, t->ir_length); + + return 0; +} + +/* Parse a Xilinx .bit container; return offset and length of the raw + * bitstream payload. Returns -1 if not a .bit. */ +static int xilinx_bit_payload(const uint8_t *buf, size_t buflen, + size_t *out_off, size_t *out_len) +{ + size_t off = 0; + uint16_t hdr_len; + + if (buflen < 13) return -1; + /* First 2 bytes are big-endian length of a magic block (typically 0x0009), + * followed by 9 magic bytes. */ + hdr_len = (uint16_t)((buf[0] << 8) | buf[1]); + if (hdr_len != 0x0009) return -1; + off = 2 + hdr_len; + + /* Then 2 bytes (0x0001) and ASCII-tagged sections a/b/c/d, then 'e' + * followed by 4 bytes big-endian length of the bitstream payload. */ + if (off + 2 > buflen) return -1; + off += 2; + while (off < buflen) { + uint8_t tag = buf[off++]; + if (tag == 'e') { + uint32_t bit_len; + if (off + 4 > buflen) return -1; + bit_len = ((uint32_t)buf[off] << 24) | ((uint32_t)buf[off + 1] << 16) + | ((uint32_t)buf[off + 2] << 8) | (uint32_t)buf[off + 3]; + off += 4; + if (off + bit_len > buflen) return -1; + *out_off = off; + *out_len = bit_len; + return 0; + } + if (tag >= 'a' && tag <= 'd') { + if (off + 2 > buflen) return -1; + hdr_len = (uint16_t)((buf[off] << 8) | buf[off + 1]); + off += 2 + hdr_len; + } else { + return -1; + } + } + return -1; +} + +int bscan_load_bitstream_file(jtag_core *jc, const fpga_target *t, + const char *path) +{ + FILE *f; + long size; + uint8_t *buf; + size_t payload_off = 0; + size_t payload_len = 0; + int ret; + + if (!path) return -1; + f = fopen(path, "rb"); + if (!f) return -1; + if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return -1; } + size = ftell(f); + if (size <= 0) { fclose(f); return -1; } + rewind(f); + + buf = malloc((size_t)size); + if (!buf) { fclose(f); return -1; } + if (fread(buf, 1, (size_t)size, f) != (size_t)size) { + free(buf); fclose(f); return -1; + } + fclose(f); + + if (xilinx_bit_payload(buf, (size_t)size, &payload_off, &payload_len) < 0) { + /* Treat as raw .bin */ + payload_off = 0; + payload_len = (size_t)size; + } + + ret = bscan_load_bitstream(jc, t, buf + payload_off, payload_len); + free(buf); + return ret; +} + +int bscan_spi_xfer(jtag_core *jc, const fpga_target *t, + const uint8_t *tx, uint8_t *rx, size_t nbytes) +{ + /* Skeleton — to be completed once we can validate the protocol + * against an actual quartiq jtagspi proxy bitstream. + * + * Expected outline: + * - bscan_set_ir(jc, t->ir_user1, t->ir_length) + * - bscan_shift_dr with
, where the header + * encodes the bit count and CS state per the proxy convention. + * - extract MISO bytes from the TDO data. */ + (void)jc; (void)t; (void)tx; (void)rx; (void)nbytes; + return -1; +} diff --git a/modules/bscan_spi/bscan_spi.h b/modules/bscan_spi/bscan_spi.h new file mode 100644 index 0000000..af20e88 --- /dev/null +++ b/modules/bscan_spi/bscan_spi.h @@ -0,0 +1,59 @@ +#ifndef _BSCAN_SPI_H +#define _BSCAN_SPI_H + +/* + * BSCAN-proxy SPI bridge (Phase 2.5). + * + * Provides: + * - low-level JTAG primitives (set_ir, shift_dr, idle_cycles) that + * operate directly on jc->io_functions, leaving jtag_core untouched; + * - bitstream loading via CFG_IN to install a BSCAN proxy in the FPGA + * fabric; + * - a fast SPI transfer routine via USER1 once the proxy is loaded. + * + * Current assumption: single device on the JTAG chain. Multi-device + * 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 +#include + +#include "jtag_core/jtag_core.h" +#include "fpga/fpga.h" + +/* --- Low-level primitives (single-device chain) -------------------- */ + +/* Shift `opcode` into IR. `ir_length` is the IR width in bits. */ +int bscan_set_ir(jtag_core *jc, unsigned int opcode, int ir_length); + +/* Shift `nbits` through DR. `tdi` may be NULL (shifts zeros). + * `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); + +/* Emit `ncycles` TCK cycles while staying in Run-Test/Idle. */ +int bscan_idle_cycles(jtag_core *jc, int ncycles); + +/* --- High-level operations ---------------------------------------- */ + +/* Load a raw bitstream payload (no .bit container header) into the + * FPGA via JPROGRAM -> CFG_IN -> shift -> JSTART. Bit-reverses each + * byte before shifting (Xilinx convention). */ +int bscan_load_bitstream(jtag_core *jc, const fpga_target *t, + const uint8_t *data, size_t nbytes); + +/* Convenience wrapper: read a file and load it. Detects the Xilinx + * .bit header and skips it; otherwise treats the file as raw payload. */ +int bscan_load_bitstream_file(jtag_core *jc, const fpga_target *t, + const char *path); + +/* Transfer `nbytes` of SPI data through the BSCAN proxy (USER1 DR). + * Placeholder: protocol details deferred until an actual proxy + * bitstream is available for testing. */ +int bscan_spi_xfer(jtag_core *jc, const fpga_target *t, + const uint8_t *tx, uint8_t *rx, size_t nbytes); + +#endif diff --git a/modules/script/script.c b/modules/script/script.c index a5f5aa9..c7ddcd0 100644 --- a/modules/script/script.c +++ b/modules/script/script.c @@ -38,6 +38,7 @@ #include "bsdl_parser/bsdl_loader.h" #include "os_interface/os_interface.h" #include "fpga/fpga.h" +#include "bscan_spi/bscan_spi.h" #include "env.h" @@ -2793,6 +2794,116 @@ static int cmd_fpga_info(script_ctx *ctx, char *line) return JTAG_CORE_NO_ERROR; } +const char *cmd_bscan_set_ir_help[] = { + " ", + "Shifts into the IR (single-device chain).", + "Useful for testing the low-level JTAG primitives.", + ""}; +static int cmd_bscan_set_ir(script_ctx *ctx, char *line) +{ + char op_txt[DEFAULT_BUFLEN]; + char len_txt[DEFAULT_BUFLEN]; + unsigned int opcode; + int ir_length, ret; + jtag_core *jc = (jtag_core *)ctx->app_ctx; + + if (get_param(ctx, line, 1, op_txt) < 0 || get_param(ctx, line, 2, len_txt) < 0) { + ctx->script_printf(ctx, MSG_ERROR, "Usage: bscan_set_ir \n"); + return JTAG_CORE_BAD_PARAMETER; + } + opcode = (unsigned int)strtoul(op_txt, NULL, 16); + ir_length = (int)strtoul(len_txt, NULL, 0); + + ret = bscan_set_ir(jc, opcode, ir_length); + if (ret < 0) { + ctx->script_printf(ctx, MSG_ERROR, "bscan_set_ir failed (%d)\n", ret); + return JTAG_CORE_IO_ERROR; + } + ctx->script_printf(ctx, MSG_INFO_0, "IR <- 0x%X (%d bits)\n", opcode, ir_length); + return JTAG_CORE_NO_ERROR; +} + +const char *cmd_bscan_shift_dr_help[] = { + "", + "Shifts zeros into DR, prints the captured TDO in hex (LSB first).", + "Run after bscan_set_ir to read a register (e.g. IDCODE -> shift 32 bits).", + ""}; +static int cmd_bscan_shift_dr(script_ctx *ctx, char *line) +{ + char nb_txt[DEFAULT_BUFLEN]; + int nbits, nbytes, i, ret; + uint8_t *tdo; + jtag_core *jc = (jtag_core *)ctx->app_ctx; + + if (get_param(ctx, line, 1, nb_txt) < 0) { + ctx->script_printf(ctx, MSG_ERROR, "Usage: bscan_shift_dr \n"); + return JTAG_CORE_BAD_PARAMETER; + } + nbits = (int)strtoul(nb_txt, NULL, 0); + if (nbits <= 0 || nbits > 8 * DEFAULT_BUFLEN) { + ctx->script_printf(ctx, MSG_ERROR, "Invalid nbits\n"); + return JTAG_CORE_BAD_PARAMETER; + } + nbytes = (nbits + 7) / 8; + tdo = calloc(1, (size_t)nbytes); + if (!tdo) return JTAG_CORE_MEM_ERROR; + + ret = bscan_shift_dr(jc, NULL, tdo, nbits); + if (ret < 0) { + free(tdo); + ctx->script_printf(ctx, MSG_ERROR, "bscan_shift_dr failed (%d)\n", ret); + return JTAG_CORE_IO_ERROR; + } + ctx->script_printf(ctx, MSG_INFO_0, "DR ="); + for (i = nbytes - 1; i >= 0; i--) { + ctx->script_printf(ctx, MSG_NONE, " %.2X", tdo[i]); + } + ctx->script_printf(ctx, MSG_NONE, "\n"); + free(tdo); + return JTAG_CORE_NO_ERROR; +} + +const char *cmd_bscan_load_bitstream_help[] = { + " ", + "Loads a bitstream (.bit or raw .bin) into device via JPROGRAM/CFG_IN/JSTART.", + "Device must be matched in the FPGA registry (run fpga_info to check).", + ""}; +static int cmd_bscan_load_bitstream(script_ctx *ctx, char *line) +{ + char dev_txt[DEFAULT_BUFLEN]; + char path[DEFAULT_BUFLEN]; + int device, ret; + unsigned long idcode; + const fpga_target *t; + jtag_core *jc = (jtag_core *)ctx->app_ctx; + + if (get_param(ctx, line, 1, dev_txt) < 0 || get_param(ctx, line, 2, path) < 0) { + ctx->script_printf(ctx, MSG_ERROR, "Usage: bscan_load_bitstream \n"); + return JTAG_CORE_BAD_PARAMETER; + } + device = (int)strtoul(dev_txt, NULL, 0); + + if (jtagcore_get_number_of_devices(jc) <= 0) { + ctx->script_printf(ctx, MSG_ERROR, "No device on the chain. Run jtag_autoinit first.\n"); + return JTAG_CORE_NOT_FOUND; + } + idcode = jtagcore_get_dev_id(jc, device); + t = fpga_lookup_by_idcode(idcode); + if (!t) { + ctx->script_printf(ctx, MSG_ERROR, "Device %d IDCODE 0x%.8lX not in FPGA registry\n", device, idcode); + return JTAG_CORE_NOT_FOUND; + } + + ctx->script_printf(ctx, MSG_INFO_0, "Loading %s on device %d (%s)...\n", path, device, t->name); + ret = bscan_load_bitstream_file(jc, t, path); + if (ret < 0) { + ctx->script_printf(ctx, MSG_ERROR, "bscan_load_bitstream_file failed (%d)\n", ret); + return JTAG_CORE_IO_ERROR; + } + ctx->script_printf(ctx, MSG_INFO_0, "Bitstream loaded.\n"); + return JTAG_CORE_NO_ERROR; +} + cmd_list script_commands_list[] = { {"print", cmd_print, cmd_print_help}, @@ -2837,6 +2948,9 @@ cmd_list script_commands_list[] = {"jtag_spi_rd_wr", cmd_spi_rd_wr, cmd_spi_rd_wr_help}, {"fpga_list", cmd_fpga_list, cmd_fpga_list_help}, {"fpga_info", cmd_fpga_info, cmd_fpga_info_help}, + {"bscan_set_ir", cmd_bscan_set_ir, cmd_bscan_set_ir_help}, + {"bscan_shift_dr", cmd_bscan_shift_dr, cmd_bscan_shift_dr_help}, + {"bscan_load_bitstream", cmd_bscan_load_bitstream, cmd_bscan_load_bitstream_help}, {0, 0}}; ///////////////////////////////////////////////////////////////////////////////