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:
110
CLAUDE.md
110
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.
|
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
|
|||||||
50–200 KB/s, so ~10–40 min for 128 MB. This is what Vivado, OpenOCD's
|
50–200 KB/s, so ~10–40 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
|
||||||
|
|||||||
26
README.md
26
README.md
@@ -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)
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
102
data/targets.yaml
Normal 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
|
||||||
@@ -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. |
|
||||||
|
|||||||
5
src/modules/arm_debug/CMakeLists.txt
Normal file
5
src/modules/arm_debug/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
file(GLOB_RECURSE ALL_SOURCES "*.c")
|
||||||
|
|
||||||
|
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)
|
||||||
|
|
||||||
|
add_library(arm_debug ${ALL_SOURCES})
|
||||||
56
src/modules/arm_debug/arm_debug.c
Normal file
56
src/modules/arm_debug/arm_debug.c
Normal 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;
|
||||||
|
}
|
||||||
38
src/modules/arm_debug/arm_debug.h
Normal file
38
src/modules/arm_debug/arm_debug.h
Normal 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
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
9
src/modules/program/CMakeLists.txt
Normal file
9
src/modules/program/CMakeLists.txt
Normal 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)
|
||||||
36
src/modules/program/program.c
Normal file
36
src/modules/program/program.c
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/modules/program/program.h
Normal file
25
src/modules/program/program.h
Normal 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
|
||||||
@@ -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}};
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|||||||
@@ -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
344
src/modules/target/target.c
Normal 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
112
src/modules/target/target.h
Normal 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
|
||||||
Reference in New Issue
Block a user