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