Last active
August 25, 2018 15:37
-
-
Save mfe5003/f720cdad876d5b3db98c2e7920c586f7 to your computer and use it in GitHub Desktop.
Changes to ad9910.py API for RAM mode
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from numpy import int32, int64 | |
from artiq.language.core import kernel, delay, portable | |
from artiq.language.units import us, ns, ms | |
from artiq.coredevice import spi2 as spi | |
from artiq.coredevice import urukul | |
urukul_sta_pll_lock = urukul.urukul_sta_pll_lock | |
_AD9910_REG_CFR1 = 0x00 | |
_AD9910_REG_CFR2 = 0x01 | |
_AD9910_REG_CFR3 = 0x02 | |
_AD9910_REG_AUX_DAC = 0x03 | |
_AD9910_REG_IO_UPD = 0x04 | |
_AD9910_REG_FTW = 0x07 | |
_AD9910_REG_POW = 0x08 | |
_AD9910_REG_ASF = 0x09 | |
_AD9910_REG_MSYNC = 0x0A | |
_AD9910_REG_DRAMPL = 0x0B # ramp limits | |
_AD9910_REG_DRAMPS = 0x0C # ramp step size | |
_AD9910_REG_DRAMPR = 0x0D # ramp rate | |
_AD9910_REG_PR0 = 0x0E | |
_AD9910_REG_PR1 = 0x0F | |
_AD9910_REG_PR2 = 0x10 | |
_AD9910_REG_PR3 = 0x11 | |
_AD9910_REG_PR4 = 0x12 | |
_AD9910_REG_PR5 = 0x13 | |
_AD9910_REG_PR6 = 0x14 | |
_AD9910_REG_PR7 = 0x15 | |
_AD9910_REG_RAM = 0x16 | |
# CFR1 configuration register 1 bit offsets | |
CFR1_RAM_EN = 31 | |
CFR1_RAM_DEST = 29 | |
# CFR2 configuration register 2 bit offsets | |
CFR2_DR_NODWELL = 17 | |
CFR2_DR_EN = 19 | |
CFR2_DR_DEST = 20 | |
# RAM profile bit offsets | |
RAM_PROFILE_ADDRSR = 40 | |
RAM_PROFILE_ADDR_END = 30 | |
RAM_PROFILE_ADDR_START = 14 | |
RAM_PROFILE_NDWELL = 5 | |
RAM_PROFILE_ZEROX = 3 | |
RAM_PROFILE_MODE = 0 | |
# RAM DESTINATIONS | |
RAM_FRQ = 0 | |
RAM_PHA = 1 | |
RAM_AMP = 2 | |
RAM_POL = 3 | |
# RAM MODES | |
RAM_MODE_DIRECTSW = 0 | |
RAM_MODE_RUP = 1 | |
RAM_MODE_BIR = 2 | |
RAM_MODE_CBIR = 3 | |
RAM_MODE_CONT = 4 | |
class AD9910: | |
""" | |
AD9910 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/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", | |
"ftw_per_hz", "pll_n", "pll_cp", "pll_vco"} | |
def __init__(self, dmgr, chip_select, cpld_device, sw_device=None, | |
pll_n=40, pll_cp=7, pll_vco=5): | |
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") | |
assert 12 <= pll_n <= 127 | |
self.pll_n = pll_n | |
assert self.cpld.refclk/4 <= 60e6 | |
sysclk = self.cpld.refclk*pll_n/4 # Urukul clock fanout divider | |
assert sysclk <= 1e9 | |
self.ftw_per_hz = 1./sysclk*(int64(1) << 32) | |
assert 0 <= pll_vco <= 5 | |
vco_min, vco_max = [(370, 510), (420, 590), (500, 700), | |
(600, 880), (700, 950), (820, 1150)][pll_vco] | |
assert vco_min <= sysclk/1e6 <= vco_max | |
self.pll_vco = pll_vco | |
assert 0 <= pll_cp <= 7 | |
self.pll_cp = pll_cp | |
self.cfr1 = 0x00000000 | |
self.cfr2 = 0x00000000 | |
@kernel | |
def write32(self, addr, data): | |
"""Write to 32 bit register. | |
:param addr: Register address | |
:param data: Data to be written | |
""" | |
self.bus.set_config_mu(urukul.SPI_CONFIG, 8, | |
urukul.SPIT_DDS_WR, self.chip_select) | |
self.bus.write(addr << 24) | |
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32, | |
urukul.SPIT_DDS_WR, self.chip_select) | |
self.bus.write(data) | |
@kernel | |
def read32(self, addr): | |
"""Read from 32 bit register. | |
:param addr: Register address | |
""" | |
self.bus.set_config_mu(urukul.SPI_CONFIG, 8, | |
urukul.SPIT_DDS_WR, self.chip_select) | |
self.bus.write((addr | 0x80) << 24) | |
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, | |
32, urukul.SPIT_DDS_RD, self.chip_select) | |
self.bus.write(0) | |
return self.bus.read() | |
@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_config_mu(urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, | |
self.chip_select) | |
self.bus.write(addr << 24) | |
self.bus.set_config_mu(urukul.SPI_CONFIG, 32, urukul.SPIT_DDS_WR, | |
self.chip_select) | |
self.bus.write(data_high) | |
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32, | |
urukul.SPIT_DDS_WR, self.chip_select) | |
self.bus.write(data_low) | |
@kernel | |
def init(self): | |
"""Initialize and configure the DDS. | |
Sets up SPI mode, confirms chip presence, powers down unused blocks, | |
configures the PLL, waits for PLL lock. Uses the | |
IO_UPDATE signal multiple times. | |
""" | |
# Set SPI mode | |
self.cfr1 = 0x00000002 | |
self.write32(_AD9910_REG_CFR1, self.cfr1) | |
self.cpld.io_update.pulse(2*us) | |
# 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(50*us) # slack | |
# Configure PLL settings and bring up PLL | |
self.cfr2 = 0x01400020 | |
self.write32(_AD9910_REG_CFR2, self.cfr2) | |
self.cpld.io_update.pulse(2*us) | |
cfr3 = (0x0807c100 | (self.pll_vco << 24) | | |
(self.pll_cp << 19) | (self.pll_n << 1)) | |
self.write32(_AD9910_REG_CFR3, cfr3 | 0x400) # PFD reset | |
self.cpld.io_update.pulse(100*us) | |
self.write32(_AD9910_REG_CFR3, cfr3) | |
self.cpld.io_update.pulse(100*us) | |
# Wait for PLL lock, up to 100 ms | |
for i in range(100): | |
sta = self.cpld.sta_read() | |
lock = urukul_sta_pll_lock(sta) | |
delay(1*ms) | |
if lock & (1 << self.chip_select - 4): | |
return | |
raise ValueError("PLL lock timeout") | |
@kernel | |
def set_mu(self, ftw, pow=0, asf=0x3fff, profile=0): | |
"""Set profile 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. | |
:param pow: Phase tuning word: 16 bit unsigned. | |
:param asf: Amplitude scale factor: 14 bit unsigned. | |
:param profile: Profile register: 0-7. | |
""" | |
if profile > 7: | |
raise ValueError("Profile out of range 0<profile<7.") | |
self.write64(_AD9910_REG_PR0 + profile, (asf << 16) | pow, 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 int32(round(self.ftw_per_hz*frequency)) | |
@portable(flags={"fast-math"}) | |
def turns_to_pow(self, turns): | |
"""Returns the phase offset word corresponding to the given phase | |
in turns.""" | |
return int32(round(turns*0x10000)) | |
@portable(flags={"fast-math"}) | |
def amplitude_to_asf(self, amplitude): | |
"""Returns amplitude scale factor corresponding to given amplitude.""" | |
return int32(round(amplitude*0x3ffe)) | |
@kernel | |
def set(self, frequency, phase=0.0, amplitude=1.0, profile=0): | |
"""Set profile 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), | |
profile=profile) | |
@kernel | |
def set_ftw(self, frequency): | |
return self.write32(_AD9910_REG_FTW, self.frequency_to_ftw(frequency)) | |
@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. | |
""" | |
self.cpld.set_att(self.chip_select - 4, att) | |
@kernel | |
def set_ram_mode(self, ram_en, ram_dest): | |
"""Turn on the digital ramp mode | |
:param ram_en: RAM mode enable: 0 -> off, 1 -> on. | |
:param ram_dest: RAM destination: RAM_FRQ, RAM_PHA, RAM_AMP, RAM_POL | |
""" | |
self.cfr1 = ((self.cfr1 & ~(7 << CFR1_RAM_DEST) | | |
(ram_en & 1) << CFR1_RAM_EN) | | |
(ram_dest & 3) << CFR1_RAM_DEST) | |
self.write32(_AD9910_REG_CFR1, self.cfr1) | |
@kernel | |
def set_ram_profile_mu(self, wvfm_start, wvfm_end, addr_step_rate, | |
profile=0, nodwell_h=0, zero_x=0, mode=1): | |
"""Set the RAM profile settings. | |
:param wvfm_start: 10b | |
:param wvfm_end: 10b | |
:param addr_step_rate: 16b | |
:param profile: 0 <= profile < 8 | |
:param nodwell_h: 1b | |
:param zero_x: 1b | |
:param mode: 3b | |
returns the number of RAM words to write | |
""" | |
if profile > 7: | |
raise ValueError("Profile out of range 0<profile<7.") | |
words = wvfm_end - wvfm_start + 1 | |
if words <= 0: | |
raise ValueError("Waveform start must proceed end address.") | |
w64 = ( | |
(addr_step_rate << RAM_PROFILE_ADDRSR) | | |
(wvfm_end & 0x3FF) << RAM_PROFILE_ADDR_END | | |
(wvfm_start & 0x3FF) << RAM_PROFILE_ADDR_START | | |
(nodwell_h & 1) << RAM_PROFILE_NDWELL | | |
(zero_x & 1) << RAM_PROFILE_ZEROX | | |
(mode & 7) << RAM_PROFILE_MODE | |
) | |
self.write64(_AD9910_REG_PR0 + profile, w64 >> 32, w64 & 0xFFFFFFFF) | |
return words | |
@kernel | |
def write_ram_mu(self, buf, words): | |
"""Writes `words` number of RAM words to waveform memory. | |
:param buf: list of length >= words | |
:param words: number of words to write to RAM | |
""" | |
self.bus.set_config_mu(urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, | |
self.chip_select) | |
self.bus.write(_AD9910_REG_RAM << 24) | |
# send the middle words | |
for i in range(words-1): | |
self.bus.set_config_mu(urukul.SPI_CONFIG, 32, | |
urukul.SPIT_DDS_WR, self.chip_select) | |
self.bus.write(buf[i]) | |
# send the last word with spi end flag | |
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32, | |
urukul.SPIT_DDS_WR, self.chip_select) | |
self.bus.write(buf[words-1]) | |
@kernel | |
def read_ram_mu(self, words): | |
"""Reads `words` number of RAM words to waveform memory. | |
:param words: number of words to read from RAM | |
""" | |
self.bus.set_config_mu(urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, | |
self.chip_select) | |
self.bus.write((_AD9910_REG_RAM | 0x80) << 24) | |
# send the middle words | |
for i in range(words-1): | |
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_INPUT, 32, | |
urukul.SPIT_DDS_WR, self.chip_select) | |
self.bus.write(buf[i]) | |
# send the last word with spi end flag | |
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32, | |
urukul.SPIT_DDS_WR, self.chip_select) | |
self.bus.write(buf[words-1]) | |
@kernel | |
def write_ram_amps(self, buf, words): | |
"""Writes `words` number of RAM words to waveform memory. | |
:param buf: list of floats 0->1: length >= words | |
:param words: number of words to write to RAM | |
""" | |
self.bus.set_config_mu(urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, | |
self.chip_select) | |
self.bus.write(_AD9910_REG_RAM << 24) | |
# send the middle words | |
for i in range(words-1): | |
self.bus.set_config_mu(urukul.SPI_CONFIG, 32, | |
urukul.SPIT_DDS_WR, self.chip_select) | |
self.bus.write(self.amplitude_to_asf(buf[i]) << 18) | |
# delay(100*ns) | |
# send the last word with spi end flag | |
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32, | |
urukul.SPIT_DDS_WR, self.chip_select) | |
self.bus.write(self.amplitude_to_asf(buf[words-1]) << 18) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
changed for profile support:
added: