Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions firmware/common/fixed_point.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,20 @@ typedef uint64_t fp_28_36_t;
#define SR_FP_MHZ(mhz) (mhz##ULL * SR_FP_ONE_MHZ)
#define SR_FP_KHZ(khz) (khz##ULL * SR_FP_ONE_KHZ)
#define SR_FP_HZ(hz) (hz##ULL * SR_FP_ONE_HZ)

/* 1.63 fixed point */
typedef uint64_t fp_1_63_t;

#define FRAC_ONE (1ULL << 63)

/* one hundredth in 1.63 fixed point */
#define FRAC_ONE_PERCENT (FRAC_ONE / (100ULL))

/* one thousandth in 1.63 fixed point */
#define FRAC_ONE_PERMILLE (FRAC_ONE / (1000ULL))

/* one millionth in 1.63 fixed point */
#define FRAC_ONE_PPM (FRAC_ONE / (1000000ULL))

/* one billionth in 1.63 fixed point */
#define FRAC_ONE_PPB (FRAC_ONE / (1000000000ULL))
96 changes: 89 additions & 7 deletions firmware/common/radio.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "rf_path.h"
#include "transceiver_mode.h"
#include "tuning.h"
#include "u128.h"
#ifdef IS_PRALINE
#include "fpga.h"
#include "tune_config.h"
Expand Down Expand Up @@ -132,6 +133,75 @@ static uint32_t radio_update_direction(radio_t* const radio, uint64_t* bank)
return (1 << RADIO_OPMODE);
}

/* Apply correction to a true frequency to get an adjusted frequency. */
static uint64_t adjust(uint64_t freq, uint64_t correction)
{
/* skip if we can */
if (correction == FRAC_ONE) {
return freq;
}

u128 product = u128_multiply(freq, correction);

/* product >> 63 */
return (product.hi << 1) | (product.lo >> 63);
}

/* Given an adjusted frequency and correction, return the true frequency. */
static uint64_t unadjust(uint64_t freq, uint64_t correction)
{
/* skip if we can */
if (correction == FRAC_ONE) {
return freq;
}

/* (1 << (63 + 63)) */
u128 numerator = (u128){
.hi = 1ULL << 62,
.lo = 0,
};
u128 denominator = (u128){
.hi = 0,
.lo = correction,
};
u128 reciprocal = u128_divide(numerator, denominator);
return adjust(freq, reciprocal.lo);
}

static uint64_t restrict_correction(uint64_t requested_correction)
{
/* Correction should be in the range 0.99 to 1.01 */
const uint64_t min_correction = FRAC_ONE - FRAC_ONE_PERCENT;
const uint64_t max_correction = FRAC_ONE + FRAC_ONE_PERCENT;
if (requested_correction >= min_correction &&
requested_correction <= max_correction) {
return requested_correction;
} else {
return FRAC_ONE;
}
}

static uint64_t set_lo(uint64_t freq_lo, uint64_t correction, bool apply)
{
fp_40_24_t corrected_lo = adjust(freq_lo, correction);
fp_40_24_t achieved_lo = mixer_set_frequency(&mixer, corrected_lo, apply);
return unadjust(achieved_lo, correction);
}

static uint64_t set_if(uint64_t freq_if, uint64_t correction, bool apply)
{
fp_40_24_t corrected_if = adjust(freq_if, correction);
fp_40_24_t achieved_if = max283x_set_frequency(&max283x, corrected_if, apply);
return unadjust(achieved_if, correction);
}

static uint64_t set_afe_rate(uint64_t afe_rate, uint64_t correction, bool apply)
{
fp_28_36_t corrected_rate = adjust(afe_rate, correction);
fp_28_36_t achieved_rate = sample_rate_set(corrected_rate, apply);
return unadjust(achieved_rate, correction);
}

#define ABSOLUTE_MIN_AFE_RATE SR_FP_KHZ(200)
#define ABSOLUTE_MAX_AFE_RATE SR_FP_KHZ(43600)
#define MAX_SUPPORTED_AFE_RATE SR_FP_KHZ(40000)
Expand Down Expand Up @@ -183,6 +253,9 @@ static uint32_t radio_update_sample_rate(radio_t* const radio, uint64_t* bank)
bool new_n = false;

const uint64_t requested_rate = bank[RADIO_SAMPLE_RATE];
const uint64_t requested_correction = bank[RADIO_CLOCK_CORRECTION];

uint64_t correction = restrict_correction(requested_correction);

if (requested_rate != RADIO_UNSET) {
rate = MIN(requested_rate, MAX_MCU_RATE);
Expand Down Expand Up @@ -236,7 +309,7 @@ static uint32_t radio_update_sample_rate(radio_t* const radio, uint64_t* bank)
new_n = (n != previous_n);

afe_rate = rate << n;
afe_rate = sample_rate_set(afe_rate, false);
afe_rate = set_afe_rate(afe_rate, correction, false);
previous_rate = radio->config[RADIO_BANK_APPLIED][RADIO_SAMPLE_RATE];
if ((previous_n == RADIO_UNSET) || previous_rate == RADIO_UNSET) {
previous_afe_rate = RADIO_UNSET;
Expand All @@ -245,7 +318,7 @@ static uint32_t radio_update_sample_rate(radio_t* const radio, uint64_t* bank)
}
new_afe_rate = (afe_rate != previous_afe_rate);
if (new_afe_rate) {
afe_rate = sample_rate_set(afe_rate, true);
afe_rate = set_afe_rate(afe_rate, correction, true);
}

rate = afe_rate >> n;
Expand Down Expand Up @@ -373,6 +446,7 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank)
#ifdef IS_PRALINE
const uint64_t requested_rotation = bank[RADIO_ROTATION];
#endif
const uint64_t requested_correction = bank[RADIO_CLOCK_CORRECTION];

const uint64_t applied_rf = radio->config[RADIO_BANK_APPLIED][RADIO_FREQUENCY_RF];
const uint64_t applied_if = radio->config[RADIO_BANK_APPLIED][RADIO_FREQUENCY_IF];
Expand All @@ -381,13 +455,16 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank)
radio->config[RADIO_BANK_APPLIED][RADIO_IMAGE_REJECT];
const uint64_t applied_rotation =
radio->config[RADIO_BANK_APPLIED][RADIO_ROTATION];
const uint64_t applied_correction =
radio->config[RADIO_BANK_APPLIED][RADIO_CLOCK_CORRECTION];

uint64_t freq_rf = applied_rf;
uint64_t analog_rf = applied_rf;
uint64_t freq_if = applied_if;
uint64_t freq_lo = applied_lo;
uint64_t img_reject = applied_img_reject;
uint64_t rotation = applied_rotation;
uint64_t correction = applied_correction;

uint64_t opmode = bank[RADIO_OPMODE];
if (opmode == RADIO_UNSET) {
Expand All @@ -405,11 +482,12 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank)
img_reject = RF_PATH_FILTER_BYPASS;
}
}
correction = restrict_correction(requested_correction);
if (requested_if != RADIO_UNSET) {
freq_if = max283x_set_frequency(&max283x, requested_if, false);
freq_if = set_if(requested_if, correction, false);
}
if (requested_lo != RADIO_UNSET) {
freq_lo = mixer_set_frequency(&mixer, freq_lo, false);
freq_lo = set_lo(requested_lo, correction, false);
}
#ifdef IS_PRALINE
if (IS_PRALINE) {
Expand Down Expand Up @@ -491,7 +569,7 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank)
freq_lo = RADIO_UNSET;
}
if (freq_lo != RADIO_UNSET) {
freq_lo = mixer_set_frequency(&mixer, freq_lo, false);
freq_lo = set_lo(freq_lo, correction, false);
}
}

Expand All @@ -513,12 +591,12 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank)

/* Apply settings. */
if ((freq_if != applied_if) && (freq_if != RADIO_UNSET)) {
freq_if = max283x_set_frequency(&max283x, freq_if, true);
freq_if = set_if(freq_if, correction, true);
radio->config[RADIO_BANK_APPLIED][RADIO_FREQUENCY_IF] = freq_if;
changed |= (1 << RADIO_FREQUENCY_IF);
}
if ((freq_lo != applied_lo) && (freq_lo != RADIO_UNSET)) {
freq_lo = mixer_set_frequency(&mixer, freq_lo, true);
freq_lo = set_lo(freq_lo, correction, true);
radio->config[RADIO_BANK_APPLIED][RADIO_FREQUENCY_LO] = freq_lo;
changed |= (1 << RADIO_FREQUENCY_LO);
}
Expand Down Expand Up @@ -561,6 +639,10 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank)
radio->config[RADIO_BANK_APPLIED][RADIO_FREQUENCY_RF] = freq_rf;
changed |= (1 << RADIO_FREQUENCY_RF);
}
if ((correction != applied_correction) && (correction != RADIO_UNSET)) {
radio->config[RADIO_BANK_APPLIED][RADIO_CLOCK_CORRECTION] = correction;
changed |= (1 << RADIO_CLOCK_CORRECTION);
}

return changed;
}
Expand Down
18 changes: 12 additions & 6 deletions firmware/common/radio.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,17 +165,23 @@ typedef enum {
* DC block enable of type bool.
*/
RADIO_DC_BLOCK = 22,
/**
* Correction factor for radio reference clock, of type fp_1_63_t.
*/
RADIO_CLOCK_CORRECTION = 23,
} radio_register_t;

#define RADIO_NUM_REGS (23)
#define RADIO_NUM_REGS (24)
#define RADIO_UNSET (0xffffffffffffffff)

/* register groups for bitfield convenience */
#define RADIO_REG_GROUP_RATE \
((1 << RADIO_SAMPLE_RATE) | (1 << RADIO_RESAMPLE_TX) | (1 << RADIO_RESAMPLE_RX))
#define RADIO_REG_GROUP_FREQ \
((1 << RADIO_FREQUENCY_RF) | (1 << RADIO_FREQUENCY_IF) | \
(1 << RADIO_FREQUENCY_LO) | (1 << RADIO_IMAGE_REJECT) | (1 << RADIO_ROTATION))
#define RADIO_REG_GROUP_RATE \
((1 << RADIO_SAMPLE_RATE) | (1 << RADIO_RESAMPLE_TX) | \
(1 << RADIO_RESAMPLE_RX) | (1 << RADIO_CLOCK_CORRECTION))
#define RADIO_REG_GROUP_FREQ \
((1 << RADIO_FREQUENCY_RF) | (1 << RADIO_FREQUENCY_IF) | \
(1 << RADIO_FREQUENCY_LO) | (1 << RADIO_IMAGE_REJECT) | (1 << RADIO_ROTATION) | \
(1 << RADIO_CLOCK_CORRECTION))
#define RADIO_REG_GROUP_BW \
((1 << RADIO_BB_BANDWIDTH_TX) | (1 << RADIO_BB_BANDWIDTH_RX) | \
(1 << RADIO_XCVR_TX_LPF) | (1 << RADIO_XCVR_RX_LPF) | \
Expand Down
88 changes: 88 additions & 0 deletions firmware/common/u128.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright 2026 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 "u128.h"

/* From https://stackoverflow.com/a/58381061 */

u128 u128_multiply(uint64_t lhs, uint64_t rhs)
{
uint64_t lo_lo = (lhs & 0xFFFFFFFF) * (rhs & 0xFFFFFFFF);
uint64_t hi_lo = (lhs >> 32) * (rhs & 0xFFFFFFFF);
uint64_t lo_hi = (lhs & 0xFFFFFFFF) * (rhs >> 32);
uint64_t hi_hi = (lhs >> 32) * (rhs >> 32);
uint64_t cross = ((lo_lo >> 32) + (hi_lo & 0xFFFFFFFF) + lo_hi);
return (u128){
.hi = ((hi_lo >> 32) + (cross >> 32) + hi_hi),
.lo = ((cross << 32) | (lo_lo & 0xFFFFFFFF)),
};
}

/* From https://stackoverflow.com/a/70052545 */

/* clang-format off */

#define SUBCcc(a,b,cy,t0,t1,t2) \
(t0=(b)+cy, t1=(a), cy=t0<cy, t2=t1<t0, cy=cy+t2, t1-t0)

#define SUBcc(a,b,cy,t0,t1) \
(t0=(b), t1=(a), cy=t1<t0, t1-t0)

#define SUBC(a,b,cy,t0,t1) \
(t0=(b)+cy, t1=(a), t1-t0)

#define ADDCcc(a,b,cy,t0,t1) \
(t0=(b)+cy, t1=(a), cy=t0<cy, t0=t0+t1, t1=t0<t1, cy=cy+t1, t0=t0)

#define ADDcc(a,b,cy,t0,t1) \
(t0=(b), t1=(a), t0=t0+t1, cy=t0<t1, t0=t0)

#define ADDC(a,b,cy,t0,t1) \
(t0=(b)+cy, t1=(a), t0+t1)

/* clang-format on */

u128 u128_divide(u128 dvnd, u128 dvsr)
{
u128 quot, rem, tmp;
uint64_t cy, t0, t1, t2;

quot.hi = dvnd.hi;
quot.lo = dvnd.lo;
rem.hi = 0;
rem.lo = 0;

for (int i = 0; i < 128; i++) {
quot.lo = ADDcc(quot.lo, quot.lo, cy, t0, t1);
quot.hi = ADDCcc(quot.hi, quot.hi, cy, t0, t1);
rem.lo = ADDCcc(rem.lo, rem.lo, cy, t0, t1);
rem.hi = ADDC(rem.hi, rem.hi, cy, t0, t1);
tmp.lo = SUBcc(rem.lo, dvsr.lo, cy, t0, t1);
tmp.hi = SUBCcc(rem.hi, dvsr.hi, cy, t0, t1, t2);
if (!cy) { // remainder >= divisor
rem.lo = tmp.lo;
rem.hi = tmp.hi;
quot.lo |= 1;
}
}

return quot;
}
32 changes: 32 additions & 0 deletions firmware/common/u128.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2026 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.
*/

#pragma once

#include <stdint.h>

typedef struct {
uint64_t hi;
uint64_t lo;
} u128;

u128 u128_multiply(uint64_t a, uint64_t b);
u128 u128_divide(u128 dvnd, u128 dvsr);
1 change: 1 addition & 0 deletions firmware/hackrf-common.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ macro(DeclareTargets)
${PATH_HACKRF_FIRMWARE_COMMON}/adc.c
${PATH_HACKRF_FIRMWARE_COMMON}/da7219.c
${PATH_HACKRF_FIRMWARE_COMMON}/max283x.c
${PATH_HACKRF_FIRMWARE_COMMON}/u128.c
)

if(BOARD STREQUAL "RAD1O")
Expand Down
2 changes: 1 addition & 1 deletion firmware/hackrf_usb/usb_descriptor.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

#define USB_VENDOR_ID (0x1D50)

#define USB_API_VERSION (0x0112)
#define USB_API_VERSION (0x0113)

#define USB_WORD(x) (x & 0xFF), ((x >> 8) & 0xFF)

Expand Down
2 changes: 1 addition & 1 deletion host/hackrf-tools/src/hackrf_debug.c
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ int radio_read_register(
return result;
}

#define RADIO_NUM_REGS (23)
#define RADIO_NUM_REGS (24)

int radio_read_registers(hackrf_device* device, const uint8_t bank)
{
Expand Down
Loading