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 - `proxy_bitstream_path` — path to the BSCAN proxy `.bit` for this part
- `caveats` — flags for known hardware gotchas (e.g. CCLK via STARTUPE3) - `caveats` — flags for known hardware gotchas (e.g. CCLK via STARTUPE3)
Registry is a compile-time array. Adding a part = one entry + its Registry is a **runtime YAML file** (`fpga_registry.yaml` at the repo
`.bsd` in `bsdl_files/` + its proxy `.bit` in `bscan_proxies/`. 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 ### Digilent SMT2 modules need libdjtg, not raw MPSSE
@@ -249,6 +255,11 @@ mkdir build && cd build && cmake .. && make
./bs/bs # interactive REPL ./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 The Digilent SMT2 backend is built by default on UNIX (disable with
`-DBS_ENABLE_DIGILENT=OFF`). To actually use such a probe, install the `-DBS_ENABLE_DIGILENT=OFF`). To actually use such a probe, install the
Adept Runtime system-wide (provides `libdjtg.so` + `libdmgr.so`). Adept Runtime system-wide (provides `libdjtg.so` + `libdmgr.so`).

View File

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

View File

@@ -12,7 +12,7 @@ the IDCODE and BSDL filename change.
`libs/libftd2xx/`). `libs/libftd2xx/`).
- The target's BSDL in `bsdl_files/` (KU15P: `xcku15p_ffve1517.bsd` is - The target's BSDL in `bsdl_files/` (KU15P: `xcku15p_ffve1517.bsd` is
bundled). 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. See [Adding a new FPGA](#6-add-a-new-fpga-target) below.
- For SPI flashing, eventually: a BSCAN proxy bitstream — see the - 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. [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 ## 3. Identify the FPGA against the registry
`fpga_info` walks the chain and matches each IDCODE against the `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 bs_explorer> fpga_info
@@ -162,16 +162,18 @@ way; you'd be there for weeks.
## 6. Add a new FPGA target ## 6. Add a new FPGA target
The registry in `modules/fpga/fpga.c` holds the per-part facts that The registry `fpga_registry.yaml` at the repo root — holds the
can't be derived from the BSDL alone (or are tedious to). The XCKU040 per-part facts that can't be derived from the BSDL alone (or are tedious
entry already there was added exactly with the steps below — use it as to). It's parsed at runtime, so adding a part is **editing YAML, no
your template. rebuild**. The XCKU040 entry already there was added exactly with the
steps below — use it as your template.
### a. Drop the BSDL ### a. Drop the BSDL
Put the part's `.bsd` in `bsdl_files/`. Source: Xilinx/AMD device page Put the part's `.bsd` in `bsdl_files/`. Source: Xilinx/AMD device page
under "Design Files / BSDL", Intel in the Quartus install, Lattice per 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 ### 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 **revision** and read as `X` (don't-care), which is why the registry
masks them off. masks them off.
### c. Fill in an `fpga_target` ### c. Add a YAML entry
| Field | What it is | XCKU040 | Append a list item under `fpgas:` in `fpga_registry.yaml`:
|-------|-----------|---------|
| `name` | human-readable label | "Xilinx Kintex UltraScale XCKU040" | | Key | What it is | XCKU040 |
|-----|-----------|---------|
| `name` | human-readable label (quoted) | "Xilinx Kintex UltraScale XCKU040" |
| `idcode` | IDCODE pattern (version nibble as 0) | `0x03822093` | | `idcode` | IDCODE pattern (version nibble as 0) | `0x03822093` |
| `idcode_mask` | bits that must match; `0x0FFFFFFF` ignores the Xilinx revision nibble | `0x0FFFFFFF` | | `idcode_mask` | bits that must match; `0x0FFFFFFF` ignores the Xilinx revision nibble (default `0xFFFFFFFF`) | `0x0FFFFFFF` |
| `family` | `FPGA_FAMILY_XILINX_7/US/USP` | `FPGA_FAMILY_XILINX_US` | | `family` | `xilinx_7/us/usp`, `microsemi_igloo2/smartfusion2`, `lattice_machxo2/3` | `xilinx_us` |
| `bsdl_filename` | basename in `bsdl_files/` | `"xcku040_ffva1156.bsd"` | | `bsdl` | basename in `bsdl_files/` | `xcku040_ffva1156.bsd` |
| `ir_length` | IR width in bits | `6` | | `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 | | `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/`, or `NULL` | `"bscan_spi_xcku040.bit"` | | `proxy_bitstream` | BSCAN proxy `.bit` in `bscan_proxies/` (omit if none) | `bscan_spi_xcku040.bit` |
| `caveats` | bit-flags for hardware gotchas (see below) | `FPGA_CAVEAT_CCLK_VIA_STARTUP` | | `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 ```yaml
{ - name: "Xilinx Kintex UltraScale XCKU040"
.name = "Xilinx Kintex UltraScale XCKU040", idcode: 0x03822093
.idcode = 0x03822093, idcode_mask: 0x0FFFFFFF
.idcode_mask = 0x0FFFFFFF, family: xilinx_us
.family = FPGA_FAMILY_XILINX_US, bsdl: xcku040_ffva1156.bsd
.bsdl_filename = "xcku040_ffva1156.bsd", ir_length: 6
.ir_length = 6, ir_cfg_in: 0x05
.ir_cfg_in = 0x05, ir_user1: 0x02
.ir_user1 = 0x02, ir_jprogram: 0x0B
.ir_jprogram = 0x0B, ir_jstart: 0x0C
.ir_jstart = 0x0C, ir_jshutdown: 0x0D
.ir_jshutdown = 0x0D, ir_isc_disable: 0x16
.ir_isc_disable = 0x16, proxy_bitstream: bscan_spi_xcku040.bit
.proxy_bitstream = "bscan_spi_xcku040.bit", caveats: cclk_via_startup
.caveats = FPGA_CAVEAT_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 ### What `caveats` means
`caveats` is a bit-field of `FPGA_CAVEAT_*` flags (in `fpga.h`) marking `caveats` is a space/comma-separated list of flag names, each backed by
**known hardware gotchas that change how the tool must drive the an `FPGA_CAVEAT_*` bit in `fpga.h`, marking **known hardware gotchas
part**. It is *not* a free-text note — each flag is something the code that change how the tool must drive the part**. It is *not* a free-text
(or you) can branch on. Set it to `0` when the part has none you know note — each flag is something the code (or you) can branch on. Omit the
of. `fpga_info` prints any flag that is set, as a human-readable line. field when the part has none. `fpga_info` prints any flag that is set,
as a human-readable line.
Currently one flag exists: Currently one flag exists:
- `FPGA_CAVEAT_CCLK_VIA_STARTUP` — on Xilinx 7-Series / UltraScale / - `cclk_via_startup` (`FPGA_CAVEAT_CCLK_VIA_STARTUP`) — on Xilinx
UltraScale+, the SPI configuration clock **CCLK is not a normal I/O 7-Series / UltraScale / UltraScale+, the SPI configuration clock
pin**: it is routed through the `STARTUP`/`STARTUPE3` primitive and **CCLK is not a normal I/O pin**: it is routed through the
therefore **cannot be toggled in EXTEST** boundary scan. Practical `STARTUP`/`STARTUPE3` primitive and therefore **cannot be toggled in
effect: the slow EXTEST SPI bit-bang can't clock the flash on these EXTEST** boundary scan. Practical effect: the slow EXTEST SPI bit-bang
parts — you must use the BSCAN proxy (Phase 2.5), where CCLK is driven can't clock the flash on these parts — you must use the BSCAN proxy
by the fabric internally and the problem disappears. Set this flag for (Phase 2.5), where CCLK is driven by the fabric internally and the
any 7-Series/US/US+ part. problem disappears. Set this for any 7-Series/US/US+ part.
To introduce a *new* caveat: add a `#define FPGA_CAVEAT_xxx (1u << n)` 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 in `fpga.h`, teach `parse_caveats()` in `fpga.c` its YAML name, use that
visible) print it in `cmd_fpga_info` in `script.c`. 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 ```sh
cd build && make ./build/bs/bs
./bs/bs bs_explorer> fpga_list # your part should appear, with its source file
bs_explorer> jtag_autoinit 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) ## 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 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 Once built, drop `bscan_spi_xcku15p.bit` into `bscan_proxies/` (it's
MIT, like the KU040 — keep `bscan_proxies/LICENSE.quartiq`) and set the MIT, like the KU040 — keep `bscan_proxies/LICENSE.quartiq`) and set the
`proxy_bitstream` field on the KU15P entry in `modules/fpga/fpga.c` `proxy_bitstream` field on the KU15P entry in `fpga_registry.yaml`
(currently `NULL`). (currently omitted).
### Load the bridge and talk SPI ### 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. | | `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 `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. | | 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). | | `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). | | `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}/..) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)
add_library(fpga ${ALL_SOURCES}) 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 <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <yaml.h>
#include "fpga.h" #include "fpga.h"
static const fpga_target fpga_registry[] = { /*
/* Xilinx Kintex UltraScale+ XCKU15P * FPGA registry loaded at runtime from a YAML file (no longer a
* IDCODE_REGISTER and INSTRUCTION_OPCODE values come from * compile-time array). Lookup order for the file:
* bsdl_files/xcku15p_ffve1517.bsd * 1. $BS_FPGA_REGISTRY (if set and non-empty)
* IR length 6 bits, version nibble (bits 31:28) ignored. */ * 2. "fpga_registry.yaml" relative to the current directory
{ * — the same CWD-relative convention as bsdl_files/ and bscan_proxies/,
.name = "Xilinx Kintex UltraScale+ XCKU15P", * so run bs_explorer from the repository root.
.idcode = 0x04A56093, *
.idcode_mask = 0x0FFFFFFF, * Loaded lazily on first access and cached for the process lifetime.
.family = FPGA_FAMILY_XILINX_USP, * The schema is one flat mapping per entry; see fpga_registry.yaml.
.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,
},
};
#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) 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) 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 NULL;
} }
return &fpga_registry[index]; return &g_registry[index];
} }
const fpga_target *fpga_lookup_by_idcode(unsigned long idcode) const fpga_target *fpga_lookup_by_idcode(unsigned long idcode)
{ {
int i; int i;
for (i = 0; i < FPGA_REGISTRY_LEN; i++) { ensure_loaded();
const fpga_target *t = &fpga_registry[i]; for (i = 0; i < g_count; i++) {
const fpga_target *t = &g_registry[i];
if ((idcode & t->idcode_mask) == (t->idcode & t->idcode_mask)) { if ((idcode & t->idcode_mask) == (t->idcode & t->idcode_mask)) {
return t; return t;
} }
@@ -75,10 +254,20 @@ const fpga_target *fpga_lookup_by_idcode(unsigned long idcode)
const char *fpga_family_name(fpga_family f) const char *fpga_family_name(fpga_family f)
{ {
switch (f) { switch (f) {
case FPGA_FAMILY_XILINX_7: return "Xilinx 7-Series"; case FPGA_FAMILY_XILINX_7: return "Xilinx 7-Series";
case FPGA_FAMILY_XILINX_US: return "Xilinx UltraScale"; case FPGA_FAMILY_XILINX_US: return "Xilinx UltraScale";
case FPGA_FAMILY_XILINX_USP: 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: 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 * - path to the BSCAN proxy bitstream
* - per-target caveats (known hardware gotchas) * - per-target caveats (known hardware gotchas)
* *
* Adding an FPGA = one entry in fpga_registry[] + its .bsd in * The registry is loaded at runtime from a YAML file (no longer a
* bsdl_files/ + (optionally) its proxy .bit in bscan_proxies/. * 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" #include "jtag_core/jtag_core.h"
@@ -22,6 +24,10 @@ typedef enum {
FPGA_FAMILY_XILINX_7, FPGA_FAMILY_XILINX_7,
FPGA_FAMILY_XILINX_US, FPGA_FAMILY_XILINX_US,
FPGA_FAMILY_XILINX_USP, FPGA_FAMILY_XILINX_USP,
FPGA_FAMILY_MICROSEMI_IGLOO2,
FPGA_FAMILY_MICROSEMI_SMARTFUSION2,
FPGA_FAMILY_LATTICE_MACHXO2,
FPGA_FAMILY_LATTICE_MACHXO3,
} fpga_family; } fpga_family;
/* Caveat flags: known hardware gotchas for a part. */ /* Caveat flags: known hardware gotchas for a part. */
@@ -48,10 +54,15 @@ typedef struct {
unsigned int caveats; /* FPGA_CAVEAT_* flags */ unsigned int caveats; /* FPGA_CAVEAT_* flags */
} fpga_target; } 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); int fpga_get_target_count(void);
const fpga_target * fpga_get_target_by_index(int index); const fpga_target * fpga_get_target_by_index(int index);
const fpga_target * fpga_lookup_by_idcode(unsigned long idcode); const fpga_target * fpga_lookup_by_idcode(unsigned long idcode);
const char * fpga_family_name(fpga_family f); 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 #endif

View File

@@ -2779,17 +2779,20 @@ static int cmd_get_pins_list(script_ctx *ctx, char *line)
const char *cmd_fpga_list_help[] = { 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) static int cmd_fpga_list(script_ctx *ctx, char *line)
{ {
int i, n; int i, n;
const fpga_target *t; const fpga_target *t;
const char *src;
(void)line; (void)line;
n = fpga_get_target_count(); 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++) { for (i = 0; i < n; i++) {
t = fpga_get_target_by_index(i); t = fpga_get_target_by_index(i);
ctx->script_printf(ctx, MSG_NONE, ctx->script_printf(ctx, MSG_NONE,