Both hold config in internal flash (programmed directly over JTAG), so the Xilinx external-flash+proxy path doesn't apply. Records two backend strategies — native per-family (Lattice ISC / IEEE 1532) vs a generic SVF/STAPL player (recommended for IGLOO2, whose algorithm is proprietary) — what already generalises, and the registry/dispatch changes needed.
245 lines
12 KiB
Markdown
245 lines
12 KiB
Markdown
# Project guide for Claude Code
|
||
|
||
This file is loaded automatically when working on `bs_explorer` in any
|
||
Claude Code session, on any machine where the repo is cloned. Keep
|
||
machine-specific or user-specific facts out of here — they belong in
|
||
`~/.claude/` memory, not in the repo.
|
||
|
||
Project status, decisions, and roadmap below are durable: update them
|
||
when reality changes, not for every transient task.
|
||
|
||
## What this project is
|
||
|
||
`bs_explorer` is an application layer on top of Viveris's
|
||
[jtag-boundary-scanner](https://github.com/viveris/jtag-boundary-scanner)
|
||
library (LGPL). End goal: program SPI configuration memories attached
|
||
to FPGAs (Xilinx KU15P first, then others) over JTAG, from a CLI tool
|
||
running on a host with an FTDI probe.
|
||
|
||
The Viveris library itself lives unchanged in `modules/`. Everything
|
||
new is in `bs/` (the REPL) and future modules (`fpga/`, `bscan_spi/`,
|
||
`spi_flash/`) sitting alongside the Viveris ones.
|
||
|
||
## Architecture
|
||
|
||
```
|
||
bs/ Application (readline REPL, no business logic)
|
||
modules/ — Viveris's library (LGPL, unchanged) —
|
||
├── jtag_core/ TAP state machine, IR/DR shifts
|
||
├── bsdl_parser/ .bsd loader
|
||
├── bus_over_jtag/ SPI/I²C/MDIO/parallel mem bit-bang over EXTEST
|
||
├── drivers/ FTDI, J-Link, Linux GPIO, LPT, Digilent (optional)
|
||
├── script/ Script engine (40+ commands, the real UI)
|
||
├── config/ Built-in config.script
|
||
├── os_interface/ Portable fs/network wrappers
|
||
└── natsort/ Natural pin-name sorting
|
||
bsdl_files/ BSDL files for target FPGAs
|
||
scripts/ Example scripts
|
||
doc/ Tutorial and longer-form docs (doc/tutorial.md is the end-to-end walkthrough)
|
||
libs/libftd2xx/ Vendored FTDI SDK
|
||
```
|
||
|
||
User interacts with the project through the script engine commands
|
||
(`jtag_*`, `set`, `if`, …), exposed via the REPL or piped script files.
|
||
Adding a feature usually means adding a new script command in
|
||
`modules/script/script.c` and the supporting C in a new module.
|
||
|
||
## Roadmap
|
||
|
||
| Phase | Module | Status | Summary |
|
||
|-------|--------|--------|---------|
|
||
| 1 | `bs/` cleanup, REPL polish, README | **done** (commit `7cb3627`) | Fix format-strings, delete dead code, tab-completion, banner |
|
||
| 2 | `fpga/` | **done** (commit `545fe09`) | Per-target descriptor (IDCODE, BSDL, IR codes, proxy path, caveats). Compile-time registry. |
|
||
| 2.5 | `bscan_spi/` | **done** (commit `dec0d14`) | Load BSCAN proxy bitstream via `CFG_IN`, expose fast `bscan_spi_xfer()` via `USER1`. Required for realistic flashing speeds. |
|
||
| 3 | `spi_flash/` | **done** (commit `c4afe87`) | Chip DB (JEDEC ID → page/sector/cmd set) + generic `read/erase/program/verify` over an `xfer` callback. detect+read validated on KCU105; erase/program implemented but not yet hardware-tested. |
|
||
| 4 | script commands | **done** (commit `d6f843e`) | `flash_detect`, `flash_read` (+file), `flash_erase`, `flash_write`, `flash_verify`. Full set validated on KCU105 (save/erase/write-random/verify/restore round-trip). ~100 KB/s write once the proxy is loaded. |
|
||
|
||
Move forward phase by phase: validate one with the user before starting
|
||
the next. Don't break the validated path
|
||
`jtag_open → jtag_autoinit → jtag_mode 0 EXTEST` while
|
||
refactoring.
|
||
|
||
## Key technical decisions
|
||
|
||
### Flashing path: BSCAN proxy, not EXTEST
|
||
|
||
The Viveris library has `bus_over_jtag/spi_over_jtag.c` which bit-bangs
|
||
SPI on FPGA pins placed in EXTEST. **This is not usable for actual
|
||
flashing.** Each SPI bit costs 2 full boundary-scan-register shifts
|
||
through JTAG, plus USB latency on the FTDI side. Order of magnitude:
|
||
~30 bytes/s effective, i.e. **weeks** to flash a 128 MB part. Useful
|
||
only for one-shot operations (read JEDEC ID, check wiring).
|
||
|
||
Real flashing path: load a small "BSCAN proxy" bitstream into the FPGA
|
||
fabric via standard JTAG configuration (`CFG_IN`). The proxy uses a
|
||
`BSCANE2` primitive to bridge the JTAG `USER1` instruction's DR shift
|
||
to the physical SPI pins, running at fabric speed (CCLK driven
|
||
internally — the `STARTUPE3` problem disappears). Realistic throughput
|
||
50–200 KB/s, so ~10–40 min for 128 MB. This is what Vivado, OpenOCD's
|
||
`jtagspi` driver, and `xc3sprog` all do.
|
||
|
||
### Per-FPGA descriptor
|
||
|
||
`fpga_target` struct (Phase 2) holds the per-target facts that can't be
|
||
derived from the BSDL alone:
|
||
- `idcode` + `idcode_mask` — match the device on the chain
|
||
- `bsdl_filename` — BSDL to auto-load
|
||
- `cfg_in_ir_code`, `user1_ir_code`, `jprogram_ir_code` — Xilinx-specific
|
||
private IR opcodes (read from BSDL when available)
|
||
- `proxy_bitstream_path` — path to the BSCAN proxy `.bit` for this part
|
||
- `caveats` — flags for known hardware gotchas (e.g. CCLK via STARTUPE3)
|
||
|
||
Registry is a compile-time array. Adding a part = one entry + its
|
||
`.bsd` in `bsdl_files/` + its proxy `.bit` in `bscan_proxies/`.
|
||
|
||
### Digilent SMT2 modules need libdjtg, not raw MPSSE
|
||
|
||
Several Xilinx dev boards (KCU105, ZCU102, …) embed a Digilent
|
||
JTAG-SMT2 / SMT2-NC for USB-JTAG. Even though it presents a stock
|
||
FTDI FT232H over USB (VID:PID 0403:6014), it runs a proprietary
|
||
firmware that **does not respond to plain MPSSE commands** — TCK
|
||
toggles but the level-shifters/buffers stay disabled, so TDO floats
|
||
high ("all ones" symptom). Standard FTDI driver path is dead on these
|
||
boards.
|
||
|
||
Workaround: `modules/drivers/digilent_jtag/` wraps libdjtg/libdmgr
|
||
(Digilent Adept Runtime), loaded via `dlopen` at runtime — no Digilent
|
||
binary or header in the repo. Because it's dlopen'd (degrades to "no
|
||
probe" if the libs are absent), it costs nothing to build in:
|
||
`BS_ENABLE_DIGILENT` defaults **ON** on UNIX (`<dlfcn.h>` required),
|
||
disable with `-DBS_ENABLE_DIGILENT=OFF`. Adept Runtime is only needed
|
||
at runtime to actually drive such a probe.
|
||
|
||
### Xilinx caveats
|
||
|
||
On 7-Series / UltraScale / UltraScale+, `CCLK` is not a regular I/O
|
||
pin — it goes through the `STARTUPE3` primitive. Cannot be driven
|
||
directly in EXTEST. Non-issue once we use the BSCAN proxy (CCLK is
|
||
driven by the fabric clock inside the proxy).
|
||
|
||
The FPGA must be in a configurable state before loading the proxy.
|
||
Issue `JPROGRAM` first to reset, then `CFG_IN` + shift the bitstream,
|
||
then `JSTART` and check `DONE`.
|
||
|
||
## Extending to other FPGA families (design note)
|
||
|
||
Not yet implemented — captured here so the design is ready when needed.
|
||
Targets in mind: small Lattice MachXO2/MachXO3 (PSU sequencing/glue) and
|
||
Microsemi/Microchip IGLOO2 / SmartFusion2.
|
||
|
||
**Key difference from Xilinx.** Our Xilinx path programs an *external*
|
||
SPI flash via a BSCAN proxy bitstream. Both Lattice MachXO2/3 and
|
||
Microsemi IGLOO2 instead hold their config in *internal* flash,
|
||
programmed *directly over JTAG* — no external flash, no proxy. So
|
||
`bscan_spi/` and `spi_flash/` do **not** apply to them; they need a
|
||
different programming backend.
|
||
|
||
**What already generalises (reuse as-is):**
|
||
- probe drivers, `jtag_core`, and the `bscan_set_ir` / `bscan_shift_dr`
|
||
primitives in `bscan_spi/` — vendor-neutral, and exactly what any
|
||
JTAG programming sequence drives;
|
||
- IDCODE detection + BSDL load (BSDL is a standard);
|
||
- the `fpga_target` registry concept and the script-command framework.
|
||
|
||
**Two strategies for the programming backend:**
|
||
|
||
1. **Native per-family backend.** Implement the vendor's JTAG flow on
|
||
top of our IR/DR primitives.
|
||
- *Lattice MachXO2/3*: the IEEE 1532 / ISC sequence — `ISC_ENABLE` →
|
||
`ISC_ERASE` → `LSC_INIT_ADDRESS` → `LSC_PROG_INCR_NV` (page loop) →
|
||
`ISC_PROGRAM_DONE`, with BUSY polling. Well documented (Lattice
|
||
TN1204, IEEE 1532 BSDL `ISC_*` attributes) → very feasible. Needs a
|
||
`.jed` parser for the payload.
|
||
- *Microsemi IGLOO2*: the on-chip programming algorithm is
|
||
proprietary and complex (sys-services, eNVM/fabric) — reimplementing
|
||
it natively is a large, fragile effort. Not recommended.
|
||
|
||
2. **Generic SVF / STAPL player (higher leverage).** Execute the JTAG
|
||
programming file the vendor tool exports, instead of re-deriving the
|
||
algorithm. The vendor's algorithm is *baked into the file*.
|
||
- **SVF** = a flat list of `SIR`/`SDR`/`RUNTEST` ops → a small player
|
||
on `bscan_set_ir`/`bscan_shift_dr` covers it; vendor-neutral
|
||
(Lattice, Microsemi, Altera all export SVF).
|
||
- **STAPL** (`.stp`/`.jam`) = a full language (loops, conditionals) →
|
||
needs an interpreter, more work, but it's Microsemi's/Lattice's
|
||
native portable format.
|
||
- **For IGLOO2 this is the pragmatic route**: let Libero/FlashPro
|
||
export SVF (or STAPL) and just play it. One player → many vendors.
|
||
|
||
Recommendation: an **SVF player** is the single highest-value
|
||
addition — it unlocks IGLOO2 *and* most other JTAG parts at once,
|
||
with the vendor owning the algorithm. Add native Lattice ISC only if
|
||
a self-contained `.jed` flow (no vendor export step) is wanted.
|
||
|
||
**Registry / dispatch adjustments:**
|
||
1. `fpga_target` is Xilinx-flavoured today (`ir_cfg_in`, `ir_user1`,
|
||
`ir_jprogram`, …). Generalise it — per-family opcode sets, or a small
|
||
"programming method" tag (`proxy_spi` / `lattice_isc` / `svf`) that
|
||
selects the backend.
|
||
2. The existing `family` enum is the natural dispatch point
|
||
(`FPGA_FAMILY_XILINX_*`, add `FPGA_FAMILY_LATTICE_*`,
|
||
`FPGA_FAMILY_MICROSEMI_*`). An SVF player is family-agnostic and
|
||
needs no per-part opcodes at all.
|
||
|
||
**Scope caveat.** Lattice MachXO2/3 and IGLOO2/SmartFusion2 are genuinely
|
||
JTAG-programmable. iCE40 is usually programmed over SPI directly (or
|
||
one-time NVCM) with minimal JTAG — out of scope for this JTAG-centric
|
||
tool.
|
||
|
||
## External references
|
||
|
||
- **BSCAN proxy bitstreams**: `quartiq/bscan_spi_bitstreams` (MIT).
|
||
Pre-built `.bit` for many Xilinx parts; Migen sources to rebuild any
|
||
part that's missing (needs Vivado). Building a proxy that isn't
|
||
pre-built (e.g. the KU15P) is covered in `doc/tutorial.md`, Phase 2.5.
|
||
- **Reference host-side implementation**: `openocd/src/flash/nor/jtagspi.c`.
|
||
Defines the proxy protocol (header with bit count + CS state, then
|
||
payload). Don't reinvent — match what OpenOCD does so we share the
|
||
same bitstreams.
|
||
- **Viveris library docs**: `modules/jtag_core/` headers are well
|
||
commented. `modules/config/config.script` documents every runtime
|
||
variable.
|
||
|
||
## Build & test
|
||
|
||
```sh
|
||
mkdir build && cd build && cmake .. && make
|
||
./bs/bs # interactive REPL
|
||
```
|
||
|
||
The Digilent SMT2 backend is built by default on UNIX (disable with
|
||
`-DBS_ENABLE_DIGILENT=OFF`). To actually use such a probe, install the
|
||
Adept Runtime system-wide (provides `libdjtg.so` + `libdmgr.so`).
|
||
|
||
No automated tests yet. Smoke test = banner appears, `exit` works.
|
||
After changes touching `jtag_core`, `drivers/ftdi_jtag`, or the
|
||
`autoinit` flow, manual hardware test required: probe + KU15P board
|
||
should scan and load `xcku15p_ffve1517.bsd`.
|
||
|
||
The FTDI driver compiles with int-to-pointer-cast warnings (Viveris's
|
||
code on 64-bit Linux). Don't fix in this repo — that's upstream.
|
||
|
||
## Commit conventions
|
||
|
||
- Messages in English, lowercase first word, imperative or short
|
||
descriptive (match existing style: "phase 1: cleanup, REPL polish,
|
||
README", "translate README to English").
|
||
- Title ≤ ~70 chars. Body wrapped ~80 chars, with bullets for what
|
||
changed. Mention the why for non-obvious choices.
|
||
- Sign-off block:
|
||
```
|
||
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
```
|
||
- One logical change per commit. Don't bundle README + bugfix +
|
||
refactor.
|
||
|
||
## What does NOT belong here
|
||
|
||
- User preferences (tone, language used in chat, "always do X first")
|
||
→ `~/.claude/`
|
||
- Machine-local facts (which probes are physically attached, paths
|
||
outside the repo, validated-on-this-machine notes) → `~/.claude/`
|
||
- Transient task state, in-progress decisions → conversation, not file
|
||
- Generated artifacts (`build/`, downloaded proxy `.bit` if we choose
|
||
not to vendor) → `.gitignore`
|