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

@@ -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). |