n310: Add Filter API to n310

Add the Filter API to n3xx specifically for the AD937x device.  The TX
filter is limited to 32 taps, and the RX filter is limited to 48 taps.

This feature requires MPM version 4.2 or later on the device.

Co-authored-by: bpadalino <bpadalino@gmail.com>
Signed-off-by: mattprost <matt.prost@ni.com>
This commit is contained in:
mattprost 2021-12-16 13:48:26 -06:00 committed by Aaron Rossetto
parent a8ad4917d3
commit b8acf58798
14 changed files with 284 additions and 37 deletions

View File

@ -2946,7 +2946,7 @@ filter_info_base::sptr ad9361_device_t::_get_filter_fir(
max_num_taps = _get_num_fir_taps(direction);
filter_info_base::sptr fir(
new digital_filter_fir<int16_t>(filter_info_base::DIGITAL_FIR_I16,
std::make_shared<digital_filter_fir<int16_t>>(filter_info_base::DIGITAL_FIR_I16,
(enable == 0) ? true : false,
5,
rate,

View File

@ -135,3 +135,15 @@ std::string magnesium_ad9371_iface::get_lo_source(const uhd::direction_t dir)
UHD_LOG_TRACE(_log_prefix, _rpc_prefix << "get_lo_source returned " << retval);
return retval;
}
void magnesium_ad9371_iface::set_fir(
const std::string& name, const int8_t gain, const std::vector<int16_t>& coeffs)
{
request<void>("set_fir", name, gain, coeffs);
}
std::pair<int8_t, std::vector<int16_t>> magnesium_ad9371_iface::get_fir(
const std::string& name)
{
return request<std::pair<int8_t, std::vector<int16_t>>>("get_fir", name);
}

View File

@ -37,6 +37,11 @@ public:
std::string get_lo_source(const uhd::direction_t dir);
void set_fir(
const std::string& name, const int8_t gain, const std::vector<int16_t>& coeffs);
std::pair<int8_t, std::vector<int16_t>> get_fir(const std::string& name);
private:
/*! Shorthand to perform an RPC request. Saves some typing.
*/

View File

@ -22,6 +22,9 @@ static constexpr double AD9371_TX_MAX_BANDWIDTH = 100.0e6; // HZ
static constexpr double AD9371_MIN_FREQ = 300.0e6; // Hz
static constexpr double AD9371_MAX_FREQ = 6.0e9; // Hz
static constexpr size_t AD9371_RX_MAX_FIR_TAPS = 48; // Coefficients
static constexpr size_t AD9371_TX_MAX_FIR_TAPS = 32; // Coefficients
static constexpr double ADF4351_MIN_FREQ = 35.0e6;
static constexpr double ADF4351_MAX_FREQ = 4.4e9;

View File

@ -1174,6 +1174,145 @@ sensor_value_t magnesium_radio_control_impl::get_tx_sensor(
_rpc_prefix + "get_sensor", "TX", name, chan));
}
/**************************************************************************
* Filter API
*************************************************************************/
std::vector<std::string> magnesium_radio_control_impl::get_rx_filter_names(
const size_t chan) const
{
UHD_ASSERT_THROW(chan < TOTAL_RADIO_PORTS);
if (chan % 2 == 0) {
return {"RX1_FIR", "RX1RX2_FIR"};
} else {
return {"RX2_FIR", "RX1RX2_FIR"};
}
}
uhd::filter_info_base::sptr magnesium_radio_control_impl::get_rx_filter(
const std::string& name, const size_t)
{
if (_mpm_compat_num[0] < 4 || (_mpm_compat_num[0] == 4 && _mpm_compat_num[1] < 2)) {
RFNOC_LOG_WARNING("Getting rx filter not supported. Please upgrade MPM to a "
"minimum version of 4.2.");
return std::make_shared<uhd::digital_filter_fir<int16_t>>(
uhd::filter_info_base::filter_type::DIGITAL_FIR_I16,
false,
0,
1.0,
1,
1,
32767,
AD9371_RX_MAX_FIR_TAPS,
std::vector<int16_t>(AD9371_RX_MAX_FIR_TAPS, 0));
}
const auto rv = _ad9371->get_fir(name);
const auto coeffs = rv.second;
// TODO: Put gain in the digital_filter_fir
return std::make_shared<uhd::digital_filter_fir<int16_t>>(
uhd::filter_info_base::filter_type::DIGITAL_FIR_I16,
false,
0,
1.0,
1,
1,
32767,
AD9371_RX_MAX_FIR_TAPS,
coeffs);
}
void magnesium_radio_control_impl::set_rx_filter(
const std::string& name, uhd::filter_info_base::sptr filter, const size_t)
{
std::lock_guard<std::recursive_mutex> l(_set_lock);
if (_mpm_compat_num[0] < 4 || (_mpm_compat_num[0] == 4 && _mpm_compat_num[1] < 2)) {
RFNOC_LOG_WARNING("Setting rx filter not supported. Please upgrade MPM to a "
"minimum version of 4.2.");
return;
}
auto fir = std::dynamic_pointer_cast<uhd::digital_filter_fir<int16_t>>(filter);
if (fir == nullptr) {
throw uhd::runtime_error("Invalid Filter Type for RX Filter");
}
if (fir->get_taps().size() != AD9371_RX_MAX_FIR_TAPS) {
throw uhd::runtime_error("AD937x RX Filter Taps must be "
+ std::to_string(AD9371_RX_MAX_FIR_TAPS)
+ " taps long!");
}
// TODO: Use gain in the digital_filter_fir
_ad9371->set_fir(name, 6, fir->get_taps());
}
std::vector<std::string> magnesium_radio_control_impl::get_tx_filter_names(
const size_t chan) const
{
UHD_ASSERT_THROW(chan < TOTAL_RADIO_PORTS);
if (chan % 2 == 0) {
return {"TX1_FIR", "TX1TX2_FIR"};
} else {
return {"TX2_FIR", "TX1TX2_FIR"};
}
}
uhd::filter_info_base::sptr magnesium_radio_control_impl::get_tx_filter(
const std::string& name, const size_t)
{
if (_mpm_compat_num[0] < 4 || (_mpm_compat_num[0] == 4 && _mpm_compat_num[1] < 2)) {
RFNOC_LOG_WARNING("Getting tx filter not supported. Please upgrade MPM to a "
"minimum version of 4.2.");
return std::make_shared<uhd::digital_filter_fir<int16_t>>(
uhd::filter_info_base::filter_type::DIGITAL_FIR_I16,
false,
0,
1.0,
1,
1,
32767,
AD9371_TX_MAX_FIR_TAPS,
std::vector<int16_t>(AD9371_TX_MAX_FIR_TAPS, 0));
}
const auto rv = _ad9371->get_fir(name);
const auto taps = rv.second;
// TODO: Use gain in the digital_filter_fir
return std::make_shared<uhd::digital_filter_fir<int16_t>>(
uhd::filter_info_base::filter_type::DIGITAL_FIR_I16,
false,
0,
1.0,
1,
1,
32767,
AD9371_TX_MAX_FIR_TAPS,
taps);
}
void magnesium_radio_control_impl::set_tx_filter(
const std::string& name, uhd::filter_info_base::sptr filter, const size_t)
{
std::lock_guard<std::recursive_mutex> l(_set_lock);
if (_mpm_compat_num[0] < 4 || (_mpm_compat_num[0] == 4 && _mpm_compat_num[1] < 2)) {
RFNOC_LOG_WARNING("Setting tx filter not supported. Please upgrade MPM to a "
"minimum version of 4.2.");
return;
}
auto fir = std::dynamic_pointer_cast<uhd::digital_filter_fir<int16_t>>(filter);
if (fir == nullptr) {
throw uhd::runtime_error("Invalid Filter Type for TX Filter");
}
if (fir->get_taps().size() != AD9371_TX_MAX_FIR_TAPS) {
throw uhd::runtime_error("AD937x TX Filter Taps must be "
+ std::to_string(AD9371_TX_MAX_FIR_TAPS)
+ " taps long!");
}
// TODO: Use gain in the digital_filter_fir
_ad9371->set_fir(name, 6, fir->get_taps());
}
/**************************************************************************
* Radio Identification API Calls
*************************************************************************/

View File

@ -14,6 +14,7 @@
#include "magnesium_ad9371_iface.hpp"
#include "magnesium_cpld_ctrl.hpp"
#include "magnesium_cpld_regs.hpp"
#include <uhd/rfnoc/filter_node.hpp>
#include <uhd/types/eeprom.hpp>
#include <uhd/types/serial.hpp>
#include <uhd/usrp/dboard_manager.hpp>
@ -31,7 +32,8 @@ namespace uhd { namespace rfnoc {
*
* This daughterboard is used on the USRP N310 and N300.
*/
class magnesium_radio_control_impl : public radio_control_impl
class magnesium_radio_control_impl : public radio_control_impl,
public uhd::rfnoc::detail::filter_node
{
public:
//! Frequency bands for RX. Bands are a function of the analog filter banks
@ -173,6 +175,23 @@ public:
std::string get_fe_name(
const size_t chan, const uhd::direction_t direction) const override;
/**************************************************************************
* Filter API
*************************************************************************/
std::vector<std::string> get_rx_filter_names(const size_t chan) const override;
uhd::filter_info_base::sptr get_rx_filter(
const std::string& name, const size_t chan) override;
void set_rx_filter(const std::string& name,
uhd::filter_info_base::sptr filter,
const size_t chan) override;
std::vector<std::string> get_tx_filter_names(const size_t chan) const override;
uhd::filter_info_base::sptr get_tx_filter(
const std::string& name, const size_t chan) override;
void set_tx_filter(const std::string& name,
uhd::filter_info_base::sptr filter,
const size_t chan) override;
private:
/**************************************************************************
* Helpers

View File

@ -17,6 +17,7 @@
#include <uhd/usrp/multi_usrp.hpp>
#include <uhd/utils/graph_utils.hpp>
#include <uhd/utils/math.hpp>
#include <uhd/utils/string.hpp>
#include <uhdlib/rfnoc/rfnoc_device.hpp>
#include <uhdlib/rfnoc/rfnoc_rx_streamer.hpp>
#include <uhdlib/rfnoc/rfnoc_tx_streamer.hpp>
@ -2229,11 +2230,13 @@ public:
const std::string& name, const size_t chan) override
{
try {
// Get the blockid and filtername separated from the name string
const auto names = string::split(name, ":");
const auto& blockid = names.first;
const auto& filter_name = names.second;
// The block_id_t constructor is pretty smart; let it handle the parsing.
block_id_t block_id(name);
auto rx_chan = _get_rx_chan(chan);
// The filter name is the `name` after the BLOCK_ID and a `:`
std::string filter_name = name.substr(block_id.to_string().size() + 1);
block_id_t block_id(blockid);
const auto rx_chan = _get_rx_chan(chan);
// Try to dynamic cast either the radio or the DDC to a filter_node, and call
// its filter function
auto block_ctrl = [rx_chan, block_id, chan]() -> noc_block_base::sptr {
@ -2270,11 +2273,12 @@ public:
{
MUX_RX_API_CALL(set_rx_filter, name, filter);
try {
const auto names = string::split(name, ":");
const auto& blockid = names.first;
const auto& filter_name = names.second;
// The block_id_t constructor is pretty smart; let it handle the parsing.
block_id_t block_id(name);
auto rx_chan = _get_rx_chan(chan);
// The filter name is the `name` after the BLOCK_ID and a `:`
std::string filter_name = name.substr(block_id.to_string().size() + 1);
block_id_t block_id(blockid);
const auto rx_chan = _get_rx_chan(chan);
// Try to dynamic cast either the radio or the DDC to a filter_node, and call
// its filter function
auto block_ctrl = [rx_chan, block_id, chan]() -> noc_block_base::sptr {
@ -2352,11 +2356,12 @@ public:
const std::string& name, const size_t chan) override
{
try {
const auto names = string::split(name, ":");
const auto& blockid = names.first;
const auto& filter_name = names.second;
// The block_id_t constructor is pretty smart; let it handle the parsing.
block_id_t block_id(name);
auto tx_chan = _get_tx_chan(chan);
// The filter name is the `name` after the BLOCK_ID and a `:`
std::string filter_name = name.substr(block_id.to_string().size() + 1);
block_id_t block_id(blockid);
const auto tx_chan = _get_tx_chan(chan);
// Try to dynamic cast either the radio or the DUC to a filter_node, and call
// its filter function
auto block_ctrl = [tx_chan, block_id, chan]() -> noc_block_base::sptr {
@ -2393,11 +2398,12 @@ public:
{
MUX_TX_API_CALL(set_tx_filter, name, filter);
try {
const auto names = string::split(name, ":");
const auto& blockid = names.first;
const auto& filter_name = names.second;
// The block_id_t constructor is pretty smart; let it handle the parsing.
block_id_t block_id(name);
auto tx_chan = _get_tx_chan(chan);
// The filter name is the `name` after the BLOCK_ID and a `:`
std::string filter_name = name.substr(block_id.to_string().size() + 1);
block_id_t block_id(blockid);
const auto tx_chan = _get_tx_chan(chan);
// Try to dynamic cast either the radio or the DUC to a filter_node, and call
// its filter function
auto block_ctrl = [tx_chan, block_id, chan]() -> noc_block_base::sptr {

View File

@ -7,6 +7,7 @@
#include <pybind11/complex.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include <numpy/arrayobject.h>

View File

@ -259,7 +259,7 @@ public:
const std::string& which, const int8_t gain, const std::vector<int16_t>& fir) = 0;
//! get the FIR filter for the frontend which
virtual std::vector<int16_t> get_fir(const std::string& which, int8_t& gain) = 0;
virtual std::pair<int8_t, std::vector<int16_t>> get_fir(const std::string& which) = 0;
// TODO: update docstring with temperature unit and calibration information
//! get the device temperature

View File

@ -430,7 +430,6 @@ public:
virtual void set_fir(
const std::string& which, const int8_t gain, const std::vector<int16_t>& fir)
{
const auto dir = _get_direction_from_antenna(which);
const auto lengths = _get_valid_fir_lengths(which);
if (std::find(lengths.begin(), lengths.end(), fir.size()) == lengths.end()) {
@ -438,15 +437,13 @@ public:
}
std::lock_guard<std::mutex> lock(*spi_mutex);
device.set_fir(dir, gain, fir);
device.set_fir(which, gain, fir);
}
virtual std::vector<int16_t> get_fir(const std::string& which, int8_t& gain)
virtual std::pair<int8_t, std::vector<int16_t>> get_fir(const std::string& which)
{
auto dir = _get_direction_from_antenna(which);
std::lock_guard<std::mutex> lock(*spi_mutex);
return device.get_fir(dir, gain);
return device.get_fir(which);
}
virtual int16_t get_temperature()

View File

@ -10,6 +10,7 @@
#include "adi/mykonos_gpio.h"
#include "config/ad937x_config_t.hpp"
#include "config/ad937x_default_config.hpp"
#include "mpm/ad937x/ad937x_ctrl.hpp"
#include <uhd/utils/algorithm.hpp>
#include <boost/format.hpp>
#include <cmath>
@ -20,6 +21,7 @@
using namespace mpm::ad937x::device;
using namespace mpm::ad937x::gpio;
using namespace mpm::chips;
using namespace uhd;
const double ad937x_device::MIN_FREQ = 300e6;
@ -59,6 +61,20 @@ static constexpr double AD9371_TX_DAC_FILT_MAX_CORNER = 187.0e6; // Hz
static const uint32_t PLL_LOCK_TIMEOUT_MS = 200;
const std::map<std::string, mykonosfirName_t> ad937x_device::_tx_filter_map{
{"TX1_FIR", TX1_FIR},
{"TX2_FIR", TX2_FIR},
{"TX1TX2_FIR", TX1TX2_FIR},
};
const std::map<std::string, mykonosfirName_t> ad937x_device::_rx_filter_map{
{"RX1_FIR", RX1_FIR},
{"RX2_FIR", RX2_FIR},
{"RX1RX2_FIR", RX1RX2_FIR},
{"OBSRX_A_FIR", OBSRX_A_FIR},
{"OBSRX_B_FIR", OBSRX_B_FIR},
};
// Amount of time to average samples for RX DC offset
// A larger averaging window will result in:
// Longer latency to correct DC offset changes
@ -666,6 +682,28 @@ void ad937x_device::set_agc_mode(const direction_t direction, const gain_mode_t
_restore_from_config_state(state);
}
void ad937x_device::set_fir(
const std::string& name, int8_t gain, const std::vector<int16_t>& fir)
{
mykonosfirName_t filter_name;
if (_rx_filter_map.count(name) == 1) {
filter_name = _rx_filter_map.at(name);
mykonos_config.rx_fir_config.set_fir(gain, fir);
} else if (_tx_filter_map.count(name) == 1) {
filter_name = _tx_filter_map.at(name);
mykonos_config.tx_fir_config.set_fir(gain, fir);
} else {
throw mpm::runtime_error("set_fir invalid name: " + name);
}
mykonosFir_t filter{.gain_dB = gain,
.numFirCoefs = static_cast<uint8_t>(fir.size()),
.coefs = const_cast<int16_t*>(fir.data())};
const auto state = _move_to_config_state();
CALL_API(MYKONOS_programFir(mykonos_config.device, filter_name, &filter));
_restore_from_config_state(state);
}
void ad937x_device::set_fir(
const direction_t direction, int8_t gain, const std::vector<int16_t>& fir)
{
@ -679,8 +717,13 @@ void ad937x_device::set_fir(
default:
MPM_THROW_INVALID_CODE_PATH();
}
// TODO: reload this on device
mykonosfirName_t filter_name = (direction == TX_DIRECTION) ? TX1TX2_FIR : RX1RX2_FIR;
mykonosFir_t filter{.gain_dB = gain,
.numFirCoefs = static_cast<uint8_t>(fir.size()),
.coefs = const_cast<int16_t*>(fir.data())};
const auto state = _move_to_config_state();
CALL_API(MYKONOS_programFir(mykonos_config.device, filter_name, &filter));
_restore_from_config_state(state);
}
void ad937x_device::set_gain_pin_step_sizes(const direction_t direction,
@ -795,16 +838,24 @@ double ad937x_device::get_gain(const direction_t direction, const chain_t chain)
}
}
std::vector<int16_t> ad937x_device::get_fir(const direction_t direction, int8_t& gain)
std::pair<int8_t, std::vector<int16_t>> ad937x_device::get_fir(const std::string& name)
{
switch (direction) {
case TX_DIRECTION:
return mykonos_config.tx_fir_config.get_fir(gain);
case RX_DIRECTION:
return mykonos_config.rx_fir_config.get_fir(gain);
default:
MPM_THROW_INVALID_CODE_PATH();
mykonosfirName_t filter_name;
if (_rx_filter_map.count(name) == 1) {
filter_name = _rx_filter_map.at(name);
} else if (_tx_filter_map.count(name) == 1) {
filter_name = _tx_filter_map.at(name);
} else {
throw mpm::runtime_error("get_fir invalid name: " + name);
}
mykonosFir_t fir;
std::vector<int16_t> rv(96, 0);
fir.coefs = rv.data();
const auto state = _move_to_config_state();
CALL_API(MYKONOS_readFir(mykonos_config.device, filter_name, &fir));
_restore_from_config_state(state);
rv.resize(fir.numFirCoefs);
return std::pair<int8_t, std::vector<int16_t>>(fir.gain_dB, rv);
}
int16_t ad937x_device::get_temperature()

View File

@ -20,6 +20,7 @@
#include <boost/filesystem.hpp>
#include <boost/noncopyable.hpp>
#include <functional>
#include <map>
#include <memory>
class ad937x_device : public boost::noncopyable
@ -82,9 +83,10 @@ public:
bool get_pll_lock_status(const uint8_t pll, const bool wait_for_lock = false);
void set_fir(const std::string& name, int8_t gain, const std::vector<int16_t>& fir);
void set_fir(
const uhd::direction_t direction, int8_t gain, const std::vector<int16_t>& fir);
std::vector<int16_t> get_fir(const uhd::direction_t direction, int8_t& gain);
std::pair<int8_t, std::vector<int16_t>> get_fir(const std::string& name);
int16_t get_temperature();
@ -136,4 +138,7 @@ private:
static double _convert_rx_gain_from_mykonos(const uint8_t gain);
static uint16_t _convert_tx_gain_to_mykonos(const double gain);
static double _convert_tx_gain_from_mykonos(const uint16_t gain);
const static std::map<std::string, mykonosfirName_t> _tx_filter_map;
const static std::map<std::string, mykonosfirName_t> _rx_filter_map;
};

View File

@ -493,6 +493,15 @@ class Magnesium(BfrfsEEPROM, DboardManagerBase):
self._reinit(self.master_clock_rate)
return bw
def set_fir(self, name, gain, coeffs):
self.log.debug("ad9371 set_fir name: {} gain: {} coeffs: {}".format(name, gain, coeffs))
self.mykonos.set_fir(name, gain, coeffs)
return
def get_fir(self, name):
self.log.debug("ad9371 get_fir name: {}".format(name))
return self.mykonos.get_fir(name)
##########################################################################
# Debug
##########################################################################

View File

@ -32,7 +32,7 @@ from usrp_mpm.sys_utils import net
TIMEOUT_INTERVAL = 5.0 # Seconds before claim expires (default value)
TOKEN_LEN = 16 # Length of the token string
# Compatibility number for MPM
MPM_COMPAT_NUM = (4, 1)
MPM_COMPAT_NUM = (4, 2)
def no_claim(func):
" Decorator for functions that require no token check "