forked from M-Labs/artiq
178 lines
6.2 KiB
Python
178 lines
6.2 KiB
Python
from numpy import int32, int64
|
|
|
|
from artiq.language.core import kernel, delay, portable
|
|
from artiq.language.units import us, ns
|
|
from artiq.coredevice.ad9912_reg import *
|
|
|
|
from artiq.coredevice import spi2 as spi
|
|
from artiq.coredevice import urukul
|
|
|
|
|
|
class AD9912:
|
|
"""
|
|
AD9912 DDS channel on Urukul
|
|
|
|
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. 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*pll_n where f_ref is the reference frequency (set in the parent
|
|
Urukul CPLD instance).
|
|
"""
|
|
kernel_invariants = {"chip_select", "cpld", "core", "bus",
|
|
"ftw_per_hz", "sysclk", "pll_n"}
|
|
|
|
def __init__(self, dmgr, chip_select, cpld_device, sw_device=None,
|
|
pll_n=10):
|
|
self.cpld = dmgr.get(cpld_device)
|
|
self.core = self.cpld.core
|
|
self.bus = self.cpld.bus
|
|
assert 4 <= chip_select <= 7
|
|
self.chip_select = chip_select
|
|
if sw_device:
|
|
self.sw = dmgr.get(sw_device)
|
|
self.kernel_invariants.add("sw")
|
|
self.pll_n = pll_n
|
|
self.sysclk = self.cpld.refclk*pll_n
|
|
assert self.sysclk <= 1e9
|
|
self.ftw_per_hz = 1/self.sysclk*(int64(1) << 48)
|
|
|
|
@kernel
|
|
def write(self, addr, data, length):
|
|
"""Variable length write to a register.
|
|
Up to 4 bytes.
|
|
|
|
:param addr: Register address
|
|
:param data: Data to be written: int32
|
|
:param length: Length in bytes (1-4)
|
|
"""
|
|
assert length > 0
|
|
assert length <= 4
|
|
self.bus.set_config_mu(urukul.SPI_CONFIG, 16,
|
|
urukul.SPIT_DDS_WR, self.chip_select)
|
|
self.bus.write((addr | ((length - 1) << 13)) << 16)
|
|
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, length*8,
|
|
urukul.SPIT_DDS_WR, self.chip_select)
|
|
self.bus.write(data << (32 - length*8))
|
|
|
|
@kernel
|
|
def read(self, addr, length):
|
|
"""Variable length read from a register.
|
|
Up to 4 bytes.
|
|
|
|
:param addr: Register address
|
|
:param length: Length in bytes (1-4)
|
|
:return: Data read
|
|
"""
|
|
assert length > 0
|
|
assert length <= 4
|
|
self.bus.set_config_mu(urukul.SPI_CONFIG, 16,
|
|
urukul.SPIT_DDS_WR, self.chip_select)
|
|
self.bus.write((addr | ((length - 1) << 13) | 0x8000) << 16)
|
|
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END
|
|
| spi.SPI_INPUT, length*8,
|
|
urukul.SPIT_DDS_RD, self.chip_select)
|
|
self.bus.write(0)
|
|
data = self.bus.read()
|
|
if length < 4:
|
|
data &= (1 << (length*8)) - 1
|
|
return data
|
|
|
|
@kernel
|
|
def init(self):
|
|
"""Initialize and configure the DDS.
|
|
|
|
Sets up SPI mode, confirms chip presence, powers down unused blocks,
|
|
and configures the PLL. Does not wait for PLL lock. Uses the
|
|
IO_UPDATE signal multiple times.
|
|
"""
|
|
# SPI mode
|
|
self.write(AD9912_SER_CONF, 0x99, length=1)
|
|
self.cpld.io_update.pulse(2*us)
|
|
# Verify chip ID and presence
|
|
prodid = self.read(AD9912_PRODIDH, length=2)
|
|
if (prodid != 0x1982) and (prodid != 0x1902):
|
|
raise ValueError("Urukul AD9912 product id mismatch")
|
|
delay(50*us)
|
|
# HSTL power down, CMOS power down
|
|
self.write(AD9912_PWRCNTRL1, 0x80, length=1)
|
|
self.cpld.io_update.pulse(2*us)
|
|
self.write(AD9912_N_DIV, self.pll_n//2 - 2, length=1)
|
|
self.cpld.io_update.pulse(2*us)
|
|
# I_cp = 375 µA, VCO high range
|
|
self.write(AD9912_PLLCFG, 0b00000101, length=1)
|
|
self.cpld.io_update.pulse(2*us)
|
|
|
|
@kernel
|
|
def set_att_mu(self, att):
|
|
"""Set digital step attenuator in machine units.
|
|
|
|
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.set_att_mu`
|
|
|
|
: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.
|
|
|
|
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.set_att`
|
|
|
|
:param att: Attenuation in dB. Higher values mean more attenuation.
|
|
"""
|
|
self.cpld.set_att(self.chip_select - 4, att)
|
|
|
|
@kernel
|
|
def set_mu(self, ftw, pow):
|
|
"""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.
|
|
"""
|
|
# streaming transfer of FTW and POW
|
|
self.bus.set_config_mu(urukul.SPI_CONFIG, 16,
|
|
urukul.SPIT_DDS_WR, self.chip_select)
|
|
self.bus.write((AD9912_POW1 << 16) | (3 << 29))
|
|
self.bus.set_config_mu(urukul.SPI_CONFIG, 32,
|
|
urukul.SPIT_DDS_WR, self.chip_select)
|
|
self.bus.write((pow << 16) | (int32(ftw >> 32) & 0xffff))
|
|
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32,
|
|
urukul.SPIT_DDS_WR, self.chip_select)
|
|
self.bus.write(int32(ftw))
|
|
self.cpld.io_update.pulse(10*ns)
|
|
|
|
@portable(flags={"fast-math"})
|
|
def frequency_to_ftw(self, frequency):
|
|
"""Returns the frequency tuning word corresponding to the given
|
|
frequency.
|
|
"""
|
|
return int64(round(self.ftw_per_hz*frequency))
|
|
|
|
@portable(flags={"fast-math"})
|
|
def turns_to_pow(self, phase):
|
|
"""Returns the phase offset word corresponding to the given
|
|
phase.
|
|
"""
|
|
return int32(round((1 << 16)*phase))
|
|
|
|
@kernel
|
|
def set(self, frequency, phase=0.0):
|
|
"""Set profile 0 data in SI units.
|
|
|
|
.. seealso:: :meth:`set_mu`
|
|
|
|
:param ftw: Frequency in Hz
|
|
:param pow: Phase tuning word in turns
|
|
"""
|
|
self.set_mu(self.frequency_to_ftw(frequency),
|
|
self.turns_to_pow(phase))
|