diff --git a/ci-scripts/build_h1_firmware.sh b/ci-scripts/build_h1_firmware.sh index a50785a8..762d64de 100755 --- a/ci-scripts/build_h1_firmware.sh +++ b/ci-scripts/build_h1_firmware.sh @@ -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 ../.. diff --git a/ci-scripts/build_hpro_firmware.sh b/ci-scripts/build_hpro_firmware.sh index 8340d0ac..830d4bf7 100755 --- a/ci-scripts/build_hpro_firmware.sh +++ b/ci-scripts/build_hpro_firmware.sh @@ -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 ../.. diff --git a/ci-scripts/hackrf_pro_test.py b/ci-scripts/hackrf_pro_test.py index bbe7e85f..1e9ac9f4 100644 --- a/ci-scripts/hackrf_pro_test.py +++ b/ci-scripts/hackrf_pro_test.py @@ -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: diff --git a/ci-scripts/hackrf_test.py b/ci-scripts/hackrf_test.py index 42d0393d..13948ad3 100644 --- a/ci-scripts/hackrf_test.py +++ b/ci-scripts/hackrf_test.py @@ -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() diff --git a/firmware/common/fpga.c b/firmware/common/fpga.c index 069d1a09..34ebd791 100644 --- a/firmware/common/fpga.c +++ b/firmware/common/fpga.c @@ -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); } diff --git a/firmware/common/fpga.h b/firmware/common/fpga.h index 383444c5..2b1fbe14 100644 --- a/firmware/common/fpga.h +++ b/firmware/common/fpga.h @@ -25,8 +25,8 @@ #include #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 { diff --git a/firmware/common/fpga_regs.def b/firmware/common/fpga_regs.def index c575c493..5964657b 100644 --- a/firmware/common/fpga_regs.def +++ b/firmware/common/fpga_regs.def @@ -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 diff --git a/firmware/common/fpga_selftest.c b/firmware/common/fpga_selftest.c index cd09b91f..2b5ec1b3 100644 --- a/firmware/common/fpga_selftest.c +++ b/firmware/common/fpga_selftest.c @@ -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); diff --git a/firmware/common/hackrf_core.c b/firmware/common/hackrf_core.c index 726d1869..4793efd9 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/hackrf_core.c @@ -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); diff --git a/firmware/common/radio.c b/firmware/common/radio.c index 8e1f445d..2ea21d63 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -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 diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index 41dbd6c4..399d01fc 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -27,6 +27,7 @@ #include "platform_scu.h" #include "hackrf_core.h" #include "selftest.h" +#include "delay.h" #include @@ -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); + } +} diff --git a/firmware/common/si5351c.h b/firmware/common/si5351c.h index 4c91bfba..001991dc 100644 --- a/firmware/common/si5351c.h +++ b/firmware/common/si5351c.h @@ -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 } diff --git a/firmware/fpga/board.py b/firmware/fpga/board.py index d5c22361..a52045cd 100644 --- a/firmware/fpga/board.py +++ b/firmware/fpga/board.py @@ -5,7 +5,7 @@ # Copyright (c) 2024 Great Scott Gadgets # 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 diff --git a/firmware/fpga/build/praline_fpga.bin b/firmware/fpga/build/praline_fpga.bin index 460d2552..addeb7a7 100644 Binary files a/firmware/fpga/build/praline_fpga.bin and b/firmware/fpga/build/praline_fpga.bin differ diff --git a/firmware/fpga/dsp/mcm.py b/firmware/fpga/dsp/mcm.py index ce9bd978..820dfe4b 100644 --- a/firmware/fpga/dsp/mcm.py +++ b/firmware/fpga/dsp/mcm.py @@ -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) diff --git a/firmware/fpga/interface/max586x.py b/firmware/fpga/interface/max586x.py index 60ffade9..8ffe1a5e 100644 --- a/firmware/fpga/interface/max586x.py +++ b/firmware/fpga/interface/max586x.py @@ -3,7 +3,7 @@ # Copyright (c) 2025 Great Scott Gadgets # 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), diff --git a/firmware/fpga/top/ext_precision_rx.py b/firmware/fpga/top/ext_precision_rx.py index 3950458e..562e6f1f 100644 --- a/firmware/fpga/top/ext_precision_rx.py +++ b/firmware/fpga/top/ext_precision_rx.py @@ -4,7 +4,7 @@ # Copyright (c) 2025 Great Scott Gadgets # 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 diff --git a/firmware/fpga/top/ext_precision_tx.py b/firmware/fpga/top/ext_precision_tx.py index 6b55acc4..08a3adf0 100644 --- a/firmware/fpga/top/ext_precision_tx.py +++ b/firmware/fpga/top/ext_precision_tx.py @@ -4,7 +4,8 @@ # Copyright (c) 2025 Great Scott Gadgets # 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 diff --git a/firmware/fpga/top/half_precision.py b/firmware/fpga/top/half_precision.py index 974f68ee..64a23df6 100644 --- a/firmware/fpga/top/half_precision.py +++ b/firmware/fpga/top/half_precision.py @@ -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 diff --git a/firmware/fpga/top/standard.py b/firmware/fpga/top/standard.py index 7f85925b..f48e34c0 100644 --- a/firmware/fpga/top/standard.py +++ b/firmware/fpga/top/standard.py @@ -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