1use crate::rxbuffer::RxBuffer;
7use crate::uio::{Mapping, Uio};
8use anyhow::{Context, Result};
9use std::sync::Arc;
10use tokio::sync::Notify;
11
12#[derive(Debug)]
17pub struct IpCore {
18 registers: Registers,
19 phys_addr: usize,
20 spectrometer: Dma,
21 spectrometer_integrations: u32,
25 spectrometer_mode: maia_json::SpectrometerMode,
26 spectrometer_input: maia_json::SpectrometerInput,
27 ddc_config: maia_json::PutDDCConfig,
29 ddc_enabled: bool,
30}
31
32#[derive(Debug)]
37pub struct InterruptWaiter {
38 notify: Arc<Notify>,
39}
40
41#[derive(Debug)]
66pub struct InterruptHandler {
67 uio: Uio,
68 registers: Registers, 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 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 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 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 coefficients.get(n).map_or(0, |c| *c)
191 };
192 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 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 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 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 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 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 pub fn spectrometer_last_buffer(&self) -> usize {
334 self.registers
335 .spectrometer()
336 .read()
337 .last_buffer()
338 .bits()
339 .into()
340 }
341
342 pub fn spectrometer_input(&self) -> maia_json::SpectrometerInput {
344 self.spectrometer_input
345 }
346
347 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 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 pub fn spectrometer_number_integrations(&self) -> u32 {
382 self.spectrometer_integrations
383 }
384
385 pub fn spectrometer_mode(&self) -> maia_json::SpectrometerMode {
394 self.spectrometer_mode
395 }
396
397 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 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 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 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 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 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 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 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 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 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 pub fn ddc_frequency(&self) -> f64 {
626 self.ddc_config.frequency
627 }
628
629 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 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 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 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 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 pub fn recorder_input_frequency_offset(&self) -> f64 {
703 self.spectrometer_input_frequency_offset()
705 }
706
707 pub fn recorder_input_decimation(&self) -> usize {
714 self.spectrometer_input_decimation()
716 }
717
718 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 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 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 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 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 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 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, };
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}