One clock knob across probes instead of per-driver names:
- jtag_open mirrors JTAG_TCK_FREQ_KHZ into PROBE_FTDI_TCK_FREQ_KHZ for
the Viveris FTDI driver (read-only at init); unset leaves the existing
value untouched
- the Digilent driver reads JTAG_TCK_FREQ_KHZ directly instead of
hardcoding 4 MHz (falls back to 4 MHz when unset)
- documented in probes.yaml; CLAUDE.md design note marks phase A done
FTDI path validated on the IGLOO2/FlashPro (250 kHz, mirror confirmed);
Digilent path not hardware-tested.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- new modules/probes/ parses probes.yaml (libyaml): a defaults: map
applied on every jtag_open + named profiles: selected with
`jtag_open <idx> <profile>` (jtag_profiles lists them); each value is
pushed into the script envvar store the driver reads at open time
- ships a flashpro profile (ADBUS4 high-Z) that lets the IGLOO2 kit's
embedded FlashPro (FT4232H, port 0) detect the chain
- CLAUDE.md: decision entry for probes.yaml + a design note on the
probe / JTAG-link / device config strategy (driver-neutral link layer)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Lets a session hand a probe back (frees its USB handle) without opening
another, e.g. to flash two FPGAs on different probes in turn:
jtag_close, then jtag_open the next one and jtag_autoinit. Mirrors the
DeInit teardown jtagcore_loaddriver already does when switching drivers.
Also fix the help printer: no-arg commands (whose params slot is "")
printed an empty body because "" was treated as the terminator. Params
are now optional and the description always shows.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
IDCODE 0x0F8031CF (mask 0x0FFFFFFF, top nibble = revision) and IR
length 8 read from bsdl_files/m2gl010t-fg484.bsd. No proxy or IR
opcodes: IGLOO2 internal flash is programmed by playing a Libero SVF,
not via the Xilinx BSCAN-proxy SPI path, so those fields don't apply.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Several BSDL files can legitimately match one IDCODE (same die in
different packages, or twins like SmartFusion2 / IGLOO2). The autoinit
loader loaded every match onto the device, silently overwriting the
first, and flagged it as "ID conflit ?". Keep the first match and skip
later files with the same IDCODE instead.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- registry moves from the array in fpga.c to fpga_registry.yaml at the
repo root, parsed via libyaml (pkg-config yaml-0.1); adding a part is
now a YAML edit, no rebuild
- looked up CWD-relative (like bsdl_files/), overridable with
$BS_FPGA_REGISTRY, loaded lazily once; public API unchanged
- fpga_list shows the source file (fpga_registry_source())
- add microsemi_igloo2/smartfusion2 and lattice_machxo2/3 families,
ready for the non-Xilinx targets
- docs updated: CLAUDE.md, README, tutorial "add a target" walkthrough
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Captures feasibility and shape of an MCU-based programmer: the MCU is
the JTAG master (GPIO/SPI-assisted driver behind the existing
drv_TX_TMS/drv_TXRX_DATA seam), jtag_core/registry/SVF-player/flash
logic reused as-is, REPL/os_interface/BSDL-parse dropped or replaced,
and the one real rework — stream payloads from SD/USB in chunks instead
of one big malloc. SVF-from-SD is the ideal fit for small Lattice/IGLOO2.
Capture the vision: a single SVF player is a near-universal backend
(Xilinx fabric config, Lattice, Microsemi IGLOO2, Altera, CPLDs from
vendor-exported files); native backends are exceptions where streaming/
speed/control matter — chiefly Xilinx external SPI flash via the proxy.
Adds a per-target path table and what an SVF player must implement.
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.
Reflect what now works: per-FPGA registry, BSCAN proxy SPI bridge, and
flash detect/read/erase/program/verify (~100 KB/s). Switch the typical
flow to the proxy path, add the fpga/bscan/flash command rows, note
persistent history, the KU040 BSDL, the new modules + bscan_proxies/ in
the layout, and the quartiq MIT proxies in the license section.
quartiq ships no UltraScale+ proxy, so the KU15P .bit must be built from
xilinx_bscan_spi.py (Migen + Vivado) after adding the part to the
generator's device table. Put the operational steps in the tutorial's
Phase 2.5 (where users look for a bitstream); CLAUDE.md just points to
it.
Rework tutorial section 6 with a worked XCKU040 example: how to pull IR
opcodes/IDCODE from the BSDL (with a grep), a field-by-field table, the
verbatim registry entry, and a dedicated section on what `caveats` are
(bit-flags for hardware gotchas, not free text) and how to add one.
Also fixes the broken in-page anchor links to the section.
Load and save readline history in $HOME/.bs_explorer_history (capped at
1000 entries), so up-arrow recalls commands from previous sessions.
Saved on exit/quit, EOF and Ctrl-C.
Completes the flash command set over the BSCAN proxy:
- flash_erase <dev> <addr> <len> : erase covering sectors (destructive)
- flash_write <dev> <addr> <file> : program from a file (no auto-erase)
- flash_verify <dev> <addr> <file>: compare flash against a file
- flash_read gains an optional [file] arg for a raw binary dump (backup)
Validated on the KCU105 with a save -> erase -> write-random -> verify
-> erase -> restore round-trip (region left untouched). Apparent write
throughput ~100 KB/s once the proxy is loaded.
jtag_scan only printed "JTAG Scan done". Now it prints the device count
and each device's IDCODE (it scans the chain but, unlike jtag_autoinit,
loads no BSDL). Also replaces the placeholder help text.
Validated on the KCU105: shows device 0 IDCODE 0x03822093.
"quirk" was unclear jargon; "caveat" matches the wording already used in
the README/CLAUDE.md ("Xilinx caveats"). Renames the struct field, the
FPGA_QUIRK_* macro, the fpga_info output and the docs. No behaviour
change.
autoinit parses every bsdl twice for a matching part — once to read the
IDCODE, once to actually attach it — producing two identical loader
blocks with no hint why. Bracket each with an [identify] / [load] line.
Read-only SPI flash commands over the BSCAN proxy (via a small adapter
to spi_flash's xfer callback). flash_detect identifies the chip from
its JEDEC ID; flash_read hex-dumps a region.
Validated on the KCU105: flash_detect -> Micron MT25QU256, flash_read
shows the stored bitstream (sync word 0xAA995566 at 0x50). Erase/program
commands deferred to Phase 4.
Transport-agnostic (xfer callback) layer with a JEDEC-ID chip database
and detect/read/erase_sector/program_page/program/verify. Standard
opcode set, 3- and 4-byte addressing (4-byte command opcodes for parts
over 16 MB). Seeded with the KCU105's MT25QU256 plus common
Winbond/Macronix/ISSI/Micron parts.
detect + read validated on the KCU105 over the proxy. erase/program are
implemented but not yet hardware-tested (destructive on a config flash).
bscan_load_bitstream + bscan_jedec confirmed end to end on a KCU105:
reads the Micron MT25QU256 config flash (0x20 BB 19) through the
quartiq XCKU040 proxy. Replace the illustrative JEDEC output with the
real one.
The TX/TXRX functions used fixed ~1-2 KB stack buffers and rejected
anything larger, so any big shift failed: bscan_idle_cycles(10000) and
above all the ~19 Mbit bitstream load for the BSCAN proxy. Size the
pack/capture buffers to the shift on the heap instead.
This is what unblocked the proxy load — see next commit.
The bscan_jedec command and tutorial referenced the JEDEC ID without
defining it. Describe the 0x9F RDID command and the manufacturer +
device byte layout, in the tutorial and the command help.
Rewrite the Phase 2.5 tutorial section for the now-working path: fetch
the proxy bitstream, bscan_load_bitstream, bscan_jedec, and the
bscan_spi_xfer primitive. Note the JPROGRAM reconfiguration caveat. The
shown JEDEC output is illustrative — not yet hardware-confirmed.
The driver dlopen's libdjtg/libdmgr and degrades to "no probe" if
they're absent, so building it in has no cost or dependency.
BS_ENABLE_DIGILENT now defaults ON on UNIX (needs <dlfcn.h>); disable
with -DBS_ENABLE_DIGILENT=OFF. Docs updated; also fixes the quartiq
license note in CLAUDE.md (MIT, not BSD-2).
bscan_spi_xcku040.bit from quartiq/bscan_spi_bitstreams (c) QUARTIQ
GmbH, MIT — LICENSE.quartiq and README.md included for attribution.
Lets the BSCAN proxy path work out of the box on the KCU105.
bscan_jedec <device> reads the SPI flash JEDEC ID (0x9F + 3 bytes)
through a loaded BSCAN proxy, via bscan_spi_xfer. Validation command for
the proxy path; not yet exercised on hardware.
One CS-framed transaction: marker + 32-bit count + MOSI + read-latency
skip + MISO, MSB-first on the wire, matching OpenOCD's jtagspi so the
quartiq proxy bitstreams work unchanged. Half-duplex (tx,txlen,rx,rxlen)
signature, single-device chain.
NOT yet validated on hardware — protocol follows the OpenOCD reference
but has not been confirmed against a live proxy + flash. Validation
(read JEDEC ID on the KCU105) is the next step.
Drop the get_/set_/_pin/_list noise from the JTAG commands (e.g.
jtag_get_probes_list -> jtag_probes, jtag_set_spi_cs_pin -> jtag_spi_cs,
jtag_spi_rd_wr -> jtag_spi_xfer). jtag_open_probe -> jtag_open (not
jtag_probe, which would clash with jtag_probes under tab-completion).
Hard rename, no aliases. Updates the state-dump emitter, help text,
example script and docs accordingly.
It returned the loaded-BSDL count, which the engine flagged as an error
code ("Command failed with code: 1") whenever a BSDL matched. Stash the
count in last_data_value and return JTAG_CORE_NO_ERROR instead.
CLAUDE.md/README/tutorial: optional BS_ENABLE_DIGILENT backend, why
SMT2 modules need libdjtg, and the new jtag_open_probe index. Mark
phases 2 and 2.5 done.
jtag_get_probes_list now prints a flat [N] index; jtag_open_probe takes
that index (decimal) or, if 0x-prefixed, the raw probe id. Avoids the
trap of typing "1" and hitting a non-existent (drv 0, probe 1).
DmgrEnumDevices builds an internal device table that must be released
with DmgrFreeDvcEnum, otherwise a second enumeration (e.g. opening a
probe by index, which re-runs Detect) fails.
TMS-only shifts go through DjtgPutTmsBits with TDI held to 0. Pure data
shifts use DjtgPutTdiBits with TMS held to 0. Mixed shifts (e.g. last
bit of Shift-IR/DR with TMS=1) fall back to DjtgPutTmsTdiBits — note
that within each pair the encoding is TDI(low) then TMS(high),
LSB-first, despite the function name (verified against the SDK
DjtgDemo sample).
Init also gained a lazy call to Detect: jtag_open_probe can be invoked
without first running jtag_get_probes_list.
Validated on KCU105: jtag_autoinit returns IDCODE 0x13822093
(XCKU040 rev1) through the Digilent SMT2-NC.
DmgrOpen + DjtgEnable + DjtgSetSpeed at 4 MHz (Adept rounds to the
nearest supported, e.g. 3.75 MHz on SMT2-NC). DeInit does the reverse.
An atexit hook also forces Close on process shutdown — leaving an open
HIF when libdjtg's C++ static destructors run triggers "pure virtual
method called".
Loads the Adept .so files lazily and resolves the symbols we need.
Detect() enumerates devices via DmgrEnumDevices/DmgrGetDvc and exposes
each as a Viveris probe slot. If Adept Runtime is missing, the driver
silently reports 0 probes.
The probe ID printed by jtag_get_probes_list is the hex value to pass
verbatim to jtag_open_probe (parsed as base 16), but reading
"ID 0x00000000" and typing "1" as a 1-based index is a natural mistake
— and jtag_open_probe will accept 1, fail with a misleading
"FT_DEVICE_NOT_FOUND" since (drv=0, probe=1) does not exist.
Append explicit [drv N, probe M] decomposition so the value to copy is
unambiguous.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
In cmd_autoinit, when find_first_file fails to open ./bsdl_files/ the
error path printed `filename` — which is only populated inside the
directory-walk loop. Outside that loop it is uninitialized stack
content, leading to garbage in the error message (and a confusing
diagnostic when bs is launched from a directory without a bsdl_files/
subfolder, e.g. build/).
Print `scanfolder` (the actual path that was attempted) instead.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
doc/tutorial.md walks from probe detection to JEDEC ID over EXTEST and
forward-references the BSCAN proxy path. Includes:
- prerequisites and build/launch
- chain scan, FPGA identification, registry lookup
- IR/DR primitive sanity check via the IDCODE register
- SPI JEDEC ID over EXTEST (with the speed caveat)
- recipe to add a new FPGA target
- troubleshooting cheat sheet
README and CLAUDE.md updated to point at it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Low-level JTAG primitives operating directly on jc->io_functions
(single-device chain assumed), independent of jtag_core:
- bscan_set_ir
- bscan_shift_dr (TDI/TDO, LSB-first packing)
- bscan_idle_cycles
High-level operations driven by an fpga_target descriptor:
- bscan_load_bitstream: JPROGRAM -> CFG_IN -> shift (bit-reversed for
Xilinx) -> JSTART -> idle -> BYPASS
- bscan_load_bitstream_file: parses the Xilinx .bit container header
(sections a/b/c/d/e), falls back to raw .bin
bscan_spi_xfer is stubbed: the quartiq jtagspi protocol details will
be wired once we have a proxy .bit to validate against (OpenOCD
src/flash/nor/jtagspi.c is the host-side reference).
Three new script commands:
- bscan_set_ir <opcode_hex> <ir_length>
- bscan_shift_dr <nbits> (writes zeros, prints captured TDO)
- bscan_load_bitstream <device> <path>
The sanity check for a healthy primitive on KU15P:
jtag_init_scan; bscan_set_ir 9 6; bscan_shift_dr 32 -> 04 A5 60 93
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
modules/fpga/ holds an fpga_target struct (IDCODE/mask, family, IR
length and private opcodes, proxy bitstream path, quirks) and a
compile-time registry. Initial entry: Xilinx Kintex UltraScale+
XCKU15P, populated from bsdl_files/xcku15p_ffve1517.bsd (IDCODE
0x04A56093, IR 6, USER1=0x02, CFG_IN=0x05, JPROGRAM=0x0B, JSTART=0x0C,
JSHUTDOWN=0x0D, ISC_DISABLE=0x16, quirk CCLK_VIA_STARTUP).
Two new script commands:
- fpga_list: enumerate the registry
- fpga_info: match each device on the JTAG chain against the registry
and surface known quirks
Adding another FPGA = one entry in fpga_registry[] + its .bsd in
bsdl_files/. Proxy .bit will be wired in phase 2.5 (bscan_spi/).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>