jtag/fpga: prog method tag + RTCK link setting (phase C)

- fpga_target gains a prog method (proxy_spi/svf/none), set in the
  registry or inferred when omitted (proxy_bitstream -> proxy_spi;
  Microsemi/Lattice -> svf); shown by fpga_info/fpga_list and exposed via
  fpga_prog_method_name() for the future program dispatch
- generalise RTCK as a neutral JTAG_RTCK, mirrored to
  PROBE_FTDI_JTAG_ENABLE_RTCK at open (FTDI-only)
- reset abstraction deferred (no clean neutral form yet); the program
  dispatch command itself lands with the SVF player

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 11:53:02 +02:00
parent ac883237ac
commit 2a03cb1145
5 changed files with 74 additions and 5 deletions

View File

@@ -194,8 +194,14 @@ fact bounded by both the probe and the board/device.
apply, then re-scans. Within-cap / unset just report the cap. Effective
`tck = min(request, device max)`; probe-max (`min(..., probe max)`) is
still TODO.
- **C** — generalise the other link settings (reset/RTCK) and wire the
`prog` method tag into backend dispatch (ties into the SVF player).
- **C (done)** — `prog` method tag (`proxy_spi`/`svf`/`none`) on each
registry entry, inferred when omitted (proxy → `proxy_spi`; Microsemi/
Lattice → `svf`); shown by `fpga_info`/`fpga_list` and available via
`fpga_prog_method_name()` for backend dispatch. RTCK generalised as a
neutral `JTAG_RTCK` (mirrored to `PROBE_FTDI_JTAG_ENABLE_RTCK`,
FTDI-only). Reset abstraction deferred — it's a bundle of probe-
specific pin/polarity/timing vars with no clean neutral form yet. The
actual `program` dispatch command lands with the SVF player.
What exists already: the **probe layer** (`probes.yaml`) and the
**device layer** (`fpga_registry.yaml`). The new work is the **JTAG-link

View File

@@ -22,6 +22,9 @@
# max_tck_khz max safe JTAG TCK in kHz for this part/board; if the
# requested clock exceeds it, jtag_autoinit clamps and
# re-opens at the cap (omit / 0 = unspecified)
# prog programming backend: proxy_spi | svf | none. Omit to
# infer (proxy_bitstream -> proxy_spi; Microsemi/Lattice
# -> svf; else none).
fpgas:
# Xilinx Kintex UltraScale+ XCKU15P
@@ -39,6 +42,7 @@ fpgas:
ir_jshutdown: 0x0D
ir_isc_disable: 0x16
caveats: cclk_via_startup
prog: proxy_spi
# proxy_bitstream not yet built for this part (see doc/tutorial.md, Phase 2.5)
# Xilinx Kintex UltraScale XCKU040 (KCU105 eval board)
@@ -57,6 +61,7 @@ fpgas:
ir_isc_disable: 0x16
proxy_bitstream: bscan_spi_xcku040.bit
caveats: cclk_via_startup
prog: proxy_spi
# Microsemi IGLOO2 M2GL010T (M2GL-EVAL-KIT)
# IDCODE / IR length from bsdl_files/m2gl010t-fg484.bsd
@@ -72,3 +77,4 @@ fpgas:
family: microsemi_igloo2
bsdl: m2gl010t-fg484.bsd
ir_length: 8
prog: svf

View File

@@ -53,6 +53,29 @@ static fpga_family parse_family(const char *s)
return FPGA_FAMILY_UNKNOWN;
}
static fpga_prog_method parse_prog(const char *s)
{
if (!s) return FPGA_PROG_NONE;
if (!strcmp(s, "proxy_spi")) return FPGA_PROG_PROXY_SPI;
if (!strcmp(s, "svf")) return FPGA_PROG_SVF;
if (!strcmp(s, "none")) return FPGA_PROG_NONE;
fprintf(stderr, "fpga: unknown prog method '%s'\n", s);
return FPGA_PROG_NONE;
}
/* Guess the programming method when the entry doesn't state one. */
static fpga_prog_method infer_prog(const fpga_target *t)
{
if (t->proxy_bitstream) return FPGA_PROG_PROXY_SPI;
switch (t->family) {
case FPGA_FAMILY_MICROSEMI_IGLOO2:
case FPGA_FAMILY_MICROSEMI_SMARTFUSION2:
case FPGA_FAMILY_LATTICE_MACHXO2:
case FPGA_FAMILY_LATTICE_MACHXO3: return FPGA_PROG_SVF;
default: return FPGA_PROG_NONE;
}
}
static unsigned int parse_caveats(const char *s)
{
unsigned int flags = 0;
@@ -90,6 +113,7 @@ static void set_field(fpga_target *t, const char *key, const char *val)
else if (!strcmp(key, "proxy_bitstream")) { free((void *)t->proxy_bitstream); t->proxy_bitstream = xstrdup(val); }
else if (!strcmp(key, "caveats")) t->caveats = parse_caveats(val);
else if (!strcmp(key, "max_tck_khz")) t->max_tck_khz = (int)strtol(val, NULL, 0);
else if (!strcmp(key, "prog")) t->prog = parse_prog(val);
else fprintf(stderr, "fpga: unknown key '%s'\n", key);
}
@@ -102,7 +126,10 @@ static int commit_entry(const fpga_target *cur)
return -1;
}
g_registry = tmp;
g_registry[g_count++] = *cur;
g_registry[g_count] = *cur;
if (g_registry[g_count].prog == FPGA_PROG_NONE)
g_registry[g_count].prog = infer_prog(&g_registry[g_count]);
g_count++;
return 0;
}
@@ -267,6 +294,16 @@ const char *fpga_family_name(fpga_family f)
}
}
const char *fpga_prog_method_name(fpga_prog_method m)
{
switch (m) {
case FPGA_PROG_PROXY_SPI: return "proxy_spi";
case FPGA_PROG_SVF: return "svf";
case FPGA_PROG_NONE:
default: return "none";
}
}
const char *fpga_registry_source(void)
{
ensure_loaded();

View File

@@ -30,6 +30,13 @@ typedef enum {
FPGA_FAMILY_LATTICE_MACHXO3,
} fpga_family;
/* Programming method — which backend drives this part. */
typedef enum {
FPGA_PROG_NONE = 0, /* no known method */
FPGA_PROG_PROXY_SPI, /* Xilinx external SPI flash via the BSCAN proxy */
FPGA_PROG_SVF, /* play a vendor-exported SVF (Lattice/Microsemi/…) */
} fpga_prog_method;
/* Caveat flags: known hardware gotchas for a part. */
#define FPGA_CAVEAT_CCLK_VIA_STARTUP (1u << 0) /* CCLK not directly drivable in EXTEST */
@@ -53,6 +60,7 @@ typedef struct {
const char *proxy_bitstream; /* path under bscan_proxies/, NULL if not yet available */
unsigned int caveats; /* FPGA_CAVEAT_* flags */
int max_tck_khz; /* max safe JTAG TCK for this part/board, 0 = unspecified */
fpga_prog_method prog; /* programming backend; inferred when omitted */
} fpga_target;
/* Registry access. The YAML file is loaded lazily on first call to any
@@ -61,6 +69,7 @@ int fpga_get_target_count(void);
const fpga_target * fpga_get_target_by_index(int index);
const fpga_target * fpga_lookup_by_idcode(unsigned long idcode);
const char * fpga_family_name(fpga_family f);
const char * fpga_prog_method_name(fpga_prog_method m);
/* Path the registry was loaded from, or NULL if nothing loaded
* (file missing / parse error). For diagnostics. */

View File

@@ -1960,6 +1960,15 @@ static int cmd_open_probe(script_ctx *ctx, char *line)
}
}
// Driver-neutral adaptive clock (JTAG_RTCK, 0/1): mirror to the FTDI
// driver's variable when explicitly set (0 is meaningful, so test
// presence rather than value). FTDI-only; no Digilent equivalent.
{
char rtck[32];
if (getEnvVarDat((envvar_entry *)ctx->env, "JTAG_RTCK", rtck, sizeof(rtck)))
setEnvVarDat((envvar_entry *)ctx->env, "PROBE_FTDI_JTAG_ENABLE_RTCK", rtck);
}
ret = jtagcore_select_and_open_probe(jc, id);
if (ret != JTAG_CORE_NO_ERROR)
{
@@ -2994,10 +3003,10 @@ static int cmd_fpga_list(script_ctx *ctx, char *line)
i, t->idcode, t->idcode_mask,
t->name, fpga_family_name(t->family));
ctx->script_printf(ctx, MSG_NONE,
" bsdl=%s ir=%d proxy=%s caveats=0x%x maxtck=%dkHz\n",
" bsdl=%s ir=%d proxy=%s caveats=0x%x maxtck=%dkHz prog=%s\n",
t->bsdl_filename, t->ir_length,
t->proxy_bitstream ? t->proxy_bitstream : "(none yet)",
t->caveats, t->max_tck_khz);
t->caveats, t->max_tck_khz, fpga_prog_method_name(t->prog));
}
return JTAG_CORE_NO_ERROR;
}
@@ -3030,6 +3039,8 @@ static int cmd_fpga_info(script_ctx *ctx, char *line)
ctx->script_printf(ctx, MSG_INFO_0,
"Device %d IDCODE 0x%.8lX -> %s [%s]\n",
i, idcode, t->name, fpga_family_name(t->family));
ctx->script_printf(ctx, MSG_NONE,
" prog: %s\n", fpga_prog_method_name(t->prog));
if (t->caveats & FPGA_CAVEAT_CCLK_VIA_STARTUP) {
ctx->script_printf(ctx, MSG_NONE,
" caveat: CCLK routed via STARTUP primitive (not drivable in EXTEST)\n");