phase 2.5: add bscan_spi/ — BSCAN proxy infrastructure

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 <opcode_hex> <ir_length>
- bscan_shift_dr <nbits>  (writes zeros, prints captured TDO)
- bscan_load_bitstream <device> <path>

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 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 23:10:54 +02:00
parent 545fe09fd5
commit dec0d14a06
4 changed files with 457 additions and 0 deletions

View File

@@ -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[] = {
"<opcode_hex> <ir_length>",
"Shifts <opcode_hex> 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 <opcode_hex> <ir_length>\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[] = {
"<nbits>",
"Shifts <nbits> 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 <nbits>\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[] = {
"<device> <path>",
"Loads a bitstream (.bit or raw .bin) into device <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 <device> <path>\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}};
///////////////////////////////////////////////////////////////////////////////