Add support for HackRF Pro (code name: Praline)

Co-authored-by: mndza <diego.hdmp@gmail.com>
Co-authored-by: Martin Ling <martin-git@earth.li>
Co-authored-by: Antoine van Gelder <antoine@greatscottgadgets.com>
This commit is contained in:
Michael Ossmann
2025-11-24 20:53:41 -05:00
parent 390837715b
commit 409acbc3c9
102 changed files with 10548 additions and 367 deletions

View File

@@ -159,7 +159,7 @@ jobs:
strategy:
matrix:
os: ['macos', 'ubuntu', 'windows']
board: ['HACKRF_ONE', 'JAWBREAKER', 'RAD1O']
board: ['HACKRF_ONE', 'JAWBREAKER', 'RAD1O', 'PRALINE']
cmake: ['3.10.0', 'latest']
exclude:
- os: 'windows'

View File

@@ -27,8 +27,13 @@ int main(void)
detect_hardware_platform();
pin_setup();
#ifndef PRALINE
/* enable 1V8 power supply so that the 1V8 LED lights up */
enable_1v8_power();
#else
/* enable 1V2 power supply so that the 3V3FPGA LED lights up */
enable_1v2_power();
#endif
/* Blink LED1/2/3 on the board. */
while (1)

View File

@@ -25,7 +25,7 @@
MEMORY
{
/* rom is really the shadow region that points to SPI flash or elsewhere */
rom (rx) : ORIGIN = 0x00000000, LENGTH = 96K
rom (rx) : ORIGIN = 0x00000000, LENGTH = 1M
ram_local1 (rwx) : ORIGIN = 0x10000000, LENGTH = 96K
ram_local2 (rwx) : ORIGIN = 0x10080000, LENGTH = 32K
ram_sleep (rwx) : ORIGIN = 0x10088000, LENGTH = 8K

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2012-2025 Great Scott Gadgets <info@greatscottgadgets.com>
* Copyright 2012 Jared Boone <jared@sharebrained.com>
*
* This file is part of HackRF
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
SECTIONS
{
/* ROM-only section */
.rom_only : {
. = ALIGN(4);
KEEP(*(.rom_only))
. = ALIGN(4);
} > rom
}

View File

@@ -25,9 +25,11 @@
#include <libopencm3/lpc43xx/scu.h>
#include <stdint.h>
#ifndef PRALINE
static refill_buffer_cb refill_buffer;
static uint32_t xsvf_buffer_len, xsvf_pos;
static unsigned char* xsvf_buffer;
#endif
void cpld_jtag_take(jtag_t* const jtag)
{
@@ -36,22 +38,26 @@ void cpld_jtag_take(jtag_t* const jtag)
/* Set initial GPIO state to the voltages of the internal or external pull-ups/downs,
* to avoid any glitches.
*/
#ifdef HACKRF_ONE
#if (defined HACKRF_ONE || defined PRALINE)
gpio_set(gpio->gpio_pp_tms);
#endif
gpio_clear(gpio->gpio_tck);
#ifndef PRALINE
gpio_set(gpio->gpio_tms);
gpio_set(gpio->gpio_tdi);
gpio_clear(gpio->gpio_tck);
#endif
#ifdef HACKRF_ONE
#if (defined HACKRF_ONE || defined PRALINE)
/* Do not drive PortaPack-specific TMS pin initially, just to be cautious. */
gpio_input(gpio->gpio_pp_tms);
gpio_input(gpio->gpio_pp_tdo);
#endif
gpio_output(gpio->gpio_tck);
#ifndef PRALINE
gpio_output(gpio->gpio_tms);
gpio_output(gpio->gpio_tdi);
gpio_output(gpio->gpio_tck);
gpio_input(gpio->gpio_tdo);
#endif
}
void cpld_jtag_release(jtag_t* const jtag)
@@ -61,17 +67,20 @@ void cpld_jtag_release(jtag_t* const jtag)
/* Make all pins inputs when JTAG interface not active.
* Let the pull-ups/downs do the work.
*/
#ifdef HACKRF_ONE
#if (defined HACKRF_ONE || defined PRALINE)
/* Do not drive PortaPack-specific pins, initially, just to be cautious. */
gpio_input(gpio->gpio_pp_tms);
gpio_input(gpio->gpio_pp_tdo);
#endif
gpio_input(gpio->gpio_tck);
#ifndef PRALINE
gpio_input(gpio->gpio_tms);
gpio_input(gpio->gpio_tdi);
gpio_input(gpio->gpio_tck);
gpio_input(gpio->gpio_tdo);
#endif
}
#ifndef PRALINE
/* return 0 if success else return error code see xsvfExecute() */
int cpld_jtag_program(
jtag_t* const jtag,
@@ -102,3 +111,4 @@ unsigned char cpld_jtag_get_next_byte(void)
xsvf_pos++;
return byte;
}
#endif

View File

@@ -27,11 +27,13 @@
#include "gpio.h"
typedef struct jtag_gpio_t {
gpio_t gpio_tms;
gpio_t gpio_tck;
#ifndef PRALINE
gpio_t gpio_tms;
gpio_t gpio_tdi;
gpio_t gpio_tdo;
#ifdef HACKRF_ONE
#endif
#if (defined HACKRF_ONE || defined PRALINE)
gpio_t gpio_pp_tms;
gpio_t gpio_pp_tdo;
#endif

View File

@@ -33,6 +33,8 @@
#define SUPPORTED_PLATFORM (PLATFORM_HACKRF1_OG | PLATFORM_HACKRF1_R9)
#elif RAD1O
#define SUPPORTED_PLATFORM PLATFORM_RAD1O
#elif PRALINE
#define SUPPORTED_PLATFORM PLATFORM_PRALINE
#else
#define SUPPORTED_PLATFORM 0
#endif

227
firmware/common/fpga.c Normal file
View File

@@ -0,0 +1,227 @@
/*
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include <stdbool.h>
#include "hackrf_core.h"
#include "ice40_spi.h"
#include "lz4_blk.h"
#include "m0_state.h"
#include "streaming.h"
#include "rf_path.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;
// USB buffer used during selftests.
#define USB_BULK_BUFFER_SIZE 0x8000
extern uint8_t usb_bulk_buffer[USB_BULK_BUFFER_SIZE];
struct fpga_image_read_ctx {
uint32_t addr;
size_t next_block_sz;
uint8_t init_flag;
uint8_t buffer[4096 + 2];
};
static size_t fpga_image_read_block_cb(void* _ctx, uint8_t* out_buffer)
{
// Assume out_buffer is 4KB
struct fpga_image_read_ctx* ctx = _ctx;
size_t block_sz = ctx->next_block_sz;
// first iteration: read first block size
if (ctx->init_flag == 0) {
w25q80bv_read(&spi_flash, ctx->addr, 2, ctx->buffer);
block_sz = ctx->buffer[0] | (ctx->buffer[1] << 8);
ctx->addr += 2;
ctx->init_flag = 1;
}
// finish at end marker
if (block_sz == 0)
return 0;
// Read compressed block (and the next block size) from flash.
w25q80bv_read(&spi_flash, ctx->addr, block_sz + 2, ctx->buffer);
ctx->addr += block_sz + 2;
ctx->next_block_sz = ctx->buffer[block_sz] | (ctx->buffer[block_sz + 1] << 8);
// Decompress block.
return lz4_blk_decompress(ctx->buffer, out_buffer, block_sz);
}
bool fpga_image_load(unsigned int index)
{
#if defined(DFU_MODE) || defined(RAM_MODE)
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);
// 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 num_bitstreams, bitstream_offset;
w25q80bv_read(&spi_flash, 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);
// A callback function is used by the FPGA programmer
// to obtain consecutive gateware chunks.
ssp1_set_mode_ice40();
ice40_spi_target_init(&ice40);
struct fpga_image_read_ctx fpga_image_ctx = {
.addr = (uint32_t) &_binary_fpga_bin_start + bitstream_offset,
};
const bool success = ice40_spi_syscfg_program(
&ice40,
fpga_image_read_block_cb,
&fpga_image_ctx);
ssp1_set_mode_max283x();
return success;
}
static uint8_t lfsr_advance(uint8_t v)
{
const uint8_t feedback = ((v >> 3) ^ (v >> 4) ^ (v >> 5) ^ (v >> 7)) & 1;
return (v << 1) | feedback;
}
bool fpga_sgpio_selftest()
{
#if defined(DFU_MODE) || defined(RAM_MODE)
return true;
#endif
// Enable PRBS mode.
ssp1_set_mode_ice40();
ice40_spi_write(&ice40, 0x01, 0x40);
ssp1_set_mode_max283x();
// Stream 512 samples from the FPGA.
sgpio_configure(&sgpio_config, SGPIO_DIRECTION_RX);
m0_set_mode(M0_MODE_RX);
m0_state.shortfall_limit = 0;
baseband_streaming_enable(&sgpio_config);
while (m0_state.m0_count < 512)
;
baseband_streaming_disable(&sgpio_config);
m0_set_mode(M0_MODE_IDLE);
// Disable PRBS mode.
ssp1_set_mode_ice40();
ice40_spi_write(&ice40, 0x01, 0);
ssp1_set_mode_max283x();
// Generate sequence from first value and compare.
bool seq_in_sync = true;
uint8_t seq = lfsr_advance(usb_bulk_buffer[0]);
for (int i = 1; i < 512; ++i) {
if (usb_bulk_buffer[i] != seq) {
seq_in_sync = false;
break;
}
seq = lfsr_advance(seq);
}
// Update selftest result.
selftest.sgpio_rx_ok = seq_in_sync;
if (!selftest.sgpio_rx_ok) {
selftest.report.pass = false;
}
return selftest.sgpio_rx_ok;
}
bool fpga_if_xcvr_selftest()
{
#if defined(DFU_MODE) || defined(RAM_MODE)
return true;
#endif
const size_t num_samples = USB_BULK_BUFFER_SIZE / 2;
// Set gateware features for the test.
ssp1_set_mode_ice40();
ice40_spi_write(&ice40, 0x01, 0x1); // RX DC block
ice40_spi_write(&ice40, 0x05, 128); // NCO phase increment
ice40_spi_write(&ice40, 0x03, 1); // NCO TX enable
ssp1_set_mode_max283x();
// Configure RX calibration path and settle for 1ms.
rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_RX_CALIBRATION);
delay_us_at_mhz(1000, 204);
// Stream samples from the FPGA.
m0_set_mode(M0_MODE_RX);
m0_state.shortfall_limit = 0;
baseband_streaming_enable(&sgpio_config);
while (m0_state.m0_count < num_samples)
;
baseband_streaming_disable(&sgpio_config);
m0_set_mode(M0_MODE_IDLE);
rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_OFF);
// Gateware default settings.
ssp1_set_mode_ice40();
ice40_spi_write(&ice40, 0x01, 0);
ice40_spi_write(&ice40, 0x03, 0);
ssp1_set_mode_max283x();
// Count zero crossings in the received samples.
// N/2 samples/channel * 2 zcs/cycle / 8 samples/cycle = N/8 zcs/channel
unsigned int expected_zcs = num_samples / 8;
unsigned int zcs_i = 0;
unsigned int zcs_q = 0;
uint8_t last_sign_i = 0;
uint8_t last_sign_q = 0;
for (size_t i = 0; i < num_samples; i += 2) {
uint8_t sign_i = (usb_bulk_buffer[i] & 0x80) ? 1 : 0;
uint8_t sign_q = (usb_bulk_buffer[i + 1] & 0x80) ? 1 : 0;
zcs_i += sign_i ^ last_sign_i;
zcs_q += sign_q ^ last_sign_q;
last_sign_i = sign_i;
last_sign_q = sign_q;
}
// Allow a zero crossings counting error of +-5%.
bool i_in_range = (zcs_i > expected_zcs * 0.95) && (zcs_i < expected_zcs * 1.05);
bool q_in_range = (zcs_q > expected_zcs * 0.95) && (zcs_q < expected_zcs * 1.05);
// Update selftest result.
selftest.xcvr_loopback_ok = i_in_range && q_in_range;
if (!selftest.xcvr_loopback_ok) {
selftest.report.pass = false;
}
return selftest.xcvr_loopback_ok;
}

31
firmware/common/fpga.h Normal file
View File

@@ -0,0 +1,31 @@
/*
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __FPGA_H
#define __FPGA_H
#include <stdbool.h>
bool fpga_image_load(unsigned int index);
bool fpga_sgpio_selftest();
bool fpga_if_xcvr_selftest();
#endif // __FPGA_H

View File

@@ -26,6 +26,8 @@
#include "sgpio.h"
#include "si5351c.h"
#include "spi_ssp.h"
#include "max2831.h"
#include "max2831_target.h"
#include "max283x.h"
#include "max5864.h"
#include "max5864_target.h"
@@ -34,14 +36,17 @@
#include "i2c_bus.h"
#include "i2c_lpc.h"
#include "cpld_jtag.h"
#include "ice40_spi.h"
#include "platform_detect.h"
#include "clkin.h"
#include "selftest.h"
#include <libopencm3/lpc43xx/cgu.h>
#include <libopencm3/lpc43xx/ccu.h>
#include <libopencm3/lpc43xx/scu.h>
#include <libopencm3/lpc43xx/ssp.h>
#include <libopencm3/lpc43xx/creg.h>
#ifdef HACKRF_ONE
#if (defined HACKRF_ONE || defined PRALINE)
#include "portapack.h"
#endif
@@ -55,16 +60,32 @@ static struct gpio_t gpio_led[] = {
#ifdef RAD1O
GPIO(5, 26),
#endif
#ifdef PRALINE
GPIO(4, 6),
#endif
};
// clang-format off
#ifndef PRALINE
static struct gpio_t gpio_1v8_enable = GPIO(3, 6);
#else
static struct gpio_t gpio_1v2_enable = GPIO(4, 7);
static struct gpio_t gpio_3v3aux_enable_n = GPIO(5, 15);
#endif
/* MAX283x GPIO (XCVR_CTL) PinMux */
/* MAX283x GPIO (XCVR_CTL / CS_XCVR) PinMux */
#ifdef PRALINE
static struct gpio_t gpio_max283x_select = GPIO(6, 28);
#else
static struct gpio_t gpio_max283x_select = GPIO(0, 15);
#endif
/* MAX5864 SPI chip select (AD_CS) GPIO PinMux */
/* MAX5864 SPI chip select (AD_CS / CS_AD) GPIO PinMux */
#ifdef PRALINE
static struct gpio_t gpio_max5864_select = GPIO(6, 30);
#else
static struct gpio_t gpio_max5864_select = GPIO(2, 7);
#endif
/* RFFC5071 GPIO serial interface PinMux */
// #ifdef RAD1O
@@ -78,6 +99,9 @@ static struct gpio_t gpio_max5864_select = GPIO(2, 7);
#ifdef HACKRF_ONE
static struct gpio_t gpio_vaa_disable = GPIO(2, 9);
#endif
#ifdef PRALINE
static struct gpio_t gpio_vaa_disable = GPIO(4, 1);
#endif
#ifdef RAD1O
static struct gpio_t gpio_vaa_enable = GPIO(2, 9);
#endif
@@ -115,10 +139,23 @@ static struct gpio_t gpio_low_high_filt_n = GPIO(2, 12);
static struct gpio_t gpio_tx_amp = GPIO(2, 15);
static struct gpio_t gpio_rx_lna = GPIO(5, 15);
#endif
#ifdef PRALINE
static struct gpio_t gpio_tx_en = GPIO(3, 4);
static struct gpio_t gpio_mix_en_n = GPIO(3, 2);
static struct gpio_t gpio_mix_en_n_r1_0 = GPIO(5, 6);
static struct gpio_t gpio_lpf_en = GPIO(4, 8);
static struct gpio_t gpio_rf_amp_en = GPIO(4, 9);
static struct gpio_t gpio_ant_bias_en_n = GPIO(1, 12);
#endif
/* CPLD JTAG interface GPIO pins */
static struct gpio_t gpio_cpld_tdo = GPIO(5, 18);
/* CPLD JTAG interface GPIO pins, FPGA config pins in Praline */
static struct gpio_t gpio_cpld_tck = GPIO(3, 0);
#ifdef PRALINE
static struct gpio_t gpio_fpga_cfg_creset = GPIO(2, 11);
static struct gpio_t gpio_fpga_cfg_cdone = GPIO(5, 14);
static struct gpio_t gpio_fpga_cfg_spi_cs = GPIO(2, 10);
#else
static struct gpio_t gpio_cpld_tdo = GPIO(5, 18);
#if (defined HACKRF_ONE || defined RAD1O)
static struct gpio_t gpio_cpld_tms = GPIO(3, 4);
static struct gpio_t gpio_cpld_tdi = GPIO(3, 1);
@@ -126,14 +163,17 @@ static struct gpio_t gpio_cpld_tdi = GPIO(3, 1);
static struct gpio_t gpio_cpld_tms = GPIO(3, 1);
static struct gpio_t gpio_cpld_tdi = GPIO(3, 4);
#endif
#endif
#ifdef HACKRF_ONE
#if (defined HACKRF_ONE || defined PRALINE)
static struct gpio_t gpio_cpld_pp_tms = GPIO(1, 1);
static struct gpio_t gpio_cpld_pp_tdo = GPIO(1, 8);
#endif
/* other CPLD interface GPIO pins */
#ifndef PRALINE
static struct gpio_t gpio_hw_sync_enable = GPIO(5, 12);
#endif
static struct gpio_t gpio_q_invert = GPIO(0, 13);
/* HackRF One r9 */
@@ -143,6 +183,19 @@ static struct gpio_t gpio_h1r9_1v8_enable = GPIO(2, 9);
static struct gpio_t gpio_h1r9_vaa_disable = GPIO(3, 6);
static struct gpio_t gpio_h1r9_hw_sync_enable = GPIO(5, 5);
#endif
#ifdef PRALINE
static struct gpio_t gpio_p2_ctrl0 = GPIO(7, 3);
static struct gpio_t gpio_p2_ctrl1 = GPIO(7, 4);
static struct gpio_t gpio_p1_ctrl0 = GPIO(0, 14);
static struct gpio_t gpio_p1_ctrl1 = GPIO(5, 16);
static struct gpio_t gpio_p1_ctrl2 = GPIO(3, 5);
static struct gpio_t gpio_clkin_ctrl = GPIO(0, 15);
static struct gpio_t gpio_aa_en = GPIO(1, 7);
static struct gpio_t gpio_trigger_in = GPIO(6, 26);
static struct gpio_t gpio_trigger_out = GPIO(5, 6);
static struct gpio_t gpio_pps_out = GPIO(5, 5);
#endif
// clang-format on
i2c_bus_t i2c0 = {
@@ -172,6 +225,45 @@ si5351c_driver_t clock_gen = {
.i2c_address = 0x60,
};
spi_bus_t spi_bus_ssp1 = {
.obj = (void*) SSP1_BASE,
.config = &ssp_config_max5864,
.start = spi_ssp_start,
.stop = spi_ssp_stop,
.transfer = spi_ssp_transfer,
.transfer_gather = spi_ssp_transfer_gather,
};
#ifdef PRALINE
const ssp_config_t ssp_config_max283x = {
/* FIXME speed up once everything is working reliably */
/*
// Freq About 0.0498MHz / 49.8KHz => Freq = PCLK / (CPSDVSR * [SCR+1]) with PCLK=PLL1=204MHz
const uint8_t serial_clock_rate = 32;
const uint8_t clock_prescale_rate = 128;
*/
// Freq About 4.857MHz => Freq = PCLK / (CPSDVSR * [SCR+1]) with PCLK=PLL1=204MHz
.data_bits = SSP_DATA_9BITS, // send 2 words
.serial_clock_rate = 21,
.clock_prescale_rate = 2,
.gpio_select = &gpio_max283x_select,
};
static struct gpio_t gpio_max2831_enable = GPIO(7, 1);
static struct gpio_t gpio_max2831_rx_enable = GPIO(7, 2);
static struct gpio_t gpio_max2831_rxhp = GPIO(6, 29);
static struct gpio_t gpio_max2831_ld = GPIO(4, 11);
max2831_driver_t max283x = {
.bus = &spi_bus_ssp1,
.gpio_enable = &gpio_max2831_enable,
.gpio_rxtx = &gpio_max2831_rx_enable,
.gpio_rxhp = &gpio_max2831_rxhp,
.gpio_ld = &gpio_max2831_ld,
.target_init = max2831_target_init,
.set_mode = max2831_target_set_mode,
};
#else
const ssp_config_t ssp_config_max283x = {
/* FIXME speed up once everything is working reliably */
/*
@@ -186,6 +278,9 @@ const ssp_config_t ssp_config_max283x = {
.gpio_select = &gpio_max283x_select,
};
max283x_driver_t max283x = {};
#endif
const ssp_config_t ssp_config_max5864 = {
/* FIXME speed up once everything is working reliably */
/*
@@ -200,17 +295,6 @@ const ssp_config_t ssp_config_max5864 = {
.gpio_select = &gpio_max5864_select,
};
spi_bus_t spi_bus_ssp1 = {
.obj = (void*) SSP1_BASE,
.config = &ssp_config_max5864,
.start = spi_ssp_start,
.stop = spi_ssp_stop,
.transfer = spi_ssp_transfer,
.transfer_gather = spi_ssp_transfer_gather,
};
max283x_driver_t max283x = {};
max5864_driver_t max5864 = {
.bus = &spi_bus_ssp1,
.target_init = max5864_target_init,
@@ -241,10 +325,60 @@ w25q80bv_driver_t spi_flash = {
sgpio_config_t sgpio_config = {
.gpio_q_invert = &gpio_q_invert,
#ifndef PRALINE
.gpio_hw_sync_enable = &gpio_hw_sync_enable,
#endif
.slice_mode_multislice = true,
};
#ifdef PRALINE
const ssp_config_t ssp_config_ice40_fpga = {
.data_bits = SSP_DATA_8BITS,
.spi_mode = SSP_CPOL_1_CPHA_1,
.serial_clock_rate = 21,
.clock_prescale_rate = 2,
.gpio_select = &gpio_fpga_cfg_spi_cs,
};
ice40_spi_driver_t ice40 = {
.bus = &spi_bus_ssp1,
.gpio_select = &gpio_fpga_cfg_spi_cs,
.gpio_creset = &gpio_fpga_cfg_creset,
.gpio_cdone = &gpio_fpga_cfg_cdone,
};
#endif
radio_t radio = {
.channel[RADIO_CHANNEL0] =
{
.id = RADIO_CHANNEL0,
.config =
{
.sample_rate[RADIO_SAMPLE_RATE_CLOCKGEN] =
{.hz = 0},
.filter[RADIO_FILTER_BASEBAND] = {.hz = 0},
.frequency[RADIO_FREQUENCY_RF] =
{
.hz = 0,
.if_hz = 0,
.lo_hz = 0,
.path = 0,
},
.gain[RADIO_GAIN_RF_AMP] = {.enable = 0},
.gain[RADIO_GAIN_RX_LNA] = {.db = 0},
.gain[RADIO_GAIN_RX_VGA] = {.db = 0},
.gain[RADIO_GAIN_TX_VGA] = {.db = 0},
.antenna[RADIO_ANTENNA_BIAS_TEE] =
{.enable = false},
.mode = TRANSCEIVER_MODE_OFF,
.clock[RADIO_CLOCK_CLKIN] = {.enable = false},
.clock[RADIO_CLOCK_CLKOUT] = {.enable = false},
.trigger_mode = HW_SYNC_MODE_OFF,
},
.clock_source = CLOCK_SOURCE_HACKRF,
},
};
rf_path_t rf_path = {
.switchctrl = 0,
#ifdef HACKRF_ONE
@@ -275,14 +409,23 @@ rf_path_t rf_path = {
.gpio_tx_amp = &gpio_tx_amp,
.gpio_rx_lna = &gpio_rx_lna,
#endif
#ifdef PRALINE
.gpio_tx_en = &gpio_tx_en,
.gpio_mix_en_n = &gpio_mix_en_n,
.gpio_lpf_en = &gpio_lpf_en,
.gpio_rf_amp_en = &gpio_rf_amp_en,
.gpio_ant_bias_en_n = &gpio_ant_bias_en_n,
#endif
};
jtag_gpio_t jtag_gpio_cpld = {
.gpio_tms = &gpio_cpld_tms,
.gpio_tck = &gpio_cpld_tck,
#ifndef PRALINE
.gpio_tms = &gpio_cpld_tms,
.gpio_tdi = &gpio_cpld_tdi,
.gpio_tdo = &gpio_cpld_tdo,
#ifdef HACKRF_ONE
#endif
#if (defined HACKRF_ONE || defined PRALINE)
.gpio_pp_tms = &gpio_cpld_pp_tms,
.gpio_pp_tdo = &gpio_cpld_pp_tdo,
#endif
@@ -406,6 +549,7 @@ bool sample_rate_frac_set(uint32_t rate_num, uint32_t rate_denom)
MSx_P2 = (128 * b) % c;
MSx_P3 = c;
#ifndef PRALINE
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
/*
* On HackRF One r9 all sample clocks are externally derived
@@ -426,6 +570,10 @@ bool sample_rate_frac_set(uint32_t rate_num, uint32_t rate_denom)
/* MS0/CLK2 is the source for SGPIO (CODEC_X2_CLK) */
si5351c_configure_multisynth(&clock_gen, 2, 0, 0, 0, 0); //p1 doesn't matter
}
#else
/* MS0/CLK0 is the source for the MAX5864/FPGA (AFE_CLK). */
si5351c_configure_multisynth(&clock_gen, 0, MSx_P1, MSx_P2, MSx_P3, 1);
#endif
if (streaming) {
sgpio_cpld_stream_enable(&sgpio_config);
@@ -486,6 +634,7 @@ bool sample_rate_set(const uint32_t sample_rate_hz)
return false;
}
#ifndef PRALINE
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
/*
* On HackRF One r9 all sample clocks are externally derived
@@ -518,22 +667,17 @@ bool sample_rate_set(const uint32_t sample_rate_hz)
1,
0); //p1 doesn't matter
}
#else
/* MS0/CLK0 is the source for the MAX5864/FPGA (AFE_CLK). */
si5351c_configure_multisynth(&clock_gen, 0, p1, p2, p3, 1);
/* MS0/CLK1 is the source for SCT_CLK (CODEC_X2_CLK). */
si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 0);
#endif
return true;
}
bool baseband_filter_bandwidth_set(const uint32_t bandwidth_hz)
{
uint32_t bandwidth_hz_real;
bandwidth_hz_real = max283x_set_lpf_bandwidth(&max283x, bandwidth_hz);
if (bandwidth_hz_real) {
hackrf_ui()->set_filter_bw(bandwidth_hz_real);
}
return bandwidth_hz_real != 0;
}
/*
Configure PLL1 (Main MCU Clock) to max speed (204MHz).
Note: PLL1 clock is used by M4/M0 core, Peripheral, APB1.
@@ -639,6 +783,16 @@ void cpu_clock_init(void)
* CLK5 -> MAX2837 (MAX2871 on rad1o)
* CLK6 -> none
* CLK7 -> LPC43xx (uses a 12MHz crystal by default)
*
* Clocks on Praline:
* CLK0 -> AFE_CLK (MAX5864/FPGA)
* CLK1 -> SCT_CLK
* CLK2 -> MCU_CLK (uses a 12MHz crystal by default)
* CLK3 -> External Clock Output (power down at boot)
* CLK4 -> XCVR_CLK (MAX2837)
* CLK5 -> MIX_CLK (RFFC5072)
* CLK6 -> AUX_CLK1
* CLK7 -> AUX_CLK2
*/
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
@@ -744,7 +898,30 @@ void cpu_clock_init(void)
CGU_BASE_SSP1_CLK =
CGU_BASE_SSP1_CLK_AUTOBLOCK(1) | CGU_BASE_SSP1_CLK_CLK_SEL(CGU_SRC_PLL1);
#if (defined JAWBREAKER || defined HACKRF_ONE)
#ifndef RAD1O
/* Enable 32kHz oscillator */
CREG_CREG0 &= ~(CREG_CREG0_PD32KHZ | CREG_CREG0_RESET32KHZ);
CREG_CREG0 |= CREG_CREG0_EN32KHZ;
/* Allow 1ms to start up. */
delay_us_at_mhz(1000, 204);
/* Use frequency monitor to check 32kHz oscillator is running. */
CGU_FREQ_MON = CGU_FREQ_MON_RCNT(511) | CGU_FREQ_MON_CLK_SEL(CGU_SRC_32K);
CGU_FREQ_MON |= CGU_FREQ_MON_MEAS_MASK;
while (CGU_FREQ_MON & CGU_FREQ_MON_MEAS_MASK)
;
uint32_t count =
(CGU_FREQ_MON & CGU_FREQ_MON_FCNT_MASK) >> CGU_FREQ_MON_FCNT_SHIFT;
// We should see a single count, because 511 cycles of the 12MHz internal
// RC oscillator corresponds to 1.39 cycles of the 32768Hz clock.
selftest.rtc_osc_ok = (count == 1);
if (!selftest.rtc_osc_ok) {
selftest.report.pass = false;
}
#endif
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
/* Disable unused clocks */
/* Start with PLLs */
CGU_PLL0AUDIO_CTRL = CGU_PLL0AUDIO_CTRL_PD(1);
@@ -815,7 +992,7 @@ void cpu_clock_init(void)
clock_source_t activate_best_clock_source(void)
{
#ifdef HACKRF_ONE
#if (defined HACKRF_ONE || defined PRALINE)
/* Ensure PortaPack reference oscillator is off while checking for external clock input. */
if (portapack_reference_oscillator && portapack()) {
portapack_reference_oscillator(false);
@@ -828,7 +1005,7 @@ clock_source_t activate_best_clock_source(void)
if (si5351c_clkin_signal_valid(&clock_gen)) {
source = CLOCK_SOURCE_EXTERNAL;
} else {
#ifdef HACKRF_ONE
#if (defined HACKRF_ONE || defined PRALINE)
/* Enable PortaPack reference oscillator (if present), and check for valid clock. */
if (portapack_reference_oscillator && portapack()) {
portapack_reference_oscillator(true);
@@ -847,6 +1024,8 @@ clock_source_t activate_best_clock_source(void)
&clock_gen,
(source == CLOCK_SOURCE_HACKRF) ? PLL_SOURCE_XTAL : PLL_SOURCE_CLKIN);
hackrf_ui()->set_clock_source(source);
radio.channel[RADIO_CHANNEL0].clock_source = source;
return source;
}
@@ -860,6 +1039,13 @@ void ssp1_set_mode_max5864(void)
spi_bus_start(max5864.bus, &ssp_config_max5864);
}
#ifdef PRALINE
void ssp1_set_mode_ice40(void)
{
spi_bus_start(&spi_bus_ssp1, &ssp_config_ice40_fpga);
}
#endif
void pin_setup(void)
{
/* Configure all GPIO as Input (safe state) */
@@ -879,14 +1065,16 @@ void pin_setup(void)
*
* LPC43xx pull-up and pull-down resistors are approximately 53K.
*/
#ifdef HACKRF_ONE
#if (defined HACKRF_ONE || defined PRALINE)
scu_pinmux(SCU_PINMUX_PP_TMS, SCU_GPIO_PUP | SCU_CONF_FUNCTION0);
scu_pinmux(SCU_PINMUX_PP_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
#endif
scu_pinmux(SCU_PINMUX_CPLD_TCK, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
#ifndef PRALINE
scu_pinmux(SCU_PINMUX_CPLD_TMS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
scu_pinmux(SCU_PINMUX_CPLD_TDI, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
scu_pinmux(SCU_PINMUX_CPLD_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION4);
scu_pinmux(SCU_PINMUX_CPLD_TCK, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
#endif
/* Configure SCU Pin Mux as GPIO */
scu_pinmux(SCU_PINMUX_LED1, SCU_GPIO_NOPULL);
@@ -895,6 +1083,9 @@ void pin_setup(void)
#ifdef RAD1O
scu_pinmux(SCU_PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION4);
#endif
#ifdef PRALINE
scu_pinmux(SCU_PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
#endif
/* Configure USB indicators */
#ifdef JAWBREAKER
@@ -902,31 +1093,52 @@ void pin_setup(void)
scu_pinmux(SCU_PINMUX_USB_LED1, SCU_CONF_FUNCTION3);
#endif
led_off(0);
led_off(1);
led_off(2);
#ifdef RAD1O
led_off(3);
#endif
gpio_output(&gpio_led[0]);
gpio_output(&gpio_led[1]);
gpio_output(&gpio_led[2]);
#ifdef RAD1O
gpio_output(&gpio_led[3]);
#endif
#ifdef PRALINE
gpio_output(&gpio_led[3]);
#endif
#ifdef PRALINE
disable_1v2_power();
disable_3v3aux_power();
gpio_output(&gpio_1v2_enable);
gpio_output(&gpio_3v3aux_enable_n);
scu_pinmux(SCU_PINMUX_EN1V2, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
scu_pinmux(SCU_PINMUX_EN3V3_AUX_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION4);
#else
disable_1v8_power();
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
#ifdef HACKRF_ONE
#ifdef HACKRF_ONE
gpio_output(&gpio_h1r9_1v8_enable);
scu_pinmux(SCU_H1R9_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
#endif
#endif
} else {
gpio_output(&gpio_1v8_enable);
scu_pinmux(SCU_PINMUX_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
}
#endif
#ifdef HACKRF_ONE
#if (defined HACKRF_ONE || defined PRALINE)
/* Safe state: start with VAA turned off: */
disable_rf_power();
/* Configure RF power supply (VAA) switch control signal as output */
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
#ifdef HACKRF_ONE
gpio_output(&gpio_h1r9_vaa_disable);
#endif
} else {
gpio_output(&gpio_vaa_disable);
}
@@ -948,10 +1160,39 @@ void pin_setup(void)
#endif
#ifdef PRALINE
scu_pinmux(SCU_P2_CTRL0, SCU_P2_CTRL0_PINCFG);
scu_pinmux(SCU_P2_CTRL1, SCU_P2_CTRL1_PINCFG);
scu_pinmux(SCU_P1_CTRL0, SCU_P1_CTRL0_PINCFG);
scu_pinmux(SCU_P1_CTRL1, SCU_P1_CTRL1_PINCFG);
scu_pinmux(SCU_P1_CTRL2, SCU_P1_CTRL2_PINCFG);
scu_pinmux(SCU_CLKIN_CTRL, SCU_CLKIN_CTRL_PINCFG);
scu_pinmux(SCU_AA_EN, SCU_AA_EN_PINCFG);
scu_pinmux(SCU_TRIGGER_IN, SCU_TRIGGER_IN_PINCFG);
scu_pinmux(SCU_TRIGGER_OUT, SCU_TRIGGER_OUT_PINCFG);
scu_pinmux(SCU_PPS_OUT, SCU_PPS_OUT_PINCFG);
p2_ctrl_set(P2_SIGNAL_CLK3);
p1_ctrl_set(P1_SIGNAL_CLKIN);
narrowband_filter_set(0);
clkin_ctrl_set(CLKIN_SIGNAL_P22);
gpio_output(&gpio_p2_ctrl0);
gpio_output(&gpio_p2_ctrl1);
gpio_output(&gpio_p1_ctrl0);
gpio_output(&gpio_p1_ctrl1);
gpio_output(&gpio_p1_ctrl2);
gpio_output(&gpio_clkin_ctrl);
gpio_output(&gpio_pps_out);
gpio_output(&gpio_aa_en);
gpio_input(&gpio_trigger_in);
gpio_input(&gpio_trigger_out);
#endif
/* enable input on SCL and SDA pins */
SCU_SFSI2C0 = SCU_I2C0_NOMINAL;
spi_bus_start(&spi_bus_ssp1, &ssp_config_max283x);
ssp1_set_mode_max283x();
mixer_bus_setup(&mixer);
@@ -961,6 +1202,14 @@ void pin_setup(void)
sgpio_config.gpio_hw_sync_enable = &gpio_h1r9_hw_sync_enable;
}
#endif
#ifdef PRALINE
board_rev_t rev = detected_revision();
if ((rev == BOARD_REV_PRALINE_R1_0) || (rev == BOARD_REV_GSG_PRALINE_R1_0)) {
rf_path.gpio_mix_en_n = &gpio_mix_en_n_r1_0;
}
#endif
rf_path_pin_setup(&rf_path);
/* Configure external clock in */
@@ -969,12 +1218,33 @@ void pin_setup(void)
sgpio_configure_pin_functions(&sgpio_config);
}
#ifdef PRALINE
void enable_1v2_power(void)
{
gpio_set(&gpio_1v2_enable);
}
void disable_1v2_power(void)
{
gpio_clear(&gpio_1v2_enable);
}
void enable_3v3aux_power(void)
{
gpio_clear(&gpio_3v3aux_enable_n);
}
void disable_3v3aux_power(void)
{
gpio_set(&gpio_3v3aux_enable_n);
}
#else
void enable_1v8_power(void)
{
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
#ifdef HACKRF_ONE
#ifdef HACKRF_ONE
gpio_set(&gpio_h1r9_1v8_enable);
#endif
#endif
} else {
gpio_set(&gpio_1v8_enable);
}
@@ -983,13 +1253,14 @@ void enable_1v8_power(void)
void disable_1v8_power(void)
{
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
#ifdef HACKRF_ONE
#ifdef HACKRF_ONE
gpio_clear(&gpio_h1r9_1v8_enable);
#endif
#endif
} else {
gpio_clear(&gpio_1v8_enable);
}
}
#endif
#ifdef HACKRF_ONE
void enable_rf_power(void)
@@ -1018,6 +1289,21 @@ void disable_rf_power(void)
}
#endif
#ifdef PRALINE
void enable_rf_power(void)
{
gpio_clear(&gpio_vaa_disable);
/* Let the voltage stabilize */
delay(1000000);
}
void disable_rf_power(void)
{
gpio_set(&gpio_vaa_disable);
}
#endif
#ifdef RAD1O
void enable_rf_power(void)
{
@@ -1033,6 +1319,17 @@ void disable_rf_power(void)
}
#endif
#ifdef PRALINE
void led_on(const led_t led)
{
gpio_clear(&gpio_led[led]);
}
void led_off(const led_t led)
{
gpio_set(&gpio_led[led]);
}
#else
void led_on(const led_t led)
{
gpio_set(&gpio_led[led]);
@@ -1042,6 +1339,7 @@ void led_off(const led_t led)
{
gpio_clear(&gpio_led[led]);
}
#endif
void led_toggle(const led_t led)
{
@@ -1051,17 +1349,28 @@ void led_toggle(const led_t led)
void set_leds(const uint8_t state)
{
int num_leds = 3;
#ifdef RAD1O
#if (defined RAD1O || defined PRALINE)
num_leds = 4;
#endif
for (int i = 0; i < num_leds; i++) {
#ifdef PRALINE
gpio_write(&gpio_led[i], ((state >> i) & 1) == 0);
#else
gpio_write(&gpio_led[i], ((state >> i) & 1) == 1);
#endif
}
}
void hw_sync_enable(const hw_sync_mode_t hw_sync_mode)
{
#ifndef PRALINE
gpio_write(sgpio_config.gpio_hw_sync_enable, hw_sync_mode == 1);
#else
ssp1_set_mode_ice40();
uint8_t prev = ice40_spi_read(&ice40, 0x01);
ice40_spi_write(&ice40, 0x01, (prev & 0x7F) | ((hw_sync_mode == 1) << 7));
ssp1_set_mode_max283x();
#endif
}
void halt_and_flash(const uint32_t duration)
@@ -1078,3 +1387,33 @@ void halt_and_flash(const uint32_t duration)
delay(duration);
}
}
#ifdef PRALINE
void p1_ctrl_set(const p1_ctrl_signal_t signal)
{
gpio_write(&gpio_p1_ctrl0, signal & 1);
gpio_write(&gpio_p1_ctrl1, (signal >> 1) & 1);
gpio_write(&gpio_p1_ctrl2, (signal >> 2) & 1);
}
void p2_ctrl_set(const p2_ctrl_signal_t signal)
{
gpio_write(&gpio_p2_ctrl0, signal & 1);
gpio_write(&gpio_p2_ctrl1, (signal >> 1) & 1);
}
void clkin_ctrl_set(const clkin_signal_t signal)
{
gpio_write(&gpio_clkin_ctrl, signal & 1);
}
void pps_out_set(const uint8_t value)
{
gpio_write(&gpio_pps_out, value & 1);
}
void narrowband_filter_set(const uint8_t value)
{
gpio_write(&gpio_aa_en, value & 1);
}
#endif

View File

@@ -34,13 +34,16 @@ extern "C" {
#include "si5351c.h"
#include "spi_ssp.h"
#include "max2831.h"
#include "max283x.h"
#include "max5864.h"
#include "mixer.h"
#include "w25q80bv.h"
#include "sgpio.h"
#include "radio.h"
#include "rf_path.h"
#include "cpld_jtag.h"
#include "ice40_spi.h"
/*
* SCU PinMux
@@ -53,8 +56,16 @@ extern "C" {
#ifdef RAD1O
#define SCU_PINMUX_LED4 (PB_6) /* GPIO5[26] on PB_6 */
#endif
#ifdef PRALINE
#define SCU_PINMUX_LED4 (P8_6) /* GPIO4[6] on P8_6 */
#endif
#define SCU_PINMUX_EN1V8 (P6_10) /* GPIO3[6] on P6_10 */
#define SCU_PINMUX_EN1V2 (P8_7) /* GPIO4[7] on P8_7 */
#ifdef PRALINE
#define SCU_PINMUX_EN3V3_AUX_N (P6_7) /* GPIO5[15] on P6_7 */
#define SCU_PINMUX_EN3V3_OC_N (P6_11) /* GPIO3[7] on P6_11 */
#endif
/* GPIO Input PinMux */
#define SCU_PINMUX_BOOT0 (P1_1) /* GPIO0[8] on P1_1 */
@@ -82,9 +93,14 @@ extern "C" {
#define SCU_SSP1_CS (P1_20) /* P1_20 */
/* CPLD JTAG interface */
#ifdef PRALINE
#define SCU_PINMUX_FPGA_CRESET (P5_2) /* GPIO2[11] on P5_2 */
#define SCU_PINMUX_FPGA_CDONE (P4_10) /* GPIO5[14] */
#define SCU_PINMUX_FPGA_SPI_CS (P5_1) /* GPIO2[10] */
#endif
#define SCU_PINMUX_CPLD_TDO (P9_5) /* GPIO5[18] */
#define SCU_PINMUX_CPLD_TCK (P6_1) /* GPIO3[ 0] */
#if (defined HACKRF_ONE || defined RAD1O)
#if (defined HACKRF_ONE || defined RAD1O || defined PRALINE)
#define SCU_PINMUX_CPLD_TMS (P6_5) /* GPIO3[ 4] */
#define SCU_PINMUX_CPLD_TDI (P6_2) /* GPIO3[ 1] */
#else
@@ -93,24 +109,76 @@ extern "C" {
#endif
/* CPLD SGPIO interface */
#define SCU_PINMUX_SGPIO0 (P0_0)
#define SCU_PINMUX_SGPIO1 (P0_1)
#define SCU_PINMUX_SGPIO2 (P1_15)
#define SCU_PINMUX_SGPIO3 (P1_16)
#define SCU_PINMUX_SGPIO4 (P6_3)
#define SCU_PINMUX_SGPIO5 (P6_6)
#define SCU_PINMUX_SGPIO6 (P2_2)
#define SCU_PINMUX_SGPIO7 (P1_0)
#if (defined JAWBREAKER || defined HACKRF_ONE || defined RAD1O)
#define SCU_PINMUX_SGPIO8 (P9_6)
#ifdef PRALINE
#define SCU_PINMUX_SGPIO0 (P0_0)
#define SCU_PINMUX_SGPIO1 (P0_1)
#define SCU_PINMUX_SGPIO2 (P1_15)
#define SCU_PINMUX_SGPIO3 (P1_16)
#define SCU_PINMUX_SGPIO4 (P9_4)
#define SCU_PINMUX_SGPIO5 (P6_6)
#define SCU_PINMUX_SGPIO6 (P2_2)
#define SCU_PINMUX_SGPIO7 (P1_0)
#define SCU_PINMUX_SGPIO8 (P8_0)
#define SCU_PINMUX_SGPIO9 (P9_3)
#define SCU_PINMUX_SGPIO10 (P8_2)
#define SCU_PINMUX_SGPIO11 (P1_17)
#define SCU_PINMUX_SGPIO12 (P1_18)
#define SCU_PINMUX_SGPIO14 (P1_18)
#define SCU_PINMUX_SGPIO15 (P1_18)
#define SCU_PINMUX_SGPIO0_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION3)
#define SCU_PINMUX_SGPIO1_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION3)
#define SCU_PINMUX_SGPIO2_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION2)
#define SCU_PINMUX_SGPIO3_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION2)
#define SCU_PINMUX_SGPIO4_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION6)
#define SCU_PINMUX_SGPIO5_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION2)
#define SCU_PINMUX_SGPIO6_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
#define SCU_PINMUX_SGPIO7_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION6)
#define SCU_PINMUX_SGPIO8_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
#define SCU_PINMUX_SGPIO9_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION6)
#define SCU_PINMUX_SGPIO10_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
#define SCU_PINMUX_SGPIO11_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION6)
#define SCU_PINMUX_SGPIO12_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
#define SCU_PINMUX_SGPIO14_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
#define SCU_PINMUX_SGPIO15_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
#else
#define SCU_PINMUX_SGPIO0 (P0_0)
#define SCU_PINMUX_SGPIO1 (P0_1)
#define SCU_PINMUX_SGPIO2 (P1_15)
#define SCU_PINMUX_SGPIO3 (P1_16)
#define SCU_PINMUX_SGPIO4 (P6_3)
#define SCU_PINMUX_SGPIO5 (P6_6)
#define SCU_PINMUX_SGPIO6 (P2_2)
#define SCU_PINMUX_SGPIO7 (P1_0)
#if (defined JAWBREAKER || defined HACKRF_ONE || defined RAD1O)
#define SCU_PINMUX_SGPIO8 (P9_6)
#endif
#define SCU_PINMUX_SGPIO9 (P4_3)
#define SCU_PINMUX_SGPIO10 (P1_14)
#define SCU_PINMUX_SGPIO11 (P1_17)
#define SCU_PINMUX_SGPIO12 (P1_18)
#define SCU_PINMUX_SGPIO14 (P4_9)
#define SCU_PINMUX_SGPIO15 (P4_10)
#define SCU_PINMUX_SGPIO0_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION3)
#define SCU_PINMUX_SGPIO1_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION3)
#define SCU_PINMUX_SGPIO2_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION2)
#define SCU_PINMUX_SGPIO3_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION2)
#define SCU_PINMUX_SGPIO4_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION2)
#define SCU_PINMUX_SGPIO5_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION2)
#define SCU_PINMUX_SGPIO6_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
#define SCU_PINMUX_SGPIO7_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION6)
#define SCU_PINMUX_SGPIO8_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION6)
#define SCU_PINMUX_SGPIO9_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION7)
#define SCU_PINMUX_SGPIO10_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION6)
#define SCU_PINMUX_SGPIO11_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION6)
#define SCU_PINMUX_SGPIO12_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
#define SCU_PINMUX_SGPIO14_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
#define SCU_PINMUX_SGPIO15_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
#endif
#define SCU_PINMUX_SGPIO9 (P4_3)
#define SCU_PINMUX_SGPIO10 (P1_14)
#define SCU_PINMUX_SGPIO11 (P1_17)
#define SCU_PINMUX_SGPIO12 (P1_18)
#define SCU_PINMUX_SGPIO14 (P4_9)
#define SCU_PINMUX_SGPIO15 (P4_10)
#define SCU_HW_SYNC_EN (P4_8) /* GPIO5[12] on P4_8 */
#define SCU_HW_SYNC_EN (P4_8) /* GPIO5[12] on P4_8 */
/* MAX2837 GPIO (XCVR_CTL) PinMux */
#ifdef RAD1O
@@ -119,13 +187,40 @@ extern "C" {
#define SCU_XCVR_B7 (P9_3) /* GPIO[] on P8_3 */
#endif
#define SCU_XCVR_ENABLE (P4_6) /* GPIO2[6] on P4_6 */
#define SCU_XCVR_RXENABLE (P4_5) /* GPIO2[5] on P4_5 */
#define SCU_XCVR_TXENABLE (P4_4) /* GPIO2[4] on P4_4 */
#define SCU_XCVR_CS (P1_20) /* GPIO0[15] on P1_20 */
#ifdef PRALINE
#define SCU_XCVR_ENABLE (PE_1) /* GPIO7[1] on PE_1 */
#define SCU_XCVR_RXENABLE (PE_2) /* GPIO7[2] on PE_2 */
#define SCU_XCVR_CS (PD_14) /* GPIO6[28] on PD_14 */
#define SCU_XCVR_RXHP (PD_15) /* GPIO6[29] on PD_15 */
#define SCU_XCVR_LD (P9_6) /* GPIO4[11] on P9_6 */
#define SCU_XCVR_ENABLE_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
#define SCU_XCVR_RXENABLE_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
#define SCU_XCVR_CS_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
#define SCU_XCVR_RXHP_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
#define SCU_XCVR_LD_PINCFG \
(SCU_GPIO_FAST | SCU_CONF_FUNCTION0 | SCU_CONF_EPD_EN_PULLDOWN | \
SCU_CONF_EPUN_DIS_PULLUP)
#else
#define SCU_XCVR_ENABLE (P4_6) /* GPIO2[6] on P4_6 */
#define SCU_XCVR_RXENABLE (P4_5) /* GPIO2[5] on P4_5 */
#define SCU_XCVR_TXENABLE (P4_4) /* GPIO2[4] on P4_4 */
#define SCU_XCVR_CS (P1_20) /* GPIO0[15] on P1_20 */
#define SCU_XCVR_ENABLE_PINCFG (SCU_GPIO_FAST)
#define SCU_XCVR_RXENABLE_PINCFG (SCU_GPIO_FAST)
#define SCU_XCVR_TXENABLE_PINCFG (SCU_GPIO_FAST)
#define SCU_XCVR_CS_PINCFG (SCU_GPIO_FAST)
#endif
/* MAX5864 SPI chip select (AD_CS) GPIO PinMux */
#define SCU_AD_CS (P5_7) /* GPIO2[7] on P5_7 */
#ifdef PRALINE
#define SCU_AD_CS (PD_16) /* GPIO6[30] on PD_16 */
#define SCU_AD_CS_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
#else
#define SCU_AD_CS (P5_7) /* GPIO2[7] on P5_7 */
#define SCU_AD_CS_PINCFG (SCU_GPIO_FAST)
#endif
/* RFFC5071 GPIO serial interface PinMux */
#if (defined JAWBREAKER || defined HACKRF_ONE)
@@ -133,6 +228,20 @@ extern "C" {
#define SCU_MIXER_SCLK (P2_6) /* GPIO5[6] on P2_6 */
#define SCU_MIXER_SDATA (P6_4) /* GPIO3[3] on P6_4 */
#define SCU_MIXER_RESETX (P5_5) /* GPIO2[14] on P5_5 */
#define SCU_MIXER_SCLK_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
#define SCU_MIXER_SDATA_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
#endif
#ifdef PRALINE
#define SCU_MIXER_ENX (P5_4) /* GPIO2[13] on P5_4 */
#define SCU_MIXER_SCLK (P9_5) /* GPIO5[18] on P9_5 */
#define SCU_MIXER_SDATA (P9_2) /* GPIO4[14] on P9_2 */
#define SCU_MIXER_RESETX (P5_5) /* GPIO2[14] on P5_5 */
#define SCU_MIXER_LD (PD_11) /* GPIO6[25] on PD_11 */
#define SCU_MIXER_SCLK_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
#define SCU_MIXER_SDATA_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
#define SCU_MIXER_LD_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
#endif
#ifdef RAD1O
#define SCU_VCO_CE (P5_4) /* GPIO2[13] on P5_4 */
@@ -153,6 +262,9 @@ extern "C" {
#ifdef HACKRF_ONE
#define SCU_NO_VAA_ENABLE (P5_0) /* GPIO2[9] on P5_0 */
#endif
#ifdef PRALINE
#define SCU_NO_VAA_ENABLE (P8_1) /* GPIO4[1] on P8_1 */
#endif
#ifdef RAD1O
#define SCU_VAA_ENABLE (P5_0) /* GPIO2[9] on P5_0 */
#endif
@@ -193,6 +305,40 @@ extern "C" {
#define SCU_TX_AMP (P5_6) /* GPIO2[15] on P5_6 */
#define SCU_RX_LNA (P6_7) /* GPIO5[15] on P6_7 */
#endif
#ifdef PRALINE
#define SCU_TX_EN (P6_5) /* GPIO3[4] on P6_5 */
#define SCU_MIX_EN_N (P6_3) /* GPIO3[2] on P6_3 */
#define SCU_MIX_EN_N_R1_0 (P2_6) /* GPIO5[6] on P2_6 */
#define SCU_LPF_EN (PA_1) /* GPIO4[8] on PA_1 */
#define SCU_RF_AMP_EN (PA_2) /* GPIO4[9] on PA_2 */
#define SCU_ANT_BIAS_EN_N (P2_12) /* GPIO1[12] on P2_12 */
#define SCU_ANT_BIAS_OC_N (P2_11) /* GPIO1[11] on P2_11 */
#endif
#ifdef PRALINE
#define SCU_P2_CTRL0 (PE_3) /* GPIO7[3] on PE_3 */
#define SCU_P2_CTRL1 (PE_4) /* GPIO7[4] on PE_4 */
#define SCU_P1_CTRL0 (P2_10) /* GPIO0[14] on P2_10 */
#define SCU_P1_CTRL1 (P6_8) /* GPIO5[16] on P6_8 */
#define SCU_P1_CTRL2 (P6_9) /* GPIO3[5] on P6_9 */
#define SCU_CLKIN_CTRL (P1_20) /* GPIO0[15] on P1_20 */
#define SCU_AA_EN (P1_14) /* GPIO1[7] on P1_14 */
#define SCU_TRIGGER_IN (PD_12) /* GPIO6[26] on PD_12 */
#define SCU_TRIGGER_OUT (P2_6) /* GPIO5[6] on P2_6 */
#define SCU_PPS_OUT (P2_5) /* GPIO5[5] on P2_5 */
#define SCU_P2_CTRL0_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
#define SCU_P2_CTRL1_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
#define SCU_P1_CTRL0_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
#define SCU_P1_CTRL1_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
#define SCU_P1_CTRL2_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
#define SCU_CLKIN_CTRL_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
#define SCU_AA_EN_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION0)
#define SCU_TRIGGER_IN_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
#define SCU_TRIGGER_OUT_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
#define SCU_PPS_OUT_PINCFG (SCU_GPIO_FAST | SCU_CONF_FUNCTION4)
#endif
#define SCU_PINMUX_PP_D0 (P7_0) /* GPIO3[8] */
#define SCU_PINMUX_PP_D1 (P7_1) /* GPIO3[9] */
@@ -242,26 +388,6 @@ extern "C" {
#define SCU_H1R9_NO_VAA_EN (P6_10) /* GPIO3[6] on P6_10 */
#define SCU_H1R9_HW_SYNC_EN (P2_5) /* GPIO5[5] on P2_5 */
typedef enum {
TRANSCEIVER_MODE_OFF = 0,
TRANSCEIVER_MODE_RX = 1,
TRANSCEIVER_MODE_TX = 2,
TRANSCEIVER_MODE_SS = 3,
TRANSCEIVER_MODE_CPLD_UPDATE = 4,
TRANSCEIVER_MODE_RX_SWEEP = 5,
} transceiver_mode_t;
typedef enum {
HW_SYNC_MODE_OFF = 0,
HW_SYNC_MODE_ON = 1,
} hw_sync_mode_t;
typedef enum {
CLOCK_SOURCE_HACKRF = 0,
CLOCK_SOURCE_EXTERNAL = 1,
CLOCK_SOURCE_PORTAPACK = 2,
} clock_source_t;
void delay(uint32_t duration);
void delay_us_at_mhz(uint32_t us, uint32_t mhz);
@@ -271,11 +397,18 @@ extern const ssp_config_t ssp_config_w25q80bv;
extern const ssp_config_t ssp_config_max283x;
extern const ssp_config_t ssp_config_max5864;
#ifndef PRALINE
extern max283x_driver_t max283x;
#else
extern max2831_driver_t max283x;
extern ice40_spi_driver_t ice40;
#endif
extern max5864_driver_t max5864;
extern mixer_driver_t mixer;
extern w25q80bv_driver_t spi_flash;
extern sgpio_config_t sgpio_config;
extern radio_t radio;
extern rf_path_t rf_path;
extern jtag_t jtag_cpld;
extern i2c_bus_t i2c0;
@@ -283,19 +416,29 @@ extern i2c_bus_t i2c0;
void cpu_clock_init(void);
void ssp1_set_mode_max283x(void);
void ssp1_set_mode_max5864(void);
#ifdef PRALINE
void ssp1_set_mode_max2831(void);
void ssp1_set_mode_ice40(void);
#endif
void pin_setup(void);
#ifdef PRALINE
void enable_1v2_power(void);
void disable_1v2_power(void);
void enable_3v3aux_power(void);
void disable_3v3aux_power(void);
#else
void enable_1v8_power(void);
void disable_1v8_power(void);
#endif
bool sample_rate_frac_set(uint32_t rate_num, uint32_t rate_denom);
bool sample_rate_set(const uint32_t sampling_rate_hz);
bool baseband_filter_bandwidth_set(const uint32_t bandwidth_hz);
clock_source_t activate_best_clock_source(void);
#if (defined HACKRF_ONE || defined RAD1O)
#if (defined HACKRF_ONE || defined RAD1O || defined PRALINE)
void enable_rf_power(void);
void disable_rf_power(void);
#endif
@@ -316,6 +459,36 @@ void hw_sync_enable(const hw_sync_mode_t hw_sync_mode);
void halt_and_flash(const uint32_t duration);
#ifdef PRALINE
typedef enum {
P1_SIGNAL_TRIGGER_IN = 0,
P1_SIGNAL_AUX_CLK1 = 1,
P1_SIGNAL_CLKIN = 2,
P1_SIGNAL_TRIGGER_OUT = 3,
P1_SIGNAL_P22_CLKIN = 4,
P1_SIGNAL_P2_5 = 5,
P1_SIGNAL_NC = 6,
P1_SIGNAL_AUX_CLK2 = 7,
} p1_ctrl_signal_t;
typedef enum {
P2_SIGNAL_CLK3 = 0,
P2_SIGNAL_TRIGGER_IN = 2,
P2_SIGNAL_TRIGGER_OUT = 3,
} p2_ctrl_signal_t;
typedef enum {
CLKIN_SIGNAL_P1 = 0,
CLKIN_SIGNAL_P22 = 1,
} clkin_signal_t;
void p1_ctrl_set(const p1_ctrl_signal_t signal);
void p2_ctrl_set(const p2_ctrl_signal_t signal);
void narrowband_filter_set(const uint8_t value);
void clkin_ctrl_set(const clkin_signal_t value);
void pps_out_set(const uint8_t value);
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -78,7 +78,7 @@ const hackrf_ui_t* hackrf_ui(void)
{
/* Detect on first use. If no UI hardware is detected, use a stub function table. */
if (ui == NULL && ui_enabled) {
#ifdef HACKRF_ONE
#if (defined HACKRF_ONE || defined PRALINE)
if (portapack_hackrf_ui_init) {
ui = portapack_hackrf_ui_init();
}

124
firmware/common/ice40_spi.c Normal file
View File

@@ -0,0 +1,124 @@
/*
* Copyright 2024 Great Scott Gadgets <info@greatscottgadgets.com>
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ice40_spi.h"
#include <libopencm3/lpc43xx/scu.h>
#include "hackrf_core.h"
void ice40_spi_target_init(ice40_spi_driver_t* const drv)
{
/* Configure SSP1 Peripheral and relevant FPGA pins. */
scu_pinmux(SCU_SSP1_CIPO, (SCU_SSP_IO | SCU_CONF_FUNCTION5));
scu_pinmux(SCU_SSP1_COPI, (SCU_SSP_IO | SCU_CONF_FUNCTION5));
scu_pinmux(SCU_SSP1_SCK, (SCU_SSP_IO | SCU_CONF_FUNCTION1));
scu_pinmux(SCU_PINMUX_FPGA_CRESET, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
scu_pinmux(SCU_PINMUX_FPGA_CDONE, SCU_GPIO_PUP | SCU_CONF_FUNCTION4);
scu_pinmux(SCU_PINMUX_FPGA_SPI_CS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
/* Configure GPIOs as inputs or outputs as needed. */
gpio_clear(drv->gpio_creset);
gpio_output(drv->gpio_creset);
gpio_input(drv->gpio_cdone);
// select is configured in SSP code
}
uint8_t ice40_spi_read(ice40_spi_driver_t* const drv, uint8_t r)
{
uint8_t value[3] = {r & 0x7F, 0, 0};
spi_bus_transfer(drv->bus, value, 3);
return value[2];
}
void ice40_spi_write(ice40_spi_driver_t* const drv, uint8_t r, uint16_t v)
{
uint8_t value[3] = {(r & 0x7F) | 0x80, v, 0};
spi_bus_transfer(drv->bus, value, 3);
}
static void spi_ssp1_wait_for_tx_fifo_not_full()
{
while ((SSP_SR(SSP1_BASE) & SSP_SR_TNF) == 0) {}
}
static void spi_ssp1_wait_for_rx_fifo_not_empty()
{
while ((SSP_SR(SSP1_BASE) & SSP_SR_RNE) == 0) {}
}
static void spi_ssp1_wait_for_not_busy()
{
while (SSP_SR(SSP1_BASE) & SSP_SR_BSY) {}
}
static uint32_t spi_ssp1_transfer_word(const uint32_t data)
{
spi_ssp1_wait_for_tx_fifo_not_full();
SSP_DR(SSP1_BASE) = data;
spi_ssp1_wait_for_not_busy();
spi_ssp1_wait_for_rx_fifo_not_empty();
return SSP_DR(SSP1_BASE);
}
bool ice40_spi_syscfg_program(
ice40_spi_driver_t* const drv,
size_t (*read_block_cb)(void* ctx, uint8_t* buffer),
void* read_ctx)
{
// Drive CRESET_B = 0, SPI_SS = 0, SPI_SCK = 1.
gpio_clear(drv->gpio_creset);
gpio_clear(drv->gpio_select);
// Wait a minimum of 200 ns.
delay_us_at_mhz(1, 204 / 4); // 250 ns.
// Release CRESET_B or drive CRESET_B = 1.
gpio_set(drv->gpio_creset);
// Wait a minimum of 1200 μs to clear internal configuration memory.
// Testing showed us that we need to wait longer. Let's wait 1800 μs.
delay_us_at_mhz(1800, 204);
// Set SPI_SS = 1, Send 8 dummy clocks.
gpio_set(drv->gpio_select);
spi_ssp1_transfer_word(0);
// Send configuration image serially on SPI_SI to iCE40, most-significant bit
// first, on falling edge of SPI_SCK.
uint8_t out_buffer[4096] = {0};
gpio_clear(drv->gpio_select);
for (;;) {
size_t read_sz = read_block_cb(read_ctx, out_buffer);
if (read_sz == 0)
break;
for (size_t j = 0; j < read_sz; j++) {
spi_ssp1_transfer_word(out_buffer[j]);
}
}
// Wait for 100 clocks cycles for CDONE to go high.
gpio_set(drv->gpio_select);
for (size_t j = 0; j < 13; j++) {
spi_ssp1_transfer_word(0);
}
return gpio_read(drv->gpio_cdone);
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2024 Great Scott Gadgets <info@greatscottgadgets.com>
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __ICE40_SPI_H
#define __ICE40_SPI_H
#include <stdint.h>
#include <stdlib.h>
#include "gpio.h"
#include "spi_bus.h"
struct ice40_spi_driver_t;
typedef struct ice40_spi_driver_t ice40_spi_driver_t;
struct ice40_spi_driver_t {
spi_bus_t* const bus;
gpio_t gpio_select;
gpio_t gpio_creset;
gpio_t gpio_cdone;
};
void ice40_spi_target_init(ice40_spi_driver_t* const drv);
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),
void* read_ctx);
#endif // __ICE40_SPI_H

86
firmware/common/lz4_blk.c Normal file
View File

@@ -0,0 +1,86 @@
/*
* Copyright 2024 Great Scott Gadgets <info@greatscottgadgets.com>
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "lz4_blk.h"
// Decompress raw LZ4 block.
int lz4_blk_decompress(const uint8_t* src, uint8_t* dst, size_t length)
{
const uint8_t* src_end = src + length; // Point to the end of the current block.
const uint8_t* dst_0 = dst; // Store original dst pointer to compute output size.
while (src < src_end) {
uint8_t token = *src++;
// Get the literal length from the high nibble of the token.
uint32_t literal_length = token >> 4;
// If literal length is 15 or more, we need to read additional length bytes.
if (literal_length == 0x0F) {
uint8_t len;
while ((len = *src++) == 0xFF) {
literal_length += 0xFF;
}
literal_length += len;
}
// Copy the literals, if any.
if (literal_length > 0) {
memcpy(dst, src, literal_length);
src += literal_length;
dst += literal_length;
}
// If we're at the end, break (no match data to process).
if (src >= src_end) {
break;
}
// Get the match offset (2 bytes).
uint16_t offset = src[0] | (src[1] << 8);
src += 2;
// Match length (low nibble of token + 4).
uint32_t match_length = (token & 0x0F) + 4;
// If match length is 19 or more, we need to read additional length bytes.
if ((token & 0x0F) == 0x0F) {
uint8_t len;
while ((len = *src++) == 0xFF) {
match_length += 0xFF;
}
match_length += len;
}
// Copy the match data.
const uint8_t* match_ptr = dst - offset;
for (uint32_t i = 0; i < match_length; i++) {
*dst++ = *match_ptr++;
}
}
// Return the size of the output.
return dst - dst_0;
}

30
firmware/common/lz4_blk.h Normal file
View File

@@ -0,0 +1,30 @@
/*
* Copyright 2024 Great Scott Gadgets <info@greatscottgadgets.com>
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __LZ4_BLK_H
#define __LZ4_BLK_H
#include <stdint.h>
#include <stdlib.h>
int lz4_blk_decompress(const uint8_t* src, uint8_t* dst, size_t length);
#endif

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "m0_state.h"
#include <libopencm3/lpc43xx/sgpio.h>
#include <stddef.h>
void m0_set_mode(enum m0_mode mode)
{
// Set requested mode and flag bit.
m0_state.requested_mode = M0_REQUEST_FLAG | mode;
// The M0 may be blocked waiting for the next SGPIO interrupt.
// In order to ensure that it sees our request, we need to set
// the interrupt flag here. The M0 will clear the flag again
// before acknowledging our request.
SGPIO_SET_STATUS_1 = (1 << SGPIO_SLICE_A);
// Wait for M0 to acknowledge by clearing the flag.
while (m0_state.requested_mode & M0_REQUEST_FLAG) {}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __M0_STATE_H__
#define __M0_STATE_H__
#include <stdint.h>
#define M0_REQUEST_FLAG (1 << 16)
struct m0_state {
uint32_t requested_mode;
uint32_t active_mode;
uint32_t m0_count;
uint32_t m4_count;
uint32_t num_shortfalls;
uint32_t longest_shortfall;
uint32_t shortfall_limit;
uint32_t threshold;
uint32_t next_mode;
uint32_t error;
};
enum m0_mode {
M0_MODE_IDLE = 0,
M0_MODE_WAIT = 1,
M0_MODE_RX = 2,
M0_MODE_TX_START = 3,
M0_MODE_TX_RUN = 4,
};
enum m0_error {
M0_ERROR_NONE = 0,
M0_ERROR_RX_TIMEOUT = 1,
M0_ERROR_TX_TIMEOUT = 2,
};
/* Address of m0_state is set in ldscripts. If you change the name of this
* variable, it won't be where it needs to be in the processor's address space,
* unless you also adjust the ldscripts.
*/
extern volatile struct m0_state m0_state;
void m0_set_mode(enum m0_mode mode);
#endif /*__M0_STATE_H__*/

385
firmware/common/max2831.c Normal file
View File

@@ -0,0 +1,385 @@
/*
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
/*
* 'gcc -DTEST -DDEBUG -O2 -o test max2831.c' prints out what test
* program would do if it had a real spi library
*
* 'gcc -DTEST -DBUS_PIRATE -O2 -o test max2831.c' prints out bus
* pirate commands to do the same thing.
*/
#include <stdint.h>
#include <string.h>
#include "max2831.h"
#include "max2831_regs.def" // private register def macros
#include "selftest.h"
#define MIN(x, y) ((x) < (y) ? (x) : (y))
/* Default register values. */
static const uint16_t max2831_regs_default[MAX2831_NUM_REGS] = {
0x1740, /* 0: enable fractional mode (Table 16 recommends 0x0740, clearing unknown bit) */
0x119a, /* 1 */
0x1003, /* 2 */
0x0079, /* 3: PLL divider settings for 2437 MHz */
0x3666, /* 4: PLL divider settings for 2437 MHz */
0x00a4, /* 5: divide reference frequency by 2 */
0x0060, /* 6: enable TX power detector */
0x1022, /* 7: 110% TX LPF bandwidth */
0x2021, /* 8: pin control of RX gain, 11 MHz LPF bandwidth */
0x03b5, /* 9: pin control of TX gain */
0x1d80, /* 10: 3.5 us PA enable delay, zero PA bias */
0x0074, /* 11: LNA high gain, RX VGA moderate gain (Table 27 recommends 0x007f, maximum gain) */
0x0140, /* 12: TX VGA minimum */
0x0e92, /* 13 */
0x0100, /* 14: reference clock output disabled */
0x0145, /* 15: RX IQ common mode 1.1 V */
};
/* Set up all registers according to defaults specified in docs. */
static void max2831_init(max2831_driver_t* const drv)
{
drv->target_init(drv);
max2831_set_mode(drv, MAX2831_MODE_SHUTDOWN);
memcpy(drv->regs, max2831_regs_default, sizeof(drv->regs));
drv->regs_dirty = 0xffff;
/* Write default register values to chip. */
max2831_regs_commit(drv);
/* Disable lock detect output. */
set_MAX2831_LOCK_DETECT_OUTPUT_EN(drv, false);
max2831_regs_commit(drv);
// Read state of lock detect pin.
bool initial = gpio_read(drv->gpio_ld);
// Enable lock detect output.
set_MAX2831_LOCK_DETECT_OUTPUT_EN(drv, true);
max2831_regs_commit(drv);
// Read new state of lock detect pin.
bool new = gpio_read(drv->gpio_ld);
// If the pin state changed, we know our writes are working.
selftest.max2831_ld_test_ok = initial != new;
if (!selftest.max2831_ld_test_ok) {
selftest.report.pass = false;
}
}
/*
* Set up pins for GPIO and SPI control, configure SSP peripheral for SPI, and
* set our own default register configuration.
*/
void max2831_setup(max2831_driver_t* const drv)
{
max2831_init(drv);
/* Use SPI control instead of B1-B7 pins for gain settings. */
set_MAX2831_RXVGA_GAIN_SPI_EN(drv, 1);
set_MAX2831_TXVGA_GAIN_SPI_EN(drv, 1);
//set_MAX2831_TXVGA_GAIN(0x3f); /* maximum gain */
set_MAX2831_TXVGA_GAIN(drv, 0x00); /* minimum gain */
//set_MAX2831_RX_HPF_SEL(drv, MAX2831_RX_HPF_100_HZ);
set_MAX2831_LNA_GAIN(drv, MAX2831_LNA_GAIN_MAX); /* maximum gain */
set_MAX2831_RXVGA_GAIN(drv, 0x18);
/* maximum rx output common-mode voltage */
//set_MAX2831_RXIQ_VCM(drv, MAX2831_RXIQ_VCM_1_2);
/* configure baseband filter for 8 MHz TX */
set_MAX2831_LPF_COARSE(drv, MAX2831_RX_LPF_7_5M);
set_MAX2831_RX_LPF_FINE_ADJ(drv, MAX2831_RX_LPF_FINE_100);
set_MAX2831_TX_LPF_FINE_ADJ(drv, MAX2831_TX_LPF_FINE_100);
/* clock output disable */
set_MAX2831_CLKOUT_PIN_EN(drv, 0);
max2831_regs_commit(drv);
}
static void max2831_write(max2831_driver_t* const drv, uint8_t r, uint16_t v)
{
uint32_t word = (((uint32_t) v & 0x3fff) << 4) | (r & 0xf);
uint16_t values[2] = {word >> 9, word & 0x1ff};
spi_bus_transfer(drv->bus, values, 2);
}
uint16_t max2831_reg_read(max2831_driver_t* const drv, uint8_t r)
{
return drv->regs[r];
}
void max2831_reg_write(max2831_driver_t* const drv, uint8_t r, uint16_t v)
{
drv->regs[r] = v;
max2831_write(drv, r, v);
MAX2831_REG_SET_CLEAN(drv, r);
}
static inline void max2831_reg_commit(max2831_driver_t* const drv, uint8_t r)
{
max2831_reg_write(drv, r, drv->regs[r]);
}
void max2831_regs_commit(max2831_driver_t* const drv)
{
int r;
for (r = 0; r < MAX2831_NUM_REGS; r++) {
if ((drv->regs_dirty >> r) & 0x1) {
max2831_reg_commit(drv, r);
}
}
}
void max2831_set_mode(max2831_driver_t* const drv, const max2831_mode_t new_mode)
{
// Only change calibration bits if necessary to reduce SPI activity.
bool tx_cal = (new_mode == MAX2831_MODE_TX_CALIBRATION);
bool rx_cal = (new_mode == MAX2831_MODE_RX_CALIBRATION);
if (get_MAX2831_TX_CAL_MODE_EN(drv) != tx_cal) {
set_MAX2831_TX_CAL_MODE_EN(drv, tx_cal);
max2831_regs_commit(drv);
}
if (get_MAX2831_RX_CAL_MODE_EN(drv) != rx_cal) {
set_MAX2831_RX_CAL_MODE_EN(drv, rx_cal);
max2831_regs_commit(drv);
}
drv->set_mode(drv, new_mode);
max2831_set_lpf_bandwidth(drv, drv->desired_lpf_bw);
}
max2831_mode_t max2831_mode(max2831_driver_t* const drv)
{
return drv->mode;
}
void max2831_start(max2831_driver_t* const drv)
{
max2831_regs_commit(drv);
max2831_set_mode(drv, MAX2831_MODE_STANDBY);
}
void max2831_tx(max2831_driver_t* const drv)
{
max2831_regs_commit(drv);
max2831_set_mode(drv, MAX2831_MODE_TX);
}
void max2831_rx(max2831_driver_t* const drv)
{
max2831_regs_commit(drv);
max2831_set_mode(drv, MAX2831_MODE_RX);
}
void max2831_tx_calibration(max2831_driver_t* const drv)
{
max2831_regs_commit(drv);
max2831_set_mode(drv, MAX2831_MODE_TX_CALIBRATION);
}
void max2831_rx_calibration(max2831_driver_t* const drv)
{
max2831_regs_commit(drv);
max2831_set_mode(drv, MAX2831_MODE_RX_CALIBRATION);
}
void max2831_stop(max2831_driver_t* const drv)
{
max2831_regs_commit(drv);
max2831_set_mode(drv, MAX2831_MODE_SHUTDOWN);
}
void max2831_set_frequency(max2831_driver_t* const drv, uint32_t freq)
{
uint32_t div_frac;
uint32_t div_int;
uint32_t div_rem;
uint32_t div_cmp;
int i;
/* ASSUME 40MHz PLL. Ratio = F*R/40,000,000. */
/* TODO: fixed to R=2. Check if it's worth exploring R=1. */
freq += (20000000 >> 21); /* round to nearest frequency */
div_int = freq / 20000000;
div_rem = freq % 20000000;
div_frac = 0;
div_cmp = 20000000;
for (i = 0; i < 20; i++) {
div_frac <<= 1;
div_rem <<= 1;
if (div_rem >= div_cmp) {
div_frac |= 0x1;
div_rem -= div_cmp;
}
}
/* Write order matters? */
//set_MAX2831_SYN_REF_DIV(drv, MAX2831_SYN_REF_DIV_2);
set_MAX2831_SYN_INT(drv, div_int);
set_MAX2831_SYN_FRAC_HI(drv, (div_frac >> 6) & 0x3fff);
set_MAX2831_SYN_FRAC_LO(drv, div_frac & 0x3f);
max2831_regs_commit(drv);
}
typedef struct {
uint32_t bandwidth_hz;
uint8_t ft;
} max2831_ft_t;
typedef struct {
uint8_t percent;
uint8_t ft_fine;
} max2831_ft_fine_t;
// clang-format off
/* measured -0.5 dB complex baseband bandwidth for each register setting */
static const max2831_ft_t max2831_rx_ft[] = {
{ 11600000, MAX2831_RX_LPF_7_5M },
{ 15100000, MAX2831_RX_LPF_8_5M },
{ 22600000, MAX2831_RX_LPF_15M },
{ 28300000, MAX2831_RX_LPF_18M },
{ 0, 0 },
};
static const max2831_ft_fine_t max2831_rx_ft_fine[] = {
{ 90, MAX2831_RX_LPF_FINE_90 },
{ 95, MAX2831_RX_LPF_FINE_95 },
{ 100, MAX2831_RX_LPF_FINE_100 },
{ 105, MAX2831_RX_LPF_FINE_105 },
{ 110, MAX2831_RX_LPF_FINE_110 },
{ 0, 0 },
};
static const max2831_ft_t max2831_tx_ft[] = {
{ 16000000, MAX2831_TX_LPF_8M },
{ 22000000, MAX2831_TX_LPF_11M },
{ 33000000, MAX2831_TX_LPF_16_5M },
{ 45000000, MAX2831_TX_LPF_22_5M },
{ 0, 0 },
};
static const max2831_ft_fine_t max2831_tx_ft_fine[] = {
{ 90, MAX2831_TX_LPF_FINE_90 },
{ 95, MAX2831_TX_LPF_FINE_95 },
{ 100, MAX2831_TX_LPF_FINE_100 },
{ 105, MAX2831_TX_LPF_FINE_105 },
{ 110, MAX2831_TX_LPF_FINE_110 },
{ 115, MAX2831_TX_LPF_FINE_115 },
{ 0, 0 },
};
//clang-format on
uint32_t max2831_set_lpf_bandwidth(max2831_driver_t* const drv, const uint32_t bandwidth_hz) {
const max2831_ft_t* coarse;
const max2831_ft_fine_t* fine;
drv->desired_lpf_bw = bandwidth_hz;
if (drv->mode == MAX2831_MODE_RX) {
coarse = max2831_rx_ft;
fine = max2831_rx_ft_fine;
} else {
coarse = max2831_tx_ft;
fine = max2831_tx_ft_fine;
}
/* Find coarse and fine settings for LPF. */
bool found = false;
const max2831_ft_fine_t* f = fine;
for (; coarse->bandwidth_hz != 0; coarse++) {
uint32_t coarse_aux = coarse->bandwidth_hz / 100;
for (f = fine; f->percent != 0; f++) {
if ((coarse_aux * f->percent) >= drv->desired_lpf_bw) {
found = true;
break;
}
}
if (found) break;
}
/*
* Use the widest setting if a wider bandwidth than our maximum is
* requested.
*/
if (!found) {
coarse--;
f--;
}
/* Program found settings. */
set_MAX2831_LPF_COARSE(drv, coarse->ft);
if (drv->mode == MAX2831_MODE_RX) {
set_MAX2831_RX_LPF_FINE_ADJ(drv, f->ft_fine);
} else {
set_MAX2831_TX_LPF_FINE_ADJ(drv, f->ft_fine);
}
max2831_regs_commit(drv);
return coarse->bandwidth_hz * f->percent / 100;
}
bool max2831_set_lna_gain(max2831_driver_t* const drv, const uint32_t gain_db) {
uint16_t val;
switch(gain_db){
case 40: // MAX2837 compatibility
case 33:
case 32: // MAX2837 compatibility
val = MAX2831_LNA_GAIN_MAX;
break;
case 24: // MAX2837 compatibility
case 16:
val = MAX2831_LNA_GAIN_M16;
break;
case 8: // MAX2837 compatibility
case 0:
val = MAX2831_LNA_GAIN_M33;
break;
default:
return false;
}
set_MAX2831_LNA_GAIN(drv, val);
max2831_reg_commit(drv, 11);
return true;
}
bool max2831_set_vga_gain(max2831_driver_t* const drv, const uint32_t gain_db) {
if( (gain_db & 0x1) || gain_db > 62) {/* 0b11111*2 */
return false;
}
set_MAX2831_RXVGA_GAIN(drv, (gain_db >> 1) );
max2831_reg_commit(drv, 11);
return true;
}
bool max2831_set_txvga_gain(max2831_driver_t* const drv, const uint32_t gain_db) {
uint16_t value = MIN((gain_db << 1) | 1, 0x3f);
set_MAX2831_TXVGA_GAIN(drv, value);
max2831_reg_commit(drv, 12);
return true;
}

99
firmware/common/max2831.h Normal file
View File

@@ -0,0 +1,99 @@
/*
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __MAX2831_H
#define __MAX2831_H
#include <stdint.h>
#include <stdbool.h>
#include "gpio.h"
#include "spi_bus.h"
/* 16 registers, each containing 14 bits of data. */
#define MAX2831_NUM_REGS 16
#define MAX2831_DATA_REGS_MAX_VALUE 16384
typedef enum {
MAX2831_MODE_SHUTDOWN,
MAX2831_MODE_STANDBY,
MAX2831_MODE_TX,
MAX2831_MODE_RX,
MAX2831_MODE_TX_CALIBRATION,
MAX2831_MODE_RX_CALIBRATION,
} max2831_mode_t;
struct max2831_driver_t;
typedef struct max2831_driver_t max2831_driver_t;
struct max2831_driver_t {
spi_bus_t* bus;
gpio_t gpio_enable;
gpio_t gpio_rxtx;
gpio_t gpio_rxhp;
gpio_t gpio_ld;
void (*target_init)(max2831_driver_t* const drv);
void (*set_mode)(max2831_driver_t* const drv, const max2831_mode_t new_mode);
max2831_mode_t mode;
uint16_t regs[MAX2831_NUM_REGS];
uint16_t regs_dirty;
uint32_t desired_lpf_bw;
};
/* Initialize chip. */
extern void max2831_setup(max2831_driver_t* const drv);
/* Read a register via SPI. Save a copy to memory and return
* value. Mark clean. */
extern uint16_t max2831_reg_read(max2831_driver_t* const drv, uint8_t r);
/* Write value to register via SPI and save a copy to memory. Mark
* clean. */
extern void max2831_reg_write(max2831_driver_t* const drv, uint8_t r, uint16_t v);
/* Write all dirty registers via SPI from memory. Mark all clean. Some
* operations require registers to be written in a certain order. Use
* provided routines for those operations. */
extern void max2831_regs_commit(max2831_driver_t* const drv);
max2831_mode_t max2831_mode(max2831_driver_t* const drv);
void max2831_set_mode(max2831_driver_t* const drv, const max2831_mode_t new_mode);
/* Turn on/off all chip functions. Does not control oscillator and CLKOUT */
extern void max2831_start(max2831_driver_t* const drv);
extern void max2831_stop(max2831_driver_t* const drv);
/* Set frequency in Hz. Frequency setting is a multi-step function
* where order of register writes matters. */
extern void max2831_set_frequency(max2831_driver_t* const drv, uint32_t freq);
uint32_t max2831_set_lpf_bandwidth(
max2831_driver_t* const drv,
const uint32_t bandwidth_hz);
bool max2831_set_lna_gain(max2831_driver_t* const drv, const uint32_t gain_db);
bool max2831_set_vga_gain(max2831_driver_t* const drv, const uint32_t gain_db);
bool max2831_set_txvga_gain(max2831_driver_t* const drv, const uint32_t gain_db);
extern void max2831_tx(max2831_driver_t* const drv);
extern void max2831_rx(max2831_driver_t* const drv);
extern void max2831_tx_calibration(max2831_driver_t* const drv);
extern void max2831_rx_calibration(max2831_driver_t* const drv);
#endif // __MAX2831_H

View File

@@ -0,0 +1,132 @@
/* -*- mode: c -*- */
#ifndef __MAX2831_REGS_DEF
#define __MAX2831_REGS_DEF
/* Generate static inline accessors that operate on the global
* regs. Done this way to (1) allow defs to be scraped out and used
* elsewhere, e.g. in scripts, (2) to avoid dealing with endian
* (structs). This may be used in firmware, or on host predefined
* register loads. */
#define MAX2831_REG_SET_CLEAN(_d, _r) (_d->regs_dirty &= ~(1UL<<_r))
#define MAX2831_REG_SET_DIRTY(_d, _r) (_d->regs_dirty |= (1UL<<_r))
/* On set_, register is always set dirty, even if nothing
* changed. This makes sure that write that have side effects,
* e.g. frequency setting, are not skipped. */
/* n=name, r=regnum, o=offset (bits from LSB), l=length (bits) */
#define __MREG__(n,r,o,l) \
static inline uint16_t get_##n(max2831_driver_t* const _d) { \
return (_d->regs[r] >> o) & ((1<<l)-1); \
} \
static inline void set_##n(max2831_driver_t* const _d, uint16_t v) { \
_d->regs[r] &= ~(((1<<l)-1)<<o); \
_d->regs[r] |= ((v&((1<<l)-1))<<o); \
MAX2831_REG_SET_DIRTY(_d, r); \
}
/* REG 0 */
__MREG__(MAX2831_PLL_MODE, 0, 10, 1)
#define MAX2831_PLL_MODE_INTEGER 0
#define MAX2831_PLL_MODE_FRACTIONAL 1
/* REG 1 */
__MREG__(MAX2831_LOCK_DETECT_OUTPUT_SEL, 1, 12, 1)
#define MAX2831_LOCK_DETECT_OUTPUT_OPEN_DRAIN 0
#define MAX2831_LOCK_DETECT_OUTPUT_CMOS 1
/* REG 3 */
__MREG__(MAX2831_SYN_INT,3,0,8)
__MREG__(MAX2831_SYN_FRAC_LO,3,8,6)
/* REG 4 */
__MREG__(MAX2831_SYN_FRAC_HI,4,0,14)
/* REG 5 */
__MREG__(MAX2831_SYN_REF_DIV,5,2,1)
#define MAX2831_SYN_REF_DIV_1 0
#define MAX2831_SYN_REF_DIV_2 1
__MREG__(MAX2831_LOCK_DETECT_OUTPUT_EN,5,5,1)
__MREG__(MAX2831_LOCK_DETECT_OUTPUT_PULLUP_EN,5,9,1)
/* REG 6 */
__MREG__(MAX2831_RX_CAL_MODE_EN,6,0,1)
__MREG__(MAX2831_TX_CAL_MODE_EN,6,1,1)
__MREG__(MAX2831_TX_POWER_DETECT_EN,6,6,1)
__MREG__(MAX2831_TX_IQ_CALIB_GAIN,6,11,2)
#define MAX2831_TX_IQ_CALIB_GAIN_9 0
#define MAX2831_TX_IQ_CALIB_GAIN_19 1
#define MAX2831_TX_IQ_CALIB_GAIN_29 2
#define MAX2831_TX_IQ_CALIB_GAIN_39 3
/* REG 7 */
__MREG__(MAX2831_RX_LPF_FINE_ADJ,7,0,3)
#define MAX2831_RX_LPF_FINE_90 0
#define MAX2831_RX_LPF_FINE_95 1
#define MAX2831_RX_LPF_FINE_100 2
#define MAX2831_RX_LPF_FINE_105 3
#define MAX2831_RX_LPF_FINE_110 4
__MREG__(MAX2831_TX_LPF_FINE_ADJ,7,3,3)
#define MAX2831_TX_LPF_FINE_90 0
#define MAX2831_TX_LPF_FINE_95 1
#define MAX2831_TX_LPF_FINE_100 2
#define MAX2831_TX_LPF_FINE_105 3
#define MAX2831_TX_LPF_FINE_110 4
#define MAX2831_TX_LPF_FINE_115 5
__MREG__(MAX2831_RX_HPF_SEL,7,12,2)
#define MAX2831_RX_HPF_100_HZ 0
#define MAX2831_RX_HPF_4_KHZ 1
#define MAX2831_RX_HPF_30_KHZ 2
/* REG 8 */
__MREG__(MAX2831_LPF_COARSE,8,0,2)
#define MAX2831_RX_LPF_7_5M 0
#define MAX2831_RX_LPF_8_5M 1
#define MAX2831_RX_LPF_15M 2
#define MAX2831_RX_LPF_18M 3
#define MAX2831_TX_LPF_8M 0
#define MAX2831_TX_LPF_11M 1
#define MAX2831_TX_LPF_16_5M 2
#define MAX2831_TX_LPF_22_5M 3
__MREG__(MAX2831_RSSI_MUX,8,8,2)
#define MAX2831_RSSI_MUX_RSSI 0
#define MAX2831_RSSI_MUX_TEMP 1
#define MAX2831_RSSI_MUX_TX_POWER 2
__MREG__(MAX2831_RSSI_MODE,8,10,1) // set to override RXHP pin
__MREG__(MAX2831_RXVGA_GAIN_SPI_EN,8,12,1)
/* REG 9 */
__MREG__(MAX2831_TXVGA_GAIN_SPI_EN,9,10,1)
/* REG 10 */
__MREG__(MAX2831_PA_BIAS1_ADJ,10,0,3)
__MREG__(MAX2831_PA_BIAS2_ADJ,10,3,4)
__MREG__(MAX2831_PA_DELAY,10,10,4)
/* REG 11 */
__MREG__(MAX2831_RXVGA_GAIN,11,0,5)
__MREG__(MAX2831_LNA_GAIN,11,5,2)
#define MAX2831_LNA_GAIN_M33 0
#define MAX2831_LNA_GAIN_M16 2
#define MAX2831_LNA_GAIN_MAX 3
/* REG 12 */
__MREG__(MAX2831_TXVGA_GAIN,12,0,6)
/* REG 14 */
__MREG__(MAX2831_XTAL_TUNE,14,0,7)
__MREG__(MAX2831_CLKOUT_PIN_EN,14,9,1)
__MREG__(MAX2831_CLKOUT_DIV,14,10,1)
#define MAX2831_CLKOUT_DIV_1 0
#define MAX2831_CLKOUT_DIV_2 1
/* REG 15 */
__MREG__(MAX2831_RXIQ_VCM,15,10,2) // RX I/Q output common mode
#define MAX2831_RXIQ_VCM_1_1 0 // 1.1V
#define MAX2831_RXIQ_VCM_1_2 1 // 1.2V
#define MAX2831_RXIQ_VCM_1_3 2 // 1.3V
#define MAX2831_RXIQ_VCM_1_45 3 // 1.45V
#endif // __MAX2831_REGS_DEF

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "max2831_target.h"
#include <libopencm3/lpc43xx/scu.h>
#include "hackrf_core.h"
void max2831_target_init(max2831_driver_t* const drv)
{
/* Configure SSP1 Peripheral (to be moved later in SSP driver) */
scu_pinmux(SCU_SSP1_COPI, (SCU_SSP_IO | SCU_CONF_FUNCTION5));
scu_pinmux(SCU_SSP1_SCK, (SCU_SSP_IO | SCU_CONF_FUNCTION1));
scu_pinmux(SCU_XCVR_CS, SCU_XCVR_CS_PINCFG);
/*
* Configure XCVR_CTL GPIO pins.
*
* The RXTX pin is also known as RXENABLE because of its use on the
* MAX2837 which had a separate TXENABLE. On MAX2831 a single RXTX pin
* switches between RX (high) and TX (low) modes.
*/
scu_pinmux(SCU_XCVR_ENABLE, SCU_XCVR_ENABLE_PINCFG);
scu_pinmux(SCU_XCVR_RXENABLE, SCU_XCVR_RXENABLE_PINCFG);
scu_pinmux(SCU_XCVR_RXHP, SCU_XCVR_RXHP_PINCFG);
scu_pinmux(SCU_XCVR_LD, SCU_XCVR_LD_PINCFG);
/* Set GPIO pins as outputs. */
gpio_output(drv->gpio_enable);
gpio_output(drv->gpio_rxtx);
gpio_output(drv->gpio_rxhp);
gpio_input(drv->gpio_ld);
}
void max2831_target_set_mode(max2831_driver_t* const drv, const max2831_mode_t new_mode)
{
/* MAX2831_MODE_SHUTDOWN:
* All circuit blocks are powered down, except the 4-wire serial bus
* and its internal programmable registers.
*
* MAX2831_MODE_STANDBY:
* Used to enable the frequency synthesizer block while the rest of the
* device is powered down. In this mode, PLL, VCO, and LO generator
* are on, so that Tx or Rx modes can be quickly enabled from this mode.
* These and other blocks can be selectively enabled in this mode.
*
* MAX2831_MODE_TX:
* All Tx circuit blocks are powered on. The external PA is powered on
* after a programmable delay using the on-chip PA bias DAC. The slow-
* charging Rx circuits are in a precharged “idle-off” state for fast
* Tx-to-Rx turnaround time.
*
* MAX2831_MODE_RX:
* All Rx circuit blocks are powered on and active. Antenna signal is
* applied; RF is downconverted, filtered, and buffered at Rx BB I and Q
* outputs. The slow- charging Tx circuits are in a precharged “idle-off”
* state for fast Rx-to-Tx turnaround time.
*/
switch (new_mode) {
default:
case MAX2831_MODE_SHUTDOWN:
gpio_clear(drv->gpio_rxtx);
gpio_clear(drv->gpio_enable);
break;
case MAX2831_MODE_STANDBY:
gpio_set(drv->gpio_rxtx);
gpio_clear(drv->gpio_enable);
break;
case MAX2831_MODE_TX:
case MAX2831_MODE_TX_CALIBRATION:
gpio_set(drv->gpio_rxtx);
gpio_set(drv->gpio_enable);
break;
case MAX2831_MODE_RX:
case MAX2831_MODE_RX_CALIBRATION:
gpio_clear(drv->gpio_rxtx);
gpio_set(drv->gpio_enable);
break;
}
drv->mode = new_mode;
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __MAX2831_TARGET_H
#define __MAX2831_TARGET_H
#include "max2831.h"
void max2831_target_init(max2831_driver_t* const drv);
void max2831_target_set_mode(max2831_driver_t* const drv, const max2831_mode_t new_mode);
#endif // __MAX2831_TARGET_H

View File

@@ -33,11 +33,12 @@
#include <string.h>
#include "max2837.h"
#include "max2837_regs.def" // private register def macros
#include "selftest.h"
/* Default register values. */
static const uint16_t max2837_regs_default[MAX2837_NUM_REGS] = {
0x150, /* 0 */
0x002, /* 1 */
0x002, /* 1: data sheet says 0x002 but read 0x1c2 */
0x1f4, /* 2 */
0x1b9, /* 3 */
0x00a, /* 4 */
@@ -66,13 +67,17 @@ static const uint16_t max2837_regs_default[MAX2837_NUM_REGS] = {
0x1a9, /* 22 */
0x24f, /* 23 */
0x180, /* 24 */
0x100, /* 25 */
0x100, /* 25: data sheet says 0x100 but read 0x10a */
0x3ca, /* 26 */
0x3e3, /* 27 */
0x3e3, /* 27: data sheet says 0x100 but read 0x3f3 */
0x0c0, /* 28 */
0x3f0, /* 29 */
0x080, /* 30 */
0x000}; /* 31 */
0x080, /* 30: data sheet says 0x080 but read 0x092 */
0x000}; /* 31: data sheet says 0x000 but read 0x1ae */
static const uint8_t max2837_regs_skip_verify[] = {1, 25, 27, 30, 31};
static uint16_t max2837_read(max2837_driver_t* const drv, uint8_t r);
/* Set up all registers according to defaults specified in docs. */
static void max2837_init(max2837_driver_t* const drv)
@@ -85,6 +90,28 @@ static void max2837_init(max2837_driver_t* const drv)
/* Write default register values to chip. */
max2837_regs_commit(drv);
/* Read back registers to verify. */
selftest.max283x_readback_total_registers = MAX2837_NUM_REGS;
for (int r = 0; r < MAX2837_NUM_REGS; r++) {
for (unsigned int i = 0; i < sizeof(max2837_regs_skip_verify); i++) {
if (max2837_regs_skip_verify[i] == r) {
goto next;
}
}
uint16_t value = max2837_read(drv, r);
if (value != drv->regs[r]) {
selftest.max283x_readback_bad_value = value;
selftest.max283x_readback_expected_value = drv->regs[r];
break;
}
next:
selftest.max283x_readback_register_count = r + 1;
}
if (selftest.max283x_readback_register_count < MAX2837_NUM_REGS) {
selftest.report.pass = false;
}
}
/*

View File

@@ -33,12 +33,12 @@ void max2837_target_init(max2837_driver_t* const drv)
scu_pinmux(SCU_SSP1_COPI, (SCU_SSP_IO | SCU_CONF_FUNCTION5));
scu_pinmux(SCU_SSP1_SCK, (SCU_SSP_IO | SCU_CONF_FUNCTION1));
scu_pinmux(SCU_XCVR_CS, SCU_GPIO_FAST);
scu_pinmux(SCU_XCVR_CS, SCU_XCVR_CS_PINCFG);
/* Configure XCVR_CTL GPIO pins. */
scu_pinmux(SCU_XCVR_ENABLE, SCU_GPIO_FAST);
scu_pinmux(SCU_XCVR_RXENABLE, SCU_GPIO_FAST);
scu_pinmux(SCU_XCVR_TXENABLE, SCU_GPIO_FAST);
scu_pinmux(SCU_XCVR_ENABLE, SCU_XCVR_ENABLE_PINCFG);
scu_pinmux(SCU_XCVR_RXENABLE, SCU_XCVR_RXENABLE_PINCFG);
scu_pinmux(SCU_XCVR_TXENABLE, SCU_XCVR_TXENABLE_PINCFG);
/* Set GPIO pins as outputs. */
gpio_output(drv->gpio_enable);

View File

@@ -33,6 +33,7 @@
#include <string.h>
#include "max2839.h"
#include "max2839_regs.def" // private register def macros
#include "selftest.h"
static uint8_t requested_lna_gain = 0;
static uint8_t requested_vga_gain = 0;
@@ -40,9 +41,9 @@ static uint8_t requested_vga_gain = 0;
/* Default register values. */
static const uint16_t max2839_regs_default[MAX2839_NUM_REGS] = {
0x000, /* 0 */
0x00c, /* 1: data sheet says 0x00c but read 0x22c */
0x00c, /* 1: data sheet says 0x00c but read 0x20c or 0x22c*/
0x080, /* 2 */
0x1b9, /* 3: data sheet says 0x1b9 but read 0x1b0 */
0x1b0, /* 3: data sheet says 0x1b9 but read 0x1b0 or 0x1b9 */
0x3e6, /* 4 */
0x100, /* 5 */
0x000, /* 6 */
@@ -64,12 +65,12 @@ static const uint16_t max2839_regs_default[MAX2839_NUM_REGS] = {
0x1a9, /* 22 */
0x24f, /* 23 */
0x180, /* 24 */
0x000, /* 25: data sheet says 0x000 but read 0x00a */
0x00a, /* 25: data sheet says 0x000 but read 0x00a */
0x3c0, /* 26 */
0x200, /* 27: data sheet says 0x200 but read 0x22a */
0x200, /* 27: data sheet says 0x200 but read 0x22a or 0x22f */
0x0c0, /* 28 */
0x03f, /* 29: data sheet says 0x03f but read 0x07f */
0x300, /* 30: data sheet says 0x300 but read 0x398 */
0x03f, /* 29: data sheet says 0x03f but read 0x07f or 0x17f */
0x300, /* 30: data sheet says 0x300 but read 0x398 or 0x31a */
0x340}; /* 31: data sheet says 0x340 but read 0x359 */
/*
@@ -79,6 +80,10 @@ static const uint16_t max2839_regs_default[MAX2839_NUM_REGS] = {
* different settings.
*/
static const uint8_t max2839_regs_skip_verify[] = {1, 3, 8, 11, 21, 25, 27, 29, 30, 31};
static uint16_t max2839_read(max2839_driver_t* const drv, uint8_t r);
/* Set up all registers according to defaults specified in docs. */
static void max2839_init(max2839_driver_t* const drv)
{
@@ -90,6 +95,28 @@ static void max2839_init(max2839_driver_t* const drv)
/* Write default register values to chip. */
max2839_regs_commit(drv);
/* Read back registers to verify. */
selftest.max283x_readback_total_registers = MAX2839_NUM_REGS;
for (int r = 0; r < MAX2839_NUM_REGS; r++) {
for (unsigned int i = 0; i < sizeof(max2839_regs_skip_verify); i++) {
if (max2839_regs_skip_verify[i] == r) {
goto next;
}
}
uint16_t value = max2839_read(drv, r);
if (value != drv->regs[r]) {
selftest.max283x_readback_bad_value = value;
selftest.max283x_readback_expected_value = drv->regs[r];
break;
}
next:
selftest.max283x_readback_register_count = r + 1;
}
if (selftest.max283x_readback_register_count < MAX2839_NUM_REGS) {
selftest.report.pass = false;
}
}
/*

View File

@@ -32,9 +32,15 @@
#include "spi_bus.h"
extern spi_bus_t spi_bus_ssp1;
#ifdef PRALINE
static struct gpio_t gpio_max2837_enable = GPIO(6, 29);
static struct gpio_t gpio_max2837_rx_enable = GPIO(3, 3);
static struct gpio_t gpio_max2837_tx_enable = GPIO(3, 2);
#else
static struct gpio_t gpio_max2837_enable = GPIO(2, 6);
static struct gpio_t gpio_max2837_rx_enable = GPIO(2, 5);
static struct gpio_t gpio_max2837_tx_enable = GPIO(2, 4);
#endif
max2837_driver_t max2837 = {
.bus = &spi_bus_ssp1,

View File

@@ -21,6 +21,7 @@
#include "max2871.h"
#include "max2871_regs.h"
#include "selftest.h"
#if (defined DEBUG)
#include <stdio.h>
@@ -35,6 +36,7 @@
#include <stdint.h>
#include <string.h>
static uint32_t max2871_spi_read(max2871_driver_t* const drv);
static void max2871_spi_write(max2871_driver_t* const drv, uint8_t r, uint32_t v);
static void max2871_write_registers(max2871_driver_t* const drv);
static void delay_ms(int ms);
@@ -67,6 +69,11 @@ void max2871_setup(max2871_driver_t* const drv)
gpio_set(drv->gpio_vco_le); /* active low */
gpio_set(drv->gpio_synt_rfout_en); /* active high */
selftest.mixer_id = max2871_spi_read(drv) >> MAX2871_DIE_SHIFT;
if (selftest.mixer_id != 7) {
selftest.report.pass = false;
}
max2871_regs_init();
int i;
for (i = 5; i >= 0; i--) {

View File

@@ -23,7 +23,8 @@
#define MAX2871_REGS_H
#include <stdint.h>
#define MAX2871_VASA (1 << 9)
#define MAX2871_VASA (1 << 9)
#define MAX2871_DIE_SHIFT 28
void max2871_regs_init(void);
uint32_t max2871_get_register(int reg);

View File

@@ -38,5 +38,5 @@ void max5864_target_init(max5864_driver_t* const drv)
* Configure CS_AD pin to keep the MAX5864 SPI disabled while we use the
* SPI bus for the MAX2837. FIXME: this should probably be somewhere else.
*/
scu_pinmux(SCU_AD_CS, SCU_GPIO_FAST);
scu_pinmux(SCU_AD_CS, SCU_AD_CS_PINCFG);
}

View File

@@ -41,9 +41,16 @@ static struct gpio_t gpio_vco_le = GPIO(2, 14);
static struct gpio_t gpio_vco_mux = GPIO(5, 25);
static struct gpio_t gpio_synt_rfout_en = GPIO(3, 5);
#endif
#ifdef PRALINE
static struct gpio_t gpio_rffc5072_select = GPIO(2, 13);
static struct gpio_t gpio_rffc5072_clock = GPIO(5, 18);
static struct gpio_t gpio_rffc5072_data = GPIO(4, 14);
static struct gpio_t gpio_rffc5072_reset = GPIO(2, 14);
static struct gpio_t gpio_rffc5072_ld = GPIO(6, 25);
#endif
// clang-format on
#if (defined JAWBREAKER || defined HACKRF_ONE)
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
const rffc5071_spi_config_t rffc5071_spi_config = {
.gpio_select = &gpio_rffc5072_select,
.gpio_clock = &gpio_rffc5072_clock,
@@ -61,6 +68,9 @@ spi_bus_t spi_bus_rffc5071 = {
mixer_driver_t mixer = {
.bus = &spi_bus_rffc5071,
.gpio_reset = &gpio_rffc5072_reset,
#ifdef PRALINE
.gpio_ld = &gpio_rffc5072_ld,
#endif
};
#endif
#ifdef RAD1O
@@ -76,7 +86,7 @@ mixer_driver_t mixer = {
void mixer_bus_setup(mixer_driver_t* const mixer)
{
#if (defined JAWBREAKER || defined HACKRF_ONE)
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
(void) mixer;
spi_bus_start(&spi_bus_rffc5071, &rffc5071_spi_config);
#endif
@@ -87,7 +97,7 @@ void mixer_bus_setup(mixer_driver_t* const mixer)
void mixer_setup(mixer_driver_t* const mixer)
{
#if (defined JAWBREAKER || defined HACKRF_ONE)
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
rffc5071_setup(mixer);
#endif
#ifdef RAD1O
@@ -97,7 +107,7 @@ void mixer_setup(mixer_driver_t* const mixer)
uint64_t mixer_set_frequency(mixer_driver_t* const mixer, uint64_t hz)
{
#if (defined JAWBREAKER || defined HACKRF_ONE)
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
return rffc5071_set_frequency(mixer, hz);
#endif
#ifdef RAD1O
@@ -107,7 +117,7 @@ uint64_t mixer_set_frequency(mixer_driver_t* const mixer, uint64_t hz)
void mixer_tx(mixer_driver_t* const mixer)
{
#if (defined JAWBREAKER || defined HACKRF_ONE)
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
rffc5071_tx(mixer);
#endif
#ifdef RAD1O
@@ -117,7 +127,7 @@ void mixer_tx(mixer_driver_t* const mixer)
void mixer_rx(mixer_driver_t* const mixer)
{
#if (defined JAWBREAKER || defined HACKRF_ONE)
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
rffc5071_rx(mixer);
#endif
#ifdef RAD1O
@@ -127,7 +137,7 @@ void mixer_rx(mixer_driver_t* const mixer)
void mixer_rxtx(mixer_driver_t* const mixer)
{
#if (defined JAWBREAKER || defined HACKRF_ONE)
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
rffc5071_rxtx(mixer);
#endif
#ifdef RAD1O
@@ -137,7 +147,7 @@ void mixer_rxtx(mixer_driver_t* const mixer)
void mixer_enable(mixer_driver_t* const mixer)
{
#if (defined JAWBREAKER || defined HACKRF_ONE)
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
rffc5071_enable(mixer);
#endif
#ifdef RAD1O
@@ -147,7 +157,7 @@ void mixer_enable(mixer_driver_t* const mixer)
void mixer_disable(mixer_driver_t* const mixer)
{
#if (defined JAWBREAKER || defined HACKRF_ONE)
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
rffc5071_disable(mixer);
#endif
#ifdef RAD1O
@@ -157,7 +167,7 @@ void mixer_disable(mixer_driver_t* const mixer)
void mixer_set_gpo(mixer_driver_t* const mixer, uint8_t gpo)
{
#if (defined JAWBREAKER || defined HACKRF_ONE)
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
rffc5071_set_gpo(mixer, gpo);
#endif
#ifdef RAD1O

View File

@@ -23,7 +23,7 @@
#ifndef __MIXER_H
#define __MIXER_H
#if (defined JAWBREAKER || defined HACKRF_ONE)
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
#include "rffc5071.h"
typedef rffc5071_driver_t mixer_driver_t;
#endif

View File

@@ -51,6 +51,9 @@ static uint32_t default_output = 0;
*
* To trigger the antenna switching synchronously with the sample clock, the
* SGPIO is configured to output its clock (f=2 * sample clock) to the SCTimer.
*
* On Praline, instead, MS0/CLK1 (SCT_CLK) is configured to output its
* clock (f=2 * sample clock) to the SCTimer.
*/
void operacake_sctimer_init()
{
@@ -89,6 +92,7 @@ void operacake_sctimer_init()
P7_0,
SCU_CONF_EPUN_DIS_PULLUP | SCU_CONF_EHS_FAST | SCU_CONF_FUNCTION1);
#ifndef PRALINE
// Configure the SGPIO to output the clock (f=2 * sample clock) on pin 12
SGPIO_OUT_MUX_CFG12 = SGPIO_OUT_MUX_CFG_P_OUT_CFG(0x08) | // clkout output mode
SGPIO_OUT_MUX_CFG_P_OE_CFG(0); // gpio_oe
@@ -97,9 +101,20 @@ void operacake_sctimer_init()
// Use the GIMA to connect the SGPIO clock to the SCTimer
GIMA_CTIN_1_IN = 0x2 << 4; // Route SGPIO12 to SCTIN1
uint8_t sct_clock_input = SCT_CONFIG_CKSEL_RISING_EDGES_ON_INPUT_1;
#else
// Configure pin P6_4 as SCT_IN_6
scu_pinmux(P6_4, SCU_CLK_IN | SCU_CONF_FUNCTION1);
// Use the GIMA to connect MS0/CLK1 (SCT_CLK) on pin P6_4 to the SCTimer
GIMA_CTIN_6_IN = 0x0 << 4;
uint8_t sct_clock_input = SCT_CONFIG_CKSEL_RISING_EDGES_ON_INPUT_6;
#endif
// We configure this register first, because the user manual says to
SCT_CONFIG |= SCT_CONFIG_UNIFY_32_BIT | SCT_CONFIG_CLKMODE_PRESCALED_BUS_CLOCK |
SCT_CONFIG_CKSEL_RISING_EDGES_ON_INPUT_1;
sct_clock_input;
// Halt the SCTimer to enable it to be configured
SCT_CTRL = SCT_CTRL_HALT_L(1);

View File

@@ -32,28 +32,32 @@ static board_rev_t revision = BOARD_REV_UNDETECTED;
static struct gpio_t gpio2_9_on_P5_0 = GPIO(2, 9);
static struct gpio_t gpio3_6_on_P6_10 = GPIO(3, 6);
static struct gpio_t gpio3_4_on_P6_5 = GPIO(3, 4);
static struct gpio_t gpio2_6_on_P4_6 = GPIO(2, 6);
#define P5_0_PUP (1 << 0)
#define P5_0_PDN (1 << 1)
#define P6_10_PUP (1 << 2)
#define P6_10_PDN (1 << 3)
#define P6_5_PDN (1 << 4)
/*
* Jawbreaker has a pull-down on P6_10 and nothing on P5_0.
* rad1o has a pull-down on P6_10 and a pull-down on P5_0.
* HackRF One OG has a pull-down on P6_10 and a pull-up on P5_0.
* HackRF One r9 has a pull-up on P6_10 and a pull-down on P5_0.
*/
* Praline has a pull-down on P6_5. */
#define JAWBREAKER_RESISTORS (P6_10_PDN)
#define RAD1O_RESISTORS (P6_10_PDN | P5_0_PDN)
#define HACKRF1_OG_RESISTORS (P6_10_PDN | P5_0_PUP)
#define HACKRF1_R9_RESISTORS (P6_10_PUP | P5_0_PDN)
#define PRALINE_RESISTORS (P6_5_PDN)
/*
* LEDs are configured so that they flash if the detected hardware platform is
* not supported by the firmware binary. Only two LEDs are flashed for a
* hardware detection failure, but three LEDs are flashed if CPLD configuration
* hardware detection failure, but three LEDs are flashed if CPLD/FPGA configuration
* fails.
*/
static struct gpio_t gpio_led1 = GPIO(2, 1);
@@ -109,7 +113,7 @@ uint32_t check_pin_strap(uint8_t pin)
* scheme with ADC0_3 tied to VCC.
*/
// clang-format off
static const uint8_t revision_from_adc[32] = {
static const uint8_t hackrf_revision_from_adc[32] = {
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
@@ -144,6 +148,47 @@ static const uint8_t revision_from_adc[32] = {
BOARD_REV_HACKRF1_R8
};
/*
* Starting with r0.1, Praline also uses a voltage on ADC0_3 to set an
* analog voltage that indicates the hardware revision. The high five
* bits of the ADC result are mapped to 32 revisions. Note that,
* unlike HackRF One, Praline revisions are mapped in ascending order.
*/
static const uint8_t praline_revision_from_adc[32] = {
BOARD_REV_PRALINE_R0_1,
BOARD_REV_PRALINE_R0_2,
BOARD_REV_PRALINE_R0_3,
BOARD_REV_PRALINE_R1_0,
BOARD_REV_PRALINE_R1_1,
BOARD_REV_PRALINE_R1_2,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
BOARD_REV_UNRECOGNIZED,
};
// clang-format on
void detect_hardware_platform(void)
@@ -158,6 +203,7 @@ void detect_hardware_platform(void)
gpio_input(&gpio2_9_on_P5_0);
gpio_input(&gpio3_6_on_P6_10);
gpio_input(&gpio3_4_on_P6_5);
/* activate internal pull-down */
scu_pinmux(P5_0, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
@@ -174,14 +220,17 @@ void detect_hardware_platform(void)
/* activate internal pull-up */
scu_pinmux(P5_0, SCU_GPIO_PUP | SCU_CONF_FUNCTION0);
scu_pinmux(P6_10, SCU_GPIO_PUP | SCU_CONF_FUNCTION0);
scu_pinmux(P6_5, SCU_GPIO_PUP | SCU_CONF_FUNCTION0);
delay_us_at_mhz(4, 96);
/* tri-state for a moment before testing input */
scu_pinmux(P5_0, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
scu_pinmux(P6_10, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
scu_pinmux(P6_5, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
delay_us_at_mhz(4, 96);
/* if input fell quickly, there must be an external pull-down */
detected_resistors |= (gpio_read(&gpio2_9_on_P5_0)) ? 0 : P5_0_PDN;
detected_resistors |= (gpio_read(&gpio3_6_on_P6_10)) ? 0 : P6_10_PDN;
detected_resistors |= (gpio_read(&gpio3_4_on_P6_5)) ? 0 : P6_5_PDN;
switch (detected_resistors) {
case JAWBREAKER_RESISTORS:
@@ -208,6 +257,12 @@ void detect_hardware_platform(void)
}
platform = BOARD_ID_HACKRF1_R9;
break;
case PRALINE_RESISTORS:
if (!(supported_platform() & PLATFORM_PRALINE)) {
halt_and_flash(3000000);
}
platform = BOARD_ID_PRALINE;
break;
default:
platform = BOARD_ID_UNRECOGNIZED;
halt_and_flash(1000000);
@@ -225,7 +280,7 @@ void detect_hardware_platform(void)
} else if (LOW(adc0_3) && HIGH(adc0_4)) {
revision = BOARD_REV_HACKRF1_R7;
} else if (LOW(adc0_4)) {
revision = revision_from_adc[adc0_3 >> 5];
revision = hackrf_revision_from_adc[adc0_3 >> 5];
} else {
revision = BOARD_REV_UNRECOGNIZED;
}
@@ -235,10 +290,27 @@ void detect_hardware_platform(void)
} else {
revision = BOARD_REV_UNRECOGNIZED;
}
} else if (platform == BOARD_ID_PRALINE) {
revision = praline_revision_from_adc[adc0_3 >> 5];
}
if ((revision > BOARD_REV_HACKRF1_OLD) && LOW(adc0_7)) {
revision |= BOARD_REV_GSG;
switch (platform) {
case BOARD_ID_HACKRF1_OG:
case BOARD_ID_HACKRF1_R9:
if ((revision > BOARD_REV_HACKRF1_OLD) && LOW(adc0_7)) {
revision |= BOARD_REV_GSG;
}
break;
case BOARD_ID_PRALINE:
scu_pinmux(P4_6, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
gpio_input(&gpio2_6_on_P4_6);
if (gpio_read(&gpio2_6_on_P4_6)) {
revision |= BOARD_REV_GSG;
}
scu_pinmux(P4_6, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
break;
default:
break;
}
}

View File

@@ -30,6 +30,7 @@
#define PLATFORM_HACKRF1_OG (1 << 1)
#define PLATFORM_RAD1O (1 << 2)
#define PLATFORM_HACKRF1_R9 (1 << 3)
#define PLATFORM_PRALINE (1 << 4)
typedef enum {
BOARD_ID_JELLYBEAN = 0,
@@ -37,6 +38,7 @@ typedef enum {
BOARD_ID_HACKRF1_OG = 2, /* HackRF One prior to r9 */
BOARD_ID_RAD1O = 3,
BOARD_ID_HACKRF1_R9 = 4,
BOARD_ID_PRALINE = 5,
BOARD_ID_UNRECOGNIZED = 0xFE, /* tried detection but did not recognize board */
BOARD_ID_UNDETECTED = 0xFF, /* detection not yet attempted */
} board_id_t;
@@ -48,11 +50,23 @@ typedef enum {
BOARD_REV_HACKRF1_R8 = 3,
BOARD_REV_HACKRF1_R9 = 4,
BOARD_REV_HACKRF1_R10 = 5,
BOARD_REV_PRALINE_R0_1 = 6,
BOARD_REV_PRALINE_R0_2 = 7,
BOARD_REV_PRALINE_R0_3 = 8,
BOARD_REV_PRALINE_R1_0 = 9,
BOARD_REV_PRALINE_R1_1 = 10,
BOARD_REV_PRALINE_R1_2 = 11,
BOARD_REV_GSG_HACKRF1_R6 = 0x81,
BOARD_REV_GSG_HACKRF1_R7 = 0x82,
BOARD_REV_GSG_HACKRF1_R8 = 0x83,
BOARD_REV_GSG_HACKRF1_R9 = 0x84,
BOARD_REV_GSG_HACKRF1_R10 = 0x85,
BOARD_REV_GSG_PRALINE_R0_1 = 0x86,
BOARD_REV_GSG_PRALINE_R0_2 = 0x87,
BOARD_REV_GSG_PRALINE_R0_3 = 0x88,
BOARD_REV_GSG_PRALINE_R1_0 = 0x89,
BOARD_REV_GSG_PRALINE_R1_1 = 0x8a,
BOARD_REV_GSG_PRALINE_R1_2 = 0x8b,
BOARD_REV_UNRECOGNIZED =
0xFE, /* tried detection but did not recognize revision */
BOARD_REV_UNDETECTED = 0xFF, /* detection not yet attempted */

460
firmware/common/radio.c Normal file
View File

@@ -0,0 +1,460 @@
/*
* Copyright 2012-2025 Great Scott Gadgets <info@greatscottgadgets.com>
* Copyright 2012 Jared Boone
* Copyright 2013 Benjamin Vernoux
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "hackrf_core.h"
#include "tuning.h"
#include "radio.h"
radio_error_t radio_set_sample_rate(
radio_t* radio,
radio_chan_id chan_id,
radio_sample_rate_id element,
radio_sample_rate_t sample_rate)
{
// we only support the clock generator at the moment
if (element != RADIO_SAMPLE_RATE_CLOCKGEN) {
return RADIO_ERR_INVALID_ELEMENT;
}
radio_config_t* config = &radio->channel[chan_id].config;
// TODO get the actual tuned frequency from sample_rate_frac_set
sample_rate.hz = (double) sample_rate.num / (double) sample_rate.div;
if (config->mode == TRANSCEIVER_MODE_OFF) {
config->sample_rate[element] = sample_rate;
return RADIO_OK;
}
bool ok = sample_rate_frac_set(sample_rate.num, sample_rate.div);
if (!ok) {
return RADIO_ERR_INVALID_PARAM;
}
config->sample_rate[element] = sample_rate;
return RADIO_OK;
}
radio_sample_rate_t radio_get_sample_rate(
radio_t* radio,
radio_chan_id chan_id,
radio_sample_rate_id element)
{
return radio->channel[chan_id].config.sample_rate[element];
}
radio_error_t radio_set_filter(
radio_t* radio,
radio_chan_id chan_id,
radio_filter_id element,
radio_filter_t filter)
{
// we only support the baseband filter at the moment
if (element != RADIO_FILTER_BASEBAND) {
return RADIO_ERR_INVALID_ELEMENT;
}
radio_config_t* config = &radio->channel[chan_id].config;
if (config->mode == TRANSCEIVER_MODE_OFF) {
config->filter[element] = filter;
return RADIO_OK;
}
uint32_t real_hz;
#ifndef PRALINE
real_hz = max283x_set_lpf_bandwidth(&max283x, filter.hz);
#else
real_hz = max2831_set_lpf_bandwidth(&max283x, filter.hz);
#endif
if (real_hz == 0) {
return RADIO_ERR_INVALID_PARAM;
}
config->filter[element] = (radio_filter_t){.hz = real_hz};
return RADIO_OK;
}
radio_filter_t radio_get_filter(
radio_t* radio,
radio_chan_id chan_id,
radio_filter_id element)
{
return radio->channel[chan_id].config.filter[element];
}
radio_error_t radio_set_gain(
radio_t* radio,
radio_chan_id chan_id,
radio_gain_id element,
radio_gain_t gain)
{
if (element > RADIO_GAIN_COUNT) {
return RADIO_ERR_INVALID_ELEMENT;
}
radio_config_t* config = &radio->channel[chan_id].config;
if (config->mode == TRANSCEIVER_MODE_OFF) {
config->gain[element] = gain;
return RADIO_OK;
}
uint8_t real_db;
switch (element) {
case RADIO_GAIN_RF_AMP:
rf_path_set_lna(&rf_path, gain.enable);
break;
case RADIO_GAIN_RX_LNA:
#ifndef PRALINE
real_db = max283x_set_lna_gain(&max283x, gain.db);
#else
real_db = max2831_set_lna_gain(&max283x, gain.db);
#endif
if (real_db == 0) {
return RADIO_ERR_INVALID_PARAM;
}
break;
case RADIO_GAIN_RX_VGA:
#ifndef PRALINE
real_db = max283x_set_vga_gain(&max283x, gain.db);
#else
real_db = max2831_set_vga_gain(&max283x, gain.db);
#endif
if (real_db == 0) {
return RADIO_ERR_INVALID_PARAM;
}
break;
case RADIO_GAIN_TX_VGA:
#ifndef PRALINE
real_db = max283x_set_txvga_gain(&max283x, gain.db);
#else
real_db = max2831_set_txvga_gain(&max283x, gain.db);
#endif
if (real_db == 0) {
return RADIO_ERR_INVALID_PARAM;
}
break;
}
config->gain[element] = gain;
return RADIO_OK;
}
radio_gain_t radio_get_gain(radio_t* radio, radio_chan_id chan_id, radio_gain_id element)
{
return radio->channel[chan_id].config.gain[element];
}
radio_error_t radio_set_frequency(
radio_t* radio,
radio_chan_id chan_id,
radio_frequency_id element,
radio_frequency_t frequency)
{
// we only support setting the final rf frequency at the moment
if (element != RADIO_FREQUENCY_RF) {
return RADIO_ERR_INVALID_ELEMENT;
}
radio_config_t* config = &radio->channel[chan_id].config;
if (config->mode == TRANSCEIVER_MODE_OFF) {
config->frequency[element] = frequency;
return RADIO_OK;
}
// explicit
if (frequency.if_hz || frequency.lo_hz) {
bool ok = set_freq_explicit(
frequency.if_hz,
frequency.lo_hz,
frequency.path);
if (!ok) {
return RADIO_ERR_INVALID_PARAM;
}
config->frequency[element] = frequency;
return RADIO_OK;
}
// auto-tune
uint64_t real_hz;
#ifndef PRALINE
switch (config->mode) {
case TRANSCEIVER_MODE_RX:
case TRANSCEIVER_MODE_RX_SWEEP:
case TRANSCEIVER_MODE_TX:
// TODO return if, of components so we can support them in the getter
real_hz = tuning_set_frequency(max283x_tune_config, frequency.hz);
break;
default:
return RADIO_ERR_INVALID_CONFIG;
}
#else
switch (config->mode) {
case TRANSCEIVER_MODE_RX:
real_hz = tuning_set_frequency(max2831_tune_config_rx, frequency.hz);
break;
case TRANSCEIVER_MODE_RX_SWEEP:
real_hz =
tuning_set_frequency(max2831_tune_config_rx_sweep, frequency.hz);
break;
case TRANSCEIVER_MODE_TX:
real_hz = tuning_set_frequency(max2831_tune_config_tx, frequency.hz);
break;
default:
return RADIO_ERR_INVALID_CONFIG;
}
#endif
if (real_hz == 0) {
return RADIO_ERR_INVALID_PARAM;
}
frequency.hz = real_hz;
config->frequency[element] = frequency;
return RADIO_OK;
}
radio_frequency_t radio_get_frequency(
radio_t* radio,
radio_chan_id chan_id,
radio_frequency_id element)
{
return radio->channel[chan_id].config.frequency[element];
}
radio_error_t radio_set_antenna(
radio_t* radio,
radio_chan_id chan_id,
radio_antenna_id element,
radio_antenna_t value)
{
if (element > RADIO_ANTENNA_COUNT) {
return RADIO_ERR_INVALID_ELEMENT;
}
radio_config_t* config = &radio->channel[chan_id].config;
if (config->mode == TRANSCEIVER_MODE_OFF) {
config->antenna[element] = value;
return RADIO_OK;
}
switch (element) {
case RADIO_ANTENNA_BIAS_TEE:
rf_path_set_antenna(
&rf_path,
config->antenna[RADIO_ANTENNA_BIAS_TEE].enable);
break;
}
config->antenna[element] = value;
return RADIO_OK;
}
radio_antenna_t radio_get_antenna(
radio_t* radio,
radio_chan_id chan_id,
radio_antenna_id element)
{
return radio->channel[chan_id].config.antenna[element];
}
radio_error_t radio_set_clock(
radio_t* radio,
radio_chan_id chan_id,
radio_clock_id element,
radio_clock_t value)
{
radio_config_t* config = &radio->channel[chan_id].config;
if (element > RADIO_CLOCK_COUNT) {
return RADIO_ERR_INVALID_ELEMENT;
}
// CLKIN is not supported as it is automatically detected from hardware state
if (element == RADIO_CLOCK_CLKIN) {
return RADIO_ERR_UNSUPPORTED_OPERATION;
}
si5351c_clkout_enable(&clock_gen, value.enable);
config->clock[element] = value;
return RADIO_OK;
}
radio_clock_t radio_get_clock(
radio_t* radio,
radio_chan_id chan_id,
radio_clock_id element)
{
if (element == RADIO_CLOCK_CLKIN) {
return (radio_clock_t){
.enable = si5351c_clkin_signal_valid(&clock_gen),
};
}
return radio->channel[chan_id].config.clock[element];
}
radio_error_t radio_set_trigger_mode(
radio_t* radio,
radio_chan_id chan_id,
hw_sync_mode_t mode)
{
radio_config_t* config = &radio->channel[chan_id].config;
config->trigger_mode = mode;
return RADIO_OK;
}
hw_sync_mode_t radio_get_trigger_mode(radio_t* radio, radio_chan_id chan_id)
{
return radio->channel[chan_id].config.trigger_mode;
}
transceiver_mode_t radio_get_mode(radio_t* radio, radio_chan_id chan_id)
{
return radio->channel[chan_id].config.mode;
}
rf_path_direction_t radio_get_direction(radio_t* radio, radio_chan_id chan_id)
{
radio_config_t* config = &radio->channel[chan_id].config;
switch (config->mode) {
case TRANSCEIVER_MODE_RX:
case TRANSCEIVER_MODE_RX_SWEEP:
return RF_PATH_DIRECTION_RX;
case TRANSCEIVER_MODE_TX:
return RF_PATH_DIRECTION_TX;
default:
return RF_PATH_DIRECTION_OFF;
}
}
clock_source_t radio_get_clock_source(radio_t* radio, radio_chan_id chan_id)
{
return radio->channel[chan_id].clock_source;
}
radio_error_t radio_switch_mode(
radio_t* radio,
radio_chan_id chan_id,
transceiver_mode_t mode)
{
radio_error_t result;
radio_channel_t* channel = &radio->channel[chan_id];
radio_config_t* config = &channel->config;
// configure firmware direction from mode (but don't configure the hardware yet!)
rf_path_direction_t direction;
switch (mode) {
case TRANSCEIVER_MODE_RX:
case TRANSCEIVER_MODE_RX_SWEEP:
direction = RF_PATH_DIRECTION_RX;
break;
case TRANSCEIVER_MODE_TX:
direction = RF_PATH_DIRECTION_TX;
break;
default:
rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_OFF);
config->mode = mode;
return RADIO_OK;
}
config->mode = mode;
// sample rate
radio_sample_rate_t sample_rate =
radio_get_sample_rate(radio, channel->id, RADIO_SAMPLE_RATE_CLOCKGEN);
result = radio_set_sample_rate(
radio,
channel->id,
RADIO_SAMPLE_RATE_CLOCKGEN,
sample_rate);
if (result != RADIO_OK) {
return result;
}
// baseband filter bandwidth
radio_filter_t filter =
radio_get_filter(radio, channel->id, RADIO_FILTER_BASEBAND);
result = radio_set_filter(radio, channel->id, RADIO_FILTER_BASEBAND, filter);
if (result != RADIO_OK) {
return result;
}
// rf_amp enable
radio_gain_t enable = radio_get_gain(radio, channel->id, RADIO_GAIN_RF_AMP);
result = radio_set_gain(radio, channel->id, RADIO_GAIN_RF_AMP, enable);
if (result != RADIO_OK) {
return result;
}
// gain
radio_gain_t gain;
if (config->mode == TRANSCEIVER_MODE_RX ||
config->mode == TRANSCEIVER_MODE_RX_SWEEP) {
gain = radio_get_gain(radio, channel->id, RADIO_GAIN_RX_LNA);
result = radio_set_gain(radio, channel->id, RADIO_GAIN_RX_LNA, gain);
if (result != RADIO_OK) {
return result;
}
gain = radio_get_gain(radio, channel->id, RADIO_GAIN_RX_VGA);
result = radio_set_gain(radio, channel->id, RADIO_GAIN_RX_VGA, gain);
if (result != RADIO_OK) {
return result;
}
} else if (config->mode == TRANSCEIVER_MODE_TX) {
gain = radio_get_gain(radio, channel->id, RADIO_GAIN_TX_VGA);
result = radio_set_gain(radio, channel->id, RADIO_GAIN_TX_VGA, gain);
if (result != RADIO_OK) {
return result;
}
}
// antenna
radio_antenna_t bias_tee =
radio_get_antenna(radio, channel->id, RADIO_ANTENNA_BIAS_TEE);
result = radio_set_antenna(radio, channel->id, RADIO_ANTENNA_BIAS_TEE, bias_tee);
if (result != RADIO_OK) {
return result;
}
// tuning frequency
radio_frequency_t frequency =
radio_get_frequency(radio, channel->id, RADIO_FREQUENCY_RF);
result = radio_set_frequency(radio, channel->id, RADIO_FREQUENCY_RF, frequency);
if (result != RADIO_OK) {
return result;
}
// finally, set the rf path direction
rf_path_set_direction(&rf_path, direction);
return RADIO_OK;
}

264
firmware/common/radio.h Normal file
View File

@@ -0,0 +1,264 @@
/*
* Copyright 2012-2025 Great Scott Gadgets <info@greatscottgadgets.com>
* Copyright 2012 Jared Boone
* Copyright 2013 Benjamin Vernoux
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __RF_CONFIG_H__
#define __RF_CONFIG_H__
#include <stdint.h>
#include <stdbool.h>
#include "rf_path.h"
typedef enum {
RADIO_OK = 1,
RADIO_ERR_INVALID_PARAM = -2,
RADIO_ERR_INVALID_CONFIG = -3,
RADIO_ERR_INVALID_CHANNEL = -4,
RADIO_ERR_INVALID_ELEMENT = -5,
RADIO_ERR_UNSUPPORTED_OPERATION = -10,
RADIO_ERR_UNIMPLEMENTED = -19,
RADIO_ERR_OTHER = -9999,
} radio_error_t;
typedef enum {
RADIO_CHANNEL0 = 0x00,
} radio_chan_id;
typedef enum {
RADIO_FILTER_BASEBAND = 0x00,
} radio_filter_id;
typedef enum {
RADIO_SAMPLE_RATE_CLOCKGEN = 0x00,
} radio_sample_rate_id;
typedef enum {
RADIO_FREQUENCY_RF = 0x00,
RADIO_FREQUENCY_IF = 0x01,
RADIO_FREQUENCY_OF = 0x02,
} radio_frequency_id;
typedef enum {
RADIO_GAIN_RF_AMP = 0x00,
RADIO_GAIN_RX_LNA = 0x01,
RADIO_GAIN_RX_VGA = 0x02,
RADIO_GAIN_TX_VGA = 0x03,
} radio_gain_id;
typedef enum {
RADIO_ANTENNA_BIAS_TEE = 0x00,
} radio_antenna_id;
typedef enum {
RADIO_CLOCK_CLKIN = 0x00,
RADIO_CLOCK_CLKOUT = 0x01,
} radio_clock_id;
typedef struct {
uint32_t num;
uint32_t div;
double hz;
} radio_sample_rate_t;
typedef struct {
uint64_t hz;
} radio_filter_t;
typedef struct {
union {
bool enable;
uint8_t db;
};
} radio_gain_t;
typedef struct {
uint64_t hz; // desired frequency
uint64_t if_hz; // intermediate frequency
uint64_t lo_hz; // front-end local oscillator frequency
uint8_t path; // image rejection filter path
} radio_frequency_t;
typedef struct {
bool enable;
} radio_antenna_t;
typedef struct {
bool enable;
} radio_clock_t;
// legacy type, moved from hackrf_core
typedef enum {
HW_SYNC_MODE_OFF = 0,
HW_SYNC_MODE_ON = 1,
} hw_sync_mode_t;
// legacy type, moved from hackrf_core
typedef enum {
CLOCK_SOURCE_HACKRF = 0,
CLOCK_SOURCE_EXTERNAL = 1,
CLOCK_SOURCE_PORTAPACK = 2,
} clock_source_t;
// legacy type, moved from usb_api_transceiver
typedef enum {
TRANSCEIVER_MODE_OFF = 0,
TRANSCEIVER_MODE_RX = 1,
TRANSCEIVER_MODE_TX = 2,
TRANSCEIVER_MODE_SS = 3,
TRANSCEIVER_MODE_CPLD_UPDATE = 4,
TRANSCEIVER_MODE_RX_SWEEP = 5,
} transceiver_mode_t;
#define RADIO_CHANNEL_COUNT 1
#define RADIO_SAMPLE_RATE_COUNT 1
#define RADIO_FILTER_COUNT 1
#define RADIO_FREQUENCY_COUNT 4
#define RADIO_GAIN_COUNT 4
#define RADIO_ANTENNA_COUNT 1
#define RADIO_CLOCK_COUNT 2
#define RADIO_MODE_COUNT 6
typedef struct {
// sample rate elements
radio_sample_rate_t sample_rate[RADIO_SAMPLE_RATE_COUNT];
// filter elements
radio_filter_t filter[RADIO_FILTER_COUNT];
// gain elements
radio_gain_t gain[RADIO_GAIN_COUNT];
// frequency elements
radio_frequency_t frequency[RADIO_FREQUENCY_COUNT];
// antenna elements
radio_antenna_t antenna[RADIO_ANTENNA_COUNT];
// clock elements
radio_clock_t clock[RADIO_CLOCK_COUNT];
// trigger elements
hw_sync_mode_t trigger_mode;
// currently active transceiver mode
transceiver_mode_t mode;
} radio_config_t;
typedef struct radio_channel_t {
radio_chan_id id;
radio_config_t config;
radio_config_t mode[RADIO_MODE_COUNT];
clock_source_t clock_source;
} radio_channel_t;
typedef struct radio_t {
radio_channel_t channel[RADIO_CHANNEL_COUNT];
} radio_t;
/**
* API Notes
*
* - All radio_set_*() functions return a radio_error_t
* - radio_set_*() functions work as follows:
* - if the channel mode is TRANSCEIVER_MODE_OFF only the configuration will
* be updated, the hardware state will remain unaffected.
* - if the channel is something other than TRANSCEIVER_MODE_OFF both the
* configuration and hardware state will be updated.
* - this makes it possible to maintain multiple channel configurations and
* switch between them with a single call to radio_switch_mode()
*/
radio_error_t radio_set_sample_rate(
radio_t* radio,
radio_chan_id chan_id,
radio_sample_rate_id element,
radio_sample_rate_t sample_rate);
radio_sample_rate_t radio_get_sample_rate(
radio_t* radio,
radio_chan_id chan_id,
radio_sample_rate_id element);
radio_error_t radio_set_filter(
radio_t* radio,
radio_chan_id chan_id,
radio_filter_id element,
radio_filter_t filter);
radio_filter_t radio_get_filter(
radio_t* radio,
radio_chan_id chan_id,
radio_filter_id element);
radio_error_t radio_set_frequency(
radio_t* radio,
radio_chan_id chan_id,
radio_frequency_id element,
radio_frequency_t frequency);
radio_frequency_t radio_get_frequency(
radio_t* radio,
radio_chan_id chan_id,
radio_frequency_id element);
radio_error_t radio_set_gain(
radio_t* radio,
radio_chan_id chan_id,
radio_gain_id element,
radio_gain_t gain);
radio_gain_t radio_get_gain(radio_t* radio, radio_chan_id chan_id, radio_gain_id element);
radio_error_t radio_set_antenna(
radio_t* radio,
radio_chan_id chan_id,
radio_antenna_id element,
radio_antenna_t value);
radio_antenna_t radio_get_antenna(
radio_t* radio,
radio_chan_id chan_id,
radio_antenna_id element);
radio_error_t radio_set_clock(
radio_t* radio,
radio_chan_id chan_id,
radio_clock_id element,
radio_clock_t value);
radio_clock_t radio_get_clock(
radio_t* radio,
radio_chan_id chan_id,
radio_clock_id element);
radio_error_t radio_set_trigger_mode(
radio_t* radio,
radio_chan_id chan_id,
hw_sync_mode_t mode);
hw_sync_mode_t radio_get_trigger_mode(radio_t* radio, radio_chan_id chan_id);
transceiver_mode_t radio_get_mode(radio_t* radio, radio_chan_id chan_id);
rf_path_direction_t radio_get_direction(radio_t* radio, radio_chan_id chan_id);
clock_source_t radio_get_clock_source(radio_t* radio, radio_chan_id chan_id);
// apply the current channel configuration and switch to the given transceiver mode
radio_error_t radio_switch_mode(
radio_t* radio,
radio_chan_id chan_id,
transceiver_mode_t mode);
#endif /*__RF_CONFIG_H__*/

View File

@@ -31,12 +31,13 @@
#include "gpio_lpc.h"
#include "platform_detect.h"
#include "mixer.h"
#include "max2831.h"
#include "max283x.h"
#include "max5864.h"
#include "sgpio.h"
#include "user_config.h"
#if (defined JAWBREAKER || defined HACKRF_ONE || defined RAD1O)
#if (defined JAWBREAKER || defined HACKRF_ONE || defined RAD1O || defined PRALINE)
/*
* RF switches on Jawbreaker are controlled by General Purpose Outputs (GPO) on
* the RFFC5072.
@@ -48,6 +49,9 @@
*
* The rad1o also uses GPIO pins to control the different switches. The amplifiers
* are also connected to the LPC.
*
* On Praline, a subset of control signals is managed by GPIO pins on the LPC, while
* the remaining signals are generated through combinatorial logic in hardware.
*/
#define SWITCHCTRL_NO_TX_AMP_PWR (1 << 0) /* GPO1 turn off TX amp power */
#define SWITCHCTRL_AMP_BYPASS (1 << 1) /* GPO2 bypass amp section */
@@ -91,16 +95,13 @@
#define SWITCHCTRL_ANT_PWR (1 << 6) /* turn on antenna port power */
#ifdef HACKRF_ONE
/*
* Starting with HackRF One r9 this control signal has been moved to the
* microcontroller.
* In HackRF One r9 this control signal has been moved to the microcontroller.
*/
#ifdef HACKRF_ONE
static struct gpio_t gpio_h1r9_no_ant_pwr = GPIO(2, 4);
#endif
#ifdef HACKRF_ONE
static void switchctrl_set_hackrf_one(rf_path_t* const rf_path, uint8_t ctrl)
{
if (ctrl & SWITCHCTRL_TX) {
@@ -192,6 +193,47 @@ static void switchctrl_set_hackrf_one(rf_path_t* const rf_path, uint8_t ctrl)
}
#endif
#ifdef PRALINE
static void switchctrl_set_praline(rf_path_t* const rf_path, uint8_t ctrl)
{
if (ctrl & SWITCHCTRL_TX) {
gpio_set(rf_path->gpio_tx_en);
if (ctrl & SWITCHCTRL_NO_TX_AMP_PWR) {
ctrl |= SWITCHCTRL_AMP_BYPASS;
}
} else {
gpio_clear(rf_path->gpio_tx_en);
if (ctrl & SWITCHCTRL_NO_RX_AMP_PWR) {
ctrl |= SWITCHCTRL_AMP_BYPASS;
}
}
if (ctrl & SWITCHCTRL_MIX_BYPASS) {
gpio_set(rf_path->gpio_mix_en_n);
} else {
gpio_clear(rf_path->gpio_mix_en_n);
}
if (ctrl & SWITCHCTRL_HP) {
gpio_clear(rf_path->gpio_lpf_en);
} else {
gpio_set(rf_path->gpio_lpf_en);
}
if (ctrl & SWITCHCTRL_AMP_BYPASS) {
gpio_clear(rf_path->gpio_rf_amp_en);
} else {
gpio_set(rf_path->gpio_rf_amp_en);
}
if (ctrl & SWITCHCTRL_ANT_PWR) {
gpio_clear(rf_path->gpio_ant_bias_en_n);
} else {
gpio_set(rf_path->gpio_ant_bias_en_n);
}
}
#endif
#ifdef RAD1O
static void switchctrl_set_rad1o(rf_path_t* const rf_path, uint8_t ctrl)
{
@@ -264,6 +306,8 @@ static void switchctrl_set(rf_path_t* const rf_path, const uint8_t gpo)
mixer_set_gpo(&mixer, gpo);
#elif HACKRF_ONE
switchctrl_set_hackrf_one(rf_path, gpo);
#elif PRALINE
switchctrl_set_praline(rf_path, gpo);
#elif RAD1O
switchctrl_set_rad1o(rf_path, gpo);
#else
@@ -357,6 +401,36 @@ void rf_path_pin_setup(rf_path_t* const rf_path)
gpio_output(rf_path->gpio_low_high_filt_n);
gpio_output(rf_path->gpio_tx_amp);
gpio_output(rf_path->gpio_rx_lna);
#elif PRALINE
/* Configure RF switch control signals */
scu_pinmux(SCU_TX_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
board_rev_t rev = detected_revision();
if ((rev == BOARD_REV_PRALINE_R1_0) || (rev == BOARD_REV_GSG_PRALINE_R1_0)) {
scu_pinmux(SCU_MIX_EN_N_R1_0, SCU_GPIO_FAST | SCU_CONF_FUNCTION4);
} else {
scu_pinmux(SCU_MIX_EN_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
}
scu_pinmux(SCU_LPF_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
scu_pinmux(SCU_RF_AMP_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
/* Configure antenna port power control signal */
scu_pinmux(SCU_ANT_BIAS_EN_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
/* Configure RF power supply (VAA) switch */
scu_pinmux(SCU_NO_VAA_ENABLE, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
/*
* Safe (initial) switch settings turn off both amplifiers and antenna port
* power and enable both amp bypass and mixer bypass.
*/
switchctrl_set(rf_path, SWITCHCTRL_SAFE);
/* Configure RF switch control signals as outputs */
gpio_output(rf_path->gpio_ant_bias_en_n);
gpio_output(rf_path->gpio_tx_en);
gpio_output(rf_path->gpio_mix_en_n);
gpio_output(rf_path->gpio_lpf_en);
gpio_output(rf_path->gpio_rf_amp_en);
#else
(void) rf_path; /* silence unused param warning */
#endif
@@ -369,12 +443,17 @@ void rf_path_init(rf_path_t* const rf_path)
max5864_shutdown(&max5864);
ssp1_set_mode_max283x();
#ifdef PRALINE
max2831_setup(&max283x);
max2831_start(&max283x);
#else
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
max283x_setup(&max283x, MAX2839_VARIANT);
} else {
max283x_setup(&max283x, MAX2837_VARIANT);
}
max283x_start(&max283x);
#endif
// On HackRF One, the mixer is now set up earlier in boot.
#ifndef HACKRF_ONE
@@ -407,7 +486,11 @@ void rf_path_set_direction(rf_path_t* const rf_path, const rf_path_direction_t d
ssp1_set_mode_max5864();
max5864_tx(&max5864);
ssp1_set_mode_max283x();
#ifdef PRALINE
max2831_tx(&max283x);
#else
max283x_tx(&max283x);
#endif
sgpio_configure(&sgpio_config, SGPIO_DIRECTION_TX);
break;
@@ -426,10 +509,31 @@ void rf_path_set_direction(rf_path_t* const rf_path, const rf_path_direction_t d
ssp1_set_mode_max5864();
max5864_rx(&max5864);
ssp1_set_mode_max283x();
#ifdef PRALINE
max2831_rx(&max283x);
#else
max283x_rx(&max283x);
#endif
sgpio_configure(&sgpio_config, SGPIO_DIRECTION_RX);
break;
#ifdef PRALINE
case RF_PATH_DIRECTION_TX_CALIBRATION:
case RF_PATH_DIRECTION_RX_CALIBRATION:
rf_path->switchctrl &= ~SWITCHCTRL_TX;
mixer_disable(&mixer);
ssp1_set_mode_max5864();
max5864_xcvr(&max5864);
ssp1_set_mode_max283x();
if (direction == RF_PATH_DIRECTION_TX_CALIBRATION) {
max2831_tx_calibration(&max283x);
} else {
max2831_rx_calibration(&max283x);
}
sgpio_configure(&sgpio_config, SGPIO_DIRECTION_RX);
break;
#endif
case RF_PATH_DIRECTION_OFF:
default:
rf_path_set_lna(rf_path, 0);
@@ -439,7 +543,11 @@ void rf_path_set_direction(rf_path_t* const rf_path, const rf_path_direction_t d
ssp1_set_mode_max5864();
max5864_standby(&max5864);
ssp1_set_mode_max283x();
#ifdef PRALINE
max2831_set_mode(&max283x, MAX2831_MODE_STANDBY);
#else
max283x_set_mode(&max283x, MAX283x_MODE_STANDBY);
#endif
sgpio_configure(&sgpio_config, SGPIO_DIRECTION_RX);
break;
}

View File

@@ -32,6 +32,10 @@ typedef enum {
RF_PATH_DIRECTION_OFF,
RF_PATH_DIRECTION_RX,
RF_PATH_DIRECTION_TX,
#ifdef PRALINE
RF_PATH_DIRECTION_TX_CALIBRATION,
RF_PATH_DIRECTION_RX_CALIBRATION,
#endif
} rf_path_direction_t;
typedef enum {
@@ -70,6 +74,13 @@ typedef struct rf_path_t {
gpio_t gpio_tx_amp;
gpio_t gpio_rx_lna;
#endif
#ifdef PRALINE
gpio_t gpio_tx_en;
gpio_t gpio_mix_en_n;
gpio_t gpio_lpf_en;
gpio_t gpio_rf_amp_en;
gpio_t gpio_ant_bias_en_n;
#endif
} rf_path_t;
void rf_path_pin_setup(rf_path_t* const rf_path);

View File

@@ -35,7 +35,9 @@
#include <string.h>
#include "rffc5071.h"
#include "rffc5071_regs.def" // private register def macros
#include "selftest.h"
#include <libopencm3/lpc43xx/scu.h>
#include "hackrf_core.h"
/* Default register values. */
@@ -51,7 +53,7 @@ static const uint16_t rffc5071_regs_default[RFFC5071_NUM_REGS] = {
0xff00, /* 08 */
0x8220, /* 09 */
0x0202, /* 0A */
0x4800, /* 0B */
0x0400, /* 0B */
0x1a94, /* 0C */
0xd89d, /* 0D */
0x8900, /* 0E */
@@ -79,6 +81,11 @@ void rffc5071_init(rffc5071_driver_t* const drv)
memcpy(drv->regs, rffc5071_regs_default, sizeof(drv->regs));
drv->regs_dirty = 0x7fffffff;
selftest.mixer_id = rffc5071_reg_read(drv, RFFC5071_READBACK_REG);
if ((selftest.mixer_id >> 3) != 2031) {
selftest.report.pass = false;
}
/* Write default register values to chip. */
rffc5071_regs_commit(drv);
}
@@ -92,6 +99,12 @@ void rffc5071_setup(rffc5071_driver_t* const drv)
gpio_set(drv->gpio_reset);
gpio_output(drv->gpio_reset);
#ifdef PRALINE
/* Configure mixer PLL lock detect pin */
scu_pinmux(SCU_MIXER_LD, SCU_MIXER_LD_PINCFG);
gpio_input(drv->gpio_ld);
#endif
rffc5071_init(drv);
/* initial setup */
@@ -109,6 +122,9 @@ void rffc5071_setup(rffc5071_driver_t* const drv)
/* GPOs are active at all times */
set_RFFC5071_GATE(drv, 1);
/* Enable GPO Lock output signal */
set_RFFC5071_LOCK(drv, 1);
rffc5071_regs_commit(drv);
}
@@ -292,3 +308,113 @@ void rffc5071_set_gpo(rffc5071_driver_t* const drv, uint8_t gpo)
rffc5071_regs_commit(drv);
}
#ifdef PRALINE
bool rffc5071_poll_ld(rffc5071_driver_t* const drv, uint8_t* prelock_state)
{
// The RFFC5072 can be configured to output PLL lock status on
// GPO4. The lock detect signal is produced by a window detector
// on the VCO tuning voltage. It goes high to show PLL lock when
// the VCO tuning voltage is within the specified range, typically
// 0.30V to 1.25V.
//
// During the tuning process the lock signal will often go high,
// only to drop lock briefly before returning to the locked state.
//
// Therefore, to reliably detect lock it is necessary to also
// track the state of the FSM that controls the tuning process.
//
// Before re-tuning begins, and after final lock has been
// established, the FSM can be considered to be in STATE_LOCKED.
//
// The very first state change only occurs around 150us _after_ a
// new frequency has been set and registers updated:
//
// L 123456L (STATE)
// ----___------_--- (LD)
//
// This means we need to track the state(s) that occur before
// STATE_LOCKED to be able to reliably identify lock.
//
// At the time of writing 15 different states have been spotted in
// the wild.
//
// The first six states occur at some point during most tuning
// operations with the others occuring less frequently.
//
// Of the first six, two states have been identified as
// STATE_PRELOCKn which, once entered, indicate that no further
// changes will occur to the locked state.
enum state {
STATE_LOCKED = 0x17, // 0b10111
STATE_00010 = 0x02,
STATE_00100 = 0x04,
STATE_01011 = 0x0b,
STATE_PRELOCK1 = 0x10, // 0b10000
STATE_PRELOCK2 = 0x1e, // 0b11110
STATE_00000 = 0x00,
STATE_00001 = 0x01, // mixer bypassed
STATE_00011 = 0x03,
STATE_00101 = 0x05,
STATE_00110 = 0x06,
STATE_00111 = 0x07,
STATE_01010 = 0x0a,
STATE_10110 = 0x16,
STATE_11110 = 0x1e, // ?
STATE_11111 = 0x1f,
STATE_NONE = 0xff,
};
// Select which fields will be made available in the readback
// register - we only need to do this the first time.
if (*prelock_state == STATE_NONE) {
set_RFFC5071_READSEL(drv, 0b0011);
rffc5071_regs_commit(drv);
}
// read fsm state
uint16_t rb = rffc5071_reg_read(drv, RFFC5071_READBACK_REG);
uint8_t rsm_state = (rb >> 11) & 0b11111;
// get gpo4 lock detect signal
bool gpo4_ld = gpio_read(drv->gpio_ld);
// parse state
switch (rsm_state) {
case STATE_LOCKED: // 'normal operation'
if (gpo4_ld &&
((*prelock_state == STATE_PRELOCK1) ||
(*prelock_state == STATE_PRELOCK2))) {
return true;
}
break;
case STATE_00010:
case STATE_00100:
case STATE_01011:
break;
case STATE_PRELOCK1:
*prelock_state = rsm_state;
break;
case STATE_PRELOCK2:
*prelock_state = rsm_state;
break;
// other states
case STATE_00000:
case STATE_00001:
case STATE_00011:
case STATE_00101:
case STATE_00110:
case STATE_00111:
case STATE_01010:
case STATE_10110:
case STATE_11111:
case STATE_NONE:
break;
default:
// unknown state
break;
}
return false;
}
#endif

View File

@@ -34,6 +34,9 @@
typedef struct {
spi_bus_t* const bus;
gpio_t gpio_reset;
#ifdef PRALINE
gpio_t gpio_ld;
#endif
uint16_t regs[RFFC5071_NUM_REGS];
uint32_t regs_dirty;
} rffc5071_driver_t;
@@ -67,5 +70,8 @@ extern void rffc5071_enable(rffc5071_driver_t* const drv);
extern void rffc5071_disable(rffc5071_driver_t* const drv);
extern void rffc5071_set_gpo(rffc5071_driver_t* const drv, uint8_t);
#ifdef PRALINE
extern bool rffc5071_poll_ld(rffc5071_driver_t* const drv, uint8_t* prelock_state);
#endif
#endif // __RFFC5071_H

View File

@@ -27,6 +27,7 @@
#define RFFC5071_REG_SET_DIRTY(_d, _r) (_d->regs_dirty |= (1UL<<_r))
#define RFFC5071_READBACK_REG 31
#define RFFC5071_DEV_ID_MASK 0xFFFC
/* Generate static inline accessors that operate on the global
* regs. Done this way to (1) allow defs to be scraped out and used

View File

@@ -65,8 +65,8 @@ static void rffc5071_spi_bus_init(spi_bus_t* const bus)
{
const rffc5071_spi_config_t* const config = bus->config;
scu_pinmux(SCU_MIXER_SCLK, SCU_GPIO_FAST | SCU_CONF_FUNCTION4);
scu_pinmux(SCU_MIXER_SDATA, SCU_GPIO_FAST);
scu_pinmux(SCU_MIXER_SCLK, SCU_MIXER_SCLK_PINCFG);
scu_pinmux(SCU_MIXER_SDATA, SCU_MIXER_SDATA_PINCFG);
gpio_output(config->gpio_clock);
rffc5071_spi_direction_out(bus);

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "selftest.h"
selftest_t selftest;

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __SELFTEST_H
#define __SELFTEST_H
#include <stdbool.h>
#include <stdint.h>
typedef struct {
uint16_t mixer_id;
#ifdef PRALINE
bool max2831_ld_test_ok;
#else
uint16_t max283x_readback_bad_value;
uint16_t max283x_readback_expected_value;
uint8_t max283x_readback_register_count;
uint8_t max283x_readback_total_registers;
#endif
uint8_t si5351_rev_id;
bool si5351_readback_ok;
#ifndef RAD1O
bool rtc_osc_ok;
#endif
#ifdef PRALINE
bool sgpio_rx_ok;
bool xcvr_loopback_ok;
#endif
struct {
bool pass;
char msg[511];
} report;
} selftest_t;
extern selftest_t selftest;
#endif // __SELFTEST_H

View File

@@ -34,21 +34,21 @@ static void update_q_invert(sgpio_config_t* const config);
void sgpio_configure_pin_functions(sgpio_config_t* const config)
{
scu_pinmux(SCU_PINMUX_SGPIO0, SCU_GPIO_FAST | SCU_CONF_FUNCTION3);
scu_pinmux(SCU_PINMUX_SGPIO1, SCU_GPIO_FAST | SCU_CONF_FUNCTION3);
scu_pinmux(SCU_PINMUX_SGPIO2, SCU_GPIO_FAST | SCU_CONF_FUNCTION2);
scu_pinmux(SCU_PINMUX_SGPIO3, SCU_GPIO_FAST | SCU_CONF_FUNCTION2);
scu_pinmux(SCU_PINMUX_SGPIO4, SCU_GPIO_FAST | SCU_CONF_FUNCTION2);
scu_pinmux(SCU_PINMUX_SGPIO5, SCU_GPIO_FAST | SCU_CONF_FUNCTION2);
scu_pinmux(SCU_PINMUX_SGPIO6, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
scu_pinmux(SCU_PINMUX_SGPIO7, SCU_GPIO_FAST | SCU_CONF_FUNCTION6);
scu_pinmux(SCU_PINMUX_SGPIO8, SCU_GPIO_FAST | SCU_CONF_FUNCTION6);
scu_pinmux(SCU_PINMUX_SGPIO9, SCU_GPIO_FAST | SCU_CONF_FUNCTION7);
scu_pinmux(SCU_PINMUX_SGPIO10, SCU_GPIO_FAST | SCU_CONF_FUNCTION6);
scu_pinmux(SCU_PINMUX_SGPIO11, SCU_GPIO_FAST | SCU_CONF_FUNCTION6);
scu_pinmux(SCU_PINMUX_SGPIO12, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); /* GPIO0[13] */
scu_pinmux(SCU_PINMUX_SGPIO14, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); /* GPIO5[13] */
scu_pinmux(SCU_PINMUX_SGPIO15, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); /* GPIO5[14] */
scu_pinmux(SCU_PINMUX_SGPIO0, SCU_PINMUX_SGPIO0_PINCFG);
scu_pinmux(SCU_PINMUX_SGPIO1, SCU_PINMUX_SGPIO1_PINCFG);
scu_pinmux(SCU_PINMUX_SGPIO2, SCU_PINMUX_SGPIO2_PINCFG);
scu_pinmux(SCU_PINMUX_SGPIO3, SCU_PINMUX_SGPIO3_PINCFG);
scu_pinmux(SCU_PINMUX_SGPIO4, SCU_PINMUX_SGPIO4_PINCFG);
scu_pinmux(SCU_PINMUX_SGPIO5, SCU_PINMUX_SGPIO5_PINCFG);
scu_pinmux(SCU_PINMUX_SGPIO6, SCU_PINMUX_SGPIO6_PINCFG);
scu_pinmux(SCU_PINMUX_SGPIO7, SCU_PINMUX_SGPIO7_PINCFG);
scu_pinmux(SCU_PINMUX_SGPIO8, SCU_PINMUX_SGPIO8_PINCFG);
scu_pinmux(SCU_PINMUX_SGPIO9, SCU_PINMUX_SGPIO9_PINCFG);
scu_pinmux(SCU_PINMUX_SGPIO10, SCU_PINMUX_SGPIO10_PINCFG);
scu_pinmux(SCU_PINMUX_SGPIO11, SCU_PINMUX_SGPIO11_PINCFG);
scu_pinmux(SCU_PINMUX_SGPIO12, SCU_PINMUX_SGPIO12_PINCFG); /* GPIO0[13] */
scu_pinmux(SCU_PINMUX_SGPIO14, SCU_PINMUX_SGPIO14_PINCFG); /* GPIO5[13] */
scu_pinmux(SCU_PINMUX_SGPIO15, SCU_PINMUX_SGPIO15_PINCFG); /* GPIO5[14] */
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
scu_pinmux(
@@ -64,7 +64,9 @@ void sgpio_configure_pin_functions(sgpio_config_t* const config)
hw_sync_enable(0);
gpio_output(config->gpio_q_invert);
#ifndef PRALINE
gpio_output(config->gpio_hw_sync_enable);
#endif
}
void sgpio_set_slice_mode(sgpio_config_t* const config, const bool multi_slice)

View File

@@ -37,7 +37,9 @@ typedef enum {
typedef struct sgpio_config_t {
gpio_t gpio_q_invert;
#ifndef PRALINE
gpio_t gpio_hw_sync_enable;
#endif
bool slice_mode_multislice;
} sgpio_config_t;

View File

@@ -25,6 +25,7 @@
#include "platform_detect.h"
#include "gpio_lpc.h"
#include "hackrf_core.h"
#include "selftest.h"
#include <libopencm3/lpc43xx/scu.h>
/* HackRF One r9 clock control */
@@ -199,7 +200,7 @@ void si5351c_configure_clock_control(
pll = SI5351C_CLK_PLL_SRC_A;
#endif
#if (defined JAWBREAKER || defined HACKRF_ONE)
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
if (source == PLL_SOURCE_CLKIN) {
/* PLLB on CLKIN */
pll = SI5351C_CLK_PLL_SRC_B;
@@ -264,6 +265,15 @@ void si5351c_configure_clock_control(
data[5] = SI5351C_CLK_POWERDOWN;
data[6] = SI5351C_CLK_POWERDOWN;
}
#ifdef PRALINE
data[1] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(pll) |
SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) |
SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA);
data[3] = clkout_ctrl;
data[5] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(pll) |
SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) |
SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA) | SI5351C_CLK_INV;
#endif
si5351c_write(drv, data, sizeof(data));
}
@@ -274,11 +284,19 @@ void si5351c_configure_clock_control(
void si5351c_enable_clock_outputs(si5351c_driver_t* const drv)
{
/* Enable CLK outputs 0, 1, 2, 4, 5 only. */
/* Praline: enable 0, 4, 5 only. */
/* 7: Clock to CPU is deactivated as it is not used and creates noise */
/* 3: External clock output is deactivated by default */
#ifndef PRALINE
uint8_t value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) |
SI5351C_CLK_ENABLE(2) | SI5351C_CLK_ENABLE(4) | SI5351C_CLK_ENABLE(5) |
SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7);
#else
uint8_t value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) |
SI5351C_CLK_DISABLE(2) | SI5351C_CLK_ENABLE(4) | SI5351C_CLK_ENABLE(5) |
SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7);
#endif
uint8_t clkout = 3;
/* HackRF One r9 has only three clock generator outputs. */
@@ -356,8 +374,8 @@ void si5351c_clkout_enable(si5351c_driver_t* const drv, uint8_t enable)
{
clkout_enabled = (enable > 0);
//FIXME this should be somewhere else
uint8_t clkout = 3;
/* HackRF One r9 has only three clock generator outputs. */
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
clkout = 2;
@@ -371,6 +389,18 @@ void si5351c_clkout_enable(si5351c_driver_t* const drv, uint8_t enable)
void si5351c_init(si5351c_driver_t* const drv)
{
/* Read revision ID */
selftest.si5351_rev_id = si5351c_read_single(drv, 0) & SI5351C_REVID;
/* Read back interrupt status mask register, flip the mask bits and verify. */
uint8_t int_mask = si5351c_read_single(drv, 2);
int_mask ^= 0xF8;
si5351c_write_single(drv, 2, int_mask);
selftest.si5351_readback_ok = (si5351c_read_single(drv, 2) == int_mask);
if (!selftest.si5351_readback_ok) {
selftest.report.pass = false;
}
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
/* CLKIN_EN */
scu_pinmux(SCU_H1R9_CLKIN_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION4);

View File

@@ -56,7 +56,8 @@ extern "C" {
#define SI5351C_CLK_IDRV_6MA 2
#define SI5351C_CLK_IDRV_8MA 3
#define SI5351C_LOS (1 << 4)
#define SI5351C_LOS (1 << 4)
#define SI5351C_REVID 0x03
enum pll_sources {
PLL_SOURCE_UNINITIALIZED = -1,

View File

@@ -39,7 +39,7 @@ void spi_ssp_start(spi_bus_t* const bus, const void* const _config)
SSP_CR1(bus->obj) = 0;
SSP_CPSR(bus->obj) = config->clock_prescale_rate;
SSP_CR0(bus->obj) = (config->serial_clock_rate << 8) | SSP_CPOL_0_CPHA_0 |
SSP_CR0(bus->obj) = (config->serial_clock_rate << 8) | config->spi_mode |
SSP_FRAME_SPI | config->data_bits;
SSP_CR1(bus->obj) =
SSP_SLAVE_OUT_ENABLE | SSP_MASTER | SSP_ENABLE | SSP_MODE_NORMAL;

View File

@@ -34,6 +34,7 @@
typedef struct ssp_config_t {
ssp_datasize_t data_bits;
ssp_cpol_cpha_t spi_mode;
uint8_t serial_clock_rate;
uint8_t clock_prescale_rate;
gpio_t gpio_select;

View File

@@ -0,0 +1,220 @@
/*
* Copyright 2024-2025 Great Scott Gadgets <info@greatscottgadgets.com>
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __TUNE_CONFIG_H__
#define __TUNE_CONFIG_H__
typedef struct {
uint16_t rf_range_end_mhz;
uint16_t if_mhz;
bool high_lo;
} tune_config_t;
#ifndef PRALINE
// TODO maybe one day?
static const tune_config_t max283x_tune_config[] = {};
#else
// clang-format off
/* tuning table optimized for TX */
static const tune_config_t max2831_tune_config_tx[] = {
{ 2100, 2375, true },
{ 2175, 2325, false },
{ 2320, 2525, false },
{ 2580, 0, false },
{ 3000, 2325, false },
{ 3100, 2375, false },
{ 3200, 2425, false },
{ 3350, 2375, false },
{ 3500, 2475, false },
{ 3550, 2425, false },
{ 3650, 2325, false },
{ 3700, 2375, false },
{ 3850, 2425, false },
{ 3925, 2375, false },
{ 4600, 2325, false },
{ 4700, 2375, false },
{ 4800, 2425, false },
{ 5100, 2375, false },
{ 5850, 2525, false },
{ 6500, 2325, false },
{ 6750, 2375, false },
{ 6850, 2425, false },
{ 6950, 2475, false },
{ 7000, 2525, false },
{ 7251, 2575, false },
{ 0, 0, false },
};
/* tuning table optimized for 20 Msps interleaved RX sweep mode */
static const tune_config_t max2831_tune_config_rx_sweep[] = {
{ 140, 2330, false },
{ 424, 2570, true },
{ 557, 2520, true },
{ 593, 2380, true },
{ 776, 2360, true },
{ 846, 2570, true },
{ 926, 2500, true },
{ 1055, 2380, true },
{ 1175, 2360, true },
{ 1391, 2340, true },
{ 1529, 2570, true },
{ 1671, 2520, true },
{ 1979, 2380, true },
{ 2150, 2330, true },
{ 2160, 2550, false },
{ 2170, 2560, false },
{ 2179, 2570, false },
{ 2184, 2520, false },
{ 2187, 2560, false },
{ 2194, 2530, false },
{ 2203, 2540, false },
{ 2212, 2550, false },
{ 2222, 2560, false },
{ 2231, 2570, false },
{ 2233, 2530, false },
{ 2237, 2520, false },
{ 2241, 2550, false },
{ 2245, 2570, false },
{ 2250, 2560, false },
{ 2252, 2550, false },
{ 2258, 2570, false },
{ 2261, 2560, false },
{ 2266, 2540, false },
{ 2271, 2570, false },
{ 2275, 2550, false },
{ 2280, 2500, false },
{ 2284, 2560, false },
{ 2285, 2530, false },
{ 2289, 2510, false },
{ 2293, 2570, false },
{ 2294, 2540, false },
{ 2298, 2520, false },
{ 2301, 2570, false },
{ 2302, 2550, false },
{ 2307, 2530, false },
{ 2308, 2560, false },
{ 2312, 2560, false },
{ 2316, 2540, false },
{ 2317, 2570, false },
{ 2320, 2570, false },
{ 2580, 0, false },
{ 2585, 2360, false },
{ 2588, 2340, false },
{ 2594, 2350, false },
{ 2606, 2330, false },
{ 2617, 2340, false },
{ 2627, 2350, false },
{ 2638, 2360, false },
{ 2649, 2370, false },
{ 2659, 2380, false },
{ 2664, 2350, false },
{ 2675, 2360, false },
{ 2686, 2370, false },
{ 2697, 2380, false },
{ 2705, 2350, false },
{ 2716, 2360, false },
{ 2728, 2370, false },
{ 2739, 2380, false },
{ 2757, 2330, false },
{ 2779, 2350, false },
{ 2790, 2360, false },
{ 2801, 2370, false },
{ 2812, 2380, false },
{ 2821, 2570, false },
{ 2831, 2520, false },
{ 2852, 2330, false },
{ 2874, 2350, false },
{ 2897, 2370, false },
{ 2913, 2510, false },
{ 2925, 2570, false },
{ 2937, 2530, false },
{ 2948, 2540, false },
{ 2960, 2550, false },
{ 2975, 2330, false },
{ 2988, 2340, false },
{ 3002, 2330, false },
{ 3014, 2360, false },
{ 3027, 2370, false },
{ 3041, 2500, false },
{ 3052, 2510, false },
{ 3064, 2520, false },
{ 3082, 2500, false },
{ 3107, 2520, false },
{ 3132, 2540, false },
{ 3157, 2560, false },
{ 3170, 2570, false },
{ 3192, 2500, false },
{ 3216, 2340, false },
{ 3270, 2330, false },
{ 3319, 2370, false },
{ 3341, 2340, false },
{ 3370, 2330, false },
{ 3400, 2350, false },
{ 3430, 2370, false },
{ 3464, 2520, false },
{ 3491, 2540, false },
{ 3519, 2560, false },
{ 3553, 2510, false },
{ 3595, 2540, false },
{ 3638, 2570, false },
{ 3665, 2540, false },
{ 3685, 2560, false },
{ 3726, 2330, false },
{ 3790, 2370, false },
{ 3910, 2350, false },
{ 4014, 2510, false },
{ 4123, 2380, false },
{ 4191, 2550, false },
{ 4349, 2510, false },
{ 4452, 2570, false },
{ 4579, 2500, false },
{ 4707, 2570, false },
{ 4831, 2560, false },
{ 4851, 2570, false },
{ 4871, 2560, false },
{ 4891, 2570, false },
{ 4911, 2540, false },
{ 4931, 2550, false },
{ 4951, 2560, false },
{ 5044, 2330, false },
{ 5065, 2340, false },
{ 5174, 2330, false },
{ 5285, 2380, false },
{ 5449, 2340, false },
{ 5574, 2510, false },
{ 5717, 2340, false },
{ 5892, 2530, false },
{ 6096, 2350, false },
{ 6254, 2560, false },
{ 6625, 2340, false },
{ 6764, 2540, false },
{ 6930, 2530, false },
{ 7251, 2570, false },
{ 0, 0, false },
};
// TODO these are just copies of max2831_tune_config_rx_sweep for now
#define max2831_tune_config_rx max2831_tune_config_rx_sweep
// clang-format on
#endif
#endif /*__TUNE_CONFIG_H__*/

View File

@@ -25,6 +25,7 @@
#include "hackrf_ui.h"
#include "hackrf_core.h"
#include "mixer.h"
#include "max2831.h"
#include "max2837.h"
#include "max2839.h"
#include "sgpio.h"
@@ -33,21 +34,41 @@
#define FREQ_ONE_MHZ (1000ULL * 1000)
#define MIN_LP_FREQ_MHZ (0)
#define MAX_LP_FREQ_MHZ (2170ULL)
#ifndef PRALINE
#define ABS_MIN_BYPASS_FREQ_MHZ (2000ULL)
#define MIN_BYPASS_FREQ_MHZ (MAX_LP_FREQ_MHZ)
#define MAX_BYPASS_FREQ_MHZ (2740ULL)
#define ABS_MAX_BYPASS_FREQ_MHZ (3000ULL)
#define MIN_LP_FREQ_MHZ (0)
#define MAX_LP_FREQ_MHZ (2170ULL)
#define MIN_HP_FREQ_MHZ (MAX_BYPASS_FREQ_MHZ)
#define MID1_HP_FREQ_MHZ (3600ULL)
#define MID2_HP_FREQ_MHZ (5100ULL)
#define MAX_HP_FREQ_MHZ (7250ULL)
#define ABS_MIN_BYPASS_FREQ_MHZ (2000ULL)
#define MIN_BYPASS_FREQ_MHZ (MAX_LP_FREQ_MHZ)
#define MAX_BYPASS_FREQ_MHZ (2740ULL)
#define ABS_MAX_BYPASS_FREQ_MHZ (3000ULL)
#define MIN_LO_FREQ_HZ (84375000ULL)
#define MAX_LO_FREQ_HZ (5400000000ULL)
#define MIN_HP_FREQ_MHZ (MAX_BYPASS_FREQ_MHZ)
#define MID1_HP_FREQ_MHZ (3600ULL)
#define MID2_HP_FREQ_MHZ (5100ULL)
#define MAX_HP_FREQ_MHZ (7250ULL)
#define MIN_LO_FREQ_HZ (84375000ULL)
#define MAX_LO_FREQ_HZ (5400000000ULL)
#else
#define MIN_LP_FREQ_MHZ (0)
#define MAX_LP_FREQ_MHZ (2320ULL)
#define ABS_MIN_BYPASS_FREQ_MHZ (2000ULL)
#define MIN_BYPASS_FREQ_MHZ (MAX_LP_FREQ_MHZ)
#define MAX_BYPASS_FREQ_MHZ (2580ULL)
#define ABS_MAX_BYPASS_FREQ_MHZ (3000ULL)
#define MIN_HP_FREQ_MHZ (MAX_BYPASS_FREQ_MHZ)
#define MAX_HP_FREQ_MHZ (7250ULL)
#define MIN_LO_FREQ_HZ (84375000ULL)
#define MAX_LO_FREQ_HZ (5400000000ULL)
#endif
static uint32_t max2837_freq_nominal_hz = 2560000000;
@@ -58,6 +79,7 @@ uint64_t freq_cache = 100000000;
* hz between 0 to 999999 Hz (not checked)
* return false on error or true if success.
*/
#ifndef PRALINE
bool set_freq(const uint64_t freq)
{
bool success;
@@ -72,12 +94,12 @@ bool set_freq(const uint64_t freq)
max283x_set_mode(&max283x, MAX283x_MODE_STANDBY);
if (freq_mhz < MAX_LP_FREQ_MHZ) {
rf_path_set_filter(&rf_path, RF_PATH_FILTER_LOW_PASS);
#ifdef RAD1O
#ifdef RAD1O
max2837_freq_nominal_hz = 2300 * FREQ_ONE_MHZ;
#else
#else
/* IF is graduated from 2650 MHz to 2340 MHz */
max2837_freq_nominal_hz = (2650 * FREQ_ONE_MHZ) - (freq / 7);
#endif
#endif
mixer_freq_hz = max2837_freq_nominal_hz + freq;
/* Set Freq and read real freq */
real_mixer_freq_hz = mixer_set_frequency(&mixer, mixer_freq_hz);
@@ -117,13 +139,90 @@ bool set_freq(const uint64_t freq)
if (success) {
freq_cache = freq;
hackrf_ui()->set_frequency(freq);
#ifdef HACKRF_ONE
#ifdef HACKRF_ONE
operacake_set_range(freq_mhz);
#endif
#endif
}
return success;
}
uint64_t tuning_set_frequency(const tune_config_t* config, const uint64_t frequency_hz)
{
(void) config;
bool result = set_freq(frequency_hz);
if (!result) {
return 0;
}
return frequency_hz;
}
#else
bool set_freq(const uint64_t freq)
{
return tuning_set_frequency(max2831_tune_config_rx_sweep, freq) != 0;
}
uint64_t tuning_set_frequency(const tune_config_t* cfg, const uint64_t freq)
{
bool found;
uint64_t mixer_freq_hz;
uint64_t real_mixer_freq_hz;
if (freq > (MAX_HP_FREQ_MHZ * FREQ_ONE_MHZ)) {
return false;
}
const uint16_t freq_mhz = freq / FREQ_ONE_MHZ;
found = false;
for (; cfg->rf_range_end_mhz != 0; cfg++) {
if (cfg->rf_range_end_mhz > freq_mhz) {
found = true;
break;
}
}
if (!found) {
return false;
}
max2831_mode_t prior_max2831_mode = max2831_mode(&max283x);
max2831_set_mode(&max283x, MAX2831_MODE_STANDBY);
if (cfg->if_mhz == 0) {
rf_path_set_filter(&rf_path, RF_PATH_FILTER_BYPASS);
max2831_set_frequency(&max283x, freq);
sgpio_cpld_set_mixer_invert(&sgpio_config, 0);
} else if (cfg->if_mhz > freq_mhz) {
rf_path_set_filter(&rf_path, RF_PATH_FILTER_LOW_PASS);
if (cfg->high_lo) {
mixer_freq_hz = FREQ_ONE_MHZ * cfg->if_mhz + freq;
real_mixer_freq_hz = mixer_set_frequency(&mixer, mixer_freq_hz);
max2831_set_frequency(&max283x, real_mixer_freq_hz - freq);
sgpio_cpld_set_mixer_invert(&sgpio_config, 1);
} else {
mixer_freq_hz = FREQ_ONE_MHZ * cfg->if_mhz - freq;
real_mixer_freq_hz = mixer_set_frequency(&mixer, mixer_freq_hz);
max2831_set_frequency(&max283x, real_mixer_freq_hz + freq);
sgpio_cpld_set_mixer_invert(&sgpio_config, 0);
}
} else {
rf_path_set_filter(&rf_path, RF_PATH_FILTER_HIGH_PASS);
mixer_freq_hz = freq - FREQ_ONE_MHZ * cfg->if_mhz;
real_mixer_freq_hz = mixer_set_frequency(&mixer, mixer_freq_hz);
max2831_set_frequency(&max283x, freq - real_mixer_freq_hz);
sgpio_cpld_set_mixer_invert(&sgpio_config, 0);
}
max2831_set_mode(&max283x, prior_max2831_mode);
freq_cache = freq;
hackrf_ui()->set_frequency(freq);
operacake_set_range(freq_mhz);
return true;
}
#endif
bool set_freq_explicit(
const uint64_t if_freq_hz,
const uint64_t lo_freq_hz,
@@ -144,7 +243,11 @@ bool set_freq_explicit(
}
rf_path_set_filter(&rf_path, path);
#ifdef PRALINE
max2831_set_frequency(&max283x, if_freq_hz);
#else
max283x_set_frequency(&max283x, if_freq_hz);
#endif
if (lo_freq_hz > if_freq_hz) {
sgpio_cpld_set_mixer_invert(&sgpio_config, 1);
} else {

View File

@@ -25,14 +25,17 @@
#define __TUNING_H__
#include "rf_path.h"
#include "tune_config.h"
#include <stdint.h>
#include <stdbool.h>
bool set_freq(const uint64_t freq);
bool set_freq(const uint64_t freq); // TODO deprecate
bool set_freq_explicit(
const uint64_t if_freq_hz,
const uint64_t lo_freq_hz,
const rf_path_filter_t path);
uint64_t tuning_set_frequency(const tune_config_t* config, const uint64_t frequency_hz);
#endif /*__TUNING_H__*/

View File

@@ -67,7 +67,8 @@ void w25q80bv_setup(w25q80bv_driver_t* const drv)
do {
device_id = w25q80bv_get_device_id(drv);
} while (device_id != W25Q80BV_DEVICE_ID_RES &&
device_id != W25Q16DV_DEVICE_ID_RES);
device_id != W25Q16DV_DEVICE_ID_RES &&
device_id != W25Q32JV_DEVICE_ID_RES);
}
uint8_t w25q80bv_get_status(w25q80bv_driver_t* const drv)
@@ -129,7 +130,8 @@ void w25q80bv_chip_erase(w25q80bv_driver_t* const drv)
do {
device_id = w25q80bv_get_device_id(drv);
} while (device_id != W25Q80BV_DEVICE_ID_RES &&
device_id != W25Q16DV_DEVICE_ID_RES);
device_id != W25Q16DV_DEVICE_ID_RES &&
device_id != W25Q32JV_DEVICE_ID_RES);
w25q80bv_wait_while_busy(drv);
w25q80bv_write_enable(drv);
@@ -182,7 +184,8 @@ void w25q80bv_program(
do {
device_id = w25q80bv_get_device_id(drv);
} while (device_id != W25Q80BV_DEVICE_ID_RES &&
device_id != W25Q16DV_DEVICE_ID_RES);
device_id != W25Q16DV_DEVICE_ID_RES &&
device_id != W25Q32JV_DEVICE_ID_RES);
/* do nothing if we would overflow the flash */
if ((len > drv->num_bytes) || (addr > drv->num_bytes) ||

View File

@@ -29,6 +29,7 @@
#define W25Q80BV_DEVICE_ID_RES 0x13 /* Expected device_id for W25Q80BV */
#define W25Q16DV_DEVICE_ID_RES 0x14 /* Expected device_id for W25Q16DV */
#define W25Q32JV_DEVICE_ID_RES 0x15 /* Expected device_id for W25Q32JV */
#include "spi_bus.h"
#include "gpio.h"

View File

@@ -0,0 +1,357 @@
#
# This file is part of HackRF.
#
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
# Copyright (c) 2024 S. Holzapfel <me@sebholzapfel.com>
# SPDX-License-Identifier: BSD-3-Clause
#
# from https://github.com/apfaudio/tiliqua/blob/main/gateware/src/amaranth_future/fixed.py
# which is also derived from https://github.com/amaranth-lang/amaranth/pull/1005
# slightly modified
from amaranth import hdl
from amaranth.utils import bits_for
from dsp.round import convergent_round
__all__ = ["Shape", "SQ", "UQ", "Value", "Const"]
class Shape(hdl.ShapeCastable):
def __init__(self, i_or_f_width, f_width = None, /, *, signed):
if f_width is None:
self.i_width, self.f_width = 0, i_or_f_width
else:
self.i_width, self.f_width = i_or_f_width, f_width
self.signed = bool(signed)
@staticmethod
def cast(shape, f_width=0):
if not isinstance(shape, hdl.Shape):
raise TypeError(f"Object {shape!r} cannot be converted to a fixed.Shape")
# i_width is what's left after subtracting f_width and sign bit, but can't be negative.
i_width = max(0, shape.width - shape.signed - f_width)
return Shape(i_width, f_width, signed = shape.signed)
def as_shape(self):
return hdl.Shape(self.signed + self.i_width + self.f_width, self.signed)
def __call__(self, target):
return Value(self, target)
def const(self, value):
if value is None:
value = 0
return Const(value, self)._target
def from_bits(self, raw):
c = Const(0, self)
c._value = raw
return c
def max(self):
c = Const(0, self)
c._value = c._max_value()
return c
def min(self):
c = Const(0, self)
c._value = c._min_value()
return c
def __repr__(self):
return f"fixed.Shape({self.i_width}, {self.f_width}, signed={self.signed})"
class SQ(Shape):
def __init__(self, *args):
super().__init__(*args, signed = True)
class UQ(Shape):
def __init__(self, *args):
super().__init__(*args, signed = False)
class Value(hdl.ValueCastable):
def __init__(self, shape, target):
self._shape = shape
self._target = target
@staticmethod
def cast(value, f_width=0):
return Shape.cast(value.shape(), f_width)(value)
def round(self, f_width=0):
# If we're increasing precision, extend with more fractional bits.
if f_width > self.f_width:
return Shape(self.i_width, f_width, signed = self.signed)(hdl.Cat(hdl.Const(0, f_width - self.f_width), self.raw()))
# If we're reducing precision, perform convergent rounding (round to even).
elif f_width < self.f_width:
return Shape(self.i_width, f_width, signed = self.signed)( convergent_round(self.raw(), self.f_width - f_width) )
return self
def truncate(self, f_width=0):
return Shape(self.i_width, f_width, signed = self.signed)(self.raw()[self.f_width - f_width:])
@property
def i_width(self):
return self._shape.i_width
@property
def f_width(self):
return self._shape.f_width
@property
def signed(self):
return self._shape.signed
def as_value(self):
return self._target
def raw(self):
"""
Adding an `s ( )` signedness wrapper in `as_value` when needed
breaks lib.wiring for some reason. In the future, raw() and
`as_value()` should be combined.
"""
if self.signed:
return self._target.as_signed()
return self._target
def shape(self):
return self._shape
def eq(self, other):
# Regular values are assigned directly to the underlying value.
if isinstance(other, hdl.Value):
return self.raw().eq(other)
# int and float are cast to fixed.Const.
elif isinstance(other, int) or isinstance(other, float):
other = Const(other, self.shape())
# Other value types are unsupported.
elif not isinstance(other, Value):
raise TypeError(f"Object {other!r} cannot be converted to a fixed.Value")
# Match precision.
other = other.round(self.f_width)
return self.raw().eq(other.raw())
def __mul__(self, other):
# Regular values are cast to fixed.Value
if isinstance(other, hdl.Value):
other = Value.cast(other)
# int are cast to fixed.Const
elif isinstance(other, int):
other = Const(other)
# Other value types are unsupported.
elif not isinstance(other, Value):
raise TypeError(f"Object {other!r} cannot be converted to a fixed.Value")
return Value.cast(self.raw() * other.raw(), self.f_width + other.f_width)
def __rmul__(self, other):
return self.__mul__(other)
def __add__(self, other):
# Regular values are cast to fixed.Value
if isinstance(other, hdl.Value):
other = Value.cast(other)
# int are cast to fixed.Const
elif isinstance(other, int):
other = Const(other)
# Other value types are unsupported.
elif not isinstance(other, Value):
raise TypeError(f"Object {other!r} cannot be converted to a fixed.Value")
f_width = max(self.f_width, other.f_width)
return Value.cast(self.round(f_width).raw() + other.round(f_width).raw(), f_width)
def __radd__(self, other):
return self.__add__(other)
def __sub__(self, other):
# Regular values are cast to fixed.Value
if isinstance(other, hdl.Value):
other = Value.cast(other)
# int are cast to fixed.Const
elif isinstance(other, int):
other = Const(other)
# Other value types are unsupported.
elif not isinstance(other, Value):
raise TypeError(f"Object {other!r} cannot be converted to a fixed.Value")
f_width = max(self.f_width, other.f_width)
return Value.cast(self.round(f_width).raw() - other.round(f_width).raw(), f_width)
def __rsub__(self, other):
return -self.__sub__(other)
def __pos__(self):
return self
def __neg__(self):
return Value.cast(-self.raw(), self.f_width)
def __abs__(self):
return Value.cast(abs(self.raw()), self.f_width)
def __lshift__(self, other):
if isinstance(other, int):
if other < 0:
raise ValueError("Shift amount cannot be negative")
if other > self.f_width:
return Value.cast(hdl.Cat(hdl.Const(0, other - self.f_width), self.raw()))
else:
return Value.cast(self.raw(), self.f_width - other)
elif not isinstance(other, hdl.Value):
raise TypeError("Shift amount must be an integer value")
if other.signed:
raise TypeError("Shift amount must be unsigned")
return Value.cast(self.raw() << other, self.f_width)
def __rshift__(self, other):
if isinstance(other, int):
if other < 0:
raise ValueError("Shift amount cannot be negative")
return Value.cast(self.raw(), self.f_width + other)
elif not isinstance(other, hdl.Value):
raise TypeError("Shift amount must be an integer value")
if other.signed:
raise TypeError("Shift amount must be unsigned")
# Extend f_width by maximal shift amount.
f_width = self.f_width + 2**other.width - 1
return Value.cast(self.round(f_width).raw() >> other, f_width)
def __lt__(self, other):
if isinstance(other, hdl.Value):
other = Value.cast(other)
elif isinstance(other, int):
other = Const(other)
elif not isinstance(other, Value):
raise TypeError(f"Object {other!r} cannot be converted to a fixed.Value")
f_width = max(self.f_width, other.f_width)
return self.round(f_width).raw() < other.round(f_width).raw()
def __ge__(self, other):
return ~self.__lt__(other)
def __eq__(self, other):
if isinstance(other, hdl.Value):
other = Value.cast(other)
elif isinstance(other, int):
other = Const(other)
elif not isinstance(other, Value):
raise TypeError(f"Object {other!r} cannot be converted to a fixed.Value")
f_width = max(self.f_width, other.f_width)
return self.round(f_width).raw() == other.round(f_width).raw()
def __repr__(self):
return f"(fixedpoint {'SQ' if self.signed else 'UQ'}{self.i_width}.{self.f_width} {self._target!r})"
class Const(Value):
def __init__(self, value, shape=None):
if isinstance(value, float) or isinstance(value, int):
num, den = value.as_integer_ratio()
elif isinstance(value, Const):
# FIXME: Memory inits seem to construct a fixed.Const with fixed.Const
self._shape = value._shape
self._value = value._value
return
else:
raise TypeError(f"Object {value!r} cannot be converted to a fixed.Const")
# Determine smallest possible shape if not already selected.
if shape is None:
f_width = bits_for(den) - 1
i_width = max(0, bits_for(abs(num)) - f_width)
shape = Shape(i_width, f_width, signed = num < 0)
# Scale value to given precision.
if 2**shape.f_width > den:
num *= 2**shape.f_width // den
elif 2**shape.f_width < den:
num = round(num / (den // 2**shape.f_width))
value = num
self._shape = shape
if value > self._max_value():
print("WARN fixed.Const: clamp", value, "to", self._max_value())
value = self._max_value()
if value < self._min_value():
print("WARN fixed.Const: clamp", value, "to", self._min_value())
value = self._min_value()
self._value = value
def _max_value(self):
return 2**(self._shape.i_width +
self._shape.f_width) - 1
def _min_value(self):
if self._shape.signed:
return -1 * 2**(self._shape.i_width +
self._shape.f_width)
else:
return 0
@property
def _target(self):
return hdl.Const(self._value, self._shape.as_shape())
def as_integer_ratio(self):
return self._value, 2**self.f_width
def as_float(self):
if self._value > self._max_value():
v = self._min_value() + self._value - self._max_value() - 1
else:
v = self._value
return v / 2**self.f_width
# TODO: Operators
def __mul__(self, other):
# Regular values are cast to fixed.Value
if isinstance(other, hdl.Value):
other = Value.cast(other)
# int are cast to fixed.Const
elif isinstance(other, int):
other = Const(other)
# Other value types are unsupported.
elif not isinstance(other, Value):
raise TypeError(f"Object {other!r} cannot be converted to a fixed.Value")
return Value.cast(self.raw() * other.raw(), self.f_width + other.f_width)
def __rmul__(self, other):
return self.__mul__(other)

90
firmware/fpga/board.py Normal file
View File

@@ -0,0 +1,90 @@
#!/usr/bin/env python3
#
# This file is part of HackRF.
#
# Copyright (c) 2024 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
from amaranth import Elaboratable, Signal, Instance, Module, ClockDomain
from amaranth.build import Resource, Pins, Clock, Attrs
from amaranth.vendor import LatticeICE40Platform
from amaranth_boards.resources import SPIResource
__all__ = ["PralinePlatform", "ClockDomainGenerator"]
class PralinePlatform(LatticeICE40Platform):
device = "iCE40UP5K"
package = "SG48"
default_clk = "SB_HFOSC" # 48 MHz internal oscillator
hfosc_div = 0 # Do not divide
resources = [
Resource("fpga_clk", 0, Pins("39", dir="i"),
Attrs(GLOBAL=True, IO_STANDARD="SB_LVCMOS")),
# ADC/DAC interfaces.
Resource("afe_clk", 0, Pins("35", dir="i"),
Clock(40e6), Attrs(GLOBAL=True, IO_STANDARD="SB_LVCMOS")),
Resource("dd", 0, Pins("38 37 36 34 32 31 28 27 26 25", dir="o"),
Attrs(IO_STANDARD="SB_LVCMOS")),
Resource("da", 0, Pins("46 45 44 43 42 41 40 39", dir="i"),
Attrs(IO_STANDARD="SB_LVCMOS")),
# SGPIO interface.
Resource("host_clk", 0, Pins("20", dir="o"),
Attrs(IO_STANDARD="SB_LVCMOS")),
Resource("host_data", 0, Pins("21 19 6 13 10 3 4 18", dir="io"),
Attrs(IO_STANDARD="SB_LVCMOS")),
Resource("q_invert", 0, Pins("9", dir="i"),
Attrs(IO_STANDARD="SB_LVCMOS")),
Resource("direction", 0, Pins("12", dir="i"),
Attrs(IO_STANDARD="SB_LVCMOS")),
Resource("disable", 0, Pins("23", dir="i"),
Attrs(IO_STANDARD="SB_LVCMOS")),
Resource("capture_en", 0, Pins("11", dir="o"),
Attrs(IO_STANDARD="SB_LVCMOS")),
Resource("trigger_in", 0, Pins("48", dir="i"),
Attrs(IO_STANDARD="SB_LVCMOS")),
Resource("trigger_out", 0, Pins("2", dir="o"),
Attrs(IO_STANDARD="SB_LVCMOS")),
# SPI can be used after configuration.
SPIResource("spi", 0, role="peripheral",
cs_n="16", clk="15", copi="17", cipo="14",
attrs=Attrs(IO_STANDARD="SB_LVCMOS"),
),
]
connectors = []
class ClockDomainGenerator(Elaboratable):
@staticmethod
def lut_delay(m, signal, *, depth):
signal_out = signal
for i in range(depth):
signal_in = signal_out
signal_out = Signal()
m.submodules += Instance("SB_LUT4",
p_LUT_INIT=0xAAAA, # Buffer configuration
i_I0=signal_in,
o_O=signal_out,
)
return signal_out
def elaborate(self, platform):
m = Module()
# Define clock domains.
m.domains.gck1 = cd_gck1 = ClockDomain(name="gck1", reset_less=True) # analog front-end clock.
# We need to delay `gck1` clock by at least 8ns, not possible with the PLL alone.
# Each LUT introduces a minimum propagation delay of 9ns (best case).
delayed_gck1 = self.lut_delay(m, platform.request("afe_clk").i, depth=2)
m.d.comb += cd_gck1.clk.eq(delayed_gck1)
platform.add_clock_constraint(delayed_gck1, 40e6)
return m

68
firmware/fpga/build.py Normal file
View File

@@ -0,0 +1,68 @@
#!/usr/bin/env python3
#
# This file is part of HackRF.
#
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
import lz4.block
import struct
import os
from board import PralinePlatform
from top.standard import Top as standard
from top.half_precision import Top as half_precision
from top.ext_precision_rx import Top as ext_precision_rx
from top.ext_precision_tx import Top as ext_precision_tx
BLOCK_SIZE = 4096 # 4 KiB blocks
OUTPUT_FILE = "praline_fpga.bin"
def compress_blockwise(input_stream, output_stream):
# For every block...
while block := f_in.read(BLOCK_SIZE):
# Compress the block using raw LZ4 block compression.
compressed_block = lz4.block.compress(block, store_size=False,
mode="high_compression", compression=12)
# Write the compressed block length and its contents to the output file.
f_out.write(struct.pack("<H", len(compressed_block)))
f_out.write(compressed_block)
# Write end marker (block of size 0)
f_out.write(struct.pack("<H", 0))
if __name__ == "__main__":
fpga_images = {
"0_standard": standard(),
"1_halfprec": half_precision(),
"2_extprec_rx": ext_precision_rx(),
"3_extprec_tx": ext_precision_tx(),
}
# Build bitstreams first.
for name, image in fpga_images.items():
PralinePlatform().build(image, name=name)
# Pack all the bitstreams.
with open(f"build/{OUTPUT_FILE}", "wb") as f_out:
f_out.write(struct.pack("<I", len(fpga_images))) # number of bitstreams
f_out.seek(4 * len(fpga_images), os.SEEK_CUR) # reserve 4-byte slots for offsets
offsets = []
# Write compressed bitstreams in our custom raw LZ4 block format.
for name, image in fpga_images.items():
img_path = f"build/{name}.bin"
offsets.append(f_out.tell())
with open(img_path, 'rb') as f_in:
compress_blockwise(f_in, f_out)
# Write offsets table, right after the number of bitstreams.
f_out.seek(4, os.SEEK_SET)
for offset in offsets:
f_out.write(struct.pack("<I", offset))

Binary file not shown.

View File

743
firmware/fpga/dsp/cic.py Normal file
View File

@@ -0,0 +1,743 @@
#
# This file is part of HackRF.
#
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
from math import floor, log2, ceil, comb
from amaranth import Module, Signal, Const, signed, ResetInserter, DomainRenamer, C
from amaranth.utils import bits_for
from amaranth.lib import wiring, stream, data
from amaranth.lib.wiring import In, Out, connect
from dsp.round import convergent_round
class CICInterpolator(wiring.Component):
def __init__(self, M, stages, rates, width_in, width_out=None, num_channels=1, always_ready=False, domain="sync"):
self.M = M
self.stages = stages
self.rates = rates
self.width_in = width_in
self.num_channels = num_channels
if width_out is None:
width_out = width_in + self.bit_growths()[-1]
self.width_out = width_out
self._domain = domain
super().__init__({
"input": In(stream.Signature(
data.ArrayLayout(signed(width_in), num_channels),
always_ready=always_ready
)),
"output": Out(stream.Signature(
data.ArrayLayout(signed(width_out), num_channels),
always_ready=always_ready
)),
"factor": In(range(bits_for(max(rates)))),
})
def bit_growths(self):
bit_growths = cic_growth(N=self.stages, M=self.M, R=max(self.rates))
return bit_growths
def elaborate(self, platform):
m = Module()
always_ready = self.output.signature.always_ready
# Detect interpolation factor changes to provide an internal reset signal.
factor_last = Signal.like(self.factor)
factor_change = Signal()
m.d.sync += factor_last.eq(self.factor)
m.d.sync += factor_change.eq(factor_last != self.factor)
factor_reset = ResetInserter(factor_change)
# Calculated bit growths only used below for integrator stages.
bit_growths = iter(self.bit_growths())
stages = []
# When M=1, we can replace the inner CIC stage with an equivalent zero-order hold integrator.
inner_zoh = self.M == 1
# Comb stages.
width = self.width_in
for i in range(self.stages - int(inner_zoh)):
next_width = self.width_in + next(bit_growths)
stage = factor_reset(CombStage(self.M, width, width_out=next_width, num_channels=self.num_channels, always_ready=always_ready))
m.submodules[f"comb{i}"] = stage
stages += [ stage ]
width = next_width
# Upsampling.
if list(self.rates) != [1]:
if inner_zoh:
_ = next(bit_growths), next(bit_growths) # drop comb and integrator growths
stage = factor_reset(Upsampler(self.num_channels * width, max(self.rate), zero_order_hold=inner_zoh, variable=True, always_ready=always_ready))
m.submodules["upsampler"] = stage
m.d.sync += stage.factor.eq(1 << self.factor)
stages += [ stage ]
# Integrator stages.
for i in range(self.stages - int(inner_zoh)):
width_out = self.width_in + next(bit_growths)
stage = SignExtend(width, width_out, num_channels=self.num_channels, always_ready=always_ready)
m.submodules[f"signextend{i}"] = stage
stages += [ stage ]
stage = factor_reset(IntegratorStage(width_out, width_out, num_channels=self.num_channels, always_ready=always_ready))
m.submodules[f"integrator{i}"] = stage
stages += [ stage ]
width = width_out
# Variable gain stage.
min_shift = self.width_in + cic_growth(N=self.stages, M=self.M, R=min(self.rates))[-1] - self.width_out # at min rate
shift_per_rate = { int(log2(rate)): min_shift + (self.stages-1)*i for i, rate in enumerate(self.rates) }
stage = factor_reset(ProgrammableShift(width, width_out=self.width_out, num_channels=self.num_channels, shift_map=shift_per_rate, always_ready=always_ready))
m.submodules["gain"] = stage
if len(self.rates) > 1:
m.d.sync += stage.shift.eq(self.factor)
stages += [ stage ]
width = self.width_out
# Connect all stages to build the final filter.
# For the upsampling CIC, we can only drop bits at the last stage.
last = wiring.flipped(self.input)
for stage in stages:
connect(m, last, stage.input)
last = stage.output
connect(m, last, wiring.flipped(self.output))
if self._domain != "sync":
m = DomainRenamer(self._domain)(m)
return m
class CICDecimator(wiring.Component):
def __init__(self, M, stages, rates, width_in, width_out=None, num_channels=1, always_ready=False, domain="sync"):
self.M = M
self.stages = stages
self.rates = rates
self.width_in = width_in
self.num_channels = num_channels
self._domain = domain
if width_out is None:
width_out = width_in + ceil(stages * log2(max(rates) * M))
self.width_out = width_out
super().__init__({
"input": In(stream.Signature(
data.ArrayLayout(signed(width_in), num_channels),
always_ready=always_ready
)),
"output": Out(stream.Signature(
data.ArrayLayout(signed(width_out), num_channels),
always_ready=always_ready
)),
"factor": In(range(bits_for(max(rates)))),
})
def truncation_summary(self):
rates = min(self.rates)
return cic_truncation(N=self.stages, R=rates, M=self.M,
Bin=self.width_in, Bout=self.width_out)
def elaborate(self, platform):
m = Module()
stages = []
always_ready = self.output.signature.always_ready
full_width = self.width_in + ceil(self.stages * log2(max(self.rates) * self.M))
stage_widths = ( full_width - n for n in self.truncation_summary() )
# Sign extend stage
last_width = self.width_in
stage_width = next(stage_widths)
stage = SignExtend(last_width, stage_width, num_channels=self.num_channels, always_ready=always_ready)
m.submodules["signextend"] = stage
stages += [ stage ]
last_width = stage_width
# Integrator stages
for i in range(self.stages):
stage_width = next(stage_widths)
stage = IntegratorStage(last_width, stage_width, num_channels=self.num_channels, always_ready=always_ready)
m.submodules[f"integrator{i}"] = stage
stages += [ stage ]
last_width = stage_width
# Downsampling
if list(self.rates) != [1]:
stage = Downsampler(self.num_channels * last_width, max(self.rates), variable=True, always_ready=always_ready)
m.submodules["downsampler"] = stage
m.d.sync += stage.factor.eq(1 << self.factor)
stages += [ stage ]
# Comb stages
for i in range(self.stages):
stage_width = next(stage_widths)
stage = CombStage(self.M, last_width, stage_width, num_channels=self.num_channels, always_ready=always_ready)
m.submodules[f"comb{i}"] = stage
stages += [ stage ]
last_width = stage_width
# Gain stage
# Ensure filter gain is at least the gain from width conversion.
min_growth = ceil(self.stages * log2(min(self.rates) * self.M))
if min_growth < self.width_out - self.width_in:
growth = self.width_out - self.width_in - min_growth
stage = WidthConverter(last_width, last_width+growth, num_channels=self.num_channels, always_ready=always_ready)
m.submodules["gain0"] = stage
stages += [ stage ]
last_width = last_width + growth
if len(self.rates) > 1:
shift_per_rate = { int(log2(rate)): self.stages*i for i, rate in enumerate(self.rates) }
stage = ProgrammableShift(last_width, width_out=self.width_out, num_channels=self.num_channels, shift_map=shift_per_rate, always_ready=always_ready)
m.submodules["gain"] = stage
m.d.sync += stage.shift.eq(self.factor)
stages += [stage]
last_width = self.width_out
# Connect stages, rounding/truncating where needed
last = wiring.flipped(self.input)
for stage in stages:
connect(m, last, stage.input)
last = stage.output
connect(m, last, wiring.flipped(self.output))
if self._domain != "sync":
m = DomainRenamer(self._domain)(m)
return m
class ProgrammableShift(wiring.Component):
def __init__(self, width_in, shift_map, width_out=None, num_channels=1, always_ready=False):
self.num_channels = num_channels
self.width_in = width_in
self.width_out = width_out or width_in
self.shift_map = shift_map
if len(self.shift_map) == 1:
self.shift = C(list(self.shift_map.keys())[0])
super().__init__({
"input": In(stream.Signature(
data.ArrayLayout(signed(self.width_in), num_channels),
always_ready=always_ready
)),
"output": Out(stream.Signature(
data.ArrayLayout(signed(self.width_out), num_channels),
always_ready=always_ready
)),
} | ({"shift": In(range(max(shift_map.keys())+1))} if len(shift_map)>1 else {}))
def elaborate(self, platform):
m = Module()
# Implement the map itself (should it be done outside?)
max_shift = max(self.shift_map.values())
value_scaled = [ Signal(signed(self.width_in + max_shift)) for _ in range(self.num_channels) ]
scaled_valid = Signal()
scaled_ready = Signal()
with m.If(~scaled_valid | scaled_ready):
if not self.input.signature.always_ready:
m.d.comb += self.input.ready.eq(1)
m.d.sync += scaled_valid.eq(self.input.valid)
with m.If(self.input.valid):
for c in range(self.num_channels):
with m.Switch(self.shift):
for k, v in self.shift_map.items():
with m.Case(k):
m.d.sync += value_scaled[c].eq(self.input.payload[c] << (max_shift - v))
with m.If(~self.output.valid | self.output.ready):
m.d.comb += scaled_ready.eq(1)
m.d.sync += self.output.valid.eq(scaled_valid)
with m.If(scaled_valid):
for c in range(self.num_channels):
if max_shift > 0:
# Convergent rounding / round to even.
m.d.sync += self.output.payload[c].eq(convergent_round(value_scaled[c], max_shift))
# Truncation.
#m.d.sync += self.output.payload[c].eq(value_scaled[c][max_shift:])
else:
m.d.sync += self.output.payload[c].eq(value_scaled[c])
return m
class SignExtend(wiring.Component):
def __init__(self, width_in, width_out, num_channels=1, always_ready=False):
self.num_channels = num_channels
self.always_ready = always_ready
super().__init__({
"input": In(stream.Signature(
data.ArrayLayout(signed(width_in), num_channels),
always_ready=always_ready
)),
"output": Out(stream.Signature(
data.ArrayLayout(signed(width_out), num_channels),
always_ready=always_ready
)),
})
def elaborate(self, platform):
m = Module()
for c in range(self.num_channels):
m.d.comb += self.output.p[c].eq(self.input.p[c])
m.d.comb += self.output.valid.eq(self.input.valid)
if not self.always_ready:
m.d.comb += self.input.ready.eq(self.output.ready)
return m
class WidthConverter(wiring.Component):
def __init__(self, width_in, width_out, num_channels=1, always_ready=False):
self.width_in = width_in
self.width_out = width_out
self.num_channels = num_channels
self.always_ready = always_ready
super().__init__({
"input": In(stream.Signature(
data.ArrayLayout(signed(width_in), num_channels),
always_ready=always_ready
)),
"output": Out(stream.Signature(
data.ArrayLayout(signed(width_out), num_channels),
always_ready=always_ready
)),
})
def elaborate(self, platform):
m = Module()
shift = self.width_out - self.width_in
for c in range(self.num_channels):
m.d.comb += self.output.p[c].eq(self.input.p[c] << shift)
m.d.comb += self.output.valid.eq(self.input.valid)
if not self.always_ready:
m.d.comb += self.input.ready.eq(self.output.ready)
return m
class CombStage(wiring.Component):
def __init__(self, M, width_in, width_out=None, num_channels=1, always_ready=False):
assert M in (1,2)
self.M = M
self.width_in = width_in
self.width_out = width_out or width_in + 1
self.num_channels = num_channels
super().__init__({
"input": In(stream.Signature(
data.ArrayLayout(signed(self.width_in), num_channels),
always_ready=always_ready
)),
"output": Out(stream.Signature(
data.ArrayLayout(signed(self.width_out), num_channels),
always_ready=always_ready
)),
})
def elaborate(self, platform):
m = Module()
shift = max(self.width_in - self.width_out, 0)
delay = [ Signal.like(self.input.p) for _ in range(self.M) ]
with m.If(~self.output.valid | self.output.ready):
if not self.input.signature.always_ready:
m.d.comb += self.input.ready.eq(1)
m.d.sync += self.output.valid.eq(self.input.valid)
with m.If(self.input.valid):
m.d.sync += delay[0].eq(self.input.p)
m.d.sync += [ delay[i].eq(delay[i-1]) for i in range(1, self.M) ]
for c in range(self.num_channels):
diff = self.input.p[c] - delay[-1][c]
m.d.sync += self.output.p[c].eq(diff if shift == 0 else (diff >> shift))
return m
class IntegratorStage(wiring.Component):
def __init__(self, width_in, width_out, num_channels=1, always_ready=False):
self.width_in = width_in
self.width_out = width_out
self.num_channels = num_channels
super().__init__({
"input": In(stream.Signature(
data.ArrayLayout(signed(width_in), num_channels),
always_ready=always_ready
)),
"output": Out(stream.Signature(
data.ArrayLayout(signed(width_out), num_channels),
always_ready=always_ready
)),
})
def elaborate(self, platform):
m = Module()
shift = max(self.width_in - self.width_out, 0)
accumulator = Signal.like(self.input.p)
for c in range(self.num_channels):
m.d.comb += self.output.payload[c].eq(accumulator[c] if shift == 0 else (accumulator[c] >> shift))
with m.If(~self.output.valid | self.output.ready):
if not self.input.signature.always_ready:
m.d.comb += self.input.ready.eq(1)
m.d.sync += self.output.valid.eq(self.input.valid)
with m.If(self.input.valid):
for c in range(self.num_channels):
m.d.sync += accumulator[c].eq(accumulator[c] + self.input.payload[c])
return m
class Upsampler(wiring.Component):
def __init__(self, width, factor, zero_order_hold=False, variable=False, always_ready=False):
self.width = width
self.zoh = zero_order_hold
signature = {
"input": In(stream.Signature(width, always_ready=always_ready)),
"output": Out(stream.Signature(width, always_ready=always_ready)),
}
if variable:
signature.update({"factor": In(range(factor + 1))})
else:
self.factor = Const(factor)
super().__init__(signature)
def elaborate(self, platform):
m = Module()
counter = Signal.like(self.factor)
ready_stb = Signal(init=1)
if not self.input.signature.always_ready:
m.d.comb += self.input.ready.eq(ready_stb)
with m.If(~self.output.valid | self.output.ready):
with m.If(counter == 0):
m.d.sync += self.output.payload.eq(self.input.payload)
m.d.sync += self.output.valid.eq(self.input.valid)
with m.If(self.input.valid):
m.d.sync += counter.eq(self.factor - 1)
m.d.sync += ready_stb.eq(self.factor < 2)
with m.Else():
if not self.zoh:
m.d.sync += self.output.payload.eq(0)
m.d.sync += self.output.valid.eq(1)
m.d.sync += counter.eq(counter - 1)
with m.If(counter == 1):
m.d.sync += ready_stb.eq(1)
return m
class Downsampler(wiring.Component):
def __init__(self, width, factor, variable=False, always_ready=False):
signature = {
"input": In(stream.Signature(width, always_ready=always_ready)),
"output": Out(stream.Signature(width, always_ready=always_ready)),
}
if variable:
# TODO: optimize bit
signature.update({"factor": In(range(factor + 1))})
else:
self.factor = Const(factor)
super().__init__(signature)
def elaborate(self, platform):
m = Module()
counter = Signal.like(self.factor)
with m.If(self.input.ready & self.input.valid):
with m.If(counter == 0):
m.d.sync += counter.eq(self.factor - 1)
with m.Else():
m.d.sync += counter.eq(counter - 1)
with m.If(self.output.ready | ~self.output.valid):
if not self.input.signature.always_ready:
m.d.comb += self.input.ready.eq(1)
m.d.sync += self.output.valid.eq(self.input.valid & (counter == 0))
with m.If(self.input.valid & (counter == 0)):
m.d.sync += self.output.payload.eq(self.input.payload)
return m
# Refs:
# [1]: Eugene Hogenauer, "An Economical Class of Digital Filters For Decimation and Interpolation,"
# IEEE Trans. Acoust. Speech and Signal Proc., Vol. ASSP-29, April 1981, pp. 155-162.
# [2]: Rick Lyons, "Computing CIC filter register pruning using MATLAB"
# https://www.dsprelated.com/showcode/269.php
# [3]: Peter Thorwartl, "Implementation of Filters", Part 3, lecture notes.
# https://www.so-logic.net/documents/trainings/03_so_implementation_of_filters.pdf
# CIC downsamplers / decimators
# How much can we prune / truncate every stage output given a desired output width ?
# Calculate how many bits we can discard after each intermediate stage such that the quantization
# error introduced is not greater than the one introduced by truncating/rounding at the filter
# output.
def F_sq(N, R, M, i):
assert i <= 2*N + 1
if i == 2*N + 1: return 1 # eq. (16b) from [1]
# h(k) and L (range of k), eq. (9b) from [1]
if i <= N:
# integrator stage
L = N * (R * M - 1) + i - 1
def h(k):
return sum((-1)**l * comb(N, l) * comb(N-i+k-R*M*l, k-R*M*l)
for l in range(k//(R*M)+1))
else:
# comb stage
L = 2*N + 1 - i
def h(k):
return (-1)**k * comb(2*N+1-i, k)
# Compute standard deviation error gain from stage i to output
F_i_sq = sum(h(k)**2 for k in range(L+1))
return F_i_sq
def cic_truncation(N, R, M, Bin, Bout=None):
full_width = Bin + ceil(N * log2(R * M)) # maximum width at output
Bout = Bout or full_width # allow to specify full width
B_last = full_width - Bout # number of bits discarded at last stage
t = log2(2**(2*B_last)/12) + log2(6 / N) # Last two terms of (21) from [1]
truncation = []
for stage in range(2*N):
ou = F_sq(N, R, M, stage+1)
B_i = floor(0.5 * (-log2(ou) + t)) # Eq. (21) from [1]
truncation.append(max(0, B_i))
truncation.append(max(0, B_last))
truncation[0] = 0 # [2]: fix case where input is truncated prior to any filtering
return truncation
# CIC upsamplers / interpolators
# How much bit growth there is per intermediate stage?
# In the interpolator case, we cannot discard bits in intermediate stages: small errors in the
# interpolator stages causes the variance of the error to grow without bound resulting in an
# unstable filter.
def cic_growth(N, R, M):
growths = []
for i in range(1, 2*N+1):
if i <= N:
G_i = 2**i # comb stage
# special case from [1] when differential delay is 1
if M == 1 and i == N:
G_i = 2**(N - 1)
else:
G_i = (2**(2*N-i) * (R*M)**(i-N)) / R # integration stage
growths.append(ceil(log2(G_i)))
return growths
#
# Tests
#
import unittest
import numpy as np
from amaranth.sim import Simulator
from collections import namedtuple
class _TestFilter(unittest.TestCase):
def _generate_samples(self, count, width, f_width=0):
# Generate `count` random samples.
rng = np.random.default_rng(0)
samples = rng.normal(0, 1, count)
# Convert to integer.
samples = np.round(samples / max(abs(samples)) * (2**(width-1) - 1)).astype(int)
assert max(samples) < 2**(width-1) and min(samples) >= -2**(width-1) # sanity check
if f_width > 0:
return samples / (1 << f_width)
return samples
def _filter(self, dut, samples, count, oob=[], outfile=None):
async def input_process(ctx):
if hasattr(dut, "enable"):
ctx.set(dut.enable, 1)
for name, value in oob.items():
ctx.set(getattr(dut, name), value)
await ctx.tick()
await ctx.tick()
for sample in samples:
ctx.set(dut.input.payload, [sample.item()])
ctx.set(dut.input.valid, 1)
await ctx.tick().until(dut.input.ready)
ctx.set(dut.input.valid, 0)
filtered = []
async def output_process(ctx):
if not dut.output.signature.always_ready:
ctx.set(dut.output.ready, 1)
async for clk, rst, valid, payload in ctx.tick().sample(dut.output.valid, dut.output.payload):
if valid:
filtered.append(payload[0])
if len(filtered) == count:
break
sim = Simulator(dut)
sim.add_clock(1/100e6)
sim.add_testbench(input_process)
sim.add_testbench(output_process)
if outfile is not None:
with sim.write_vcd(outfile):
sim.run()
else:
sim.run()
return filtered
class TestCICDecimator(_TestFilter):
def test_filter(self):
num_samples = 1024
input_width = 8
input_samples = self._generate_samples(num_samples, input_width)
test = namedtuple('CICDecimatorTest', ['M', 'order', 'rates', 'factor_log', 'width_in', 'width_out', 'outfile'], defaults=(None,)*7)
cic_tests = []
# for different CIC orders...
for o in [1,2,3,4]:
# test signal with no rate change
cic_tests.append(test(M=1, order=o, rates=(1,), factor_log=0, width_in=8, width_out=8))
cic_tests.append(test(M=2, order=o, rates=(1,), factor_log=0, width_in=8, width_out=8))
cic_tests.append(test(M=2, order=o, rates=(1,), factor_log=0, width_in=8, width_out=12))
# test decimation by 4 with different M values and minimum decimation factors
cic_tests.append(test(M=1, order=o, rates=(1, 2, 4, 8, 16, 32), factor_log=2, width_in=8, width_out=8))
cic_tests.append(test(M=2, order=o, rates=(1, 2, 4, 8, 16, 32), factor_log=2, width_in=8, width_out=8))
cic_tests.append(test(M=1, order=o, rates=(2, 4, 8, 16, 32), factor_log=2, width_in=8, width_out=8))
cic_tests.append(test(M=2, order=o, rates=(2, 4, 8, 16, 32), factor_log=2, width_in=8, width_out=8))
cic_tests.append(test(M=1, order=o, rates=(4, 8, 16, 32), factor_log=2, width_in=8, width_out=8))
# different bit widths
cic_tests.append(test(M=1, order=o, rates=(1, 2, 4, 8, 16, 32), factor_log=2, width_in=8, width_out=9))
cic_tests.append(test(M=1, order=o, rates=(1, 2, 4, 8, 16, 32), factor_log=2, width_in=8, width_out=10))
cic_tests.append(test(M=1, order=o, rates=(1, 2, 4, 8, 16, 32), factor_log=0, width_in=8, width_out=12))
cic_tests.append(test(M=1, order=o, rates=(1, 2, 4, 8, 16, 32), factor_log=1, width_in=8, width_out=12))
cic_tests.append(test(M=1, order=o, rates=(1, 2, 4, 8, 16, 32), factor_log=2, width_in=8, width_out=12))
# test fixed decimation by 32
cic_tests.append(test(M=1, order=o, rates=(32,), factor_log=5, width_in=8, width_out=8))
for t in cic_tests:
with self.subTest(t):
factor_log = t.factor_log
factor = 1 << factor_log
cic_order = t.order
M = t.M
# Build taps by convolving boxcar filter repeatedly.
taps0 = [1 for _ in range(factor*M)]
taps = [1]
for i in range(cic_order):
taps = np.convolve(taps, taps0)
# Compute the expected result.
cic_gain = (factor*M)**cic_order
width_gain = 2**(t.width_out - t.width_in)
filtered_np = np.convolve(input_samples, taps)
filtered_np = filtered_np[::factor] # decimate
filtered_np = np.round(filtered_np * width_gain // cic_gain) # scale
filtered_np = filtered_np.astype(np .int32).tolist() # convert to python list
# Simulate DUT
dut = CICDecimator(M, cic_order, t.rates, t.width_in, t.width_out, always_ready=True)
filtered = self._filter(dut, input_samples, len(input_samples)//factor, oob={"factor":factor_log}, outfile=t.outfile)
# As we have some rounding error, we expect some samples to differ at most by 1
max_diff = np.max(np.abs(np.array(filtered) - np.array(filtered_np[:len(filtered)])))
self.assertLessEqual(max_diff, 1)
#self.assertListEqual(filtered_np[:len(filtered)], filtered)
class TestCICInterpolator(_TestFilter):
def test_filter(self):
num_samples = 1024
test = namedtuple('CICInterpolatorTest', ['M', 'order', 'rates', 'factor_log', 'width_in', 'width_out', 'outfile'], defaults=(None,)*7)
cic_tests = []
# for different CIC orders...
for o in [1,2,3,4]:
# test signal bypass
cic_tests.append(test(M=1, order=o, rates=(1,), factor_log=0, width_in=8, width_out=8))
cic_tests.append(test(M=1, order=o, rates=(1,), factor_log=0, width_in=12, width_out=8))
# test interpolation by 4 with different M values and minimum interpolation factors
cic_tests.append(test(M=1, order=o, rates=(1, 2, 4, 8, 16, 32), factor_log=2, width_in=8, width_out=8))
cic_tests.append(test(M=2, order=o, rates=(1, 2, 4, 8, 16, 32), factor_log=2, width_in=8, width_out=8))
cic_tests.append(test(M=1, order=o, rates=(2, 4, 8, 16, 32), factor_log=2, width_in=8, width_out=8))
cic_tests.append(test(M=2, order=o, rates=(2, 4, 8, 16, 32), factor_log=2, width_in=8, width_out=8))
cic_tests.append(test(M=1, order=o, rates=(4, 8, 16, 32), factor_log=2, width_in=8, width_out=8))
# different bit widths
cic_tests.append(test(M=1, order=o, rates=(1, 2, 4, 8, 16, 32), factor_log=2, width_in=12, width_out=8))
cic_tests.append(test(M=2, order=o, rates=(1, 2, 4, 8, 16, 32), factor_log=2, width_in=12, width_out=8))
cic_tests.append(test(M=1, order=o, rates=(2, 4, 8, 16, 32), factor_log=2, width_in=12, width_out=8))
# test fixed interpolation by 32
cic_tests.append(test(M=1, order=o, rates=(32,), factor_log=5, width_in=8, width_out=8))
cic_tests.append(test(M=1, order=o, rates=(32,), factor_log=5, width_in=12, width_out=8))
for t in cic_tests:
with self.subTest(t):
input_samples = self._generate_samples(num_samples, t.width_in)
factor_log = t.factor_log
factor = 1 << factor_log
cic_order = t.order
M = t.M
# Build taps by convolving boxcar filter repeatedly.
taps0 = [1 for _ in range(factor*M)]
taps = [1]
for i in range(cic_order):
taps = np.convolve(taps, taps0)
# Compute the expected result
cic_gain = (factor*M)**cic_order // factor
width_gain = 2**(t.width_out - t.width_in)
filtered_np = np.zeros(factor * num_samples)
filtered_np[::factor] = input_samples
filtered_np = np.convolve(filtered_np, taps)
filtered_np = np.round(filtered_np * width_gain / cic_gain) # scale
filtered_np = filtered_np.astype(np.int32).tolist() # convert to python list
# Simulate DUT
dut = CICInterpolator(M, cic_order, t.rates, t.width_in, t.width_out, always_ready=False)
filtered = self._filter(dut, input_samples, len(input_samples)//factor, oob={"factor":factor_log}, outfile=t.outfile)
self.assertListEqual(filtered_np[:len(filtered)], filtered)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,141 @@
#
# This file is part of HackRF.
#
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
from amaranth import Module, Signal, Mux, signed, DomainRenamer, Cat, EnableInserter
from amaranth.lib import wiring, stream, data
from amaranth.lib.wiring import In, Out
class DCBlock(wiring.Component):
"""
DC blocking filter with dithering
Removes DC offset using a leaky integrator:
y[n] = x[n] - avg[n-1]
avg[n] = alpha * y[n] + avg[n-1]
where alpha is the leakage coefficient (2**-ratio).
"""
def __init__(self, width, ratio=12, num_channels=1, always_ready=True, domain="sync"):
self.ratio = ratio
self.width = width
self.num_channels = num_channels
self.domain = domain
sig = stream.Signature(
data.ArrayLayout(signed(width), num_channels),
always_ready=always_ready
)
super().__init__({
"input": In(sig),
"output": Out(sig),
"enable": In(1),
})
def elaborate(self, platform):
m = Module()
# Resync control signaling.
enable = Signal()
m.d.sync += [
enable .eq(self.enable),
]
# Fixed-point configuration.
ratio = self.ratio
input_shape = signed(self.width)
ext_precision = signed(self.width + ratio)
# Shared PRNG for all channels.
prng_en = Signal()
m.submodules.prng = prng = EnableInserter(prng_en)(Xoroshiro64AOX())
prng_bits = prng.output
# Common signaling.
with m.If(~self.output.valid | self.output.ready):
if not self.input.signature.always_ready:
m.d.comb += self.input.ready.eq(1)
m.d.sync += self.output.valid.eq(self.input.valid)
with m.If(self.input.ready & self.input.valid):
m.d.comb += prng_en.eq(1)
# Per-channel processing.
for c in range(self.num_channels):
# Signal declarations.
sample_in = self.input.p[c]
y = Signal(input_shape) # current output
y_q = Signal(input_shape) # last output
avg = Signal(ext_precision) # current average
avg_q = Signal(ext_precision) # last average
qavg = Signal(input_shape) # quantized avg
qavg_q = Signal(input_shape) # last quantized avg
error = Signal(ratio)
dither = Signal(ratio) # dither pattern
# Generate unique dither pattern for each channel.
m.d.sync += dither.eq(prng_bits.word_select(c, ratio))
def saturating_sub(a, b):
r = a - b
r_sat = Cat((~r[-1]).replicate(self.width-1), r[-1])
overflow = r[-1] ^ r[-2] # sign bit of the result different from carry (top 2 bits)
return Mux(overflow, r_sat, r)
with m.If(self.input.valid & self.input.ready):
m.d.sync += [
y_q .eq(y),
avg_q .eq(avg),
qavg_q .eq(qavg),
]
m.d.comb += [
y .eq(saturating_sub(sample_in, qavg_q)),
avg .eq(avg_q + y_q), # update avg
# Truncate with dithering, discard quantization error.
Cat(error, qavg).eq(avg + dither),
]
m.d.sync += self.output.p[c].eq(Mux(enable, y, self.input.p[c]))
if self.domain != "sync":
m = DomainRenamer(self.domain)(m)
return m
class Xoroshiro64AOX(wiring.Component):
""" Variant of xoroshiro64 for faster hardware implementation """
""" AOX mod from 'A Fast Hardware Pseudorandom Number Generator Based on xoroshiro128' """
output: Out(32)
def __init__(self, s0=1, s1=0):
self.s0 = s0
self.s1 = s1
super().__init__()
def elaborate(self, platform):
m = Module()
s0 = Signal(32, init=self.s0)
s1 = Signal(32, init=self.s1)
a = 26
b = 9
c = 13
sx = Signal(32)
sa = Signal(32)
m.d.comb += sx.eq(s0 ^ s1)
m.d.comb += sa.eq(s0 & s1)
m.d.sync += s0.eq(s0.rotate_left(a) ^ sx ^ (sx << b))
m.d.sync += s1.eq(sx.rotate_left(c))
m.d.sync += self.output.eq(sx ^ (sa.rotate_left(1) | sa.rotate_left(2)))
return m

604
firmware/fpga/dsp/fir.py Normal file
View File

@@ -0,0 +1,604 @@
#
# This file is part of HackRF.
#
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
from math import ceil, log2
from amaranth import Module, Signal, Mux, DomainRenamer
from amaranth.lib import wiring, stream, data, memory
from amaranth.lib.wiring import In, Out
from amaranth.utils import bits_for
from amaranth_future import fixed
from dsp.mcm import ShiftAddMCM
class HalfBandDecimator(wiring.Component):
def __init__(self, taps, data_shape, shape_out=None, always_ready=False, domain="sync"):
midtap = taps[len(taps)//2]
assert taps[1::2] == [0]*(len(taps)//4) + [midtap] + [0]*(len(taps)//4)
assert midtap == 0.5
self.taps = taps
self.data_shape = data_shape
if shape_out is None:
shape_out = data_shape
self.shape_out = shape_out
self.always_ready = always_ready
self._domain = domain
super().__init__({
"input": In(stream.Signature(
data.ArrayLayout(data_shape, 2),
always_ready=always_ready
)),
"output": Out(stream.Signature(
data.ArrayLayout(shape_out, 2),
always_ready=always_ready
)),
"enable": In(1),
})
@staticmethod
def interleave_with_zeros(seq, factor):
out = []
for n in seq:
out.append(n)
out.extend([0]*factor)
return out[:-factor]
def elaborate(self, platform):
m = Module()
always_ready = self.always_ready
taps = [ 2 * tap for tap in self.taps ] # scale by 0.5 at the output
fir_taps = self.interleave_with_zeros(taps[0::2], 1)
# Arms
m.submodules.fir = fir = FIRFilter(fir_taps, shape=self.data_shape, always_ready=always_ready,
num_channels=1, add_tap=len(fir_taps)//2+1)
with m.FSM():
with m.State("BYPASS"):
with m.If(~self.output.valid | self.output.ready):
m.d.sync += self.output.valid.eq(self.input.valid)
with m.If(self.input.valid):
m.d.sync += self.output.payload.eq(self.input.payload)
if not self.input.signature.always_ready:
m.d.comb += self.input.ready.eq(1)
with m.If(self.enable):
m.next = "DECIMATE"
with m.State("DECIMATE"):
# Input switching.
odd = Signal()
input_idx = Signal()
even_valid = Signal()
even_buffer = Signal.like(self.input.p)
q_inputs = Signal.like(self.input.p)
if not self.input.signature.always_ready:
m.d.comb += self.input.ready.eq((~odd & ~even_valid) | fir.input.ready)
# Even samples are buffered and used as a secondary
# carry addition for the FIR filter.
# I and Q channels are muxed in time, demuxed later in the output stage.
with m.If(self.input.valid & self.input.ready):
m.d.sync += odd.eq(~odd)
with m.If(~odd):
with m.If(~even_valid | fir.input.ready):
m.d.sync += even_valid.eq(self.input.valid)
with m.If(self.input.valid):
m.d.sync += even_buffer.eq(self.input.p)
# Process two I samples and two Q samples in sequence.
with m.If(fir.input.ready & fir.input.valid):
m.d.sync += input_idx.eq(input_idx ^ 1)
with m.If(input_idx == 0):
m.d.comb += [
fir.add_input .eq(even_buffer[0]),
fir.input.p .eq(self.input.p[0]),
fir.input.valid .eq(self.input.valid & even_valid),
]
with m.If(fir.input.ready & fir.input.valid):
m.d.sync += [
q_inputs[0].eq(even_buffer[1]),
q_inputs[1].eq(self.input.p[1]),
]
with m.Else():
m.d.comb += [
fir.add_input .eq(q_inputs[0]),
fir.input.p .eq(q_inputs[1]),
fir.input.valid .eq(1),
]
# Output sum and demux.
output_idx = Signal()
with m.If(~self.output.valid | self.output.ready):
if not fir.output.signature.always_ready:
m.d.comb += fir.output.ready.eq(1)
m.d.sync += self.output.valid.eq(fir.output.valid & output_idx)
with m.If(fir.output.valid):
m.d.sync += self.output.p[0].eq(self.output.p[1])
m.d.sync += self.output.p[1].eq(fir.output.p[0] * fixed.Const(0.5))
m.d.sync += output_idx.eq(output_idx ^ 1)
# Mode switch logic.
with m.If(~self.enable):
m.d.sync += input_idx.eq(0)
m.d.sync += output_idx.eq(0)
m.d.sync += odd.eq(0)
m.d.sync += even_valid.eq(0)
m.next = "BYPASS"
if self._domain != "sync":
m = DomainRenamer(self._domain)(m)
return m
class HalfBandInterpolator(wiring.Component):
def __init__(self, taps, data_shape, shape_out=None, always_ready=False, num_channels=1, domain="sync"):
midtap = taps[len(taps)//2]
assert taps[1::2] == [0]*(len(taps)//4) + [midtap] + [0]*(len(taps)//4)
assert midtap == 0.5
self.taps = taps
self.data_shape = data_shape
if shape_out is None:
shape_out = data_shape
self.shape_out = shape_out
self.always_ready = always_ready
self._domain = domain
self.num_channels = num_channels
super().__init__({
"input": In(stream.Signature(
data.ArrayLayout(data_shape, num_channels),
always_ready=always_ready
)),
"output": Out(stream.Signature(
data.ArrayLayout(shape_out, num_channels),
always_ready=always_ready
)),
"enable": In(1),
})
def elaborate(self, platform):
m = Module()
always_ready = self.always_ready
taps = [ 2 * tap for tap in self.taps ]
arm0_taps = taps[0::2]
arm1_taps = taps[1::2]
delay = arm1_taps.index(1)
# Arms
m.submodules.fir0 = fir0 = FIRFilter(arm0_taps, shape=self.data_shape, shape_out=self.shape_out, always_ready=always_ready, num_channels=self.num_channels)
m.submodules.fir1 = fir1 = Delay(delay, shape=self.data_shape, always_ready=always_ready, num_channels=self.num_channels)
arms = [fir0, fir1]
with m.FSM():
with m.State("BYPASS"):
with m.If(~self.output.valid | self.output.ready):
m.d.sync += self.output.valid.eq(self.input.valid)
with m.If(self.input.valid):
m.d.sync += self.output.payload.eq(self.input.payload)
if not self.input.signature.always_ready:
m.d.comb += self.input.ready.eq(1)
with m.If(self.enable):
m.next = "INTERPOLATE"
with m.State("INTERPOLATE"):
# Mode switch logic.
with m.If(~self.enable):
m.next = "BYPASS"
# Input
for i, arm in enumerate(arms):
m.d.comb += arm.input.payload.eq(self.input.payload)
m.d.comb += arm.input.valid.eq(self.input.valid & arms[i^1].input.ready)
if not self.input.signature.always_ready:
m.d.comb += self.input.ready.eq(arms[0].input.ready & arms[1].input.ready)
# Output
# Arm index selection: switch after every delivered sample
arm_index = Signal()
# Output buffers for each arm.
arm_outputs = [arm.output for arm in arms]
if self.output.signature.always_ready:
buffers = [stream.Signature(arm.payload.shape()).create() for arm in arm_outputs]
for arm, buf in zip(arm_outputs, buffers):
with m.If(~buf.valid | buf.ready):
if not arm.signature.always_ready:
m.d.comb += arm.ready.eq(1)
m.d.sync += buf.valid.eq(arm.valid)
with m.If(arm.valid):
m.d.sync += buf.payload.eq(arm.payload)
arm_outputs = buffers
with m.If(~self.output.valid | self.output.ready):
with m.Switch(arm_index):
for i, arm in enumerate(arm_outputs):
with m.Case(i):
for c in range(self.num_channels):
m.d.sync += self.output.payload[c].eq(arm.payload[c])
m.d.sync += self.output.valid.eq(arm.valid)
if not arm.signature.always_ready:
m.d.comb += arm.ready.eq(1)
with m.If(arm.valid):
m.d.sync += arm_index.eq(arm_index ^ 1)
if self._domain != "sync":
m = DomainRenamer(self._domain)(m)
return m
class FIRFilter(wiring.Component):
def __init__(self, taps, shape, shape_out=None, always_ready=False, num_channels=1, add_tap=None):
self.taps = list(taps)
self.add_tap = add_tap
self.shape = shape
if shape_out is None:
shape_out = self.compute_output_shape()
self.shape_out = shape_out
self.num_channels = num_channels
self.always_ready = always_ready
sig = {
"input": In(stream.Signature(
data.ArrayLayout(shape, num_channels),
always_ready=always_ready
)),
"output": Out(stream.Signature(
data.ArrayLayout(shape_out, num_channels),
always_ready=always_ready
))
}
if add_tap is not None:
sig |= {"add_input": In(data.ArrayLayout(shape, num_channels))}
super().__init__(sig)
def taps_shape(self):
taps_as_ratios = [tap.as_integer_ratio() for tap in self.taps]
f_width = bits_for(max(tap[1] for tap in taps_as_ratios)) - 1
i_width = max(0, bits_for(max(abs(tap[0]) for tap in taps_as_ratios)) - f_width)
return fixed.Shape(i_width, f_width, signed=any(tap < 0 for tap in self.taps))
def compute_output_shape(self):
taps_shape = self.taps_shape()
signed = self.shape.signed | taps_shape.signed
f_width = self.shape.f_width + taps_shape.f_width
filter_gain = ceil(log2(sum(self.taps) + (1 if self.add_tap is not None else 0)))
i_width = max(0, self.shape.as_shape().width + taps_shape.as_shape().width - signed - f_width + filter_gain)
return fixed.Shape(i_width, f_width, signed=signed)
def elaborate(self, platform):
m = Module()
# Implement transposed-form FIR because it needs a smaller number of registers.
# Helper function to create smaller size registers for fixed point ops.
def fixed_reg(value, *args, **kwargs):
reg = Signal.like(value.raw(), *args, **kwargs)
return fixed.Value(value.shape(), reg)
# Implement constant multipliers.
terms = []
for i, tap in enumerate(self.taps):
tap_fixed = fixed.Const(tap)
terms.append(tap_fixed._value)
m.submodules.mcm = mcm = ShiftAddMCM(self.shape.as_shape().width, terms, num_channels=self.num_channels, always_ready=self.always_ready)
wiring.connect(m, wiring.flipped(self.input), mcm.input)
# Cast outputs to fixed point values.
muls = []
for i, tap in enumerate(self.taps):
tap_fixed = fixed.Const(tap)
muls.append([ fixed.Value.cast(mcm.output.p[c][f"{i}"], tap_fixed.f_width + self.shape.f_width) for c in range(self.num_channels) ])
# Implement adder line.
with m.If(~self.output.valid | self.output.ready):
if not self.always_ready:
m.d.comb += mcm.output.ready.eq(1)
m.d.sync += self.output.valid.eq(mcm.output.valid)
# Carry sum
if self.add_tap is not None:
add_input_q = Signal.like(self.add_input)
m.d.sync += add_input_q.eq(self.add_input)
for c in range(self.num_channels):
accum = None
for i, tap in enumerate(self.taps[::-1]):
match (accum, tap):
case (None, 0): continue
case (None, _): value = muls[::-1][i][c]
case (_, 0): value = accum
case (_, _): value = muls[::-1][i][c] + accum
if self.add_tap is not None:
if i == self.add_tap:
value += add_input_q[c]
accum = fixed_reg(value, name=f"add_{c}_{i}")
with m.If(mcm.output.valid & mcm.output.ready):
m.d.sync += accum.eq(value)
m.d.comb += self.output.payload[c].eq(accum)
return m
class Delay(wiring.Component):
def __init__(self, delay, shape, always_ready=False, num_channels=1):
self.delay = delay
self.shape = shape
self.num_channels = num_channels
super().__init__({
"input": In(stream.Signature(
data.ArrayLayout(shape, num_channels),
always_ready=always_ready
)),
"output": Out(stream.Signature(
data.ArrayLayout(shape, num_channels),
always_ready=always_ready
)),
})
def elaborate(self, platform):
if self.delay < 3:
return self.elaborate_regs()
return self.elaborate_memory()
def elaborate_regs(self):
m = Module()
last = self.input.payload
for i in range(self.delay + 1):
reg = Signal.like(last, name=f"reg_{i}")
with m.If(self.input.valid & self.input.ready):
m.d.sync += reg.eq(last)
last = reg
m.d.comb += self.output.payload.eq(last)
with m.If(self.output.ready | ~self.output.valid):
if not self.input.signature.always_ready:
m.d.comb += self.input.ready.eq(1)
m.d.sync += self.output.valid.eq(self.input.valid)
return m
def elaborate_memory(self):
m = Module()
m.submodules.mem = mem = memory.Memory(
shape=self.input.payload.shape(),
depth=self.delay,
init=(tuple(0 for _ in range(self.num_channels)) for _ in range(self.delay))
)
mem_wr = mem.write_port(domain="sync")
mem_rd = mem.read_port(domain="sync")
addr = Signal.like(mem_wr.addr)
with m.If(self.input.valid & self.input.ready):
m.d.sync += addr.eq(Mux(addr == self.delay-1, 0, addr + 1))
m.d.comb += [
mem_wr.addr .eq(addr),
mem_rd.addr .eq(addr),
mem_wr.data .eq(self.input.payload),
mem_wr.en .eq(self.input.valid & self.input.ready),
mem_rd.en .eq(self.input.valid & self.input.ready),
self.output.payload .eq(mem_rd.data),
]
with m.If(self.output.ready | ~self.output.valid):
if not self.input.signature.always_ready:
m.d.comb += self.input.ready.eq(1)
m.d.sync += self.output.valid.eq(self.input.valid)
return m
#
# Tests
#
import unittest
import numpy as np
from amaranth.sim import Simulator
class _TestFilter(unittest.TestCase):
rng = np.random.default_rng(0)
def _generate_samples(self, count, width, f_width=0):
# Generate `count` random samples.
samples = self.rng.normal(0, 1, count)
# Convert to integer.
samples = np.round(samples / max(abs(samples)) * (2**(width-1) - 1)).astype(int)
assert max(samples) < 2**(width-1) and min(samples) >= -2**(width-1) # sanity check
if f_width > 0:
return samples / (1 << f_width)
return samples
def _filter(self, dut, samples, count, num_channels=1, outfile=None, empty_cycles=0):
async def input_process(ctx):
if hasattr(dut, "enable"):
ctx.set(dut.enable, 1)
await ctx.tick()
ctx.set(dut.input.valid, 1)
for sample in samples:
if num_channels > 1:
ctx.set(dut.input.payload, [s.item() for s in sample])
else:
ctx.set(dut.input.payload, [sample.item()])
await ctx.tick().until(dut.input.ready)
if empty_cycles > 0:
ctx.set(dut.input.valid, 0)
await ctx.tick().repeat(empty_cycles)
ctx.set(dut.input.valid, 1)
ctx.set(dut.input.valid, 0)
filtered = []
async def output_process(ctx):
if not dut.output.signature.always_ready:
ctx.set(dut.output.ready, 1)
while len(filtered) < count:
payload, = await ctx.tick().sample(dut.output.payload).until(dut.output.valid)
if num_channels > 1:
filtered.append([v.as_float() for v in payload])
else:
filtered.append(payload[0].as_float())
if not dut.output.signature.always_ready:
ctx.set(dut.output.ready, 0)
sim = Simulator(dut)
sim.add_clock(1/100e6)
sim.add_testbench(input_process)
sim.add_testbench(output_process)
if outfile:
with sim.write_vcd(outfile):
sim.run()
else:
sim.run()
return filtered
class TestFIRFilter(_TestFilter):
def test_filter(self):
taps = [-1, 0, 9, 16, 9, 0, -1]
taps = [ tap / 32 for tap in taps ]
num_samples = 1024
input_width = 8
input_samples = self._generate_samples(num_samples, input_width)
# Compute the expected result
filtered_np = np.convolve(input_samples, taps).tolist()
# Simulate DUT
dut = FIRFilter(taps, fixed.SQ(15, 0), always_ready=True)
filtered = self._filter(dut, input_samples, len(input_samples))
self.assertListEqual(filtered_np[:len(filtered)], filtered)
class TestHalfBandDecimator(_TestFilter):
def test_filter_no_backpressure(self):
taps = [-1, 0, 9, 16, 9, 0, -1]
taps = [ tap / 32 for tap in taps ]
num_samples = 1024
input_width = 8
samples_i_in = self._generate_samples(num_samples, input_width, f_width=7)
samples_q_in = self._generate_samples(num_samples, input_width, f_width=7)
# Compute the expected result
filtered_i_np = np.convolve(samples_i_in, taps)[1::2].tolist()
filtered_q_np = np.convolve(samples_q_in, taps)[1::2].tolist()
# Simulate DUT
dut = HalfBandDecimator(taps, data_shape=fixed.SQ(7), shape_out=fixed.SQ(0,16), always_ready=True)
filtered = self._filter(dut, zip(samples_i_in, samples_q_in), len(samples_i_in) // 2, num_channels=2)
filtered_i = [ x[0] for x in filtered ]
filtered_q = [ x[1] for x in filtered ]
self.assertListEqual(filtered_i_np[:len(filtered_i)], filtered_i)
self.assertListEqual(filtered_q_np[:len(filtered_q)], filtered_q)
def test_filter_with_spare_cycles(self):
taps = [-1, 0, 9, 16, 9, 0, -1]
taps = [ tap / 32 for tap in taps ]
num_samples = 1024
input_width = 8
samples_i_in = self._generate_samples(num_samples, input_width, f_width=7)
samples_q_in = self._generate_samples(num_samples, input_width, f_width=7)
# Compute the expected result
filtered_i_np = np.convolve(samples_i_in, taps)[1::2].tolist()
filtered_q_np = np.convolve(samples_q_in, taps)[1::2].tolist()
# Simulate DUT
dut = HalfBandDecimator(taps, data_shape=fixed.SQ(7), shape_out=fixed.SQ(0,16), always_ready=True)
filtered = self._filter(dut, zip(samples_i_in, samples_q_in), len(samples_i_in) // 2, num_channels=2, empty_cycles=3)
filtered_i = [ x[0] for x in filtered ]
filtered_q = [ x[1] for x in filtered ]
self.assertListEqual(filtered_i_np[:len(filtered_i)], filtered_i)
self.assertListEqual(filtered_q_np[:len(filtered_q)], filtered_q)
def test_filter_with_backpressure(self):
taps = [-1, 0, 9, 16, 9, 0, -1]
taps = [ tap / 32 for tap in taps ]
num_samples = 1024
input_width = 8
samples_i_in = self._generate_samples(num_samples, input_width, f_width=7)
samples_q_in = self._generate_samples(num_samples, input_width, f_width=7)
# Compute the expected result
filtered_i_np = np.convolve(samples_i_in, taps)[1::2].tolist()
filtered_q_np = np.convolve(samples_q_in, taps)[1::2].tolist()
# Simulate DUT
dut = HalfBandDecimator(taps, data_shape=fixed.SQ(7), shape_out=fixed.SQ(0,16), always_ready=False)
filtered = self._filter(dut, zip(samples_i_in, samples_q_in), len(samples_i_in) // 2, num_channels=2)
filtered_i = [ x[0] for x in filtered ]
filtered_q = [ x[1] for x in filtered ]
self.assertListEqual(filtered_i_np[:len(filtered_i)], filtered_i)
self.assertListEqual(filtered_q_np[:len(filtered_q)], filtered_q)
class TestHalfBandInterpolator(_TestFilter):
def test_filter(self):
taps = [-1, 0, 9, 16, 9, 0, -1]
taps = [ tap / 32 for tap in taps ]
num_samples = 1024
input_width = 8
input_samples = self._generate_samples(num_samples, input_width, f_width=7)
# Compute the expected result
input_samples_pad = np.zeros(2*len(input_samples))
input_samples_pad[0::2] = 2*input_samples # pad with zeros, adjust gain
filtered_np = np.convolve(input_samples_pad, taps).tolist()
# Simulate DUT
dut = HalfBandInterpolator(taps, data_shape=fixed.SQ(0, 7), shape_out=fixed.SQ(0,16), always_ready=False)
filtered = self._filter(dut, input_samples, len(input_samples) * 2)
self.assertListEqual(filtered_np[:len(filtered)], filtered)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,829 @@
#
# This file is part of HackRF.
#
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
from math import ceil, log2
from amaranth import Module, Signal, Mux, DomainRenamer, ClockSignal, signed
from amaranth.lib import wiring, stream, data, memory
from amaranth.lib.wiring import In, Out
from amaranth.utils import bits_for
from amaranth_future import fixed
from dsp.sb_mac16 import SB_MAC16
from dsp.fir import Delay
class HalfBandDecimatorMAC16(wiring.Component):
def __init__(self, taps, data_shape, overclock_rate=4, shape_out=None, always_ready=False, domain="sync"):
midtap = taps[len(taps)//2]
assert taps[1::2] == [0]*(len(taps)//4) + [midtap] + [0]*(len(taps)//4)
self.taps = taps
self.data_shape = data_shape
if shape_out is None:
shape_out = data_shape
self.shape_out = shape_out
self.always_ready = always_ready
self.overclock_rate = overclock_rate
self._domain = domain
super().__init__({
"input": In(stream.Signature(
data.ArrayLayout(data_shape, 2),
always_ready=always_ready
)),
"output": Out(stream.Signature(
data.ArrayLayout(shape_out, 2),
always_ready=always_ready
)),
})
def elaborate(self, platform):
m = Module()
always_ready = self.always_ready
taps = [ 2 * tap for tap in self.taps ] # scale by 0.5 at the output
fir_taps = taps[0::2]
dly_taps = taps[1::2]
delay = dly_taps.index(1) - 1
# Arms
m.submodules.fir = fir = FIRFilterMAC16(fir_taps, shape=self.data_shape, overclock_rate=2*self.overclock_rate, always_ready=always_ready, num_channels=2, carry=self.data_shape)
m.submodules.dly = dly = Delay(delay, shape=self.data_shape, always_ready=always_ready, num_channels=2)
# Input switching.
odd = Signal()
if not self.input.signature.always_ready:
m.d.comb += self.input.ready.eq(~odd | fir.input.ready)
m.d.comb += dly.output.ready.eq(1)
m.d.comb += [
dly.input.p.eq(self.input.p),
dly.input.valid.eq(self.input.valid & ~odd),
]
# Even samples are buffered and used as a secondary
# carry addition for the FIR filter.
with m.If(self.input.valid & self.input.ready):
m.d.sync += odd.eq(~odd)
#
for c in range(2):
m.d.comb += [
fir.sum_carry[c] .eq(dly.output.p[c]), # TODO: optimize shape?
fir.input.p[c] .eq(self.input.p[c]),
]
m.d.comb += fir.input.valid .eq(self.input.valid & odd)
# Output.
with m.If(~self.output.valid | self.output.ready):
if not fir.output.signature.always_ready:
m.d.comb += fir.output.ready.eq(1)
m.d.sync += self.output.valid.eq(fir.output.valid)
with m.If(fir.output.valid):
m.d.sync += self.output.p[0].eq(fir.output.p[0] * fixed.Const(0.5))
m.d.sync += self.output.p[1].eq(fir.output.p[1] * fixed.Const(0.5))
if self._domain != "sync":
m = DomainRenamer(self._domain)(m)
return m
class HalfBandInterpolatorMAC16(wiring.Component):
def __init__(self, taps, data_shape, shape_out=None, overclock_rate=4, always_ready=False, num_channels=1, domain="sync"):
midtap = taps[len(taps)//2]
assert taps[1::2] == [0]*(len(taps)//4) + [midtap] + [0]*(len(taps)//4)
assert midtap == 0.5
self.taps = taps
self.data_shape = data_shape
if shape_out is None:
shape_out = data_shape
self.shape_out = shape_out
self.always_ready = always_ready
self._domain = domain
self.num_channels = num_channels
self.overclock_rate = overclock_rate
super().__init__({
"input": In(stream.Signature(
data.ArrayLayout(data_shape, num_channels),
always_ready=always_ready
)),
"output": Out(stream.Signature(
data.ArrayLayout(shape_out, num_channels),
always_ready=always_ready
)),
})
def elaborate(self, platform):
m = Module()
always_ready = self.always_ready
taps = [ 2 * tap for tap in self.taps ]
arm0_taps = taps[0::2]
# Arms
m.submodules.fir = fir = FIRFilterMAC16(arm0_taps, shape=self.data_shape, shape_out=self.shape_out, overclock_rate=self.overclock_rate, always_ready=always_ready, num_channels=self.num_channels, delayed_port=True)
busy = Signal()
with m.If(fir.input.valid & fir.input.ready):
m.d.sync += busy.eq(1)
# Input
m.d.comb += fir.input.payload.eq(self.input.payload)
m.d.comb += fir.input.valid.eq(self.input.valid & ~busy)
if not self.input.signature.always_ready:
m.d.comb += self.input.ready.eq(fir.input.ready & ~busy)
# Output
# Arm index selection: switch after every delivered sample
arm_index = Signal()
delayed = Signal.like(fir.input_delayed)
with m.If(fir.output.valid & fir.output.ready):
m.d.sync += delayed.eq(fir.input_delayed)
with m.If(~self.output.valid | self.output.ready):
with m.Switch(arm_index):
with m.Case(0):
for c in range(self.num_channels):
m.d.sync += self.output.payload[c].eq(fir.output.payload[c])
m.d.sync += self.output.valid.eq(fir.output.valid)
if not fir.output.signature.always_ready:
m.d.comb += fir.output.ready.eq(1)
with m.If(fir.output.valid):
m.d.sync += arm_index.eq(1)
with m.Case(1):
for c in range(self.num_channels):
m.d.sync += self.output.payload[c].eq(delayed[c])
m.d.sync += self.output.valid.eq(1)
m.d.sync += arm_index.eq(0)
m.d.sync += busy.eq(0)
if self._domain != "sync":
m = DomainRenamer(self._domain)(m)
return m
class FIRFilterMAC16(wiring.Component):
def __init__(self, taps, shape, shape_out=None, always_ready=False, overclock_rate=8, num_channels=1, carry=None, delayed_port=False):
self.carry = carry
self.taps = list(taps)
self.shape = shape
if shape_out is None:
shape_out = self.compute_output_shape()
self.shape_out = shape_out
self.num_channels = num_channels
self.always_ready = always_ready
self.overclock_rate = overclock_rate
self.delayed_port = delayed_port
signature = {
"input": In(stream.Signature(
data.ArrayLayout(shape, num_channels),
always_ready=always_ready
)),
"output": Out(stream.Signature(
data.ArrayLayout(shape_out, num_channels),
always_ready=always_ready
)),
}
if carry is not None:
signature.update({
"sum_carry": In(data.ArrayLayout(carry, num_channels))
})
if delayed_port:
signature.update({
"input_delayed": Out(data.ArrayLayout(shape, num_channels))
})
super().__init__(signature)
def taps_shape(self):
taps_as_ratios = [tap.as_integer_ratio() for tap in self.taps]
f_width = bits_for(max(tap[1] for tap in taps_as_ratios)) - 1
i_width = max(0, bits_for(max(abs(tap[0]) for tap in taps_as_ratios)) - f_width)
return fixed.Shape(i_width, f_width, signed=any(tap < 0 for tap in self.taps))
def compute_output_shape(self):
taps_shape = self.taps_shape()
signed = self.shape.signed | taps_shape.signed
f_width = self.shape.f_width + taps_shape.f_width
filter_gain = ceil(log2(sum(self.taps)))
i_width = max(0, self.shape.as_shape().width + taps_shape.as_shape().width - signed - f_width + filter_gain)
if self.carry is not None:
f_width = max(f_width, self.carry.f_width)
i_width = max(i_width, self.carry.i_width) + 1
shape_out = fixed.Shape(i_width, f_width, signed=signed)
return shape_out
def elaborate(self, platform):
m = Module()
# Build filter out of FIRFilterSerialMAC16 blocks.
overclock_factor = self.overclock_rate
# Symmetric coefficients special case.
symmetric = (self.taps == self.taps[::-1])
# Even-symmetric case. (N=2*K)
# Odd-symmetric case. (N=2*K+1)
if symmetric:
taps = self.taps[:ceil(len(self.taps)/2)]
odd_symmetric = ((len(self.taps) % 2) == 1)
else:
taps = self.taps
dsp_block_count = ceil(len(taps) / overclock_factor)
def pipe(signal, length):
name = signal.name if hasattr(signal, "name") else "signal"
pipe = [ signal ] + [ Signal.like(signal, name=f"{name}_q{i}") for i in range(length) ]
for i in range(length):
m.d.sync += pipe[i+1].eq(pipe[i])
return pipe
if self.carry is not None:
sum_carry_q = Signal.like(self.sum_carry)
with m.If(self.input.valid & self.input.ready):
m.d.sync += sum_carry_q.eq(self.sum_carry)
for c in range(self.num_channels):
last = self.input
dsp_blocks = []
for i in range(dsp_block_count):
taps_slice = taps[i*overclock_factor:(i+1)*overclock_factor]
input_delayed = len(taps_slice)
carry = last.output.p.shape() if i > 0 else self.carry
if (i == dsp_block_count-1) and symmetric and odd_symmetric:
taps_slice[-1] /= 2
input_delayed -= 1
dsp = FIRFilterSerialMAC16(taps=taps_slice, shape=self.shape, taps_shape=self.taps_shape(), carry=carry, symmetry=symmetric,
input_delayed_cycles=input_delayed, always_ready=self.always_ready)
dsp_blocks.append(dsp)
if i == 0:
m.d.comb += [
dsp.input.p .eq(self.input.p[c]),
dsp.input.valid .eq(self.input.valid & self.input.ready),
]
if not self.input.signature.always_ready:
m.d.comb += self.input.ready.eq(dsp.input.ready)
if self.carry is not None:
m.d.comb += dsp.sum_carry.eq(sum_carry_q[c])
else:
m.d.comb += [
dsp.input.p .eq(pipe(last.input_delayed, last.delay())[-1]),
dsp.input.valid .eq(last.output.valid),
dsp.sum_carry .eq(last.output.p),
]
if not last.output.signature.always_ready:
m.d.comb += last.output.ready.eq(dsp.input.ready)
last = dsp
if self.delayed_port:
m.d.comb += self.input_delayed[c].eq(last.input_delayed)
if symmetric:
for i in reversed(range(dsp_block_count)):
end_block = (i == dsp_block_count-1)
m.d.comb += [
dsp_blocks[i].rev_input .eq(dsp_blocks[i+1].rev_delayed if not end_block else dsp_blocks[i].input_delayed),
]
m.submodules += dsp_blocks
m.d.comb += [
self.output.payload[c] .eq(last.output.p),
self.output.valid .eq(last.output.valid),
]
if not last.output.signature.always_ready:
m.d.comb += last.output.ready.eq(self.output.ready)
return m
class FIRFilterSerialMAC16(wiring.Component):
def __init__(self, taps, shape, shape_out=None, taps_shape=None, carry=None, symmetry=False, input_delayed_cycles=None, always_ready=False):
assert shape.as_shape().width <= 16, "DSP slice inputs have a maximum width of 16 bit."
self.carry = carry
self.taps = list(taps)
self.shape = shape
self.taps_shape = taps_shape or self.taps_shape()
if shape_out is None:
shape_out = self.compute_output_shape()
self.shape_out = shape_out
self.always_ready = always_ready
self.symmetry = symmetry
if input_delayed_cycles is None:
self.input_delayed_cycles = len(self.taps)
else:
self.input_delayed_cycles = input_delayed_cycles
signature = {
"input": In(stream.Signature(shape, always_ready=always_ready)),
"input_delayed": Out(shape),
"output": Out(stream.Signature(shape_out, always_ready=always_ready)),
}
if carry is not None:
signature.update({
"sum_carry": In(carry)
})
else:
self.sum_carry = 0
if symmetry:
signature.update({
"rev_input": In(shape),
"rev_delayed": Out(shape),
})
super().__init__(signature)
def taps_shape(self):
taps_as_ratios = [tap.as_integer_ratio() for tap in self.taps]
f_width = bits_for(max(tap[1] for tap in taps_as_ratios)) - 1
i_width = max(0, bits_for(max(abs(tap[0]) for tap in taps_as_ratios)) - f_width)
return fixed.Shape(i_width, f_width, signed=any(tap < 0 for tap in self.taps))
def compute_output_shape(self):
taps_shape = self.taps_shape
signed = self.shape.signed | taps_shape.signed
f_width = self.shape.f_width + taps_shape.f_width
filter_gain = ceil(log2(max(1, sum(self.taps))))
i_width = max(0, self.shape.as_shape().width + taps_shape.as_shape().width - signed - f_width + filter_gain)
if self.carry is not None:
f_width = max(f_width, self.carry.f_width)
i_width = max(i_width, self.carry.i_width) + 1
shape_out = fixed.Shape(i_width, f_width, signed=signed)
return shape_out
def delay(self):
return 1 + 1 + 3 + len(self.taps) - 1
def elaborate(self, platform):
m = Module()
depth = len(self.taps)
counter_in = Signal(range(depth))
counter_mult = Signal(range(depth))
counter_out = Signal(range(depth))
dsp_ready = ~self.output.valid | self.output.ready
window_valid = Signal()
window_ready = dsp_ready
multin_valid = Signal()
input_ready = Signal()
# Ready to process a sample either when the DSP slice is ready and the samples window is:
# - Not valid yet.
# - Only valid for 1 more cycle.
m.d.comb += input_ready.eq(~window_valid | ((counter_in == depth-1) & window_ready))
if not self.input.signature.always_ready:
m.d.comb += self.input.ready.eq(input_ready)
window = [ Signal.like(self.input.p, name=f"window_{i}") for i in range(max(depth, self.input_delayed_cycles)) ]
# Sample window.
with m.If(input_ready):
m.d.sync += window_valid.eq(self.input.valid)
with m.If(self.input.valid):
m.d.sync += window[0].eq(self.input.p)
for i in range(1, len(window)):
m.d.sync += window[i].eq(window[i-1])
m.d.sync += multin_valid.eq(window_valid)
dsp_a = Signal.like(self.input.p)
with m.Switch(counter_in):
for i in range(depth):
with m.Case(i):
m.d.sync += dsp_a.eq(window[i])
m.d.comb += self.input_delayed.eq(window[self.input_delayed_cycles-1])
# Sample counter.
with m.If(window_ready & window_valid):
m.d.sync += counter_in.eq(_incr(counter_in, depth))
# Symmetry handling.
if self.symmetry:
window_rev = [ Signal.like(self.input.p, name=f"window_rev_{i}") for i in range(depth) ]
with m.If(input_ready & self.input.valid):
m.d.sync += window_rev[0].eq(self.rev_input)
m.d.sync += [ window_rev[i].eq(window_rev[i-1]) for i in range(1, len(window_rev)) ]
m.d.comb += self.rev_delayed.eq(window_rev[-1])
dsp_a_rev = Signal.like(self.input.p)
with m.Switch(counter_in):
for i in range(depth):
with m.Case(i):
m.d.sync += dsp_a_rev.eq(window_rev[depth-1-i])
# Coefficient ROM.
taps_shape = self.taps_shape
assert taps_shape.as_shape().width <= 16, "DSP slice inputs have a maximum width of 16 bit."
coeff_data = memory.MemoryData(
shape=taps_shape,
depth=depth, # +200 to force BRAM
init=(fixed.Const(tap, shape=taps_shape) for tap in self.taps),
)
m.submodules.coeff_rom = coeff_rom = memory.Memory(data=coeff_data)
coeff_rd = coeff_rom.read_port(domain="sync")
m.d.comb += coeff_rd.addr.eq(counter_in)
shape_out = self.compute_output_shape()
if self.carry:
sum_carry_q = Signal.like(self.sum_carry)
with m.If(self.input.ready & self.input.valid):
m.d.sync += sum_carry_q.eq(self.sum_carry)
m.submodules.dsp = dsp = iCE40Multiplier()
if self.symmetry:
m.d.comb += dsp.a.eq(dsp_a + dsp_a_rev)
else:
m.d.comb += dsp.a.eq(dsp_a)
m.d.comb += [
dsp.b .eq(coeff_rd.data),
shape_out(dsp.p) .eq(sum_carry_q if self.carry is not None else 0),
dsp.valid_in .eq(multin_valid & window_ready),
dsp.p_load .eq(counter_mult == 0),
self.output.p .eq(shape_out(dsp.o)),
self.output.valid .eq(dsp.valid_out & (counter_out == depth-1)),
]
# Multiplier input and output counters.
with m.If(dsp.valid_in):
m.d.sync += counter_mult.eq(_incr(counter_mult, depth))
with m.If(dsp.valid_out):
m.d.sync += counter_out.eq(_incr(counter_out, depth))
return m
class iCE40Multiplier(wiring.Component):
a: In(signed(16))
b: In(signed(16))
valid_in: In(1)
p: In(signed(32))
p_load: In(1)
o: Out(signed(32))
valid_out: Out(1)
def elaborate(self, platform):
m = Module()
def pipe(signal, length):
pipe = [ signal ] + [ Signal.like(signal, name=f"{signal.name}_q{i}") for i in range(length) ]
for i in range(length):
m.d.sync += pipe[i+1].eq(pipe[i])
return pipe
p_load_v = Signal()
dsp_delay = 3
valid_pipe = pipe(self.valid_in, dsp_delay)
m.d.comb += p_load_v.eq(self.p_load & self.valid_in)
p_pipe = pipe(self.p, dsp_delay-1)
p_load_pipe = pipe(p_load_v, dsp_delay - 1)
m.d.comb += self.valid_out.eq(valid_pipe[dsp_delay])
m.submodules.sb_mac16 = mac = SB_MAC16(
C_REG=0,
A_REG=1,
B_REG=1,
D_REG=0,
TOP_8x8_MULT_REG=0,
BOT_8x8_MULT_REG=0,
PIPELINE_16x16_MULT_REG1=0,
PIPELINE_16x16_MULT_REG2=1,
TOPOUTPUT_SELECT=1,
TOPADDSUB_LOWERINPUT=2,
TOPADDSUB_UPPERINPUT=1,
TOPADDSUB_CARRYSELECT=3,
BOTOUTPUT_SELECT=1,
BOTADDSUB_LOWERINPUT=2,
BOTADDSUB_UPPERINPUT=1,
BOTADDSUB_CARRYSELECT=0,
MODE_8x8=0,
A_SIGNED=1,
B_SIGNED=1,
)
m.d.comb += [
# Inputs.
mac.CLK .eq(ClockSignal("sync")),
mac.CE .eq(1),
mac.C .eq(Mux(p_load_pipe[2], p_pipe[2][16:], self.o[16:])),
mac.A .eq(self.a),
mac.B .eq(self.b),
mac.D .eq(Mux(p_load_pipe[2], p_pipe[2][:16], self.o[:16])),
mac.AHOLD .eq(~valid_pipe[0]), # 0: load
mac.BHOLD .eq(~valid_pipe[0]),
mac.CHOLD .eq(0),
mac.DHOLD .eq(0),
mac.OHOLDTOP .eq(~valid_pipe[2]),
mac.OHOLDBOT .eq(~valid_pipe[2]),
mac.ADDSUBTOP .eq(0),
mac.ADDSUBBOT .eq(0),
mac.OLOADTOP .eq(0),
mac.OLOADBOT .eq(0),
# Outputs.
self.o .eq(mac.O),
]
return m
def _incr(signal, modulo):
if modulo == 2 ** len(signal):
return signal + 1
else:
return Mux(signal == modulo - 1, 0, signal + 1)
#
# Tests
#
import unittest
import numpy as np
from amaranth.sim import Simulator
class _TestFilter(unittest.TestCase):
rng = np.random.default_rng(0)
def _generate_samples(self, count, width, f_width=0):
# Generate `count` random samples.
samples = self.rng.normal(0, 1, count)
# Convert to integer.
samples = np.round(samples / max(abs(samples)) * (2**(width-1) - 1)).astype(int)
assert max(samples) < 2**(width-1) and min(samples) >= -2**(width-1) # sanity check
if f_width > 0:
return samples / (1 << f_width)
return samples
def _filter(self, dut, samples, count, num_channels=1, outfile=None, empty_cycles=0):
async def input_process(ctx):
if hasattr(dut, "enable"):
ctx.set(dut.enable, 1)
await ctx.tick()
for i, sample in enumerate(samples):
if num_channels > 1:
ctx.set(dut.input.payload, [s.item() for s in sample])
else:
if isinstance(dut.input.payload.shape(), data.ArrayLayout):
ctx.set(dut.input.payload, [sample.item()])
else:
ctx.set(dut.input.payload, sample.item())
ctx.set(dut.input.valid, 1)
await ctx.tick().until(dut.input.ready)
ctx.set(dut.input.valid, 0)
if empty_cycles > 0:
await ctx.tick().repeat(empty_cycles)
filtered = []
async def output_process(ctx):
if not dut.output.signature.always_ready:
ctx.set(dut.output.ready, 1)
while len(filtered) < count:
payload, = await ctx.tick().sample(dut.output.payload).until(dut.output.valid)
if num_channels > 1:
filtered.append([v.as_float() for v in payload])
else:
if isinstance(payload.shape(), data.ArrayLayout):
filtered.append(payload[0].as_float())
else:
filtered.append(payload.as_float())
if not dut.output.signature.always_ready:
ctx.set(dut.output.ready, 0)
sim = Simulator(dut)
sim.add_clock(1/100e6)
sim.add_testbench(input_process)
sim.add_testbench(output_process)
if outfile:
with sim.write_vcd(outfile):
sim.run()
else:
sim.run()
return filtered
class TestFIRFilterMAC16(_TestFilter):
def test_filter_serial(self):
taps = [-1, 0, 9, 16, 9, 0, -1]
taps = [ tap / 32 for tap in taps ]
num_samples = 1024
input_width = 8
input_samples = self._generate_samples(num_samples, input_width)
# Compute the expected result
filtered_np = np.convolve(input_samples, taps).tolist()
# Simulate DUT
dut = FIRFilterSerialMAC16(taps, fixed.SQ(15, 0), always_ready=False)
filtered = self._filter(dut, input_samples, len(input_samples))
self.assertListEqual(filtered_np[:len(filtered)], filtered)
def test_filter(self):
taps = [-1, 0, 9, 16, 9, 0, -1]
taps = [ tap / 32 for tap in taps ]
num_samples = 1024
input_width = 8
input_samples = self._generate_samples(num_samples, input_width)
# Compute the expected result
filtered_np = np.convolve(input_samples, taps).tolist()
# Simulate DUT
dut = FIRFilterMAC16(taps, fixed.SQ(15, 0), always_ready=False)
filtered = self._filter(dut, input_samples, len(input_samples))
self.assertListEqual(filtered_np[:len(filtered)], filtered)
class TestHalfBandDecimatorMAC16(_TestFilter):
def test_filter(self):
common_dut_options = dict(
data_shape=fixed.SQ(7),
shape_out=fixed.SQ(0,31),
overclock_rate=4,
)
taps0 = (np.array([-1, 0, 9, 16, 9, 0, -1]) / 32).tolist()
taps1 = (np.array([-2, 0, 7, 0, -18, 0, 41, 0, -92, 0, 320, 512, 320, 0, -92, 0, 41, 0, -18, 0, 7, 0, -2]) / 1024).tolist()
inputs = {
"test_filter_with_backpressure": {
"num_samples": 1024,
"dut_options": dict(**common_dut_options, always_ready=False, taps=taps0),
"sim_opts": dict(empty_cycles=0),
},
"test_filter_with_backpressure_and_empty_cycles": {
"num_samples": 1024,
"dut_options": dict(**common_dut_options, always_ready=False, taps=taps0),
"sim_opts": dict(empty_cycles=3),
},
"test_filter_with_backpressure_taps1": {
"num_samples": 1024,
"dut_options": dict(**common_dut_options, always_ready=False, taps=taps1),
"sim_opts": dict(empty_cycles=0),
},
"test_filter_no_backpressure_and_empty_cycles_taps1": {
"num_samples": 1024,
"dut_options": dict(**common_dut_options, always_ready=True, taps=taps0),
"sim_opts": dict(empty_cycles=3),
},
"test_filter_no_backpressure": {
"num_samples": 1024,
"dut_options": dict(**common_dut_options, always_ready=True, taps=taps1),
"sim_opts": dict(empty_cycles=3),
},
}
for name, scenario in inputs.items():
with self.subTest(name):
taps = scenario["dut_options"]["taps"]
num_samples = scenario["num_samples"]
input_width = 8
samples_i_in = self._generate_samples(num_samples, input_width, f_width=7)
samples_q_in = self._generate_samples(num_samples, input_width, f_width=7)
# Compute the expected result
filtered_i_np = np.convolve(samples_i_in, taps)[1::2].tolist()
filtered_q_np = np.convolve(samples_q_in, taps)[1::2].tolist()
# Simulate DUT
dut = HalfBandDecimatorMAC16(**scenario["dut_options"])
filtered = self._filter(dut, zip(samples_i_in, samples_q_in), len(samples_i_in) // 2, num_channels=2, **scenario["sim_opts"])
filtered_i = [ x[0] for x in filtered ]
filtered_q = [ x[1] for x in filtered ]
self.assertListEqual(filtered_i_np[:len(filtered_i)], filtered_i)
self.assertListEqual(filtered_q_np[:len(filtered_q)], filtered_q)
class TestHalfBandInterpolatorMAC16(_TestFilter):
def test_filter(self):
common_dut_options = dict(
data_shape=fixed.SQ(7),
shape_out=fixed.SQ(1,16),
overclock_rate=4,
)
taps0 = (np.array([-1, 0, 9, 16, 9, 0, -1]) / 32).tolist()
taps1 = (np.array([-2, 0, 7, 0, -18, 0, 41, 0, -92, 0, 320, 512, 320, 0, -92, 0, 41, 0, -18, 0, 7, 0, -2]) / 1024).tolist()
inputs = {
"test_filter_with_backpressure": {
"num_samples": 1024,
"dut_options": dict(**common_dut_options, always_ready=False, num_channels=2, taps=taps0),
"sim_opts": dict(empty_cycles=0),
},
"test_filter_with_backpressure_and_empty_cycles": {
"num_samples": 1024,
"dut_options": dict(**common_dut_options, num_channels=2, always_ready=False, taps=taps0),
"sim_opts": dict(empty_cycles=3),
},
"test_filter_with_backpressure_taps1": {
"num_samples": 1024,
"dut_options": dict(**common_dut_options, num_channels=2, always_ready=False, taps=taps1),
"sim_opts": dict(empty_cycles=0),
},
"test_filter_no_backpressure_and_empty_cycles_taps1": {
"num_samples": 1024,
"dut_options": dict(**common_dut_options, num_channels=2, always_ready=True, taps=taps0),
"sim_opts": dict(empty_cycles=8),
},
"test_filter_no_backpressure": {
"num_samples": 1024,
"dut_options": dict(**common_dut_options, num_channels=2, always_ready=True, taps=taps1),
"sim_opts": dict(empty_cycles=16),
},
}
for name, scenario in inputs.items():
with self.subTest(name):
taps = scenario["dut_options"]["taps"]
num_samples = scenario["num_samples"]
input_width = 8
samples_i_in = self._generate_samples(num_samples, input_width, f_width=7)
samples_q_in = self._generate_samples(num_samples, input_width, f_width=7)
# Compute the expected result
input_samples_pad = np.zeros(2*len(samples_i_in))
input_samples_pad[0::2] = 2*samples_i_in # pad with zeros, adjust gain
filtered_i_np = np.convolve(input_samples_pad, taps).tolist()
input_samples_pad = np.zeros(2*len(samples_q_in))
input_samples_pad[0::2] = 2*samples_q_in # pad with zeros, adjust gain
filtered_q_np = np.convolve(input_samples_pad, taps).tolist()
# Simulate DUT
dut = HalfBandInterpolatorMAC16(**scenario["dut_options"])
filtered = self._filter(dut, zip(samples_i_in, samples_q_in), len(samples_i_in) * 2, num_channels=2, **scenario["sim_opts"])
filtered_i = [ x[0] for x in filtered ]
filtered_q = [ x[1] for x in filtered ]
self.assertListEqual(filtered_i_np[:len(filtered_i)], filtered_i)
self.assertListEqual(filtered_q_np[:len(filtered_q)], filtered_q)
if __name__ == "__main__":
unittest.main()

156
firmware/fpga/dsp/mcm.py Normal file
View File

@@ -0,0 +1,156 @@
#
# This file is part of HackRF.
#
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
from collections import defaultdict
from amaranth import Module, Signal, signed
from amaranth.lib import wiring, stream, data
from amaranth.lib.wiring import In, Out
from amaranth.utils import bits_for
class ShiftAddMCM(wiring.Component):
def __init__(self, width, terms, num_channels=1, always_ready=False):
self.terms = terms
self.width = width
self.num_channels = num_channels
super().__init__({
"input": In(stream.Signature(
data.ArrayLayout(signed(width), num_channels),
always_ready=always_ready)),
"output": Out(stream.Signature(
data.ArrayLayout(
data.StructLayout({
f"{i}": signed(width + bits_for(term)) for i, term in enumerate(terms)
}), num_channels), always_ready=always_ready)),
})
def elaborate(self, platform):
m = Module()
# Get unique, odd terms.
terms = self.terms
unique_terms = defaultdict(list)
for i, term in enumerate(terms):
if term == 0:
continue
term_odd, shift = make_odd(term)
unique_terms[term_odd] += [(i, shift)]
# Negated inputs for CSD.
input_neg = Signal.like(self.input.p)
for c in range(self.num_channels):
m.d.comb += input_neg[c].eq(-self.input.p[c])
with m.If(~self.output.valid | self.output.ready):
if not self.input.signature.always_ready:
m.d.comb += self.input.ready.eq(1)
m.d.sync += self.output.valid.eq(self.input.valid)
for term, outputs in unique_terms.items():
term_csd = to_csd(term)
for c in range(self.num_channels):
n = self.input.p[c]
n_neg = input_neg[c]
result = None
for s, t in enumerate(term_csd):
if t == 0:
continue
n_base = n if t == 1 else n_neg
shifted_n = n_base if s == 0 else (n_base << s)
if result is None:
result = shifted_n
else:
result += shifted_n
# A single register can feed multiple outputs.
result_q = Signal(signed(self.width+bits_for(term-1)), name=f"mul_{term}_{c}")
with m.If(self.input.ready & self.input.valid):
m.d.sync += result_q.eq(result)
for out_index, shift in outputs:
m.d.comb += self.output.p[c][f"{out_index}"].eq(result_q if shift == 0 else (result_q << shift))
return m
def make_odd(n):
"""Convert number to odd fundamental by right-shifting. Returns (odd_part, shift_amount)"""
if n == 0:
return 0, 0
shift = 0
while n % 2 == 0:
n = n >> 1
shift += 1
return n, shift
def multiply(n, k):
if k == 0:
return 0
csd_k = to_csd(k)
result = None
for i, c in enumerate(csd_k):
if c == 0:
continue
shifted_n = n if i == 0 else (n << i)
if result is None:
if c == 1:
result = shifted_n
elif c == -1:
result = -shifted_n
else:
if c == 1:
result += shifted_n
elif c == -1:
result -= shifted_n
return result[:bits_for(k-1)+len(n)].as_signed()
def to_csd(n):
""" Convert integer to Canonical Signed Digit representation (LSB first). """
if n == 0:
return [0]
sign = n < 0
n = abs(n)
binary = [ int(b) for b in f"{n:b}" ][::-1]
# Apply CSD conversion algorithm.
binary_padded = binary + [0]
carry = 0
csd = []
for i, bit in enumerate(binary_padded):
nextbit = binary_padded[i+1] if i+1 < len(binary_padded) else 0
d = bit ^ carry
ys = nextbit & d # sign bit
yd = ~nextbit & d # data bit
csd.append(yd - ys)
carry = (bit & nextbit) | ((bit|nextbit)&carry)
if sign:
csd = [-1*c for c in csd]
# Remove trailing zeros.
while len(csd) > 1 and csd[-1] == 0:
csd.pop()
# Regular binary representation is preferred if the number
# of additions was not improved.
if sum(binary) <= sum(abs(d) for d in csd) - sign:
if sign:
return [ -d for d in binary ]
return binary
return csd

103
firmware/fpga/dsp/nco.py Executable file
View File

@@ -0,0 +1,103 @@
#
# This file is part of HackRF.
#
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
from math import pi, sin, cos
from amaranth import Module, Signal, Mux, Cat
from amaranth.lib import wiring, memory
from amaranth.lib.wiring import In, Out
from util import IQSample
class NCO(wiring.Component):
"""
Retrieve cos(x), sin(x) using a look-up table.
Latency is 2 cycles.
We only precompute 1/8 of the (cos,sin) cycle, and the top 3 bits of the
phase are used to reconstruct the final values with symmetric properties.
Parameters
----------
phase_width : int
Bit width of the phase accumulator.
output_width : int
Bit width of the output cos/sin words.
Signals
-------
phase : Signal(phase_width), in
Input phase.
en : Signal(1), in
Enable strobe.
output : IQSample(output_width), out
Returned result for cos(phase), sin(phase).
"""
def __init__(self, phase_width=24, output_width=10):
self.phase_width = phase_width
self.output_width = output_width
super().__init__({
"phase": In(phase_width),
"en": In(1),
"output": Out(IQSample(output_width)),
})
def elaborate(self, platform):
m = Module()
# Create internal table with precomputed entries.
addr_width = (self.output_width + 1) - 3
lut_depth = 1 << addr_width
lut_scale = (1 << (self.output_width-1)) - 1
lut_phases = [ i * pi / 4 / lut_depth for i in range(lut_depth) ]
lut_data = memory.MemoryData(
shape=IQSample(self.output_width),
depth=lut_depth,
init=({"i": round(lut_scale * cos(x)), "q": round(lut_scale * sin(x))} for x in lut_phases)
)
m.submodules.table = table = memory.Memory(data=lut_data)
table_rd = table.read_port(domain="sync")
# 3 MSBs of the phase word: sign, quadrant, octant.
o, q, s = self.phase[-3:]
rev_addr = o
swap = Signal()
neg_cos = Signal()
neg_sin = Signal()
with m.If(self.en):
m.d.sync += [
swap .eq(q ^ o),
neg_cos .eq(s ^ q),
neg_sin .eq(s),
]
# Map phase to the [0,pi/4) range.
octant_phase = Signal(addr_width)
octant_mask = rev_addr.replicate(len(octant_phase)) # reverse mask
m.d.comb += octant_phase.eq(octant_mask ^ self.phase[-addr_width-3:-3])
# Retrieve precomputed (cos, sin) values from the reduced range.
e_s0 = Signal(IQSample(self.output_width))
m.d.comb += [
table_rd.addr.eq(octant_phase),
table_rd.en .eq(self.en),
e_s0 .eq(table_rd.data),
]
# Unmap the phase to its original octant.
e_s1 = Signal.like(e_s0)
e_s2 = Signal.like(e_s1)
m.d.comb += [
Cat(e_s1.i, e_s1.q) .eq(Mux(swap, Cat(e_s0.q, e_s0.i), e_s0)),
e_s2.i .eq(Mux(neg_cos, -e_s1.i, e_s1.i)),
e_s2.q .eq(Mux(neg_sin, -e_s1.q, e_s1.q)),
]
m.d.sync += self.output.eq(e_s2)
return m

View File

@@ -0,0 +1,55 @@
#
# This file is part of HackRF.
#
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
from amaranth import Module, Signal, Mux
from amaranth.lib import wiring, stream
from amaranth.lib.wiring import In, Out
from util import IQSample
class QuarterShift(wiring.Component):
input: In(stream.Signature(IQSample(8), always_ready=True))
output: Out(stream.Signature(IQSample(8), always_ready=True))
enable: In(1)
up: In(1)
def elaborate(self, platform):
m = Module()
index = Signal(range(4))
with m.If(~self.output.valid | self.output.ready):
if not self.input.signature.always_ready:
m.d.comb += self.input.ready.eq(1)
m.d.sync += self.output.valid.eq(self.input.valid)
with m.If(self.input.valid):
# Select direction of shift with the `up` signal.
with m.If(self.up):
m.d.sync += index.eq(index - 1)
with m.Else():
m.d.sync += index.eq(index + 1)
# Generate control signals derived from `index`.
swap = index[0]
inv_q = index[0] ^ index[1]
inv_i = index[1]
# First stage: swap.
i = Mux(swap, self.input.p.q, self.input.p.i)
q = Mux(swap, self.input.p.i, self.input.p.q)
# Second stage: sign inversion.
i = Mux(inv_i, -i, i)
q = Mux(inv_q, -q, q)
with m.If(self.enable):
m.d.sync += self.output.p.i.eq(i)
m.d.sync += self.output.p.q.eq(q)
with m.Else():
m.d.sync += self.output.p.i.eq(self.input.p.i)
m.d.sync += self.output.p.q.eq(self.input.p.q)
return m

View File

@@ -0,0 +1,17 @@
#
# This file is part of HackRF.
#
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
def convergent_round(value, discarded_bits):
retained = value[discarded_bits:]
discarded = value[:discarded_bits]
msb_discarded = discarded[-1]
rest_discarded = discarded[:-1]
lsb_retained = retained[0]
# Round up:
# - If discarded > 0.5
# - If discarded == 0.5 and retained is odd
round_up = msb_discarded & (rest_discarded.any() | lsb_retained)
return retained + round_up

View File

@@ -0,0 +1,343 @@
#
# This file is part of HackRF.
#
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
from amaranth import Module, Instance, Signal, Mux, Cat
from amaranth.lib import wiring
from amaranth.lib.wiring import In, Out
from amaranth.vendor import SiliconBluePlatform
class SB_MAC16(wiring.Component):
# Input ports
CLK: In(1)
CE: In(1)
C: In(16)
A: In(16)
B: In(16)
D: In(16)
AHOLD: In(1)
BHOLD: In(1)
CHOLD: In(1)
DHOLD: In(1)
IRSTTOP: In(1)
IRSTBOT: In(1)
ORSTTOP: In(1)
ORSTBOT: In(1)
OLOADTOP: In(1)
OLOADBOT: In(1)
ADDSUBTOP: In(1)
ADDSUBBOT: In(1)
OHOLDTOP: In(1)
OHOLDBOT: In(1)
CI: In(1)
ACCUMCI: In(1)
SIGNEXTIN: In(1)
# Output ports
O: Out(32)
CO: Out(1)
ACCUMCO: Out(1)
SIGNEXTOUT: Out(1)
def __init__(self,
NEG_TRIGGER=0,
C_REG=0,
A_REG=0,
B_REG=0,
D_REG=0,
TOP_8x8_MULT_REG=0,
BOT_8x8_MULT_REG=0,
PIPELINE_16x16_MULT_REG1=0,
PIPELINE_16x16_MULT_REG2=0,
TOPOUTPUT_SELECT=0,
TOPADDSUB_LOWERINPUT=0,
TOPADDSUB_UPPERINPUT=0,
TOPADDSUB_CARRYSELECT=0,
BOTOUTPUT_SELECT=0,
BOTADDSUB_LOWERINPUT=0,
BOTADDSUB_UPPERINPUT=0,
BOTADDSUB_CARRYSELECT=0,
MODE_8x8=0,
A_SIGNED=0,
B_SIGNED=0):
super().__init__()
# Parameters
self.parameters = dict(
NEG_TRIGGER=NEG_TRIGGER,
C_REG=C_REG,
A_REG=A_REG,
B_REG=B_REG,
D_REG=D_REG,
TOP_8x8_MULT_REG=TOP_8x8_MULT_REG,
BOT_8x8_MULT_REG=BOT_8x8_MULT_REG,
PIPELINE_16x16_MULT_REG1=PIPELINE_16x16_MULT_REG1,
PIPELINE_16x16_MULT_REG2=PIPELINE_16x16_MULT_REG2,
TOPOUTPUT_SELECT=TOPOUTPUT_SELECT,
TOPADDSUB_LOWERINPUT=TOPADDSUB_LOWERINPUT,
TOPADDSUB_UPPERINPUT=TOPADDSUB_UPPERINPUT,
TOPADDSUB_CARRYSELECT=TOPADDSUB_CARRYSELECT,
BOTOUTPUT_SELECT=BOTOUTPUT_SELECT,
BOTADDSUB_LOWERINPUT=BOTADDSUB_LOWERINPUT,
BOTADDSUB_UPPERINPUT=BOTADDSUB_UPPERINPUT,
BOTADDSUB_CARRYSELECT=BOTADDSUB_CARRYSELECT,
MODE_8x8=MODE_8x8,
A_SIGNED=A_SIGNED,
B_SIGNED=B_SIGNED,
)
def elaborate(self, platform):
if isinstance(platform, SiliconBluePlatform):
return self.elaborate_hard_macro()
else:
return self.elaborate_simulation()
def elaborate_hard_macro(self):
m = Module()
m.submodules.sb_mac16 = Instance("SB_MAC16",
# Parameters.
**{ f"p_{k}": v for k, v in self.parameters.items() },
# Inputs.
i_CLK=self.CLK,
i_CE=self.CE,
i_C=self.C,
i_A=self.A,
i_B=self.B,
i_D=self.D,
i_IRSTTOP=self.IRSTTOP,
i_IRSTBOT=self.IRSTBOT,
i_ORSTTOP=self.ORSTTOP,
i_ORSTBOT=self.ORSTBOT,
i_AHOLD=self.AHOLD,
i_BHOLD=self.BHOLD,
i_CHOLD=self.CHOLD,
i_DHOLD=self.DHOLD,
i_OHOLDTOP=self.OHOLDTOP,
i_OHOLDBOT=self.OHOLDBOT,
i_ADDSUBTOP=self.ADDSUBTOP,
i_ADDSUBBOT=self.ADDSUBBOT,
i_OLOADTOP=self.OLOADTOP,
i_OLOADBOT=self.OLOADBOT,
i_CI=self.CI,
i_ACCUMCI=self.ACCUMCI,
i_SIGNEXTIN=self.SIGNEXTIN,
# Outputs.
o_O=self.O,
o_CO=self.CO,
o_ACCUMCO=self.ACCUMCO,
o_SIGNEXTOUT=self.SIGNEXTOUT,
)
return m
def elaborate_simulation(self):
m = Module()
p = self.parameters
assert p["NEG_TRIGGER"] == 0, "Falling edge input clock polarity not supported in simulation."
# Internal wire, compare Figure on page 133 of ICE Technology Library 3.0 and Fig 2 on page 2 of Lattice TN1295-DSP
# http://www.latticesemi.com/~/media/LatticeSemi/Documents/TechnicalBriefs/SBTICETechnologyLibrary201608.pdf
# https://www.latticesemi.com/-/media/LatticeSemi/Documents/ApplicationNotes/AD/DSPFunctionUsageGuideforICE40Devices.ashx
iA = Signal(16)
iB = Signal(16)
iC = Signal(16)
iD = Signal(16)
iF = Signal(16)
iJ = Signal(16)
iK = Signal(16)
iG = Signal(16)
iL = Signal(32)
iH = Signal(32)
iW = Signal(16)
iX = Signal(16)
iP = Signal(16)
iQ = Signal(16)
iY = Signal(16)
iZ = Signal(16)
iR = Signal(16)
iS = Signal(16)
HCI = Signal()
LCI = Signal()
LCO = Signal()
# Registers
rC = Signal(16)
rA = Signal(16)
rB = Signal(16)
rD = Signal(16)
rF = Signal(16)
rJ = Signal(16)
rK = Signal(16)
rG = Signal(16)
rH = Signal(32)
rQ = Signal(16)
rS = Signal(16)
# Regs C and A
with m.If(self.IRSTTOP):
m.d.sync += [
rC.eq(0),
rA.eq(0),
]
with m.Elif(self.CE):
with m.If(~self.CHOLD):
m.d.sync += rC.eq(self.C)
with m.If(~self.AHOLD):
m.d.sync += rA.eq(self.A)
m.d.comb += [
iC.eq(rC if p["C_REG"] else self.C),
iA.eq(rA if p["A_REG"] else self.A),
]
# Regs B and D
with m.If(self.IRSTBOT):
m.d.sync += [
rB.eq(0),
rD.eq(0)
]
with m.Elif(self.CE):
with m.If(~self.BHOLD):
m.d.sync += rB.eq(self.B)
with m.If(~self.DHOLD):
m.d.sync += rD.eq(self.D)
m.d.comb += [
iB.eq(rB if p["B_REG"] else self.B),
iD.eq(rD if p["D_REG"] else self.D),
]
# Multiplier Stage
p_Ah_Bh = Signal(16)
p_Al_Bh = Signal(16)
p_Ah_Bl = Signal(16)
p_Al_Bl = Signal(16)
Ah = Signal(16)
Al = Signal(16)
Bh = Signal(16)
Bl = Signal(16)
m.d.comb += [
Ah.eq(Cat(iA[8:16], Mux(p["A_SIGNED"], iA[15].replicate(8), 0))),
Al.eq(Cat(iA[0:8], Mux(p["A_SIGNED"] & p["MODE_8x8"], iA[7].replicate(8), 0))),
Bh.eq(Cat(iB[8:16], Mux(p["B_SIGNED"], iB[15].replicate(8), 0))),
Bl.eq(Cat(iB[0:8], Mux(p["B_SIGNED"] & p["MODE_8x8"], iB[7].replicate(8), 0))),
p_Ah_Bh.eq(Ah * Bh), # F
p_Al_Bh.eq(Al[0:8] * Bh), # J
p_Ah_Bl.eq(Ah * Bl[0:8]), # K
p_Al_Bl.eq(Al * Bl), # G
]
# Regs F and J
with m.If(self.IRSTTOP):
m.d.sync += [
rF.eq(0),
rJ.eq(0)
]
with m.Elif(self.CE):
m.d.sync += rF.eq(p_Ah_Bh)
if not p["MODE_8x8"]:
m.d.sync += rJ.eq(p_Al_Bh)
m.d.comb += [
iF.eq(rF if p["TOP_8x8_MULT_REG"] else p_Ah_Bh),
iJ.eq(rJ if p["PIPELINE_16x16_MULT_REG1"] else p_Al_Bh),
]
# Regs K and G
with m.If(self.IRSTBOT):
m.d.sync += [
rK.eq(0),
rG.eq(0)
]
with m.Elif(self.CE):
with m.If(~p["MODE_8x8"]):
m.d.sync += rK.eq(p_Ah_Bl)
m.d.sync += rG.eq(p_Al_Bl)
m.d.comb += [
iK.eq(rK if p["PIPELINE_16x16_MULT_REG1"] else p_Ah_Bl),
iG.eq(rG if p["BOT_8x8_MULT_REG"] else p_Al_Bl),
]
# Adder Stage
iK_e = Signal(24)
iJ_e = Signal(24)
m.d.comb += [
iK_e.eq(Cat(iK, Mux(p["A_SIGNED"], iK[15].replicate(8), 0))),
iJ_e.eq(Cat(iJ, Mux(p["B_SIGNED"], iJ[15].replicate(8), 0))),
iL.eq(iG + (iK_e << 8) + (iJ_e << 8) + (iF << 16)),
]
# Reg H
with m.If(self.IRSTBOT):
m.d.sync += rH.eq(0)
with m.Elif(self.CE):
if not p["MODE_8x8"]:
m.d.sync += rH.eq(iL)
m.d.comb += iH.eq(rH if p["PIPELINE_16x16_MULT_REG2"] else iL)
# Hi Output Stage
XW = Signal(17)
Oh = Signal(16)
m.d.comb += [
iW.eq([iQ, iC][p["TOPADDSUB_UPPERINPUT"]]),
iX.eq([iA, iF, iH[16:32], iZ[15].replicate(16)][p["TOPADDSUB_LOWERINPUT"]]),
XW.eq(iX + (iW ^ self.ADDSUBTOP.replicate(16)) + HCI),
self.ACCUMCO.eq(XW[16]),
self.CO.eq(self.ACCUMCO ^ self.ADDSUBTOP),
iP.eq(Mux(self.OLOADTOP, iC, XW[0:16] ^ self.ADDSUBTOP.replicate(16))),
]
with m.If(self.ORSTTOP):
m.d.sync += rQ.eq(0)
with m.Elif(self.CE):
with m.If(~self.OHOLDTOP):
m.d.sync += rQ.eq(iP)
m.d.comb += [
iQ.eq(rQ),
Oh.eq([iP, iQ, iF, iH[16:32]][p["TOPOUTPUT_SELECT"]]),
HCI.eq([0, 1, LCO, LCO ^ self.ADDSUBBOT][p["TOPADDSUB_CARRYSELECT"]]),
self.SIGNEXTOUT.eq(iX[15]),
]
# Lo Output Stage
YZ = Signal(17)
Ol = Signal(16)
m.d.comb += [
iY.eq([iS, iD][p["BOTADDSUB_UPPERINPUT"]]),
iZ.eq([iB, iG, iH[0:16], self.SIGNEXTIN.replicate(16)][p["BOTADDSUB_LOWERINPUT"]]),
YZ.eq(iZ + (iY ^ self.ADDSUBBOT.replicate(16)) + LCI),
LCO.eq(YZ[16]),
iR.eq(Mux(self.OLOADBOT, iD, YZ[0:16] ^ self.ADDSUBBOT.replicate(16))),
]
with m.If(self.ORSTBOT):
m.d.sync += rS.eq(0)
with m.Elif(self.CE):
with m.If(~self.OHOLDBOT):
m.d.sync += rS.eq(iR)
m.d.comb += [
iS.eq(rS),
Ol.eq([iR, iS, iG, iH[0:16]][p["BOTOUTPUT_SELECT"]]),
LCI.eq([0, 1, self.ACCUMCI, self.CI][p["BOTADDSUB_CARRYSELECT"]]),
self.O.eq(Cat(Ol, Oh)),
]
return m

View File

@@ -0,0 +1 @@
from .max586x import MAX586xInterface

View File

@@ -0,0 +1,66 @@
# This file is part of HackRF.
#
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
from amaranth import Module, Signal, C, Cat
from amaranth.lib import io, stream, wiring
from amaranth.lib.wiring import Out, In
from util import IQSample
class MAX586xInterface(wiring.Component):
adc_stream: Out(stream.Signature(IQSample(8), always_ready=True))
dac_stream: In(stream.Signature(IQSample(8), always_ready=True))
adc_capture: In(1)
dac_capture: In(1)
q_invert: In(1)
def __init__(self, bb_domain):
super().__init__()
self._bb_domain = bb_domain
def elaborate(self, platform):
m = Module()
adc_stream = self.adc_stream
dac_stream = self.dac_stream
# Generate masks for inverting the Q component based on the q_invert signal.
q_invert = Signal()
rx_q_mask = Signal(8)
tx_q_mask = Signal(10)
m.d[self._bb_domain] += q_invert.eq(self.q_invert)
with m.If(q_invert):
m.d.comb += [
rx_q_mask.eq(0x80),
tx_q_mask.eq(0x1FF),
]
with m.Else():
m.d.comb += [
rx_q_mask.eq(0x7F),
tx_q_mask.eq(0x200),
]
# Capture the ADC signals using a DDR input buffer.
m.submodules.adc_in = adc_in = io.DDRBuffer("i", platform.request("da", dir="-"), i_domain=self._bb_domain)
m.d.comb += [
adc_stream.p.i .eq(adc_in.i[0] ^ 0x80), # I: non-inverted between MAX2837 and MAX5864.
adc_stream.p.q .eq(adc_in.i[1] ^ rx_q_mask), # Q: inverted between MAX2837 and MAX5864.
adc_stream.valid .eq(self.adc_capture),
]
# Output the transformed data to the DAC using a DDR output buffer.
m.submodules.dac_out = dac_out = io.DDRBuffer("o", platform.request("dd", dir="-"), o_domain=self._bb_domain)
with m.If(dac_stream.valid):
m.d.comb += [
dac_out.o[0] .eq(Cat(C(0, 2), dac_stream.p.i) ^ 0x200),
dac_out.o[1] .eq(Cat(C(0, 2), dac_stream.p.q) ^ tx_q_mask),
]
with m.Else():
m.d.comb += [
dac_out.o[0] .eq(0x200),
dac_out.o[1] .eq(0x200),
]
return m

View File

@@ -0,0 +1,424 @@
#
# This file is part of HackRF.
#
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
from amaranth import Elaboratable, Module, Instance, Signal, ClockSignal
# References:
# [1] LATTICE ICE™ Technology Library, Version 3.0, August, 2016
# [2] iCE40™ LP/HX/LM Family Handbook, HB1011 Version 01.2, November 2013
class SPIDeviceInterface(Elaboratable):
def __init__(self, port):
# I/O port.
self.port = port
# Data I/O.
self.word_in = Signal(8)
self.word_out = Signal(8)
self.word_in_stb = Signal()
# Status flags.
self.busy = Signal()
def elaborate(self, platform):
m = Module()
spi_adr = Signal(8, init=0b1000) # address
spi_dati = Signal(8) # data input
spi_dato = Signal(8) # data output
spi_rw = Signal() # selects between read or write (high = write)
spi_stb = Signal() # strobe must be asserted to start a read/write
spi_ack = Signal() # ack that the transfer is done (read valid, write ack)
# SB_SPI interface is documented in [1].
sb_spi_params = {
# SPI port connections.
"o_SO": self.port.cipo.o,
"o_SOE": self.port.cipo.oe,
"i_SI": self.port.copi.i,
"i_SCKI": self.port.clk.i,
"i_SCSNI": ~self.port.cs.i, # chip select is inverted due to PinsN
# Internal signaling.
"i_SBCLKI": ClockSignal("sync"),
"i_SBSTBI": spi_stb,
"i_SBRWI": spi_rw,
"o_SBACKO": spi_ack,
}
sb_spi_params |= { f"i_SBADRI{i}": spi_adr[i] for i in range(8) }
sb_spi_params |= { f"i_SBDATI{i}": spi_dati[i] for i in range(8) }
sb_spi_params |= { f"o_SBDATO{i}": spi_dato[i] for i in range(8) }
m.submodules.sb_spi = sb_spi = Instance("SB_SPI", **sb_spi_params)
# Register addresses (from [2]).
SPI_ADDR_SPICR0 = 0b1000 # SPI Control Register 0
SPI_ADDR_SPICR1 = 0b1001 # SPI Control Register 1
SPI_ADDR_SPICR2 = 0b1010 # SPI Control Register 2
SPI_ADDR_SPIBR = 0b1011 # SPI Clock Prescale
SPI_ADDR_SPISR = 0b1100 # SPI Status Register
SPI_ADDR_SPITXDR = 0b1101 # SPI Transmit Data Register
SPI_ADDR_SPIRXDR = 0b1110 # SPI Receive Data Register
SPI_ADDR_SPICSR = 0b1111 # SPI Master Chip Select Register
# Initial values for programming registers ([2]).
registers_init = {
SPI_ADDR_SPICR2: 0b00000110, # CPOL=1 CPHA=1 mode, MSB first
SPI_ADDR_SPICR1: 0b10000000, # Enable SPI
}
# De-assert strobe signals unless explicitly asserted.
m.d.sync += spi_stb.eq(0)
m.d.sync += self.word_in_stb.eq(0)
with m.FSM():
# Register initialization.
for i, (address, value) in enumerate(registers_init.items()):
with m.State(f"INIT{i}"):
m.d.sync += [
spi_adr .eq(address),
spi_dati .eq(value),
spi_stb .eq(1),
spi_rw .eq(1),
]
with m.If(spi_ack):
m.d.sync += spi_stb.eq(0)
if i+1 < len(registers_init):
m.next = f"INIT{i+1}"
else:
m.next = "WAIT"
with m.State("WAIT"):
m.d.sync += [
spi_adr .eq(SPI_ADDR_SPISR),
spi_stb .eq(1),
spi_rw .eq(0),
]
with m.If(spi_ack):
m.d.sync += spi_stb.eq(0)
# bit 3 = RRDY, data is available to read
# bit 4 = TRDY, transmit data is empty
# bit 6 = BUSY, chip select is asserted (low)
# bit 7 = TIP, transfer in progress
m.d.sync += self.busy.eq(spi_dato[6])
with m.If(spi_dato[7] & spi_dato[4]):
m.next = "SPI_TRANSMIT"
with m.Elif(spi_dato[3]):
m.next = "SPI_READ"
with m.State("SPI_READ"):
m.d.sync += [
spi_adr .eq(SPI_ADDR_SPIRXDR),
spi_stb .eq(1),
spi_rw .eq(0),
]
with m.If(spi_ack):
m.d.sync += [
spi_stb .eq(0),
self.word_in .eq(spi_dato),
self.word_in_stb .eq(1),
]
m.next = "WAIT"
with m.State("SPI_TRANSMIT"):
m.d.sync += [
spi_adr .eq(SPI_ADDR_SPITXDR),
spi_dati .eq(self.word_out),
spi_stb .eq(1),
spi_rw .eq(1),
]
with m.If(spi_ack):
m.d.sync += spi_stb.eq(0)
m.next = "WAIT"
return m
class SPICommandInterface(Elaboratable):
""" Wrapper of SPIDeviceInterface that splits data sequences into phases.
I/O signals:
O: command -- the command read from the SPI bus
O: command_ready -- a new command is ready
O: word_received -- the most recent word received
O: word_complete -- strobe indicating a new word is present on word_in
I: word_to_send -- the word to be loaded; latched in on next word_complete and while cs is low
"""
def __init__(self, port):
# I/O port.
self.interface = SPIDeviceInterface(port)
# Command I/O.
self.command = Signal(8)
self.command_ready = Signal()
# Data I/O
self.word_received = Signal(8)
self.word_to_send = Signal.like(self.word_received)
self.word_complete = Signal()
def elaborate(self, platform):
m = Module()
# Attach our SPI interface.
m.submodules.interface = interface = self.interface
# De-assert our control signals unless explicitly asserted.
m.d.sync += [
self.command_ready.eq(0),
self.word_complete.eq(0)
]
m.d.comb += interface.word_out.eq(self.word_to_send)
with m.FSM():
with m.State("COMMAND_PHASE"):
with m.If(interface.word_in_stb):
m.d.sync += [
self.command .eq(interface.word_in),
self.command_ready .eq(1),
]
m.next = "DATA_PHASE"
# Do not advance if chip select is deasserted.
with m.If(~interface.busy):
m.next = "COMMAND_PHASE"
with m.State("DATA_PHASE"):
with m.If(interface.word_in_stb):
m.d.sync += self.word_received.eq(interface.word_in)
m.d.sync += self.word_complete.eq(1)
m.next = "DUMMY_PHASE"
# Do not advance if chip select is deasserted.
with m.If(~interface.busy):
m.next = "COMMAND_PHASE"
# The SB_SPI block always returns 0xFF for the second byte, so at least one
# dummy byte must be added to retrieve valid data. This behavior is shown in
# Figure 22-16, "Minimally Specified SPI Transaction Example," from [2].
with m.State("DUMMY_PHASE"):
with m.If(~interface.busy):
m.next = "COMMAND_PHASE"
return m
class SPIRegisterInterface(Elaboratable):
""" SPI device interface that allows for register reads and writes via SPI.
The SPI transaction format matches:
in: WAAAAAAA[...] VVVVVVVV[...] DDDDDDDD[...]
out: XXXXXXXX[...] XXXXXXXX[...] RRRRRRRR[...]
Where:
W = write bit; a '1' indicates that the provided value is a write request
A = all bits of the address
V = value to be written into the register, if W is set
R = value to be read from the register
Other I/O ports are added dynamically with add_register().
"""
def __init__(self, port):
"""
Parameters:
address_size -- the size of an address, in bits; recommended to be one bit
less than a binary number, as the write command is formed by adding a one-bit
write flag to the start of every address
register_size -- The size of any given register, in bits.
"""
self.address_size = 7
self.register_size = 8
#
# Internal details.
#
# Instantiate an SPI command transciever submodule.
self.interface = SPICommandInterface(port)
# Create a new, empty dictionary mapping registers to their signals.
self.registers = {}
# Create signals for each of our register control signals.
self._is_write = Signal()
self._address = Signal(self.address_size)
def _ensure_register_is_unused(self, address):
""" Checks to make sure a register address isn't in use before issuing it. """
if address in self.registers:
raise ValueError("can't add more than one register with address 0x{:x}!".format(address))
def add_sfr(self, address, *, read=None, write_signal=None, write_strobe=None, read_strobe=None):
""" Adds a special function register to the given command interface.
Parameters:
address -- the register's address, as a big-endian integer
read -- a Signal or integer constant representing the
value to be read at the given address; if not provided, the default
value will be read
read_strobe -- a Signal that is asserted when a read is completed; if not provided,
the relevant strobe will be left unconnected
write_signal -- a Signal set to the value to be written when a write is requested;
if not provided, writes will be ignored
write_strobe -- a Signal that goes high when a value is available for a write request
"""
assert address < (2 ** self.address_size)
self._ensure_register_is_unused(address)
# Add the register to our collection.
self.registers[address] = {
'read': read,
'write_signal': write_signal,
'write_strobe': write_strobe,
'read_strobe': read_strobe,
'elaborate': None,
}
def add_read_only_register(self, address, *, read, read_strobe=None):
""" Adds a read-only register.
Parameters:
address -- the register's address, as a big-endian integer
read -- a Signal or integer constant representing the
value to be read at the given address; if not provided, the default
value will be read
read_strobe -- a Signal that is asserted when a read is completed; if not provided,
the relevant strobe will be left unconnected
"""
self.add_sfr(address, read=read, read_strobe=read_strobe)
def add_register(self, address, *, value_signal=None, size=None, name=None, read_strobe=None,
write_strobe=None, init=0):
""" Adds a standard, memory-backed register.
Parameters:
address -- the register's address, as a big-endian integer
value_signal -- the signal that will store the register's value; if omitted
a storage register will be created automatically
size -- if value_signal isn't provided, this sets the size of the created register
init -- if value_signal isn't provided, this sets the reset value of the created register
read_strobe -- a Signal to be asserted when the register is read; ignored if not provided
write_strobe -- a Signal to be asserted when the register is written; ignored if not provided
Returns:
value_signal -- a signal that stores the register's value; which may be the value_signal arg,
or may be a signal created during execution
"""
self._ensure_register_is_unused(address)
# Generate a name for the register, if we don't already have one.
name = name if name else "register_{:x}".format(address)
# Generate a backing store for the register, if we don't already have one.
if value_signal is None:
size = self.register_size if (size is None) else size
value_signal = Signal(size, name=name, init=init)
# If we don't have a write strobe signal, create an internal one.
if write_strobe is None:
write_strobe = Signal(name=name + "_write_strobe")
# Create our register-value-input and our write strobe.
write_value = Signal.like(value_signal, name=name + "_write_value")
# Create a generator for a the fragments that will manage the register's memory.
def _elaborate_memory_register(m):
with m.If(write_strobe):
m.d.sync += value_signal.eq(write_value)
# Add the register to our collection.
self.registers[address] = {
'read': value_signal,
'write_signal': write_value,
'write_strobe': write_strobe,
'read_strobe': read_strobe,
'elaborate': _elaborate_memory_register,
}
return value_signal
def _elaborate_register(self, m, register_address, connections):
""" Generates the hardware connections that handle a given register. """
#
# Elaborate our register hardware.
#
# Create a signal that goes high iff the given register is selected.
register_selected = Signal(name="register_address_matches_{:x}".format(register_address))
m.d.comb += register_selected.eq(self._address == register_address)
# Our write signal is always connected to word_received; but it's only meaningful
# when write_strobe is asserted.
if connections['write_signal'] is not None:
m.d.comb += connections['write_signal'].eq(self.interface.word_received)
# If we have a write strobe, assert it iff:
# - this register is selected
# - the relevant command is a write command
# - we've just finished receiving the command's argument
if connections['write_strobe'] is not None:
m.d.comb += [
connections['write_strobe'].eq(self._is_write & self.interface.word_complete & register_selected)
]
# Create essentially the same connection with the read strobe.
if connections['read_strobe'] is not None:
m.d.comb += [
connections['read_strobe'].eq(~self._is_write & self.interface.word_complete & register_selected)
]
# If we have any additional code that assists in elaborating this register, run it.
if connections['elaborate']:
connections['elaborate'](m)
def elaborate(self, platform):
m = Module()
# Attach our SPI interface.
m.submodules.interface = self.interface
# Split the command into our "write" and "address" signals.
m.d.comb += [
self._is_write.eq(self.interface.command[-1]),
self._address .eq(self.interface.command[:-1])
]
# Create the control/write logic for each of our registers.
for address, connections in self.registers.items():
self._elaborate_register(m, address, connections)
# Build the logic to select the 'to_send' value, which is selected
# from all of our registers according to the selected register address.
with m.Switch(self._address):
for address, connections in self.registers.items():
if connections['read'] is not None:
with m.Case(address):
# Hook up the word-to-send signal to the read value for the relevant
# register.
m.d.comb += self.interface.word_to_send.eq(connections['read'])
return m

View File

@@ -0,0 +1,3 @@
amaranth==v0.5.8
amaranth-boards @ git+https://github.com/amaranth-lang/amaranth-boards.git@23c66d6
lz4

View File

@@ -0,0 +1,215 @@
#
# This file is part of HackRF.
#
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
from amaranth import Elaboratable, Module, Signal, Mux, Instance, Cat, ClockSignal, DomainRenamer
from amaranth.lib import io, fifo, stream, wiring
from amaranth.lib.wiring import Out, In, connect
from amaranth_future import fixed
from board import PralinePlatform, ClockDomainGenerator
from interface import MAX586xInterface
from interface.spi import SPIRegisterInterface
from dsp.fir import FIRFilter
from dsp.fir_mac16 import HalfBandDecimatorMAC16
from dsp.cic import CICDecimator
from dsp.dc_block import DCBlock
from dsp.quarter_shift import QuarterShift
from util import ClockConverter, IQSample
class MCUInterface(wiring.Component):
adc_stream: In(stream.Signature(IQSample(12), always_ready=True))
direction: In(1)
enable: In(1)
def __init__(self, domain="sync"):
self._domain = domain
super().__init__()
def elaborate(self, platform):
m = Module()
adc_stream = self.adc_stream
# Determine data transfer direction.
direction = Signal()
enable = Signal()
m.d.sync += enable.eq(self.enable)
m.d.sync += direction.eq(self.direction)
transfer_from_adc = (direction == 0)
# SGPIO clock and data lines.
m.submodules.clk_out = clk_out = io.DDRBuffer("o", platform.request("host_clk", dir="-"), o_domain=self._domain)
m.submodules.host_io = host_io = io.DDRBuffer('io', platform.request("host_data", dir="-"), i_domain=self._domain, o_domain=self._domain)
# State machine to control SGPIO clock and data lines.
rx_clk_en = Signal()
m.d.sync += clk_out.o[1].eq(rx_clk_en)
m.d.sync += host_io.oe.eq(transfer_from_adc)
data_to_host = Signal.like(adc_stream.p)
rx_data_buffer = Signal(8)
m.d.comb += host_io.o[0].eq(rx_data_buffer)
m.d.comb += host_io.o[1].eq(rx_data_buffer)
with m.FSM():
with m.State("IDLE"):
m.d.comb += rx_clk_en.eq(enable & transfer_from_adc & adc_stream.valid)
with m.If(rx_clk_en):
m.d.sync += rx_data_buffer.eq(adc_stream.p.i >> 8)
m.d.sync += data_to_host.eq(adc_stream.p)
m.next = "RX_I1"
with m.State("RX_I1"):
m.d.comb += rx_clk_en.eq(1)
m.d.sync += rx_data_buffer.eq(data_to_host.i)
m.next = "RX_Q0"
with m.State("RX_Q0"):
m.d.comb += rx_clk_en.eq(1)
m.d.sync += rx_data_buffer.eq(data_to_host.q >> 8)
m.next = "RX_Q1"
with m.State("RX_Q1"):
m.d.comb += rx_clk_en.eq(1)
m.d.sync += rx_data_buffer.eq(data_to_host.q)
m.next = "IDLE"
if self._domain != "sync":
m = DomainRenamer(self._domain)(m)
return m
class FlowAndTriggerControl(wiring.Component):
trigger_en: In(1)
direction: Out(1) # async
enable: Out(1) # async
adc_capture: Out(1)
dac_capture: Out(1)
def __init__(self, domain):
super().__init__()
self._domain = domain
def elaborate(self, platform):
m = Module()
#
# Signal synchronization and trigger logic.
#
trigger_enable = self.trigger_en
trigger_in = platform.request("trigger_in").i
trigger_out = platform.request("trigger_out").o
host_data_enable = ~platform.request("disable").i
m.d.comb += trigger_out.eq(host_data_enable)
# Create a latch for the trigger input signal using a special FPGA primitive.
trigger_in_latched = Signal()
trigger_in_reg = Instance("SB_DFFES",
i_D = 0,
i_S = trigger_in, # async set
i_E = ~host_data_enable,
i_C = ClockSignal(self._domain),
o_Q = trigger_in_latched
)
m.submodules.trigger_in_reg = trigger_in_reg
# Export signals for direction control and capture gating.
m.d.comb += self.direction.eq(platform.request("direction").i)
m.d.comb += self.enable.eq(host_data_enable)
with m.If(host_data_enable):
m.d[self._domain] += self.adc_capture.eq((trigger_in_latched | ~trigger_enable) & (self.direction == 0))
m.d[self._domain] += self.dac_capture.eq((trigger_in_latched | ~trigger_enable) & (self.direction == 1))
with m.Else():
m.d[self._domain] += self.adc_capture.eq(0)
m.d[self._domain] += self.dac_capture.eq(0)
return m
class Top(Elaboratable):
def elaborate(self, platform):
m = Module()
m.submodules.clkgen = ClockDomainGenerator()
# Submodules.
m.submodules.flow_ctl = flow_ctl = FlowAndTriggerControl(domain="gck1")
m.submodules.adcdac_intf = adcdac_intf = MAX586xInterface(bb_domain="gck1")
m.submodules.mcu_intf = mcu_intf = MCUInterface(domain="sync")
m.d.comb += adcdac_intf.adc_capture.eq(flow_ctl.adc_capture)
m.d.comb += adcdac_intf.dac_capture.eq(flow_ctl.dac_capture)
m.d.comb += adcdac_intf.q_invert.eq(platform.request("q_invert").i)
m.d.comb += mcu_intf.direction.eq(flow_ctl.direction)
m.d.comb += mcu_intf.enable.eq(flow_ctl.enable)
# Half-band filter taps.
taps_hb1 = [-2, 0, 5, 0, -10, 0,18, 0, -30, 0,53, 0,-101, 0, 323, 512, 323, 0,-101, 0, 53, 0, -30, 0,18, 0, -10, 0, 5, 0,-2]
taps_hb1 = [ tap/1024 for tap in taps_hb1 ]
taps_hb2 = [ -6, 0, 19, 0, -44, 0, 89, 0, -163, 0, 278, 0, -452, 0, 711, 0, -1113, 0, 1800, 0, -3298, 0, 10370, 16384, 10370, 0, -3298, 0, 1800, 0, -1113, 0, 711, 0, -452, 0, 278, 0, -163, 0, 89, 0, -44, 0, 19, 0, -6]
taps_hb2 = [ tap/16384/2 for tap in taps_hb2 ]
rx_chain = {
# DC block and quarter shift.
"dc_block": DCBlock(width=8, num_channels=2, domain="gck1"),
"quarter_shift": DomainRenamer("gck1")(QuarterShift()),
# CIC mandatory first stage with compensator.
"cic": CICDecimator(2, 4, (4,8,16,32), width_in=8, width_out=12, num_channels=2, always_ready=True, domain="gck1"),
"cic_comp": DomainRenamer("gck1")(FIRFilter([-0.125, 0, 0.75, 0, -0.125], shape=fixed.SQ(11), shape_out=fixed.SQ(11), always_ready=True, num_channels=2)),
# Final half-band decimator stages.
"hbfir1": HalfBandDecimatorMAC16(taps_hb1, data_shape=fixed.SQ(11), overclock_rate=4, always_ready=True, domain="gck1"),
"hbfir2": HalfBandDecimatorMAC16(taps_hb2, data_shape=fixed.SQ(11), overclock_rate=8, always_ready=True, domain="gck1"),
# Clock domain conversion.
"clkconv": ClockConverter(IQSample(12), 4, "gck1", "sync", always_ready=True),
}
for k,v in rx_chain.items():
m.submodules[f"rx_{k}"] = v
# Connect receiver chain.
last = adcdac_intf.adc_stream
for block in rx_chain.values():
connect(m, last, block.input)
last = block.output
connect(m, last, mcu_intf.adc_stream)
# SPI register interface.
spi_port = platform.request("spi")
m.submodules.spi_regs = spi_regs = SPIRegisterInterface(spi_port)
# Add control registers.
ctrl = spi_regs.add_register(0x01, init=0)
rx_decim = spi_regs.add_register(0x02, init=0, size=3)
#tx_intrp = spi_regs.add_register(0x04, init=0, size=3)
m.d.comb += [
# Trigger enable.
flow_ctl.trigger_en .eq(ctrl[7]),
# RX settings.
rx_chain["dc_block"].enable .eq(ctrl[0]),
rx_chain["quarter_shift"].enable .eq(ctrl[1]),
rx_chain["quarter_shift"].up .eq(ctrl[2]),
# RX decimation rate.
rx_chain["cic"].factor .eq(rx_decim+2),
]
return m
if __name__ == "__main__":
plat = PralinePlatform()
plat.build(Top())

View File

@@ -0,0 +1,215 @@
#
# This file is part of HackRF.
#
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
from amaranth import Elaboratable, Module, Signal, Instance, Cat, ClockSignal, DomainRenamer
from amaranth.lib import io, fifo, stream, wiring
from amaranth.lib.wiring import Out, In, connect
from amaranth_future import fixed
from board import PralinePlatform, ClockDomainGenerator
from interface import MAX586xInterface
from interface.spi import SPIRegisterInterface
from dsp.fir import FIRFilter
from dsp.fir_mac16 import HalfBandInterpolatorMAC16
from dsp.cic import CICInterpolator
from util import ClockConverter, IQSample, StreamSkidBuffer
class MCUInterface(wiring.Component):
dac_stream: Out(stream.Signature(IQSample(12)))
direction: In(1)
enable: In(1)
def __init__(self, domain="sync"):
self._domain = domain
super().__init__()
def elaborate(self, platform):
m = Module()
dac_stream = self.dac_stream
# Determine data transfer direction.
direction = Signal()
enable = Signal()
m.d.sync += enable.eq(self.enable)
m.d.sync += direction.eq(self.direction)
transfer_to_dac = (direction == 1)
# SGPIO clock and data lines.
m.submodules.clk_out = clk_out = io.DDRBuffer("o", platform.request("host_clk", dir="-"), o_domain=self._domain)
m.submodules.host_io = host_io = io.DDRBuffer('io', platform.request("host_data", dir="-"), i_domain=self._domain, o_domain=self._domain)
# State machine to control SGPIO clock and data lines.
tx_clk_en = Signal()
m.d.sync += clk_out.o[0].eq(tx_clk_en)
tx_dly_write = Signal(4)
tx_in_sample = Signal(4*8)
m.d.sync += tx_dly_write.eq(tx_dly_write << 1)
m.d.sync += tx_in_sample.eq(Cat(host_io.i[1], tx_in_sample))
# Small TX FIFO to avoid overflows from the write delay.
m.submodules.tx_fifo = tx_fifo = fifo.SyncFIFOBuffered(width=24, depth=4)
m.d.comb += [
tx_fifo.w_data.word_select(0, 12) .eq(tx_in_sample[20:32]),
tx_fifo.w_data.word_select(1, 12) .eq(tx_in_sample[4:16]),
tx_fifo.w_en .eq(tx_dly_write[-1]),
dac_stream.p .eq(tx_fifo.r_data),
dac_stream.valid .eq(tx_fifo.r_rdy),
tx_fifo.r_en .eq(dac_stream.ready),
]
with m.FSM():
with m.State("IDLE"):
m.d.comb += tx_clk_en.eq(enable & transfer_to_dac & dac_stream.ready)
with m.If(tx_clk_en):
m.next = "TX_I1"
with m.State("TX_I1"):
m.d.comb += tx_clk_en.eq(1)
m.next = "TX_Q0"
with m.State("TX_Q0"):
m.d.comb += tx_clk_en.eq(1)
m.next = "TX_Q1"
with m.State("TX_Q1"):
m.d.comb += tx_clk_en.eq(1)
m.d.sync += tx_dly_write[0].eq(1) # delayed write
m.next = "IDLE"
if self._domain != "sync":
m = DomainRenamer(self._domain)(m)
return m
class FlowAndTriggerControl(wiring.Component):
trigger_en: In(1)
direction: Out(1) # async
enable: Out(1) # async
adc_capture: Out(1)
dac_capture: Out(1)
def __init__(self, domain):
super().__init__()
self._domain = domain
def elaborate(self, platform):
m = Module()
#
# Signal synchronization and trigger logic.
#
trigger_enable = self.trigger_en
trigger_in = platform.request("trigger_in").i
trigger_out = platform.request("trigger_out").o
host_data_enable = ~platform.request("disable").i
m.d.comb += trigger_out.eq(host_data_enable)
# Create a latch for the trigger input signal using a special FPGA primitive.
trigger_in_latched = Signal()
trigger_in_reg = Instance("SB_DFFES",
i_D = 0,
i_S = trigger_in, # async set
i_E = ~host_data_enable,
i_C = ClockSignal(self._domain),
o_Q = trigger_in_latched
)
m.submodules.trigger_in_reg = trigger_in_reg
# Export signals for direction control and capture gating.
m.d.comb += self.direction.eq(platform.request("direction").i)
m.d.comb += self.enable.eq(host_data_enable)
with m.If(host_data_enable):
m.d[self._domain] += self.adc_capture.eq((trigger_in_latched | ~trigger_enable) & (self.direction == 0))
m.d[self._domain] += self.dac_capture.eq((trigger_in_latched | ~trigger_enable) & (self.direction == 1))
with m.Else():
m.d[self._domain] += self.adc_capture.eq(0)
m.d[self._domain] += self.dac_capture.eq(0)
return m
class Top(Elaboratable):
def elaborate(self, platform):
m = Module()
m.submodules.clkgen = ClockDomainGenerator()
# Submodules.
m.submodules.flow_ctl = flow_ctl = FlowAndTriggerControl(domain="gck1")
m.submodules.adcdac_intf = adcdac_intf = MAX586xInterface(bb_domain="gck1")
m.submodules.mcu_intf = mcu_intf = MCUInterface(domain="sync")
m.d.comb += adcdac_intf.dac_capture.eq(flow_ctl.dac_capture)
m.d.comb += adcdac_intf.q_invert.eq(platform.request("q_invert").i)
m.d.comb += mcu_intf.direction.eq(flow_ctl.direction)
m.d.comb += mcu_intf.enable.eq(flow_ctl.enable)
# Half-band filter taps.
taps_hb1 = [-2, 0, 5, 0, -10, 0,18, 0, -30, 0,53, 0,-101, 0, 323, 512, 323, 0,-101, 0, 53, 0, -30, 0,18, 0, -10, 0, 5, 0,-2]
taps_hb1 = [ tap/1024 for tap in taps_hb1 ]
taps_hb2 = [3, 0, -16, 0, 77, 128, 77, 0, -16, 0, 3]
taps_hb2 = [ tap/256 for tap in taps_hb2 ]
tx_chain = {
# Clock domain conversion.
"clkconv": ClockConverter(IQSample(12), 4, "sync", "gck1", always_ready=False),
# Half-band interpolation stages (+ skid buffers for timing closure).
"hbfir1": HalfBandInterpolatorMAC16(taps_hb1, data_shape=fixed.SQ(11),
overclock_rate=8, num_channels=2, always_ready=False, domain="gck1"),
"skid1": DomainRenamer("gck1")(StreamSkidBuffer(IQSample(12), always_ready=False)),
"hbfir2": HalfBandInterpolatorMAC16(taps_hb2, data_shape=fixed.SQ(11),
overclock_rate=4, num_channels=2, always_ready=False, domain="gck1"),
"skid2": DomainRenamer("gck1")(StreamSkidBuffer(IQSample(12), always_ready=False)),
# CIC interpolation stage.
"cic_comp": DomainRenamer("gck1")(FIRFilter([-0.125, 0, 0.75, 0, -0.125], shape=fixed.SQ(11), shape_out=fixed.SQ(11), always_ready=False, num_channels=2)),
"cic_interpolator": CICInterpolator(2, 4, (4, 8, 16, 32), 12, 8, num_channels=2,
always_ready=False, domain="gck1"),
}
for k,v in tx_chain.items():
m.submodules[f"tx_{k}"] = v
# Connect transmitter chain.
last = mcu_intf.dac_stream
for block in tx_chain.values():
connect(m, last, block.input)
last = block.output
connect(m, last, adcdac_intf.dac_stream)
# SPI register interface.
spi_port = platform.request("spi")
m.submodules.spi_regs = spi_regs = SPIRegisterInterface(spi_port)
# Add control registers.
ctrl = spi_regs.add_register(0x01, init=0)
tx_intrp = spi_regs.add_register(0x02, init=0, size=3)
m.d.comb += [
# Trigger enable.
flow_ctl.trigger_en .eq(ctrl[7]),
# TX interpolation rate.
tx_chain["cic_interpolator"].factor .eq(tx_intrp + 2),
]
return m
if __name__ == "__main__":
plat = PralinePlatform()
plat.build(Top())

View File

@@ -0,0 +1,227 @@
#!/usr/bin/env python3
#
# This file is part of HackRF.
#
# Copyright (c) 2024 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
from amaranth import Elaboratable, Module, Signal, C, Mux, Instance, Cat, ClockSignal, DomainRenamer, signed
from amaranth.lib import io, stream, wiring, cdc, data, fifo
from amaranth.lib.wiring import Out, In, connect
from board import PralinePlatform, ClockDomainGenerator
from interface import MAX586xInterface
from interface.spi import SPIRegisterInterface
from dsp.dc_block import DCBlock
from dsp.round import convergent_round
from util import IQSample, ClockConverter
class MCUInterface(wiring.Component):
adc_stream: In(stream.Signature(IQSample(4), always_ready=True))
dac_stream: Out(stream.Signature(IQSample(4)))
direction: In(1)
enable: In(1)
def __init__(self, domain="sync"):
self._domain = domain
super().__init__()
def elaborate(self, platform):
m = Module()
adc_stream = self.adc_stream
dac_stream = self.dac_stream
# Determine data transfer direction.
direction = Signal()
enable = Signal()
m.d.sync += enable.eq(self.enable)
m.d.sync += direction.eq(self.direction)
transfer_from_adc = (direction == 0)
transfer_to_dac = (direction == 1)
# SGPIO clock and data lines.
m.submodules.clk_out = clk_out = io.DDRBuffer("o", platform.request("host_clk", dir="-"), o_domain=self._domain)
m.submodules.host_io = host_io = io.DDRBuffer('io', platform.request("host_data", dir="-"), i_domain=self._domain, o_domain=self._domain)
# State machine to control SGPIO clock and data lines.
m.d.sync += clk_out.o[0].eq(0)
m.d.sync += clk_out.o[1].eq(0)
m.d.sync += host_io.oe.eq(transfer_from_adc)
data_to_host = Signal.like(Cat(adc_stream.p.i, adc_stream.p.q))
assert len(data_to_host) == 8
m.d.comb += host_io.o[0].eq(data_to_host)
m.d.comb += host_io.o[1].eq(data_to_host)
tx_dly_write = Signal(2)
m.d.sync += tx_dly_write.eq(tx_dly_write << 1)
m.d.comb += dac_stream.payload.eq(host_io.i[1])
m.d.comb += dac_stream.valid.eq(tx_dly_write[-1])
with m.FSM():
with m.State("IDLE"):
with m.If(enable):
with m.If(transfer_from_adc & adc_stream.valid):
m.d.sync += data_to_host.eq(Cat(adc_stream.p.i, adc_stream.p.q))
m.d.sync += clk_out.o[1].eq(1)
with m.Elif(transfer_to_dac & dac_stream.ready):
m.d.sync += clk_out.o[0].eq(1)
m.d.sync += tx_dly_write[0].eq(1) # delayed write
if self._domain != "sync":
m = DomainRenamer(self._domain)(m)
return m
class FlowAndTriggerControl(wiring.Component):
trigger_en: In(1)
direction: Out(1) # async
enable: Out(1) # async
adc_capture: Out(1)
dac_capture: Out(1)
def __init__(self, domain):
super().__init__()
self._domain = domain
def elaborate(self, platform):
m = Module()
#
# Signal synchronization and trigger logic.
#
trigger_enable = self.trigger_en
trigger_in = platform.request("trigger_in").i
trigger_out = platform.request("trigger_out").o
host_data_enable = ~platform.request("disable").i
m.d.comb += trigger_out.eq(host_data_enable)
# Create a latch for the trigger input signal using a FPGA primitive.
trigger_in_latched = Signal()
trigger_in_reg = Instance("SB_DFFES",
i_D = 0,
i_S = trigger_in, # async set
i_E = ~host_data_enable,
i_C = ClockSignal(self._domain),
o_Q = trigger_in_latched
)
m.submodules.trigger_in_reg = trigger_in_reg
# Export signals for direction control and gating captures.
m.d.comb += self.direction.eq(platform.request("direction").i)
m.d.comb += self.enable.eq(host_data_enable)
with m.If(host_data_enable):
m.d[self._domain] += self.adc_capture.eq((trigger_in_latched | ~trigger_enable) & (self.direction == 0))
m.d[self._domain] += self.dac_capture.eq((trigger_in_latched | ~trigger_enable) & (self.direction == 1))
with m.Else():
m.d[self._domain] += self.adc_capture.eq(0)
m.d[self._domain] += self.dac_capture.eq(0)
return m
class IQHalfPrecisionConverter(wiring.Component):
input: In(stream.Signature(IQSample(8), always_ready=True))
output: Out(stream.Signature(IQSample(4), always_ready=True))
def elaborate(self, platform):
m = Module()
m.d.comb += [
self.output.p.i .eq(convergent_round(self.input.p.i, 4)),
self.output.p.q .eq(convergent_round(self.input.p.q, 4)),
self.output.valid .eq(self.input.valid),
]
return m
class IQHalfPrecisionConverterInv(wiring.Component):
input: In(stream.Signature(IQSample(4)))
output: Out(stream.Signature(IQSample(8)))
def elaborate(self, platform):
m = Module()
m.d.comb += [
self.output.p.i .eq(self.input.p.i << 4),
self.output.p.q .eq(self.input.p.q << 4),
self.output.valid .eq(self.input.valid),
self.input.ready .eq(self.output.ready),
]
return m
class Top(Elaboratable):
def elaborate(self, platform):
m = Module()
m.submodules.clkgen = ClockDomainGenerator()
# Submodules.
m.submodules.flow_ctl = flow_ctl = FlowAndTriggerControl(domain="gck1")
m.submodules.adcdac_intf = adcdac_intf = MAX586xInterface(bb_domain="gck1")
m.submodules.mcu_intf = mcu_intf = MCUInterface(domain="sync")
m.d.comb += adcdac_intf.adc_capture.eq(flow_ctl.adc_capture)
m.d.comb += adcdac_intf.dac_capture.eq(flow_ctl.dac_capture)
m.d.comb += adcdac_intf.q_invert.eq(platform.request("q_invert").i)
m.d.comb += mcu_intf.direction.eq(flow_ctl.direction)
m.d.comb += mcu_intf.enable.eq(flow_ctl.enable)
rx_chain = {
"dc_block": DCBlock(width=8, num_channels=2, domain="gck1"),
"half_prec": DomainRenamer("gck1")(IQHalfPrecisionConverter()),
"clkconv": ClockConverter(IQSample(4), 4, "gck1", "sync"),
}
m.submodules += rx_chain.values()
# Connect receiver chain.
last = adcdac_intf.adc_stream
for block in rx_chain.values():
connect(m, last, block.input)
last = block.output
connect(m, last, mcu_intf.adc_stream)
tx_chain = {
"clkconv": ClockConverter(IQSample(4), 4, "sync", "gck1", always_ready=False),
"half_prec": DomainRenamer("gck1")(IQHalfPrecisionConverterInv()),
}
m.submodules += tx_chain.values()
# Connect transmitter chain.
last = mcu_intf.dac_stream
for block in tx_chain.values():
connect(m, last, block.input)
last = block.output
connect(m, last, adcdac_intf.dac_stream)
# SPI register interface.
spi_port = platform.request("spi")
m.submodules.spi_regs = spi_regs = SPIRegisterInterface(spi_port)
# Add control registers.
ctrl = spi_regs.add_register(0x01, init=0)
m.d.comb += [
# Trigger enable.
flow_ctl.trigger_en .eq(ctrl[7]),
# RX settings.
rx_chain["dc_block"].enable .eq(ctrl[0]),
]
return m
if __name__ == "__main__":
plat = PralinePlatform()
plat.build(Top_HP())

View File

@@ -0,0 +1,320 @@
#
# This file is part of HackRF.
#
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
from amaranth import Elaboratable, Module, Signal, Mux, Instance, Cat, ClockSignal, DomainRenamer, EnableInserter
from amaranth.lib import io, fifo, stream, wiring, cdc
from amaranth.lib.wiring import Out, In, connect
from amaranth_future import fixed
from board import PralinePlatform, ClockDomainGenerator
from interface import MAX586xInterface
from interface.spi import SPIRegisterInterface
from dsp.fir import HalfBandDecimator, HalfBandInterpolator
from dsp.cic import CICDecimator, CICInterpolator
from dsp.dc_block import DCBlock
from dsp.quarter_shift import QuarterShift
from dsp.nco import NCO
from util import ClockConverter, IQSample, StreamSkidBuffer, LinearFeedbackShiftRegister
class MCUInterface(wiring.Component):
adc_stream: In(stream.Signature(IQSample(8), always_ready=True))
dac_stream: Out(stream.Signature(IQSample(8)))
direction: In(1)
enable: In(1)
prbs: In(1)
def __init__(self, domain="sync"):
self._domain = domain
super().__init__()
def elaborate(self, platform):
m = Module()
adc_stream = self.adc_stream
dac_stream = self.dac_stream
# Determine data transfer direction.
direction = Signal()
enable = Signal()
m.submodules.enable_cdc = cdc.FFSynchronizer(self.enable, enable, o_domain=self._domain)
m.submodules.direction_cdc = cdc.FFSynchronizer(self.direction, direction, o_domain=self._domain)
transfer_from_adc = (direction == 0)
transfer_to_dac = (direction == 1)
# SGPIO clock and data lines.
m.submodules.clk_out = clk_out = io.DDRBuffer("o", platform.request("host_clk", dir="-"), o_domain=self._domain)
m.submodules.host_io = host_io = io.DDRBuffer('io', platform.request("host_data", dir="-"), i_domain=self._domain, o_domain=self._domain)
# State machine to control SGPIO clock and data lines.
tx_clk_en = Signal()
rx_clk_en = Signal()
m.d.sync += clk_out.o[0].eq(tx_clk_en)
m.d.sync += clk_out.o[1].eq(rx_clk_en)
m.d.sync += host_io.oe.eq(transfer_from_adc)
data_to_host = Signal.like(adc_stream.p)
m.d.comb += host_io.o[0].eq(data_to_host)
m.d.comb += host_io.o[1].eq(data_to_host)
tx_dly_write = Signal(3)
host_io_prev_data = Signal(8)
m.d.sync += tx_dly_write.eq(tx_dly_write << 1)
m.d.sync += host_io_prev_data.eq(host_io.i[1])
# Small TX FIFO to avoid overflows from the write delay.
m.submodules.tx_fifo = tx_fifo = fifo.SyncFIFOBuffered(width=16, depth=8)
m.d.comb += [
tx_fifo.w_data .eq(Cat(host_io_prev_data, host_io.i[1])),
tx_fifo.w_en .eq(tx_dly_write[-1]),
dac_stream.p .eq(tx_fifo.r_data),
dac_stream.valid .eq(tx_fifo.r_rdy),
tx_fifo.r_en .eq(dac_stream.ready),
]
# Pseudo-random binary sequence generator.
prbs_advance = Signal()
prbs_count = Signal(2)
m.submodules.prbs = prbs = EnableInserter(prbs_advance)(
LinearFeedbackShiftRegister(degree=8, taps=[8,6,5,4], init=0b10110001))
with m.FSM():
with m.State("IDLE"):
m.d.comb += tx_clk_en.eq(enable & transfer_to_dac & dac_stream.ready)
m.d.comb += rx_clk_en.eq(enable & transfer_from_adc & adc_stream.valid)
with m.If(self.prbs):
m.next = "PRBS"
with m.Elif(rx_clk_en):
m.d.sync += data_to_host.eq(adc_stream.p)
m.next = "RX_Q"
with m.Elif(tx_clk_en):
m.next = "TX_Q"
with m.State("RX_Q"):
m.d.comb += rx_clk_en.eq(1)
m.d.sync += data_to_host.i.eq(data_to_host.q)
m.next = "IDLE"
with m.State("TX_Q"):
m.d.comb += tx_clk_en.eq(1)
m.d.sync += tx_dly_write[0].eq(1) # delayed write
m.next = "IDLE"
with m.State("PRBS"):
m.d.sync += host_io.oe.eq(1)
m.d.sync += data_to_host.eq(prbs.value)
m.d.comb += rx_clk_en.eq(prbs_count == 0)
m.d.comb += prbs_advance.eq(prbs_count == 0)
m.d.sync += prbs_count.eq(prbs_count + 1)
with m.If(~self.prbs):
m.next = "IDLE"
if self._domain != "sync":
m = DomainRenamer(self._domain)(m)
return m
class FlowAndTriggerControl(wiring.Component):
trigger_en: In(1)
direction: Out(1) # async
enable: Out(1) # async
adc_capture: Out(1)
dac_capture: Out(1)
def __init__(self, domain):
super().__init__()
self._domain = domain
def elaborate(self, platform):
m = Module()
#
# Signal synchronization and trigger logic.
#
trigger_enable = self.trigger_en
trigger_in = platform.request("trigger_in").i
trigger_out = platform.request("trigger_out").o
host_data_enable = ~platform.request("disable").i
m.d.comb += trigger_out.eq(host_data_enable)
# Create a latch for the trigger input signal using a special FPGA primitive.
trigger_in_latched = Signal()
trigger_in_reg = Instance("SB_DFFES",
i_D = 0,
i_S = trigger_in, # async set
i_E = ~host_data_enable,
i_C = ClockSignal(self._domain),
o_Q = trigger_in_latched
)
m.submodules.trigger_in_reg = trigger_in_reg
# Export signals for direction control and capture gating.
m.d.comb += self.direction.eq(platform.request("direction").i)
m.d.comb += self.enable.eq(host_data_enable)
with m.If(host_data_enable):
m.d[self._domain] += self.adc_capture.eq((trigger_in_latched | ~trigger_enable) & (self.direction == 0))
m.d[self._domain] += self.dac_capture.eq((trigger_in_latched | ~trigger_enable) & (self.direction == 1))
with m.Else():
m.d[self._domain] += self.adc_capture.eq(0)
m.d[self._domain] += self.dac_capture.eq(0)
return m
class Top(Elaboratable):
def elaborate(self, platform):
m = Module()
m.submodules.clkgen = ClockDomainGenerator()
# Submodules.
m.submodules.flow_ctl = flow_ctl = FlowAndTriggerControl(domain="gck1")
m.submodules.adcdac_intf = adcdac_intf = MAX586xInterface(bb_domain="gck1")
m.submodules.mcu_intf = mcu_intf = MCUInterface(domain="sync")
m.d.comb += adcdac_intf.adc_capture.eq(flow_ctl.adc_capture)
m.d.comb += adcdac_intf.dac_capture.eq(flow_ctl.dac_capture)
m.d.comb += adcdac_intf.q_invert.eq(platform.request("q_invert").i)
m.d.comb += mcu_intf.direction.eq(flow_ctl.direction)
m.d.comb += mcu_intf.enable.eq(flow_ctl.enable)
# Half-band filter taps.
taps = [-2, 0, 7, 0, -18, 0, 41, 0, -92, 0, 320, 512, 320, 0, -92, 0, 41, 0, -18, 0, 7, 0, -2]
taps = [ tap/1024 for tap in taps ]
taps2 = [3, 0, -16, 0, 77, 128, 77, 0, -16, 0, 3]
taps2 = [ tap/256 for tap in taps2 ]
taps3 = [-9, 0, 73, 128, 73, 0, -9]
taps3 = [ tap/256 for tap in taps3 ]
taps4 = [-8, 0, 72, 128, 72, 0, -8]
taps4 = [ tap/256 for tap in taps4 ]
taps5 = [-1, 0, 9, 16, 9, 0, -1]
taps5 = [ tap/32 for tap in taps5 ]
common_rx_filter_opts = dict(
data_shape=fixed.SQ(7),
always_ready=True,
domain="gck1",
)
rx_chain = {
# DC block and quarter shift.
"dc_block": DCBlock(width=8, num_channels=2, domain="gck1"),
"quarter_shift": DomainRenamer("gck1")(QuarterShift()),
# Half-band decimation stages.
"hbfir5": HalfBandDecimator(taps5, **common_rx_filter_opts),
"hbfir4": HalfBandDecimator(taps4, **common_rx_filter_opts),
"hbfir3": HalfBandDecimator(taps3, **common_rx_filter_opts),
"hbfir2": HalfBandDecimator(taps2, **common_rx_filter_opts),
"hbfir1": HalfBandDecimator(taps, **common_rx_filter_opts),
# Clock domain conversion.
"clkconv": ClockConverter(IQSample(8), 4, "gck1", "sync"),
}
for k,v in rx_chain.items():
m.submodules[f"rx_{k}"] = v
# Connect receiver chain.
last = adcdac_intf.adc_stream
for block in rx_chain.values():
connect(m, last, block.input)
last = block.output
connect(m, last, mcu_intf.adc_stream)
tx_chain = {
# Clock domain conversion.
"clkconv": ClockConverter(IQSample(8), 4, "sync", "gck1", always_ready=False),
# Half-band interpolation stages (+ skid buffers for timing closure).
"hbfir1": HalfBandInterpolator(taps, data_shape=fixed.SQ(7),
num_channels=2, always_ready=False, domain="gck1"),
"skid2": DomainRenamer("gck1")(StreamSkidBuffer(IQSample(8), always_ready=False)),
"hbfir2": HalfBandInterpolator(taps2, data_shape=fixed.SQ(7),
num_channels=2, always_ready=False, domain="gck1"),
"skid3": DomainRenamer("gck1")(StreamSkidBuffer(IQSample(8), always_ready=False)),
# CIC interpolation stage.
"cic_interpolator": CICInterpolator(1, 3, (1, 2, 4, 8), 8, 8, num_channels=2,
always_ready=False, domain="gck1"),
}
for k,v in tx_chain.items():
m.submodules[f"tx_{k}"] = v
# Connect transmitter chain.
last = mcu_intf.dac_stream
for block in tx_chain.values():
connect(m, last, block.input)
last = block.output
# DAC can also be driven with an internal NCO.
m.submodules.nco = nco = DomainRenamer("gck1")(NCO(phase_width=16, output_width=8))
with m.If(nco.en):
m.d.comb += [
adcdac_intf.dac_stream.p.eq(nco.output),
adcdac_intf.dac_stream.valid.eq(1),
tx_chain["cic_interpolator"].output.ready.eq(1),
]
with m.Else():
connect(m, last, adcdac_intf.dac_stream)
# SPI register interface.
spi_port = platform.request("spi")
m.submodules.spi_regs = spi_regs = SPIRegisterInterface(spi_port)
# Add control registers.
ctrl = spi_regs.add_register(0x01, init=0)
rx_decim = spi_regs.add_register(0x02, init=0, size=3)
tx_ctrl = spi_regs.add_register(0x03, init=0, size=1)
tx_intrp = spi_regs.add_register(0x04, init=0, size=3)
tx_pstep = spi_regs.add_register(0x05, init=0)
m.d.sync += [
# Trigger enable.
flow_ctl.trigger_en .eq(ctrl[7]),
# PRBS enable.
mcu_intf.prbs .eq(ctrl[6]),
# RX settings.
rx_chain["dc_block"].enable .eq(ctrl[0]),
rx_chain["quarter_shift"].enable .eq(ctrl[1]),
rx_chain["quarter_shift"].up .eq(ctrl[2]),
# RX decimation rate.
rx_chain["hbfir5"].enable .eq(rx_decim > 4),
rx_chain["hbfir4"].enable .eq(rx_decim > 3),
rx_chain["hbfir3"].enable .eq(rx_decim > 2),
rx_chain["hbfir2"].enable .eq(rx_decim > 1),
rx_chain["hbfir1"].enable .eq(rx_decim > 0),
# TX interpolation rate.
tx_chain["cic_interpolator"].factor .eq(Mux(tx_intrp > 2, tx_intrp - 2, 0)),
tx_chain["hbfir1"].enable .eq(tx_intrp > 0),
tx_chain["hbfir2"].enable .eq(tx_intrp > 1),
]
# TX NCO control.
tx_pstep_gck1 = Signal(8)
m.submodules.nco_phase_cdc = cdc.FFSynchronizer(tx_pstep, tx_pstep_gck1, o_domain="gck1")
m.d.gck1 += [
nco.en .eq(tx_ctrl[0]),
nco.phase .eq(nco.phase + (tx_pstep_gck1 << 6)),
]
return m
if __name__ == "__main__":
plat = PralinePlatform()
plat.build(Top())

View File

@@ -0,0 +1,57 @@
#
# This file is part of HackRF.
#
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
from amaranth import Module, signed, Shape
from amaranth.lib import wiring, stream, data, fifo
from amaranth.lib.wiring import In, Out
from ._stream import StreamSkidBuffer, StreamMux, StreamDemux
from .lfsr import LinearFeedbackShiftRegister
class IQSample(data.StructLayout):
def __init__(self, width=8):
super().__init__({
"i": signed(width),
"q": signed(width),
})
class ClockConverter(wiring.Component):
def __init__(self, shape, depth, input_domain, output_domain, always_ready=True):
super().__init__({
"input": In(stream.Signature(shape, always_ready=always_ready)),
"output": Out(stream.Signature(shape, always_ready=always_ready)),
})
self.shape = shape
self.depth = depth
self._input_domain = input_domain
self._output_domain = output_domain
def elaborate(self, platform):
m = Module()
m.submodules.mem = mem = fifo.AsyncFIFO(
width=Shape.cast(self.shape).width,
depth=self.depth,
r_domain=self._output_domain,
w_domain=self._input_domain)
m.d.comb += [
# write.
mem.w_data .eq(self.input.p),
mem.w_en .eq(self.input.valid),
# read.
self.output.p .eq(mem.r_data),
self.output.valid .eq(mem.r_rdy),
mem.r_en .eq(self.output.ready),
]
if not self.input.signature.always_ready:
m.d.comb += self.input.ready.eq(mem.w_rdy)
return m

View File

@@ -0,0 +1,189 @@
#
# This file is part of HackRF.
#
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
import unittest
from amaranth import Module, Mux, Signal, Cat
from amaranth.lib import wiring, stream, data
from amaranth.lib.wiring import In, Out
from amaranth.sim import Simulator
class StreamSkidBuffer(wiring.Component):
def __init__(self, shape, always_ready=False):
super().__init__({
"input": In(stream.Signature(shape, always_ready=always_ready)),
"output": Out(stream.Signature(shape, always_ready=always_ready)),
})
def elaborate(self, platform):
m = Module()
# To provide for the "elasticity" needed due to a registered "ready" signal, we need
# two registers for the payload. When the consumer is not ready, there's a cycle
# where the data from the producer is stored in r_payload.
# Read https://www.itdev.co.uk/blog/pipelining-axi-buses-registered-ready-signals
r_payload = Signal.like(self.input.payload, reset_less=True)
r_valid = Signal()
with m.If(self.input.ready):
m.d.sync += r_valid.eq(self.input.valid)
m.d.sync += r_payload.eq(self.input.payload)
# r_valid can only be asserted when there is incoming data but the consumer is not ready.
with m.If(self.output.ready):
m.d.sync += r_valid.eq(0)
if not self.input.signature.always_ready:
m.d.comb += self.input.ready.eq(~r_valid)
m.d.comb += self.output.valid.eq(self.input.valid | r_valid)
m.d.comb += self.output.p.eq(Mux(r_valid, r_payload, self.input.p))
return m
class StreamMux(wiring.Component):
def __init__(self, data_shape, num_channels, always_ready=False):
self.num_channels = num_channels
super().__init__({
"input": In(stream.Signature(
data.ArrayLayout(data_shape, num_channels),
always_ready=always_ready
)),
"output": Out(stream.Signature(
data.ArrayLayout(data_shape, 1),
always_ready=always_ready
)),
})
def elaborate(self, platform):
m = Module()
ratio = self.num_channels
counter = Signal(range(ratio))
sreg = Signal.like(self.input.p)
m.d.comb += self.output.payload.eq(sreg[0])
with m.If(self.output.ready & self.output.valid):
m.d.sync += counter.eq(counter + 1)
for i in range(ratio-1):
m.d.sync += sreg[i].eq(sreg[i+1])
with m.If(~self.output.valid | (self.output.ready & (counter == ratio-1))):
m.d.sync += self.output.valid.eq(self.input.valid)
m.d.sync += sreg.eq(self.input.payload)
if not self.input.signature.always_ready:
m.d.comb += self.input.ready.eq(1)
return m
class StreamDemux(wiring.Component):
def __init__(self, data_shape, num_channels, always_ready=False):
self.num_channels = num_channels
super().__init__({
"input": In(stream.Signature(
data.ArrayLayout(data_shape, 1),
always_ready=always_ready
)),
"output": Out(stream.Signature(
data.ArrayLayout(data_shape, num_channels),
always_ready=always_ready
)),
})
def elaborate(self, platform):
m = Module()
ratio = self.num_channels
counter = Signal(range(ratio))
with m.If(~self.output.valid | self.output.ready):
m.d.sync += self.output.valid.eq(self.input.valid & (counter == ratio-1))
if not self.input.signature.always_ready:
m.d.comb += self.input.ready.eq(1)
with m.If(self.input.valid):
m.d.sync += self.output.p[ratio-1].eq(self.input.p[0])
for i in range(ratio-1):
m.d.sync += self.output.p[i].eq(self.output.p[i+1])
# TODO: if I remove the following line timing is much worse. Study why.
m.d.sync += self.output.p.eq(Cat(self.output.p[len(self.input.p):], self.input.p))
m.d.sync += counter.eq(counter + 1)
return m
class TestStreamMux(unittest.TestCase):
def test_mux(self):
dut = StreamMux(data_shape=8, num_channels=2)
input_stream = [[0xAA, 0xBB], [0xCC, 0xDD]]
output_stream = []
output_len = 4
async def stream_input(ctx):
for sample in input_stream:
ctx.set(dut.input.payload, sample)
ctx.set(dut.input.valid, 1)
await ctx.tick().until(dut.input.ready)
ctx.set(dut.input.valid, 0)
async def stream_output(ctx):
ctx.set(dut.output.ready, 1)
while len(output_stream) < output_len:
await ctx.tick()
if ctx.get(dut.output.valid):
output_stream.append(ctx.get(dut.output.payload))
sim = Simulator(dut)
sim.add_clock(1e-6)
sim.add_testbench(stream_input)
sim.add_testbench(stream_output)
sim.run()
self.assertListEqual(output_stream, [[0xAA], [0xBB], [0xCC], [0xDD]])
def test_demux(self):
dut = StreamDemux(data_shape=8, num_channels=2)
input_stream = [[0xAA], [0xBB], [0xCC], [0xDD]]
output_stream = []
output_len = 2
async def stream_input(ctx):
for sample in input_stream:
ctx.set(dut.input.payload, sample)
ctx.set(dut.input.valid, 1)
await ctx.tick().until(dut.input.ready)
ctx.set(dut.input.valid, 0)
async def stream_output(ctx):
ctx.set(dut.output.ready, 1)
while len(output_stream) < output_len:
await ctx.tick()
if ctx.get(dut.output.valid):
output_stream.append(ctx.get(dut.output.payload))
sim = Simulator(dut)
sim.add_clock(1e-6)
sim.add_testbench(stream_input)
sim.add_testbench(stream_output)
sim.run()
self.assertListEqual(output_stream, [[0xAA, 0xBB], [0xCC, 0xDD]])
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,59 @@
#
# This file is part of Glasgow Interface Explorer.
#
# Copyright (c) 2025 Glasgow Interface Explorer contributors.
# SPDX-License-Identifier: BSD-0-Clause
from amaranth import *
__all__ = ["LinearFeedbackShiftRegister"]
class LinearFeedbackShiftRegister(Elaboratable):
"""A linear feedback shift register. Useful for generating long pseudorandom sequences with
a minimal amount of logic.
Use ``CEInserter`` and ``ResetInserter`` transformers to control the LFSR.
:param degree:
Width of register, in bits.
:type degree: int
:param taps:
Feedback taps, with bits numbered starting at 1 (i.e. polynomial degrees).
:type taps: list of int
:param reset:
Initial value loaded into the register. Must be non-zero, or only zeroes will be
generated.
:type reset: int
"""
def __init__(self, degree, taps, init=1):
assert init != 0
self.degree = degree
self.taps = taps
self.init = init
self.value = Signal(degree, init=init)
def elaborate(self, platform):
m = Module()
feedback = 0
for tap in self.taps:
feedback ^= (self.value >> (tap - 1)) & 1
m.d.sync += self.value.eq((self.value << 1) | feedback)
return m
def generate(self):
"""Generate every distinct value the LFSR will take."""
value = self.init
mask = (1 << self.degree) - 1
while True:
yield value
feedback = 0
for tap in self.taps:
feedback ^= (value >> (tap - 1)) & 1
value = ((value << 1) & mask) | feedback
if value == self.init:
break

View File

@@ -40,6 +40,8 @@ SET(LIBOPENCM3 ${PATH_HACKRF_FIRMWARE}/libopencm3)
SET(PATH_DFU_PY ${PATH_HACKRF_FIRMWARE}/dfu.py)
SET(PATH_CPLD_BITSTREAM_TOOL ${PATH_HACKRF_FIRMWARE}/tools/cpld_bitstream.py)
set(PATH_HACKRF_CPLD_DATA_C ${CMAKE_CURRENT_BINARY_DIR}/hackrf_cpld_data.c)
SET(PATH_PRALINE_FPGA_BIN ${PATH_HACKRF_FIRMWARE}/fpga/build/praline_fpga.bin)
SET(PATH_PRALINE_FPGA_OBJ ${CMAKE_CURRENT_BINARY_DIR}/fpga.o)
include(${PATH_HACKRF_FIRMWARE}/dfu-util.cmake)
@@ -72,7 +74,7 @@ if(NOT DEFINED BOARD)
set(BOARD HACKRF_ONE)
endif()
if(BOARD STREQUAL "HACKRF_ONE")
if(BOARD STREQUAL "HACKRF_ONE" OR BOARD STREQUAL "PRALINE")
set(MCU_PARTNO LPC4320)
else()
set(MCU_PARTNO LPC4330)
@@ -84,7 +86,7 @@ endif()
SET(HACKRF_OPTS "-D${BOARD} -DLPC43XX -D${MCU_PARTNO} -DTX_ENABLE -D'VERSION_STRING=\"${VERSION}\"'")
SET(LDSCRIPT_M4 "-T${PATH_HACKRF_FIRMWARE_COMMON}/${MCU_PARTNO}_M4_memory.ld -Tlibopencm3_lpc43xx_rom_to_ram.ld -T${PATH_HACKRF_FIRMWARE_COMMON}/LPC43xx_M4_M0_image_from_text.ld")
SET(LDSCRIPT_M4 "-T${PATH_HACKRF_FIRMWARE_COMMON}/${MCU_PARTNO}_M4_memory.ld -Tlibopencm3_lpc43xx_rom_to_ram.ld -T${PATH_HACKRF_FIRMWARE_COMMON}/LPC43xx_M4_M0_image_from_text.ld -T${PATH_HACKRF_FIRMWARE_COMMON}/LPC43xx_M4_memory_rom_only.ld")
SET(LDSCRIPT_M4_RAM "-T${PATH_HACKRF_FIRMWARE_COMMON}/${MCU_PARTNO}_M4_memory.ld -Tlibopencm3_lpc43xx.ld -T${PATH_HACKRF_FIRMWARE_COMMON}/LPC43xx_M4_M0_image_from_text.ld")
@@ -143,7 +145,7 @@ macro(DeclareTarget project_name variant_suffix cflags ldflags)
add_library(${project_name}${variant_suffix}_objects OBJECT ${SRC_M4} ${project_name}${variant_suffix}_m0_bin.s)
set_target_properties(${project_name}${variant_suffix}_objects PROPERTIES COMPILE_FLAGS "${cflags}")
add_executable(${project_name}${variant_suffix}.elf $<TARGET_OBJECTS:${project_name}${variant_suffix}_objects>)
add_executable(${project_name}${variant_suffix}.elf $<TARGET_OBJECTS:${project_name}${variant_suffix}_objects> ${OBJ_M4})
add_dependencies(${project_name}${variant_suffix}.elf libopencm3_${project_name})
target_link_libraries(
@@ -170,11 +172,6 @@ macro(DeclareTargets)
${PATH_HACKRF_FIRMWARE_COMMON}/sgpio.c
${PATH_HACKRF_FIRMWARE_COMMON}/rf_path.c
${PATH_HACKRF_FIRMWARE_COMMON}/si5351c.c
${PATH_HACKRF_FIRMWARE_COMMON}/max283x.c
${PATH_HACKRF_FIRMWARE_COMMON}/max2837.c
${PATH_HACKRF_FIRMWARE_COMMON}/max2837_target.c
${PATH_HACKRF_FIRMWARE_COMMON}/max2839.c
${PATH_HACKRF_FIRMWARE_COMMON}/max2839_target.c
${PATH_HACKRF_FIRMWARE_COMMON}/max5864.c
${PATH_HACKRF_FIRMWARE_COMMON}/max5864_target.c
${PATH_HACKRF_FIRMWARE_COMMON}/mixer.c
@@ -191,6 +188,9 @@ macro(DeclareTargets)
${PATH_HACKRF_FIRMWARE_COMMON}/clkin.c
${PATH_HACKRF_FIRMWARE_COMMON}/gpdma.c
${PATH_HACKRF_FIRMWARE_COMMON}/user_config.c
${PATH_HACKRF_FIRMWARE_COMMON}/radio.c
${PATH_HACKRF_FIRMWARE_COMMON}/selftest.c
${PATH_HACKRF_FIRMWARE_COMMON}/m0_state.c
)
if(BOARD STREQUAL "RAD1O")
@@ -207,6 +207,25 @@ macro(DeclareTargets)
)
endif()
if(BOARD STREQUAL "PRALINE")
SET(SRC_M4
${SRC_M4}
${PATH_HACKRF_FIRMWARE_COMMON}/fpga.c
${PATH_HACKRF_FIRMWARE_COMMON}/ice40_spi.c
${PATH_HACKRF_FIRMWARE_COMMON}/max2831.c
${PATH_HACKRF_FIRMWARE_COMMON}/max2831_target.c
)
else()
SET(SRC_M4
${SRC_M4}
${PATH_HACKRF_FIRMWARE_COMMON}/max283x.c
${PATH_HACKRF_FIRMWARE_COMMON}/max2837.c
${PATH_HACKRF_FIRMWARE_COMMON}/max2837_target.c
${PATH_HACKRF_FIRMWARE_COMMON}/max2839.c
${PATH_HACKRF_FIRMWARE_COMMON}/max2839_target.c
)
endif()
link_directories(
"${PATH_HACKRF_FIRMWARE_COMMON}"
"${LIBOPENCM3}/lib"
@@ -228,7 +247,7 @@ macro(DeclareTargets)
set_target_properties(${PROJECT_NAME}_m0.elf PROPERTIES LINK_FLAGS "${LDFLAGS_M0}")
DeclareTarget("${PROJECT_NAME}" "" "${CFLAGS_M4}" "${LDFLAGS_M4}")
DeclareTarget("${PROJECT_NAME}" "_ram" "${CFLAGS_M4_RAM}" "${LDFLAGS_M4_RAM}")
DeclareTarget("${PROJECT_NAME}" "_ram" "${CFLAGS_M4_RAM} -DRAM_MODE" "${LDFLAGS_M4_RAM}")
DeclareTarget("${PROJECT_NAME}" "_dfu" "${CFLAGS_M4_RAM} -DDFU_MODE" "${LDFLAGS_M4_RAM}")
add_custom_target(

View File

@@ -31,6 +31,18 @@ add_custom_command(
DEPENDS ${PATH_CPLD_BITSTREAM_TOOL} ${PATH_HACKRF_CPLD_XSVF}
)
add_custom_command(
OUTPUT ${PATH_PRALINE_FPGA_OBJ}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND ${CMAKE_COMMAND} -E copy ${PATH_PRALINE_FPGA_BIN} "fpga.bin"
COMMAND ${CMAKE_OBJCOPY}
-I binary
-O elf32-littlearm
--rename-section .data=.rom_only
"fpga.bin" ${PATH_PRALINE_FPGA_OBJ}
DEPENDS ${PATH_PRALINE_FPGA_BIN}
)
set(SRC_M4
hackrf_usb.c
"${PATH_HACKRF_FIRMWARE_COMMON}/tuning.c"
@@ -42,22 +54,17 @@ set(SRC_M4
usb_device.c
usb_endpoint.c
usb_api_board_info.c
usb_api_cpld.c
usb_api_m0_state.c
usb_api_register.c
usb_api_spiflash.c
usb_api_transceiver.c
usb_api_operacake.c
usb_api_sweep.c
usb_api_selftest.c
usb_api_ui.c
"${PATH_HACKRF_FIRMWARE_COMMON}/usb_queue.c"
"${PATH_HACKRF_FIRMWARE_COMMON}/fault_handler.c"
"${PATH_HACKRF_FIRMWARE_COMMON}/cpld_jtag.c"
"${PATH_HACKRF_FIRMWARE_COMMON}/cpld_xc2c.c"
"${PATH_HACKRF_CPLD_DATA_C}"
"${PATH_HACKRF_FIRMWARE_COMMON}/xapp058/lenval.c"
"${PATH_HACKRF_FIRMWARE_COMMON}/xapp058/micro.c"
"${PATH_HACKRF_FIRMWARE_COMMON}/xapp058/ports.c"
"${PATH_HACKRF_FIRMWARE_COMMON}/crc.c"
"${PATH_HACKRF_FIRMWARE_COMMON}/rom_iap.c"
"${PATH_HACKRF_FIRMWARE_COMMON}/operacake.c"
@@ -66,7 +73,28 @@ set(SRC_M4
set(SRC_M0 sgpio_m0.s)
if(BOARD STREQUAL "HACKRF_ONE")
if(BOARD STREQUAL "PRALINE")
SET(SRC_M4
${SRC_M4}
"${PATH_HACKRF_FIRMWARE_COMMON}/lz4_blk.c"
usb_api_praline.c
)
SET(OBJ_M4
${PATH_PRALINE_FPGA_OBJ}
)
else()
SET(SRC_M4
${SRC_M4}
usb_api_cpld.c
"${PATH_HACKRF_FIRMWARE_COMMON}/cpld_xc2c.c"
"${PATH_HACKRF_CPLD_DATA_C}"
"${PATH_HACKRF_FIRMWARE_COMMON}/xapp058/lenval.c"
"${PATH_HACKRF_FIRMWARE_COMMON}/xapp058/micro.c"
"${PATH_HACKRF_FIRMWARE_COMMON}/xapp058/ports.c"
)
endif()
if(BOARD STREQUAL "HACKRF_ONE" OR BOARD STREQUAL "PRALINE")
SET(SRC_M4
${SRC_M4}
"${PATH_HACKRF_FIRMWARE_COMMON}/portapack.c"

View File

@@ -46,6 +46,8 @@
#include "usb_api_register.h"
#include "usb_api_spiflash.h"
#include "usb_api_operacake.h"
#include "usb_api_praline.h"
#include "usb_api_selftest.h"
#include "operacake.h"
#include "usb_api_sweep.h"
#include "usb_api_transceiver.h"
@@ -57,6 +59,8 @@
#include "hackrf_ui.h"
#include "platform_detect.h"
#include "clkin.h"
#include "fpga.h"
#include "selftest.h"
extern uint32_t __m0_start__;
extern uint32_t __m0_end__;
@@ -92,7 +96,7 @@ static usb_request_handler_fn vendor_request_handler[] = {
usb_vendor_request_set_vga_gain,
usb_vendor_request_set_txvga_gain,
NULL, // was set_if_freq
#ifdef HACKRF_ONE
#if (defined HACKRF_ONE || defined PRALINE)
usb_vendor_request_set_antenna_enable,
#else
NULL,
@@ -126,6 +130,24 @@ static usb_request_handler_fn vendor_request_handler[] = {
usb_vendor_request_read_supported_platform,
usb_vendor_request_set_leds,
usb_vendor_request_user_config_set_bias_t_opts,
#ifdef PRALINE
usb_vendor_request_spi_write_fpga,
usb_vendor_request_spi_read_fpga,
usb_vendor_request_p2_ctrl,
usb_vendor_request_p1_ctrl,
usb_vendor_request_set_narrowband_filter,
usb_vendor_request_set_fpga_bitstream,
usb_vendor_request_clkin_ctrl,
#else
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
#endif
usb_vendor_request_read_selftest,
};
static const uint32_t vendor_request_handler_count =
@@ -200,6 +222,7 @@ void usb_set_descriptor_by_serial_number(void)
}
}
#ifndef PRALINE
static bool cpld_jtag_sram_load(jtag_t* const jtag)
{
cpld_jtag_take(jtag);
@@ -211,6 +234,7 @@ static bool cpld_jtag_sram_load(jtag_t* const jtag)
cpld_jtag_release(jtag);
return success;
}
#endif
static void m0_rom_to_ram()
{
@@ -231,15 +255,23 @@ int main(void)
// Copy M0 image from ROM before SPIFI is disabled
m0_rom_to_ram();
// This will be cleared if any self-test check fails.
selftest.report.pass = true;
detect_hardware_platform();
pin_setup();
#ifndef PRALINE
enable_1v8_power();
#else
enable_3v3aux_power();
enable_1v2_power();
#endif
#ifdef HACKRF_ONE
// Set up mixer before enabling RF power, because its
// GPO is used to control the antenna bias tee.
mixer_setup(&mixer);
#endif
#if (defined HACKRF_ONE || defined RAD1O)
#if (defined HACKRF_ONE || defined RAD1O || defined PRALINE)
enable_rf_power();
#endif
cpu_clock_init();
@@ -248,11 +280,21 @@ int main(void)
ipc_halt_m0();
ipc_start_m0((uint32_t) &__ram_m0_start__);
#ifndef PRALINE
if (!cpld_jtag_sram_load(&jtag_cpld)) {
halt_and_flash(6000000);
}
#else
#if !defined(DFU_MODE) && !defined(RAM_MODE)
if (!fpga_image_load(0)) {
halt_and_flash(6000000);
}
delay_us_at_mhz(100, 204);
fpga_sgpio_selftest();
#endif
#endif
#ifdef HACKRF_ONE
#if (defined HACKRF_ONE || defined PRALINE)
portapack_init();
#endif
@@ -281,6 +323,10 @@ int main(void)
rf_path_init(&rf_path);
#ifdef PRALINE
fpga_if_xcvr_selftest();
#endif
bool operacake_allow_gpio;
if (hackrf_ui()->operacake_gpio_compatible()) {
operacake_allow_gpio = true;
@@ -321,9 +367,11 @@ int main(void)
case TRANSCEIVER_MODE_RX_SWEEP:
sweep_mode(request.seq);
break;
#ifndef PRALINE
case TRANSCEIVER_MODE_CPLD_UPDATE:
cpld_update();
break;
#endif
default:
break;
}

View File

@@ -26,21 +26,6 @@
#include <usb_request.h>
#include <usb_queue.h>
void m0_set_mode(enum m0_mode mode)
{
// Set requested mode and flag bit.
m0_state.requested_mode = M0_REQUEST_FLAG | mode;
// The M0 may be blocked waiting for the next SGPIO interrupt.
// In order to ensure that it sees our request, we need to set
// the interrupt flag here. The M0 will clear the flag again
// before acknowledging our request.
SGPIO_SET_STATUS_1 = (1 << SGPIO_SLICE_A);
// Wait for M0 to acknowledge by clearing the flag.
while (m0_state.requested_mode & M0_REQUEST_FLAG) {}
}
usb_request_status_t usb_vendor_request_get_m0_state(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage)

View File

@@ -19,51 +19,14 @@
* Boston, MA 02110-1301, USA.
*/
#ifndef __M0_STATE_H__
#define __M0_STATE_H__
#ifndef __M0_STATE_USB_H__
#define __M0_STATE_USB_H__
#include <stdint.h>
#include <usb_request.h>
#define M0_REQUEST_FLAG (1 << 16)
struct m0_state {
uint32_t requested_mode;
uint32_t active_mode;
uint32_t m0_count;
uint32_t m4_count;
uint32_t num_shortfalls;
uint32_t longest_shortfall;
uint32_t shortfall_limit;
uint32_t threshold;
uint32_t next_mode;
uint32_t error;
};
enum m0_mode {
M0_MODE_IDLE = 0,
M0_MODE_WAIT = 1,
M0_MODE_RX = 2,
M0_MODE_TX_START = 3,
M0_MODE_TX_RUN = 4,
};
enum m0_error {
M0_ERROR_NONE = 0,
M0_ERROR_RX_TIMEOUT = 1,
M0_ERROR_TX_TIMEOUT = 2,
};
/* Address of m0_state is set in ldscripts. If you change the name of this
* variable, it won't be where it needs to be in the processor's address space,
* unless you also adjust the ldscripts.
*/
extern volatile struct m0_state m0_state;
void m0_set_mode(enum m0_mode mode);
#include "m0_state.h"
usb_request_status_t usb_vendor_request_get_m0_state(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage);
#endif /*__M0_STATE_H__*/
#endif /*__M0_STATE_USB_H__*/

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2012-2022 Great Scott Gadgets <info@greatscottgadgets.com>
* Copyright 2012 Jared Boone
* Copyright 2013 Benjamin Vernoux
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "usb_api_praline.h"
#include "usb_queue.h"
#include <hackrf_core.h>
#include <stddef.h>
usb_request_status_t usb_vendor_request_p1_ctrl(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage)
{
if (stage == USB_TRANSFER_STAGE_SETUP) {
p1_ctrl_set(endpoint->setup.value);
usb_transfer_schedule_ack(endpoint->in);
}
return USB_REQUEST_STATUS_OK;
}
usb_request_status_t usb_vendor_request_p2_ctrl(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage)
{
if (stage == USB_TRANSFER_STAGE_SETUP) {
p2_ctrl_set(endpoint->setup.value);
usb_transfer_schedule_ack(endpoint->in);
}
return USB_REQUEST_STATUS_OK;
}
usb_request_status_t usb_vendor_request_clkin_ctrl(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage)
{
if (stage == USB_TRANSFER_STAGE_SETUP) {
clkin_ctrl_set(endpoint->setup.value & 1);
usb_transfer_schedule_ack(endpoint->in);
}
return USB_REQUEST_STATUS_OK;
}
usb_request_status_t usb_vendor_request_set_narrowband_filter(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage)
{
if (stage == USB_TRANSFER_STAGE_SETUP) {
narrowband_filter_set(endpoint->setup.value);
usb_transfer_schedule_ack(endpoint->in);
}
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 (stage == USB_TRANSFER_STAGE_SETUP) {
if (!fpga_image_load(endpoint->setup.value)) {
return USB_REQUEST_STATUS_STALL;
}
usb_transfer_schedule_ack(endpoint->in);
}
return USB_REQUEST_STATUS_OK;
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __USB_API_PRALINE_H__
#define __USB_API_PRALINE_H__
#include <usb_type.h>
#include <usb_request.h>
usb_request_status_t usb_vendor_request_p2_ctrl(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage);
usb_request_status_t usb_vendor_request_p1_ctrl(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage);
usb_request_status_t usb_vendor_request_clkin_ctrl(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage);
usb_request_status_t usb_vendor_request_set_narrowband_filter(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage);
usb_request_status_t usb_vendor_request_set_fpga_bitstream(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage);
#endif /* end of include guard: __USB_API_PRALINE_H__ */

View File

@@ -25,8 +25,10 @@
#include <user_config.h>
#include <hackrf_core.h>
#include <usb_queue.h>
#include <max2831.h>
#include <max283x.h>
#include <rffc5071.h>
#include <ice40_spi.h>
#include <stddef.h>
#include <stdint.h>
@@ -38,6 +40,7 @@ usb_request_status_t usb_vendor_request_write_max283x(
const usb_transfer_stage_t stage)
{
if (stage == USB_TRANSFER_STAGE_SETUP) {
#ifndef PRALINE
if (endpoint->setup.index < MAX2837_NUM_REGS) {
if (endpoint->setup.value < MAX2837_DATA_REGS_MAX_VALUE) {
max283x_reg_write(
@@ -48,6 +51,18 @@ usb_request_status_t usb_vendor_request_write_max283x(
return USB_REQUEST_STATUS_OK;
}
}
#else
if (endpoint->setup.index < MAX2831_NUM_REGS) {
if (endpoint->setup.value < MAX2831_DATA_REGS_MAX_VALUE) {
max2831_reg_write(
&max283x,
endpoint->setup.index,
endpoint->setup.value);
usb_transfer_schedule_ack(endpoint->in);
return USB_REQUEST_STATUS_OK;
}
}
#endif
return USB_REQUEST_STATUS_STALL;
} else {
return USB_REQUEST_STATUS_OK;
@@ -59,6 +74,7 @@ usb_request_status_t usb_vendor_request_read_max283x(
const usb_transfer_stage_t stage)
{
if (stage == USB_TRANSFER_STAGE_SETUP) {
#ifndef PRALINE
if (endpoint->setup.index < MAX2837_NUM_REGS) {
const uint16_t value =
max283x_reg_read(&max283x, endpoint->setup.index);
@@ -73,6 +89,22 @@ usb_request_status_t usb_vendor_request_read_max283x(
usb_transfer_schedule_ack(endpoint->out);
return USB_REQUEST_STATUS_OK;
}
#else
if (endpoint->setup.index < MAX2831_NUM_REGS) {
const uint16_t value =
max2831_reg_read(&max283x, endpoint->setup.index);
endpoint->buffer[0] = value & 0xff;
endpoint->buffer[1] = value >> 8;
usb_transfer_schedule_block(
endpoint->in,
&endpoint->buffer,
2,
NULL,
NULL);
usb_transfer_schedule_ack(endpoint->out);
return USB_REQUEST_STATUS_OK;
}
#endif
return USB_REQUEST_STATUS_STALL;
} else {
return USB_REQUEST_STATUS_OK;
@@ -219,3 +251,39 @@ usb_request_status_t usb_vendor_request_user_config_set_bias_t_opts(
}
return USB_REQUEST_STATUS_OK;
}
#ifdef PRALINE
usb_request_status_t usb_vendor_request_spi_write_fpga(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage)
{
if (stage == USB_TRANSFER_STAGE_SETUP) {
ssp1_set_mode_ice40();
ice40_spi_write(&ice40, endpoint->setup.index, endpoint->setup.value);
ssp1_set_mode_max283x();
usb_transfer_schedule_ack(endpoint->in);
return USB_REQUEST_STATUS_OK;
}
return USB_REQUEST_STATUS_OK;
}
usb_request_status_t usb_vendor_request_spi_read_fpga(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage)
{
if (stage == USB_TRANSFER_STAGE_SETUP) {
ssp1_set_mode_ice40();
const uint8_t value = ice40_spi_read(&ice40, endpoint->setup.index);
ssp1_set_mode_max283x();
endpoint->buffer[0] = value;
usb_transfer_schedule_block(
endpoint->in,
&endpoint->buffer,
1,
NULL,
NULL);
usb_transfer_schedule_ack(endpoint->out);
}
return USB_REQUEST_STATUS_OK;
}
#endif

View File

@@ -57,5 +57,11 @@ usb_request_status_t usb_vendor_request_set_leds(
usb_request_status_t usb_vendor_request_user_config_set_bias_t_opts(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage);
usb_request_status_t usb_vendor_request_spi_write_fpga(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage);
usb_request_status_t usb_vendor_request_spi_read_fpga(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage);
#endif /* end of include guard: __USB_API_REGISTER_H__ */

View File

@@ -0,0 +1,130 @@
/*
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include <stdio.h>
#include <stddef.h>
#include <usb_queue.h>
#include <libopencm3/lpc43xx/cgu.h>
#include "usb_api_selftest.h"
#include "selftest.h"
#include "platform_detect.h"
static char* itoa(int val, int base)
{
static char buf[32] = {0};
int i = 30;
if (val == 0) {
buf[0] = '0';
buf[1] = '\0';
return &buf[0];
}
for (; val && i; --i, val /= base)
buf[i] = "0123456789abcdef"[val % base];
return &buf[i + 1];
}
void append(char** dest, size_t* capacity, const char* str)
{
for (int i = 0;; i++) {
if (capacity == 0 || str[i] == '\0') {
return;
}
*((*dest)++) = str[i];
*capacity -= 1;
}
}
void generate_selftest_report(void)
{
char* s = &selftest.report.msg[0];
size_t c = sizeof(selftest.report.msg);
#ifdef RAD1O
append(&s, &c, "Mixer: MAX2871, ID: ");
append(&s, &c, itoa(selftest.mixer_id, 10));
append(&s, &c, "\n");
#else
append(&s, &c, "Mixer: RFFC5072, ID: ");
append(&s, &c, itoa(selftest.mixer_id >> 3, 10));
append(&s, &c, ", Rev: ");
append(&s, &c, itoa(selftest.mixer_id & 0x7, 10));
append(&s, &c, "\n");
#endif
append(&s, &c, "Clock: Si5351");
append(&s, &c, ", Rev: ");
append(&s, &c, itoa(selftest.si5351_rev_id, 10));
append(&s, &c, ", readback: ");
append(&s, &c, selftest.si5351_readback_ok ? "OK" : "FAIL");
append(&s, &c, "\n");
#ifdef PRALINE
append(&s, &c, "Transceiver: MAX2831, LD pin test: ");
append(&s, &c, selftest.max2831_ld_test_ok ? "PASS" : "FAIL");
append(&s, &c, "\n");
#else
append(&s, &c, "Transceiver: ");
append(&s,
&c,
(detected_platform() == BOARD_ID_HACKRF1_R9 ? "MAX2839" : "MAX2837"));
append(&s, &c, ", readback success: ");
append(&s, &c, itoa(selftest.max283x_readback_register_count, 10));
append(&s, &c, "/");
append(&s, &c, itoa(selftest.max283x_readback_total_registers, 10));
if (selftest.max283x_readback_register_count <
selftest.max283x_readback_total_registers) {
append(&s, &c, ", bad value: 0x");
append(&s, &c, itoa(selftest.max283x_readback_bad_value, 10));
append(&s, &c, ", expected: 0x");
append(&s, &c, itoa(selftest.max283x_readback_expected_value, 10));
}
append(&s, &c, "\n");
#endif
#ifndef RAD1O
append(&s, &c, "32kHz oscillator: ");
append(&s, &c, selftest.rtc_osc_ok ? "PASS" : "FAIL");
append(&s, &c, "\n");
#endif
#ifdef PRALINE
append(&s, &c, "SGPIO RX test: ");
append(&s, &c, selftest.sgpio_rx_ok ? "PASS" : "FAIL");
append(&s, &c, "\n");
append(&s, &c, "Loopback test: ");
append(&s, &c, selftest.xcvr_loopback_ok ? "PASS" : "FAIL");
append(&s, &c, "\n");
#endif
}
usb_request_status_t usb_vendor_request_read_selftest(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage)
{
if (stage == USB_TRANSFER_STAGE_SETUP) {
generate_selftest_report();
usb_transfer_schedule_block(
endpoint->in,
&selftest.report,
sizeof(selftest.report),
NULL,
NULL);
usb_transfer_schedule_ack(endpoint->out);
return USB_REQUEST_STATUS_OK;
} else {
return USB_REQUEST_STATUS_OK;
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __USB_API_SELFTEST_H
#define __USB_API_SELFTEST_H
#include <usb_type.h>
#include <usb_request.h>
usb_request_status_t usb_vendor_request_read_selftest(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage);
#endif // __USB_API_SELFTEST_H

View File

@@ -33,11 +33,15 @@
#include <libopencm3/lpc43xx/m4/nvic.h>
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define FREQ_GRANULARITY 1000000
#define MAX_RANGES 10
#define THROWAWAY_BUFFERS 2
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define FREQ_GRANULARITY 1000000
#define MAX_RANGES 10
#ifndef PRALINE
#define THROWAWAY_BUFFERS 2
#else
#define THROWAWAY_BUFFERS 1
#endif
static uint64_t sweep_freq;
static uint16_t frequencies[MAX_RANGES * 2];
@@ -88,7 +92,11 @@ usb_request_status_t usb_vendor_request_init_sweep(
((uint16_t) (data[10 + i * 2]) << 8) + data[9 + i * 2];
}
sweep_freq = (uint64_t) frequencies[0] * FREQ_GRANULARITY;
set_freq(sweep_freq + offset);
radio_set_frequency(
&radio,
RADIO_CHANNEL0,
RADIO_FREQUENCY_RF,
(radio_frequency_t){.hz = sweep_freq + offset});
usb_transfer_schedule_ack(endpoint->in);
}
return USB_REQUEST_STATUS_OK;
@@ -99,9 +107,9 @@ void sweep_bulk_transfer_complete(void* user_data, unsigned int bytes_transferre
(void) user_data;
(void) bytes_transferred;
// For each buffer transferred, we need to bump the count by three buffers
// worth of data, to allow for the discarded buffers.
m0_state.m4_count += 3 * 0x4000;
// For each buffer transferred, we need to bump the count to allow
// for the buffer(s) that are to be discarded.
m0_state.m4_count += (THROWAWAY_BUFFERS + 1) * 0x4000;
}
void sweep_mode(uint32_t seq)
@@ -121,8 +129,8 @@ void sweep_mode(uint32_t seq)
// 4. M4 adds the sweep metadata at the start of the block and
// schedules a bulk transfer for the block.
//
// 5. M4 retunes - this takes about 760us worst-case, so should be
// complete before the M0 goes back to RX.
// 5. M4 retunes - this takes about 760us worst-case (300us on praline),
// so should be complete before the M0 goes back to RX.
//
// 6. M4 spins until the M0 mode changes to RX, then advances the
// m0_count limit by 16K and sets the next mode to WAIT.
@@ -152,8 +160,9 @@ void sweep_mode(uint32_t seq)
}
}
// Set M0 to switch back to RX after two more buffers.
m0_state.threshold += 0x8000;
// Set M0 to switch back to RX after we have received our
// discard buffers.
m0_state.threshold += (0x4000 * THROWAWAY_BUFFERS);
m0_state.next_mode = M0_MODE_RX;
// Write metadata to buffer.
@@ -178,7 +187,7 @@ void sweep_mode(uint32_t seq)
NULL);
// Use other buffer next time.
phase = (phase + 1) % 2;
phase = (phase + 1) % THROWAWAY_BUFFERS;
if (++blocks_queued == dwell_blocks) {
// Calculate next sweep frequency.
@@ -211,7 +220,11 @@ void sweep_mode(uint32_t seq)
}
// Retune to new frequency.
nvic_disable_irq(NVIC_USB0_IRQ);
set_freq(sweep_freq + offset);
radio_set_frequency(
&radio,
RADIO_CHANNEL0,
RADIO_FREQUENCY_RF,
(radio_frequency_t){.hz = sweep_freq + offset});
nvic_enable_irq(NVIC_USB0_IRQ);
blocks_queued = 0;
}

View File

@@ -78,7 +78,17 @@ usb_request_status_t usb_vendor_request_set_baseband_filter_bandwidth(
if (stage == USB_TRANSFER_STAGE_SETUP) {
const uint32_t bandwidth =
(endpoint->setup.index << 16) | endpoint->setup.value;
if (baseband_filter_bandwidth_set(bandwidth)) {
radio_error_t result = radio_set_filter(
&radio,
RADIO_CHANNEL0,
RADIO_FILTER_BASEBAND,
(radio_filter_t){.hz = bandwidth});
if (result == RADIO_OK) {
radio_filter_t real = radio_get_filter(
&radio,
RADIO_CHANNEL0,
RADIO_FILTER_BASEBAND);
hackrf_ui()->set_filter_bw(real.hz);
usb_transfer_schedule_ack(endpoint->in);
return USB_REQUEST_STATUS_OK;
}
@@ -103,7 +113,43 @@ usb_request_status_t usb_vendor_request_set_freq(
} else if (stage == USB_TRANSFER_STAGE_DATA) {
const uint64_t freq =
set_freq_params.freq_mhz * 1000000ULL + set_freq_params.freq_hz;
if (set_freq(freq)) {
radio_error_t result = radio_set_frequency(
&radio,
RADIO_CHANNEL0,
RADIO_FREQUENCY_RF,
(radio_frequency_t){.hz = freq});
if (result == RADIO_OK) {
usb_transfer_schedule_ack(endpoint->in);
return USB_REQUEST_STATUS_OK;
}
return USB_REQUEST_STATUS_STALL;
} else {
return USB_REQUEST_STATUS_OK;
}
}
usb_request_status_t usb_vendor_request_set_freq_explicit(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage)
{
if (stage == USB_TRANSFER_STAGE_SETUP) {
usb_transfer_schedule_block(
endpoint->out,
&explicit_params,
sizeof(struct set_freq_explicit_params),
NULL,
NULL);
return USB_REQUEST_STATUS_OK;
} else if (stage == USB_TRANSFER_STAGE_DATA) {
radio_error_t result = radio_set_frequency(
&radio,
RADIO_CHANNEL0,
RADIO_FREQUENCY_RF,
(radio_frequency_t){
.if_hz = explicit_params.if_freq_hz,
.lo_hz = explicit_params.lo_freq_hz,
.path = explicit_params.path});
if (result == RADIO_OK) {
usb_transfer_schedule_ack(endpoint->in);
return USB_REQUEST_STATUS_OK;
}
@@ -126,9 +172,15 @@ usb_request_status_t usb_vendor_request_set_sample_rate_frac(
NULL);
return USB_REQUEST_STATUS_OK;
} else if (stage == USB_TRANSFER_STAGE_DATA) {
if (sample_rate_frac_set(
set_sample_r_params.freq_hz * 2,
set_sample_r_params.divider)) {
radio_error_t result = radio_set_sample_rate(
&radio,
RADIO_CHANNEL0,
RADIO_SAMPLE_RATE_CLOCKGEN,
(radio_sample_rate_t){
.num = set_sample_r_params.freq_hz * 2,
.div = set_sample_r_params.divider,
});
if (result == RADIO_OK) {
usb_transfer_schedule_ack(endpoint->in);
return USB_REQUEST_STATUS_OK;
}
@@ -142,14 +194,17 @@ usb_request_status_t usb_vendor_request_set_amp_enable(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage)
{
radio_gain_t off = {.enable = false};
radio_gain_t on = {.enable = true};
if (stage == USB_TRANSFER_STAGE_SETUP) {
switch (endpoint->setup.value) {
case 0:
rf_path_set_lna(&rf_path, 0);
radio_set_gain(&radio, RADIO_CHANNEL0, RADIO_GAIN_RF_AMP, off);
usb_transfer_schedule_ack(endpoint->in);
return USB_REQUEST_STATUS_OK;
case 1:
rf_path_set_lna(&rf_path, 1);
radio_set_gain(&radio, RADIO_CHANNEL0, RADIO_GAIN_RF_AMP, on);
usb_transfer_schedule_ack(endpoint->in);
return USB_REQUEST_STATUS_OK;
default:
@@ -165,8 +220,9 @@ usb_request_status_t usb_vendor_request_set_lna_gain(
const usb_transfer_stage_t stage)
{
if (stage == USB_TRANSFER_STAGE_SETUP) {
uint8_t value;
value = max283x_set_lna_gain(&max283x, endpoint->setup.index);
radio_gain_t gain = {.db = endpoint->setup.index};
uint8_t value =
radio_set_gain(&radio, RADIO_CHANNEL0, RADIO_GAIN_RX_LNA, gain);
endpoint->buffer[0] = value;
if (value) {
hackrf_ui()->set_bb_lna_gain(endpoint->setup.index);
@@ -188,8 +244,9 @@ usb_request_status_t usb_vendor_request_set_vga_gain(
const usb_transfer_stage_t stage)
{
if (stage == USB_TRANSFER_STAGE_SETUP) {
uint8_t value;
value = max283x_set_vga_gain(&max283x, endpoint->setup.index);
radio_gain_t gain = {.db = endpoint->setup.index};
uint8_t value =
radio_set_gain(&radio, RADIO_CHANNEL0, RADIO_GAIN_RX_VGA, gain);
endpoint->buffer[0] = value;
if (value) {
hackrf_ui()->set_bb_vga_gain(endpoint->setup.index);
@@ -211,8 +268,9 @@ usb_request_status_t usb_vendor_request_set_txvga_gain(
const usb_transfer_stage_t stage)
{
if (stage == USB_TRANSFER_STAGE_SETUP) {
uint8_t value;
value = max283x_set_txvga_gain(&max283x, endpoint->setup.index);
radio_gain_t gain = {.db = endpoint->setup.index};
uint8_t value =
radio_set_gain(&radio, RADIO_CHANNEL0, RADIO_GAIN_TX_VGA, gain);
endpoint->buffer[0] = value;
if (value) {
hackrf_ui()->set_bb_tx_vga_gain(endpoint->setup.index);
@@ -233,14 +291,25 @@ usb_request_status_t usb_vendor_request_set_antenna_enable(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage)
{
radio_antenna_t off = {.enable = false};
radio_antenna_t on = {.enable = true};
if (stage == USB_TRANSFER_STAGE_SETUP) {
switch (endpoint->setup.value) {
case 0:
rf_path_set_antenna(&rf_path, 0);
radio_set_antenna(
&radio,
RADIO_CHANNEL0,
RADIO_ANTENNA_BIAS_TEE,
off);
usb_transfer_schedule_ack(endpoint->in);
return USB_REQUEST_STATUS_OK;
case 1:
rf_path_set_antenna(&rf_path, 1);
radio_set_antenna(
&radio,
RADIO_CHANNEL0,
RADIO_ANTENNA_BIAS_TEE,
on);
usb_transfer_schedule_ack(endpoint->in);
return USB_REQUEST_STATUS_OK;
default:
@@ -251,41 +320,9 @@ usb_request_status_t usb_vendor_request_set_antenna_enable(
}
}
usb_request_status_t usb_vendor_request_set_freq_explicit(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage)
{
if (stage == USB_TRANSFER_STAGE_SETUP) {
usb_transfer_schedule_block(
endpoint->out,
&explicit_params,
sizeof(struct set_freq_explicit_params),
NULL,
NULL);
return USB_REQUEST_STATUS_OK;
} else if (stage == USB_TRANSFER_STAGE_DATA) {
if (set_freq_explicit(
explicit_params.if_freq_hz,
explicit_params.lo_freq_hz,
explicit_params.path)) {
usb_transfer_schedule_ack(endpoint->in);
return USB_REQUEST_STATUS_OK;
}
return USB_REQUEST_STATUS_STALL;
} else {
return USB_REQUEST_STATUS_OK;
}
}
static volatile hw_sync_mode_t _hw_sync_mode = HW_SYNC_MODE_OFF;
static volatile uint32_t _tx_underrun_limit;
static volatile uint32_t _rx_overrun_limit;
void set_hw_sync_mode(const hw_sync_mode_t new_hw_sync_mode)
{
_hw_sync_mode = new_hw_sync_mode;
}
volatile transceiver_request_t transceiver_request = {
.mode = TRANSCEIVER_MODE_OFF,
.seq = 0,
@@ -311,12 +348,13 @@ void transceiver_shutdown(void)
led_off(LED2);
led_off(LED3);
rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_OFF);
radio_switch_mode(&radio, RADIO_CHANNEL0, TRANSCEIVER_MODE_OFF);
m0_set_mode(M0_MODE_IDLE);
}
void transceiver_startup(const transceiver_mode_t mode)
{
radio_switch_mode(&radio, RADIO_CHANNEL0, mode);
hackrf_ui()->set_transceiver_mode(mode);
switch (mode) {
@@ -324,14 +362,12 @@ void transceiver_startup(const transceiver_mode_t mode)
case TRANSCEIVER_MODE_RX:
led_off(LED3);
led_on(LED2);
rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_RX);
m0_set_mode(M0_MODE_RX);
m0_state.shortfall_limit = _rx_overrun_limit;
break;
case TRANSCEIVER_MODE_TX:
led_off(LED2);
led_on(LED3);
rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_TX);
m0_set_mode(M0_MODE_TX_START);
m0_state.shortfall_limit = _tx_underrun_limit;
break;
@@ -340,7 +376,8 @@ void transceiver_startup(const transceiver_mode_t mode)
}
activate_best_clock_source();
hw_sync_enable(_hw_sync_mode);
hw_sync_mode_t trigger_mode = radio_get_trigger_mode(&radio, RADIO_CHANNEL0);
hw_sync_enable(trigger_mode);
}
usb_request_status_t usb_vendor_request_set_transceiver_mode(
@@ -370,9 +407,15 @@ usb_request_status_t usb_vendor_request_set_hw_sync_mode(
const usb_transfer_stage_t stage)
{
if (stage == USB_TRANSFER_STAGE_SETUP) {
set_hw_sync_mode(endpoint->setup.value);
usb_transfer_schedule_ack(endpoint->in);
return USB_REQUEST_STATUS_OK;
radio_error_t result = radio_set_trigger_mode(
&radio,
RADIO_CHANNEL0,
endpoint->setup.value);
if (result == RADIO_OK) {
usb_transfer_schedule_ack(endpoint->in);
return USB_REQUEST_STATUS_OK;
}
return USB_REQUEST_STATUS_STALL;
} else {
return USB_REQUEST_STATUS_OK;
}

View File

@@ -79,7 +79,6 @@ usb_request_status_t usb_vendor_request_set_rx_overrun_limit(
void request_transceiver_mode(transceiver_mode_t mode);
void transceiver_startup(transceiver_mode_t mode);
void transceiver_shutdown(void);
void start_streaming_on_hw_sync();
void rx_mode(uint32_t seq);
void tx_mode(uint32_t seq);
void off_mode(uint32_t seq);

View File

@@ -27,7 +27,7 @@
#define USB_VENDOR_ID (0x1D50)
#ifdef HACKRF_ONE
#if (defined HACKRF_ONE || defined PRALINE)
#define USB_PRODUCT_ID (0x6089)
#elif JAWBREAKER
#define USB_PRODUCT_ID (0x604B)
@@ -37,7 +37,7 @@
#define USB_PRODUCT_ID (0xFFFF)
#endif
#define USB_API_VERSION (0x0108)
#define USB_API_VERSION (0x0109)
#define USB_WORD(x) (x & 0xFF), ((x >> 8) & 0xFF)
@@ -226,6 +226,16 @@ uint8_t usb_descriptor_string_product[] = {
'd', 0x00,
'1', 0x00,
'o', 0x00,
#elif PRALINE
16, // bLength
USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType
'P', 0x00,
'r', 0x00,
'a', 0x00,
'l', 0x00,
'i', 0x00,
'n', 0x00,
'e', 0x00,
#else
14, // bLength
USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType

View File

@@ -59,6 +59,46 @@ int parse_int(char* s, uint8_t* const value)
}
}
int parse_p1_ctrl_signal(char* s, enum p1_ctrl_signal* const signal)
{
if (strcasecmp("trigger_in", s) == 0) {
*signal = P1_SIGNAL_TRIGGER_IN;
} else if (strcasecmp("aux_clk1", s) == 0) {
*signal = P1_SIGNAL_AUX_CLK1;
} else if (strcasecmp("clkin", s) == 0) {
*signal = P1_SIGNAL_CLKIN;
} else if (strcasecmp("trigger_out", s) == 0) {
*signal = P1_SIGNAL_TRIGGER_OUT;
} else if (strcasecmp("p22_clkin", s) == 0) {
*signal = P1_SIGNAL_P22_CLKIN;
} else if (strcasecmp("pps_out", s) == 0) {
*signal = P1_SIGNAL_P2_5;
} else if (strcasecmp("off", s) == 0) {
*signal = P1_SIGNAL_NC;
} else if (strcasecmp("aux_clk2", s) == 0) {
*signal = P1_SIGNAL_AUX_CLK2;
} else {
fprintf(stderr, "Invalid signal '%s'\n", s);
return HACKRF_ERROR_INVALID_PARAM;
}
return HACKRF_SUCCESS;
}
int parse_p2_ctrl_signal(char* s, enum p2_ctrl_signal* const signal)
{
if (strcasecmp("clkout", s) == 0) {
*signal = P2_SIGNAL_CLK3;
} else if (strcasecmp("trigger_in", s) == 0) {
*signal = P2_SIGNAL_TRIGGER_IN;
} else if (strcasecmp("trigger_out", s) == 0) {
*signal = P2_SIGNAL_TRIGGER_OUT;
} else {
fprintf(stderr, "Invalid signal '%s'\n", s);
return HACKRF_ERROR_INVALID_PARAM;
}
return HACKRF_SUCCESS;
}
int si5351c_read_register(hackrf_device* device, const uint16_t register_number)
{
uint16_t register_value;
@@ -247,6 +287,10 @@ static void usage()
printf("\t-a, --all: read settings for all clocks\n");
printf("\t-i, --clkin: get CLKIN status\n");
printf("\t-o, --clkout <clkout_enable>: enable/disable CLKOUT\n");
printf("\t-1, --p1 <signal>: select the HackRF Pro P1 SMA connector signal (default: clkin)\n");
printf("\tone of: clkin, trigger_in, trigger_out, p22_clkin, pps_out, aux_clk1, aux_clk2, off\n");
printf("\t-2, --p2 <signal>: select the signal for the HackRF Pro P2 SMA connector (default: clkout)\n");
printf("\tone of: clkout, trigger_in, trigger_out\n");
printf("\t-d, --device <serial_number>: Serial number of desired HackRF.\n");
printf("\nExamples:\n");
printf("\thackrf_clock -r 3 : prints settings for CLKOUT\n");
@@ -258,6 +302,8 @@ static struct option long_options[] = {
{"all", no_argument, 0, 'a'},
{"clkin", required_argument, 0, 'i'},
{"clkout", required_argument, 0, 'o'},
{"p1", required_argument, 0, '1'},
{"p2", required_argument, 0, '2'},
{"device", required_argument, 0, 'd'},
{0, 0, 0, 0},
};
@@ -272,6 +318,10 @@ int main(int argc, char** argv)
bool clkin = false;
uint8_t clkout_enable;
uint8_t clkin_status;
bool p1_ctrl = false;
bool p2_ctrl = false;
enum p1_ctrl_signal p1_signal = P1_SIGNAL_CLKIN;
enum p2_ctrl_signal p2_signal = P2_SIGNAL_CLK3;
const char* serial_number = NULL;
int result = hackrf_init();
@@ -282,8 +332,12 @@ int main(int argc, char** argv)
return EXIT_FAILURE;
}
while ((opt = getopt_long(argc, argv, "r:aio:d:h?", long_options, &option_index)) !=
EOF) {
while ((opt = getopt_long(
argc,
argv,
"r:aio:1:2:d:h?",
long_options,
&option_index)) != EOF) {
switch (opt) {
case 'r':
read = true;
@@ -303,6 +357,16 @@ int main(int argc, char** argv)
result = parse_int(optarg, &clkout_enable);
break;
case '1':
p1_ctrl = true;
result = parse_p1_ctrl_signal(optarg, &p1_signal);
break;
case '2':
p2_ctrl = true;
result = parse_p2_ctrl_signal(optarg, &p2_signal);
break;
case 'd':
serial_number = optarg;
break;
@@ -326,7 +390,7 @@ int main(int argc, char** argv)
}
}
if (!clkin && !clkout && !read) {
if (!clkin && !clkout && !read && !p1_ctrl && !p2_ctrl) {
fprintf(stderr, "An operation must be specified.\n");
usage();
return EXIT_FAILURE;
@@ -372,6 +436,26 @@ int main(int argc, char** argv)
}
}
if (p1_ctrl) {
result = hackrf_set_p1_ctrl(device, p1_signal);
if (result) {
printf("hackrf_set_p1_ctrl() failed: %s (%d)\n",
hackrf_error_name(result),
result);
return EXIT_FAILURE;
}
}
if (p2_ctrl) {
result = hackrf_set_p2_ctrl(device, p2_signal);
if (result) {
printf("hackrf_set_p2_ctrl() failed: %s (%d)\n",
hackrf_error_name(result),
result);
return EXIT_FAILURE;
}
}
result = hackrf_close(device);
if (result) {
printf("hackrf_close() failed: %s (%d)\n",

View File

@@ -32,6 +32,15 @@
#define REGISTER_INVALID 32767
enum parts {
PART_NONE = 0,
PART_MAX2837 = 1,
PART_SI5351C = 2,
PART_RFFC5072 = 3,
PART_MAX2831 = 4,
PART_GATEWARE = 5,
};
int parse_int(char* s, uint32_t* const value)
{
uint_fast8_t base = 10;
@@ -60,11 +69,30 @@ int parse_int(char* s, uint32_t* const value)
}
}
int max2837_read_register(hackrf_device* device, const uint16_t register_number)
int max283x_read_register(
hackrf_device* device,
const uint16_t register_number,
uint8_t part)
{
uint16_t register_value;
int result =
hackrf_max2837_read(device, (uint8_t) register_number, &register_value);
int result = HACKRF_SUCCESS;
switch (part) {
case PART_MAX2837:
result = hackrf_max2837_read(
device,
(uint8_t) register_number,
&register_value);
break;
case PART_MAX2831:
result = hackrf_max2831_read(
device,
(uint8_t) register_number,
&register_value);
break;
default:
return HACKRF_ERROR_INVALID_PARAM;
}
if (result == HACKRF_SUCCESS) {
printf("[%2d] -> 0x%03x\n", register_number, register_value);
@@ -76,13 +104,25 @@ int max2837_read_register(hackrf_device* device, const uint16_t register_number)
return result;
}
int max2837_read_registers(hackrf_device* device)
int max283x_read_registers(hackrf_device* device, uint8_t part)
{
uint16_t register_number;
uint16_t register_count;
int result = HACKRF_SUCCESS;
for (register_number = 0; register_number < 32; register_number++) {
result = max2837_read_register(device, register_number);
switch (part) {
case PART_MAX2837:
register_count = 32;
break;
case PART_MAX2831:
register_count = 16;
break;
default:
return HACKRF_ERROR_INVALID_PARAM;
}
for (register_number = 0; register_number < register_count; register_number++) {
result = max283x_read_register(device, register_number, part);
if (result != HACKRF_SUCCESS) {
break;
}
@@ -90,13 +130,30 @@ int max2837_read_registers(hackrf_device* device)
return result;
}
int max2837_write_register(
int max283x_write_register(
hackrf_device* device,
const uint16_t register_number,
const uint16_t register_value)
const uint16_t register_value,
uint8_t part)
{
int result = HACKRF_SUCCESS;
result = hackrf_max2837_write(device, (uint8_t) register_number, register_value);
switch (part) {
case PART_MAX2837:
result = hackrf_max2837_write(
device,
(uint8_t) register_number,
register_value);
break;
case PART_MAX2831:
result = hackrf_max2831_write(
device,
(uint8_t) register_number,
register_value);
break;
default:
return HACKRF_ERROR_INVALID_PARAM;
}
if (result == HACKRF_SUCCESS) {
printf("0x%03x -> [%2d]\n", register_value, register_number);
@@ -150,7 +207,7 @@ int si5351c_write_register(
if (result == HACKRF_SUCCESS) {
printf("0x%2x -> [%3d]\n", register_value, register_number);
} else {
printf("hackrf_max2837_write() failed: %s (%d)\n",
printf("hackrf_si5351c_write() failed: %s (%d)\n",
hackrf_error_name(result),
result);
}
@@ -360,22 +417,54 @@ int rffc5072_write_register(
return result;
}
enum parts {
PART_NONE = 0,
PART_MAX2837 = 1,
PART_SI5351C = 2,
PART_RFFC5072 = 3,
};
int fpga_spi_read_register(hackrf_device* device, const uint16_t register_number)
{
uint8_t register_value;
int result =
hackrf_fpga_spi_read(device, (uint8_t) register_number, &register_value);
if (result == HACKRF_SUCCESS) {
printf("[%2d] -> 0x%02x\n", register_number, register_value);
} else {
printf("hackrf_fpga_spi_read() failed: %s (%d)\n",
hackrf_error_name(result),
result);
}
return result;
}
int fpga_spi_write_register(
hackrf_device* device,
const uint16_t register_number,
const uint16_t register_value)
{
int result = HACKRF_SUCCESS;
result = hackrf_fpga_spi_write(device, (uint8_t) register_number, register_value);
if (result == HACKRF_SUCCESS) {
printf("0x%02x -> [%2d]\n", register_value, register_number);
} else {
printf("hackrf_fpga_spi_write() failed: %s (%d)\n",
hackrf_error_name(result),
result);
}
return result;
}
int read_register(hackrf_device* device, uint8_t part, const uint16_t register_number)
{
switch (part) {
case PART_MAX2837:
return max2837_read_register(device, register_number);
case PART_MAX2831:
return max283x_read_register(device, register_number, part);
case PART_SI5351C:
return si5351c_read_register(device, register_number);
case PART_RFFC5072:
return rffc5072_read_register(device, register_number);
case PART_GATEWARE:
return fpga_spi_read_register(device, register_number);
}
return HACKRF_ERROR_INVALID_PARAM;
}
@@ -384,7 +473,8 @@ int read_registers(hackrf_device* device, uint8_t part)
{
switch (part) {
case PART_MAX2837:
return max2837_read_registers(device);
case PART_MAX2831:
return max283x_read_registers(device, part);
case PART_SI5351C:
return si5351c_read_registers(device);
case PART_RFFC5072:
@@ -401,11 +491,18 @@ int write_register(
{
switch (part) {
case PART_MAX2837:
return max2837_write_register(device, register_number, register_value);
case PART_MAX2831:
return max283x_write_register(
device,
register_number,
register_value,
part);
case PART_SI5351C:
return si5351c_write_register(device, register_number, register_value);
case PART_RFFC5072:
return rffc5072_write_register(device, register_number, register_value);
case PART_GATEWARE:
return fpga_spi_write_register(device, register_number, register_value);
}
return HACKRF_ERROR_INVALID_PARAM;
}
@@ -465,19 +562,26 @@ static void usage()
printf("\t-w, --write <v>: write register specified by last -n argument with value <v>\n");
printf("\t-c, --config: print SI5351C multisynth configuration information\n");
printf("\t-d, --device <s>: specify a particular device by serial number\n");
printf("\t-m, --max2837: target MAX2837\n");
printf("\t-m, --max283x: target MAX283x\n");
printf("\t-s, --si5351c: target SI5351C\n");
printf("\t-f, --rffc5072: target RFFC5072\n");
printf("\t-g, --gateware: target gateware registers\n");
printf("\t-P, --fpga <n>: load the n-th bitstream to the FPGA\n");
printf("\t-1, --p1 <n>: P1 control\n");
printf("\t-2, --p2 <n>: P2 control\n");
printf("\t-C, --clkin <0/1>: CLKIN control (0 for P1_CLKIN, 1 for P22_CLKIN)\n");
printf("\t-N, --narrowband <0/1>: narrowband filter disable/enable\n");
printf("\t-S, --state: display M0 state\n");
printf("\t-T, --tx-underrun-limit <n>: set TX underrun limit in bytes (0 for no limit)\n");
printf("\t-R, --rx-overrun-limit <n>: set RX overrun limit in bytes (0 for no limit)\n");
printf("\t-u, --ui <1/0>: enable/disable UI\n");
printf("\t-l, --leds <state>: configure LED state (0 for all off, 1 for default)\n");
printf("\t-t, --selftest: read self-test report\n");
printf("\nExamples:\n");
printf("\thackrf_debug --si5351c -n 0 -r # reads from si5351c register 0\n");
printf("\thackrf_debug --si5351c -c # displays si5351c multisynth configuration\n");
printf("\thackrf_debug --rffc5072 -r # reads all rffc5072 registers\n");
printf("\thackrf_debug --max2837 -n 10 -w 22 # writes max2837 register 10 with 22 decimal\n");
printf("\thackrf_debug --max283x -n 10 -w 22 # writes max283x register 10 with 22 decimal\n");
printf("\thackrf_debug --state # displays M0 state\n");
}
@@ -489,19 +593,28 @@ static struct option long_options[] = {
{"device", required_argument, 0, 'd'},
{"help", no_argument, 0, 'h'},
{"max2837", no_argument, 0, 'm'},
{"max283x", no_argument, 0, 'm'},
{"si5351c", no_argument, 0, 's'},
{"rffc5072", no_argument, 0, 'f'},
{"gateware", no_argument, 0, 'g'},
{"fpga", required_argument, 0, 'P'},
{"p1", required_argument, 0, '1'},
{"p2", required_argument, 0, '2'},
{"clkin", required_argument, 0, 'C'},
{"narrowband", required_argument, 0, 'N'},
{"state", no_argument, 0, 'S'},
{"tx-underrun-limit", required_argument, 0, 'T'},
{"rx-overrun-limit", required_argument, 0, 'R'},
{"ui", required_argument, 0, 'u'},
{"leds", required_argument, 0, 'l'},
{"selftest", no_argument, 0, 't'},
{0, 0, 0, 0},
};
int main(int argc, char** argv)
{
int opt;
uint8_t board_id = BOARD_ID_UNDETECTED;
uint32_t register_number = REGISTER_INVALID;
uint32_t register_value;
hackrf_device* device = NULL;
@@ -518,8 +631,19 @@ int main(int argc, char** argv)
uint32_t led_state;
uint32_t tx_limit;
uint32_t rx_limit;
uint32_t p1_state;
uint32_t p2_state;
uint32_t clkin_state;
uint32_t narrowband_state;
uint32_t bitstream_index;
bool set_tx_limit = false;
bool set_rx_limit = false;
bool set_p1 = false;
bool set_p2 = false;
bool set_clkin = false;
bool set_narrowband = false;
bool set_fpga_bitstream = false;
bool read_selftest = false;
int result = hackrf_init();
if (result) {
@@ -532,7 +656,7 @@ int main(int argc, char** argv)
while ((opt = getopt_long(
argc,
argv,
"n:rw:d:cmsfST:R:h?u:l:",
"n:rw:d:cmsfg1:2:C:N:P:ST:R:h?u:l:t",
long_options,
&option_index)) != EOF) {
switch (opt) {
@@ -594,6 +718,39 @@ int main(int argc, char** argv)
part = PART_RFFC5072;
break;
case 'g':
if (part != PART_NONE) {
fprintf(stderr, "Only one part can be specified.'\n");
return EXIT_FAILURE;
}
part = PART_GATEWARE;
break;
case '1':
set_p1 = true;
result = parse_int(optarg, &p1_state);
break;
case '2':
set_p2 = true;
result = parse_int(optarg, &p2_state);
break;
case 'C':
set_clkin = true;
result = parse_int(optarg, &clkin_state);
break;
case 'N':
set_narrowband = true;
result = parse_int(optarg, &narrowband_state);
break;
case 'P':
set_fpga_bitstream = true;
result = parse_int(optarg, &bitstream_index);
break;
case 'u':
set_ui = true;
result = parse_int(optarg, &ui_enable);
@@ -603,6 +760,9 @@ int main(int argc, char** argv)
set_leds = true;
result = parse_int(optarg, &led_state);
break;
case 't':
read_selftest = true;
break;
case 'h':
case '?':
@@ -642,14 +802,16 @@ int main(int argc, char** argv)
}
if (!(write || read || dump_config || dump_state || set_tx_limit ||
set_rx_limit || set_ui || set_leds)) {
set_rx_limit || set_ui || set_leds || set_p1 || set_p2 || set_clkin ||
set_narrowband || set_fpga_bitstream || read_selftest)) {
fprintf(stderr, "Specify read, write, or config option.\n");
usage();
return EXIT_FAILURE;
}
if (part == PART_NONE && !set_ui && !dump_state && !set_tx_limit &&
!set_rx_limit && !set_leds) {
!set_rx_limit && !set_leds && !set_p1 && !set_p2 && !set_clkin &&
!set_narrowband && !set_fpga_bitstream && !read_selftest) {
fprintf(stderr, "Specify a part to read, write, or print config from.\n");
usage();
return EXIT_FAILURE;
@@ -663,6 +825,20 @@ int main(int argc, char** argv)
return EXIT_FAILURE;
}
if (part == PART_MAX2837) {
result = hackrf_board_id_read(device, &board_id);
if (result != HACKRF_SUCCESS) {
fprintf(stderr,
"hackrf_board_id_read() failed: %s (%d)\n",
hackrf_error_name(result),
result);
return EXIT_FAILURE;
}
if (board_id == BOARD_ID_PRALINE) {
part = PART_MAX2831;
}
}
if (write) {
result = write_register(device, part, register_number, register_value);
}
@@ -699,6 +875,56 @@ int main(int argc, char** argv)
}
}
if (set_p1) {
result = hackrf_set_p1_ctrl(device, p1_state);
if (result != HACKRF_SUCCESS) {
printf("hackrf_set_p1_ctrl() failed: %s (%d)\n",
hackrf_error_name(result),
result);
return EXIT_FAILURE;
}
}
if (set_p2) {
result = hackrf_set_p2_ctrl(device, p2_state);
if (result != HACKRF_SUCCESS) {
printf("hackrf_set_p2_ctrl() failed: %s (%d)\n",
hackrf_error_name(result),
result);
return EXIT_FAILURE;
}
}
if (set_clkin) {
result = hackrf_set_clkin_ctrl(device, clkin_state);
if (result != HACKRF_SUCCESS) {
printf("hackrf_set_clkin_ctrl() failed: %s (%d)\n",
hackrf_error_name(result),
result);
return EXIT_FAILURE;
}
}
if (set_narrowband) {
result = hackrf_set_narrowband_filter(device, narrowband_state);
if (result != HACKRF_SUCCESS) {
printf("hackrf_set_narrowband_filter() failed: %s (%d)\n",
hackrf_error_name(result),
result);
return EXIT_FAILURE;
}
}
if (set_fpga_bitstream) {
result = hackrf_set_fpga_bitstream(device, bitstream_index);
if (result != HACKRF_SUCCESS) {
printf("hackrf_set_fpga_bitstream() failed: %s (%d)\n",
hackrf_error_name(result),
result);
return EXIT_FAILURE;
}
}
if (dump_state) {
hackrf_m0_state state;
result = hackrf_get_m0_state(device, &state);
@@ -725,6 +951,19 @@ int main(int argc, char** argv)
result = hackrf_set_leds(device, led_state);
}
if (read_selftest) {
hackrf_selftest selftest;
result = hackrf_read_selftest(device, &selftest);
if (result != HACKRF_SUCCESS) {
printf("hackrf_read_selftest() failed: %s (%d)\n",
hackrf_error_name(result),
result);
return EXIT_FAILURE;
}
printf("Self-test result: %s\n", selftest.pass ? "PASS" : "FAIL");
printf("%s", selftest.msg);
}
result = hackrf_close(device);
if (result) {
printf("hackrf_close() failed: %s (%d)\n",

View File

@@ -46,7 +46,7 @@ void print_board_rev(uint8_t board_rev)
}
}
void print_supported_platform(uint32_t platform, uint8_t board_id)
void print_supported_platform(uint32_t platform, uint8_t board_id, uint8_t board_rev)
{
printf("Hardware supported by installed firmware:\n");
if (platform & HACKRF_PLATFORM_JAWBREAKER) {
@@ -59,6 +59,13 @@ void print_supported_platform(uint32_t platform, uint8_t board_id)
(platform & HACKRF_PLATFORM_HACKRF1_R9)) {
printf(" HackRF One\n");
}
if (platform & HACKRF_PLATFORM_PRALINE) {
if (board_rev & HACKRF_BOARD_REV_GSG) {
printf(" HackRF Pro\n");
} else {
printf(" Praline\n");
}
}
switch (board_id) {
case BOARD_ID_HACKRF1_OG:
if (!(platform & HACKRF_PLATFORM_HACKRF1_OG)) {
@@ -79,6 +86,11 @@ void print_supported_platform(uint32_t platform, uint8_t board_id)
break;
}
printf("Error: Firmware does not support hardware platform.\n");
case BOARD_ID_PRALINE:
if (platform & HACKRF_PLATFORM_PRALINE) {
break;
}
printf("Error: Firmware does not support hardware platform.\n");
}
}
@@ -188,7 +200,8 @@ int main(void)
read_partid_serialno.part_id[0],
read_partid_serialno.part_id[1]);
if ((usb_version >= 0x0106) && ((board_id == 2) || (board_id == 4))) {
if ((usb_version >= 0x0106) &&
((board_id == 2) || (board_id == 4) || (board_id == 5))) {
result = hackrf_board_rev_read(device, &board_rev);
if (result != HACKRF_SUCCESS) {
fprintf(stderr,
@@ -210,7 +223,7 @@ int main(void)
result);
return EXIT_FAILURE;
}
print_supported_platform(supported_platform, board_id);
print_supported_platform(supported_platform, board_id, board_rev);
}
result = hackrf_get_operacake_boards(device, &operacakes[0]);
@@ -247,6 +260,21 @@ int main(void)
}
#endif /* HACKRF_ISSUE_609_IS_FIXED */
if (usb_version >= 0x0109) {
hackrf_selftest selftest;
result = hackrf_read_selftest(device, &selftest);
if (result != HACKRF_SUCCESS) {
printf("hackrf_read_selftest() failed: %s (%d)\n",
hackrf_error_name(result),
result);
return EXIT_FAILURE;
}
if (!selftest.pass) {
printf("Self-test FAIL:\n");
printf("%s", selftest.msg);
}
}
result = hackrf_close(device);
if (result != HACKRF_SUCCESS) {
fprintf(stderr,

View File

@@ -96,7 +96,6 @@ int gettimeofday(struct timeval* tv, void* ignored)
#define OFFSET 7500000
#define BLOCKS_PER_TRANSFER 16
#define THROWAWAY_BLOCKS 2
#if defined _WIN32
#define m_sleep(a) Sleep((a))

Some files were not shown because too many files have changed in this diff Show More