From 9ad776268e23e3a6adf8481b29ee322e69884c63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Sun, 24 May 2026 15:33:58 +0200 Subject: [PATCH] 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 ` 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 all work). Co-Authored-By: Claude Opus 4.7 --- CLAUDE.md | 110 +++++-- README.md | 26 +- data/fpga_registry.yaml | 80 ----- data/probes.yaml | 9 +- data/targets.yaml | 102 ++++++ doc/tutorial.md | 61 +++- src/modules/arm_debug/CMakeLists.txt | 5 + src/modules/arm_debug/arm_debug.c | 56 ++++ src/modules/arm_debug/arm_debug.h | 38 +++ src/modules/bscan/bscan.c | 18 +- src/modules/bscan/bscan.h | 8 +- src/modules/fpga/fpga.c | 311 ------------------ src/modules/fpga/fpga.h | 78 ----- src/modules/program/CMakeLists.txt | 9 + src/modules/program/program.c | 36 ++ src/modules/program/program.h | 25 ++ src/modules/script/script.c | 121 ++++--- src/modules/{fpga => target}/CMakeLists.txt | 6 +- src/modules/target/target.c | 344 ++++++++++++++++++++ src/modules/target/target.h | 112 +++++++ 20 files changed, 973 insertions(+), 582 deletions(-) delete mode 100644 data/fpga_registry.yaml create mode 100644 data/targets.yaml create mode 100644 src/modules/arm_debug/CMakeLists.txt create mode 100644 src/modules/arm_debug/arm_debug.c create mode 100644 src/modules/arm_debug/arm_debug.h delete mode 100644 src/modules/fpga/fpga.c delete mode 100644 src/modules/fpga/fpga.h create mode 100644 src/modules/program/CMakeLists.txt create mode 100644 src/modules/program/program.c create mode 100644 src/modules/program/program.h rename src/modules/{fpga => target}/CMakeLists.txt (72%) create mode 100644 src/modules/target/target.c create mode 100644 src/modules/target/target.h diff --git a/CLAUDE.md b/CLAUDE.md index 36e9aae..50846f0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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. The Viveris library itself lives unchanged in `src/modules/`. Everything -new is in `src/bs/` (the REPL) and the project modules (`fpga/`, `bscan/`, -`spi_flash/`, `svf/`, `probes/`) sitting alongside the Viveris ones. +new is in `src/bs/` (the REPL) and the project modules (`target/`, +`bscan/`, `spi_flash/`, `svf/`, `probes/`, `program/`, `arm_debug/`) +sitting alongside the Viveris ones. ## Architecture @@ -38,14 +39,16 @@ src/ — code + libs — ├── os_interface/ Portable fs/network wrappers └── natsort/ Natural pin-name sorting — 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/ │ idle_cycles) + BSCAN proxy (bitstream load, SPI-over-USER1) ├── spi_flash/ SPI NOR chip DB + read/erase/program/verify over a callback ├── 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 — -├── 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) ├── bsdl_files/ BSDL files for target FPGAs ├── 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 | |-------|--------|--------|---------| | 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. | | 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. | | 5 | `probes/` + JTAG-link | **done** | `data/probes.yaml` probe-config profiles (`jtag_open `, `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 ` 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 the next. Don't break the validated path @@ -95,25 +99,25 @@ internally — the `STARTUPE3` problem disappears). Realistic throughput 50–200 KB/s, so ~10–40 min for 128 MB. This is what Vivado, OpenOCD's `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 -derived from the BSDL alone: -- `idcode` + `idcode_mask` — match the device on the chain -- `bsdl_filename` — BSDL to auto-load -- `cfg_in_ir_code`, `user1_ir_code`, `jprogram_ir_code` — Xilinx-specific - private IR opcodes (read from BSDL when available) -- `proxy_bitstream_path` — path to the BSCAN proxy `.bit` for this part -- `caveats` — flags for known hardware gotchas (e.g. CCLK via STARTUPE3) +The `jtag_target` struct (`target/target.h`) holds the per-target facts +that can't be derived from the IDCODE/BSDL alone. A `kind` (fpga|cpu) +selects a sub-struct; the rest is shared: +- common — `idcode`+`idcode_mask` (chain match), `family`, `ir_length`, + `prog` (programming backend), `max_tck_khz` +- `fpga` sub-struct — `bsdl_filename`, the Xilinx config IR opcodes + (`cfg_in`/`user1`/`jprogram`/…), `proxy_bitstream`, `caveats` +- `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 -root), parsed via libyaml — no longer a compile-time array. It is looked -up CWD-relative (like `data/bsdl_files/`), overridable with -`$BS_FPGA_REGISTRY`, and loaded lazily on first access. Adding a part = -one YAML entry + its `.bsd` in `data/bsdl_files/` + (optionally) its proxy -`.bit` in `data/bscan_proxies/` — no rebuild. The `family` field accepts -`xilinx_*`, `microsemi_igloo2/smartfusion2`, `lattice_machxo2/3` -(enum in `fpga.h`). +Registry is a **runtime YAML file** (`data/targets.yaml`), parsed via +libyaml — looked up CWD-relative (like `data/bsdl_files/`), overridable +with `$BS_TARGETS`, loaded lazily. Adding a target = one flat YAML entry +(+ a `.bsd`/proxy for FPGAs) — no rebuild. `family` accepts `xilinx_*`, +`microsemi_*`, `lattice_*`, `arm7`/`arm9`; `prog` is +`proxy_spi`/`svf`/`arm_flash`/`none` (inferred when omitted). Enums in +`target.h`. ### 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` | | **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 @@ -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 needed. This also dissolves the chicken-and-egg: a conservative link 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`). ### Phasing @@ -213,7 +217,7 @@ fact bounded by both the probe and the board/device. still TODO. - **C (done)** — `prog` method tag (`proxy_spi`/`svf`/`none`) on each 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 neutral `JTAG_RTCK` (mirrored to `PROBE_FTDI_JTAG_ENABLE_RTCK`, 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. 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. ## 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 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 ` 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) 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 -`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 -`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 `-DBS_ENABLE_DIGILENT=OFF`). To actually use such a probe, install the diff --git a/README.md b/README.md index 5280eb6..235afbc 100644 --- a/README.md +++ b/README.md @@ -18,16 +18,18 @@ library by Viveris (LGPL). - JTAG chain detection through FTDI / J-Link / Linux GPIO / Digilent SMT2 probes: OK - Automatic BSDL loading by IDCODE: 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 - 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) - SVF player (`svf_play`) — program any device from a vendor-exported SVF: OK (single-device subset) +- `program ` — 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 (`xcku15p_ffve1517.bsd`), Kintex UltraScale KU040 (`xcku040_ffva1156.bsd`), 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). ## Dependencies @@ -76,8 +78,8 @@ are looked up relative to the current directory: full list. Loaded at startup. - `data/probes.yaml` — probe-config profiles, applied with `jtag_open ` (`$BS_PROBES` overrides the path). -- `data/fpga_registry.yaml` — the FPGA target registry - (`$BS_FPGA_REGISTRY` overrides the path). +- `data/targets.yaml` — the FPGA target registry + (`$BS_TARGETS` overrides the path). - `data/bsdl_files/`, `data/bscan_proxies/` — BSDLs and proxy bitstreams. ## REPL @@ -100,7 +102,7 @@ bs_explorer> jtag_profiles # available profiles bs_explorer> jtag_open 0 # or: jtag_open 0 # 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) 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` | | 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` | -| 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` | | 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` | Use `help ` for per-command help. @@ -148,6 +150,8 @@ Use `help ` for per-command help. Microsemi eval kits, which needs ADBUS4 left high-Z) are handled by a **probe profile** in `data/probes.yaml`: `jtag_profiles` lists them, `jtag_open ` 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** - **Linux GPIO** (sysfs; deprecated on recent kernels, libgpiod migration TBD) - **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 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). ## Repository layout @@ -180,17 +184,19 @@ src/ — code + libs — ├── bsdl_parser/ .bsd loader ├── bus_over_jtag/ SPI / I²C / MDIO / parallel mem bit-bang (EXTEST) ├── drivers/ FTDI, J-Link, Linux GPIO, LPT, Digilent (optional) - ├── fpga/ 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) ├── spi_flash/ SPI NOR chip database + read/erase/program/verify ├── svf/ SVF player (program from a vendor-exported SVF) ├── 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 ├── config/ Built-in config.script ├── os_interface/ Portable fs/network wrappers └── natsort/ Natural-order pin-name sorting 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) ├── bsdl_files/ BSDL files for target FPGAs ├── bscan_proxies/ BSCAN proxy bitstreams (MIT, from quartiq) diff --git a/data/fpga_registry.yaml b/data/fpga_registry.yaml deleted file mode 100644 index cd81e9b..0000000 --- a/data/fpga_registry.yaml +++ /dev/null @@ -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 diff --git a/data/probes.yaml b/data/probes.yaml index 3191551..3fc4638 100644 --- a/data/probes.yaml +++ b/data/probes.yaml @@ -1,6 +1,6 @@ # 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 # root), or via $BS_PROBES. # @@ -29,3 +29,10 @@ profiles: # Plain FT2232H probe: nothing to override beyond the defaults. 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: {} diff --git a/data/targets.yaml b/data/targets.yaml new file mode 100644 index 0000000..fc704e4 --- /dev/null +++ b/data/targets.yaml @@ -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 diff --git a/doc/tutorial.md b/doc/tutorial.md index 1db6437..af21d1c 100644 --- a/doc/tutorial.md +++ b/doc/tutorial.md @@ -9,7 +9,7 @@ paths and the tutorial covers both: - **any other part** (Lattice, Microsemi, …), by playing a 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. ## Prerequisites @@ -19,7 +19,7 @@ only the IDCODE and BSDL filename change. `src/libs/libftd2xx/`). - The target's BSDL in `data/bsdl_files/` (KU15P: `xcku15p_ffve1517.bsd` is 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. - 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. @@ -34,7 +34,7 @@ README and the `Digilent SMT2` block in `CLAUDE.md` for the why. ```sh 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 ``` @@ -121,11 +121,11 @@ mis-wired. Power-cycle and re-check the harness before going further. ## 3. Identify the FPGA against the registry -`fpga_info` walks the chain and matches each IDCODE against the -registry in `data/fpga_registry.yaml`: +`target_info` walks the chain and matches each IDCODE against the +registry in `data/targets.yaml`: ``` -bs_explorer> fpga_info +bs_explorer> target_info Device 0 IDCODE 0x04A56093 -> Xilinx Kintex UltraScale+ XCKU15P [Xilinx UltraScale+] prog: proxy_spi 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 [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) @@ -216,7 +216,7 @@ way; you'd be there for weeks. ## 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 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 @@ -247,7 +247,7 @@ masks them off. ### 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 | |-----|-----------|---------| @@ -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) | — | | `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 - 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 that change how the tool must drive the part**. It is *not* a free-text note — each flag is something the code (or you) can branch on. Omit the -field when the part has none. `fpga_info` prints any flag that is set, +field when the part has none. `target_info` prints any flag that is set, as a human-readable line. 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)` in `fpga.h`, teach `parse_caveats()` in `fpga.c` its YAML name, use that name in the YAML, and (if it should be visible) print it in -`cmd_fpga_info` in `script.c`. +`cmd_target_info` in `script.c`. ### d. Verify — no rebuild @@ -319,12 +319,12 @@ the repo root and check: ```sh ./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> 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) @@ -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 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). ### 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_autoinit # fpga_info should show 'prog: svf' +bs_explorer> jtag_autoinit # target_info should show 'prog: svf' bs_explorer> svf_play design.svf ... 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 small IGLOO2 parts). `bs_explorer` only *plays* it, which is fully open. +## One command: `program` + +Rather than remember which backend a part uses, `program ` +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 | 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. | | All IDCODEs read `0xFFFFFFFF` | TDO floats high — broken TDO link, wrong voltage reference, or a Digilent SMT2 module being driven via raw FTDI MPSSE (use the Digilent backend instead). | | All IDCODEs read `0x00000000` | TDO tied low or no clock reaching the target. | -| `fpga_info` says "not in registry" | Add 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). | | `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. | diff --git a/src/modules/arm_debug/CMakeLists.txt b/src/modules/arm_debug/CMakeLists.txt new file mode 100644 index 0000000..482ab5a --- /dev/null +++ b/src/modules/arm_debug/CMakeLists.txt @@ -0,0 +1,5 @@ +file(GLOB_RECURSE ALL_SOURCES "*.c") + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_library(arm_debug ${ALL_SOURCES}) diff --git a/src/modules/arm_debug/arm_debug.c b/src/modules/arm_debug/arm_debug.c new file mode 100644 index 0000000..b850281 --- /dev/null +++ b/src/modules/arm_debug/arm_debug.c @@ -0,0 +1,56 @@ +#include + +#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; +} diff --git a/src/modules/arm_debug/arm_debug.h b/src/modules/arm_debug/arm_debug.h new file mode 100644 index 0000000..f6949b3 --- /dev/null +++ b/src/modules/arm_debug/arm_debug.h @@ -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 diff --git a/src/modules/bscan/bscan.c b/src/modules/bscan/bscan.c index 0f0e1a4..86df8de 100644 --- a/src/modules/bscan/bscan.c +++ b/src/modules/bscan/bscan.c @@ -206,7 +206,7 @@ static uint8_t reverse_bits(uint8_t 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) { uint8_t *reversed; @@ -214,7 +214,7 @@ int bscan_load_bitstream(jtag_core *jc, const fpga_target *t, size_t i; 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. */ 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 * to wait for INIT_B to go high before CFG_IN. * 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); /* 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 * (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 * 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); /* 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; } -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) { 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. */ #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, uint8_t *rx, size_t rxlen) { @@ -356,7 +356,7 @@ int bscan_spi_xfer(jtag_core *jc, const fpga_target *t, size_t i; 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 (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) */ } - 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) { free(dr_out); free(dr_in); return -1; diff --git a/src/modules/bscan/bscan.h b/src/modules/bscan/bscan.h index 1d40cc1..325a42e 100644 --- a/src/modules/bscan/bscan.h +++ b/src/modules/bscan/bscan.h @@ -24,7 +24,7 @@ #include #include "jtag_core/jtag_core.h" -#include "fpga/fpga.h" +#include "target/target.h" /* --- 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 * FPGA via JPROGRAM -> CFG_IN -> shift -> JSTART. Bit-reverses each * 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); /* Convenience wrapper: read a file and load it. Detects the Xilinx * .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); /* 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. * Follows the quartiq/OpenOCD jtagspi proxy framing. Single-device * 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, uint8_t *rx, size_t rxlen); diff --git a/src/modules/fpga/fpga.c b/src/modules/fpga/fpga.c deleted file mode 100644 index f0fa5eb..0000000 --- a/src/modules/fpga/fpga.c +++ /dev/null @@ -1,311 +0,0 @@ -#include -#include -#include -#include - -#include - -#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; -} diff --git a/src/modules/fpga/fpga.h b/src/modules/fpga/fpga.h deleted file mode 100644 index 4dfa316..0000000 --- a/src/modules/fpga/fpga.h +++ /dev/null @@ -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 diff --git a/src/modules/program/CMakeLists.txt b/src/modules/program/CMakeLists.txt new file mode 100644 index 0000000..ce33366 --- /dev/null +++ b/src/modules/program/CMakeLists.txt @@ -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) diff --git a/src/modules/program/program.c b/src/modules/program/program.c new file mode 100644 index 0000000..cac2b7a --- /dev/null +++ b/src/modules/program/program.c @@ -0,0 +1,36 @@ +#include + +#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; + } +} diff --git a/src/modules/program/program.h b/src/modules/program/program.h new file mode 100644 index 0000000..0ecdb39 --- /dev/null +++ b/src/modules/program/program.h @@ -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 diff --git a/src/modules/script/script.c b/src/modules/script/script.c index 54e0244..4bfb409 100644 --- a/src/modules/script/script.c +++ b/src/modules/script/script.c @@ -37,10 +37,11 @@ #include "bsdl_parser/bsdl_loader.h" #include "os_interface/os_interface.h" -#include "fpga/fpga.h" +#include "target/target.h" #include "probes/probes.h" #include "bscan/bscan.h" #include "svf/svf.h" +#include "program/program.h" #include "spi_flash/spi_flash.h" #include "env.h" @@ -1603,11 +1604,11 @@ static int autoinit_run(script_ctx *ctx) static int chain_max_tck_khz(jtag_core *jc) { int i, n, cap = 0; - const fpga_target *t; + const jtag_target *t; n = jtagcore_get_number_of_devices(jc); 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)) 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; } -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; - const fpga_target *t; + const jtag_target *t; const char *src; (void)line; - n = fpga_get_target_count(); - src = fpga_registry_source(); - ctx->script_printf(ctx, MSG_INFO_0, "%d FPGA target(s) registered (from %s):\n", + n = target_get_count(); + src = target_registry_source(); + ctx->script_printf(ctx, MSG_INFO_0, "%d target(s) registered (from %s):\n", n, src ? src : ""); 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, - " [%d] IDCODE %.8lX/%.8lX %s (%s)\n", - i, t->idcode, t->idcode_mask, - t->name, fpga_family_name(t->family)); - ctx->script_printf(ctx, MSG_NONE, - " bsdl=%s ir=%d proxy=%s caveats=0x%x maxtck=%dkHz prog=%s\n", - t->bsdl_filename, t->ir_length, - t->proxy_bitstream ? t->proxy_bitstream : "(none yet)", - t->caveats, t->max_tck_khz, fpga_prog_method_name(t->prog)); + " [%d] IDCODE %.8lX/%.8lX %s (%s, %s, prog=%s)\n", + i, t->idcode, t->idcode_mask, t->name, + 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, + " ram=0x%lX+0x%lX flash=0x%lX+0x%lX ir=%d maxtck=%dkHz\n", + t->cpu.ram_base, t->cpu.ram_size, + t->cpu.flash_base, t->cpu.flash_size, t->ir_length, t->max_tck_khz); + 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; } -const char *cmd_fpga_info_help[] = { +const char *cmd_target_info_help[] = { "", "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; int i, n; unsigned long idcode; - const fpga_target *t; + const jtag_target *t; (void)line; 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++) { idcode = jtagcore_get_dev_id(jc, i); - t = fpga_lookup_by_idcode(idcode); + t = target_lookup_by_idcode(idcode); if (t) { ctx->script_printf(ctx, MSG_INFO_0, - "Device %d IDCODE 0x%.8lX -> %s [%s]\n", - i, idcode, t->name, fpga_family_name(t->family)); + "Device %d IDCODE 0x%.8lX -> %s [%s, %s]\n", + i, idcode, t->name, target_kind_name(t->kind), target_family_name(t->family)); ctx->script_printf(ctx, MSG_NONE, - " prog: %s\n", fpga_prog_method_name(t->prog)); - if (t->caveats & FPGA_CAVEAT_CCLK_VIA_STARTUP) { + " prog: %s\n", target_prog_name(t->prog)); + if (t->kind == TARGET_FPGA && (t->fpga.caveats & TARGET_CAVEAT_CCLK_VIA_STARTUP)) { ctx->script_printf(ctx, MSG_NONE, " 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[] = { " ", "Loads a bitstream (.bit or raw .bin) into 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) { @@ -3135,7 +3143,7 @@ static int cmd_bscan_load_bitstream(script_ctx *ctx, char *line) char path[DEFAULT_BUFLEN]; int device, ret; unsigned long idcode; - const fpga_target *t; + const jtag_target *t; jtag_core *jc = (jtag_core *)ctx->app_ctx; 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; } idcode = jtagcore_get_dev_id(jc, device); - t = fpga_lookup_by_idcode(idcode); + t = target_lookup_by_idcode(idcode); if (!t) { ctx->script_printf(ctx, MSG_ERROR, "Device %d IDCODE 0x%.8lX not in FPGA registry\n", device, idcode); return JTAG_CORE_NOT_FOUND; @@ -3177,7 +3185,7 @@ static int cmd_bscan_jedec(script_ctx *ctx, char *line) char dev_txt[DEFAULT_BUFLEN]; int device; unsigned long idcode; - const fpga_target *t; + const jtag_target *t; uint8_t tx = 0x9F; uint8_t rx[3] = {0}; 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; } idcode = jtagcore_get_dev_id(jc, device); - t = fpga_lookup_by_idcode(idcode); + t = target_lookup_by_idcode(idcode); if (!t) { ctx->script_printf(ctx, MSG_ERROR, "Device %d IDCODE 0x%.8lX not in FPGA registry\n", device, idcode); 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. */ struct flash_proxy_ctx { 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, 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; unsigned long idcode; - const fpga_target *t; + const jtag_target *t; if (jtagcore_get_number_of_devices(jc) <= 0) { ctx->script_printf(ctx, MSG_ERROR, "No device on the chain. Run jtag_autoinit first.\n"); return JTAG_CORE_NOT_FOUND; } idcode = jtagcore_get_dev_id(jc, device); - t = fpga_lookup_by_idcode(idcode); + t = target_lookup_by_idcode(idcode); if (!t) { ctx->script_printf(ctx, MSG_ERROR, "Device %d IDCODE 0x%.8lX not in FPGA registry\n", device, idcode); return JTAG_CORE_NOT_FOUND; @@ -3542,6 +3550,42 @@ static int cmd_svf_play(script_ctx *ctx, char *line) return JTAG_CORE_NO_ERROR; } +const char *cmd_program_help[] = { + "(int) (str)", + "Program a chain device from , 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 \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[] = { {"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_clk", cmd_set_spi_clk_pin, cmd_set_spi_clk_pin_help}, {"jtag_spi_xfer", cmd_spi_rd_wr, cmd_spi_rd_wr_help}, - {"fpga_list", cmd_fpga_list, cmd_fpga_list_help}, - {"fpga_info", cmd_fpga_info, cmd_fpga_info_help}, + {"target_list", cmd_target_list, cmd_target_list_help}, + {"target_info", cmd_target_info, cmd_target_info_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_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_verify", cmd_flash_verify, cmd_flash_verify_help}, {"svf_play", cmd_svf_play, cmd_svf_play_help}, + {"program", cmd_program, cmd_program_help}, {0, 0}}; /////////////////////////////////////////////////////////////////////////////// diff --git a/src/modules/fpga/CMakeLists.txt b/src/modules/target/CMakeLists.txt similarity index 72% rename from src/modules/fpga/CMakeLists.txt rename to src/modules/target/CMakeLists.txt index 21e56bf..c71df73 100644 --- a/src/modules/fpga/CMakeLists.txt +++ b/src/modules/target/CMakeLists.txt @@ -4,11 +4,11 @@ file(GLOB_RECURSE ALL_SOURCES "*.c") 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 # 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) pkg_check_modules(YAML REQUIRED IMPORTED_TARGET yaml-0.1) -target_link_libraries(fpga PUBLIC PkgConfig::YAML) +target_link_libraries(target PUBLIC PkgConfig::YAML) diff --git a/src/modules/target/target.c b/src/modules/target/target.c new file mode 100644 index 0000000..44545f8 --- /dev/null +++ b/src/modules/target/target.c @@ -0,0 +1,344 @@ +#include +#include +#include +#include + +#include + +#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; +} diff --git a/src/modules/target/target.h b/src/modules/target/target.h new file mode 100644 index 0000000..678f21b --- /dev/null +++ b/src/modules/target/target.h @@ -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