fpga: load registry from yaml at runtime, not compile-time

- registry moves from the array in fpga.c to fpga_registry.yaml at the
  repo root, parsed via libyaml (pkg-config yaml-0.1); adding a part is
  now a YAML edit, no rebuild
- looked up CWD-relative (like bsdl_files/), overridable with
  $BS_FPGA_REGISTRY, loaded lazily once; public API unchanged
- fpga_list shows the source file (fpga_registry_source())
- add microsemi_igloo2/smartfusion2 and lattice_machxo2/3 families,
  ready for the non-Xilinx targets
- docs updated: CLAUDE.md, README, tutorial "add a target" walkthrough

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 10:19:53 +02:00
parent 27836d63bb
commit 3579c5efb0
8 changed files with 409 additions and 118 deletions

View File

@@ -89,8 +89,14 @@ derived from the BSDL alone:
- `proxy_bitstream_path` — path to the BSCAN proxy `.bit` for this part
- `caveats` — flags for known hardware gotchas (e.g. CCLK via STARTUPE3)
Registry is a compile-time array. Adding a part = one entry + its
`.bsd` in `bsdl_files/` + its proxy `.bit` in `bscan_proxies/`.
Registry is a **runtime YAML file** (`fpga_registry.yaml` at the repo
root), parsed via libyaml — no longer a compile-time array. It is looked
up CWD-relative (like `bsdl_files/`), overridable with
`$BS_FPGA_REGISTRY`, and loaded lazily on first access. Adding a part =
one YAML entry + its `.bsd` in `bsdl_files/` + (optionally) its proxy
`.bit` in `bscan_proxies/` — no rebuild. The `family` field accepts
`xilinx_*`, `microsemi_igloo2/smartfusion2`, `lattice_machxo2/3`
(enum in `fpga.h`).
### Digilent SMT2 modules need libdjtg, not raw MPSSE
@@ -249,6 +255,11 @@ mkdir build && cd build && cmake .. && make
./bs/bs # interactive REPL
```
Build needs **libyaml** (pkg-config `yaml-0.1`; Arch `libyaml`, Debian
`libyaml-dev`) — the FPGA registry is parsed from `fpga_registry.yaml`
at runtime. Run `bs` from the repo root so it finds that file (and
`bsdl_files/`, `bscan_proxies/`), or point `$BS_FPGA_REGISTRY` at it.
The Digilent SMT2 backend is built by default on UNIX (disable with
`-DBS_ENABLE_DIGILENT=OFF`). To actually use such a probe, install the
Adept Runtime system-wide (provides `libdjtg.so` + `libdmgr.so`).

View File

@@ -28,6 +28,8 @@ target).
- CMake ≥ 3.10, gcc/clang
- `readline` (Arch: `readline`, Debian/Ubuntu: `libreadline-dev`)
- `libyaml` for the FPGA registry, found via pkg-config `yaml-0.1`
(Arch: `libyaml`, Debian/Ubuntu: `libyaml-dev`)
- `libftd2xx` for FTDI probes (vendored in `libs/libftd2xx/`)
- *To drive a Digilent SMT2/SMT2-NC probe:* the Digilent
[Adept Runtime](https://digilent.com/shop/software/digilent-adept/)
@@ -149,13 +151,14 @@ modules/
├── bsdl_parser/ .bsd loader
├── bus_over_jtag/ SPI / I²C / MDIO / parallel mem bit-bang (EXTEST)
├── drivers/ FTDI, J-Link, Linux GPIO, LPT, Digilent (optional)
├── fpga/ Per-target registry (IDCODE, BSDL, IR opcodes, caveats)
├── fpga/ Registry loader (parses fpga_registry.yaml at runtime)
├── bscan_spi/ BSCAN proxy loader + fast SPI-over-USER1 bridge
├── spi_flash/ SPI NOR chip database + read/erase/program/verify
├── script/ Script engine
├── config/ Built-in config.script
├── os_interface/ Portable fs/network wrappers
└── natsort/ Natural-order pin-name sorting
fpga_registry.yaml FPGA registry (IDCODE → BSDL, IR opcodes, proxy, caveats)
bsdl_files/ BSDL files for target FPGAs
bscan_proxies/ BSCAN proxy bitstreams (MIT, from quartiq)
scripts/ Example scripts

View File

@@ -12,7 +12,7 @@ the IDCODE and BSDL filename change.
`libs/libftd2xx/`).
- The target's BSDL in `bsdl_files/` (KU15P: `xcku15p_ffve1517.bsd` is
bundled).
- An entry for the target in `modules/fpga/fpga.c` (KU15P is bundled).
- An entry for the target in `fpga_registry.yaml` (KU15P is bundled).
See [Adding a new FPGA](#6-add-a-new-fpga-target) below.
- For SPI flashing, eventually: a BSCAN proxy bitstream — see the
[Phase 2.5 caveat](#phase-25-spi-through-the-bscan-proxy-bridge-bitstream) at the end.
@@ -92,7 +92,7 @@ mis-wired. Power-cycle and re-check the harness before going further.
## 3. Identify the FPGA against the registry
`fpga_info` walks the chain and matches each IDCODE against the
compile-time registry in `modules/fpga/fpga.c`:
registry in `fpga_registry.yaml`:
```
bs_explorer> fpga_info
@@ -162,16 +162,18 @@ way; you'd be there for weeks.
## 6. Add a new FPGA target
The registry in `modules/fpga/fpga.c` holds the per-part facts that
can't be derived from the BSDL alone (or are tedious to). The XCKU040
entry already there was added exactly with the steps below — use it as
your template.
The registry `fpga_registry.yaml` at the repo root — holds the
per-part facts that can't be derived from the BSDL alone (or are tedious
to). It's parsed at runtime, so adding a part is **editing YAML, no
rebuild**. The XCKU040 entry already there was added exactly with the
steps below — use it as your template.
### a. Drop the BSDL
Put the part's `.bsd` in `bsdl_files/`. Source: Xilinx/AMD device page
under "Design Files / BSDL", Intel in the Quartus install, Lattice per
part. `jtag_autoinit` will then auto-load it by IDCODE.
part, Microsemi/Microchip via Libero. `jtag_autoinit` will then
auto-load it by IDCODE.
### b. Pull the facts out of the BSDL
@@ -189,75 +191,84 @@ For the XCKU040 this yields IR length 6, and the private opcodes
**revision** and read as `X` (don't-care), which is why the registry
masks them off.
### c. Fill in an `fpga_target`
### c. Add a YAML entry
| Field | What it is | XCKU040 |
|-------|-----------|---------|
| `name` | human-readable label | "Xilinx Kintex UltraScale XCKU040" |
Append a list item under `fpgas:` in `fpga_registry.yaml`:
| Key | What it is | XCKU040 |
|-----|-----------|---------|
| `name` | human-readable label (quoted) | "Xilinx Kintex UltraScale XCKU040" |
| `idcode` | IDCODE pattern (version nibble as 0) | `0x03822093` |
| `idcode_mask` | bits that must match; `0x0FFFFFFF` ignores the Xilinx revision nibble | `0x0FFFFFFF` |
| `family` | `FPGA_FAMILY_XILINX_7/US/USP` | `FPGA_FAMILY_XILINX_US` |
| `bsdl_filename` | basename in `bsdl_files/` | `"xcku040_ffva1156.bsd"` |
| `idcode_mask` | bits that must match; `0x0FFFFFFF` ignores the Xilinx revision nibble (default `0xFFFFFFFF`) | `0x0FFFFFFF` |
| `family` | `xilinx_7/us/usp`, `microsemi_igloo2/smartfusion2`, `lattice_machxo2/3` | `xilinx_us` |
| `bsdl` | basename in `bsdl_files/` | `xcku040_ffva1156.bsd` |
| `ir_length` | IR width in bits | `6` |
| `ir_cfg_in` / `ir_user1` / `ir_jprogram` / `ir_jstart` / `ir_jshutdown` / `ir_isc_disable` | private IR opcodes (0 = N/A) | from the BSDL |
| `proxy_bitstream` | BSCAN proxy `.bit` in `bscan_proxies/`, or `NULL` | `"bscan_spi_xcku040.bit"` |
| `caveats` | bit-flags for hardware gotchas (see below) | `FPGA_CAVEAT_CCLK_VIA_STARTUP` |
| `ir_cfg_in` / `ir_user1` / `ir_jprogram` / `ir_jstart` / `ir_jshutdown` / `ir_isc_disable` | private IR opcodes (omit = 0/N/A) | from the BSDL |
| `proxy_bitstream` | BSCAN proxy `.bit` in `bscan_proxies/` (omit if none) | `bscan_spi_xcku040.bit` |
| `caveats` | space/comma-separated flag names (omit if none) | `cclk_via_startup` |
The resulting entry (verbatim from `fpga.c`):
The resulting entry (verbatim from `fpga_registry.yaml`):
```c
{
.name = "Xilinx Kintex UltraScale XCKU040",
.idcode = 0x03822093,
.idcode_mask = 0x0FFFFFFF,
.family = FPGA_FAMILY_XILINX_US,
.bsdl_filename = "xcku040_ffva1156.bsd",
.ir_length = 6,
.ir_cfg_in = 0x05,
.ir_user1 = 0x02,
.ir_jprogram = 0x0B,
.ir_jstart = 0x0C,
.ir_jshutdown = 0x0D,
.ir_isc_disable = 0x16,
.proxy_bitstream = "bscan_spi_xcku040.bit",
.caveats = FPGA_CAVEAT_CCLK_VIA_STARTUP,
},
```yaml
- name: "Xilinx Kintex UltraScale XCKU040"
idcode: 0x03822093
idcode_mask: 0x0FFFFFFF
family: xilinx_us
bsdl: xcku040_ffva1156.bsd
ir_length: 6
ir_cfg_in: 0x05
ir_user1: 0x02
ir_jprogram: 0x0B
ir_jstart: 0x0C
ir_jshutdown: 0x0D
ir_isc_disable: 0x16
proxy_bitstream: bscan_spi_xcku040.bit
caveats: cclk_via_startup
```
Omit any field that doesn't apply: a missing `proxy_bitstream` means
"none yet", a missing `caveats` means none, a missing `idcode_mask`
defaults to exact match (`0xFFFFFFFF`).
### What `caveats` means
`caveats` is a bit-field of `FPGA_CAVEAT_*` flags (in `fpga.h`) marking
**known hardware gotchas that change how the tool must drive the
part**. It is *not* a free-text note — each flag is something the code
(or you) can branch on. Set it to `0` when the part has none you know
of. `fpga_info` prints any flag that is set, as a human-readable line.
`caveats` is a space/comma-separated list of flag names, each backed by
an `FPGA_CAVEAT_*` bit in `fpga.h`, marking **known hardware gotchas
that change how the tool must drive the part**. It is *not* a free-text
note — each flag is something the code (or you) can branch on. Omit the
field when the part has none. `fpga_info` prints any flag that is set,
as a human-readable line.
Currently one flag exists:
- `FPGA_CAVEAT_CCLK_VIA_STARTUP` — on Xilinx 7-Series / UltraScale /
UltraScale+, the SPI configuration clock **CCLK is not a normal I/O
pin**: it is routed through the `STARTUP`/`STARTUPE3` primitive and
therefore **cannot be toggled in EXTEST** boundary scan. Practical
effect: the slow EXTEST SPI bit-bang can't clock the flash on these
parts — you must use the BSCAN proxy (Phase 2.5), where CCLK is driven
by the fabric internally and the problem disappears. Set this flag for
any 7-Series/US/US+ part.
- `cclk_via_startup` (`FPGA_CAVEAT_CCLK_VIA_STARTUP`) — on Xilinx
7-Series / UltraScale / UltraScale+, the SPI configuration clock
**CCLK is not a normal I/O pin**: it is routed through the
`STARTUP`/`STARTUPE3` primitive and therefore **cannot be toggled in
EXTEST** boundary scan. Practical effect: the slow EXTEST SPI bit-bang
can't clock the flash on these parts — you must use the BSCAN proxy
(Phase 2.5), where CCLK is driven by the fabric internally and the
problem disappears. Set this for any 7-Series/US/US+ part.
To introduce a *new* caveat: add a `#define FPGA_CAVEAT_xxx (1u << n)`
in `fpga.h`, OR it into the relevant entries, and (if it should be
visible) print it in `cmd_fpga_info` in `script.c`.
in `fpga.h`, teach `parse_caveats()` in `fpga.c` its YAML name, use that
name in the YAML, and (if it should be visible) print it in
`cmd_fpga_info` in `script.c`.
### d. Rebuild and verify
### d. Verify — no rebuild
The registry is compile-time — no runtime registration:
The registry is loaded at runtime, so just (re)start bs_explorer from
the repo root and check:
```sh
cd build && make
./bs/bs
./build/bs/bs
bs_explorer> fpga_list # your part should appear, with its source file
bs_explorer> jtag_autoinit
bs_explorer> fpga_info # should show your part, family, and any caveats
bs_explorer> fpga_info # should show your part, family, and any caveats
```
(`fpga_list` reads the registry without needing a probe.)
## Phase 2.5: SPI through the BSCAN proxy (bridge bitstream)
Talking to the SPI flash via EXTEST is fine for a JEDEC ID but useless
@@ -301,8 +312,8 @@ The XCKU15P first has to be **added to the generator's device table**
Once built, drop `bscan_spi_xcku15p.bit` into `bscan_proxies/` (it's
MIT, like the KU040 — keep `bscan_proxies/LICENSE.quartiq`) and set the
`proxy_bitstream` field on the KU15P entry in `modules/fpga/fpga.c`
(currently `NULL`).
`proxy_bitstream` field on the KU15P entry in `fpga_registry.yaml`
(currently omitted).
### Load the bridge and talk SPI
@@ -362,7 +373,7 @@ of this primitive.
| `jtag_autoinit` finds 0 devices | TDI/TDO swap, TRST held low, voltage mismatch, or chain broken. |
| All IDCODEs read `0xFFFFFFFF` | TDO floats high — broken TDO link, wrong voltage reference, or a Digilent SMT2 module being driven via raw FTDI MPSSE (use the Digilent backend instead). |
| All IDCODEs read `0x00000000` | TDO tied low or no clock reaching the target. |
| `fpga_info` says "not in registry" | Add the part to `fpga_registry[]`. |
| `fpga_info` says "not in registry" | Add an entry to `fpga_registry.yaml`. |
| `bscan_shift_dr 32` doesn't return the expected IDCODE | Wrong IR opcode/length, wrong device index, or a multi-device chain (current primitives assume single device). |
| `jtag_spi_xfer` is hopelessly slow | That's expected via EXTEST — switch to BSCAN proxy (Phase 2.5). |

56
fpga_registry.yaml Normal file
View File

@@ -0,0 +1,56 @@
# bs_explorer FPGA registry
#
# Loaded at runtime by modules/fpga/. Looked up relative to the current
# directory (run bs_explorer from the repo root), or via $BS_FPGA_REGISTRY.
#
# One entry per programmable device. Fields:
# name human-readable part name (quoted)
# idcode JTAG IDCODE pattern (hex)
# idcode_mask bits compared when matching (0x0FFFFFFF on Xilinx
# masks the version nibble); default 0xFFFFFFFF
# family xilinx_7 | xilinx_us | xilinx_usp |
# microsemi_igloo2 | microsemi_smartfusion2 |
# lattice_machxo2 | lattice_machxo3
# bsdl basename of the .bsd in bsdl_files/
# ir_length IR width in bits
# ir_cfg_in / ir_user1 / ir_jprogram / ir_jstart / ir_jshutdown /
# ir_isc_disable private IR opcodes (hex; from the BSDL on Xilinx)
# proxy_bitstream basename of the proxy .bit in bscan_proxies/
# (omit if none is available yet)
# caveats space/comma-separated flags: cclk_via_startup
# (omit if none)
fpgas:
# Xilinx Kintex UltraScale+ XCKU15P
# IDCODE / opcodes from bsdl_files/xcku15p_ffve1517.bsd, IR length 6.
- name: "Xilinx Kintex UltraScale+ XCKU15P"
idcode: 0x04A56093
idcode_mask: 0x0FFFFFFF
family: xilinx_usp
bsdl: xcku15p_ffve1517.bsd
ir_length: 6
ir_cfg_in: 0x05
ir_user1: 0x02
ir_jprogram: 0x0B
ir_jstart: 0x0C
ir_jshutdown: 0x0D
ir_isc_disable: 0x16
caveats: cclk_via_startup
# proxy_bitstream not yet built for this part (see doc/tutorial.md, Phase 2.5)
# Xilinx Kintex UltraScale XCKU040 (KCU105 eval board)
# IDCODE / opcodes from bsdl_files/xcku040_ffva1156.bsd, IR length 6.
- name: "Xilinx Kintex UltraScale XCKU040"
idcode: 0x03822093
idcode_mask: 0x0FFFFFFF
family: xilinx_us
bsdl: xcku040_ffva1156.bsd
ir_length: 6
ir_cfg_in: 0x05
ir_user1: 0x02
ir_jprogram: 0x0B
ir_jstart: 0x0C
ir_jshutdown: 0x0D
ir_isc_disable: 0x16
proxy_bitstream: bscan_spi_xcku040.bit
caveats: cclk_via_startup

View File

@@ -5,3 +5,10 @@ file(GLOB_RECURSE ALL_SOURCES "*.c")
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)
add_library(fpga ${ALL_SOURCES})
# The registry is parsed from YAML at runtime via libyaml (the canonical
# C YAML parser). Found through pkg-config; PUBLIC so the executable that
# links fpga picks up the dependency transitively.
find_package(PkgConfig REQUIRED)
pkg_check_modules(YAML REQUIRED IMPORTED_TARGET yaml-0.1)
target_link_libraries(fpga PUBLIC PkgConfig::YAML)

View File

@@ -1,70 +1,249 @@
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <yaml.h>
#include "fpga.h"
static const fpga_target fpga_registry[] = {
/* Xilinx Kintex UltraScale+ XCKU15P
* IDCODE_REGISTER and INSTRUCTION_OPCODE values come from
* bsdl_files/xcku15p_ffve1517.bsd
* IR length 6 bits, version nibble (bits 31:28) ignored. */
{
.name = "Xilinx Kintex UltraScale+ XCKU15P",
.idcode = 0x04A56093,
.idcode_mask = 0x0FFFFFFF,
.family = FPGA_FAMILY_XILINX_USP,
.bsdl_filename = "xcku15p_ffve1517.bsd",
.ir_length = 6,
.ir_cfg_in = 0x05,
.ir_user1 = 0x02,
.ir_jprogram = 0x0B,
.ir_jstart = 0x0C,
.ir_jshutdown = 0x0D,
.ir_isc_disable = 0x16,
.proxy_bitstream = NULL, /* TODO Phase 2.5: bscan_spi_xcku15p.bit */
.caveats = FPGA_CAVEAT_CCLK_VIA_STARTUP,
},
/* Xilinx Kintex UltraScale XCKU040 (KCU105 eval board)
* IDCODE_REGISTER and INSTRUCTION_OPCODE values come from
* bsdl_files/xcku040_ffva1156.bsd
* IR length 6 bits, version nibble (bits 31:28) ignored. */
{
.name = "Xilinx Kintex UltraScale XCKU040",
.idcode = 0x03822093,
.idcode_mask = 0x0FFFFFFF,
.family = FPGA_FAMILY_XILINX_US,
.bsdl_filename = "xcku040_ffva1156.bsd",
.ir_length = 6,
.ir_cfg_in = 0x05,
.ir_user1 = 0x02,
.ir_jprogram = 0x0B,
.ir_jstart = 0x0C,
.ir_jshutdown = 0x0D,
.ir_isc_disable = 0x16,
.proxy_bitstream = "bscan_spi_xcku040.bit",
.caveats = FPGA_CAVEAT_CCLK_VIA_STARTUP,
},
};
/*
* 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 FPGA_REGISTRY_LEN ((int)(sizeof(fpga_registry) / sizeof(fpga_registry[0])))
#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 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 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;
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)
{
return FPGA_REGISTRY_LEN;
ensure_loaded();
return g_count;
}
const fpga_target *fpga_get_target_by_index(int index)
{
if (index < 0 || index >= FPGA_REGISTRY_LEN) {
ensure_loaded();
if (index < 0 || index >= g_count) {
return NULL;
}
return &fpga_registry[index];
return &g_registry[index];
}
const fpga_target *fpga_lookup_by_idcode(unsigned long idcode)
{
int i;
for (i = 0; i < FPGA_REGISTRY_LEN; i++) {
const fpga_target *t = &fpga_registry[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;
}
@@ -75,10 +254,20 @@ const fpga_target *fpga_lookup_by_idcode(unsigned long idcode)
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_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";
default: return "Unknown";
}
}
const char *fpga_registry_source(void)
{
ensure_loaded();
return g_source[0] ? g_source : NULL;
}

View File

@@ -11,8 +11,10 @@
* - path to the BSCAN proxy bitstream
* - per-target caveats (known hardware gotchas)
*
* Adding an FPGA = one entry in fpga_registry[] + its .bsd in
* bsdl_files/ + (optionally) its proxy .bit in bscan_proxies/.
* The registry is loaded at runtime from a YAML file (no longer a
* compile-time array). Adding an FPGA = one entry in the YAML +
* its .bsd in bsdl_files/ + (optionally) its proxy .bit in
* bscan_proxies/. See fpga_registry.yaml for the format.
*/
#include "jtag_core/jtag_core.h"
@@ -22,6 +24,10 @@ typedef enum {
FPGA_FAMILY_XILINX_7,
FPGA_FAMILY_XILINX_US,
FPGA_FAMILY_XILINX_USP,
FPGA_FAMILY_MICROSEMI_IGLOO2,
FPGA_FAMILY_MICROSEMI_SMARTFUSION2,
FPGA_FAMILY_LATTICE_MACHXO2,
FPGA_FAMILY_LATTICE_MACHXO3,
} fpga_family;
/* Caveat flags: known hardware gotchas for a part. */
@@ -48,10 +54,15 @@ typedef struct {
unsigned int caveats; /* FPGA_CAVEAT_* flags */
} fpga_target;
/* Registry access */
/* Registry access. The YAML file is loaded lazily on first call to any
* of these, and cached for the process lifetime. */
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);
/* Path the registry was loaded from, or NULL if nothing loaded
* (file missing / parse error). For diagnostics. */
const char * fpga_registry_source(void);
#endif

View File

@@ -2779,17 +2779,20 @@ static int cmd_get_pins_list(script_ctx *ctx, char *line)
const char *cmd_fpga_list_help[] = {
"",
"Lists the FPGA targets known to this build (registry in modules/fpga/).",
"Lists the FPGA targets in the registry (loaded from fpga_registry.yaml).",
""};
static int cmd_fpga_list(script_ctx *ctx, char *line)
{
int i, n;
const fpga_target *t;
const char *src;
(void)line;
n = fpga_get_target_count();
ctx->script_printf(ctx, MSG_INFO_0, "%d FPGA target(s) registered:\n", n);
src = fpga_registry_source();
ctx->script_printf(ctx, MSG_INFO_0, "%d FPGA target(s) registered (from %s):\n",
n, src ? src : "<no registry loaded>");
for (i = 0; i < n; i++) {
t = fpga_get_target_by_index(i);
ctx->script_printf(ctx, MSG_NONE,