Files
bs_explorer/doc/build_xilinx_proxy.md

7.0 KiB
Raw Blame History

Building a BSCAN SPI proxy bitstream for a Xilinx FPGA

The fast SPI-flash path in bs_explorer (tutorial.md, Phase 2.5) loads a tiny "BSCAN proxy" bitstream into the FPGA fabric so SPI can run through the JTAG USER1 instruction at fabric speed. Pre-built proxies for many parts ship in quartiq/bscan_spi_bitstreams (MIT) — that repo also contains the generator (Migen + Vivado) used to make them.

For parts the generator doesn't already know — all of UltraScale+ (XCKU15P, XCKU3P, XCKU11P, XCZU…, Virtex US+), plus any 7-Series / UltraScale part missing from its table — you have to add a platform entry and build the .bit yourself. This document walks through that, using the Kintex UltraScale+ XCKU15P as the worked example.

The same recipe applies to any other Xilinx part: only the device string, package pin LOCs and I/O standard change.

What you end up doing

  1. install Vivado and the quartiq generator's Python deps;
  2. add a Migen platform entry for your part (the generator picks parts out of a hard-coded table — it is not just a CLI flag);
  3. run the generator, which calls Vivado to synth / place / route into a .bit;
  4. drop the .bit into data/bscan_proxies/ and reference it from data/targets.yaml;
  5. smoke-test on the board.

Prerequisites

  • Vivado matching your part's family. UltraScale+ needs Vivado 2019.2 or newer (2022.2 is what quartiq's README pins). The free WebPACK / Standard edition covers the smaller US+ devices like the XCKU3P / XCKU11P; larger parts (KU15P included) may need a paid Vivado licence. ISE 14.7 only for Spartan-6 and earlier.
  • Python ≥ 3.8 with venv, and the Migen revision pinned in the generator's requirements.txt.
  • Roughly 3080 GB of disk for Vivado, plus a few GB scratch per synthesis run.

Confirm Vivado is reachable from your shell:

source /opt/Xilinx/Vivado/2022.2/settings64.sh
vivado -version       # should print the version

1. Clone quartiq and install its Python deps

git clone https://github.com/quartiq/bscan_spi_bitstreams
cd bscan_spi_bitstreams
python3 -m venv --system-site-packages .venv
./.venv/bin/pip install -r requirements.txt

--system-site-packages lets the venv see a system-wide Migen if one is installed; drop the flag for a fully isolated venv.

2. Add your part to the generator's table

Open xilinx_bscan_spi.py. Near the bottom you'll find a table of Platform subclasses, one per supported part, each setting at least:

  • device — the full Vivado part string, e.g. "xcku15p-ffve1517-2-e". Speed grade and temp range matter — copy them from your board's schematic or the Vivado project that targets the same silicon.
  • toolchain"vivado" for 7-Series and newer, "ise" for older.
  • The SPI-flash pin locations for the part's configuration bank (typically D00_MOSI_0, D01_DIN_0, FCS_B_0). CCLK is not declared here — it is driven inside the FPGA via STARTUPE3 for UltraScale/+ and STARTUPE2 for 7-Series, which the generator already instantiates. (This is exactly what dissolves the cclk_via_startup caveat described in CLAUDE.md.)
  • The I/O standard for those pins (LVCMOS18 for 1.8 V banks like the KCU105/KU15P config bank, LVCMOS25/LVCMOS33 otherwise). Wrong voltage ⇒ Vivado DRC errors, or worse, damaged I/O on the flash.

Pick the closest existing entry as a template. For the KU15P, the KU040 entry is the right neighbour: same toolchain (vivado), same STARTUPE3, same bank-0 pinout shape. Duplicate it, change:

  • the device string to your part + package + speed grade;
  • the pin LOCs if the package differs (e.g. FFVE1517 vs FFVA1156);
  • the class name, and add it to the dispatch dictionary at the bottom of the file so a CLI key resolves to your new class.

Cross-check the four pin LOCs against the part's Package Pin table in the Xilinx/AMD datasheet — these pads are board-independent (they are the hard-wired configuration-bank pins), but the package suffix changes them.

3. Run the generator

source /opt/Xilinx/Vivado/2022.2/settings64.sh
./.venv/bin/python3 xilinx_bscan_spi.py xcku15p

The argument is the key you added to the dispatch dictionary in step 2 — not the raw Vivado part string. The script writes a Migen build tree, calls Vivado, and on success drops bscan_spi_xcku15p.bit (a few KB — just a BSCANE2, a STARTUPE3 and a small FSM) in the current directory.

Expect 13 minutes on a modern host. A failed run leaves its logs under build_xcku15p/; common culprits:

Vivado error Fix
[Place 30-574] pin LOC not on package wrong package suffix in device, or copied LOCs from a different package
[Drc NSTD-1] unconstrained I/O missing I/O standard on a pin → fix the platform entry
Part xcku15p-… is not installed install the device support pack in Vivado, or pick a part covered by your licence
Vivado not found forgot to source settings64.sh before launching the generator

4. Drop it into bs_explorer

cp bscan_spi_xcku15p.bit /path/to/bs_explorer/data/bscan_proxies/

The directory's LICENSE.quartiq (MIT) covers your build too — leave it in place. Point the registry entry at the new file (this is the KU15P entry that currently has proxy_bitstream omitted in data/targets.yaml):

  - 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
    proxy_bitstream: bscan_spi_xcku15p.bit   # <-- the file you just built
    caveats:         cclk_via_startup
    prog:            proxy_spi

No rebuild — the registry is loaded at runtime.

5. Smoke-test on the board

bs_explorer> jtag_open 1
bs_explorer> jtag_autoinit
bs_explorer> bscan_load_bitstream 0 data/bscan_proxies/bscan_spi_xcku15p.bit
bs_explorer> bscan_jedec 0
JEDEC ID: 20 BB 19            # or whatever flash your board carries

A plausible JEDEC ID — manufacturer byte matching the flash on the schematic (0x20 Micron, 0xEF Winbond, 0xC2 Macronix, 0x01 Cypress/Infineon, 0x9D ISSI, …) — confirms the proxy is wired right: STARTUPE3 is driving CCLK, the BSCANE2 capture is on USER1, and the four pin LOCs match the physical flash. A 00 00 00 or FF FF FF reply almost always means one of the LOCs is wrong — re-check the package's configuration-bank pinout.

Upstreaming (optional)

If your new part is broadly useful — any stock dev-board MPSoC, common KU/VU/ZU device — the quartiq project takes PRs against xilinx_bscan_spi.py. Once merged, future users get a pre-built .bit and skip this whole document.