jtag: cap the clock by device max_tck_khz at autoinit (phase B)

- fpga_target gains max_tck_khz (registry key), the max safe JTAG TCK
  for a part/board (0 = unspecified)
- jtag_autoinit, after identifying the chain, resolves the clock: if the
  requested JTAG_TCK_FREQ_KHZ exceeds the smallest device max, it clamps
  it and re-opens the probe once (stored probe id) to apply, then
  re-scans; within-cap / unset just report the cap
- autoinit body extracted into autoinit_run() so it can re-run after the
  re-tune; fpga_list shows maxtck

Validated on the IGLOO2/FlashPro (req 500 -> clamp 200 -> reopen -> still
detected; within-cap and unset paths don't reopen).

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

View File

@@ -188,7 +188,12 @@ fact bounded by both the probe and the board/device.
read directly by our Digilent driver; unset → each driver's own default
(FTDI 1000, Digilent 4000). Set it via `set`, a `probes.yaml` profile,
or `defaults:`. (FTDI path hardware-validated; Digilent path untested.)
- **B** — device `max_tck_khz` + resolution after `jtag_autoinit`.
- **B (done)** — registry `max_tck_khz` caps the clock: `jtag_autoinit`,
after identifying the chain, clamps `JTAG_TCK_FREQ_KHZ` to the smallest
device max and re-opens the probe once (via the stored probe id) to
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).

View File

@@ -19,6 +19,9 @@
# (omit if none is available yet)
# caveats space/comma-separated flags: cclk_via_startup
# (omit if none)
# 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)
fpgas:
# Xilinx Kintex UltraScale+ XCKU15P

View File

@@ -89,6 +89,7 @@ static void set_field(fpga_target *t, const char *key, const char *val)
else if (!strcmp(key, "ir_isc_disable")) t->ir_isc_disable = (unsigned int)strtoul(val, NULL, 0);
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 fprintf(stderr, "fpga: unknown key '%s'\n", key);
}

View File

@@ -52,6 +52,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_target;
/* Registry access. The YAML file is loaded lazily on first call to any

View File

@@ -1474,7 +1474,13 @@ const char *cmd_autoinit_help[] = {
"explanations line two",
""
};
static int cmd_autoinit(script_ctx *ctx, char *line)
/* Id of the currently open probe (drv<<8|probe), -1 if none. Lets
* jtag_autoinit re-open the same probe to apply a clock re-tune. */
static int g_probe_id = -1;
/* Scan the chain and auto-load matching BSDLs. Returns the number of
* BSDLs loaded (>= 0), or a negative JTAG_CORE_* error. */
static int autoinit_run(script_ctx *ctx)
{
jtag_core *jc;
int number_of_devices, dev_nb;
@@ -1588,9 +1594,86 @@ static int cmd_autoinit(script_ctx *ctx, char *line)
return JTAG_CORE_ACCESS_ERROR;
}
return loaded_bsdl;
}
/* Smallest device-declared max TCK (kHz) over the matched chain devices,
* or 0 if none of them declare one. */
static int chain_max_tck_khz(jtag_core *jc)
{
int i, n, cap = 0;
const fpga_target *t;
n = jtagcore_get_number_of_devices(jc);
for (i = 0; i < n; i++) {
t = fpga_lookup_by_idcode(jtagcore_get_dev_id(jc, i));
if (t && t->max_tck_khz > 0 && (cap == 0 || t->max_tck_khz < cap))
cap = t->max_tck_khz;
}
return cap;
}
static int cmd_autoinit(script_ctx *ctx, char *line)
{
jtag_core *jc;
int loaded, cap, req;
(void)line;
jc = (jtag_core *)ctx->app_ctx;
loaded = autoinit_run(ctx);
if (loaded < 0)
return loaded;
// Phase B: resolve the JTAG clock against the devices now identified.
// The registry can cap the TCK for a part/board; if the requested
// clock exceeds that, clamp it and re-open the probe so it takes
// effect (the chain is then re-scanned at the safe clock).
cap = chain_max_tck_khz(jc);
req = jtagcore_getEnvVarValue(jc, "JTAG_TCK_FREQ_KHZ");
if (cap > 0)
{
if (req > 0 && req > cap)
{
char buf[24];
snprintf(buf, sizeof(buf), "%d", cap);
ctx->script_printf(ctx, MSG_WARNING,
"JTAG clock %d kHz exceeds the device max %d kHz; clamping.\n", req, cap);
setEnvVarDat((envvar_entry *)ctx->env, "JTAG_TCK_FREQ_KHZ", buf);
setEnvVarDat((envvar_entry *)ctx->env, "PROBE_FTDI_TCK_FREQ_KHZ", buf);
if (g_probe_id >= 0)
{
ctx->script_printf(ctx, MSG_INFO_0,
"Re-opening at %d kHz and re-scanning.\n", cap);
if (jtagcore_select_and_open_probe(jc, g_probe_id) == JTAG_CORE_NO_ERROR)
{
loaded = autoinit_run(ctx);
if (loaded < 0)
return loaded;
}
else
{
ctx->script_printf(ctx, MSG_ERROR,
"Re-open failed; the clock will apply on the next jtag_open.\n");
}
}
else
{
ctx->script_printf(ctx, MSG_WARNING,
"Reopen the probe (jtag_close / jtag_open) to apply the clamped clock.\n");
}
}
else
{
ctx->script_printf(ctx, MSG_INFO_0,
"Device max JTAG clock: %d kHz%s.\n", cap,
(req > 0) ? "" : " (set JTAG_TCK_FREQ_KHZ to control the clock)");
}
}
// Expose the count to scripts via last_data_value, but report success
// (a non-zero return is treated as an error code by the engine).
ctx->last_data_value = loaded_bsdl;
ctx->last_data_value = loaded;
return JTAG_CORE_NO_ERROR;
}
@@ -1885,6 +1968,7 @@ static int cmd_open_probe(script_ctx *ctx, char *line)
}
else
{
g_probe_id = id;
ctx->script_printf(ctx, MSG_INFO_0, "Probe Ok !\n");
return JTAG_CORE_NO_ERROR;
}
@@ -1921,6 +2005,7 @@ static int cmd_close_probe(script_ctx *ctx, char *line)
// chain looks probe-less until the next jtag_open.
jc->io_functions.drv_DeInit(jc);
memset(&jc->io_functions, 0, sizeof(jc->io_functions));
g_probe_id = -1;
ctx->script_printf(ctx, MSG_INFO_0, "Probe released.\n");
}
else
@@ -2909,10 +2994,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\n",
" bsdl=%s ir=%d proxy=%s caveats=0x%x maxtck=%dkHz\n",
t->bsdl_filename, t->ir_length,
t->proxy_bitstream ? t->proxy_bitstream : "(none yet)",
t->caveats);
t->caveats, t->max_tck_khz);
}
return JTAG_CORE_NO_ERROR;
}