diff --git a/artiq/coredevice/ad9910.py b/artiq/coredevice/ad9910.py index 4b958b97e..285ec8816 100644 --- a/artiq/coredevice/ad9910.py +++ b/artiq/coredevice/ad9910.py @@ -30,11 +30,21 @@ _AD9910_REG_RAM = 0x16 class AD9910: """ - Support for the AD9910 DDS on Urukul + AD9910 DDS channel on Urukul. - :param chip_select: Chip select configuration. + This class supports a single DDS channel and exposes the DDS, + the digital step attenuator, and the RF switch. + + :param chip_select: Chip select configuration. On Urukul this is an + encoded chip select and not "one-hot". :param cpld_device: Name of the Urukul CPLD this device is on. - :param sw_device: Name of the RF switch device. + :param sw_device: Name of the RF switch device. The RF switch is a + TTLOut channel available as the :attr:`sw` attribute of this instance. + :param pll_n: DDS PLL multiplier. The DDS sample clock is + f_ref/4*pll_n where f_ref is the reference frequency (set in the parent + Urukul CPLD instance). + :param pll_cp: DDS PLL charge pump setting. + :param pll_vco: DDS PLL VCO range selection. """ kernel_invariants = {"chip_select", "cpld", "core", "bus", "sw", "ftw_per_hz", "sysclk", "pll_n", "pll_cp", "pll_vco"} @@ -50,7 +60,9 @@ class AD9910: self.sw = dmgr.get(sw_device) assert 12 <= pll_n <= 127 self.pll_n = pll_n + assert self.cpld.refclk < 60e6 self.sysclk = self.cpld.refclk*pll_n/4 # Urukul clock fanout divider + assert self.sysclk < 1e9 self.ftw_per_hz = 1./self.sysclk*(int64(1) << 32) assert 0 <= pll_vco <= 5 vco_min, vco_max = [(370, 510), (420, 590), (500, 700), @@ -62,6 +74,11 @@ class AD9910: @kernel def write32(self, addr, data): + """Write to 32 bit register. + + :param addr: Register address + :param data: Data to be written + """ self.bus.set_xfer(self.chip_select, 8, 0) self.bus.write(addr << 24) delay_mu(-self.bus.xfer_period_mu + 8) @@ -71,6 +88,12 @@ class AD9910: @kernel def write64(self, addr, data_high, data_low): + """Write to 64 bit register. + + :param addr: Register address + :param data_high: High (MSB) 32 bits of the data + :param data_low: Low (LSB) 32 data bits + """ self.bus.set_xfer(self.chip_select, 8, 0) self.bus.write(addr << 24) t = self.bus.xfer_period_mu @@ -82,6 +105,10 @@ class AD9910: @kernel def read32(self, addr): + """Read from 32 bit register. + + :param addr: Register address + """ self.bus.set_xfer(self.chip_select, 8, 0) self.bus.write((addr | 0x80) << 24) delay_mu(-self.bus.xfer_period_mu + 8) @@ -93,13 +120,17 @@ class AD9910: @kernel def init(self): + """Initialize and configure the DDS.""" + # Set SPI mode self.write32(_AD9910_REG_CFR1, 0x00000002) - delay(100*ns) + delay(100*us) self.cpld.io_update.pulse(100*ns) + # Use the AUX DAC setting to identify and confirm presence aux_dac = self.read32(_AD9910_REG_AUX_DAC) if aux_dac & 0xff != 0x7f: raise ValueError("Urukul AD9910 AUX_DAC mismatch") delay(100*us) + # Configure PLL settings and bring up PLL self.write32(_AD9910_REG_CFR2, 0x01400020) cfr3 = (0x0807c100 | (self.pll_vco << 24) | (self.pll_cp << 19) | (self.pll_n << 1)) @@ -109,6 +140,7 @@ class AD9910: self.write32(_AD9910_REG_CFR3, cfr3) delay(100*us) self.cpld.io_update.pulse(100*ns) + # Wait for PLL lock, up to 100 ms for i in range(100): lock = urukul_sta_pll_lock(self.cpld.sta_read()) delay(1*ms) @@ -118,6 +150,15 @@ class AD9910: @kernel def set_mu(self, ftw, pow=0, asf=0x3fff): + """Set profile 0 data in machine units. + + After the SPI transfer, the shared IO update pin is pulsed to + activate the data. + + :param ftw: Frequency tuning word: 32 bit unsigned. + :param pow: Phase tuning word: 16 bit unsigned. + :param asf: Amplitude scale factor: 14 bit unsigned. + """ self.write64(_AD9910_REG_PR0, (asf << 16) | pow, ftw) self.cpld.io_update.pulse(10*ns) @@ -141,14 +182,30 @@ class AD9910: @kernel def set(self, frequency, phase=0.0, amplitude=1.0): + """Set profile 0 data in SI units. + + .. seealso:: :meth:`set_mu` + + :param ftw: Frequency in Hz + :param pow: Phase tuning word in turns + :param asf: Amplitude in units of full scale + """ self.set_mu(self.frequency_to_ftw(frequency), self.turns_to_pow(phase), self.amplitude_to_asf(amplitude)) @kernel def set_att_mu(self, att): + """Set digital step attenuator in machine units. + + :param att: Attenuation setting, 8 bit digital. + """ self.cpld.set_att_mu(self.chip_select - 4, att) @kernel def set_att(self, att): + """Set digital step attenuator in SI units. + + :param att: Attenuation in dB. + """ self.cpld.set_att(self.chip_select - 4, att)