target: generalize the registry to FPGAs + CPUs, add program dispatch

Restructure in anticipation of programming ARM CPUs (ARM7/9 via
EmbeddedICE, e.g. over an Olimex ARM-USB-OCD); FPGA path unchanged.

- modules/fpga -> modules/target; fpga_target -> jtag_target with a
  `kind` (fpga|cpu) and grouped fpga/cpu sub-structs; data/targets.yaml
  (env BS_TARGETS); API target_*; commands target_list/target_info
  (kind-aware). Add arm7/arm9 families, arm_flash prog, embeddedice
  debug, and cpu fields (ram_base/size, flash_base/size).
- new program/: `program <dev> <file>` dispatches by the target's prog
  (svf wired; proxy_spi points at the flash workflow; arm_flash -> arm_debug).
- new arm_debug/: EmbeddedICE halt/resume/mem + arm_flash backend
  declared, not implemented yet.
- bscan_* take const jtag_target* and read the fpga sub-struct.
- data/probes.yaml: arm-usb-ocd profile slot; data/targets.yaml: an ARM7
  example entry. Docs + an ARM-debug design note in CLAUDE.md.

Builds; FPGA path re-validated on the IGLOO2 (target_list shows the CPU
example; jtag_open/autoinit/program 0 <svf> all work).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 15:33:58 +02:00
parent d1bdce91dc
commit 9ad776268e
20 changed files with 973 additions and 582 deletions

110
CLAUDE.md
View File

@@ -18,8 +18,9 @@ configuration flash via a BSCAN proxy (started with the KU15P), and
other families (Lattice, Microsemi, …) by playing a vendor-exported SVF. other families (Lattice, Microsemi, …) by playing a vendor-exported SVF.
The Viveris library itself lives unchanged in `src/modules/`. Everything The Viveris library itself lives unchanged in `src/modules/`. Everything
new is in `src/bs/` (the REPL) and the project modules (`fpga/`, `bscan/`, new is in `src/bs/` (the REPL) and the project modules (`target/`,
`spi_flash/`, `svf/`, `probes/`) sitting alongside the Viveris ones. `bscan/`, `spi_flash/`, `svf/`, `probes/`, `program/`, `arm_debug/`)
sitting alongside the Viveris ones.
## Architecture ## Architecture
@@ -38,14 +39,16 @@ src/ — code + libs —
├── os_interface/ Portable fs/network wrappers ├── os_interface/ Portable fs/network wrappers
└── natsort/ Natural pin-name sorting └── natsort/ Natural pin-name sorting
— new (this project) — — new (this project) —
├── fpga/ Registry loader (parses data/fpga_registry.yaml, libyaml) ├── target/ Target registry: FPGAs + CPUs (parses data/targets.yaml)
├── bscan/ JTAG TAP primitives (set_ir/shift_ir/shift_dr/tap_reset/ ├── bscan/ JTAG TAP primitives (set_ir/shift_ir/shift_dr/tap_reset/
│ idle_cycles) + BSCAN proxy (bitstream load, SPI-over-USER1) │ idle_cycles) + BSCAN proxy (bitstream load, SPI-over-USER1)
├── spi_flash/ SPI NOR chip DB + read/erase/program/verify over a callback ├── spi_flash/ SPI NOR chip DB + read/erase/program/verify over a callback
├── svf/ SVF player (svf_play): SIR/SDR/RUNTEST/STATE, masked compare ├── svf/ SVF player (svf_play): SIR/SDR/RUNTEST/STATE, masked compare
── probes/ Probe-config profiles loader (parses data/probes.yaml, libyaml) ── probes/ Probe-config profiles loader (parses data/probes.yaml, libyaml)
├── program/ `program` dispatch: routes a target to its backend by `prog`
└── arm_debug/ ARM (EmbeddedICE) debug + flash backend (not implemented yet)
data/ — runtime resources, looked up CWD-relative — data/ — runtime resources, looked up CWD-relative —
├── fpga_registry.yaml FPGA registry (IDCODE BSDL, IR opcodes, proxy, caveats, prog, max_tck) ├── targets.yaml Target registry (FPGAs + CPUs: IDCODE, BSDL/proxy, debug, flash, prog)
├── probes.yaml Probe-config profiles (defaults + per-probe overrides) ├── probes.yaml Probe-config profiles (defaults + per-probe overrides)
├── 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)
@@ -64,12 +67,13 @@ Adding a feature usually means adding a new script command in
| Phase | Module | Status | Summary | | Phase | Module | Status | Summary |
|-------|--------|--------|---------| |-------|--------|--------|---------|
| 1 | `bs/` cleanup, REPL polish, README | **done** (commit `7cb3627`) | Fix format-strings, delete dead code, tab-completion, banner | | 1 | `bs/` cleanup, REPL polish, README | **done** (commit `7cb3627`) | Fix format-strings, delete dead code, tab-completion, banner |
| 2 | `fpga/` | **done** (commit `545fe09`) | Per-target descriptor (IDCODE, BSDL, IR codes, proxy path, caveats). Now a **runtime YAML** registry (`data/fpga_registry.yaml`, libyaml), later gaining `prog` method + `max_tck_khz`. | | 2 | `fpga/` | **done** (commit `545fe09`) | Per-target descriptor (IDCODE, BSDL, IR codes, proxy path, caveats). Now a **runtime YAML** registry (`data/targets.yaml`, libyaml), later gaining `prog` method + `max_tck_khz`. |
| 2.5 | `bscan/` | **done** (commit `dec0d14`) | Load BSCAN proxy bitstream via `CFG_IN`, expose fast `bscan_spi_xfer()` via `USER1`. Required for realistic flashing speeds. | | 2.5 | `bscan/` | **done** (commit `dec0d14`) | Load BSCAN proxy bitstream via `CFG_IN`, expose fast `bscan_spi_xfer()` via `USER1`. Required for realistic flashing speeds. |
| 3 | `spi_flash/` | **done** (commit `c4afe87`) | Chip DB (JEDEC ID → page/sector/cmd set) + generic `read/erase/program/verify` over an `xfer` callback. detect+read validated on KCU105; erase/program implemented but not yet hardware-tested. | | 3 | `spi_flash/` | **done** (commit `c4afe87`) | Chip DB (JEDEC ID → page/sector/cmd set) + generic `read/erase/program/verify` over an `xfer` callback. detect+read validated on KCU105; erase/program implemented but not yet hardware-tested. |
| 4 | script commands | **done** (commit `d6f843e`) | `flash_detect`, `flash_read` (+file), `flash_erase`, `flash_write`, `flash_verify`. Full set validated on KCU105 (save/erase/write-random/verify/restore round-trip). ~100 KB/s write once the proxy is loaded. | | 4 | script commands | **done** (commit `d6f843e`) | `flash_detect`, `flash_read` (+file), `flash_erase`, `flash_write`, `flash_verify`. Full set validated on KCU105 (save/erase/write-random/verify/restore round-trip). ~100 KB/s write once the proxy is loaded. |
| 5 | `probes/` + JTAG-link | **done** | `data/probes.yaml` probe-config profiles (`jtag_open <idx> <profile>`, `jtag_profiles`, `jtag_close`); driver-neutral `JTAG_TCK_FREQ_KHZ`/`JTAG_RTCK`; device `max_tck_khz` clock cap resolved at `jtag_autoinit`; `prog` method tag. See the config-strategy design note. Validated on the IGLOO2 (FlashPro). | | 5 | `probes/` + JTAG-link | **done** | `data/probes.yaml` probe-config profiles (`jtag_open <idx> <profile>`, `jtag_profiles`, `jtag_close`); driver-neutral `JTAG_TCK_FREQ_KHZ`/`JTAG_RTCK`; device `max_tck_khz` clock cap resolved at `jtag_autoinit`; `prog` method tag. See the config-strategy design note. Validated on the IGLOO2 (FlashPro). |
| 6 | `svf/` | **done** (subset, commit `c77d86e`) | SVF player + `svf_play`: SIR/SDR with masked TDO compare, RUNTEST, STATE — single-device. Validated on the IGLOO2 IDCODE; a real Libero SVF and a generic `program` dispatch off the `prog` tag are still TODO. | | 6 | `svf/` | **done** (subset, commit `c77d86e`) | SVF player + `svf_play`: SIR/SDR with masked TDO compare, RUNTEST, STATE — single-device. Validated on the IGLOO2 IDCODE. |
| 7 | `target/` + `program/` + `arm_debug/` | **structure done; ARM impl TODO** | Generalized `fpga/` into a kind-aware `target/` registry (FPGA \| CPU). `program <dev> <file>` dispatches by `prog` (svf wired; proxy_spi points at the flash workflow). `arm_debug/` (EmbeddedICE) + `arm_flash` backend are declared but not implemented; `arm-usb-ocd` probe profile added. FPGA path re-validated on the IGLOO2. See the ARM-debug design note. |
Move forward phase by phase: validate one with the user before starting Move forward phase by phase: validate one with the user before starting
the next. Don't break the validated path the next. Don't break the validated path
@@ -95,25 +99,25 @@ internally — the `STARTUPE3` problem disappears). Realistic throughput
50200 KB/s, so ~1040 min for 128 MB. This is what Vivado, OpenOCD's 50200 KB/s, so ~1040 min for 128 MB. This is what Vivado, OpenOCD's
`jtagspi` driver, and `xc3sprog` all do. `jtagspi` driver, and `xc3sprog` all do.
### Per-FPGA descriptor ### Per-target descriptor
`fpga_target` struct (Phase 2) holds the per-target facts that can't be The `jtag_target` struct (`target/target.h`) holds the per-target facts
derived from the BSDL alone: that can't be derived from the IDCODE/BSDL alone. A `kind` (fpga|cpu)
- `idcode` + `idcode_mask` — match the device on the chain selects a sub-struct; the rest is shared:
- `bsdl_filename` — BSDL to auto-load - common — `idcode`+`idcode_mask` (chain match), `family`, `ir_length`,
- `cfg_in_ir_code`, `user1_ir_code`, `jprogram_ir_code` — Xilinx-specific `prog` (programming backend), `max_tck_khz`
private IR opcodes (read from BSDL when available) - `fpga` sub-struct — `bsdl_filename`, the Xilinx config IR opcodes
- `proxy_bitstream_path` — path to the BSCAN proxy `.bit` for this part (`cfg_in`/`user1`/`jprogram`/…), `proxy_bitstream`, `caveats`
- `caveats` — flags for known hardware gotchas (e.g. CCLK via STARTUPE3) - `cpu` sub-struct — `debug` transport (EmbeddedICE), work-RAM
(`ram_base`/`size`) and on-chip flash region (`flash_base`/`size`)
Registry is a **runtime YAML file** (`data/fpga_registry.yaml` at the repo Registry is a **runtime YAML file** (`data/targets.yaml`), parsed via
root), parsed via libyaml — no longer a compile-time array. It is looked libyaml — looked up CWD-relative (like `data/bsdl_files/`), overridable
up CWD-relative (like `data/bsdl_files/`), overridable with with `$BS_TARGETS`, loaded lazily. Adding a target = one flat YAML entry
`$BS_FPGA_REGISTRY`, and loaded lazily on first access. Adding a part = (+ a `.bsd`/proxy for FPGAs) — no rebuild. `family` accepts `xilinx_*`,
one YAML entry + its `.bsd` in `data/bsdl_files/` + (optionally) its proxy `microsemi_*`, `lattice_*`, `arm7`/`arm9`; `prog` is
`.bit` in `data/bscan_proxies/` — no rebuild. The `family` field accepts `proxy_spi`/`svf`/`arm_flash`/`none` (inferred when omitted). Enums in
`xilinx_*`, `microsemi_igloo2/smartfusion2`, `lattice_machxo2/3` `target.h`.
(enum in `fpga.h`).
### Digilent SMT2 modules need libdjtg, not raw MPSSE ### Digilent SMT2 modules need libdjtg, not raw MPSSE
@@ -171,7 +175,7 @@ driver-neutral terms that get resolved per session.**
|-------|-------------|----------------|---------| |-------|-------------|----------------|---------|
| **Probe (sonde)** | driver + interface, pin map, buffer-enable, TRST/SRST pins, level-shift, *max TCK the adapter supports* | `data/probes.yaml` (done) | at `jtag_open` | | **Probe (sonde)** | driver + interface, pin map, buffer-enable, TRST/SRST pins, level-shift, *max TCK the adapter supports* | `data/probes.yaml` (done) | at `jtag_open` |
| **JTAG link** | TCK freq, RTCK, reset behaviour, chain layout — **driver-neutral names**, resolved to effective values | *missing today* | open, then refined after detect | | **JTAG link** | TCK freq, RTCK, reset behaviour, chain layout — **driver-neutral names**, resolved to effective values | *missing today* | open, then refined after detect |
| **Device** | IDCODE/BSDL/IR/proxy/caveats, programming method, *max TCK the part/board tolerates* | `data/fpga_registry.yaml` (done) | after IDCODE match | | **Device** | IDCODE/BSDL/IR/proxy/caveats, programming method, *max TCK the part/board tolerates* | `data/targets.yaml` (done) | after IDCODE match |
### The smell that motivated this ### The smell that motivated this
@@ -195,7 +199,7 @@ fact bounded by both the probe and the board/device.
method) is known — using the `jtag_close`→reopen seam if a re-init is method) is known — using the `jtag_close`→reopen seam if a re-init is
needed. This also dissolves the chicken-and-egg: a conservative link needed. This also dissolves the chicken-and-egg: a conservative link
default gets you to detection, then the device refines it. default gets you to detection, then the device refines it.
- `data/probes.yaml` gains an optional `max_tck_khz`; `data/fpga_registry.yaml` - `data/probes.yaml` gains an optional `max_tck_khz`; `data/targets.yaml`
gains optional `max_tck_khz` + a `prog` method tag (`proxy_spi`/`svf`). gains optional `max_tck_khz` + a `prog` method tag (`proxy_spi`/`svf`).
### Phasing ### Phasing
@@ -213,7 +217,7 @@ fact bounded by both the probe and the board/device.
still TODO. still TODO.
- **C (done)** — `prog` method tag (`proxy_spi`/`svf`/`none`) on each - **C (done)** — `prog` method tag (`proxy_spi`/`svf`/`none`) on each
registry entry, inferred when omitted (proxy → `proxy_spi`; Microsemi/ registry entry, inferred when omitted (proxy → `proxy_spi`; Microsemi/
Lattice → `svf`); shown by `fpga_info`/`fpga_list` and available via Lattice → `svf`); shown by `target_info`/`target_list` and available via
`fpga_prog_method_name()` for backend dispatch. RTCK generalised as a `fpga_prog_method_name()` for backend dispatch. RTCK generalised as a
neutral `JTAG_RTCK` (mirrored to `PROBE_FTDI_JTAG_ENABLE_RTCK`, neutral `JTAG_RTCK` (mirrored to `PROBE_FTDI_JTAG_ENABLE_RTCK`,
FTDI-only). Reset abstraction deferred — it's a bundle of probe- FTDI-only). Reset abstraction deferred — it's a bundle of probe-
@@ -221,7 +225,7 @@ fact bounded by both the probe and the board/device.
actual `program` dispatch command lands with the SVF player. actual `program` dispatch command lands with the SVF player.
What exists already: the **probe layer** (`data/probes.yaml`) and the What exists already: the **probe layer** (`data/probes.yaml`) and the
**device layer** (`data/fpga_registry.yaml`). The new work is the **JTAG-link **device layer** (`data/targets.yaml`). The new work is the **JTAG-link
layer** in the middle. layer** in the middle.
## Programming backends: beyond Xilinx external flash (design note) ## Programming backends: beyond Xilinx external flash (design note)
@@ -298,6 +302,52 @@ a registry entry.
one-time NVCM) with minimal JTAG — out of scope for this JTAG-centric one-time NVCM) with minimal JTAG — out of scope for this JTAG-centric
tool. tool.
## Programming CPUs over JTAG: ARM7/9 via EmbeddedICE (design note)
Structure in place (`target/` kind=cpu, `program/` dispatch, `arm_debug/`
+ `arm_flash` declared); the debug/flash code is the next real work.
### Why CPUs are a different shape
A CPU isn't configured like an FPGA — there's no bitstream or BSDL pin
map to drive. You **halt the core through its debug unit, write a small
flash loader into on-chip RAM, run it to program the internal flash, and
verify** — the OpenOCD approach. So the target carries different facts
(captured in the `cpu` sub-struct): the debug transport, a work-RAM
region for the loader, and the flash region.
### The debug seam (`arm_debug/`)
ARM7/ARM9 expose **EmbeddedICE** on a JTAG TAP: a scan chain (DSCR, DCC,
breakpoint regs) reached via the standard IR/DR shifts. The seam is four
primitives — `halt`, `resume`, `mem_read`, `mem_write` — built on the
existing `bscan_*` IR/DR functions; everything else (the RAM loader, the
per-MCU flash algorithm) sits on top. Cortex-M is the same idea behind a
*different* transport (ADIv5/DAP, and SWD which isn't JTAG); it would be
a second `debug` value behind the same seam, not a rewrite.
### Backend dispatch (`program/`)
`program <dev> <file>` looks the device up in the registry and routes by
its `prog` tag: `svf`→SVF player, `proxy_spi`→the Xilinx flash workflow,
`arm_flash`→`arm_debug`'s RAM-loader flash. Adding a backend = one `prog`
value + one case. This is the seam that makes new target classes drop in
without touching the REPL.
### Probe
ARM-USB-OCD is an Olimex **FT2232** (OpenOCD-class) adapter — the
existing FTDI driver drives it; it just needs a `probes.yaml` profile
(`arm-usb-ocd`) with the Olimex control-pin map (TRST/SRST, buffer
enable). The profile slot exists; the exact pin numbers still need
filling from the Olimex schematic / OpenOCD's interface config.
### What's left (the implementation)
EmbeddedICE scan-chain access + halt/resume + memory R/W, then a per-MCU
RAM flash loader (LPC2xxx, AT91SAM7, …) and the `arm_flash` backend. The
registry, dispatch, probe-profile and config layers are ready for it.
## Embedded port (design note) ## Embedded port (design note)
Not yet implemented — captured for a possible standalone programmer. Not yet implemented — captured for a possible standalone programmer.
@@ -365,9 +415,9 @@ mkdir build && cd build && cmake .. && make
``` ```
Build needs **libyaml** (pkg-config `yaml-0.1`; Arch `libyaml`, Debian Build needs **libyaml** (pkg-config `yaml-0.1`; Arch `libyaml`, Debian
`libyaml-dev`) — the FPGA registry is parsed from `data/fpga_registry.yaml` `libyaml-dev`) — the FPGA registry is parsed from `data/targets.yaml`
at runtime. Run `bs` from the repo root so it finds that file (and at runtime. Run `bs` from the repo root so it finds that file (and
`data/bsdl_files/`, `data/bscan_proxies/`), or point `$BS_FPGA_REGISTRY` at it. `data/bsdl_files/`, `data/bscan_proxies/`), or point `$BS_TARGETS` 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

View File

@@ -18,16 +18,18 @@ library by Viveris (LGPL).
- JTAG chain detection through FTDI / J-Link / Linux GPIO / Digilent SMT2 probes: OK - JTAG chain detection through FTDI / J-Link / Linux GPIO / Digilent SMT2 probes: OK
- Automatic BSDL loading by IDCODE: OK - Automatic BSDL loading by IDCODE: OK
- Pin control in SAMPLE / EXTEST, incl. slow SPI bit-bang: OK - Pin control in SAMPLE / EXTEST, incl. slow SPI bit-bang: OK
- FPGA registry (runtime YAML: IDCODE → BSDL, IR opcodes, proxy, caveats, programming method): OK - Target registry (runtime YAML, FPGAs + CPUs: IDCODE → BSDL/debug, IR opcodes, proxy, caveats, programming method): OK
- Probe-config profiles (`data/probes.yaml`) + driver-neutral JTAG clock with per-device cap: OK - Probe-config profiles (`data/probes.yaml`) + driver-neutral JTAG clock with per-device cap: OK
- BSCAN proxy SPI bridge (load proxy bitstream, talk SPI via `USER1`): OK - BSCAN proxy SPI bridge (load proxy bitstream, talk SPI via `USER1`): OK
- SPI flash detect / read / erase / program / verify: OK (~100 KB/s via the proxy) - SPI flash detect / read / erase / program / verify: OK (~100 KB/s via the proxy)
- SVF player (`svf_play`) — program any device from a vendor-exported SVF: OK (single-device subset) - SVF player (`svf_play`) — program any device from a vendor-exported SVF: OK (single-device subset)
- `program <dev> <file>` — dispatches to the right backend by the target's `prog` method: OK
- ARM7/9 CPU flashing (EmbeddedICE, ARM-USB-OCD): structure in place; debug/flash backend not implemented yet
Bundled BSDLs: Xilinx Kintex UltraScale+ KU15P Bundled BSDLs: Xilinx Kintex UltraScale+ KU15P
(`xcku15p_ffve1517.bsd`), Kintex UltraScale KU040 (`xcku040_ffva1156.bsd`), (`xcku15p_ffve1517.bsd`), Kintex UltraScale KU040 (`xcku040_ffva1156.bsd`),
and Microsemi IGLOO2 M2GL010T (`m2gl010t-fg484.bsd`). Add more by dropping and Microsemi IGLOO2 M2GL010T (`m2gl010t-fg484.bsd`). Add more by dropping
`.bsd` files in `data/bsdl_files/` plus an entry in `data/fpga_registry.yaml` (see `.bsd` files in `data/bsdl_files/` plus an entry in `data/targets.yaml` (see
[`doc/tutorial.md`](doc/tutorial.md) for adding a target). [`doc/tutorial.md`](doc/tutorial.md) for adding a target).
## Dependencies ## Dependencies
@@ -76,8 +78,8 @@ are looked up relative to the current directory:
full list. Loaded at startup. full list. Loaded at startup.
- `data/probes.yaml` — probe-config profiles, applied with - `data/probes.yaml` — probe-config profiles, applied with
`jtag_open <idx> <profile>` (`$BS_PROBES` overrides the path). `jtag_open <idx> <profile>` (`$BS_PROBES` overrides the path).
- `data/fpga_registry.yaml` — the FPGA target registry - `data/targets.yaml` — the FPGA target registry
(`$BS_FPGA_REGISTRY` overrides the path). (`$BS_TARGETS` overrides the path).
- `data/bsdl_files/`, `data/bscan_proxies/` — BSDLs and proxy bitstreams. - `data/bsdl_files/`, `data/bscan_proxies/` — BSDLs and proxy bitstreams.
## REPL ## REPL
@@ -100,7 +102,7 @@ bs_explorer> jtag_profiles # available profiles
bs_explorer> jtag_open 0 # or: jtag_open 0 <profile> bs_explorer> jtag_open 0 # or: jtag_open 0 <profile>
# 2. Scan the chain and auto-load matching BSDLs # 2. Scan the chain and auto-load matching BSDLs
bs_explorer> jtag_autoinit # fpga_info then shows the prog method bs_explorer> jtag_autoinit # target_info then shows the prog method
# 3. Load the BSCAN proxy into the fabric (fast SPI bridge) # 3. Load the BSCAN proxy into the fabric (fast SPI bridge)
bs_explorer> bscan_load_bitstream 0 data/bscan_proxies/bscan_spi_xcku040.bit bs_explorer> bscan_load_bitstream 0 data/bscan_proxies/bscan_spi_xcku040.bit
@@ -132,10 +134,10 @@ lives in [`doc/tutorial.md`](doc/tutorial.md).
| Probe / chain | `jtag_probes`, `jtag_open`, `jtag_close`, `jtag_profiles`, `jtag_scan`, `jtag_autoinit`, `jtag_ndev`, `jtag_devices` | | Probe / chain | `jtag_probes`, `jtag_open`, `jtag_close`, `jtag_profiles`, `jtag_scan`, `jtag_autoinit`, `jtag_ndev`, `jtag_devices` |
| BSDL / pins | `jtag_bsdl`, `jtag_pins`, `jtag_mode`, `jtag_pin_dir`, `jtag_pin_set`, `jtag_pin_get`, `jtag_push_pop` | | BSDL / pins | `jtag_bsdl`, `jtag_pins`, `jtag_mode`, `jtag_pin_dir`, `jtag_pin_set`, `jtag_pin_get`, `jtag_push_pop` |
| I²C / MDIO / SPI over BS pins (EXTEST) | `jtag_i2c_scl`, `jtag_i2c_sda`, `jtag_i2c_rd`, `jtag_i2c_wr`, `jtag_mdio_mdc`, `jtag_mdio_io`, `jtag_mdio_rd`, `jtag_mdio_wr`, `jtag_spi_cs/mosi/miso/clk`, `jtag_spi_xfer` | | I²C / MDIO / SPI over BS pins (EXTEST) | `jtag_i2c_scl`, `jtag_i2c_sda`, `jtag_i2c_rd`, `jtag_i2c_wr`, `jtag_mdio_mdc`, `jtag_mdio_io`, `jtag_mdio_rd`, `jtag_mdio_wr`, `jtag_spi_cs/mosi/miso/clk`, `jtag_spi_xfer` |
| FPGA registry | `fpga_list`, `fpga_info` | | Targets (FPGA/CPU) | `target_list`, `target_info` |
| BSCAN proxy | `bscan_load_bitstream`, `bscan_jedec`, `bscan_set_ir`, `bscan_shift_dr` | | BSCAN proxy | `bscan_load_bitstream`, `bscan_jedec`, `bscan_set_ir`, `bscan_shift_dr` |
| SPI flash (via proxy) | `flash_detect`, `flash_read`, `flash_erase`, `flash_write`, `flash_verify` | | SPI flash (via proxy) | `flash_detect`, `flash_read`, `flash_erase`, `flash_write`, `flash_verify` |
| SVF player | `svf_play` | | Program | `program` (dispatch by target), `svf_play` |
| Misc | `help`, `?`, `version`, `exit` | | Misc | `help`, `?`, `version`, `exit` |
Use `help <command>` for per-command help. Use `help <command>` for per-command help.
@@ -148,6 +150,8 @@ Use `help <command>` for per-command help.
Microsemi eval kits, which needs ADBUS4 left high-Z) are handled by a Microsemi eval kits, which needs ADBUS4 left high-Z) are handled by a
**probe profile** in `data/probes.yaml`: `jtag_profiles` lists them, **probe profile** in `data/probes.yaml`: `jtag_profiles` lists them,
`jtag_open <idx> <profile>` applies one (e.g. `jtag_open 0 flashpro`). `jtag_open <idx> <profile>` applies one (e.g. `jtag_open 0 flashpro`).
The Olimex **ARM-USB-OCD** (also an FT2232, for ARM CPU targets) has an
`arm-usb-ocd` profile slot — pending its control-pin map.
- **SEGGER J-Link** - **SEGGER J-Link**
- **Linux GPIO** (sysfs; deprecated on recent kernels, libgpiod migration TBD) - **Linux GPIO** (sysfs; deprecated on recent kernels, libgpiod migration TBD)
- **Digilent JTAG-SMT2 / SMT2-NC** — built in by default on Linux - **Digilent JTAG-SMT2 / SMT2-NC** — built in by default on Linux
@@ -166,7 +170,7 @@ flash on these parts.
The **BSCAN proxy sidesteps this entirely**: it drives `CCLK` from the The **BSCAN proxy sidesteps this entirely**: it drives `CCLK` from the
fabric internally, so flashing runs at full speed. Parts affected are fabric internally, so flashing runs at full speed. Parts affected are
flagged with the `CCLK_VIA_STARTUP` caveat in the registry (`fpga_info` flagged with the `CCLK_VIA_STARTUP` caveat in the registry (`target_info`
shows it). shows it).
## Repository layout ## Repository layout
@@ -180,17 +184,19 @@ src/ — code + libs —
├── 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/ Registry loader (parses data/fpga_registry.yaml at runtime) ├── target/ Target registry loader: FPGAs + CPUs (data/targets.yaml)
├── bscan/ JTAG TAP primitives + BSCAN proxy (bitstream, SPI-over-USER1) ├── bscan/ JTAG TAP primitives + BSCAN proxy (bitstream, SPI-over-USER1)
├── spi_flash/ SPI NOR chip database + read/erase/program/verify ├── spi_flash/ SPI NOR chip database + read/erase/program/verify
├── svf/ SVF player (program from a vendor-exported SVF) ├── svf/ SVF player (program from a vendor-exported SVF)
├── probes/ Probe-config profiles loader (data/probes.yaml) ├── probes/ Probe-config profiles loader (data/probes.yaml)
├── program/ `program` dispatch: routes a target to its backend by prog
├── arm_debug/ ARM (EmbeddedICE) debug + flash backend (not implemented yet)
├── 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
data/ — runtime resources, looked up from the CWD — data/ — runtime resources, looked up from the CWD —
├── fpga_registry.yaml FPGA registry (IDCODE → BSDL, IR opcodes, proxy, caveats) ├── targets.yaml Target registry (FPGAs + CPUs)
├── probes.yaml Probe-config profiles (defaults + per-probe overrides) ├── probes.yaml Probe-config profiles (defaults + per-probe overrides)
├── 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)

View File

@@ -1,80 +0,0 @@
# 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)
# max_tck_khz max safe JTAG TCK in kHz for this part/board; if the
# requested clock exceeds it, jtag_autoinit clamps and
# re-opens at the cap (omit / 0 = unspecified)
# prog programming backend: proxy_spi | svf | none. Omit to
# infer (proxy_bitstream -> proxy_spi; Microsemi/Lattice
# -> svf; else 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
prog: proxy_spi
# 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
prog: proxy_spi
# Microsemi IGLOO2 M2GL010T (M2GL-EVAL-KIT)
# IDCODE / IR length from bsdl_files/m2gl010t-fg484.bsd
# IDCODE_REGISTER "XXXX1111100000000011000111001111" -> 0x0F8031CF
# (top nibble = silicon revision, masked off). Shared die with the
# SmartFusion2 M2S010, so both report the same IDCODE.
# No proxy: IGLOO2 internal flash is programmed by playing an SVF
# exported from Libero (SVF player not yet implemented), not via the
# Xilinx BSCAN-proxy SPI path — so the ir_*/proxy fields don't apply.
- name: "Microsemi IGLOO2 M2GL010T"
idcode: 0x0F8031CF
idcode_mask: 0x0FFFFFFF
family: microsemi_igloo2
bsdl: m2gl010t-fg484.bsd
ir_length: 8
prog: svf

View File

@@ -1,6 +1,6 @@
# bs_explorer probe-config profiles # bs_explorer probe-config profiles
# #
# Loaded at runtime by modules/probes/, layered on top of the built-in # Loaded at runtime by src/modules/probes/, layered on top of the built-in
# config.script defaults. Looked up CWD-relative (run from the repo # config.script defaults. Looked up CWD-relative (run from the repo
# root), or via $BS_PROBES. # root), or via $BS_PROBES.
# #
@@ -29,3 +29,10 @@ profiles:
# Plain FT2232H probe: nothing to override beyond the defaults. # Plain FT2232H probe: nothing to override beyond the defaults.
ft2232h: {} ft2232h: {}
# Olimex ARM-USB-OCD (FT2232, OpenOCD-class) — for ARM CPU targets.
# The core MPSSE JTAG pins (TCK/TDI/TDO/TMS = ADBUS0-3) match the
# defaults; the control pins (nTRST, nSRST, output-buffer enable) are
# board-specific. TODO: fill the TRST/SRST/buffer pin numbers from the
# Olimex schematic / OpenOCD's interface config before driving a target.
arm-usb-ocd: {}

102
data/targets.yaml Normal file
View File

@@ -0,0 +1,102 @@
# bs_explorer JTAG target registry (FPGAs and CPUs)
#
# Loaded at runtime by src/modules/target/. Looked up relative to the
# current directory (run bs_explorer from the repo root), or via
# $BS_TARGETS.
#
# One flat entry per device under `targets:`. `kind` selects which
# fields apply. Common fields:
# name human-readable part name (quoted)
# idcode JTAG IDCODE pattern (hex)
# idcode_mask bits compared when matching (0x0FFFFFFF masks a
# version nibble); default 0xFFFFFFFF
# kind fpga | cpu (default fpga)
# family xilinx_7/us/usp | microsemi_igloo2/smartfusion2 |
# lattice_machxo2/3 | arm7 | arm9
# ir_length IR width in bits
# prog backend: proxy_spi | svf | arm_flash | none.
# Omit to infer (proxy -> proxy_spi; Microsemi/Lattice
# -> svf; cpu with a debug iface -> arm_flash).
# max_tck_khz max safe JTAG TCK in kHz; jtag_autoinit clamps and
# re-opens if exceeded (omit / 0 = unspecified)
#
# FPGA fields (kind: fpga):
# bsdl basename of the .bsd in data/bsdl_files/
# 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 data/bscan_proxies/
# caveats space/comma-separated flags: cclk_via_startup
#
# CPU fields (kind: cpu):
# debug debug transport: embeddedice (ARM7/ARM9)
# ram_base/ram_size on-chip work-RAM for the flash loader (hex)
# flash_base/flash_size on-chip flash region (hex)
targets:
# Xilinx Kintex UltraScale+ XCKU15P
# IDCODE / opcodes from data/bsdl_files/xcku15p_ffve1517.bsd, IR length 6.
- name: "Xilinx Kintex UltraScale+ XCKU15P"
kind: fpga
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
prog: proxy_spi
# proxy_bitstream not yet built for this part (see doc/tutorial.md, Phase 2.5)
# Xilinx Kintex UltraScale XCKU040 (KCU105 eval board)
- name: "Xilinx Kintex UltraScale XCKU040"
kind: fpga
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
prog: proxy_spi
# Microsemi IGLOO2 M2GL010T (M2GL-EVAL-KIT)
# IDCODE / IR length from data/bsdl_files/m2gl010t-fg484.bsd
# IDCODE_REGISTER "XXXX1111100000000011000111001111" -> 0x0F8031CF
# (top nibble = silicon revision, masked off). Programmed by playing
# an SVF exported from Libero, not the Xilinx proxy path.
- name: "Microsemi IGLOO2 M2GL010T"
kind: fpga
idcode: 0x0F8031CF
idcode_mask: 0x0FFFFFFF
family: microsemi_igloo2
bsdl: m2gl010t-fg484.bsd
ir_length: 8
prog: svf
# --- CPU example (ARM7TDMI-S, LPC2148-class) ----------------------
# EXAMPLE entry: verify the IDCODE, IR length and RAM/flash regions
# against your actual part before relying on it. The arm_flash backend
# is not implemented yet (see src/modules/arm_debug/ and CLAUDE.md).
- name: "ARM7TDMI-S (example, e.g. NXP LPC2148)"
kind: cpu
idcode: 0x4F1F0F0F
idcode_mask: 0x0FFFFFFF
family: arm7
ir_length: 4
debug: embeddedice
ram_base: 0x40000000
ram_size: 0x8000
flash_base: 0x0
flash_size: 0x7D000
prog: arm_flash

View File

@@ -9,7 +9,7 @@ paths and the tutorial covers both:
- **any other part** (Lattice, Microsemi, …), by playing a - **any other part** (Lattice, Microsemi, …), by playing a
vendor-exported **SVF** — shown on a Microsemi IGLOO2 M2GL010T. vendor-exported **SVF** — shown on a Microsemi IGLOO2 M2GL010T.
The early steps are identical for any device in `data/fpga_registry.yaml`; The early steps are identical for any device in `data/targets.yaml`;
only the IDCODE and BSDL filename change. only the IDCODE and BSDL filename change.
## Prerequisites ## Prerequisites
@@ -19,7 +19,7 @@ only the IDCODE and BSDL filename change.
`src/libs/libftd2xx/`). `src/libs/libftd2xx/`).
- The target's BSDL in `data/bsdl_files/` (KU15P: `xcku15p_ffve1517.bsd` is - The target's BSDL in `data/bsdl_files/` (KU15P: `xcku15p_ffve1517.bsd` is
bundled). bundled).
- An entry for the target in `data/fpga_registry.yaml` (KU15P is bundled). - An entry for the target in `data/targets.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.
@@ -34,7 +34,7 @@ README and the `Digilent SMT2` block in `CLAUDE.md` for the why.
```sh ```sh
mkdir build && cd build && cmake .. && make && cd .. mkdir build && cd build && cmake .. && make && cd ..
./build/bs # run from the repo root: data/probes.yaml, data/fpga_registry.yaml, ./build/bs # run from the repo root: data/probes.yaml, data/targets.yaml,
# data/bsdl_files/ and data/bscan_proxies/ are looked up in the CWD # data/bsdl_files/ and data/bscan_proxies/ are looked up in the CWD
``` ```
@@ -121,11 +121,11 @@ 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 `target_info` walks the chain and matches each IDCODE against the
registry in `data/fpga_registry.yaml`: registry in `data/targets.yaml`:
``` ```
bs_explorer> fpga_info bs_explorer> target_info
Device 0 IDCODE 0x04A56093 -> Xilinx Kintex UltraScale+ XCKU15P [Xilinx UltraScale+] Device 0 IDCODE 0x04A56093 -> Xilinx Kintex UltraScale+ XCKU15P [Xilinx UltraScale+]
prog: proxy_spi prog: proxy_spi
caveat: CCLK routed via STARTUP primitive (not drivable in EXTEST) caveat: CCLK routed via STARTUP primitive (not drivable in EXTEST)
@@ -138,7 +138,7 @@ or `svf` ([§Programming via SVF](#programming-via-svf-lattice-microsemi-)).
If you get `not in registry`, add an entry — see If you get `not in registry`, add an entry — see
[Adding a new FPGA](#6-add-a-new-fpga-target). [Adding a new FPGA](#6-add-a-new-fpga-target).
`fpga_list` prints the whole registry without needing a probe. `target_list` prints the whole registry without needing a probe.
## JTAG clock (optional) ## JTAG clock (optional)
@@ -216,7 +216,7 @@ way; you'd be there for weeks.
## 6. Add a new FPGA target ## 6. Add a new FPGA target
The registry — `data/fpga_registry.yaml` at the repo root — holds the The registry — `data/targets.yaml` at the repo root — holds the
per-part facts that can't be derived from the BSDL alone (or are tedious 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 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 rebuild**. The XCKU040 entry already there was added exactly with the
@@ -247,7 +247,7 @@ masks them off.
### c. Add a YAML entry ### c. Add a YAML entry
Append a list item under `fpgas:` in `data/fpga_registry.yaml`: Append a list item under `fpgas:` in `data/targets.yaml`:
| Key | What it is | XCKU040 | | Key | What it is | XCKU040 |
|-----|-----------|---------| |-----|-----------|---------|
@@ -263,7 +263,7 @@ Append a list item under `fpgas:` in `data/fpga_registry.yaml`:
| `max_tck_khz` | max safe JTAG TCK in kHz; `jtag_autoinit` clamps + re-opens if exceeded (omit = unspecified) | — | | `max_tck_khz` | max safe JTAG TCK in kHz; `jtag_autoinit` clamps + re-opens if exceeded (omit = unspecified) | — |
| `prog` | programming backend `proxy_spi`/`svf`/`none` (omit → inferred: a proxy ⇒ `proxy_spi`, Microsemi/Lattice ⇒ `svf`) | `proxy_spi` | | `prog` | programming backend `proxy_spi`/`svf`/`none` (omit → inferred: a proxy ⇒ `proxy_spi`, Microsemi/Lattice ⇒ `svf`) | `proxy_spi` |
The resulting entry (verbatim from `data/fpga_registry.yaml`): The resulting entry (verbatim from `data/targets.yaml`):
```yaml ```yaml
- name: "Xilinx Kintex UltraScale XCKU040" - name: "Xilinx Kintex UltraScale XCKU040"
@@ -293,7 +293,7 @@ defaults to exact match (`0xFFFFFFFF`).
an `FPGA_CAVEAT_*` bit in `fpga.h`, marking **known hardware gotchas 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 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 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, field when the part has none. `target_info` prints any flag that is set,
as a human-readable line. as a human-readable line.
Currently one flag exists: Currently one flag exists:
@@ -310,7 +310,7 @@ Currently one flag exists:
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`, teach `parse_caveats()` in `fpga.c` its YAML name, use that 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 name in the YAML, and (if it should be visible) print it in
`cmd_fpga_info` in `script.c`. `cmd_target_info` in `script.c`.
### d. Verify — no rebuild ### d. Verify — no rebuild
@@ -319,12 +319,12 @@ the repo root and check:
```sh ```sh
./build/bs ./build/bs
bs_explorer> fpga_list # your part should appear, with its source file bs_explorer> target_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> target_info # should show your part, family, and any caveats
``` ```
(`fpga_list` reads the registry without needing a probe.) (`target_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)
@@ -369,7 +369,7 @@ The XCKU15P first has to be **added to the generator's device table**
Once built, drop `bscan_spi_xcku15p.bit` into `data/bscan_proxies/` (it's Once built, drop `bscan_spi_xcku15p.bit` into `data/bscan_proxies/` (it's
MIT, like the KU040 — keep `data/bscan_proxies/LICENSE.quartiq`) and set the MIT, like the KU040 — keep `data/bscan_proxies/LICENSE.quartiq`) and set the
`proxy_bitstream` field on the KU15P entry in `data/fpga_registry.yaml` `proxy_bitstream` field on the KU15P entry in `data/targets.yaml`
(currently omitted). (currently omitted).
### Load the bridge and talk SPI ### Load the bridge and talk SPI
@@ -434,7 +434,7 @@ compares that flag a failed erase/program.
``` ```
bs_explorer> jtag_open 0 flashpro # the probe profile for your kit bs_explorer> jtag_open 0 flashpro # the probe profile for your kit
bs_explorer> jtag_autoinit # fpga_info should show 'prog: svf' bs_explorer> jtag_autoinit # target_info should show 'prog: svf'
bs_explorer> svf_play design.svf bs_explorer> svf_play design.svf
... ...
SVF done: 1342 commands, 1338 scans, 71 compares SVF done: 1342 commands, 1338 scans, 71 compares
@@ -476,6 +476,31 @@ bitstream/SVF generator for Microsemi (or most vendors) — you need the
vendor tool to *produce* the SVF (Libero has a free tier covering the vendor tool to *produce* the SVF (Libero has a free tier covering the
small IGLOO2 parts). `bs_explorer` only *plays* it, which is fully open. small IGLOO2 parts). `bs_explorer` only *plays* it, which is fully open.
## One command: `program`
Rather than remember which backend a part uses, `program <dev> <file>`
dispatches on the device's registry `prog` method:
```
bs_explorer> jtag_autoinit
bs_explorer> program 0 design.svf # prog=svf -> plays the SVF
```
`svf` plays the file; `proxy_spi` points you at the flash workflow
(`bscan_load_bitstream` + `flash_write`/`flash_verify`); `arm_flash`
routes to the ARM backend.
### CPU targets (ARM7/9) — structure only
The registry also describes **CPUs** (`kind: cpu`): an ARM debug
transport (`debug: embeddedice`), work-RAM and an on-chip flash region.
`target_list` shows them and `program` routes `prog: arm_flash` to the
ARM backend — but that backend (halt the core over JTAG, load a RAM
flasher, program internal flash) is **not implemented yet**. An Olimex
ARM-USB-OCD is an FT2232, so it opens with the existing FTDI driver via
the `arm-usb-ocd` probe profile. See the ARM-debug design note in
`CLAUDE.md`.
## Troubleshooting cheat sheet ## Troubleshooting cheat sheet
| Symptom | Likely cause | | Symptom | Likely cause |
@@ -484,7 +509,7 @@ small IGLOO2 parts). `bs_explorer` only *plays* it, which is fully open.
| `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 an entry to `data/fpga_registry.yaml`. | | `target_info` says "not in registry" | Add an entry to `data/targets.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). |
| Detected fine, then reads turn to garbage / `0x00000000` mid-session | Target board lost power — JTAG floats (the USB probe stays enumerated regardless). Re-power the board. | | Detected fine, then reads turn to garbage / `0x00000000` mid-session | Target board lost power — JTAG floats (the USB probe stays enumerated regardless). Re-power the board. |

View File

@@ -0,0 +1,5 @@
file(GLOB_RECURSE ALL_SOURCES "*.c")
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)
add_library(arm_debug ${ALL_SOURCES})

View File

@@ -0,0 +1,56 @@
#include <stdio.h>
#include "arm_debug.h"
/* Not implemented yet — every entry point reports failure for now. The
* real work — EmbeddedICE scan chains, halt/resume, memory access over
* the bscan_* primitives, and a per-MCU RAM flash loader — slots in
* behind these signatures without touching callers. */
int arm_debug_halt(jtag_core *jc, const jtag_target *t)
{
(void)jc; (void)t;
fprintf(stderr, "arm_debug: halt not implemented yet\n");
return -1;
}
int arm_debug_resume(jtag_core *jc, const jtag_target *t)
{
(void)jc; (void)t;
fprintf(stderr, "arm_debug: resume not implemented yet\n");
return -1;
}
int arm_debug_mem_read(jtag_core *jc, const jtag_target *t,
unsigned long addr, void *buf, unsigned long len)
{
(void)jc; (void)t; (void)addr; (void)buf; (void)len;
fprintf(stderr, "arm_debug: mem_read not implemented yet\n");
return -1;
}
int arm_debug_mem_write(jtag_core *jc, const jtag_target *t,
unsigned long addr, const void *buf, unsigned long len)
{
(void)jc; (void)t; (void)addr; (void)buf; (void)len;
fprintf(stderr, "arm_debug: mem_write not implemented yet\n");
return -1;
}
int arm_flash_program(jtag_core *jc, const jtag_target *t, const char *file,
arm_log_fn log, void *user)
{
char msg[256];
(void)jc; (void)file;
if (log) {
snprintf(msg, sizeof(msg),
"arm_flash: backend not implemented yet. "
"Target '%s' debug=%d ram=0x%lX+0x%lX flash=0x%lX+0x%lX.",
t ? t->name : "?",
t ? (int)t->cpu.debug : 0,
t ? t->cpu.ram_base : 0UL, t ? t->cpu.ram_size : 0UL,
t ? t->cpu.flash_base : 0UL, t ? t->cpu.flash_size : 0UL);
log(user, 1, msg);
}
return -1;
}

View File

@@ -0,0 +1,38 @@
#ifndef _ARM_DEBUG_H
#define _ARM_DEBUG_H
/*
* ARM debug over JTAG (EmbeddedICE, ARM7/ARM9) — not implemented yet.
*
* The debug transport and the RAM-loader flash backend are not yet
* implemented; these declarations are the seams the implementation will
* fill. Planned flow (the OpenOCD approach):
*
* halt the core -> write a small flash loader into the target's
* work-RAM -> run it to program on-chip flash -> verify.
*
* It needs EmbeddedICE scan-chain access (DSCR/DCC) built on the
* bscan_* IR/DR primitives, plus per-MCU flash parameters from the
* registry (cpu.ram_base/size, cpu.flash_base/size). Cortex-M (ADIv5/
* DAP) would be a second debug transport behind the same seam.
*/
#include "jtag_core/jtag_core.h"
#include "target/target.h"
typedef void (*arm_log_fn)(void *user, int is_error, const char *msg);
/* Low-level debug primitives (stubs for now). */
int arm_debug_halt(jtag_core *jc, const jtag_target *t);
int arm_debug_resume(jtag_core *jc, const jtag_target *t);
int arm_debug_mem_read(jtag_core *jc, const jtag_target *t,
unsigned long addr, void *buf, unsigned long len);
int arm_debug_mem_write(jtag_core *jc, const jtag_target *t,
unsigned long addr, const void *buf, unsigned long len);
/* Program on-chip flash from `file` via the RAM loader (stub). Returns
* 0 on success, < 0 on error / not-implemented. */
int arm_flash_program(jtag_core *jc, const jtag_target *t, const char *file,
arm_log_fn log, void *user);
#endif

View File

@@ -206,7 +206,7 @@ static uint8_t reverse_bits(uint8_t b)
return b; return b;
} }
int bscan_load_bitstream(jtag_core *jc, const fpga_target *t, int bscan_load_bitstream(jtag_core *jc, const jtag_target *t,
const uint8_t *data, size_t nbytes) const uint8_t *data, size_t nbytes)
{ {
uint8_t *reversed; uint8_t *reversed;
@@ -214,7 +214,7 @@ int bscan_load_bitstream(jtag_core *jc, const fpga_target *t,
size_t i; size_t i;
if (!drv_ok(jc) || !t || !data || nbytes == 0) return -1; if (!drv_ok(jc) || !t || !data || nbytes == 0) return -1;
if (!t->ir_jprogram || !t->ir_cfg_in || !t->ir_jstart) { if (!t->fpga.ir_jprogram || !t->fpga.ir_cfg_in || !t->fpga.ir_jstart) {
/* No configuration opcodes known for this family. */ /* No configuration opcodes known for this family. */
return -1; return -1;
} }
@@ -222,11 +222,11 @@ int bscan_load_bitstream(jtag_core *jc, const fpga_target *t,
/* JPROGRAM clears the configuration memory. Min ~10k TCK cycles /* JPROGRAM clears the configuration memory. Min ~10k TCK cycles
* to wait for INIT_B to go high before CFG_IN. * to wait for INIT_B to go high before CFG_IN.
* TODO: poll INIT_B via SAMPLE instead of fixed wait. */ * TODO: poll INIT_B via SAMPLE instead of fixed wait. */
if (bscan_set_ir(jc, t->ir_jprogram, t->ir_length) < 0) return -1; if (bscan_set_ir(jc, t->fpga.ir_jprogram, t->ir_length) < 0) return -1;
bscan_idle_cycles(jc, 10000); bscan_idle_cycles(jc, 10000);
/* CFG_IN routes DR shifts to the configuration interface. */ /* CFG_IN routes DR shifts to the configuration interface. */
if (bscan_set_ir(jc, t->ir_cfg_in, t->ir_length) < 0) return -1; if (bscan_set_ir(jc, t->fpga.ir_cfg_in, t->ir_length) < 0) return -1;
/* Xilinx bitstream bytes must be bit-reversed before JTAG shift /* Xilinx bitstream bytes must be bit-reversed before JTAG shift
* (configuration interface latches MSB first, JTAG shifts LSB first). */ * (configuration interface latches MSB first, JTAG shifts LSB first). */
@@ -243,7 +243,7 @@ int bscan_load_bitstream(jtag_core *jc, const fpga_target *t,
/* JSTART triggers the fabric startup. UG470/UG570: ≥12 cycles in /* JSTART triggers the fabric startup. UG470/UG570: ≥12 cycles in
* Idle to complete the sequence. Use 2000 for margin. */ * Idle to complete the sequence. Use 2000 for margin. */
if (bscan_set_ir(jc, t->ir_jstart, t->ir_length) < 0) return -1; if (bscan_set_ir(jc, t->fpga.ir_jstart, t->ir_length) < 0) return -1;
bscan_idle_cycles(jc, 2000); bscan_idle_cycles(jc, 2000);
/* Park on BYPASS (all 1s) so other operations don't trip on a /* Park on BYPASS (all 1s) so other operations don't trip on a
@@ -297,7 +297,7 @@ static int xilinx_bit_payload(const uint8_t *buf, size_t buflen,
return -1; return -1;
} }
int bscan_load_bitstream_file(jtag_core *jc, const fpga_target *t, int bscan_load_bitstream_file(jtag_core *jc, const jtag_target *t,
const char *path) const char *path)
{ {
FILE *f; FILE *f;
@@ -339,7 +339,7 @@ int bscan_load_bitstream_file(jtag_core *jc, const fpga_target *t,
* OpenOCD's jtagspi. The header asserts a single-device chain. */ * OpenOCD's jtagspi. The header asserts a single-device chain. */
#define BSCAN_SPI_READ_LATENCY 1 #define BSCAN_SPI_READ_LATENCY 1
int bscan_spi_xfer(jtag_core *jc, const fpga_target *t, int bscan_spi_xfer(jtag_core *jc, const jtag_target *t,
const uint8_t *tx, size_t txlen, const uint8_t *tx, size_t txlen,
uint8_t *rx, size_t rxlen) uint8_t *rx, size_t rxlen)
{ {
@@ -356,7 +356,7 @@ int bscan_spi_xfer(jtag_core *jc, const fpga_target *t,
size_t i; size_t i;
uint8_t *dr_out, *dr_in; uint8_t *dr_out, *dr_in;
if (!drv_ok(jc) || !t || !t->ir_user1 || spi_bytes == 0) return -1; if (!drv_ok(jc) || !t || !t->fpga.ir_user1 || spi_bytes == 0) return -1;
if (txlen && !tx) return -1; if (txlen && !tx) return -1;
if (rxlen && !rx) return -1; if (rxlen && !rx) return -1;
@@ -395,7 +395,7 @@ int bscan_spi_xfer(jtag_core *jc, const fpga_target *t,
bit += (int)rxlen * 8; /* MISO region (MOSI=0) */ bit += (int)rxlen * 8; /* MISO region (MOSI=0) */
} }
if (bscan_set_ir(jc, t->ir_user1, t->ir_length) < 0 || if (bscan_set_ir(jc, t->fpga.ir_user1, t->ir_length) < 0 ||
bscan_shift_dr(jc, dr_out, dr_in, total_bits) < 0) { bscan_shift_dr(jc, dr_out, dr_in, total_bits) < 0) {
free(dr_out); free(dr_in); free(dr_out); free(dr_in);
return -1; return -1;

View File

@@ -24,7 +24,7 @@
#include <stdint.h> #include <stdint.h>
#include "jtag_core/jtag_core.h" #include "jtag_core/jtag_core.h"
#include "fpga/fpga.h" #include "target/target.h"
/* --- Low-level primitives (single-device chain) -------------------- */ /* --- Low-level primitives (single-device chain) -------------------- */
@@ -51,12 +51,12 @@ int bscan_tap_reset(jtag_core *jc);
/* Load a raw bitstream payload (no .bit container header) into the /* Load a raw bitstream payload (no .bit container header) into the
* FPGA via JPROGRAM -> CFG_IN -> shift -> JSTART. Bit-reverses each * FPGA via JPROGRAM -> CFG_IN -> shift -> JSTART. Bit-reverses each
* byte before shifting (Xilinx convention). */ * byte before shifting (Xilinx convention). */
int bscan_load_bitstream(jtag_core *jc, const fpga_target *t, int bscan_load_bitstream(jtag_core *jc, const jtag_target *t,
const uint8_t *data, size_t nbytes); const uint8_t *data, size_t nbytes);
/* Convenience wrapper: read a file and load it. Detects the Xilinx /* Convenience wrapper: read a file and load it. Detects the Xilinx
* .bit header and skips it; otherwise treats the file as raw payload. */ * .bit header and skips it; otherwise treats the file as raw payload. */
int bscan_load_bitstream_file(jtag_core *jc, const fpga_target *t, int bscan_load_bitstream_file(jtag_core *jc, const jtag_target *t,
const char *path); const char *path);
/* One CS-framed SPI transaction through the BSCAN proxy (USER1 DR): /* One CS-framed SPI transaction through the BSCAN proxy (USER1 DR):
@@ -65,7 +65,7 @@ int bscan_load_bitstream_file(jtag_core *jc, const fpga_target *t,
* Bytes are MSB-first on the wire; bit-order juggling is internal. * Bytes are MSB-first on the wire; bit-order juggling is internal.
* Follows the quartiq/OpenOCD jtagspi proxy framing. Single-device * Follows the quartiq/OpenOCD jtagspi proxy framing. Single-device
* chain only. Requires a proxy bitstream already loaded (USER1 live). */ * chain only. Requires a proxy bitstream already loaded (USER1 live). */
int bscan_spi_xfer(jtag_core *jc, const fpga_target *t, int bscan_spi_xfer(jtag_core *jc, const jtag_target *t,
const uint8_t *tx, size_t txlen, const uint8_t *tx, size_t txlen,
uint8_t *rx, size_t rxlen); uint8_t *rx, size_t rxlen);

View File

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

View File

@@ -1,78 +0,0 @@
#ifndef _FPGA_H
#define _FPGA_H
/*
* Per-target FPGA descriptor and registry.
*
* Holds the facts that cannot be derived from the BSDL alone:
* - IDCODE pattern to match on the chain
* - private IR opcodes (USER1, CFG_IN, JPROGRAM, …) needed for
* configuration and for the BSCAN proxy bridge (Phase 2.5)
* - path to the BSCAN proxy bitstream
* - per-target caveats (known hardware gotchas)
*
* 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"
typedef enum {
FPGA_FAMILY_UNKNOWN = 0,
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;
/* Programming method — which backend drives this part. */
typedef enum {
FPGA_PROG_NONE = 0, /* no known method */
FPGA_PROG_PROXY_SPI, /* Xilinx external SPI flash via the BSCAN proxy */
FPGA_PROG_SVF, /* play a vendor-exported SVF (Lattice/Microsemi/…) */
} fpga_prog_method;
/* Caveat flags: known hardware gotchas for a part. */
#define FPGA_CAVEAT_CCLK_VIA_STARTUP (1u << 0) /* CCLK not directly drivable in EXTEST */
typedef struct {
const char *name; /* human-readable part name */
unsigned long idcode; /* IDCODE pattern */
unsigned long idcode_mask; /* bits to ignore (typically 0x0FFFFFFF for Xilinx — version masked) */
fpga_family family;
const char *bsdl_filename; /* basename within bsdl_files/ */
int ir_length; /* IR width in bits */
/* Private IR opcodes (0 = N/A for this family).
* For Xilinx, these are read from the BSDL INSTRUCTION_OPCODE block. */
unsigned int ir_cfg_in;
unsigned int ir_user1;
unsigned int ir_jprogram;
unsigned int ir_jstart;
unsigned int ir_jshutdown;
unsigned int ir_isc_disable;
const char *proxy_bitstream; /* path under bscan_proxies/, NULL if not yet available */
unsigned int caveats; /* FPGA_CAVEAT_* flags */
int max_tck_khz; /* max safe JTAG TCK for this part/board, 0 = unspecified */
fpga_prog_method prog; /* programming backend; inferred when omitted */
} fpga_target;
/* 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);
const char * fpga_prog_method_name(fpga_prog_method m);
/* 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

@@ -0,0 +1,9 @@
file(GLOB_RECURSE ALL_SOURCES "*.c")
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)
add_library(program ${ALL_SOURCES})
# program dispatches to these backends; declare the deps so the static
# libs are ordered correctly on the link line (program before svf/arm_debug).
target_link_libraries(program PUBLIC svf arm_debug)

View File

@@ -0,0 +1,36 @@
#include <stddef.h>
#include "program.h"
#include "svf/svf.h"
#include "arm_debug/arm_debug.h"
/* program_log_fn, svf_log_fn and arm_log_fn are the same shape
* (void(*)(void*,int,const char*)); pass the caller's log through. */
int program_dispatch(jtag_core *jc, const jtag_target *t, const char *file,
program_log_fn log, void *user)
{
if (!t) {
if (log) log(user, 1, "no matching target on the chain");
return -1;
}
switch (t->prog) {
case TARGET_PROG_SVF:
return svf_play_file(jc, file, (svf_log_fn)log, user, NULL);
case TARGET_PROG_ARM_FLASH:
return arm_flash_program(jc, t, file, (arm_log_fn)log, user);
case TARGET_PROG_PROXY_SPI:
if (log) log(user, 1,
"proxy_spi: use the flash workflow instead "
"(bscan_load_bitstream, then flash_write / flash_verify)");
return -1;
case TARGET_PROG_NONE:
default:
if (log) log(user, 1, "no programming method known for this target");
return -1;
}
}

View File

@@ -0,0 +1,25 @@
#ifndef _PROGRAM_H
#define _PROGRAM_H
/*
* Programming-backend dispatch.
*
* One entry point routes a target to the right backend by its `prog`
* method (set/inferred in the registry):
* svf -> play the file through the SVF player;
* arm_flash -> ARM RAM-loader flash (not implemented yet);
* proxy_spi -> Xilinx external flash; a one-shot here is multi-step,
* so this points at the flash_* workflow for now.
*
* This is the seam new backends plug into — add a `prog` value + a case.
*/
#include "jtag_core/jtag_core.h"
#include "target/target.h"
typedef void (*program_log_fn)(void *user, int is_error, const char *msg);
int program_dispatch(jtag_core *jc, const jtag_target *t, const char *file,
program_log_fn log, void *user);
#endif

View File

@@ -37,10 +37,11 @@
#include "bsdl_parser/bsdl_loader.h" #include "bsdl_parser/bsdl_loader.h"
#include "os_interface/os_interface.h" #include "os_interface/os_interface.h"
#include "fpga/fpga.h" #include "target/target.h"
#include "probes/probes.h" #include "probes/probes.h"
#include "bscan/bscan.h" #include "bscan/bscan.h"
#include "svf/svf.h" #include "svf/svf.h"
#include "program/program.h"
#include "spi_flash/spi_flash.h" #include "spi_flash/spi_flash.h"
#include "env.h" #include "env.h"
@@ -1603,11 +1604,11 @@ static int autoinit_run(script_ctx *ctx)
static int chain_max_tck_khz(jtag_core *jc) static int chain_max_tck_khz(jtag_core *jc)
{ {
int i, n, cap = 0; int i, n, cap = 0;
const fpga_target *t; const jtag_target *t;
n = jtagcore_get_number_of_devices(jc); n = jtagcore_get_number_of_devices(jc);
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
t = fpga_lookup_by_idcode(jtagcore_get_dev_id(jc, i)); t = target_lookup_by_idcode(jtagcore_get_dev_id(jc, i));
if (t && t->max_tck_khz > 0 && (cap == 0 || t->max_tck_khz < cap)) if (t && t->max_tck_khz > 0 && (cap == 0 || t->max_tck_khz < cap))
cap = t->max_tck_khz; cap = t->max_tck_khz;
} }
@@ -2981,48 +2982,55 @@ static int cmd_get_pins_list(script_ctx *ctx, char *line)
return JTAG_CORE_BAD_PARAMETER; return JTAG_CORE_BAD_PARAMETER;
} }
const char *cmd_fpga_list_help[] = { const char *cmd_target_list_help[] = {
"", "",
"Lists the FPGA targets in the registry (loaded from fpga_registry.yaml).", "Lists the JTAG targets in the registry (loaded from data/targets.yaml).",
""}; ""};
static int cmd_fpga_list(script_ctx *ctx, char *line) static int cmd_target_list(script_ctx *ctx, char *line)
{ {
int i, n; int i, n;
const fpga_target *t; const jtag_target *t;
const char *src; const char *src;
(void)line; (void)line;
n = fpga_get_target_count(); n = target_get_count();
src = fpga_registry_source(); src = target_registry_source();
ctx->script_printf(ctx, MSG_INFO_0, "%d FPGA target(s) registered (from %s):\n", ctx->script_printf(ctx, MSG_INFO_0, "%d target(s) registered (from %s):\n",
n, src ? src : "<no registry loaded>"); 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 = target_get_by_index(i);
ctx->script_printf(ctx, MSG_NONE, ctx->script_printf(ctx, MSG_NONE,
" [%d] IDCODE %.8lX/%.8lX %s (%s)\n", " [%d] IDCODE %.8lX/%.8lX %s (%s, %s, prog=%s)\n",
i, t->idcode, t->idcode_mask, i, t->idcode, t->idcode_mask, t->name,
t->name, fpga_family_name(t->family)); target_kind_name(t->kind), target_family_name(t->family),
target_prog_name(t->prog));
if (t->kind == TARGET_CPU)
ctx->script_printf(ctx, MSG_NONE, ctx->script_printf(ctx, MSG_NONE,
" bsdl=%s ir=%d proxy=%s caveats=0x%x maxtck=%dkHz prog=%s\n", " ram=0x%lX+0x%lX flash=0x%lX+0x%lX ir=%d maxtck=%dkHz\n",
t->bsdl_filename, t->ir_length, t->cpu.ram_base, t->cpu.ram_size,
t->proxy_bitstream ? t->proxy_bitstream : "(none yet)", t->cpu.flash_base, t->cpu.flash_size, t->ir_length, t->max_tck_khz);
t->caveats, t->max_tck_khz, fpga_prog_method_name(t->prog)); else
ctx->script_printf(ctx, MSG_NONE,
" bsdl=%s ir=%d proxy=%s caveats=0x%x maxtck=%dkHz\n",
t->fpga.bsdl_filename ? t->fpga.bsdl_filename : "(none)", t->ir_length,
t->fpga.proxy_bitstream ? t->fpga.proxy_bitstream : "(none)",
t->fpga.caveats, t->max_tck_khz);
} }
return JTAG_CORE_NO_ERROR; return JTAG_CORE_NO_ERROR;
} }
const char *cmd_fpga_info_help[] = { const char *cmd_target_info_help[] = {
"", "",
"Reports, for each device on the JTAG chain, whether its IDCODE", "Reports, for each device on the JTAG chain, whether its IDCODE",
"matches a known FPGA target. Requires jtag_scan or jtag_autoinit first.", "matches a known target. Requires jtag_scan or jtag_autoinit first.",
""}; ""};
static int cmd_fpga_info(script_ctx *ctx, char *line) static int cmd_target_info(script_ctx *ctx, char *line)
{ {
jtag_core *jc; jtag_core *jc;
int i, n; int i, n;
unsigned long idcode; unsigned long idcode;
const fpga_target *t; const jtag_target *t;
(void)line; (void)line;
jc = (jtag_core *)ctx->app_ctx; jc = (jtag_core *)ctx->app_ctx;
@@ -3035,14 +3043,14 @@ static int cmd_fpga_info(script_ctx *ctx, char *line)
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
idcode = jtagcore_get_dev_id(jc, i); idcode = jtagcore_get_dev_id(jc, i);
t = fpga_lookup_by_idcode(idcode); t = target_lookup_by_idcode(idcode);
if (t) { if (t) {
ctx->script_printf(ctx, MSG_INFO_0, ctx->script_printf(ctx, MSG_INFO_0,
"Device %d IDCODE 0x%.8lX -> %s [%s]\n", "Device %d IDCODE 0x%.8lX -> %s [%s, %s]\n",
i, idcode, t->name, fpga_family_name(t->family)); i, idcode, t->name, target_kind_name(t->kind), target_family_name(t->family));
ctx->script_printf(ctx, MSG_NONE, ctx->script_printf(ctx, MSG_NONE,
" prog: %s\n", fpga_prog_method_name(t->prog)); " prog: %s\n", target_prog_name(t->prog));
if (t->caveats & FPGA_CAVEAT_CCLK_VIA_STARTUP) { if (t->kind == TARGET_FPGA && (t->fpga.caveats & TARGET_CAVEAT_CCLK_VIA_STARTUP)) {
ctx->script_printf(ctx, MSG_NONE, ctx->script_printf(ctx, MSG_NONE,
" caveat: CCLK routed via STARTUP primitive (not drivable in EXTEST)\n"); " caveat: CCLK routed via STARTUP primitive (not drivable in EXTEST)\n");
} }
@@ -3127,7 +3135,7 @@ static int cmd_bscan_shift_dr(script_ctx *ctx, char *line)
const char *cmd_bscan_load_bitstream_help[] = { const char *cmd_bscan_load_bitstream_help[] = {
"<device> <path>", "<device> <path>",
"Loads a bitstream (.bit or raw .bin) into device <device> via JPROGRAM/CFG_IN/JSTART.", "Loads a bitstream (.bit or raw .bin) into device <device> via JPROGRAM/CFG_IN/JSTART.",
"Device must be matched in the FPGA registry (run fpga_info to check).", "Device must be matched in the FPGA registry (run target_info to check).",
""}; ""};
static int cmd_bscan_load_bitstream(script_ctx *ctx, char *line) static int cmd_bscan_load_bitstream(script_ctx *ctx, char *line)
{ {
@@ -3135,7 +3143,7 @@ static int cmd_bscan_load_bitstream(script_ctx *ctx, char *line)
char path[DEFAULT_BUFLEN]; char path[DEFAULT_BUFLEN];
int device, ret; int device, ret;
unsigned long idcode; unsigned long idcode;
const fpga_target *t; const jtag_target *t;
jtag_core *jc = (jtag_core *)ctx->app_ctx; jtag_core *jc = (jtag_core *)ctx->app_ctx;
if (get_param(ctx, line, 1, dev_txt) < 0 || get_param(ctx, line, 2, path) < 0) { if (get_param(ctx, line, 1, dev_txt) < 0 || get_param(ctx, line, 2, path) < 0) {
@@ -3149,7 +3157,7 @@ static int cmd_bscan_load_bitstream(script_ctx *ctx, char *line)
return JTAG_CORE_NOT_FOUND; return JTAG_CORE_NOT_FOUND;
} }
idcode = jtagcore_get_dev_id(jc, device); idcode = jtagcore_get_dev_id(jc, device);
t = fpga_lookup_by_idcode(idcode); t = target_lookup_by_idcode(idcode);
if (!t) { if (!t) {
ctx->script_printf(ctx, MSG_ERROR, "Device %d IDCODE 0x%.8lX not in FPGA registry\n", device, idcode); ctx->script_printf(ctx, MSG_ERROR, "Device %d IDCODE 0x%.8lX not in FPGA registry\n", device, idcode);
return JTAG_CORE_NOT_FOUND; return JTAG_CORE_NOT_FOUND;
@@ -3177,7 +3185,7 @@ static int cmd_bscan_jedec(script_ctx *ctx, char *line)
char dev_txt[DEFAULT_BUFLEN]; char dev_txt[DEFAULT_BUFLEN];
int device; int device;
unsigned long idcode; unsigned long idcode;
const fpga_target *t; const jtag_target *t;
uint8_t tx = 0x9F; uint8_t tx = 0x9F;
uint8_t rx[3] = {0}; uint8_t rx[3] = {0};
jtag_core *jc = (jtag_core *)ctx->app_ctx; jtag_core *jc = (jtag_core *)ctx->app_ctx;
@@ -3193,7 +3201,7 @@ static int cmd_bscan_jedec(script_ctx *ctx, char *line)
return JTAG_CORE_NOT_FOUND; return JTAG_CORE_NOT_FOUND;
} }
idcode = jtagcore_get_dev_id(jc, device); idcode = jtagcore_get_dev_id(jc, device);
t = fpga_lookup_by_idcode(idcode); t = target_lookup_by_idcode(idcode);
if (!t) { if (!t) {
ctx->script_printf(ctx, MSG_ERROR, "Device %d IDCODE 0x%.8lX not in FPGA registry\n", device, idcode); ctx->script_printf(ctx, MSG_ERROR, "Device %d IDCODE 0x%.8lX not in FPGA registry\n", device, idcode);
return JTAG_CORE_NOT_FOUND; return JTAG_CORE_NOT_FOUND;
@@ -3214,7 +3222,7 @@ static int cmd_bscan_jedec(script_ctx *ctx, char *line)
/* Adapt the BSCAN proxy to the spi_flash transport callback. */ /* Adapt the BSCAN proxy to the spi_flash transport callback. */
struct flash_proxy_ctx { struct flash_proxy_ctx {
jtag_core *jc; jtag_core *jc;
const fpga_target *t; const jtag_target *t;
}; };
static int flash_proxy_xfer(void *c, const uint8_t *tx, size_t txlen, static int flash_proxy_xfer(void *c, const uint8_t *tx, size_t txlen,
uint8_t *rx, size_t rxlen) uint8_t *rx, size_t rxlen)
@@ -3231,14 +3239,14 @@ static int flash_setup(script_ctx *ctx, int device,
{ {
jtag_core *jc = (jtag_core *)ctx->app_ctx; jtag_core *jc = (jtag_core *)ctx->app_ctx;
unsigned long idcode; unsigned long idcode;
const fpga_target *t; const jtag_target *t;
if (jtagcore_get_number_of_devices(jc) <= 0) { if (jtagcore_get_number_of_devices(jc) <= 0) {
ctx->script_printf(ctx, MSG_ERROR, "No device on the chain. Run jtag_autoinit first.\n"); ctx->script_printf(ctx, MSG_ERROR, "No device on the chain. Run jtag_autoinit first.\n");
return JTAG_CORE_NOT_FOUND; return JTAG_CORE_NOT_FOUND;
} }
idcode = jtagcore_get_dev_id(jc, device); idcode = jtagcore_get_dev_id(jc, device);
t = fpga_lookup_by_idcode(idcode); t = target_lookup_by_idcode(idcode);
if (!t) { if (!t) {
ctx->script_printf(ctx, MSG_ERROR, "Device %d IDCODE 0x%.8lX not in FPGA registry\n", device, idcode); ctx->script_printf(ctx, MSG_ERROR, "Device %d IDCODE 0x%.8lX not in FPGA registry\n", device, idcode);
return JTAG_CORE_NOT_FOUND; return JTAG_CORE_NOT_FOUND;
@@ -3542,6 +3550,42 @@ static int cmd_svf_play(script_ctx *ctx, char *line)
return JTAG_CORE_NO_ERROR; return JTAG_CORE_NO_ERROR;
} }
const char *cmd_program_help[] = {
"<dev>(int) <file>(str)",
"Program a chain device from <file>, dispatching on the target's",
"registry programming method: svf -> play the SVF; proxy_spi -> use",
"the flash workflow (bscan_load_bitstream + flash_write/verify);",
"arm_flash -> ARM RAM-loader flash (not implemented yet).",
"Run jtag_autoinit first so the device is identified.",
""
};
static int cmd_program(script_ctx *ctx, char *line)
{
jtag_core *jc;
char dev_s[32], path[MAX_PATH + 1];
int dev;
unsigned long idcode;
const jtag_target *t;
jc = (jtag_core *)ctx->app_ctx;
if (get_param(ctx, line, 1, dev_s) <= 0 || get_param(ctx, line, 2, path) <= 0) {
ctx->script_printf(ctx, MSG_ERROR, "Usage: program <dev> <file>\n");
return JTAG_CORE_BAD_PARAMETER;
}
dev = (int)strtol(dev_s, NULL, 0);
idcode = jtagcore_get_dev_id(jc, dev);
t = target_lookup_by_idcode(idcode);
if (!t) {
ctx->script_printf(ctx, MSG_ERROR,
"Device %d (IDCODE 0x%.8lX) not in the registry. Run jtag_autoinit.\n",
dev, idcode);
return JTAG_CORE_NOT_FOUND;
}
if (program_dispatch(jc, t, path, svf_log_cb, ctx) < 0)
return JTAG_CORE_ACCESS_ERROR;
return JTAG_CORE_NO_ERROR;
}
cmd_list script_commands_list[] = cmd_list script_commands_list[] =
{ {
{"print", cmd_print, cmd_print_help}, {"print", cmd_print, cmd_print_help},
@@ -3586,8 +3630,8 @@ cmd_list script_commands_list[] =
{"jtag_spi_miso", cmd_set_spi_miso_pin, cmd_set_spi_miso_pin_help}, {"jtag_spi_miso", cmd_set_spi_miso_pin, cmd_set_spi_miso_pin_help},
{"jtag_spi_clk", cmd_set_spi_clk_pin, cmd_set_spi_clk_pin_help}, {"jtag_spi_clk", cmd_set_spi_clk_pin, cmd_set_spi_clk_pin_help},
{"jtag_spi_xfer", cmd_spi_rd_wr, cmd_spi_rd_wr_help}, {"jtag_spi_xfer", cmd_spi_rd_wr, cmd_spi_rd_wr_help},
{"fpga_list", cmd_fpga_list, cmd_fpga_list_help}, {"target_list", cmd_target_list, cmd_target_list_help},
{"fpga_info", cmd_fpga_info, cmd_fpga_info_help}, {"target_info", cmd_target_info, cmd_target_info_help},
{"bscan_set_ir", cmd_bscan_set_ir, cmd_bscan_set_ir_help}, {"bscan_set_ir", cmd_bscan_set_ir, cmd_bscan_set_ir_help},
{"bscan_shift_dr", cmd_bscan_shift_dr, cmd_bscan_shift_dr_help}, {"bscan_shift_dr", cmd_bscan_shift_dr, cmd_bscan_shift_dr_help},
{"bscan_load_bitstream", cmd_bscan_load_bitstream, cmd_bscan_load_bitstream_help}, {"bscan_load_bitstream", cmd_bscan_load_bitstream, cmd_bscan_load_bitstream_help},
@@ -3598,6 +3642,7 @@ cmd_list script_commands_list[] =
{"flash_write", cmd_flash_write, cmd_flash_write_help}, {"flash_write", cmd_flash_write, cmd_flash_write_help},
{"flash_verify", cmd_flash_verify, cmd_flash_verify_help}, {"flash_verify", cmd_flash_verify, cmd_flash_verify_help},
{"svf_play", cmd_svf_play, cmd_svf_play_help}, {"svf_play", cmd_svf_play, cmd_svf_play_help},
{"program", cmd_program, cmd_program_help},
{0, 0}}; {0, 0}};
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////

View File

@@ -4,11 +4,11 @@ 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(target ${ALL_SOURCES})
# The registry is parsed from YAML at runtime via libyaml (the canonical # The registry is parsed from YAML at runtime via libyaml (the canonical
# C YAML parser). Found through pkg-config; PUBLIC so the executable that # C YAML parser). Found through pkg-config; PUBLIC so the executable that
# links fpga picks up the dependency transitively. # links target picks up the dependency transitively.
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
pkg_check_modules(YAML REQUIRED IMPORTED_TARGET yaml-0.1) pkg_check_modules(YAML REQUIRED IMPORTED_TARGET yaml-0.1)
target_link_libraries(fpga PUBLIC PkgConfig::YAML) target_link_libraries(target PUBLIC PkgConfig::YAML)

344
src/modules/target/target.c Normal file
View File

@@ -0,0 +1,344 @@
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <yaml.h>
#include "target.h"
/*
* JTAG target registry loaded at runtime from a YAML file. Lookup order:
* 1. $BS_TARGETS (if set and non-empty)
* 2. "data/targets.yaml" relative to the current directory
* — same CWD-relative convention as data/bsdl_files/, so run bs_explorer
* from the repository root.
*
* Loaded lazily on first access, cached for the process lifetime. The
* schema is one flat mapping per entry under a top-level `targets:`
* sequence; `kind` (fpga|cpu) selects which fields apply. See
* data/targets.yaml.
*/
#define DEFAULT_REGISTRY_FILE "data/targets.yaml"
static jtag_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 target_kind parse_kind(const char *s)
{
if (s && !strcmp(s, "cpu")) return TARGET_CPU;
return TARGET_FPGA; /* default / "fpga" */
}
static target_family parse_family(const char *s)
{
if (!s) return TARGET_FAMILY_UNKNOWN;
if (!strcmp(s, "xilinx_7")) return TARGET_FAMILY_XILINX_7;
if (!strcmp(s, "xilinx_us")) return TARGET_FAMILY_XILINX_US;
if (!strcmp(s, "xilinx_usp")) return TARGET_FAMILY_XILINX_USP;
if (!strcmp(s, "microsemi_igloo2")) return TARGET_FAMILY_MICROSEMI_IGLOO2;
if (!strcmp(s, "microsemi_smartfusion2")) return TARGET_FAMILY_MICROSEMI_SMARTFUSION2;
if (!strcmp(s, "lattice_machxo2")) return TARGET_FAMILY_LATTICE_MACHXO2;
if (!strcmp(s, "lattice_machxo3")) return TARGET_FAMILY_LATTICE_MACHXO3;
if (!strcmp(s, "arm7")) return TARGET_FAMILY_ARM7;
if (!strcmp(s, "arm9")) return TARGET_FAMILY_ARM9;
fprintf(stderr, "target: unknown family '%s'\n", s);
return TARGET_FAMILY_UNKNOWN;
}
static target_prog parse_prog(const char *s)
{
if (!s) return TARGET_PROG_NONE;
if (!strcmp(s, "proxy_spi")) return TARGET_PROG_PROXY_SPI;
if (!strcmp(s, "svf")) return TARGET_PROG_SVF;
if (!strcmp(s, "arm_flash")) return TARGET_PROG_ARM_FLASH;
if (!strcmp(s, "none")) return TARGET_PROG_NONE;
fprintf(stderr, "target: unknown prog method '%s'\n", s);
return TARGET_PROG_NONE;
}
static target_debug parse_debug(const char *s)
{
if (!s) return TARGET_DEBUG_NONE;
if (!strcmp(s, "embeddedice")) return TARGET_DEBUG_EMBEDDEDICE;
if (!strcmp(s, "none")) return TARGET_DEBUG_NONE;
fprintf(stderr, "target: unknown debug iface '%s'\n", s);
return TARGET_DEBUG_NONE;
}
/* Guess the programming method when the entry doesn't state one. */
static target_prog infer_prog(const jtag_target *t)
{
if (t->kind == TARGET_CPU)
return (t->cpu.debug != TARGET_DEBUG_NONE) ? TARGET_PROG_ARM_FLASH : TARGET_PROG_NONE;
if (t->fpga.proxy_bitstream) return TARGET_PROG_PROXY_SPI;
switch (t->family) {
case TARGET_FAMILY_MICROSEMI_IGLOO2:
case TARGET_FAMILY_MICROSEMI_SMARTFUSION2:
case TARGET_FAMILY_LATTICE_MACHXO2:
case TARGET_FAMILY_LATTICE_MACHXO3: return TARGET_PROG_SVF;
default: return TARGET_PROG_NONE;
}
}
static unsigned int parse_caveats(const char *s)
{
unsigned int flags = 0;
char buf[256];
char *tok;
if (!s) return 0;
strncpy(buf, s, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
for (tok = strtok(buf, " ,|"); tok; tok = strtok(NULL, " ,|")) {
if (!strcmp(tok, "cclk_via_startup"))
flags |= TARGET_CAVEAT_CCLK_VIA_STARTUP;
else
fprintf(stderr, "target: unknown caveat '%s'\n", tok);
}
return flags;
}
/* Apply one "key: value" pair (both scalars) to the entry being built.
* FPGA keys route into t->fpga, CPU keys into t->cpu; the rest is common. */
static void set_field(jtag_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, "kind")) t->kind = parse_kind(val);
else if (!strcmp(key, "family")) t->family = parse_family(val);
else if (!strcmp(key, "ir_length")) t->ir_length = (int)strtol(val, NULL, 0);
else if (!strcmp(key, "prog")) t->prog = parse_prog(val);
else if (!strcmp(key, "max_tck_khz")) t->max_tck_khz = (int)strtol(val, NULL, 0);
/* FPGA */
else if (!strcmp(key, "bsdl")) { free((void *)t->fpga.bsdl_filename); t->fpga.bsdl_filename = xstrdup(val); }
else if (!strcmp(key, "ir_cfg_in")) t->fpga.ir_cfg_in = (unsigned int)strtoul(val, NULL, 0);
else if (!strcmp(key, "ir_user1")) t->fpga.ir_user1 = (unsigned int)strtoul(val, NULL, 0);
else if (!strcmp(key, "ir_jprogram")) t->fpga.ir_jprogram = (unsigned int)strtoul(val, NULL, 0);
else if (!strcmp(key, "ir_jstart")) t->fpga.ir_jstart = (unsigned int)strtoul(val, NULL, 0);
else if (!strcmp(key, "ir_jshutdown")) t->fpga.ir_jshutdown = (unsigned int)strtoul(val, NULL, 0);
else if (!strcmp(key, "ir_isc_disable")) t->fpga.ir_isc_disable = (unsigned int)strtoul(val, NULL, 0);
else if (!strcmp(key, "proxy_bitstream")) { free((void *)t->fpga.proxy_bitstream); t->fpga.proxy_bitstream = xstrdup(val); }
else if (!strcmp(key, "caveats")) t->fpga.caveats = parse_caveats(val);
/* CPU */
else if (!strcmp(key, "debug")) t->cpu.debug = parse_debug(val);
else if (!strcmp(key, "ram_base")) t->cpu.ram_base = strtoul(val, NULL, 0);
else if (!strcmp(key, "ram_size")) t->cpu.ram_size = strtoul(val, NULL, 0);
else if (!strcmp(key, "flash_base")) t->cpu.flash_base = strtoul(val, NULL, 0);
else if (!strcmp(key, "flash_size")) t->cpu.flash_size = strtoul(val, NULL, 0);
else fprintf(stderr, "target: unknown key '%s'\n", key);
}
static int commit_entry(const jtag_target *cur)
{
jtag_target *tmp = (jtag_target *)realloc(g_registry,
(g_count + 1) * sizeof(*g_registry));
if (!tmp) {
fprintf(stderr, "target: out of memory loading registry\n");
return -1;
}
g_registry = tmp;
g_registry[g_count] = *cur;
if (g_registry[g_count].prog == TARGET_PROG_NONE)
g_registry[g_count].prog = infer_prog(&g_registry[g_count]);
g_count++;
return 0;
}
/* Walk the YAML event stream. A mapping with a single `targets:` 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, rc = 0;
int in_seq = 0; /* inside the targets: sequence */
int in_item = 0; /* inside one entry mapping */
int expect_seq = 0; /* last top-level key was "targets" */
char *pending_key = NULL;
jtag_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, "target: 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_seq) {
if (!strcmp(v, "targets"))
expect_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_seq && !in_seq) { in_seq = 1; expect_seq = 0; }
break;
case YAML_SEQUENCE_END_EVENT:
if (in_seq && !in_item) in_seq = 0;
break;
case YAML_MAPPING_START_EVENT:
if (in_seq && !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_TARGETS");
if (!path || !*path) path = DEFAULT_REGISTRY_FILE;
load_registry(path);
if (g_count > 0) {
strncpy(g_source, path, sizeof(g_source) - 1);
g_source[sizeof(g_source) - 1] = '\0';
} else {
fprintf(stderr,
"target: no targets loaded from '%s' "
"(set BS_TARGETS, or run from the directory containing it)\n",
path);
}
}
/* ---- public API -------------------------------------------------- */
int target_get_count(void)
{
ensure_loaded();
return g_count;
}
const jtag_target *target_get_by_index(int index)
{
ensure_loaded();
if (index < 0 || index >= g_count) return NULL;
return &g_registry[index];
}
const jtag_target *target_lookup_by_idcode(unsigned long idcode)
{
int i;
ensure_loaded();
for (i = 0; i < g_count; i++) {
const jtag_target *t = &g_registry[i];
if ((idcode & t->idcode_mask) == (t->idcode & t->idcode_mask))
return t;
}
return NULL;
}
const char *target_family_name(target_family f)
{
switch (f) {
case TARGET_FAMILY_XILINX_7: return "Xilinx 7-Series";
case TARGET_FAMILY_XILINX_US: return "Xilinx UltraScale";
case TARGET_FAMILY_XILINX_USP: return "Xilinx UltraScale+";
case TARGET_FAMILY_MICROSEMI_IGLOO2: return "Microsemi IGLOO2";
case TARGET_FAMILY_MICROSEMI_SMARTFUSION2: return "Microsemi SmartFusion2";
case TARGET_FAMILY_LATTICE_MACHXO2: return "Lattice MachXO2";
case TARGET_FAMILY_LATTICE_MACHXO3: return "Lattice MachXO3";
case TARGET_FAMILY_ARM7: return "ARM7";
case TARGET_FAMILY_ARM9: return "ARM9";
case TARGET_FAMILY_UNKNOWN:
default: return "Unknown";
}
}
const char *target_prog_name(target_prog p)
{
switch (p) {
case TARGET_PROG_PROXY_SPI: return "proxy_spi";
case TARGET_PROG_SVF: return "svf";
case TARGET_PROG_ARM_FLASH: return "arm_flash";
case TARGET_PROG_NONE:
default: return "none";
}
}
const char *target_kind_name(target_kind k)
{
switch (k) {
case TARGET_CPU: return "cpu";
case TARGET_FPGA:
default: return "fpga";
}
}
const char *target_registry_source(void)
{
ensure_loaded();
return g_source[0] ? g_source : NULL;
}

112
src/modules/target/target.h Normal file
View File

@@ -0,0 +1,112 @@
#ifndef _TARGET_H
#define _TARGET_H
/*
* JTAG target registry — FPGAs and CPUs.
*
* A `jtag_target` describes one programmable device on the chain: the
* facts that can't be derived from the IDCODE/BSDL alone. Shared fields
* (IDCODE, IR length, family, programming method, max TCK) sit at the
* top; kind-specific facts live in the `fpga` or `cpu` sub-struct
* selected by `kind`.
*
* - FPGA: BSDL, BSCAN proxy bitstream, private config IR opcodes
* (CFG_IN/USER1/JPROGRAM/…), caveats.
* - CPU: debug interface (EmbeddedICE for ARM7/9), work-RAM for a
* flash loader, on-chip flash region. (The CPU programming backend
* itself is not implemented yet — see arm_debug/ and the design note.)
*
* The registry is loaded at runtime from data/targets.yaml (libyaml).
* Adding a target = one flat YAML entry (+ a .bsd for FPGAs). The
* `kind` selects which fields are meaningful.
*/
#include "jtag_core/jtag_core.h"
typedef enum {
TARGET_FPGA = 0,
TARGET_CPU,
} target_kind;
typedef enum {
TARGET_FAMILY_UNKNOWN = 0,
TARGET_FAMILY_XILINX_7,
TARGET_FAMILY_XILINX_US,
TARGET_FAMILY_XILINX_USP,
TARGET_FAMILY_MICROSEMI_IGLOO2,
TARGET_FAMILY_MICROSEMI_SMARTFUSION2,
TARGET_FAMILY_LATTICE_MACHXO2,
TARGET_FAMILY_LATTICE_MACHXO3,
TARGET_FAMILY_ARM7, /* ARM7TDMI(-S): LPC2xxx, AT91SAM7, … */
TARGET_FAMILY_ARM9, /* ARM9: also EmbeddedICE */
} target_family;
/* Programming backend — which handler drives this target (dispatched in
* the program/ module by this tag). */
typedef enum {
TARGET_PROG_NONE = 0, /* no known method */
TARGET_PROG_PROXY_SPI, /* Xilinx external SPI flash via the BSCAN proxy */
TARGET_PROG_SVF, /* play a vendor-exported SVF (Lattice/Microsemi/…) */
TARGET_PROG_ARM_FLASH, /* halt over JTAG debug, RAM loader -> on-chip flash (not implemented yet) */
} target_prog;
/* CPU debug transport (over JTAG). */
typedef enum {
TARGET_DEBUG_NONE = 0,
TARGET_DEBUG_EMBEDDEDICE, /* ARM7/ARM9 */
} target_debug;
/* Caveat flags: known hardware gotchas for a target. */
#define TARGET_CAVEAT_CCLK_VIA_STARTUP (1u << 0) /* CCLK not directly drivable in EXTEST */
/* FPGA-specific facts (valid when kind == TARGET_FPGA). */
typedef struct {
const char *bsdl_filename; /* basename within data/bsdl_files/ */
/* Private IR opcodes (0 = N/A). For Xilinx, from the BSDL. */
unsigned int ir_cfg_in;
unsigned int ir_user1;
unsigned int ir_jprogram;
unsigned int ir_jstart;
unsigned int ir_jshutdown;
unsigned int ir_isc_disable;
const char *proxy_bitstream; /* basename under data/bscan_proxies/, NULL if none */
unsigned int caveats; /* TARGET_CAVEAT_* flags */
} target_fpga;
/* CPU-specific facts (valid when kind == TARGET_CPU). */
typedef struct {
target_debug debug; /* debug transport */
unsigned long ram_base; /* work-RAM for the flash loader */
unsigned long ram_size;
unsigned long flash_base; /* on-chip flash region */
unsigned long flash_size;
} target_cpu;
typedef struct {
const char *name; /* human-readable part name */
unsigned long idcode; /* IDCODE pattern */
unsigned long idcode_mask; /* bits compared (e.g. 0x0FFFFFFF masks the version nibble) */
target_kind kind;
target_family family;
int ir_length; /* IR width in bits (chain property, all kinds) */
target_prog prog; /* programming backend; inferred when omitted */
int max_tck_khz; /* max safe JTAG TCK, 0 = unspecified */
target_fpga fpga; /* kind == TARGET_FPGA */
target_cpu cpu; /* kind == TARGET_CPU */
} jtag_target;
/* Registry access. data/targets.yaml is loaded lazily on first call and
* cached for the process lifetime. */
int target_get_count(void);
const jtag_target * target_get_by_index(int index);
const jtag_target * target_lookup_by_idcode(unsigned long idcode);
const char * target_family_name(target_family f);
const char * target_prog_name(target_prog p);
const char * target_kind_name(target_kind k);
/* Path the registry was loaded from, or NULL if nothing loaded. */
const char * target_registry_source(void);
#endif