Support customization of FPGA image loading mechanism.

This commit is contained in:
Martin Ling
2026-03-16 15:29:15 +00:00
parent 8188e84784
commit 431becb190
6 changed files with 78 additions and 36 deletions

View File

@@ -44,6 +44,19 @@ struct fpga_driver_t {
uint8_t regs_dirty;
};
struct fpga_loader_t {
/* Start address added as an offset to all read() calls. */
uint32_t start_addr;
/* Any one-off setup needed before calling read(). May be NULL. */
void (*setup)(void);
/* Read data from the specified address. */
void (*read)(uint32_t addr, uint32_t len, uint8_t* const data);
/* Buffer to use for compressed data (4096 bytes). */
uint8_t* in_buffer;
/* Buffer to use for decompressed data (4096 bytes). */
uint8_t* out_buffer;
};
/* Initialize the loaded bitstream's registers to their default values. */
extern void fpga_init(fpga_driver_t* const drv);
@@ -75,7 +88,7 @@ void fpga_set_prbs_enable(fpga_driver_t* const drv, const bool enable);
void fpga_set_tx_nco_enable(fpga_driver_t* const drv, const bool enable);
void fpga_set_tx_nco_pstep(fpga_driver_t* const drv, const uint8_t phase_increment);
bool fpga_image_load(unsigned int index);
bool fpga_image_load(struct fpga_loader_t* loader, unsigned int index);
bool fpga_spi_selftest(void);
bool fpga_sgpio_selftest(void);
bool fpga_if_xcvr_selftest(void);

View File

@@ -21,31 +21,28 @@
#include "hackrf_core.h"
#include "lz4_blk.h"
#include "lz4_buf.h"
#include "selftest.h"
// FPGA bitstreams blob.
extern uint32_t _binary_fpga_bin_start;
extern uint32_t _binary_fpga_bin_end;
extern uint32_t _binary_fpga_bin_size;
#include "fpga.h"
struct fpga_image_read_ctx {
struct fpga_loader_t* loader;
uint32_t addr;
size_t next_block_sz;
uint8_t init_flag;
};
static size_t fpga_image_read_block_cb(void* _ctx, uint8_t* out_buffer)
static size_t fpga_image_read_block_cb(void* _ctx)
{
// Assume out_buffer is 4KB
struct fpga_image_read_ctx* ctx = _ctx;
struct fpga_loader_t* loader = ctx->loader;
size_t block_sz = ctx->next_block_sz;
uint8_t block_sz_buf[2];
// first iteration: read first block size
if (ctx->init_flag == 0) {
w25q80bv_read(&spi_flash, ctx->addr, 2, block_sz_buf);
loader->read(ctx->addr, 2, block_sz_buf);
block_sz = block_sz_buf[0] | block_sz_buf[1] << 8;
ctx->addr += 2;
ctx->init_flag = 1;
@@ -56,48 +53,42 @@ static size_t fpga_image_read_block_cb(void* _ctx, uint8_t* out_buffer)
return 0;
// Read compressed block (and the next block size) from flash.
w25q80bv_read(&spi_flash, ctx->addr, block_sz, lz4_in_buf);
loader->read(ctx->addr, block_sz, loader->in_buffer);
ctx->addr += block_sz;
w25q80bv_read(&spi_flash, ctx->addr, 2, block_sz_buf);
loader->read(ctx->addr, 2, block_sz_buf);
ctx->next_block_sz = block_sz_buf[0] | block_sz_buf[1] << 8;
ctx->addr += 2;
// Decompress block.
return lz4_blk_decompress(lz4_in_buf, out_buffer, block_sz);
return lz4_blk_decompress(loader->in_buffer, loader->out_buffer, block_sz);
}
bool fpga_image_load(unsigned int index)
bool fpga_image_load(struct fpga_loader_t* loader, unsigned int index)
{
#if defined(DFU_MODE) || defined(RAM_MODE)
selftest.fpga_image_load = SKIPPED;
selftest.report.pass = false;
return false;
#endif
// TODO: do SPI setup and read number of bitstreams once!
// Prepare for SPI flash access.
spi_bus_start(spi_flash.bus, &ssp_config_w25q80bv);
w25q80bv_setup(&spi_flash);
if (loader->setup != NULL)
loader->setup();
// Read number of bitstreams from flash.
// Check the bitstream exists, and extract its offset.
uint32_t addr = (uint32_t) &_binary_fpga_bin_start;
uint32_t addr = loader->start_addr;
uint32_t num_bitstreams, bitstream_offset;
w25q80bv_read(&spi_flash, addr, 4, (uint8_t*) &num_bitstreams);
loader->read(addr, 4, (uint8_t*) &num_bitstreams);
if (index >= num_bitstreams)
return false;
w25q80bv_read(&spi_flash, addr + 4 * (index + 1), 4, (uint8_t*) &bitstream_offset);
loader->read(addr + 4 * (index + 1), 4, (uint8_t*) &bitstream_offset);
// A callback function is used by the FPGA programmer
// to obtain consecutive gateware chunks.
ice40_spi_target_init(&ice40);
ssp1_set_mode_ice40();
struct fpga_image_read_ctx fpga_image_ctx = {
.addr = (uint32_t) &_binary_fpga_bin_start + bitstream_offset,
.loader = loader,
.addr = loader->start_addr + bitstream_offset,
};
const bool success = ice40_spi_syscfg_program(
&ice40,
loader->out_buffer,
fpga_image_read_block_cb,
&fpga_image_ctx);
ssp1_set_mode_max283x();

View File

@@ -82,7 +82,8 @@ static uint32_t spi_ssp1_transfer_word(const uint32_t data)
bool ice40_spi_syscfg_program(
ice40_spi_driver_t* const drv,
size_t (*read_block_cb)(void* ctx, uint8_t* buffer),
uint8_t* buf,
size_t (*read_block_cb)(void* ctx),
void* read_ctx)
{
// Drive CRESET_B = 0, SPI_SS = 0, SPI_SCK = 1.
@@ -107,11 +108,11 @@ bool ice40_spi_syscfg_program(
// first, on falling edge of SPI_SCK.
gpio_clear(drv->gpio_select);
for (;;) {
size_t read_sz = read_block_cb(read_ctx, lz4_out_buf);
size_t read_sz = read_block_cb(read_ctx);
if (read_sz == 0)
break;
for (size_t j = 0; j < read_sz; j++) {
spi_ssp1_transfer_word(lz4_out_buf[j]);
spi_ssp1_transfer_word(buf[j]);
}
}

View File

@@ -42,7 +42,8 @@ uint8_t ice40_spi_read(ice40_spi_driver_t* const drv, uint8_t r);
void ice40_spi_write(ice40_spi_driver_t* const drv, uint8_t r, uint16_t v);
bool ice40_spi_syscfg_program(
ice40_spi_driver_t* const drv,
size_t (*read_block_cb)(void* ctx, uint8_t* buffer),
uint8_t* buf,
size_t (*read_block_cb)(void* ctx),
void* read_ctx);
#endif // __ICE40_SPI_H

View File

@@ -63,6 +63,7 @@
#include "fpga.h"
#include "selftest.h"
#include "delay.h"
#include "lz4_buf.h"
extern uint32_t __m0_start__;
extern uint32_t __m0_end__;
@@ -256,6 +257,29 @@ static void m0_rom_to_ram(void)
memcpy(dest, (uint32_t*) (base + src), len);
}
#if defined(PRALINE) && !(defined(DFU_MODE) || defined(RAM_MODE))
extern uint32_t _binary_fpga_bin_start;
void fpga_loader_setup(void)
{
spi_bus_start(spi_flash.bus, &ssp_config_w25q80bv);
w25q80bv_setup(&spi_flash);
}
void fpga_loader_read(uint32_t addr, uint32_t size, uint8_t* buf)
{
w25q80bv_read(&spi_flash, addr, size, buf);
}
struct fpga_loader_t fpga_loader = {
.start_addr = (uint32_t) &_binary_fpga_bin_start,
.setup = fpga_loader_setup,
.read = fpga_loader_read,
.in_buffer = lz4_in_buf,
.out_buffer = lz4_out_buf,
};
#endif
int main(void)
{
// Copy M0 image from ROM before SPIFI is disabled
@@ -315,7 +339,12 @@ int main(void)
halt_and_flash(6000000);
}
#else
fpga_image_load(0);
#if defined(DFU_MODE) || defined(RAM_MODE)
selftest.fpga_image_load = SKIPPED;
selftest.report.pass = false;
#else
fpga_image_load(&fpga_loader, 0);
#endif
delay_us_at_mhz(100, 204);
fpga_spi_selftest();
fpga_sgpio_selftest();

View File

@@ -24,6 +24,7 @@
#include "usb_api_praline.h"
#include "usb_queue.h"
#include <hackrf_core.h>
#include <fpga.h>
#include <stddef.h>
@@ -71,17 +72,23 @@ usb_request_status_t usb_vendor_request_set_narrowband_filter(
return USB_REQUEST_STATUS_OK;
}
bool fpga_image_load(unsigned int index);
usb_request_status_t usb_vendor_request_set_fpga_bitstream(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage)
{
#if defined(DFU_MODE) || defined(RAM_MODE)
(void) endpoint;
(void) stage;
return USB_REQUEST_STATUS_STALL;
#else
extern struct fpga_loader_t fpga_loader;
if (stage == USB_TRANSFER_STAGE_SETUP) {
if (!fpga_image_load(endpoint->setup.value)) {
if (!fpga_image_load(&fpga_loader, endpoint->setup.value)) {
return USB_REQUEST_STATUS_STALL;
}
usb_transfer_schedule_ack(endpoint->in);
}
return USB_REQUEST_STATUS_OK;
}
#endif
}