ad9914: port to NAC3

This commit is contained in:
Sebastien Bourdeauducq 2022-02-28 10:22:29 +08:00
parent 0266d52497
commit bf8e188868
1 changed files with 65 additions and 48 deletions

View File

@ -2,13 +2,18 @@
Driver for the AD9914 DDS (with parallel bus) on RTIO. Driver for the AD9914 DDS (with parallel bus) on RTIO.
""" """
from numpy import int32, int64
from artiq.language.core import * from artiq.language.core import *
from artiq.language.types import *
from artiq.language.units import * from artiq.language.units import *
from artiq.coredevice.rtio import rtio_output from artiq.coredevice.rtio import rtio_output
from artiq.coredevice.core import Core
from numpy import int32, int64
# NAC3TODO work around https://git.m-labs.hk/M-Labs/nac3/issues/189
@nac3
class ValueError(Exception):
pass
__all__ = [ __all__ = [
@ -43,6 +48,7 @@ AD9914_FUD = 0x80
AD9914_GPIO = 0x81 AD9914_GPIO = 0x81
@nac3
class AD9914: class AD9914:
"""Driver for one AD9914 DDS channel. """Driver for one AD9914 DDS channel.
@ -57,10 +63,21 @@ class AD9914:
:param channel: channel number (on the bus) of the DDS device to control. :param channel: channel number (on the bus) of the DDS device to control.
""" """
kernel_invariants = {"core", "sysclk", "bus_channel", "channel", core: KernelInvariant[Core]
"rtio_period_mu", "sysclk_per_mu", "write_duration_mu", sysclk: KernelInvariant[float]
"dac_cal_duration_mu", "init_duration_mu", "init_sync_duration_mu", bus_channel: KernelInvariant[int32]
"set_duration_mu", "set_x_duration_mu", "exit_x_duration_mu"} channel: KernelInvariant[int32]
phase_mode: Kernel[int32]
rtio_period_mu: KernelInvariant[int64]
sysclk_per_mu: KernelInvariant[int64]
write_duration_mu: KernelInvariant[int64]
dac_cal_duration_mu: KernelInvariant[int64]
init_duration_mu: KernelInvariant[int64]
init_sync_duration_mu: KernelInvariant[int64]
set_duration_mu: KernelInvariant[int64]
set_x_duration_mu: KernelInvariant[int64]
exit_x_duration_mu: KernelInvariant[int64]
def __init__(self, dmgr, sysclk, bus_channel, channel, core_device="core"): def __init__(self, dmgr, sysclk, bus_channel, channel, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
@ -70,7 +87,7 @@ class AD9914:
self.phase_mode = PHASE_MODE_CONTINUOUS self.phase_mode = PHASE_MODE_CONTINUOUS
self.rtio_period_mu = int64(8) self.rtio_period_mu = int64(8)
self.sysclk_per_mu = int32(self.sysclk * self.core.ref_period) self.sysclk_per_mu = int64(self.sysclk * self.core.ref_period)
self.write_duration_mu = 5 * self.rtio_period_mu self.write_duration_mu = 5 * self.rtio_period_mu
self.dac_cal_duration_mu = 147000 * self.rtio_period_mu self.dac_cal_duration_mu = 147000 * self.rtio_period_mu
@ -81,7 +98,7 @@ class AD9914:
self.exit_x_duration_mu = 3 * self.write_duration_mu self.exit_x_duration_mu = 3 * self.write_duration_mu
@kernel @kernel
def write(self, addr, data): def write(self, addr: int32, data: int32):
rtio_output((self.bus_channel << 8) | addr, data) rtio_output((self.bus_channel << 8) | addr, data)
delay_mu(self.write_duration_mu) delay_mu(self.write_duration_mu)
@ -113,7 +130,7 @@ class AD9914:
self.write(AD9914_FUD, 0) self.write(AD9914_FUD, 0)
@kernel @kernel
def init_sync(self, sync_delay): def init_sync(self, sync_delay: int32):
"""Resets and initializes the DDS channel as well as configures """Resets and initializes the DDS channel as well as configures
the AD9914 DDS for synchronisation. The synchronisation procedure the AD9914 DDS for synchronisation. The synchronisation procedure
follows the steps outlined in the AN-1254 application note. follows the steps outlined in the AN-1254 application note.
@ -157,7 +174,7 @@ class AD9914:
self.write(AD9914_FUD, 0) self.write(AD9914_FUD, 0)
@kernel @kernel
def set_phase_mode(self, phase_mode): def set_phase_mode(self, phase_mode: int32):
"""Sets the phase mode of the DDS channel. Supported phase modes are: """Sets the phase mode of the DDS channel. Supported phase modes are:
* :const:`PHASE_MODE_CONTINUOUS`: the phase accumulator is unchanged when * :const:`PHASE_MODE_CONTINUOUS`: the phase accumulator is unchanged when
@ -181,8 +198,8 @@ class AD9914:
self.phase_mode = phase_mode self.phase_mode = phase_mode
@kernel @kernel
def set_mu(self, ftw, pow=0, phase_mode=_PHASE_MODE_DEFAULT, def set_mu(self, ftw: int32, pow: int32 = 0, phase_mode: int32 = _PHASE_MODE_DEFAULT,
asf=0x0fff, ref_time_mu=-1): asf: int32 = 0x0fff, ref_time_mu: int64 = int64(-1)) -> int32:
"""Sets the DDS channel to the specified frequency and phase. """Sets the DDS channel to the specified frequency and phase.
This uses machine units (FTW and POW). The frequency tuning word width This uses machine units (FTW and POW). The frequency tuning word width
@ -205,7 +222,7 @@ class AD9914:
""" """
if phase_mode == _PHASE_MODE_DEFAULT: if phase_mode == _PHASE_MODE_DEFAULT:
phase_mode = self.phase_mode phase_mode = self.phase_mode
if ref_time_mu < 0: if ref_time_mu < int64(0):
ref_time_mu = now_mu() ref_time_mu = now_mu()
delay_mu(-self.set_duration_mu) delay_mu(-self.set_duration_mu)
@ -224,60 +241,60 @@ class AD9914:
# Clear phase accumulator on FUD # Clear phase accumulator on FUD
# Enable autoclear phase accumulator and enables OSK. # Enable autoclear phase accumulator and enables OSK.
self.write(AD9914_REG_CFR1L, 0x2108) self.write(AD9914_REG_CFR1L, 0x2108)
fud_time = now_mu() + 2 * self.write_duration_mu fud_time = now_mu() + int64(2) * self.write_duration_mu
pow -= int32((ref_time_mu - fud_time) * self.sysclk_per_mu * ftw >> (32 - 16)) pow -= int32((ref_time_mu - fud_time) * self.sysclk_per_mu * int64(ftw) >> int64(32 - 16))
if phase_mode == PHASE_MODE_TRACKING: if phase_mode == PHASE_MODE_TRACKING:
pow += int32(ref_time_mu * self.sysclk_per_mu * ftw >> (32 - 16)) pow += int32(ref_time_mu * self.sysclk_per_mu * int64(ftw) >> int64(32 - 16))
self.write(AD9914_REG_POW, pow) self.write(AD9914_REG_POW, pow)
self.write(AD9914_REG_ASF, asf) self.write(AD9914_REG_ASF, asf)
self.write(AD9914_FUD, 0) self.write(AD9914_FUD, 0)
return pow return pow
@portable(flags={"fast-math"}) @portable
def frequency_to_ftw(self, frequency): def frequency_to_ftw(self, frequency: float) -> int32:
"""Returns the 32-bit frequency tuning word corresponding to the given """Returns the 32-bit frequency tuning word corresponding to the given
frequency. frequency.
""" """
return int32(round(float(int64(2)**32*frequency/self.sysclk))) return round(float(int64(2)**int64(32))*frequency/self.sysclk)
@portable(flags={"fast-math"}) @portable
def ftw_to_frequency(self, ftw): def ftw_to_frequency(self, ftw: int32) -> float:
"""Returns the frequency corresponding to the given frequency tuning """Returns the frequency corresponding to the given frequency tuning
word. word.
""" """
return ftw*self.sysclk/int64(2)**32 return float(ftw)*self.sysclk/float(int64(2)**int64(32))
@portable(flags={"fast-math"}) @portable
def turns_to_pow(self, turns): def turns_to_pow(self, turns: float) -> int32:
"""Returns the 16-bit phase offset word corresponding to the given """Returns the 16-bit phase offset word corresponding to the given
phase in turns.""" phase in turns."""
return round(float(turns*2**16)) & 0xffff return round(float(turns*float(2**16))) & 0xffff
@portable(flags={"fast-math"}) @portable
def pow_to_turns(self, pow): def pow_to_turns(self, pow: int32) -> float:
"""Returns the phase in turns corresponding to the given phase offset """Returns the phase in turns corresponding to the given phase offset
word.""" word."""
return pow/2**16 return float(pow)/float(2**16)
@portable(flags={"fast-math"}) @portable
def amplitude_to_asf(self, amplitude): def amplitude_to_asf(self, amplitude: float) -> int32:
"""Returns 12-bit amplitude scale factor corresponding to given """Returns 12-bit amplitude scale factor corresponding to given
amplitude.""" amplitude."""
code = round(float(amplitude * 0x0fff)) code = round(float(amplitude * float(0x0fff)))
if code < 0 or code > 0xfff: if code < 0 or code > 0xfff:
raise ValueError("Invalid AD9914 amplitude!") raise ValueError("Invalid AD9914 amplitude!")
return code return code
@portable(flags={"fast-math"}) @portable
def asf_to_amplitude(self, asf): def asf_to_amplitude(self, asf: int32) -> float:
"""Returns the amplitude corresponding to the given amplitude scale """Returns the amplitude corresponding to the given amplitude scale
factor.""" factor."""
return asf/0x0fff return asf/0x0fff
@kernel @kernel
def set(self, frequency, phase=0.0, phase_mode=_PHASE_MODE_DEFAULT, def set(self, frequency: float, phase: float = 0.0, phase_mode: int32 = _PHASE_MODE_DEFAULT,
amplitude=1.0): amplitude: float = 1.0) -> float:
"""Like :meth:`set_mu`, but uses Hz and turns.""" """Like :meth:`set_mu`, but uses Hz and turns."""
return self.pow_to_turns( return self.pow_to_turns(
self.set_mu(self.frequency_to_ftw(frequency), self.set_mu(self.frequency_to_ftw(frequency),
@ -286,7 +303,7 @@ class AD9914:
# Extended-resolution functions # Extended-resolution functions
@kernel @kernel
def set_x_mu(self, xftw, amplitude=0x0fff): def set_x_mu(self, xftw: int64, amplitude: int32 = 0x0fff):
"""Set the DDS frequency and amplitude with an extended-resolution """Set the DDS frequency and amplitude with an extended-resolution
(63-bit) frequency tuning word. (63-bit) frequency tuning word.
@ -300,10 +317,10 @@ class AD9914:
self.write(AD9914_GPIO, (1 << self.channel) << 1) self.write(AD9914_GPIO, (1 << self.channel) << 1)
self.write(AD9914_REG_DRGAL, xftw & 0xffff) self.write(AD9914_REG_DRGAL, int32(xftw) & 0xffff)
self.write(AD9914_REG_DRGAH, (xftw >> 16) & 0x7fff) self.write(AD9914_REG_DRGAH, int32(xftw >> int64(16)) & 0x7fff)
self.write(AD9914_REG_DRGFL, (xftw >> 31) & 0xffff) self.write(AD9914_REG_DRGFL, int32(xftw >> int64(31)) & 0xffff)
self.write(AD9914_REG_DRGFH, (xftw >> 47) & 0xffff) self.write(AD9914_REG_DRGFH, int32(xftw >> int64(47)) & 0xffff)
self.write(AD9914_REG_ASF, amplitude) self.write(AD9914_REG_ASF, amplitude)
self.write(AD9914_FUD, 0) self.write(AD9914_FUD, 0)
@ -316,23 +333,23 @@ class AD9914:
self.write(AD9914_REG_DRGAL, 0) self.write(AD9914_REG_DRGAL, 0)
self.write(AD9914_REG_DRGAH, 0) self.write(AD9914_REG_DRGAH, 0)
@portable(flags={"fast-math"}) @portable
def frequency_to_xftw(self, frequency): def frequency_to_xftw(self, frequency: float) -> int64:
"""Returns the 63-bit frequency tuning word corresponding to the given """Returns the 63-bit frequency tuning word corresponding to the given
frequency (extended resolution mode). frequency (extended resolution mode).
""" """
return int64(round(2.0*float(int64(2)**62)*frequency/self.sysclk)) & ( return round64(2.0*float(int64(2)**int64(62))*frequency/self.sysclk) & (
(int64(1) << 63) - 1) (int64(1) << int64(63)) - int64(1))
@portable(flags={"fast-math"}) @portable
def xftw_to_frequency(self, xftw): def xftw_to_frequency(self, xftw: int64) -> float:
"""Returns the frequency corresponding to the given frequency tuning """Returns the frequency corresponding to the given frequency tuning
word (extended resolution mode). word (extended resolution mode).
""" """
return xftw*self.sysclk/(2.0*float(int64(2)**62)) return float(xftw)*self.sysclk/(2.0*float(int64(2)**int64(62)))
@kernel @kernel
def set_x(self, frequency, amplitude=1.0): def set_x(self, frequency: float, amplitude: float = 1.0):
"""Like :meth:`set_x_mu`, but uses Hz and turns. """Like :meth:`set_x_mu`, but uses Hz and turns.
Note that the precision of ``float`` is less than the precision Note that the precision of ``float`` is less than the precision