Files
bs_explorer/CLAUDE.md
francois bbb99ba35c add CLAUDE.md with project guide
Durable project context (architecture, roadmap, key decisions, external
references, commit conventions) so any Claude Code session on any
machine has the same baseline understanding. Machine-local facts stay
out of the repo, in ~/.claude/.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 22:41:13 +02:00

6.7 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
├── 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
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/ planned Per-target descriptor (IDCODE, BSDL, IR codes, proxy path, quirks). Compile-time registry.
2.5 bscan_spi/ planned Load BSCAN proxy bitstream via CFG_IN, expose fast bscan_spi_xfer() via USER1. Required for realistic flashing speeds.
3 spi_flash/ planned Chip database (JEDEC ID → page/sector/cmd set). Generic read/erase/program/verify over either backend.
4 script commands planned flash_detect, flash_read/write/erase/verify.

Move forward phase by phase: validate one with the user before starting the next. Don't break the validated path jtag_open_probe → jtag_autoinit → jtag_set_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
  • quirks — flags for known caveats (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/.

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 (BSD-2). 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

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