maia_httpd/
fpga.rs

1//! Maia SDR FPGA IP core.
2//!
3//! This module contains the userspace driver that interfaces with the FPGA IP
4//! core.
5
6use crate::rxbuffer::RxBuffer;
7use crate::uio::{Mapping, Uio};
8use anyhow::{Context, Result};
9use std::sync::Arc;
10use tokio::sync::Notify;
11
12/// Maia SDR FPGA IP core.
13///
14/// This struct represents the FPGA IP core and gives access to its registers
15/// and DMA buffers.
16#[derive(Debug)]
17pub struct IpCore {
18    registers: Registers,
19    phys_addr: usize,
20    spectrometer: Dma,
21    // RAM-based cache for the number of spectrometer integrations and
22    // mode. These are used to speed up IpCore::spectrometer_number_integrations
23    // and IpCore::spectrometer_mode by avoiding to read the FPGA register.
24    spectrometer_integrations: u32,
25    spectrometer_mode: maia_json::SpectrometerMode,
26    spectrometer_input: maia_json::SpectrometerInput,
27    // RAM-based cache for DDC configuration
28    ddc_config: maia_json::PutDDCConfig,
29    ddc_enabled: bool,
30}
31
32/// Interrupt waiter.
33///
34/// This is associated with an interrupt of a particular type and can be used by
35/// a future to await until such an interrupt happens.
36#[derive(Debug)]
37pub struct InterruptWaiter {
38    notify: Arc<Notify>,
39}
40
41/// Interrupt handler.
42///
43/// Receives the interrupts produced by the FPGA IP core and sends notifications
44/// to the [`InterruptWaiter`]s. It is necessary to call
45/// [`InterruptHandler::run`] in order to receive and process interrupts.
46///
47/// # Examples
48///
49/// This shows how to create an `InterruptHandler`, obtain an `InterruptWaiter`,
50/// and wait for an interrupt, while running the `InterruptHandler` concurrently
51/// in a Tokio task.
52///
53///
54/// ```
55/// # async fn f() -> Result<(), anyhow::Error> {
56/// use maia_httpd::fpga::IpCore;
57///
58/// let (ip_core, interrupt_handler) = IpCore::take().await?;
59/// let waiter = interrupt_handler.waiter_recorder();
60/// tokio::spawn(async move { interrupt_handler.run() });
61/// waiter.wait().await;
62/// # Ok(())
63/// # }
64/// ```
65#[derive(Debug)]
66pub struct InterruptHandler {
67    uio: Uio,
68    registers: Registers, // should only access registers.interrupts
69    notify_spectrometer: Arc<Notify>,
70    notify_recorder: Arc<Notify>,
71}
72
73#[derive(Debug)]
74struct Dma {
75    buffer: RxBuffer,
76    last_written: Option<usize>,
77    num_buffers_mask: usize,
78}
79
80#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
81struct Version {
82    major: u8,
83    minor: u8,
84    bugfix: u8,
85}
86
87impl std::fmt::Display for Version {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
89        write!(f, "{}.{}.{}", self.major, self.minor, self.bugfix)
90    }
91}
92
93#[derive(Debug)]
94struct Registers(Mapping);
95
96impl std::ops::Deref for Registers {
97    type Target = maia_pac::maia_sdr::RegisterBlock;
98    fn deref(&self) -> &Self::Target {
99        unsafe { &*(self.0.addr() as *const maia_pac::maia_sdr::RegisterBlock) }
100    }
101}
102
103unsafe impl Send for Registers {}
104
105fn default_ddc_config() -> maia_json::PutDDCConfig {
106    // this design can be calculated quickly and it is good for sample rates
107    // above 61.44 Msps
108    let input_samp_rate = 61.44e6;
109    crate::ddc::make_design(
110        &maia_json::PutDDCDesign {
111            frequency: 0.0,
112            decimation: 20,
113            transition_bandwidth: None,
114            passband_ripple: None,
115            stopband_attenuation_db: None,
116            stopband_one_over_f: None,
117        },
118        input_samp_rate,
119    )
120    .unwrap()
121}
122
123macro_rules! impl_set_ddc_fir {
124    ($func:ident, $addr_offset:expr, $do_fold:expr, $decimation_reg:ident, $op_reg:ident, $odd_reg:ident) => {
125        fn $func(
126            &self,
127            coefficients: &[i32],
128            decimation: usize,
129            input_samp_rate: f64,
130        ) -> Result<()> {
131            use crate::ddc::constants;
132
133            const MIN_COEFF: i32 = -(1 << (constants::COEFFICIENT_BITS - 1));
134            const MAX_COEFF: i32 = (1 << (constants::COEFFICIENT_BITS - 1)) - 1;
135            if coefficients
136                .iter()
137                .any(|c| !(MIN_COEFF..=MAX_COEFF).contains(c))
138            {
139                anyhow::bail!("FIR coefficient out of range");
140            }
141            if !(2..=constants::MAX_DECIMATION).contains(&decimation) {
142                anyhow::bail!("decimation out of range");
143            }
144            if coefficients.is_empty() {
145                anyhow::bail!("no coefficients specified");
146            }
147            // Pretend that the coefficient list length is divisible by decimation
148            // by "virtually" extending the list with zeros.
149            let operations = coefficients.len().div_ceil(decimation);
150            let odd_operations = operations % 2 == 1;
151            let operations = if $do_fold {
152                operations.div_ceil(2)
153            } else {
154                operations
155            };
156            if operations > constants::MAX_OPERATIONS {
157                anyhow::bail!("coefficient list too long (too many operations)");
158            }
159            if operations as f64 * input_samp_rate > constants::CLOCK_FREQUENCY {
160                anyhow::bail!(
161                    "coefficient list too long (too many operations for input sample rate)"
162                )
163            }
164
165            // See test_fir.py in maia-hdl for FIR addressing details
166            const ADDR_OFFSET: usize = $addr_offset;
167            const NUM_ADDR: usize = if $do_fold {
168                constants::MAX_COEFFICIENTS_4DSP
169            } else {
170                constants::MAX_COEFFICIENTS_2DSP
171            };
172            if operations * decimation > NUM_ADDR {
173                anyhow::bail!("coefficient list too long (does not fit in BRAM)");
174            }
175            for addr in 0..NUM_ADDR {
176                let (off, fold) = if $do_fold && addr >= NUM_ADDR / 2 {
177                    (1, NUM_ADDR / 2)
178                } else {
179                    (0, 0)
180                };
181                let k = (addr - fold) / operations;
182                let coeff = if k >= decimation {
183                    0
184                } else {
185                    let j = (addr - fold) % operations;
186                    const FOLD_MULT: usize = if $do_fold { 2 } else { 1 };
187                    let n = (FOLD_MULT * j + off) * decimation + (decimation - 1 - k);
188                    // map_or is used to "virtually" extend the coefficient list
189                    // length to a multiple of decimation.
190                    coefficients.get(n).map_or(0, |c| *c)
191                };
192                // TODO: these can be write() instead of modify() if the SVD includes reset values
193                let waddr = u16::try_from(addr + ADDR_OFFSET).unwrap();
194                self.registers
195                    .ddc_coeff_addr()
196                    .modify(|_, w| unsafe { w.coeff_waddr().bits(waddr) });
197                self.registers.ddc_coeff().modify(|_, w| unsafe {
198                    w.coeff_wren().bit(true).coeff_wdata().bits(coeff as u32)
199                });
200            }
201
202            let dec = u8::try_from(decimation).unwrap();
203            self.registers
204                .ddc_decimation()
205                .modify(|_, w| unsafe { w.$decimation_reg().bits(dec) });
206            let opm1 = u8::try_from(operations - 1).unwrap();
207            self.registers.ddc_control().modify(|_, w| unsafe {
208                let w = w.$op_reg().bits(opm1);
209                if $do_fold {
210                    w.$odd_reg().bit(odd_operations)
211                } else {
212                    w
213                }
214            });
215
216            Ok(())
217        }
218    };
219}
220
221impl IpCore {
222    /// Opens the FPGA IP core.
223    ///
224    /// This function can only be run successfully once in the lifetime of the
225    /// process. If this function has returned `Ok` previously, all subsequent
226    /// calls will return `Err`. This ensures that there is only a single
227    /// [`IpCore`] object in the program.
228    ///
229    /// On success, the `IpCore` and the corresponding [`InterruptHandler`] are
230    /// returned.
231    pub async fn take() -> Result<(IpCore, InterruptHandler)> {
232        let uio = Uio::from_name("maia-sdr")
233            .await
234            .context("failed to open maia-sdr UIO")?;
235        let mapping = uio
236            .map_mapping(0)
237            .await
238            .context("failed to map maia-sdr UIO")?;
239        let phys_addr = uio.map_addr(0).await?;
240        let spectrometer = Dma::new("maia-sdr-spectrometer")
241            .await
242            .context("failed to open maia-sdr-spectrometer DMA buffer")?;
243        let interrupt_registers = Registers(mapping.clone());
244        let mut ip_core = IpCore {
245            registers: Registers(mapping),
246            phys_addr,
247            spectrometer,
248            // These are initialized to the correct value below, after removing
249            // the SDR reset.
250            spectrometer_input: maia_json::SpectrometerInput::AD9361,
251            spectrometer_integrations: 0,
252            spectrometer_mode: maia_json::SpectrometerMode::Average,
253            ddc_config: default_ddc_config(),
254            ddc_enabled: false,
255        };
256
257        ip_core.log_open().await?;
258        ip_core.check_product_id()?;
259        ip_core.set_sdr_reset(false);
260        ip_core.spectrometer_integrations = ip_core
261            .registers
262            .spectrometer()
263            .read()
264            .num_integrations()
265            .bits()
266            .into();
267        // this also modifies the DDC enable
268        ip_core
269            .set_spectrometer_input(
270                if ip_core.registers.spectrometer().read().use_ddc_out().bit() {
271                    maia_json::SpectrometerInput::DDC
272                } else {
273                    maia_json::SpectrometerInput::AD9361
274                },
275                // fake the input sample rate so that this never fails
276                0.0,
277            )
278            .unwrap();
279        ip_core.spectrometer_mode = if ip_core.registers.spectrometer().read().peak_detect().bit() {
280            maia_json::SpectrometerMode::PeakDetect
281        } else {
282            maia_json::SpectrometerMode::Average
283        };
284        ip_core.set_ddc_config(&default_ddc_config(), 0.0).unwrap();
285        let interrupt_handler = InterruptHandler::new(uio, interrupt_registers);
286        Ok((ip_core, interrupt_handler))
287    }
288
289    fn version_struct(&self) -> Version {
290        let version = self.registers.version().read();
291        Version {
292            major: version.major().bits(),
293            minor: version.minor().bits(),
294            bugfix: version.bugfix().bits(),
295        }
296    }
297
298    /// Gives the version of the IP core as a `String`.
299    pub fn version(&self) -> String {
300        format!("{}", self.version_struct())
301    }
302
303    fn check_product_id(&self) -> Result<()> {
304        const PRODUCT_ID: &[u8; 4] = b"maia";
305        let product_id = unsafe {
306            std::slice::from_raw_parts(self.registers.0.addr() as *const u8, PRODUCT_ID.len())
307        };
308        if product_id != PRODUCT_ID {
309            anyhow::bail!("wrong product ID {:#02x?}", product_id);
310        }
311        Ok(())
312    }
313
314    fn set_sdr_reset(&self, value: bool) {
315        self.registers
316            .control()
317            .modify(|_, w| w.sdr_reset().bit(value));
318    }
319
320    async fn log_open(&self) -> Result<()> {
321        tracing::info!(
322            "opened Maia SDR IP core version {} at physical address {:#08x}",
323            self.version_struct(),
324            self.phys_addr
325        );
326        Ok(())
327    }
328
329    /// Gives the value of the last buffer register of the spectrometer.
330    ///
331    /// This register indicates the index of the last buffer to which the
332    /// spectrometer has written.
333    pub fn spectrometer_last_buffer(&self) -> usize {
334        self.registers
335            .spectrometer()
336            .read()
337            .last_buffer()
338            .bits()
339            .into()
340    }
341
342    /// Gives the signal that is used as an input to the spectrometer.
343    pub fn spectrometer_input(&self) -> maia_json::SpectrometerInput {
344        self.spectrometer_input
345    }
346
347    /// Returns the frequency offset associated with the input to the
348    /// spectrometer.
349    ///
350    /// This offset is relative to the AD9361 RX LO frequency. The offset is
351    /// zero if the input is the AD9361, or the DDC frequency if the input is
352    /// the DDC.
353    pub fn spectrometer_input_frequency_offset(&self) -> f64 {
354        match self.spectrometer_input() {
355            maia_json::SpectrometerInput::AD9361 => 0.0,
356            maia_json::SpectrometerInput::DDC => self.ddc_frequency(),
357        }
358    }
359
360    /// Returns the decimation factor associated with the input to the
361    /// spectrometer.
362    ///
363    /// This decimation factor relates the AD9361 sample rate to the sample rate
364    /// used by the input of the spectrometer. The decimation factor is 1 if the
365    /// input is the AD9361, or the DDC decimation if the input is the DDC.
366    pub fn spectrometer_input_decimation(&self) -> usize {
367        match self.spectrometer_input() {
368            maia_json::SpectrometerInput::AD9361 => 1,
369            maia_json::SpectrometerInput::DDC => self.ddc_decimation(),
370        }
371    }
372
373    /// Gives the value of the number of integrations register of the spectrometer.
374    ///
375    /// This register indicates how many FFTs are non-coherently accumulated by
376    /// the spectrometer.
377    ///
378    /// Note: [`IpCore`] caches in RAM the value of this register every time
379    /// that it is updated, so calls to this function are very fast because the
380    /// FPGA register doesn't need to be accessed.
381    pub fn spectrometer_number_integrations(&self) -> u32 {
382        self.spectrometer_integrations
383    }
384
385    /// Returns the current spectrometer mode.
386    ///
387    /// This register indicates whether the spectrometer is running in average
388    /// power mode or in peak detect mode.
389    ///
390    /// Note: [`IpCore`] caches in RAM the value of this register every time
391    /// that it is updated, so calls to this function are very fast because the
392    /// FPGA register doesn't need to be accessed.
393    pub fn spectrometer_mode(&self) -> maia_json::SpectrometerMode {
394        self.spectrometer_mode
395    }
396
397    /// Sets the spectrometer input.
398    ///
399    /// This sets the signal that is used as an input for the spectrometer. The
400    /// function can fail if the DDC output is selected but the current DDC
401    /// configuration cannot run with the current input sample rate, as given in
402    /// the `input_samp_rate` argument.
403    pub fn set_spectrometer_input(
404        &mut self,
405        input: maia_json::SpectrometerInput,
406        input_samp_freq: f64,
407    ) -> Result<()> {
408        let use_ddc = matches!(input, maia_json::SpectrometerInput::DDC);
409        if use_ddc {
410            let max_samp_freq = self
411                .ddc_config_summary(input_samp_freq)
412                .max_input_sampling_frequency;
413            if input_samp_freq > max_samp_freq {
414                anyhow::bail!(
415                    "cannot set spectrometer input to DDC: \
416                     current DDC input sampling frequency {input_samp_freq} is greater than \
417                     maximum DDC sample rate {max_samp_freq}"
418                );
419            }
420        }
421        self.registers
422            .spectrometer()
423            .modify(|_, w| w.use_ddc_out().bit(use_ddc));
424        self.set_ddc_enable(use_ddc);
425        self.spectrometer_input = input;
426        Ok(())
427    }
428
429    /// Sets the value of the number of integrations register of the spectrometer.
430    ///
431    /// See [`IpCore::spectrometer_number_integrations`].
432    pub fn set_spectrometer_number_integrations(&mut self, value: u32) -> Result<()> {
433        const WIDTH: u8 = maia_pac::maia_sdr::spectrometer::NumIntegrationsW::<
434            maia_pac::maia_sdr::spectrometer::SpectrometerSpec,
435        >::WIDTH;
436        if !(1..1 << WIDTH).contains(&value) {
437            anyhow::bail!("invalid number of integrations: {}", value);
438        }
439        unsafe {
440            self.registers.spectrometer().modify(|r, w| {
441                // if reducing the number of integrations, use the abort bit
442                // to force the current integration to stop
443                let abort = u32::from(r.num_integrations().bits()) > value;
444                w.num_integrations().bits(value as _).abort().bit(abort)
445            })
446        };
447        self.spectrometer_integrations = value;
448        Ok(())
449    }
450
451    /// Sets the spectrometer mode.
452    ///
453    /// See [`IpCore::spectrometer_mode`].
454    pub fn set_spectrometer_mode(&mut self, mode: maia_json::SpectrometerMode) {
455        let peak_detect = match mode {
456            maia_json::SpectrometerMode::Average => false,
457            maia_json::SpectrometerMode::PeakDetect => true,
458        };
459        self.registers
460            .spectrometer()
461            .modify(|_, w| w.peak_detect().bit(peak_detect));
462        self.spectrometer_mode = mode;
463    }
464
465    /// Returns the new buffers that have been written by the spectrometer.
466    ///
467    /// This function returns an iterator that iterates over the buffers to
468    /// which the spectrometter has written since the last call to
469    /// `get_spectrometer_buffers`.
470    pub fn get_spectrometer_buffers(&mut self) -> impl Iterator<Item = &[u64]> {
471        self.spectrometer
472            .get_new_buffers(self.spectrometer_last_buffer())
473            .map(|buff| unsafe {
474                std::slice::from_raw_parts(
475                    buff.as_ptr() as *const u64,
476                    buff.len() / std::mem::size_of::<u64>(),
477                )
478            })
479    }
480
481    fn set_ddc_enable(&mut self, enable: bool) {
482        self.registers
483            .ddc_control()
484            .modify(|_, w| w.enable_input().bit(enable));
485        self.ddc_enabled = enable;
486    }
487
488    /// Gives the current configuration of the DDC.
489    ///
490    /// The `input_sampling_frequency` parameter indicates the sampling
491    /// frequency of the source connected to the DDC input (typically the
492    /// AD9361).
493    pub fn ddc_config(&self, input_sampling_frequency: f64) -> maia_json::DDCConfig {
494        let summary = self.ddc_config_summary(input_sampling_frequency);
495        maia_json::DDCConfig {
496            enabled: summary.enabled,
497            frequency: summary.frequency,
498            decimation: summary.decimation,
499            input_sampling_frequency: summary.input_sampling_frequency,
500            output_sampling_frequency: summary.output_sampling_frequency,
501            max_input_sampling_frequency: summary.max_input_sampling_frequency,
502            fir1: self.ddc_config.fir1.clone(),
503            fir2: self.ddc_config.fir2.clone(),
504            fir3: self.ddc_config.fir3.clone(),
505        }
506    }
507
508    /// Gives a summary of the current configuration of the DDC.
509    ///
510    /// The summary does not include the FIR filter taps.
511    ///
512    /// The `input_sampling_frequency` parameter indicates the sampling
513    /// frequency of the source connected to the DDC input (typically the
514    /// AD9361).
515    pub fn ddc_config_summary(&self, input_sampling_frequency: f64) -> maia_json::DDCConfigSummary {
516        use crate::ddc::constants;
517
518        let n = self.ddc_config.fir1.coefficients.len();
519        let d = usize::try_from(self.ddc_config.fir1.decimation).unwrap();
520        let operations = n.div_ceil(d).div_ceil(2);
521        let mut max_input_sampling_frequency = constants::CLOCK_FREQUENCY / operations as f64;
522        let mut decimation = d;
523
524        if let Some(fir) = &self.ddc_config.fir2 {
525            let n = fir.coefficients.len();
526            let d = usize::try_from(fir.decimation).unwrap();
527            let operations = n.div_ceil(d);
528            max_input_sampling_frequency = max_input_sampling_frequency
529                .min(constants::CLOCK_FREQUENCY * decimation as f64 / operations as f64);
530            decimation *= d;
531        }
532
533        if let Some(fir) = &self.ddc_config.fir3 {
534            let n = fir.coefficients.len();
535            let d = usize::try_from(fir.decimation).unwrap();
536            let operations = n.div_ceil(d).div_ceil(2);
537            max_input_sampling_frequency = max_input_sampling_frequency
538                .min(constants::CLOCK_FREQUENCY * decimation as f64 / operations as f64);
539            decimation *= d;
540        }
541        maia_json::DDCConfigSummary {
542            enabled: self.ddc_enabled,
543            frequency: self.ddc_frequency(),
544            decimation: u32::try_from(decimation).unwrap(),
545            input_sampling_frequency,
546            output_sampling_frequency: input_sampling_frequency / decimation as f64,
547            max_input_sampling_frequency,
548        }
549    }
550
551    /// Sets the configuration of the DDC.
552    ///
553    /// Setting the DDC configuration can fail if the parameters are out of
554    /// range for the capabilities of the DDC (for instance, if too many FIR
555    /// coefficients have been specified). If setting the configuration fails
556    /// mid-way, this function tries to revert to the previous configuration in
557    /// order to leave the DDC with a consistent configuration.
558    ///
559    /// This `input_samp_rate` parameter indicates the sample rate at the input
560    /// of the DDC in samples per second. It is used to check if the FPGA DSPs
561    /// can do enough multiplications per sample as required by the FIR filters.
562    pub fn set_ddc_config(
563        &mut self,
564        config: &maia_json::PutDDCConfig,
565        input_samp_rate: f64,
566    ) -> Result<()> {
567        if let Err(e) = self.try_set_ddc_config(config, input_samp_rate) {
568            // revert DDC config; this should not fail, since the
569            // configuration was previously set successfully
570            if let Err(err) = self.try_set_ddc_config(&self.ddc_config, input_samp_rate) {
571                tracing::error!("error reverting DDC configuration: {err}");
572            }
573            Err(e)
574        } else {
575            // save DDC config
576            self.ddc_config.clone_from(config);
577            Ok(())
578        }
579    }
580
581    fn try_set_ddc_config(
582        &self,
583        config: &maia_json::PutDDCConfig,
584        input_samp_rate: f64,
585    ) -> Result<()> {
586        let mut input_samp_rate = input_samp_rate;
587        self.try_set_ddc_frequency(config.frequency, input_samp_rate)
588            .context("failed to configure DDC frequency")?;
589        self.set_ddc_fir1(
590            &config.fir1.coefficients,
591            usize::try_from(config.fir1.decimation).unwrap(),
592            input_samp_rate,
593        )
594        .context("failed to configure fir1")?;
595        input_samp_rate /= config.fir1.decimation as f64;
596        if let Some(config) = &config.fir2 {
597            self.set_ddc_fir2(
598                &config.coefficients,
599                usize::try_from(config.decimation).unwrap(),
600                input_samp_rate,
601            )
602            .context("failed to configure fir2")?;
603            input_samp_rate /= config.decimation as f64;
604        }
605        if let Some(config) = &config.fir3 {
606            self.set_ddc_fir3(
607                &config.coefficients,
608                usize::try_from(config.decimation).unwrap(),
609                input_samp_rate,
610            )
611            .context("failed to configure fir3")?;
612        }
613        self.registers.ddc_control().modify(|_, w| {
614            w.bypass2()
615                .bit(config.fir2.is_none())
616                .bypass3()
617                .bit(config.fir3.is_none())
618        });
619        Ok(())
620    }
621
622    /// Gets the mixer frequency of the DDC.
623    ///
624    /// The frequency is given in units of Hz.
625    pub fn ddc_frequency(&self) -> f64 {
626        self.ddc_config.frequency
627    }
628
629    /// Sets the mixer frequency of the DDC.
630    ///
631    /// The `frequency` is given in units of Hz.
632    pub fn set_ddc_frequency(&mut self, frequency: f64, input_samp_rate: f64) -> Result<()> {
633        self.try_set_ddc_frequency(frequency, input_samp_rate)?;
634        // update configuration cache if we succeeded
635        self.ddc_config.frequency = frequency;
636        Ok(())
637    }
638
639    fn try_set_ddc_frequency(&self, frequency: f64, input_samp_rate: f64) -> Result<()> {
640        if !(-0.5 * input_samp_rate..=0.5 * input_samp_rate).contains(&frequency) {
641            anyhow::bail!(
642                "frequency {frequency} is out of range with input sample rate {input_samp_rate}"
643            );
644        }
645        let cycles_per_sample = frequency / input_samp_rate;
646        const NCO_WIDTH: usize = 28;
647        let scale = (1 << NCO_WIDTH) as f64;
648        let nco_freq = (cycles_per_sample * scale).round() as i32;
649        // TODO: this could be write instead of modify if the register
650        // had declared its reset value
651        self.registers
652            .ddc_frequency()
653            .modify(|_, w| unsafe { w.frequency().bits(nco_freq as u32) });
654        Ok(())
655    }
656
657    impl_set_ddc_fir!(
658        set_ddc_fir1,
659        0,
660        true,
661        decimation1,
662        operations_minus_one1,
663        odd_operations1
664    );
665    // we need an odd_operations field for fir2, even though it doesn't have its
666    // own and it isn't touched
667    impl_set_ddc_fir!(
668        set_ddc_fir2,
669        256,
670        false,
671        decimation2,
672        operations_minus_one2,
673        odd_operations1
674    );
675    impl_set_ddc_fir!(
676        set_ddc_fir3,
677        512,
678        true,
679        decimation3,
680        operations_minus_one3,
681        odd_operations3
682    );
683
684    /// Gives the decimation factor set in the DDC.
685    pub fn ddc_decimation(&self) -> usize {
686        let mut decimation = usize::try_from(self.ddc_config.fir1.decimation).unwrap();
687        if let Some(config) = &self.ddc_config.fir2 {
688            decimation *= usize::try_from(config.decimation).unwrap();
689        }
690        if let Some(config) = &self.ddc_config.fir3 {
691            decimation *= usize::try_from(config.decimation).unwrap();
692        }
693        decimation
694    }
695
696    /// Returns the frequency offset associated with the input to the
697    /// recorder.
698    ///
699    /// This offset is relative to the AD9361 RX LO frequency. The offset is
700    /// zero if the input is the AD9361, or the DDC frequency if the input is
701    /// the DDC.
702    pub fn recorder_input_frequency_offset(&self) -> f64 {
703        // currently the recorder shares the same input as the spectrometer
704        self.spectrometer_input_frequency_offset()
705    }
706
707    /// Returns the decimation factor associated with the input to the
708    /// recorder.
709    ///
710    /// This decimation factor relates the AD9361 sample rate to the sample rate
711    /// used by the input of the recorder. The decimation factor is 1 if the
712    /// input is the AD9361, or the DDC decimation if the input is the DDC.
713    pub fn recorder_input_decimation(&self) -> usize {
714        // currently the recorder shares the same input as the spectrometer
715        self.spectrometer_input_decimation()
716    }
717
718    /// Gives the value of the recorder mode register of the recorder.
719    ///
720    /// This register is used to select 8-bit mode or 12-bit mode.
721    pub fn recorder_mode(&self) -> Result<maia_json::RecorderMode> {
722        Ok(
723            match self.registers.recorder_control().read().mode().bits() {
724                0 => maia_json::RecorderMode::IQ16bit,
725                1 => maia_json::RecorderMode::IQ12bit,
726                2 => maia_json::RecorderMode::IQ8bit,
727                _ => anyhow::bail!("invalid recorder mode value"),
728            },
729        )
730    }
731
732    /// Sets the value of the recorder mode register of the recorder.
733    ///
734    /// See [`IpCore::recorder_mode`].
735    pub fn set_recorder_mode(&self, mode: maia_json::RecorderMode) {
736        let mode = match mode {
737            maia_json::RecorderMode::IQ16bit => 0,
738            maia_json::RecorderMode::IQ12bit => 1,
739            maia_json::RecorderMode::IQ8bit => 2,
740        };
741        self.registers
742            .recorder_control()
743            .modify(|_, w| unsafe { w.mode().bits(mode) });
744    }
745
746    /// Starts a recording.
747    ///
748    /// The recording will end when the recording DMA buffer is exhausted or
749    /// when [`IpCore::recorder_stop`] is called.
750    pub fn recorder_start(&self) {
751        tracing::info!("starting recorder");
752        self.registers
753            .recorder_control()
754            .modify(|_, w| w.start().set_bit());
755    }
756
757    /// Stops a recording.
758    ///
759    /// This stops a currently running recording.
760    pub fn recorder_stop(&self) {
761        tracing::info!("stopping recorder");
762        self.registers
763            .recorder_control()
764            .modify(|_, w| w.stop().set_bit());
765    }
766
767    /// Gives the value of the next address register of the recorder.
768    ///
769    /// This register indicates the next physical address to which the recorder
770    /// would have written if it had not stopped. It can be used to calculate
771    /// the size of the recording.
772    pub fn recorder_next_address(&self) -> usize {
773        usize::try_from(self.registers.recorder_next_address().read().bits()).unwrap()
774    }
775}
776
777macro_rules! impl_interrupt_handler {
778    ($($interrupt:ident),*) => {
779        paste::paste! {
780            fn new(uio: Uio, registers: Registers) -> InterruptHandler {
781                InterruptHandler {
782                    uio,
783                    registers,
784                    $(
785                        [<notify_ $interrupt>]: Arc::new(Notify::new()),
786                    )*
787                }
788            }
789
790            async fn wait_and_notify(&mut self) -> Result<()> {
791                self.uio.irq_enable().await?;
792                self.uio.irq_wait().await?;
793                let interrupts = self.registers.interrupts().read();
794                $(
795                    if interrupts.$interrupt().bit() {
796                        self.[<notify_ $interrupt>].notify_one();
797                    }
798                )*;
799                Ok(())
800            }
801
802            $(
803                #[doc = concat!("Returns a waiter for the ", stringify!($interrupt), " interrupt.")]
804                pub fn [<waiter_ $interrupt>](&self) -> InterruptWaiter {
805                    InterruptWaiter {
806                        notify: Arc::clone(&self.[<notify_ $interrupt>]),
807                    }
808                }
809            )*
810        }
811    }
812}
813
814impl InterruptHandler {
815    /// Runs the interrupt handler.
816    ///
817    /// This function only returns if there is an error.
818    ///
819    /// The function must be run concurrently with the rest of the application
820    /// so that interrupts can be received and notifications can be sent to the
821    /// waiters.
822    pub async fn run(mut self) -> Result<()> {
823        loop {
824            self.wait_and_notify().await?;
825        }
826    }
827
828    impl_interrupt_handler!(spectrometer, recorder);
829}
830
831impl InterruptWaiter {
832    /// Waits for an interrupt.
833    ///
834    /// Awaiting on the future returned by this function will only return when
835    /// the interrupt is received.
836    pub fn wait(&self) -> impl std::future::Future<Output = ()> + '_ {
837        self.notify.notified()
838    }
839}
840
841impl Dma {
842    async fn new(name: &str) -> Result<Dma> {
843        let buffer = RxBuffer::new(name)
844            .await
845            .context("failed to open rxbuffer DMA buffer")?;
846        let num_buffers = buffer.num_buffers();
847        if !num_buffers.is_power_of_two() {
848            anyhow::bail!("num_buffers is not a power of 2");
849        }
850        Ok(Dma {
851            buffer,
852            last_written: None,
853            num_buffers_mask: num_buffers - 1,
854        })
855    }
856
857    fn get_new_buffers(&mut self, last_written: usize) -> impl Iterator<Item = &[u8]> {
858        let start = match self.last_written {
859            Some(n) => n + 1,
860            None => last_written + 1, // this yields an empty iterator
861        };
862        self.last_written = Some(last_written);
863        let end = (last_written + 1) & self.num_buffers_mask;
864
865        (start..)
866            .map(|n| n & self.num_buffers_mask)
867            .take_while(move |&n| n != end)
868            .map(|n| {
869                self.buffer.cache_invalidate(n).unwrap();
870                self.buffer.buffer_as_slice(n)
871            })
872    }
873}