Merge pull request #1676 from mndza/rearrange-gateware-regs

This commit is contained in:
Michael Ossmann 2026-03-26 04:07:18 -04:00 committed by GitHub
commit a8661eef4a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 299 additions and 192 deletions

View file

@ -3,6 +3,7 @@ set -e
git submodule init
git submodule update
cd firmware/hackrf_usb
cmake -B build
rm -rf build
cmake -DBOARD=HACKRF_ONE -B build
cmake --build build
cd ../..

View file

@ -3,6 +3,7 @@ set -e
git submodule init
git submodule update
cd firmware/hackrf_usb
rm -rf build
cmake -DBOARD=PRALINE -B build
cmake --build build
cd ../..

View file

@ -719,8 +719,7 @@ class HackRF:
log(receive.stdout + receive.stderr)
fail(220 + self.unit_number)
# confirm that PLL locked to new source
expected_value = "0x01" if (enable and self.partner.revision != "r9") else "0x51"
# confirm that PLL A locked to new source
timeout = time.time() + 1
while time.time() < timeout:
debug = subprocess.run([self.partner.bin_dir + "/hackrf_debug", "-d",
@ -730,9 +729,17 @@ class HackRF:
if debug.returncode != 0:
log(debug.stderr)
fail(230 + self.unit_number)
if expected_value in debug.stdout:
output = str(debug.stdout).split()
try:
val = int(output[3], 0)
except:
log(debug.stderr)
log(traceback.format_exc())
fail(230 + self.unit_number)
# Check LOL_A
if not (val >> 5) & 1:
break
if expected_value not in debug.stdout:
if (val >> 5) & 1:
fail(232 + self.unit_number)
time.sleep(0.1)
else:

View file

@ -595,8 +595,7 @@ class HackRF:
log(receive.stdout + receive.stderr)
fail(220 + self.unit_number)
# confirm that PLL locked to new source
expected_value = "0x01" if (enable and self.partner.revision != "r9") else "0x51"
# confirm that PLL A locked to new source
timeout = time.time() + 1
while time.time() < timeout:
debug = subprocess.run([self.partner.bin_dir + "hackrf_debug", "-d",
@ -606,9 +605,17 @@ class HackRF:
if debug.returncode != 0:
log(debug.stderr)
fail(230 + self.unit_number)
if expected_value in debug.stdout:
output = str(debug.stdout).split()
try:
val = int(output[3], 0)
except:
log(debug.stderr)
log(traceback.format_exc())
fail(230 + self.unit_number)
# Check LOL_A
if not (val >> 5) & 1:
break
if expected_value not in debug.stdout:
if (val >> 5) & 1:
fail(232 + self.unit_number)
time.sleep(0.1)
else:
@ -1351,7 +1358,7 @@ def main():
eut.clkout_connected = True
tester.clkout_connected = True
if count > 0 and not args.fw_update:
if count > 0 and not args.fwupdate:
eut.activate_leds(False)
eut.test_serial()

View file

@ -30,8 +30,7 @@ void fpga_init(fpga_driver_t* const drv)
{
// Standard bitstream default register values.
set_FPGA_STANDARD_CTRL_DC_BLOCK(drv, true);
set_FPGA_STANDARD_CTRL_QUARTER_SHIFT_EN(drv, false);
set_FPGA_STANDARD_CTRL_QUARTER_SHIFT_UP(drv, false);
set_FPGA_STANDARD_RX_PSTEP(drv, 0);
set_FPGA_STANDARD_CTRL_PRBS(drv, false);
set_FPGA_STANDARD_CTRL_TRIGGER_EN(drv, false);
set_FPGA_STANDARD_TX_CTRL(drv, 0);
@ -105,8 +104,7 @@ void fpga_set_rx_quarter_shift_mode(
fpga_driver_t* const drv,
const fpga_quarter_shift_mode_t mode)
{
set_FPGA_STANDARD_CTRL_QUARTER_SHIFT_EN(drv, (mode >> 0) & 0b1);
set_FPGA_STANDARD_CTRL_QUARTER_SHIFT_UP(drv, (mode >> 1) & 0b1);
set_FPGA_STANDARD_RX_PSTEP(drv, (mode & 0b11) << 6);
fpga_regs_commit(drv);
}

View file

@ -25,8 +25,8 @@
#include <stdbool.h>
#include "ice40_spi.h"
/* Up to 6 registers, each containing up to 8 bits of data */
#define FPGA_NUM_REGS 6
/* Up to 7 registers, each containing up to 8 bits of data */
#define FPGA_NUM_REGS 7
#define FPGA_DATA_REGS_MAX_VALUE 255
typedef enum {

View file

@ -53,22 +53,23 @@ const uint8_t n = r;
/* REG 01 (1): CTRL */
__MREG__(FPGA_STANDARD_CTRL, 1, 0, 8)
__MREG__(FPGA_STANDARD_CTRL_DC_BLOCK, 1, 0, 1)
__MREG__(FPGA_STANDARD_CTRL_QUARTER_SHIFT_EN, 1, 1, 1)
__MREG__(FPGA_STANDARD_CTRL_QUARTER_SHIFT_UP, 1, 2, 1)
__MREG__(FPGA_STANDARD_CTRL_PRBS, 1, 6, 1)
__MREG__(FPGA_STANDARD_CTRL_TRIGGER_EN, 1, 7, 1)
/* REG 02 (2): RX_DECIM */
__MREG__(FPGA_STANDARD_RX_DECIM, 2, 0, 3)
/* REG 03 (3): TX_CTRL */
__MREG__(FPGA_STANDARD_TX_CTRL, 3, 0, 1)
__MREG__(FPGA_STANDARD_TX_CTRL_NCO_EN, 3, 0, 1)
/* REG 03 (3): RX_PSTEP */
__MREG__(FPGA_STANDARD_RX_PSTEP, 3, 0, 8)
/* REG 04 (4): TX_INTRP */
__MREG__(FPGA_STANDARD_TX_INTRP, 4, 0, 3)
/* REG 04 (4): TX_CTRL */
__MREG__(FPGA_STANDARD_TX_CTRL, 4, 0, 1)
__MREG__(FPGA_STANDARD_TX_CTRL_NCO_EN, 4, 0, 1)
/* REG 05 (5): TX_PSTEP */
__MREG__(FPGA_STANDARD_TX_PSTEP, 5, 0, 8)
/* REG 05 (5): TX_INTRP */
__MREG__(FPGA_STANDARD_TX_INTRP, 5, 0, 3)
/* REG 06 (6): TX_PSTEP */
__MREG__(FPGA_STANDARD_TX_PSTEP, 6, 0, 8)
#endif // __FPGA_REGS_DEF

View file

@ -60,7 +60,7 @@ bool fpga_spi_selftest(void)
}
// Test writing a register and reading it back.
uint8_t reg = 5;
uint8_t reg = 6;
uint8_t write_value = 0xA5;
ssp1_set_mode_ice40();
ice40_spi_write(&ice40, reg, write_value);

View file

@ -266,6 +266,7 @@ fp_40_24_t sample_rate_set(const fp_40_24_t sample_rate, const bool program)
sgpio_cpld_stream_disable(&sgpio_config);
}
#ifndef PRALINE
/* Integer mode can be enabled if p1 is even and p2 is zero. */
if (p1 & 0x1 || p2) {
si5351c_set_int_mode(&clock_gen, 0, 0);
@ -273,7 +274,6 @@ fp_40_24_t sample_rate_set(const fp_40_24_t sample_rate, const bool program)
si5351c_set_int_mode(&clock_gen, 0, 1);
}
#ifndef PRALINE
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
/*
* On HackRF One r9 all sample clocks are externally derived
@ -295,8 +295,31 @@ fp_40_24_t sample_rate_set(const fp_40_24_t sample_rate, const bool program)
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). */
/* MS0/CLK0 is the source for the MAX5864 (AFE_CLK). */
si5351c_configure_multisynth(&clock_gen, 0, p1, p2, p3, 1);
/* MS1/CLK1 is the source for the FPGA (FPGA_CLK and SCT_CLK). */
si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 1);
/* Delay FPGA_CLK relative to AFE_CLK. */
uint8_t phase_offset = 0;
if (p1 < 2100) {
phase_offset = (p1 >> 4) - 6;
}
si5351c_set_phase(&clock_gen, 1, phase_offset);
if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) {
/*
* On older boards FPGA_CLK is on CLK2 while SCT_CLK is on
* CLK1. We configure both so that behavior is consistent with
* newer boards that use CLK1 for both FPGA_CLK and SCT_CLK.
*/
si5351c_configure_multisynth(&clock_gen, 2, p1, p2, p3, 1);
si5351c_set_phase(&clock_gen, 2, phase_offset);
}
/* Reset PLL to synchronize output clock phase. */
si5351c_reset_pll(&clock_gen);
#endif
if (streaming) {
@ -528,8 +551,6 @@ void clock_gen_init(void)
si5351c_power_down_all_clocks(&clock_gen);
si5351c_set_crystal_configuration(&clock_gen);
si5351c_enable_xo_and_ms_fanout(&clock_gen);
si5351c_configure_pll_sources(&clock_gen);
si5351c_configure_pll_multisynth(&clock_gen);
/*
* Clocks on HackRF One r9:
@ -592,6 +613,7 @@ void clock_gen_init(void)
/* Set to 10 MHz, the common rate between Jawbreaker and HackRF One. */
sample_rate_set(10ULL * FP_ONE_MHZ, true);
si5351c_configure_clock_control(&clock_gen);
si5351c_set_clock_source(&clock_gen, PLL_SOURCE_XTAL);
// soft reset
si5351c_reset_pll(&clock_gen);

View file

@ -188,6 +188,7 @@ static bool radio_update_sample_rate(radio_t* const radio, uint64_t* bank)
switch (opmode) {
case TRANSCEIVER_MODE_TX:
case TRANSCEIVER_MODE_SS:
n = compute_resample_log(rate / FP_ONE_HZ, requested_n);
if (n != radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_TX]) {
#ifdef PRALINE
fpga_set_tx_interpolation_ratio(&fpga, n);
@ -196,10 +197,6 @@ static bool radio_update_sample_rate(radio_t* const radio, uint64_t* bank)
}
break;
default:
/*
* Resampling is enabled only in RX mode to work around a
* spectrum inversion bug with TX interpolation.
*/
n = compute_resample_log(rate / FP_ONE_HZ, requested_n);
if (n != radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_RX]) {
#ifdef PRALINE

View file

@ -27,6 +27,7 @@
#include "platform_scu.h"
#include "hackrf_core.h"
#include "selftest.h"
#include "delay.h"
#include <stdbool.h>
@ -116,34 +117,46 @@ void si5351c_enable_xo_and_ms_fanout(si5351c_driver_t* const drv)
/*
* Register 15: PLL Input Source
* CLKIN_DIV=0 (Divide by 1)
* PLLA_SRC=0 (XTAL)
* PLLB_SRC=1 (CLKIN)
* Set both PLLA_SRC and PLLB_SRC
*/
void si5351c_configure_pll_sources(si5351c_driver_t* const drv)
void si5351c_configure_pll_sources(
si5351c_driver_t* const drv,
const enum pll_sources source)
{
uint8_t data[] = {15, 0x08};
uint8_t data[] = {15, 0x00};
if (source == PLL_SOURCE_CLKIN) {
data[1] = 0x0c;
}
si5351c_write(drv, data, sizeof(data));
}
/* MultiSynth NA (PLLA) and NB (PLLB) */
void si5351c_configure_pll_multisynth(si5351c_driver_t* const drv)
void si5351c_configure_pll_multisynth(
si5351c_driver_t* const drv,
const enum pll_sources source)
{
/*PLLA: 25MHz XTAL * (0x0e00+512)/128 = 800mhz -> int mode */
/* XTAL: 25 MHz * (0x0e00 + 512) / 128 = 800 MHz, integer mode */
uint8_t data[] = {26, 0x00, 0x01, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00};
if (source == PLL_SOURCE_CLKIN) {
/* CLKIN: 10 MHz * (0x2600 + 512) / 128 = 800 MHz, integer mode */
data[4] = 0x26;
}
si5351c_write(drv, data, sizeof(data));
/*PLLB: 10MHz CLKIN * (0x2600+512)/128 = 800mhz */
/* Apply same configuration to PLL B. */
data[0] = 34;
data[4] = 0x26;
si5351c_write(drv, data, sizeof(data));
}
void si5351c_reset_pll(si5351c_driver_t* const drv)
{
si5351c_disable_all_outputs(drv);
/* reset PLLA and PLLB */
uint8_t data[] = {177, 0xA0};
si5351c_write(drv, data, sizeof(data));
delay_us_at_mhz(2000, 204);
si5351c_enable_clock_outputs(drv);
}
void si5351c_configure_multisynth(
@ -180,43 +193,11 @@ void si5351c_configure_multisynth(
si5351c_write(drv, data, sizeof(data));
}
void si5351c_configure_clock_control(
si5351c_driver_t* const drv,
const enum pll_sources source)
void si5351c_configure_clock_control(si5351c_driver_t* const drv)
{
uint8_t pll;
const uint8_t pll = SI5351C_CLK_PLL_SRC_A;
uint8_t clkout_ctrl;
#ifdef RAD1O
(void) source;
/* PLLA on XTAL */
pll = SI5351C_CLK_PLL_SRC_A;
#endif
#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE)
if (source == PLL_SOURCE_CLKIN) {
/* PLLB on CLKIN */
pll = SI5351C_CLK_PLL_SRC_B;
#if defined(HACKRF_ONE)
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
/*
* HackRF One r9 always uses PLL A on the XTAL input
* but externally switches that input to CLKIN.
*/
pll = SI5351C_CLK_PLL_SRC_A;
gpio_set(platform_gpio()->h1r9_clkin_en);
}
#endif
} else {
/* PLLA on XTAL */
pll = SI5351C_CLK_PLL_SRC_A;
#if defined(HACKRF_ONE)
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
gpio_clear(platform_gpio()->h1r9_clkin_en);
}
#endif
}
#endif
if (clkout_enabled) {
clkout_ctrl = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(pll) |
SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) |
@ -263,13 +244,27 @@ void si5351c_configure_clock_control(
data[6] = SI5351C_CLK_POWERDOWN;
}
#ifdef PRALINE
/* CLK0: AFE_CLK */
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;
/* CLK1: SCT_CLK and FPGA_CLK */
data[2] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(pll) |
SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) |
SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_2MA);
/* CLK4: XCVR_CLK */
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;
if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) {
/* CLK2: FPGA_CLK (not shared with SCT_CLK on older boards) */
data[3] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(pll) |
SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) |
SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_2MA);
} else {
/* CLK2: MCU_CLK */
data[3] = SI5351C_CLK_POWERDOWN;
}
#endif
si5351c_write(drv, data, sizeof(data));
}
@ -281,7 +276,7 @@ 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. */
/* Praline: enable 0, 1, 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 */
@ -291,8 +286,14 @@ void si5351c_enable_clock_outputs(si5351c_driver_t* const drv)
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);
SI5351C_CLK_ENABLE(4) | SI5351C_CLK_ENABLE(5) | SI5351C_CLK_DISABLE(6) |
SI5351C_CLK_DISABLE(7);
if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) {
/* CLK2: FPGA_CLK (not shared with SCT_CLK on older boards) */
value |= SI5351C_CLK_ENABLE(2);
} else {
value |= SI5351C_CLK_DISABLE(2);
}
#endif
uint8_t clkout = 3;
@ -348,18 +349,26 @@ void si5351c_set_clock_source(si5351c_driver_t* const drv, const enum pll_source
if (source == active_clock_source) {
return;
}
si5351c_configure_clock_control(drv, source);
active_clock_source = source;
si5351c_disable_all_outputs(drv);
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
/* 25MHz XTAL * (0x0e00+512)/128 = 800mhz -> int mode */
uint8_t pll_data[] = {26, 0x00, 0x01, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00};
#if defined(HACKRF_ONE)
/*
* HackRF One r9 always uses PLL A on the XTAL input
* but externally switches that input to CLKIN.
*/
si5351c_configure_pll_sources(drv, PLL_SOURCE_XTAL);
if (source == PLL_SOURCE_CLKIN) {
/* 10MHz CLKIN * (0x2600+512)/128 = 800mhz */
pll_data[4] = 0x26;
gpio_set(platform_gpio()->h1r9_clkin_en);
} else {
gpio_clear(platform_gpio()->h1r9_clkin_en);
}
si5351c_write(drv, pll_data, sizeof(pll_data));
si5351c_reset_pll(drv);
#endif
} else {
si5351c_configure_pll_sources(drv, source);
}
si5351c_configure_pll_multisynth(drv, source);
active_clock_source = source;
si5351c_reset_pll(drv);
}
bool si5351c_clkin_signal_valid(si5351c_driver_t* const drv)
@ -385,7 +394,7 @@ void si5351c_clkout_enable(si5351c_driver_t* const drv, uint8_t enable)
/* Configure clock to 10MHz */
si5351c_configure_multisynth(drv, clkout, 80 * 128 - 512, 0, 1, 0);
si5351c_configure_clock_control(drv, active_clock_source);
si5351c_configure_clock_control(drv);
si5351c_enable_clock_outputs(drv);
}
@ -434,3 +443,21 @@ void si5351c_init(si5351c_driver_t* const drv)
#endif
(void) drv;
}
/*
* Set initial phase offset of output multisynth. AN619 associates this setting
* with outputs, but it seems to really be a multisynth setting.
*
* After changing this setting, you must call si5351c_reset_pll() to
* synchronize outputs with the new phase offset.
*/
void si5351c_set_phase(
si5351c_driver_t* const drv,
const uint8_t ms_number,
const uint8_t offset)
{
const uint8_t address = 165 + ms_number;
if (ms_number < 8) {
si5351c_write_single(drv, address, offset & 0x7f);
}
}

View file

@ -75,8 +75,12 @@ void si5351c_disable_oeb_pin_control(si5351c_driver_t* const drv);
void si5351c_power_down_all_clocks(si5351c_driver_t* const drv);
void si5351c_set_crystal_configuration(si5351c_driver_t* const drv);
void si5351c_enable_xo_and_ms_fanout(si5351c_driver_t* const drv);
void si5351c_configure_pll_sources(si5351c_driver_t* const drv);
void si5351c_configure_pll_multisynth(si5351c_driver_t* const drv);
void si5351c_configure_pll_sources(
si5351c_driver_t* const drv,
const enum pll_sources source);
void si5351c_configure_pll_multisynth(
si5351c_driver_t* const drv,
const enum pll_sources source);
void si5351c_reset_pll(si5351c_driver_t* const drv);
void si5351c_configure_multisynth(
si5351c_driver_t* const drv,
@ -85,9 +89,7 @@ void si5351c_configure_multisynth(
const uint32_t p2,
const uint32_t p3,
const uint_fast8_t r_div);
void si5351c_configure_clock_control(
si5351c_driver_t* const drv,
const enum pll_sources source);
void si5351c_configure_clock_control(si5351c_driver_t* const drv);
void si5351c_enable_clock_outputs(si5351c_driver_t* const drv);
void si5351c_set_int_mode(
si5351c_driver_t* const drv,
@ -104,6 +106,10 @@ void si5351c_write(
const size_t data_count);
void si5351c_clkout_enable(si5351c_driver_t* const drv, uint8_t enable);
void si5351c_init(si5351c_driver_t* const drv);
void si5351c_set_phase(
si5351c_driver_t* const drv,
const uint8_t ms_number,
const uint8_t offset);
#ifdef __cplusplus
}

View file

@ -5,7 +5,7 @@
# 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 import Elaboratable, Signal, Const, Instance, Module, ClockDomain
from amaranth.build import Resource, Pins, Clock, Attrs
from amaranth.vendor import LatticeICE40Platform
from amaranth_boards.resources import SPIResource
@ -21,8 +21,8 @@ class PralinePlatform(LatticeICE40Platform):
hfosc_div = 0 # Do not divide
resources = [
Resource("fpga_clk", 0, Pins("39", dir="i"),
Attrs(GLOBAL=True, IO_STANDARD="SB_LVCMOS")),
Resource("fpga_clk", 0, Pins("47", dir="i"),
Attrs(IO_STANDARD="SB_LVCMOS")),
# ADC/DAC interfaces.
Resource("afe_clk", 0, Pins("35", dir="i"),
@ -65,15 +65,23 @@ class PralinePlatform(LatticeICE40Platform):
class ClockDomainGenerator(Elaboratable):
@staticmethod
def lut_delay(m, signal, *, depth):
def lut_delay(m, signal, *, depth, invert=False, bel=None):
# Each LUT introduces a minimum propagation delay of 9ns (best case).
signal_out = signal
for i in range(depth):
signal_in = signal_out
signal_out = Signal()
signal_out = Signal(attrs={"keep": True}) # avoid LUT optimization
other_opts = {}
if bel is not None:
other_opts["a_BEL"] = f"{bel}/lc{i}"
m.submodules += Instance("SB_LUT4",
p_LUT_INIT=0xAAAA, # Buffer configuration
p_LUT_INIT=Const(0b01 if invert else 0b10, 16),
i_I0=signal_in,
i_I1=Const(0),
i_I2=Const(0),
i_I3=Const(0),
o_O=signal_out,
**other_opts,
)
return signal_out
@ -81,12 +89,17 @@ class ClockDomainGenerator(Elaboratable):
m = Module()
# Define clock domains.
m.domains.gck1 = cd_gck1 = ClockDomain(name="gck1", reset_less=True) # analog front-end clock.
m.domains.adclk = cd_adclk = ClockDomain(name="adclk", reset_less=True)
m.domains.daclk = cd_daclk = ClockDomain(name="daclk", reset_less=True)
# 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)
adclk_ref = platform.request("afe_clk").i
fpgaclk_ref = platform.request("fpga_clk").i
delayed_adclk = self.lut_delay(m, adclk_ref, depth=1, bel="X12/Y30") # delay `afe_clk` clock by at least 8ns
m.d.comb += cd_adclk.clk.eq(delayed_adclk)
platform.add_clock_constraint(delayed_adclk, 40e6)
m.d.comb += cd_daclk.clk.eq(fpgaclk_ref)
platform.add_clock_constraint(cd_daclk.clk, 40e6)
return m

Binary file not shown.

View file

@ -24,7 +24,7 @@ class ShiftAddMCM(wiring.Component):
"output": Out(stream.Signature(
data.ArrayLayout(
data.StructLayout({
f"{i}": signed(width + bits_for(term)) for i, term in enumerate(terms)
f"{i}": signed(width + bits_for(abs(term))) for i, term in enumerate(terms)
}), num_channels), always_ready=always_ready)),
})
@ -71,7 +71,7 @@ class ShiftAddMCM(wiring.Component):
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}")
result_q = Signal(signed(self.width+bits_for(abs(term))), name=f"mul_{term}_{c}")
with m.If(self.input.ready & self.input.valid):
m.d.sync += result_q.eq(result)

View file

@ -3,7 +3,7 @@
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
from amaranth import Module, Signal, C, Cat
from amaranth import Module, Signal, C, Cat, Mux
from amaranth.lib import io, stream, wiring
from amaranth.lib.wiring import Out, In
@ -15,9 +15,10 @@ class MAX586xInterface(wiring.Component):
dac_stream: In(stream.Signature(IQSample(8), always_ready=True))
q_invert: In(1)
def __init__(self, bb_domain):
def __init__(self, adc_domain, dac_domain):
super().__init__()
self._bb_domain = bb_domain
self._adc_domain = adc_domain
self._dac_domain = dac_domain
def elaborate(self, platform):
m = Module()
@ -25,30 +26,27 @@ class MAX586xInterface(wiring.Component):
dac_stream = self.dac_stream
# Generate masks for inverting the Q component based on the q_invert signal.
q_invert = Signal()
q_invert_rx = Signal()
q_invert_tx = 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),
]
m.d[self._adc_domain] += q_invert_rx.eq(self.q_invert)
m.d[self._dac_domain] += q_invert_tx.eq(self.q_invert)
m.d.comb += [
rx_q_mask.eq(Mux(q_invert_rx, 0x80, 0x7F)),
tx_q_mask.eq(Mux(q_invert_tx, 0x1FF, 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.submodules.adc_in = adc_in = io.DDRBuffer("i", platform.request("da", dir="-"), i_domain=self._adc_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.
]
# Output 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)
m.submodules.dac_out = dac_out = io.DDRBuffer("o", platform.request("dd", dir="-"), o_domain=self._dac_domain)
with m.If(dac_stream.valid):
m.d.comb += [
dac_out.o[0] .eq(Cat(C(0, 2), dac_stream.p.i) ^ 0x200),

View file

@ -4,7 +4,7 @@
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
from amaranth import Elaboratable, Module, Cat, DomainRenamer
from amaranth import Elaboratable, Module, Cat, DomainRenamer, Signal
from amaranth.lib.wiring import connect
from amaranth_future import fixed
@ -25,22 +25,24 @@ class Top(Elaboratable):
m = Module()
m.submodules.clkgen = ClockDomainGenerator()
adc_clk = "adclk"
dac_clk = "daclk"
# Submodules.
m.submodules.adcdac_intf = adcdac_intf = MAX586xInterface(bb_domain="gck1")
m.submodules.adcdac_intf = adcdac_intf = MAX586xInterface(adc_domain=adc_clk, dac_domain=dac_clk)
m.submodules.mcu_intf = mcu_intf = SGPIOInterface(
sample_width=24,
rx_assignments=[
lambda w: Cat(w[8:12], w[11].replicate(4)),
lambda w: w[0:8],
lambda w: Cat(w[20:24], w[23].replicate(4)),
lambda w: Cat(w[8:12], w[11].replicate(4)),
lambda w: w[12:20],
lambda w: Cat(w[20:24], w[23].replicate(4)),
],
tx_assignments=[
lambda w, v: w[8:12].eq(v),
lambda w, v: w[0:8].eq(v),
lambda w, v: w[20:24].eq(v),
lambda w, v: w[8:12].eq(v),
lambda w, v: w[12:20].eq(v),
lambda w, v: w[20:24].eq(v),
],
domain="sync"
)
@ -56,19 +58,19 @@ class Top(Elaboratable):
rx_chain = {
# DC block and quarter shift.
"dc_block": DCBlock(width=8, num_channels=2, domain="gck1"),
"quarter_shift": DomainRenamer("gck1")(QuarterShift()),
"dc_block": DCBlock(width=8, num_channels=2, domain=adc_clk),
"quarter_shift": DomainRenamer(adc_clk)(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)),
"cic": CICDecimator(2, 4, (4,8,16,32), width_in=8, width_out=12, num_channels=2, always_ready=True, domain=adc_clk),
"cic_comp": DomainRenamer(adc_clk)(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"),
"hbfir1": HalfBandDecimatorMAC16(taps_hb1, data_shape=fixed.SQ(11), overclock_rate=4, always_ready=True, domain=adc_clk),
"hbfir2": HalfBandDecimatorMAC16(taps_hb2, data_shape=fixed.SQ(11), overclock_rate=8, always_ready=True, domain=adc_clk),
# Clock domain conversion.
"clkconv": ClockConverter(IQSample(12), 8, "gck1", "sync", always_ready=True),
"clkconv": ClockConverter(IQSample(12), 8, adc_clk, "sync", always_ready=True),
}
for k,v in rx_chain.items():
m.submodules[f"rx_{k}"] = v
@ -85,9 +87,11 @@ class Top(Elaboratable):
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)
ctrl = spi_regs.add_register(0x01, init=0)
rx_decim = Signal(3, init=2)
rx_decim_new = Signal(3)
rx_decim_stb = Signal()
spi_regs.add_sfr(0x02, read=rx_decim, write_signal=rx_decim_new, write_strobe=rx_decim_stb)
m.d.comb += [
# Trigger enable.
@ -99,8 +103,13 @@ class Top(Elaboratable):
rx_chain["quarter_shift"].up .eq(ctrl[2]),
# RX decimation rate.
rx_chain["cic"].factor .eq(rx_decim+2),
rx_chain["cic"].factor .eq(rx_decim),
]
with m.If(rx_decim_stb):
with m.If(rx_decim_new < 2):
m.d.sync += rx_decim.eq(2)
with m.Else():
m.d.sync += rx_decim.eq(rx_decim_new)
return m

View file

@ -4,7 +4,8 @@
# Copyright (c) 2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
from amaranth import Elaboratable, Module, Cat, DomainRenamer
from amaranth import Elaboratable, Module, Cat, DomainRenamer, Signal
from amaranth.lib import cdc
from amaranth.lib.wiring import connect
from amaranth_future import fixed
@ -23,22 +24,24 @@ class Top(Elaboratable):
m = Module()
m.submodules.clkgen = ClockDomainGenerator()
adc_clk = "adclk"
dac_clk = "daclk"
# Submodules.
m.submodules.adcdac_intf = adcdac_intf = MAX586xInterface(bb_domain="gck1")
m.submodules.adcdac_intf = adcdac_intf = MAX586xInterface(adc_domain=adc_clk, dac_domain=dac_clk)
m.submodules.mcu_intf = mcu_intf = SGPIOInterface(
sample_width=24,
rx_assignments=[
lambda w: Cat(w[8:12], w[11].replicate(4)),
lambda w: w[0:8],
lambda w: Cat(w[20:24], w[23].replicate(4)),
lambda w: Cat(w[8:12], w[11].replicate(4)),
lambda w: w[12:20],
lambda w: Cat(w[20:24], w[23].replicate(4)),
],
tx_assignments=[
lambda w, v: w[8:12].eq(v),
lambda w, v: w[0:8].eq(v),
lambda w, v: w[20:24].eq(v),
lambda w, v: w[8:12].eq(v),
lambda w, v: w[12:20].eq(v),
lambda w, v: w[20:24].eq(v),
],
domain="sync"
)
@ -55,20 +58,20 @@ class Top(Elaboratable):
tx_chain = {
# Clock domain conversion.
"clkconv": ClockConverter(IQSample(12), 8, "sync", "gck1", always_ready=False),
"clkconv": ClockConverter(IQSample(12), 8, "sync", dac_clk, 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)),
overclock_rate=8, num_channels=2, always_ready=False, domain=dac_clk),
"skid1": DomainRenamer(dac_clk)(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)),
overclock_rate=4, num_channels=2, always_ready=False, domain=dac_clk),
"skid2": DomainRenamer(dac_clk)(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_comp": DomainRenamer(dac_clk)(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"),
always_ready=False, domain=dac_clk),
}
for k,v in tx_chain.items():
m.submodules[f"tx_{k}"] = v
@ -86,16 +89,24 @@ class Top(Elaboratable):
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)
ctrl = spi_regs.add_register(0x01, init=0)
tx_intrp = Signal(3, init=2)
tx_intrp_new = Signal(3)
tx_intrp_stb = Signal()
spi_regs.add_sfr(0x05, read=tx_intrp, write_signal=tx_intrp_new, write_strobe=tx_intrp_stb)
m.d.comb += [
# Trigger enable.
mcu_intf.trigger_en .eq(ctrl[7]),
# TX interpolation rate.
tx_chain["cic_interpolator"].factor .eq(tx_intrp + 2),
]
# TX interpolation rate.
m.submodules.rx_decim_cdc = cdc.FFSynchronizer(tx_intrp, tx_chain["cic_interpolator"].factor, o_domain=dac_clk)
with m.If(tx_intrp_stb):
with m.If(tx_intrp_new < 2):
m.d.sync += tx_intrp.eq(2)
with m.Else():
m.d.sync += tx_intrp.eq(tx_intrp_new)
return m

View file

@ -54,17 +54,19 @@ class Top(Elaboratable):
m = Module()
m.submodules.clkgen = ClockDomainGenerator()
adc_clk = "adclk"
dac_clk = "daclk"
# Submodules.
m.submodules.adcdac_intf = adcdac_intf = MAX586xInterface(bb_domain="gck1")
m.submodules.adcdac_intf = adcdac_intf = MAX586xInterface(adc_domain=adc_clk, dac_domain=dac_clk)
m.submodules.mcu_intf = mcu_intf = SGPIOInterface(sample_width=8, domain="sync")
m.d.comb += adcdac_intf.q_invert.eq(platform.request("q_invert").i)
rx_chain = {
"dc_block": DCBlock(width=8, num_channels=2, domain="gck1"),
"half_prec": DomainRenamer("gck1")(IQHalfPrecisionConverter()),
"clkconv": ClockConverter(IQSample(4), 16, "gck1", "sync"),
"dc_block": DCBlock(width=8, num_channels=2, domain=adc_clk),
"half_prec": DomainRenamer(adc_clk)(IQHalfPrecisionConverter()),
"clkconv": ClockConverter(IQSample(4), 16, adc_clk, "sync"),
}
for k,v in rx_chain.items():
m.submodules[f"rx_{k}"] = v
@ -78,8 +80,8 @@ class Top(Elaboratable):
tx_chain = {
"clkconv": ClockConverter(IQSample(4), 16, "sync", "gck1", always_ready=False),
"half_prec": DomainRenamer("gck1")(IQHalfPrecisionConverterInv()),
"clkconv": ClockConverter(IQSample(4), 16, "sync", dac_clk, always_ready=False),
"half_prec": DomainRenamer(dac_clk)(IQHalfPrecisionConverterInv()),
}
for k,v in tx_chain.items():
m.submodules[f"tx_{k}"] = v

View file

@ -26,9 +26,11 @@ class Top(Elaboratable):
m = Module()
m.submodules.clkgen = ClockDomainGenerator()
adc_clk = "adclk"
dac_clk = "daclk"
# Submodules.
m.submodules.adcdac_intf = adcdac_intf = MAX586xInterface(bb_domain="gck1")
m.submodules.adcdac_intf = adcdac_intf = MAX586xInterface(adc_domain=adc_clk, dac_domain=dac_clk)
m.submodules.mcu_intf = mcu_intf = SGPIOInterface(sample_width=16, domain="sync")
m.d.comb += adcdac_intf.q_invert.eq(platform.request("q_invert").i)
@ -52,13 +54,13 @@ class Top(Elaboratable):
common_rx_filter_opts = dict(
data_shape=fixed.SQ(7),
always_ready=True,
domain="gck1",
domain=adc_clk,
)
rx_chain = {
# DC block and quarter shift.
"dc_block": DCBlock(width=8, num_channels=2, domain="gck1"),
"quarter_shift": DomainRenamer("gck1")(QuarterShift()),
"dc_block": DCBlock(width=8, num_channels=2, domain=adc_clk),
"quarter_shift": DomainRenamer(adc_clk)(QuarterShift()),
# Half-band decimation stages.
"hbfir5": HalfBandDecimator(taps5, **common_rx_filter_opts),
@ -68,7 +70,7 @@ class Top(Elaboratable):
"hbfir1": HalfBandDecimator(taps, **common_rx_filter_opts),
# Clock domain conversion.
"clkconv": ClockConverter(IQSample(8), 8, "gck1", "sync"),
"clkconv": ClockConverter(IQSample(8), 8, adc_clk, "sync"),
}
for k,v in rx_chain.items():
m.submodules[f"rx_{k}"] = v
@ -82,20 +84,20 @@ class Top(Elaboratable):
tx_chain = {
# Clock domain conversion.
"clkconv": ClockConverter(IQSample(8), 8, "sync", "gck1", always_ready=False),
"clkconv": ClockConverter(IQSample(8), 8, "sync", dac_clk, 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)),
num_channels=2, always_ready=False, domain=dac_clk),
"skid2": DomainRenamer(dac_clk)(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)),
num_channels=2, always_ready=False, domain=dac_clk),
"skid3": DomainRenamer(dac_clk)(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"),
"skid4": DomainRenamer("gck1")(StreamSkidBuffer(IQSample(8), always_ready=False)),
always_ready=False, domain=dac_clk),
"skid4": DomainRenamer(dac_clk)(StreamSkidBuffer(IQSample(8), always_ready=False)),
}
for k,v in tx_chain.items():
m.submodules[f"tx_{k}"] = v
@ -106,7 +108,7 @@ class Top(Elaboratable):
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))
m.submodules.nco = nco = DomainRenamer(dac_clk)(NCO(phase_width=16, output_width=8))
with m.If(nco.en):
m.d.comb += [
adcdac_intf.dac_stream.p.eq(nco.output),
@ -123,9 +125,10 @@ class Top(Elaboratable):
# 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)
rx_pstep = spi_regs.add_register(0x03, init=0)
tx_ctrl = spi_regs.add_register(0x04, init=0, size=1)
tx_intrp = spi_regs.add_register(0x05, init=0, size=3)
tx_pstep = spi_regs.add_register(0x06, init=0)
m.d.sync += [
# Trigger enable.
@ -136,8 +139,8 @@ class Top(Elaboratable):
# 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_chain["quarter_shift"].enable .eq(rx_pstep[-2]),
rx_chain["quarter_shift"].up .eq(rx_pstep[-1]),
# RX decimation rate.
rx_chain["hbfir5"].enable .eq(rx_decim > 4),
@ -145,19 +148,23 @@ class Top(Elaboratable):
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 interpolation rate.
tx_intrp_dacclk = Signal.like(tx_intrp)
m.submodules.tx_intrp_cdc = cdc.FFSynchronizer(tx_intrp, tx_intrp_dacclk, o_domain=dac_clk)
m.d.comb += [
tx_chain["cic_interpolator"].factor .eq(Mux(tx_intrp_dacclk > 2, tx_intrp_dacclk - 2, 0)),
tx_chain["hbfir1"].enable .eq(tx_intrp_dacclk > 0),
tx_chain["hbfir2"].enable .eq(tx_intrp_dacclk > 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 += [
tx_pstep_dacclk = Signal(8)
m.submodules.nco_phase_cdc = cdc.FFSynchronizer(tx_pstep, tx_pstep_dacclk, o_domain=dac_clk)
m.d[dac_clk] += [
nco.en .eq(tx_ctrl[0]),
nco.phase .eq(nco.phase + (tx_pstep_gck1 << 6)),
nco.phase .eq(nco.phase + (tx_pstep_dacclk << 6)),
]
return m