svf: initial SVF player + svf_play command
New modules/svf/ plays the single-device SVF subset over the bscan_* primitives: SIR/SDR with masked TDO compare, RUNTEST (TCK/SEC), STATE (RESET/IDLE), ENDIR/ENDDR (IDLE only), HIR/HDR/TIR/TDR (length 0), TRST, FREQUENCY. Exposed as `svf_play <file>`. It warms up the FTDI link first — the MPSSE's first data read after open returns stale FIFO content, normally hidden because jtag_scan runs before anything. Also adds the bscan_tap_reset prototype. Validated on the live IGLOO2 M2GL010T: a hand-written IDCODE-check SVF passes (masked compare) and a deliberately wrong one is caught at the mismatching bit. A generic program dispatch off the prog tag, multi-device chains and non-IDLE end states are still TODO. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
12
CLAUDE.md
12
CLAUDE.md
@@ -242,6 +242,18 @@ progress and partial ops. The equivalent SVF ("indirect flash") is huge
|
|||||||
|
|
||||||
### The SVF player (the real work)
|
### The SVF player (the real work)
|
||||||
|
|
||||||
|
**Status: initial implementation in `modules/svf/` + the `svf_play <file>`
|
||||||
|
command.** Supports the single-device subset: `SIR`/`SDR` with
|
||||||
|
`TDI`/`TDO`/`MASK`/`SMASK` and the masked TDO compare, `RUNTEST`
|
||||||
|
(TCK/SCK + SEC), `STATE` (RESET/IDLE), `ENDIR`/`ENDDR` (IDLE only),
|
||||||
|
`HIR/HDR/TIR/TDR` (length 0 only), `TRST`, `FREQUENCY`. Built on the
|
||||||
|
`bscan_*` primitives (`shift_ir`/`shift_dr`/`tap_reset`/`idle_cycles`).
|
||||||
|
Validated on the live IGLOO2 with a hand-written IDCODE-check SVF (pass
|
||||||
|
and a deliberate mismatch). `svf_play` warms up the FTDI link first (its
|
||||||
|
first data read after open returns stale FIFO content). Not yet wired
|
||||||
|
into a generic `program` dispatch off the `prog` tag; no multi-device
|
||||||
|
headers, no non-IDLE end states. Remaining design below.
|
||||||
|
|
||||||
A player is more than shifting bits. It must handle:
|
A player is more than shifting bits. It must handle:
|
||||||
- `SIR`/`SDR` with `TDI`/`TDO`/`MASK` — the **masked TDO compare** is
|
- `SIR`/`SDR` with `TDI`/`TDO`/`MASK` — the **masked TDO compare** is
|
||||||
what detects prog/erase failures; that's the main addition over
|
what detects prog/erase failures; that's the main addition over
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ the full walkthrough (probe → proxy → flash) lives in
|
|||||||
| FPGA registry | `fpga_list`, `fpga_info` |
|
| FPGA registry | `fpga_list`, `fpga_info` |
|
||||||
| BSCAN proxy | `bscan_load_bitstream`, `bscan_jedec`, `bscan_set_ir`, `bscan_shift_dr` |
|
| BSCAN proxy | `bscan_load_bitstream`, `bscan_jedec`, `bscan_set_ir`, `bscan_shift_dr` |
|
||||||
| SPI flash (via proxy) | `flash_detect`, `flash_read`, `flash_erase`, `flash_write`, `flash_verify` |
|
| SPI flash (via proxy) | `flash_detect`, `flash_read`, `flash_erase`, `flash_write`, `flash_verify` |
|
||||||
|
| SVF player | `svf_play` |
|
||||||
| Misc | `help`, `?`, `version`, `exit` |
|
| Misc | `help`, `?`, `version`, `exit` |
|
||||||
|
|
||||||
Use `help <command>` for per-command help.
|
Use `help <command>` for per-command help.
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ 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);
|
||||||
|
|
||||||
|
/* Force Test-Logic-Reset (5 TCK with TMS=1) and land in Run-Test/Idle. */
|
||||||
|
int bscan_tap_reset(jtag_core *jc);
|
||||||
|
|
||||||
/* --- High-level operations ---------------------------------------- */
|
/* --- High-level operations ---------------------------------------- */
|
||||||
|
|
||||||
/* Load a raw bitstream payload (no .bit container header) into the
|
/* Load a raw bitstream payload (no .bit container header) into the
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
#include "fpga/fpga.h"
|
#include "fpga/fpga.h"
|
||||||
#include "probes/probes.h"
|
#include "probes/probes.h"
|
||||||
#include "bscan/bscan.h"
|
#include "bscan/bscan.h"
|
||||||
|
#include "svf/svf.h"
|
||||||
#include "spi_flash/spi_flash.h"
|
#include "spi_flash/spi_flash.h"
|
||||||
|
|
||||||
#include "env.h"
|
#include "env.h"
|
||||||
@@ -3512,6 +3513,35 @@ static int cmd_flash_verify(script_ctx *ctx, char *line)
|
|||||||
return JTAG_CORE_NO_ERROR;
|
return JTAG_CORE_NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void svf_log_cb(void *user, int is_error, const char *msg)
|
||||||
|
{
|
||||||
|
script_ctx *ctx = (script_ctx *)user;
|
||||||
|
ctx->script_printf(ctx, is_error ? MSG_ERROR : MSG_INFO_0, "%s\n", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *cmd_svf_play_help[] = {
|
||||||
|
"<file>(str)",
|
||||||
|
"Play an SVF file over the open probe (single-device chain).",
|
||||||
|
"Runs SIR/SDR/RUNTEST/STATE with masked TDO compare — the universal",
|
||||||
|
"way to program a part from a vendor-exported SVF (Lattice, Microsemi,",
|
||||||
|
"Xilinx fabric, CPLDs). A TDO mismatch stops play and is reported.",
|
||||||
|
""
|
||||||
|
};
|
||||||
|
static int cmd_svf_play(script_ctx *ctx, char *line)
|
||||||
|
{
|
||||||
|
jtag_core *jc;
|
||||||
|
char path[MAX_PATH + 1];
|
||||||
|
|
||||||
|
jc = (jtag_core *)ctx->app_ctx;
|
||||||
|
if (get_param(ctx, line, 1, path) <= 0) {
|
||||||
|
ctx->script_printf(ctx, MSG_ERROR, "Usage: svf_play <file>\n");
|
||||||
|
return JTAG_CORE_BAD_PARAMETER;
|
||||||
|
}
|
||||||
|
if (svf_play_file(jc, path, svf_log_cb, ctx, NULL) < 0)
|
||||||
|
return JTAG_CORE_ACCESS_ERROR;
|
||||||
|
return JTAG_CORE_NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
cmd_list script_commands_list[] =
|
cmd_list script_commands_list[] =
|
||||||
{
|
{
|
||||||
{"print", cmd_print, cmd_print_help},
|
{"print", cmd_print, cmd_print_help},
|
||||||
@@ -3567,6 +3597,7 @@ cmd_list script_commands_list[] =
|
|||||||
{"flash_erase", cmd_flash_erase, cmd_flash_erase_help},
|
{"flash_erase", cmd_flash_erase, cmd_flash_erase_help},
|
||||||
{"flash_write", cmd_flash_write, cmd_flash_write_help},
|
{"flash_write", cmd_flash_write, cmd_flash_write_help},
|
||||||
{"flash_verify", cmd_flash_verify, cmd_flash_verify_help},
|
{"flash_verify", cmd_flash_verify, cmd_flash_verify_help},
|
||||||
|
{"svf_play", cmd_svf_play, cmd_svf_play_help},
|
||||||
{0, 0}};
|
{0, 0}};
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|||||||
7
modules/svf/CMakeLists.txt
Normal file
7
modules/svf/CMakeLists.txt
Normal file
@@ -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(svf ${ALL_SOURCES})
|
||||||
447
modules/svf/svf.c
Normal file
447
modules/svf/svf.c
Normal file
@@ -0,0 +1,447 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "bscan/bscan.h"
|
||||||
|
#include "svf.h"
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ *
|
||||||
|
* Player state
|
||||||
|
* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
jtag_core *jc;
|
||||||
|
svf_log_fn log;
|
||||||
|
void *user;
|
||||||
|
long line; /* 1-based, for diagnostics */
|
||||||
|
svf_stats stats;
|
||||||
|
|
||||||
|
/* Sticky TDI / MASK per scan type ([0] = DR, [1] = IR). TDO is not
|
||||||
|
* sticky: a compare happens only when TDO is given on that scan. */
|
||||||
|
struct {
|
||||||
|
int len;
|
||||||
|
uint8_t *tdi;
|
||||||
|
uint8_t *mask;
|
||||||
|
} st[2];
|
||||||
|
} svf_player;
|
||||||
|
|
||||||
|
static void slog(svf_player *p, int is_error, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
char buf[256];
|
||||||
|
va_list ap;
|
||||||
|
if (!p->log) return;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
p->log(p->user, is_error, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ *
|
||||||
|
* Hex / token helpers
|
||||||
|
* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
static int hexval(int c)
|
||||||
|
{
|
||||||
|
if (c >= '0' && c <= '9') return c - '0';
|
||||||
|
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||||
|
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse `hexlen` chars of hex (whitespace allowed) representing `nbits`
|
||||||
|
* bits, LSB shifted first, into buf (LSB-first per byte, as the bscan_*
|
||||||
|
* shifters expect). Returns 0, or -1 on a stray character. */
|
||||||
|
static int parse_hex_bits(const char *hex, int hexlen, int nbits, uint8_t *buf)
|
||||||
|
{
|
||||||
|
int nbytes = (nbits + 7) / 8;
|
||||||
|
int i, nib = 0;
|
||||||
|
|
||||||
|
memset(buf, 0, (size_t)nbytes);
|
||||||
|
for (i = hexlen - 1; i >= 0; i--) {
|
||||||
|
int v;
|
||||||
|
char c = hex[i];
|
||||||
|
if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
|
||||||
|
continue;
|
||||||
|
v = hexval((unsigned char)c);
|
||||||
|
if (v < 0) return -1;
|
||||||
|
if (v) {
|
||||||
|
int k;
|
||||||
|
for (k = 0; k < 4; k++) {
|
||||||
|
int bit = nib * 4 + k;
|
||||||
|
if (bit < nbits && (v & (1 << k)))
|
||||||
|
buf[bit / 8] |= (uint8_t)(1u << (bit & 7));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nib++;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct { const char *p, *end; } svf_cur;
|
||||||
|
|
||||||
|
enum { TK_END = 0, TK_WORD, TK_HEX };
|
||||||
|
|
||||||
|
/* Next token: TK_WORD copies into word[wordsz]; TK_HEX returns a pointer
|
||||||
|
* and length into the source (no copy — bodies can be large). */
|
||||||
|
static int svf_next(svf_cur *c, char *word, int wordsz,
|
||||||
|
const char **hp, int *hlen)
|
||||||
|
{
|
||||||
|
while (c->p < c->end &&
|
||||||
|
(*c->p == ' ' || *c->p == '\t' || *c->p == '\r' || *c->p == '\n'))
|
||||||
|
c->p++;
|
||||||
|
if (c->p >= c->end) return TK_END;
|
||||||
|
|
||||||
|
if (*c->p == '(') {
|
||||||
|
const char *start = ++c->p;
|
||||||
|
while (c->p < c->end && *c->p != ')') c->p++;
|
||||||
|
*hp = start;
|
||||||
|
*hlen = (int)(c->p - start);
|
||||||
|
if (c->p < c->end) c->p++; /* skip ')' */
|
||||||
|
return TK_HEX;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
int n = 0;
|
||||||
|
while (c->p < c->end && *c->p != ' ' && *c->p != '\t' &&
|
||||||
|
*c->p != '\r' && *c->p != '\n' && *c->p != '(') {
|
||||||
|
if (n < wordsz - 1) word[n++] = *c->p;
|
||||||
|
c->p++;
|
||||||
|
}
|
||||||
|
word[n] = '\0';
|
||||||
|
}
|
||||||
|
return TK_WORD;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ci_eq(const char *a, const char *b)
|
||||||
|
{
|
||||||
|
while (*a && *b) {
|
||||||
|
int ca = *a, cb = *b;
|
||||||
|
if (ca >= 'a' && ca <= 'z') ca -= 32;
|
||||||
|
if (cb >= 'a' && cb <= 'z') cb -= 32;
|
||||||
|
if (ca != cb) return 0;
|
||||||
|
a++; b++;
|
||||||
|
}
|
||||||
|
return *a == *b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ *
|
||||||
|
* Commands
|
||||||
|
* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/* SIR / SDR: shift IR/DR with optional masked TDO compare. */
|
||||||
|
static int do_scan(svf_player *p, int is_ir, svf_cur *c)
|
||||||
|
{
|
||||||
|
char word[64];
|
||||||
|
const char *hex;
|
||||||
|
int hlen, t;
|
||||||
|
int len = -1;
|
||||||
|
int nbytes;
|
||||||
|
uint8_t *tdi = NULL, *tdo = NULL, *mask = NULL, *cap = NULL;
|
||||||
|
int have_tdi = 0, have_tdo = 0, have_mask = 0;
|
||||||
|
int idx = is_ir ? 1 : 0;
|
||||||
|
int rc = -1, i;
|
||||||
|
|
||||||
|
/* length */
|
||||||
|
if (svf_next(c, word, sizeof(word), &hex, &hlen) != TK_WORD) {
|
||||||
|
slog(p, 1, "line %ld: %s missing length", p->line, is_ir ? "SIR" : "SDR");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
len = atoi(word);
|
||||||
|
if (len <= 0) {
|
||||||
|
slog(p, 1, "line %ld: %s bad length '%s'", p->line, is_ir ? "SIR" : "SDR", word);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
nbytes = (len + 7) / 8;
|
||||||
|
|
||||||
|
/* fields */
|
||||||
|
while ((t = svf_next(c, word, sizeof(word), &hex, &hlen)) != TK_END) {
|
||||||
|
const char *fld = word;
|
||||||
|
uint8_t **dst;
|
||||||
|
int *flag;
|
||||||
|
if (t != TK_WORD) goto out;
|
||||||
|
if (ci_eq(fld, "TDI")) { dst = &tdi; flag = &have_tdi; }
|
||||||
|
else if (ci_eq(fld, "TDO")) { dst = &tdo; flag = &have_tdo; }
|
||||||
|
else if (ci_eq(fld, "MASK")) { dst = &mask; flag = &have_mask; }
|
||||||
|
else if (ci_eq(fld, "SMASK")) { dst = NULL; flag = NULL; }
|
||||||
|
else { slog(p, 1, "line %ld: unexpected '%s' in scan", p->line, fld); goto out; }
|
||||||
|
|
||||||
|
if (svf_next(c, word, sizeof(word), &hex, &hlen) != TK_HEX) {
|
||||||
|
slog(p, 1, "line %ld: '%s' without (hex)", p->line, fld);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (!dst) continue; /* SMASK: parsed, ignored */
|
||||||
|
*dst = malloc((size_t)nbytes);
|
||||||
|
if (!*dst) { slog(p, 1, "out of memory"); goto out; }
|
||||||
|
if (parse_hex_bits(hex, hlen, len, *dst) < 0) {
|
||||||
|
slog(p, 1, "line %ld: bad hex in %s", p->line, fld);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
*flag = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resolve sticky TDI: reuse last value of same length when omitted. */
|
||||||
|
if (!have_tdi) {
|
||||||
|
if (p->st[idx].tdi && p->st[idx].len == len) {
|
||||||
|
tdi = malloc((size_t)nbytes);
|
||||||
|
if (!tdi) { slog(p, 1, "out of memory"); goto out; }
|
||||||
|
memcpy(tdi, p->st[idx].tdi, (size_t)nbytes);
|
||||||
|
} else {
|
||||||
|
tdi = calloc(1, (size_t)nbytes); /* default: shift zeros */
|
||||||
|
if (!tdi) { slog(p, 1, "out of memory"); goto out; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Resolve sticky MASK when a compare is wanted but no MASK given. */
|
||||||
|
if (have_tdo && !have_mask) {
|
||||||
|
mask = malloc((size_t)nbytes);
|
||||||
|
if (!mask) { slog(p, 1, "out of memory"); goto out; }
|
||||||
|
if (p->st[idx].mask && p->st[idx].len == len)
|
||||||
|
memcpy(mask, p->st[idx].mask, (size_t)nbytes);
|
||||||
|
else
|
||||||
|
memset(mask, 0xFF, (size_t)nbytes); /* default: all care */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Shift. */
|
||||||
|
if (have_tdo) {
|
||||||
|
cap = calloc(1, (size_t)nbytes);
|
||||||
|
if (!cap) { slog(p, 1, "out of memory"); goto out; }
|
||||||
|
}
|
||||||
|
if ((is_ir ? bscan_shift_ir(p->jc, tdi, cap, len)
|
||||||
|
: bscan_shift_dr(p->jc, tdi, cap, len)) < 0) {
|
||||||
|
slog(p, 1, "line %ld: shift %s failed (probe ok?)", p->line, is_ir ? "IR" : "DR");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
p->stats.scans++;
|
||||||
|
|
||||||
|
/* Masked compare. */
|
||||||
|
if (have_tdo) {
|
||||||
|
p->stats.compares++;
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
int m = (mask[i / 8] >> (i & 7)) & 1u;
|
||||||
|
if (!m) continue;
|
||||||
|
if (((cap[i / 8] >> (i & 7)) & 1u) != ((tdo[i / 8] >> (i & 7)) & 1u)) {
|
||||||
|
slog(p, 1, "line %ld: %s TDO mismatch at bit %d (len %d)",
|
||||||
|
p->line, is_ir ? "SIR" : "SDR", i, len);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update sticky state. */
|
||||||
|
if (have_tdi || p->st[idx].len != len) {
|
||||||
|
free(p->st[idx].tdi);
|
||||||
|
p->st[idx].tdi = malloc((size_t)nbytes);
|
||||||
|
if (p->st[idx].tdi) memcpy(p->st[idx].tdi, tdi, (size_t)nbytes);
|
||||||
|
}
|
||||||
|
if (have_mask) {
|
||||||
|
free(p->st[idx].mask);
|
||||||
|
p->st[idx].mask = malloc((size_t)nbytes);
|
||||||
|
if (p->st[idx].mask) memcpy(p->st[idx].mask, mask, (size_t)nbytes);
|
||||||
|
}
|
||||||
|
p->st[idx].len = len;
|
||||||
|
|
||||||
|
rc = 0;
|
||||||
|
out:
|
||||||
|
free(tdi); free(tdo); free(mask); free(cap);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HIR/HDR/TIR/TDR: header/trailer bits. Single-device only -> must be 0. */
|
||||||
|
static int do_header(svf_player *p, const char *kw, svf_cur *c)
|
||||||
|
{
|
||||||
|
char word[64];
|
||||||
|
const char *hex; int hlen, t, len;
|
||||||
|
|
||||||
|
if (svf_next(c, word, sizeof(word), &hex, &hlen) != TK_WORD) {
|
||||||
|
slog(p, 1, "line %ld: %s missing length", p->line, kw);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
len = atoi(word);
|
||||||
|
if (len != 0) {
|
||||||
|
slog(p, 1, "line %ld: %s %d — multi-device chains not supported",
|
||||||
|
p->line, kw, len);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
/* drain any TDI()/TDO()/... that may accompany a 0-length header */
|
||||||
|
while ((t = svf_next(c, word, sizeof(word), &hex, &hlen)) != TK_END)
|
||||||
|
;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int do_endstate(svf_player *p, const char *kw, svf_cur *c)
|
||||||
|
{
|
||||||
|
char word[64]; const char *hex; int hlen;
|
||||||
|
if (svf_next(c, word, sizeof(word), &hex, &hlen) != TK_WORD) {
|
||||||
|
slog(p, 1, "line %ld: %s missing state", p->line, kw);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!ci_eq(word, "IDLE")) {
|
||||||
|
slog(p, 1, "line %ld: %s %s — only IDLE end state supported",
|
||||||
|
p->line, kw, word);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0; /* scans already finish in Run-Test/Idle */
|
||||||
|
}
|
||||||
|
|
||||||
|
static int do_state(svf_player *p, svf_cur *c)
|
||||||
|
{
|
||||||
|
char word[64]; const char *hex; int hlen, t;
|
||||||
|
int want_reset = 0, only_idle = 1;
|
||||||
|
|
||||||
|
while ((t = svf_next(c, word, sizeof(word), &hex, &hlen)) != TK_END) {
|
||||||
|
if (t != TK_WORD) continue;
|
||||||
|
if (ci_eq(word, "RESET")) want_reset = 1;
|
||||||
|
else if (ci_eq(word, "IDLE")) /* ok */;
|
||||||
|
else only_idle = 0;
|
||||||
|
}
|
||||||
|
if (want_reset)
|
||||||
|
return bscan_tap_reset(p->jc);
|
||||||
|
if (only_idle)
|
||||||
|
return 0; /* already in Idle between commands */
|
||||||
|
slog(p, 1, "line %ld: STATE — only RESET / IDLE supported", p->line);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int do_runtest(svf_player *p, svf_cur *c)
|
||||||
|
{
|
||||||
|
char word[64]; const char *hex; int hlen, t;
|
||||||
|
long cycles = 0;
|
||||||
|
double secs = 0.0;
|
||||||
|
char pending[64];
|
||||||
|
int have_pending = 0;
|
||||||
|
|
||||||
|
while ((t = svf_next(c, word, sizeof(word), &hex, &hlen)) != TK_END) {
|
||||||
|
if (t != TK_WORD) continue;
|
||||||
|
if (have_pending && (ci_eq(word, "TCK") || ci_eq(word, "SCK"))) {
|
||||||
|
cycles = atol(pending);
|
||||||
|
have_pending = 0;
|
||||||
|
} else if (have_pending && ci_eq(word, "SEC")) {
|
||||||
|
secs = atof(pending);
|
||||||
|
have_pending = 0;
|
||||||
|
} else if (word[0] == '.' || (word[0] >= '0' && word[0] <= '9')) {
|
||||||
|
strncpy(pending, word, sizeof(pending) - 1);
|
||||||
|
pending[sizeof(pending) - 1] = '\0';
|
||||||
|
have_pending = 1;
|
||||||
|
} else {
|
||||||
|
have_pending = 0; /* MAXIMUM / ENDSTATE / state names, etc. */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (cycles > 0) {
|
||||||
|
int chunk = (cycles > 1000000) ? 1000000 : (int)cycles;
|
||||||
|
if (bscan_idle_cycles(p->jc, chunk) < 0) {
|
||||||
|
slog(p, 1, "line %ld: RUNTEST idle failed", p->line);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
cycles -= chunk;
|
||||||
|
}
|
||||||
|
if (secs > 0.0) {
|
||||||
|
if (secs > 60.0) secs = 60.0; /* sanity cap */
|
||||||
|
usleep((useconds_t)(secs * 1e6));
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ *
|
||||||
|
* Top-level
|
||||||
|
* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/* Blank out '!' and '//' comments to end-of-line (keep newlines). */
|
||||||
|
static void strip_comments(char *buf, long len)
|
||||||
|
{
|
||||||
|
long i;
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
if (buf[i] == '!' || (buf[i] == '/' && i + 1 < len && buf[i + 1] == '/')) {
|
||||||
|
while (i < len && buf[i] != '\n') buf[i++] = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int dispatch(svf_player *p, char *cmd, long cmdlen)
|
||||||
|
{
|
||||||
|
svf_cur c;
|
||||||
|
char kw[64];
|
||||||
|
const char *hex; int hlen;
|
||||||
|
|
||||||
|
c.p = cmd; c.end = cmd + cmdlen;
|
||||||
|
if (svf_next(&c, kw, sizeof(kw), &hex, &hlen) != TK_WORD)
|
||||||
|
return 0; /* blank */
|
||||||
|
|
||||||
|
p->stats.commands++;
|
||||||
|
|
||||||
|
if (ci_eq(kw, "SDR")) return do_scan(p, 0, &c);
|
||||||
|
else if (ci_eq(kw, "SIR")) return do_scan(p, 1, &c);
|
||||||
|
else if (ci_eq(kw, "RUNTEST")) return do_runtest(p, &c);
|
||||||
|
else if (ci_eq(kw, "STATE")) return do_state(p, &c);
|
||||||
|
else if (ci_eq(kw, "ENDIR") || ci_eq(kw, "ENDDR")) return do_endstate(p, kw, &c);
|
||||||
|
else if (ci_eq(kw, "HIR") || ci_eq(kw, "HDR") ||
|
||||||
|
ci_eq(kw, "TIR") || ci_eq(kw, "TDR")) return do_header(p, kw, &c);
|
||||||
|
else if (ci_eq(kw, "TRST") || ci_eq(kw, "FREQUENCY"))
|
||||||
|
return 0; /* accepted; not acted on (single-device, clock set at open) */
|
||||||
|
|
||||||
|
slog(p, 1, "line %ld: unsupported command '%s'", p->line, kw);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int svf_play_file(jtag_core *jc, const char *path,
|
||||||
|
svf_log_fn log, void *user, svf_stats *stats)
|
||||||
|
{
|
||||||
|
FILE *f;
|
||||||
|
long size, i, cmd_start;
|
||||||
|
char *buf;
|
||||||
|
svf_player p;
|
||||||
|
int rc = 0;
|
||||||
|
|
||||||
|
memset(&p, 0, sizeof(p));
|
||||||
|
p.jc = jc; p.log = log; p.user = user; p.line = 1;
|
||||||
|
|
||||||
|
f = fopen(path, "rb");
|
||||||
|
if (!f) { slog(&p, 1, "cannot open %s", path); return -1; }
|
||||||
|
if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return -1; }
|
||||||
|
size = ftell(f);
|
||||||
|
if (size <= 0) { fclose(f); slog(&p, 1, "empty file"); return -1; }
|
||||||
|
rewind(f);
|
||||||
|
buf = malloc((size_t)size + 1);
|
||||||
|
if (!buf) { fclose(f); slog(&p, 1, "out of memory"); return -1; }
|
||||||
|
if (fread(buf, 1, (size_t)size, f) != (size_t)size) {
|
||||||
|
free(buf); fclose(f); slog(&p, 1, "read error"); return -1;
|
||||||
|
}
|
||||||
|
buf[size] = '\0';
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
strip_comments(buf, size);
|
||||||
|
|
||||||
|
/* Warm up the link before the first real scan: the FTDI MPSSE's first
|
||||||
|
* data read after a fresh open returns stale FIFO content. The normal
|
||||||
|
* Viveris flow hides this (jtag_scan/autoinit runs first); do a
|
||||||
|
* throwaway reset + DR read so a standalone svf_play is reliable. */
|
||||||
|
{
|
||||||
|
uint8_t junk[4];
|
||||||
|
bscan_tap_reset(jc);
|
||||||
|
bscan_shift_dr(jc, NULL, junk, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Split on ';' and dispatch each command, tracking the line number. */
|
||||||
|
cmd_start = 0;
|
||||||
|
for (i = 0; i < size; i++) {
|
||||||
|
if (buf[i] == '\n') p.line++;
|
||||||
|
if (buf[i] == ';') {
|
||||||
|
rc = dispatch(&p, buf + cmd_start, i - cmd_start);
|
||||||
|
if (rc < 0) break;
|
||||||
|
cmd_start = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(buf);
|
||||||
|
free(p.st[0].tdi); free(p.st[0].mask);
|
||||||
|
free(p.st[1].tdi); free(p.st[1].mask);
|
||||||
|
|
||||||
|
if (stats) *stats = p.stats;
|
||||||
|
if (rc == 0)
|
||||||
|
slog(&p, 0, "SVF done: %ld commands, %ld scans, %ld compares",
|
||||||
|
p.stats.commands, p.stats.scans, p.stats.compares);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
40
modules/svf/svf.h
Normal file
40
modules/svf/svf.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#ifndef _SVF_H
|
||||||
|
#define _SVF_H
|
||||||
|
|
||||||
|
#include "jtag_core/jtag_core.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SVF player (single-device chain).
|
||||||
|
*
|
||||||
|
* Plays a standard subset of Serial Vector Format over the currently
|
||||||
|
* open probe, using the bscan_* TAP primitives. SVF is what Libero,
|
||||||
|
* Diamond/Radiant, Vivado, … export with the vendor programming
|
||||||
|
* algorithm already baked in, so one player programs many targets with
|
||||||
|
* no per-vendor code.
|
||||||
|
*
|
||||||
|
* Supported: SIR / SDR with TDI/TDO/MASK/SMASK and a masked TDO compare;
|
||||||
|
* RUNTEST (TCK/SCK counts and SEC delays); STATE (RESET / IDLE);
|
||||||
|
* ENDIR / ENDDR (IDLE only); HIR/HDR/TIR/TDR (length 0 only); TRST;
|
||||||
|
* FREQUENCY. SMASK is parsed but not applied.
|
||||||
|
*
|
||||||
|
* Not supported (single-device tool): non-zero header/trailer scans
|
||||||
|
* (multi-device chains) and non-IDLE stable end states — both rejected
|
||||||
|
* with a clear error.
|
||||||
|
*
|
||||||
|
* log() receives progress / error lines (is_error != 0 on failure).
|
||||||
|
* Returns 0 on success, < 0 on parse error or a TDO compare mismatch.
|
||||||
|
* stats may be NULL.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef void (*svf_log_fn)(void *user, int is_error, const char *msg);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
long commands; /* commands executed */
|
||||||
|
long scans; /* SIR + SDR */
|
||||||
|
long compares; /* TDO compares performed */
|
||||||
|
} svf_stats;
|
||||||
|
|
||||||
|
int svf_play_file(jtag_core *jc, const char *path,
|
||||||
|
svf_log_fn log, void *user, svf_stats *stats);
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user