1 //
2 // Copyright 2020 Ettus Research, a National Instruments Brand
3 //
4 // SPDX-License-Identifier: GPL-3.0-or-later
5 //
6 
7 #include <uhd/convert.hpp>
8 #include <uhd/exception.hpp>
9 #include <uhd/rfnoc/defaults.hpp>
10 #include <uhd/rfnoc/multichan_register_iface.hpp>
11 #include <uhd/rfnoc/property.hpp>
12 #include <uhd/rfnoc/registry.hpp>
13 #include <uhd/rfnoc/siggen_block_control.hpp>
14 #include <uhd/utils/math.hpp>
15 #include <uhdlib/utils/narrow.hpp>
16 #include <limits>
17 #include <string>
18 
19 using namespace uhd::rfnoc;
20 
21 
22 // Register offsets
23 const uint32_t siggen_block_control::REG_BLOCK_SIZE       = 1 << 5;
24 const uint32_t siggen_block_control::REG_ENABLE_OFFSET    = 0x00;
25 const uint32_t siggen_block_control::REG_SPP_OFFSET       = 0x04;
26 const uint32_t siggen_block_control::REG_WAVEFORM_OFFSET  = 0x08;
27 const uint32_t siggen_block_control::REG_GAIN_OFFSET      = 0x0C;
28 const uint32_t siggen_block_control::REG_CONSTANT_OFFSET  = 0x10;
29 const uint32_t siggen_block_control::REG_PHASE_INC_OFFSET = 0x14;
30 const uint32_t siggen_block_control::REG_CARTESIAN_OFFSET = 0x18;
31 
32 // User property names
33 const char* const PROP_KEY_ENABLE         = "enable";
34 const char* const PROP_KEY_WAVEFORM       = "waveform";
35 const char* const PROP_KEY_AMPLITUDE      = "amplitude";
36 const char* const PROP_KEY_CONSTANT_I     = "constant_i";
37 const char* const PROP_KEY_CONSTANT_Q     = "constant_q";
38 const char* const PROP_KEY_SINE_PHASE_INC = "sine_phase_increment";
39 
40 namespace {
41 template <class T>
clamp(const double v)42 const T clamp(const double v)
43 {
44     constexpr T min_t = std::numeric_limits<T>::min();
45     constexpr T max_t = std::numeric_limits<T>::max();
46     return (v < min_t) ? min_t : (v > max_t) ? max_t : T(v);
47 }
48 } // namespace
49 
50 class siggen_block_control_impl : public siggen_block_control
51 {
52 public:
RFNOC_BLOCK_CONSTRUCTOR(siggen_block_control)53     RFNOC_BLOCK_CONSTRUCTOR(siggen_block_control),
54         _siggen_reg_iface(*this, 0, REG_BLOCK_SIZE)
55     {
56         _register_props();
57     }
58 
set_enable(const bool enable,const size_t port)59     void set_enable(const bool enable, const size_t port)
60     {
61         set_property<bool>(PROP_KEY_ENABLE, enable, port);
62     }
63 
get_enable(const size_t port) const64     bool get_enable(const size_t port) const
65     {
66         return _prop_enable.at(port).get();
67     }
68 
set_waveform(const siggen_waveform waveform,const size_t port)69     void set_waveform(const siggen_waveform waveform, const size_t port)
70     {
71         set_property<int>(PROP_KEY_WAVEFORM, static_cast<int>(waveform), port);
72     }
73 
get_waveform(const size_t port) const74     siggen_waveform get_waveform(const size_t port) const
75     {
76         return static_cast<siggen_waveform>(_prop_waveform.at(port).get());
77     }
78 
set_amplitude(const double amplitude,const size_t port)79     void set_amplitude(const double amplitude, const size_t port)
80     {
81         set_property<double>(PROP_KEY_AMPLITUDE, amplitude, port);
82     }
83 
get_amplitude(const size_t port) const84     double get_amplitude(const size_t port) const
85     {
86         return _prop_amplitude.at(port).get();
87     }
88 
set_constant(const std::complex<double> constant,const size_t port)89     void set_constant(const std::complex<double> constant, const size_t port)
90     {
91         set_property<double>(PROP_KEY_CONSTANT_I, constant.real(), port);
92         set_property<double>(PROP_KEY_CONSTANT_Q, constant.imag(), port);
93     }
94 
get_constant(const size_t port) const95     std::complex<double> get_constant(const size_t port) const
96     {
97         return std::complex<double>(
98             _prop_constant_i.at(port).get(), _prop_constant_q.at(port).get());
99     }
100 
set_sine_phase_increment(const double phase_inc,const size_t port)101     void set_sine_phase_increment(const double phase_inc, const size_t port)
102     {
103         set_property<double>(PROP_KEY_SINE_PHASE_INC, phase_inc, port);
104     }
105 
get_sine_phase_increment(const size_t port) const106     double get_sine_phase_increment(const size_t port) const
107     {
108         return _prop_phase_inc.at(port).get();
109     }
110 
set_samples_per_packet(const size_t spp,const size_t port)111     void set_samples_per_packet(const size_t spp, const size_t port)
112     {
113         set_property<int>(PROP_KEY_SPP, uhd::narrow_cast<int>(spp), port);
114     }
115 
get_samples_per_packet(const size_t port) const116     size_t get_samples_per_packet(const size_t port) const
117     {
118         return _prop_spp.at(port).get();
119     }
120 
121     /**************************************************************************
122      * Initialization
123      *************************************************************************/
124 private:
_register_props()125     void _register_props()
126     {
127         const size_t num_outputs = get_num_output_ports();
128         _prop_enable.reserve(num_outputs);
129         _prop_waveform.reserve(num_outputs);
130         _prop_amplitude.reserve(num_outputs);
131         _prop_constant_i.reserve(num_outputs);
132         _prop_constant_q.reserve(num_outputs);
133         _prop_phase_inc.reserve(num_outputs);
134         _prop_spp.reserve(num_outputs);
135         _prop_type_out.reserve(num_outputs);
136 
137         for (size_t port = 0; port < num_outputs; port++) {
138             // register edge properties
139             _prop_type_out.emplace_back(property_t<std::string>{
140                 PROP_KEY_TYPE, IO_TYPE_SC16, {res_source_info::OUTPUT_EDGE, port}});
141             register_property(&_prop_type_out.back());
142 
143             // register user properties
144             _prop_enable.emplace_back(
145                 property_t<bool>{PROP_KEY_ENABLE, false, {res_source_info::USER, port}});
146             _prop_waveform.emplace_back(property_t<int>{PROP_KEY_WAVEFORM,
147                 static_cast<int>(siggen_waveform::CONSTANT),
148                 {res_source_info::USER, port}});
149             _prop_amplitude.emplace_back(property_t<double>{
150                 PROP_KEY_AMPLITUDE, 1.0, {res_source_info::USER, port}});
151             _prop_constant_i.emplace_back(property_t<double>{
152                 PROP_KEY_CONSTANT_I, 1.0, {res_source_info::USER, port}});
153             _prop_constant_q.emplace_back(property_t<double>{
154                 PROP_KEY_CONSTANT_Q, 1.0, {res_source_info::USER, port}});
155             _prop_phase_inc.emplace_back(property_t<double>{
156                 PROP_KEY_SINE_PHASE_INC, 1.0, {res_source_info::USER, port}});
157             _prop_spp.emplace_back(property_t<int>{
158                 PROP_KEY_SPP, DEFAULT_SPP, {res_source_info::USER, port}});
159 
160             register_property(&_prop_enable.back(), [this, port]() {
161                 _siggen_reg_iface.poke32(REG_ENABLE_OFFSET,
162                     uint32_t(_prop_enable.at(port).get() ? 1 : 0),
163                     port);
164             });
165             register_property(&_prop_waveform.back());
166             register_property(&_prop_amplitude.back());
167             register_property(&_prop_constant_i.back(), [this, port]() {
168                 const double constant_i = _prop_constant_i.at(port).get();
169                 if (constant_i < -1.0 || constant_i > 1.0) {
170                     throw uhd::value_error("Constant real value must be in [-1.0, 1.0]");
171                 }
172                 _set_constant_register(port);
173             });
174             register_property(&_prop_constant_q.back(), [this, port]() {
175                 const double constant_q = _prop_constant_q.at(port).get();
176                 if (constant_q < -1.0 || constant_q > 1.0) {
177                     throw uhd::value_error(
178                         "Constant imaginary value must be in [-1.0, 1.0]");
179                 }
180                 _set_constant_register(port);
181             });
182             register_property(&_prop_phase_inc.back(), [this, port]() {
183                 const double phase_inc = _prop_phase_inc.at(port).get();
184                 if (phase_inc < (-uhd::math::PI) || phase_inc > (uhd::math::PI)) {
185                     throw uhd::value_error(
186                         "Phase increment value must be in [-pi, pi]");
187                 }
188                 const int16_t phase_inc_scaled_rads_fp =
189                     clamp<int16_t>((phase_inc / uhd::math::PI) * 8192.0);
190                 _siggen_reg_iface.poke32(
191                     REG_PHASE_INC_OFFSET, phase_inc_scaled_rads_fp & 0xffff, port);
192             });
193             register_property(&_prop_spp.back(), [this, port]() {
194                 const uint32_t spp = _prop_spp.at(port).get();
195                 RFNOC_LOG_TRACE(
196                     "Setting samples per packet to " << spp << " on port " << port);
197                 _siggen_reg_iface.poke32(REG_SPP_OFFSET, spp, port);
198             });
199 
200             add_property_resolver({&_prop_waveform.back(), &_prop_amplitude.back()},
201                 {&_prop_amplitude.back()},
202                 [this, port]() {
203                     // Range check the waveform and amplitude properties.
204                     // If either are out of range, throw an exception and
205                     // do not set any registers.
206                     const int waveform_val = _prop_waveform.at(port).get();
207                     const int low_limit    = static_cast<int>(siggen_waveform::CONSTANT);
208                     const int high_limit   = static_cast<int>(siggen_waveform::NOISE);
209                     if (waveform_val < low_limit || waveform_val > high_limit) {
210                         throw uhd::value_error("Waveform value must be in ["
211                                                + std::to_string(low_limit) + ", "
212                                                + std::to_string(high_limit) + "]");
213                     }
214                     const double amplitude = _prop_amplitude.at(port).get();
215                     if (amplitude < 0.0 || amplitude > 1.0) {
216                         throw uhd::value_error("Amplitude value must be in [0.0, 1.0]");
217                     }
218 
219                     // Set the waveform register appropriately.
220                     _siggen_reg_iface.poke32(REG_WAVEFORM_OFFSET, waveform_val, port);
221 
222                     // Now set the other registers based on the waveform and
223                     // the desired amplitude.
224                     siggen_waveform waveform = static_cast<siggen_waveform>(waveform_val);
225                     switch (waveform) {
226                         case siggen_waveform::CONSTANT:
227                             // The amplitude is fixed at 1 in constant mode.
228                             _prop_amplitude.at(port).set(1.0);
229                             _set_gain_register(1.0, port);
230                             break;
231                         case siggen_waveform::SINE_WAVE: {
232                             // Set the phasor to the appropriate amplitude value and
233                             // fix the gain to 1.
234 
235                             // The CORDIC IP scales the value written to the Cartesian
236                             // coordinate register (i.e., the phasor that is rotated to
237                             // generate the sinusoid) by this value, so we pre-scale the
238                             // input value before writing. See the comment in the
239                             // rfnoc_block_siggen_regs.vh header file for the derivation
240                             // of this value.
241                             constexpr double cordic_scale_value = 1.164435344782938;
242                             _set_cartesian_register(amplitude / cordic_scale_value, port);
243                             _set_gain_register(1.0, port);
244                             break;
245                         }
246                         case siggen_waveform::NOISE:
247                             // Use the gain register to set the gain of the random noise
248                             // signal.
249                             _set_gain_register(amplitude, port);
250                             break;
251                     }
252                 });
253 
254             add_property_resolver(
255                 {&_prop_spp.back(),
256                     get_mtu_prop_ref({res_source_info::OUTPUT_EDGE, port})},
257                 {&_prop_spp.back()},
258                 [this, port]() {
259                     // MTU is max payload size, header with timestamp is already
260                     // accounted for
261                     int spp = _prop_spp.at(port).get();
262                     const int mtu =
263                         static_cast<int>(get_mtu({res_source_info::OUTPUT_EDGE, port}));
264                     const int mtu_samps =
265                         mtu
266                         / uhd::convert::get_bytes_per_item(_prop_type_out.at(port).get());
267                     if (spp > mtu_samps) {
268                         RFNOC_LOG_WARNING("spp value " << spp << " exceeds MTU of " << mtu
269                                                        << "! Coercing to " << mtu_samps);
270                         spp = mtu_samps;
271                     }
272                     if (spp <= 0) {
273                         spp = DEFAULT_SPP;
274                         RFNOC_LOG_WARNING(
275                             "spp must be greater than zero! Coercing to " << spp);
276                     }
277                     _prop_spp.at(port).set(spp);
278                 });
279 
280             // add resolver for type
281             add_property_resolver({&_prop_type_out.back()},
282                 {&_prop_type_out.back()},
283                 [this, port]() { _prop_type_out.at(port).set(IO_TYPE_SC16); });
284         }
285     }
286 
_set_constant_register(const size_t port)287     void _set_constant_register(const size_t port)
288     {
289         const int16_t constant_i_fp =
290             clamp<int16_t>(_prop_constant_i.at(port).get() * 32768.0);
291         const int16_t constant_q_fp =
292             clamp<int16_t>(_prop_constant_q.at(port).get() * 32768.0);
293         const uint32_t constant_reg_value = (uint32_t(constant_i_fp) << 16)
294                                       | (uint32_t(constant_q_fp) & 0xffff);
295 
296         _siggen_reg_iface.poke32(REG_CONSTANT_OFFSET, constant_reg_value, port);
297     }
298 
_set_gain_register(const double gain,const size_t port)299     void _set_gain_register(const double gain, const size_t port)
300     {
301         const int16_t gain_fp = clamp<int16_t>(gain * 32768.0);
302         _siggen_reg_iface.poke32(REG_GAIN_OFFSET, gain_fp, port);
303     }
304 
_set_cartesian_register(const double amplitude,const size_t port)305     void _set_cartesian_register(const double amplitude, const size_t port)
306     {
307         // The rotator that rotates the phasor to generate the sinusoidal
308         // data has an initial phase offset which is impossible to predict.
309         // Thus, the Cartesian parameter is largely immaterial, as long as
310         // the phasor's amplitude matchines with the client has specified.
311         // For simplicity, the Cartesian parameter is chosen to have a real
312         // (X) component of 0.0 and an imaginary (Y) component of the desired
313         // amplitude.
314         const int16_t cartesian_i_fp = clamp<int16_t>(amplitude * 32767.0);
315 
316         // Bits 31:16 represent the imaginary component (the pre-scaled
317         // fixed point amplitude), while bits 15:0 represents the real
318         // component (which are zeroed).
319         const uint32_t cartesian_reg_value = (uint32_t(cartesian_i_fp) << 16);
320         _siggen_reg_iface.poke32(REG_CARTESIAN_OFFSET, cartesian_reg_value, port);
321     }
322 
323     /**************************************************************************
324      * Attributes
325      *************************************************************************/
326     std::vector<property_t<bool>> _prop_enable;
327     std::vector<property_t<int>> _prop_waveform;
328     std::vector<property_t<double>> _prop_amplitude;
329     std::vector<property_t<double>> _prop_constant_i;
330     std::vector<property_t<double>> _prop_constant_q;
331     std::vector<property_t<double>> _prop_phase_inc;
332     std::vector<property_t<int>> _prop_spp;
333     std::vector<property_t<std::string>> _prop_type_out;
334 
335     /**************************************************************************
336      * Register interface
337      *************************************************************************/
338     multichan_register_iface _siggen_reg_iface;
339 };
340 
341 UHD_RFNOC_BLOCK_REGISTER_DIRECT(
342     siggen_block_control, SIGGEN_BLOCK, "SigGen", CLOCK_KEY_GRAPH, "bus_clk")
343