- 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>
312 lines
11 KiB
C
312 lines
11 KiB
C
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <yaml.h>
|
|
|
|
#include "fpga.h"
|
|
|
|
/*
|
|
* FPGA registry loaded at runtime from a YAML file (no longer a
|
|
* compile-time array). Lookup order for the file:
|
|
* 1. $BS_FPGA_REGISTRY (if set and non-empty)
|
|
* 2. "fpga_registry.yaml" relative to the current directory
|
|
* — the same CWD-relative convention as bsdl_files/ and bscan_proxies/,
|
|
* so run bs_explorer from the repository root.
|
|
*
|
|
* Loaded lazily on first access and cached for the process lifetime.
|
|
* The schema is one flat mapping per entry; see fpga_registry.yaml.
|
|
*/
|
|
|
|
#define DEFAULT_REGISTRY_FILE "fpga_registry.yaml"
|
|
|
|
static fpga_target *g_registry = NULL;
|
|
static int g_count = 0;
|
|
static int g_loaded = 0; /* load attempted (success or not) */
|
|
static char g_source[1024] = "";
|
|
|
|
/* ---- small helpers ----------------------------------------------- */
|
|
|
|
static char *xstrdup(const char *s)
|
|
{
|
|
char *p;
|
|
size_t n;
|
|
if (!s) return NULL;
|
|
n = strlen(s) + 1;
|
|
p = (char *)malloc(n);
|
|
if (p) memcpy(p, s, n);
|
|
return p;
|
|
}
|
|
|
|
static fpga_family parse_family(const char *s)
|
|
{
|
|
if (!s) return FPGA_FAMILY_UNKNOWN;
|
|
if (!strcmp(s, "xilinx_7")) return FPGA_FAMILY_XILINX_7;
|
|
if (!strcmp(s, "xilinx_us")) return FPGA_FAMILY_XILINX_US;
|
|
if (!strcmp(s, "xilinx_usp")) return FPGA_FAMILY_XILINX_USP;
|
|
if (!strcmp(s, "microsemi_igloo2")) return FPGA_FAMILY_MICROSEMI_IGLOO2;
|
|
if (!strcmp(s, "microsemi_smartfusion2")) return FPGA_FAMILY_MICROSEMI_SMARTFUSION2;
|
|
if (!strcmp(s, "lattice_machxo2")) return FPGA_FAMILY_LATTICE_MACHXO2;
|
|
if (!strcmp(s, "lattice_machxo3")) return FPGA_FAMILY_LATTICE_MACHXO3;
|
|
fprintf(stderr, "fpga: unknown family '%s'\n", 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;
|
|
char buf[256];
|
|
char *tok;
|
|
|
|
if (!s) return 0;
|
|
strncpy(buf, s, sizeof(buf) - 1);
|
|
buf[sizeof(buf) - 1] = '\0';
|
|
|
|
for (tok = strtok(buf, " ,|"); tok; tok = strtok(NULL, " ,|")) {
|
|
if (!strcmp(tok, "cclk_via_startup"))
|
|
flags |= FPGA_CAVEAT_CCLK_VIA_STARTUP;
|
|
else
|
|
fprintf(stderr, "fpga: unknown caveat '%s'\n", tok);
|
|
}
|
|
return flags;
|
|
}
|
|
|
|
/* Apply one "key: value" pair (both scalars) to the entry being built. */
|
|
static void set_field(fpga_target *t, const char *key, const char *val)
|
|
{
|
|
if (!strcmp(key, "name")) { free((void *)t->name); t->name = xstrdup(val); }
|
|
else if (!strcmp(key, "idcode")) t->idcode = strtoul(val, NULL, 0);
|
|
else if (!strcmp(key, "idcode_mask")) t->idcode_mask = strtoul(val, NULL, 0);
|
|
else if (!strcmp(key, "family")) t->family = parse_family(val);
|
|
else if (!strcmp(key, "bsdl")) { free((void *)t->bsdl_filename); t->bsdl_filename = xstrdup(val); }
|
|
else if (!strcmp(key, "ir_length")) t->ir_length = (int)strtol(val, NULL, 0);
|
|
else if (!strcmp(key, "ir_cfg_in")) t->ir_cfg_in = (unsigned int)strtoul(val, NULL, 0);
|
|
else if (!strcmp(key, "ir_user1")) t->ir_user1 = (unsigned int)strtoul(val, NULL, 0);
|
|
else if (!strcmp(key, "ir_jprogram")) t->ir_jprogram = (unsigned int)strtoul(val, NULL, 0);
|
|
else if (!strcmp(key, "ir_jstart")) t->ir_jstart = (unsigned int)strtoul(val, NULL, 0);
|
|
else if (!strcmp(key, "ir_jshutdown")) t->ir_jshutdown = (unsigned int)strtoul(val, NULL, 0);
|
|
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 if (!strcmp(key, "prog")) t->prog = parse_prog(val);
|
|
else fprintf(stderr, "fpga: unknown key '%s'\n", key);
|
|
}
|
|
|
|
static int commit_entry(const fpga_target *cur)
|
|
{
|
|
fpga_target *tmp = (fpga_target *)realloc(g_registry,
|
|
(g_count + 1) * sizeof(*g_registry));
|
|
if (!tmp) {
|
|
fprintf(stderr, "fpga: out of memory loading registry\n");
|
|
return -1;
|
|
}
|
|
g_registry = tmp;
|
|
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;
|
|
}
|
|
|
|
/* Walk the YAML event stream. The document is a mapping with a single
|
|
* "fpgas" key holding a sequence of flat (scalar-only) mappings. */
|
|
static int load_registry(const char *path)
|
|
{
|
|
FILE *f;
|
|
yaml_parser_t parser;
|
|
yaml_event_t ev;
|
|
int done = 0;
|
|
int rc = 0;
|
|
|
|
int in_fpgas = 0; /* inside the fpgas: sequence */
|
|
int in_item = 0; /* inside one entry mapping */
|
|
int expect_fpgas_seq = 0; /* last top-level key was "fpgas" */
|
|
char *pending_key = NULL;
|
|
fpga_target cur;
|
|
|
|
f = fopen(path, "rb");
|
|
if (!f) return -1;
|
|
|
|
if (!yaml_parser_initialize(&parser)) { fclose(f); return -1; }
|
|
yaml_parser_set_input_file(&parser, f);
|
|
|
|
while (!done) {
|
|
if (!yaml_parser_parse(&parser, &ev)) {
|
|
fprintf(stderr, "fpga: YAML parse error in %s: %s (line %lu)\n",
|
|
path, parser.problem ? parser.problem : "?",
|
|
(unsigned long)parser.problem_mark.line + 1);
|
|
rc = -1;
|
|
break;
|
|
}
|
|
|
|
switch (ev.type) {
|
|
case YAML_SCALAR_EVENT: {
|
|
const char *v = (const char *)ev.data.scalar.value;
|
|
if (!in_fpgas) {
|
|
if (!strcmp(v, "fpgas"))
|
|
expect_fpgas_seq = 1;
|
|
} else if (in_item) {
|
|
if (!pending_key) {
|
|
pending_key = xstrdup(v);
|
|
} else {
|
|
set_field(&cur, pending_key, v);
|
|
free(pending_key);
|
|
pending_key = NULL;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case YAML_SEQUENCE_START_EVENT:
|
|
if (expect_fpgas_seq && !in_fpgas) {
|
|
in_fpgas = 1;
|
|
expect_fpgas_seq = 0;
|
|
}
|
|
break;
|
|
case YAML_SEQUENCE_END_EVENT:
|
|
if (in_fpgas && !in_item)
|
|
in_fpgas = 0; /* end of the fpgas list */
|
|
break;
|
|
case YAML_MAPPING_START_EVENT:
|
|
if (in_fpgas && !in_item) {
|
|
memset(&cur, 0, sizeof(cur));
|
|
cur.idcode_mask = 0xFFFFFFFFUL; /* safe default: exact match */
|
|
in_item = 1;
|
|
}
|
|
break;
|
|
case YAML_MAPPING_END_EVENT:
|
|
if (in_item) {
|
|
in_item = 0;
|
|
free(pending_key);
|
|
pending_key = NULL;
|
|
if (commit_entry(&cur) != 0) {
|
|
rc = -1;
|
|
yaml_event_delete(&ev);
|
|
done = 1;
|
|
continue;
|
|
}
|
|
}
|
|
break;
|
|
case YAML_STREAM_END_EVENT:
|
|
done = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
yaml_event_delete(&ev);
|
|
}
|
|
|
|
free(pending_key);
|
|
yaml_parser_delete(&parser);
|
|
fclose(f);
|
|
return rc;
|
|
}
|
|
|
|
static void ensure_loaded(void)
|
|
{
|
|
const char *path;
|
|
|
|
if (g_loaded) return;
|
|
g_loaded = 1;
|
|
|
|
path = getenv("BS_FPGA_REGISTRY");
|
|
if (!path || !*path) path = DEFAULT_REGISTRY_FILE;
|
|
|
|
load_registry(path); /* fills g_registry/g_count; warns on its own errors */
|
|
|
|
if (g_count > 0) {
|
|
strncpy(g_source, path, sizeof(g_source) - 1);
|
|
g_source[sizeof(g_source) - 1] = '\0';
|
|
} else {
|
|
fprintf(stderr,
|
|
"fpga: no targets loaded from '%s' "
|
|
"(set BS_FPGA_REGISTRY, or run from the directory containing it)\n",
|
|
path);
|
|
}
|
|
}
|
|
|
|
/* ---- public API -------------------------------------------------- */
|
|
|
|
int fpga_get_target_count(void)
|
|
{
|
|
ensure_loaded();
|
|
return g_count;
|
|
}
|
|
|
|
const fpga_target *fpga_get_target_by_index(int index)
|
|
{
|
|
ensure_loaded();
|
|
if (index < 0 || index >= g_count) {
|
|
return NULL;
|
|
}
|
|
return &g_registry[index];
|
|
}
|
|
|
|
const fpga_target *fpga_lookup_by_idcode(unsigned long idcode)
|
|
{
|
|
int i;
|
|
ensure_loaded();
|
|
for (i = 0; i < g_count; i++) {
|
|
const fpga_target *t = &g_registry[i];
|
|
if ((idcode & t->idcode_mask) == (t->idcode & t->idcode_mask)) {
|
|
return t;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
const char *fpga_family_name(fpga_family f)
|
|
{
|
|
switch (f) {
|
|
case FPGA_FAMILY_XILINX_7: return "Xilinx 7-Series";
|
|
case FPGA_FAMILY_XILINX_US: return "Xilinx UltraScale";
|
|
case FPGA_FAMILY_XILINX_USP: return "Xilinx UltraScale+";
|
|
case FPGA_FAMILY_MICROSEMI_IGLOO2: return "Microsemi IGLOO2";
|
|
case FPGA_FAMILY_MICROSEMI_SMARTFUSION2: return "Microsemi SmartFusion2";
|
|
case FPGA_FAMILY_LATTICE_MACHXO2: return "Lattice MachXO2";
|
|
case FPGA_FAMILY_LATTICE_MACHXO3: return "Lattice MachXO3";
|
|
case FPGA_FAMILY_UNKNOWN:
|
|
default: return "Unknown";
|
|
}
|
|
}
|
|
|
|
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();
|
|
return g_source[0] ? g_source : NULL;
|
|
}
|