mirror of
https://github.com/greatscottgadgets/hackrf.git
synced 2026-02-19 16:22:52 +01:00
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:
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
31
firmware/common/LPC43xx_M4_memory_rom_only.ld
Normal file
31
firmware/common/LPC43xx_M4_memory_rom_only.ld
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
227
firmware/common/fpga.c
Normal 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
31
firmware/common/fpga.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
124
firmware/common/ice40_spi.c
Normal 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);
|
||||
}
|
||||
48
firmware/common/ice40_spi.h
Normal file
48
firmware/common/ice40_spi.h
Normal 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
86
firmware/common/lz4_blk.c
Normal 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
30
firmware/common/lz4_blk.h
Normal 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
|
||||
39
firmware/common/m0_state.c
Normal file
39
firmware/common/m0_state.c
Normal 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) {}
|
||||
}
|
||||
64
firmware/common/m0_state.h
Normal file
64
firmware/common/m0_state.h
Normal 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
385
firmware/common/max2831.c
Normal 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
99
firmware/common/max2831.h
Normal 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
|
||||
132
firmware/common/max2831_regs.def
Normal file
132
firmware/common/max2831_regs.def
Normal 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
|
||||
101
firmware/common/max2831_target.c
Normal file
101
firmware/common/max2831_target.c
Normal 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;
|
||||
}
|
||||
30
firmware/common/max2831_target.h
Normal file
30
firmware/common/max2831_target.h
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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--) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
460
firmware/common/radio.c
Normal 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
264
firmware/common/radio.h
Normal 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__*/
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
23
firmware/common/selftest.c
Normal file
23
firmware/common/selftest.c
Normal 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;
|
||||
55
firmware/common/selftest.h
Normal file
55
firmware/common/selftest.h
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
220
firmware/common/tune_config.h
Normal file
220
firmware/common/tune_config.h
Normal 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__*/
|
||||
@@ -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 {
|
||||
|
||||
@@ -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__*/
|
||||
|
||||
@@ -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) ||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
357
firmware/fpga/amaranth_future/fixed.py
Normal file
357
firmware/fpga/amaranth_future/fixed.py
Normal 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
90
firmware/fpga/board.py
Normal 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
68
firmware/fpga/build.py
Normal 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))
|
||||
|
||||
BIN
firmware/fpga/build/praline_fpga.bin
Normal file
BIN
firmware/fpga/build/praline_fpga.bin
Normal file
Binary file not shown.
0
firmware/fpga/dsp/__init__.py
Normal file
0
firmware/fpga/dsp/__init__.py
Normal file
743
firmware/fpga/dsp/cic.py
Normal file
743
firmware/fpga/dsp/cic.py
Normal 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()
|
||||
141
firmware/fpga/dsp/dc_block.py
Normal file
141
firmware/fpga/dsp/dc_block.py
Normal 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
604
firmware/fpga/dsp/fir.py
Normal 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()
|
||||
829
firmware/fpga/dsp/fir_mac16.py
Normal file
829
firmware/fpga/dsp/fir_mac16.py
Normal 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
156
firmware/fpga/dsp/mcm.py
Normal 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
103
firmware/fpga/dsp/nco.py
Executable 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
|
||||
55
firmware/fpga/dsp/quarter_shift.py
Normal file
55
firmware/fpga/dsp/quarter_shift.py
Normal 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
|
||||
17
firmware/fpga/dsp/round.py
Normal file
17
firmware/fpga/dsp/round.py
Normal 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
|
||||
343
firmware/fpga/dsp/sb_mac16.py
Normal file
343
firmware/fpga/dsp/sb_mac16.py
Normal 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
|
||||
1
firmware/fpga/interface/__init__.py
Normal file
1
firmware/fpga/interface/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .max586x import MAX586xInterface
|
||||
66
firmware/fpga/interface/max586x.py
Normal file
66
firmware/fpga/interface/max586x.py
Normal 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
|
||||
424
firmware/fpga/interface/spi.py
Normal file
424
firmware/fpga/interface/spi.py
Normal 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
|
||||
3
firmware/fpga/requirements.txt
Normal file
3
firmware/fpga/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
amaranth==v0.5.8
|
||||
amaranth-boards @ git+https://github.com/amaranth-lang/amaranth-boards.git@23c66d6
|
||||
lz4
|
||||
215
firmware/fpga/top/ext_precision_rx.py
Normal file
215
firmware/fpga/top/ext_precision_rx.py
Normal 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())
|
||||
215
firmware/fpga/top/ext_precision_tx.py
Normal file
215
firmware/fpga/top/ext_precision_tx.py
Normal 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())
|
||||
227
firmware/fpga/top/half_precision.py
Normal file
227
firmware/fpga/top/half_precision.py
Normal 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())
|
||||
320
firmware/fpga/top/standard.py
Normal file
320
firmware/fpga/top/standard.py
Normal 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())
|
||||
57
firmware/fpga/util/__init__.py
Normal file
57
firmware/fpga/util/__init__.py
Normal 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
|
||||
|
||||
189
firmware/fpga/util/_stream.py
Normal file
189
firmware/fpga/util/_stream.py
Normal 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()
|
||||
59
firmware/fpga/util/lfsr.py
Normal file
59
firmware/fpga/util/lfsr.py
Normal 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
|
||||
@@ -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(
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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__*/
|
||||
|
||||
87
firmware/hackrf_usb/usb_api_praline.c
Normal file
87
firmware/hackrf_usb/usb_api_praline.c
Normal 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;
|
||||
}
|
||||
48
firmware/hackrf_usb/usb_api_praline.h
Normal file
48
firmware/hackrf_usb/usb_api_praline.h
Normal 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__ */
|
||||
@@ -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
|
||||
|
||||
@@ -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__ */
|
||||
|
||||
130
firmware/hackrf_usb/usb_api_selftest.c
Normal file
130
firmware/hackrf_usb/usb_api_selftest.c
Normal 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;
|
||||
}
|
||||
}
|
||||
32
firmware/hackrf_usb/usb_api_selftest.h
Normal file
32
firmware/hackrf_usb/usb_api_selftest.h
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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, ®ister_value);
|
||||
int result = HACKRF_SUCCESS;
|
||||
|
||||
switch (part) {
|
||||
case PART_MAX2837:
|
||||
result = hackrf_max2837_read(
|
||||
device,
|
||||
(uint8_t) register_number,
|
||||
®ister_value);
|
||||
break;
|
||||
case PART_MAX2831:
|
||||
result = hackrf_max2831_read(
|
||||
device,
|
||||
(uint8_t) register_number,
|
||||
®ister_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, ®ister_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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user