Files
bs_explorer/CLAUDE.md
François e987afc624 doc: mark Phase 4 done
Full flash command set (detect/read/erase/write/verify) validated on the
KCU105 via a save/erase/write-random/verify/restore round-trip.
2026-05-24 00:53:07 +02:00

8.2 KiB
Raw Blame History

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 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 50200 KB/s, so ~1040 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.

External references

  • BSCAN proxy bitstreams: quartiq/bscan_spi_bitstreams (MIT). Pre-built .bit for most Xilinx parts; Migen sources to rebuild any part that's missing (needs Vivado).
  • 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

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