SPI-over-JTAG (SOJ) source review¶
Reviewed against the checkout at 949bab1 (v.1.1.3), with the fixes in this
working tree applied afterward. The review covers the C++ transport, the JTAG
chain wrapper, the SOJ RTL, and the optional diagnostic tool.
Findings and fixes¶
| Priority | Area | Finding | Fix in this checkout |
|---|---|---|---|
| Critical | src/vendors/xilinx.cpp, get_spiOverJtag_version() |
The v2 probe computed an 8-byte transfer (v2_real_len + 2) into 7-byte v2_pkt and v2_jrx arrays. This could overwrite the stack during v2 detection. |
Use zero-initialized vectors sized to the exact transfer length. |
| Critical | v2 detection | Returning 2.0f was conditional on _verbose > 0; normal users silently fell back to v1 even when the probe found v2 data. |
Keep logging conditional, but make detection independent of verbosity. |
| High | JTAG chain setup | Version probing manually added bypass bits even though Jtag::shiftDR() already inserts _dr_bits_before. Multi-device chains therefore received duplicated padding. |
Removed the second, manual padding scan. |
| High | v1/v2 transfers | Read-only transfers left MOSI bytes and the SOJ packet tail uninitialized. Results depended on stack contents and could send arbitrary SPI bits. | Zero-initialize v1 and v2 TX/RX buffers, including the extra packet byte. |
| High | spi_wait() |
The initial header+command buffer was resent for every status poll. In v2 this reissued the SOJ header/flash command instead of clocking dummy bytes, breaking erase/program polling. | Send the command once, then use 0xff 0xff dummy bytes for polling and exit. |
| High | USER fallback | RDID fallback probed and could select USER4. In the bundled wrapper USER4 is the bridge version interface, not the SPI path. | Restrict fallback candidates to USER1–USER3. |
| Medium | v2 length boundary | real_len == 32 was encoded in short mode even though the 5-bit short length field cannot represent 32. |
Select extended mode for real_len >= 32. |
| Medium | diagnostic tool | The tool used a nonexistent Part type, hardcoded 0x1e as USER1, and allocated VLAs. |
Read IR length from the selected Jtag device, use Xilinx USER1 0x02 for its documented 7-series/Spartan-6 scope, and use vectors. |
| Medium | JTAG target selection | device_select() and the CLI accepted an index equal to the device count, leading to an out-of-range target lookup. |
Reject index >= device_count before selecting the target. |
The bundled RTL assigns USER4 to the version interface in
xilinx_spiOverJtag.v.
This is also consistent with AMD's BSCANE2 documentation,
which defines USER1–USER4 as separate JTAG user chains.
Remaining SOJ risks requiring hardware or RTL simulation¶
RX alignment is still not proven¶
The current C++ path extracts short-packet data at idx = 2 and long-packet
data at idx = 3. The diagnostic tool documents three RX header bytes and
tests idx = 3 for short packets. Historical SOJ code also reconstructed a
chain-dependent bit shift, while the current Jtag::shiftDR() automatically
handles bypass devices before the selected target. These are incompatible
assumptions, so changing the offset again without a capture could fix one
board and break another.
Required next test:
- Capture a short RDID transfer from a known-good single-device Artix-7/HS3 setup and a long read crossing the 32-byte boundary.
- Repeat with a multi-device chain.
- Record the raw TDI/TDO bit stream and assert the expected data offset in a golden-vector test.
The relevant implementation is in
xilinx.cpp, and the chain behavior is in
jtag.cpp.
Detection heuristics are permissive¶
looks_like_valid_jedec_reply() rejects only all-zero/all-0xff and a few
edge-byte patterns. A noisy USER chain can still look valid. The safer design
is to require a known JEDEC manufacturer or a repeated identical RDID response
before changing _user_instruction or promoting _soj_is_v2.
The diagnostic is not yet a CLI feature¶
run_spi_debug() is conditionally compiled by ENABLE_SPI_DEBUG, but there
is no main.cpp option or call site that exposes the documented
--spi-debug command. It is currently a buildable helper, not an end-to-end
user command.
RTL test coverage is absent¶
spiOverJtag_core.v has the expected short/extended header states and an
intentional infinite-loop mode, but there is no checked-in SOJ testbench or
golden packet test. The header comments also do not consistently match the
actual bit fields. Add an RTL simulation that checks lengths 0, 1, 31, 32,
and a long read/write, including USER4 version capture.
Validation available in this environment¶
g++ -std=gnu++17 -Wall -Wextra -Wpedantic -DDATA_DIR=... -fsyntax-only src/vendors/xilinx.cpp— passed; existing C++20 designated-initializer warnings remain inspiFlashdb.hpp.- The optional diagnostic translation unit passes the same syntax check.
python3 tests/test_intel_hex_writer.py— all tests passed.git diff --check— passed.- Full CMake build, RTL simulation, and physical HS3/XPCU transfers were not available because CMake/Verilog simulators and JTAG hardware are absent in this runtime.
For comparison with the upstream implementation, see the
SOJ implementation at commit 24e46d1
and the upstream
SOJ RTL.