bscan_spi: implement bscan_spi_xfer over the jtagspi proxy

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.
This commit is contained in:
2026-05-23 17:16:19 +02:00
parent ba9372c8b2
commit 0c9cc679f1
2 changed files with 88 additions and 15 deletions

View File

@@ -261,17 +261,86 @@ int bscan_load_bitstream_file(jtag_core *jc, const fpga_target *t,
return ret;
}
/* Pipeline latency between a MOSI bit going in and its MISO bit
* appearing on TDO, in TCK cycles. For a single-device chain this is
* one (the proxy registers TDO); equals jtag_tap_count_enabled() in
* OpenOCD's jtagspi. The header asserts a single-device chain. */
#define BSCAN_SPI_READ_LATENCY 1
int bscan_spi_xfer(jtag_core *jc, const fpga_target *t,
const uint8_t *tx, uint8_t *rx, size_t nbytes)
const uint8_t *tx, size_t txlen,
uint8_t *rx, size_t rxlen)
{
/* Skeleton — to be completed once we can validate the protocol
* against an actual quartiq jtagspi proxy bitstream.
*
* Expected outline:
* - bscan_set_ir(jc, t->ir_user1, t->ir_length)
* - bscan_shift_dr with <header><payload>, where the header
* encodes the bit count and CS state per the proxy convention.
* - extract MISO bytes from the TDO data. */
(void)jc; (void)t; (void)tx; (void)rx; (void)nbytes;
/* DR frame (quartiq/OpenOCD jtagspi proxy, single device):
* marker(1)=1 | count(32, MSB-first) | MOSI(txlen*8, MSB-first/byte)
* | latency skip | MISO capture(rxlen*8, MSB-first/byte)
* count = total SPI bits - 1. The skip absorbs the TDO pipeline
* delay so the captured MISO aligns to byte boundaries.
* Bits are placed LSB-first per byte, the layout bscan_shift_dr
* shifts in order; ordering them here gives MSB-first on the wire. */
size_t spi_bytes = txlen + rxlen;
uint32_t count;
int total_bits, dr_bytes, capture_start, bit, j;
size_t i;
uint8_t *dr_out, *dr_in;
if (!drv_ok(jc) || !t || !t->ir_user1 || spi_bytes == 0) return -1;
if (txlen && !tx) return -1;
if (rxlen && !rx) return -1;
count = (uint32_t)(spi_bytes * 8u) - 1u;
total_bits = 1 + 32 + (int)txlen * 8;
if (rxlen) total_bits += BSCAN_SPI_READ_LATENCY + (int)rxlen * 8;
dr_bytes = (total_bits + 7) / 8;
dr_out = calloc(1, (size_t)dr_bytes);
dr_in = rxlen ? calloc(1, (size_t)dr_bytes) : NULL;
if (!dr_out || (rxlen && !dr_in)) { free(dr_out); free(dr_in); return -1; }
#define BS_SET(buf, pos) ((buf)[(pos) >> 3] |= (uint8_t)(1u << ((pos) & 7)))
#define BS_GET(buf, pos) (((buf)[(pos) >> 3] >> ((pos) & 7)) & 1u)
bit = 0;
BS_SET(dr_out, bit); bit++; /* marker = 1 */
for (j = 31; j >= 0; j--) { /* count, MSB-first */
if (count & (1u << j)) BS_SET(dr_out, bit);
bit++;
}
for (i = 0; i < txlen; i++) { /* MOSI, MSB-first/byte */
for (j = 7; j >= 0; j--) {
if (tx[i] & (1u << j)) BS_SET(dr_out, bit);
bit++;
}
}
capture_start = -1;
if (rxlen) {
bit += BSCAN_SPI_READ_LATENCY; /* skip pipeline delay */
capture_start = bit;
bit += (int)rxlen * 8; /* MISO region (MOSI=0) */
}
if (bscan_set_ir(jc, t->ir_user1, t->ir_length) < 0 ||
bscan_shift_dr(jc, dr_out, dr_in, total_bits) < 0) {
free(dr_out); free(dr_in);
return -1;
}
if (rxlen) {
memset(rx, 0, rxlen);
for (i = 0; i < rxlen * 8; i++) {
if (BS_GET(dr_in, capture_start + (int)i)) {
rx[i >> 3] |= (uint8_t)(1u << (7 - (i & 7))); /* MSB-first */
}
}
}
#undef BS_SET
#undef BS_GET
free(dr_out);
free(dr_in);
return 0;
}

View File

@@ -50,10 +50,14 @@ int bscan_load_bitstream(jtag_core *jc, const fpga_target *t,
int bscan_load_bitstream_file(jtag_core *jc, const fpga_target *t,
const char *path);
/* Transfer `nbytes` of SPI data through the BSCAN proxy (USER1 DR).
* Placeholder: protocol details deferred until an actual proxy
* bitstream is available for testing. */
/* One CS-framed SPI transaction through the BSCAN proxy (USER1 DR):
* clock out `txlen` MOSI bytes (e.g. command + address + write data),
* then read `rxlen` MISO bytes into `rx`. Either length may be 0.
* Bytes are MSB-first on the wire; bit-order juggling is internal.
* Follows the quartiq/OpenOCD jtagspi proxy framing. Single-device
* chain only. Requires a proxy bitstream already loaded (USER1 live). */
int bscan_spi_xfer(jtag_core *jc, const fpga_target *t,
const uint8_t *tx, uint8_t *rx, size_t nbytes);
const uint8_t *tx, size_t txlen,
uint8_t *rx, size_t rxlen);
#endif