From 3579c5efb0666382ac8b811a145ee05582841d3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Sun, 24 May 2026 10:19:53 +0200 Subject: [PATCH] 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 --- CLAUDE.md | 15 +- README.md | 5 +- doc/tutorial.md | 127 +++++++++------- fpga_registry.yaml | 56 +++++++ modules/fpga/CMakeLists.txt | 7 + modules/fpga/fpga.c | 293 +++++++++++++++++++++++++++++------- modules/fpga/fpga.h | 17 ++- modules/script/script.c | 7 +- 8 files changed, 409 insertions(+), 118 deletions(-) create mode 100644 fpga_registry.yaml diff --git a/CLAUDE.md b/CLAUDE.md index 044e981..16fe4f9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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`). diff --git a/README.md b/README.md index 0cbfc35..7e58113 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/doc/tutorial.md b/doc/tutorial.md index 70e9db1..bb9f72e 100644 --- a/doc/tutorial.md +++ b/doc/tutorial.md @@ -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). | diff --git a/fpga_registry.yaml b/fpga_registry.yaml new file mode 100644 index 0000000..e13f984 --- /dev/null +++ b/fpga_registry.yaml @@ -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 diff --git a/modules/fpga/CMakeLists.txt b/modules/fpga/CMakeLists.txt index 6264b85..21e56bf 100644 --- a/modules/fpga/CMakeLists.txt +++ b/modules/fpga/CMakeLists.txt @@ -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) diff --git a/modules/fpga/fpga.c b/modules/fpga/fpga.c index a17c189..130fc0f 100644 --- a/modules/fpga/fpga.c +++ b/modules/fpga/fpga.c @@ -1,70 +1,249 @@ #include +#include +#include +#include + +#include #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; +} diff --git a/modules/fpga/fpga.h b/modules/fpga/fpga.h index d5ec84b..f3d52f0 100644 --- a/modules/fpga/fpga.h +++ b/modules/fpga/fpga.h @@ -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 diff --git a/modules/script/script.c b/modules/script/script.c index a9fc89a..70c67dc 100644 --- a/modules/script/script.c +++ b/modules/script/script.c @@ -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 : ""); for (i = 0; i < n; i++) { t = fpga_get_target_by_index(i); ctx->script_printf(ctx, MSG_NONE,