1 //
2 // Copyright 2011-2014 Ettus Research LLC
3 // Copyright 2018 Ettus Research, a National Instruments Company
4 //
5 // SPDX-License-Identifier: GPL-3.0-or-later
6 //
7
8 #include <uhd/exception.hpp>
9 #include <uhd/types/dict.hpp>
10 #include <uhd/utils/log.hpp>
11 #include <uhd/utils/math.hpp>
12 #include <uhd/utils/safe_call.hpp>
13 #include <uhdlib/usrp/cores/dsp_core_utils.hpp>
14 #include <uhdlib/usrp/cores/rx_dsp_core_3000.hpp>
15 #include <boost/assign/list_of.hpp>
16 #include <boost/math/special_functions/round.hpp>
17 #include <algorithm>
18 #include <cmath>
19 #include <functional>
20
21 #define REG_DSP_RX_FREQ _dsp_base + 0
22 #define REG_DSP_RX_SCALE_IQ _dsp_base + 4
23 #define REG_DSP_RX_DECIM _dsp_base + 8
24 #define REG_DSP_RX_MUX _dsp_base + 12
25 #define REG_DSP_RX_COEFFS _dsp_base + 16
26 // FIXME: Add code to support REG_DSP_RX_COEFFS
27
28 #define FLAG_DSP_RX_MUX_SWAP_IQ (1 << 0)
29 #define FLAG_DSP_RX_MUX_REAL_MODE (1 << 1)
30 #define FLAG_DSP_RX_MUX_INVERT_Q (1 << 2)
31 #define FLAG_DSP_RX_MUX_INVERT_I (1 << 3)
32
33 template <class T>
ceil_log2(T num)34 T ceil_log2(T num)
35 {
36 return std::ceil(std::log(num) / std::log(T(2)));
37 }
38
39 using namespace uhd;
40
41 const double rx_dsp_core_3000::DEFAULT_CORDIC_FREQ = 0.0;
42 const double rx_dsp_core_3000::DEFAULT_RATE = 1e6;
43
~rx_dsp_core_3000(void)44 rx_dsp_core_3000::~rx_dsp_core_3000(void)
45 {
46 /* NOP */
47 }
48
49 class rx_dsp_core_3000_impl : public rx_dsp_core_3000
50 {
51 public:
rx_dsp_core_3000_impl(wb_iface::sptr iface,const size_t dsp_base,const bool is_b200)52 rx_dsp_core_3000_impl(wb_iface::sptr iface, const size_t dsp_base, const bool is_b200)
53 : _iface(iface), _dsp_base(dsp_base), _is_b200(is_b200)
54 {
55 }
56
~rx_dsp_core_3000_impl(void)57 ~rx_dsp_core_3000_impl(void)
58 {
59 UHD_SAFE_CALL(; // NOP
60 )
61 }
62
set_mux(const uhd::usrp::fe_connection_t & fe_conn)63 void set_mux(const uhd::usrp::fe_connection_t& fe_conn)
64 {
65 uint32_t reg_val = 0;
66 switch (fe_conn.get_sampling_mode()) {
67 case uhd::usrp::fe_connection_t::REAL:
68 case uhd::usrp::fe_connection_t::HETERODYNE:
69 reg_val = FLAG_DSP_RX_MUX_REAL_MODE;
70 break;
71 default:
72 reg_val = 0;
73 break;
74 }
75
76 if (fe_conn.is_iq_swapped())
77 reg_val |= FLAG_DSP_RX_MUX_SWAP_IQ;
78 if (fe_conn.is_i_inverted())
79 reg_val |= FLAG_DSP_RX_MUX_INVERT_I;
80 if (fe_conn.is_q_inverted())
81 reg_val |= FLAG_DSP_RX_MUX_INVERT_Q;
82
83 _iface->poke32(REG_DSP_RX_MUX, reg_val);
84
85 if (fe_conn.get_sampling_mode() == uhd::usrp::fe_connection_t::HETERODYNE) {
86 // 1. Remember the sign of the IF frequency.
87 // It will be discarded in the next step
88 int if_freq_sign = boost::math::sign(fe_conn.get_if_freq());
89 // 2. Map IF frequency to the range [0, _tick_rate)
90 double if_freq = std::abs(std::fmod(fe_conn.get_if_freq(), _tick_rate));
91 // 3. Map IF frequency to the range [-_tick_rate/2, _tick_rate/2)
92 // This is the aliased frequency
93 if (if_freq > (_tick_rate / 2.0)) {
94 if_freq -= _tick_rate;
95 }
96 // 4. Set DSP offset to spin the signal in the opposite
97 // direction as the aliased frequency
98 _dsp_freq_offset = if_freq * (-if_freq_sign);
99 } else {
100 _dsp_freq_offset = 0.0;
101 }
102 }
103
set_tick_rate(const double rate)104 void set_tick_rate(const double rate)
105 {
106 _tick_rate = rate;
107 set_freq(_current_freq);
108 }
109
set_link_rate(const double rate)110 void set_link_rate(const double rate)
111 {
112 //_link_rate = rate/sizeof(uint32_t); //in samps/s
113 _link_rate = rate / sizeof(uint16_t); // in samps/s (allows for 8sc)
114 }
115
get_host_rates(void)116 uhd::meta_range_t get_host_rates(void)
117 {
118 meta_range_t range;
119 if (!_is_b200) {
120 for (int rate = 1024; rate > 512; rate -= 8) {
121 range.push_back(range_t(_tick_rate / rate));
122 }
123 }
124 for (int rate = 512; rate > 256; rate -= 4) {
125 range.push_back(range_t(_tick_rate / rate));
126 }
127 for (int rate = 256; rate > 128; rate -= 2) {
128 range.push_back(range_t(_tick_rate / rate));
129 }
130 for (int rate = 128; rate >= int(std::ceil(_tick_rate / _link_rate)); rate -= 1) {
131 range.push_back(range_t(_tick_rate / rate));
132 }
133 return range;
134 }
135
set_host_rate(const double rate)136 double set_host_rate(const double rate)
137 {
138 const size_t decim_rate =
139 boost::math::iround(_tick_rate / this->get_host_rates().clip(rate, true));
140 size_t decim = decim_rate;
141
142 // determine which half-band filters are activated
143 int hb0 = 0, hb1 = 0, hb2 = 0, hb_enable = 0;
144 if (decim % 2 == 0) {
145 hb0 = 1;
146 decim /= 2;
147 }
148 if (decim % 2 == 0) {
149 hb1 = 1;
150 decim /= 2;
151 }
152 // the third half-band is not supported by the B200
153 if (decim % 2 == 0 && !_is_b200) {
154 hb2 = 1;
155 decim /= 2;
156 }
157
158 if (_is_b200) {
159 _iface->poke32(REG_DSP_RX_DECIM,
160 (hb0 << 9) /*small HB */ | (hb1 << 8) /*large HB*/ | (decim & 0xff));
161
162 if (decim > 1 and hb0 == 0 and hb1 == 0) {
163 UHD_LOGGER_WARNING("CORES")
164 << boost::format(
165 "The requested decimation is odd; the user should expect CIC "
166 "rolloff.\n"
167 "Select an even decimation to ensure that a halfband filter "
168 "is enabled.\n"
169 "decimation = dsp_rate/samp_rate -> %d = (%f MHz)/(%f MHz)\n")
170 % decim_rate % (_tick_rate / 1e6) % (rate / 1e6);
171 }
172 } else {
173 // Encode Halfband config for setting register programming.
174 if (hb2) { // Implies HB1 and HB0 also asserted
175 hb_enable = 3;
176 } else if (hb1) { // Implies HB0 is also asserted
177 hb_enable = 2;
178 } else if (hb0) {
179 hb_enable = 1;
180 } else {
181 hb_enable = 0;
182 }
183 _iface->poke32(REG_DSP_RX_DECIM, (hb_enable << 8) | (decim & 0xff));
184
185 if (decim > 1 and hb0 == 0 and hb1 == 0 and hb2 == 0) {
186 UHD_LOGGER_WARNING("CORES")
187 << boost::format(
188 "The requested decimation is odd; the user should expect "
189 "passband CIC rolloff.\n"
190 "Select an even decimation to ensure that a halfband filter "
191 "is enabled.\n"
192 "Decimations factorable by 4 will enable 2 halfbands, those "
193 "factorable by 8 will enable 3 halfbands.\n"
194 "decimation = dsp_rate/samp_rate -> %d = (%f MHz)/(%f MHz)\n")
195 % decim_rate % (_tick_rate / 1e6) % (rate / 1e6);
196 }
197 }
198
199 // Caclulate algorithmic gain of CIC for a given decimation.
200 // For Ettus CIC R=decim, M=1, N=4. Gain = (R * M) ^ N
201 const double rate_pow = std::pow(double(decim & 0xff), 4);
202 // Calculate compensation gain values for algorithmic gain of CORDIC and CIC
203 // taking into account gain compensation blocks already hardcoded in place in DDC
204 // (that provide simple 1/2^n gain compensation). CORDIC algorithmic gain limits
205 // asymptotically around 1.647 after many iterations.
206 //
207 // The polar rotation of [I,Q] = [1,1] by Pi/8 also yields max magnitude of
208 // SQRT(2) (~1.4142) however input to the CORDIC thats outside the unit circle can
209 // only be sourced from a saturated RF frontend. To provide additional dynamic
210 // range head room accordingly using scale factor applied at egress from DDC would
211 // cost us small signal performance, thus we do no provide compensation gain for a
212 // saturated front end and allow the signal to clip in the H/W as needed. If we
213 // wished to avoid the signal clipping in these circumstances then adjust code to
214 // read: _scaling_adjustment = std::pow(2,
215 // ceil_log2(rate_pow))/(1.648*rate_pow*1.415);
216 _scaling_adjustment = std::pow(2, ceil_log2(rate_pow)) / (1.648 * rate_pow);
217
218 this->update_scalar();
219
220 return _tick_rate / decim_rate;
221 }
222
223 // Calculate compensation gain values for algorithmic gain of CORDIC and CIC taking
224 // into account gain compensation blocks already hardcoded in place in DDC (that
225 // provide simple 1/2^n gain compensation). Further more factor in OTW format which
226 // adds further gain factor to weight output samples correctly.
update_scalar(void)227 void update_scalar(void)
228 {
229 const double target_scalar =
230 (1 << (_is_b200 ? 16 : 15)) * _scaling_adjustment / _dsp_extra_scaling;
231 const int32_t actual_scalar = boost::math::iround(target_scalar);
232 // Calculate the error introduced by using integer representation for the scalar,
233 // can be corrected in host later.
234 _fxpt_scalar_correction = target_scalar / actual_scalar;
235 // Write DDC with scaling correction for CIC and CORDIC that maximizes dynamic
236 // range in 32/16/12/8bits.
237 _iface->poke32(REG_DSP_RX_SCALE_IQ, actual_scalar);
238 }
239
get_scaling_adjustment(void)240 double get_scaling_adjustment(void)
241 {
242 return _fxpt_scalar_correction * _host_extra_scaling / 32767.;
243 }
244
set_freq(const double requested_freq)245 double set_freq(const double requested_freq)
246 {
247 double actual_freq;
248 int32_t freq_word;
249 get_freq_and_freq_word(
250 requested_freq + _dsp_freq_offset, _tick_rate, actual_freq, freq_word);
251 _iface->poke32(REG_DSP_RX_FREQ, uint32_t(freq_word));
252 _current_freq = actual_freq;
253 return actual_freq;
254 }
255
get_freq(void)256 double get_freq(void)
257 {
258 return _current_freq;
259 }
260
get_freq_range(void)261 uhd::meta_range_t get_freq_range(void)
262 {
263 // Too keep the DSP range symmetric about 0, we use abs(_dsp_freq_offset)
264 const double offset = std::abs<double>(_dsp_freq_offset);
265 return uhd::meta_range_t(-(_tick_rate - offset) / 2,
266 +(_tick_rate - offset) / 2,
267 _tick_rate / std::pow(2.0, 32));
268 }
269
setup(const uhd::stream_args_t & stream_args)270 void setup(const uhd::stream_args_t& stream_args)
271 {
272 if (stream_args.otw_format == "sc16") {
273 _dsp_extra_scaling = 1.0;
274 _host_extra_scaling = 1.0;
275 } else if (stream_args.otw_format == "sc8") {
276 double peak = stream_args.args.cast<double>("peak", 1.0);
277 peak = std::max(peak, 1.0 / 256);
278 _host_extra_scaling = peak * 256;
279 _dsp_extra_scaling = peak;
280 } else if (stream_args.otw_format == "sc12") {
281 double peak = stream_args.args.cast<double>("peak", 1.0);
282 peak = std::max(peak, 1.0 / 16);
283 _host_extra_scaling = peak * 16;
284 _dsp_extra_scaling = peak;
285 } else if (stream_args.otw_format == "fc32") {
286 _host_extra_scaling = 1.0;
287 _dsp_extra_scaling = 1.0;
288 } else
289 throw uhd::value_error(
290 "USRP RX cannot handle requested wire format: " + stream_args.otw_format);
291
292 _host_extra_scaling *= stream_args.args.cast<double>("fullscale", 1.0);
293
294 this->update_scalar();
295 }
296
populate_subtree(property_tree::sptr subtree)297 void populate_subtree(property_tree::sptr subtree)
298 {
299 subtree->create<meta_range_t>("rate/range")
300 .set_publisher(std::bind(&rx_dsp_core_3000::get_host_rates, this));
301 subtree->create<double>("rate/value")
302 .set(DEFAULT_RATE)
303 .set_coercer(
304 std::bind(&rx_dsp_core_3000::set_host_rate, this, std::placeholders::_1));
305 subtree->create<double>("freq/value")
306 .set(DEFAULT_CORDIC_FREQ)
307 .set_coercer(
308 std::bind(&rx_dsp_core_3000::set_freq, this, std::placeholders::_1))
309 .set_publisher([this]() { return this->get_freq(); });
310 subtree->create<meta_range_t>("freq/range")
311 .set_publisher(std::bind(&rx_dsp_core_3000::get_freq_range, this));
312 }
313
314 private:
315 wb_iface::sptr _iface;
316 const size_t _dsp_base;
317 const bool _is_b200; // TODO: Obsolete this when we switch to the new DDC on the B200
318
319 double _dsp_freq_offset = 0.0;
320 double _tick_rate = 1.0;
321 double _link_rate = 0.0;
322 double _scaling_adjustment = 1.0;
323 double _dsp_extra_scaling = 1.0;
324 double _host_extra_scaling = 0.0;
325 double _fxpt_scalar_correction = 0.0;
326 double _current_freq = 0.0;
327 };
328
make(wb_iface::sptr iface,const size_t dsp_base,const bool is_b200)329 rx_dsp_core_3000::sptr rx_dsp_core_3000::make(
330 wb_iface::sptr iface, const size_t dsp_base, const bool is_b200 /* = false */)
331 {
332 return sptr(new rx_dsp_core_3000_impl(iface, dsp_base, is_b200));
333 }
334