Simplified use of the AD9910 RAM feature (#1584)

* coredevice: Change Urukul default single-tone profile to 7

This allows using the internal profile control in RAM modulation mode (which always starts to play back at profile 0) without competing for the content of the profile 0 register used in single tone mode.

Signed-off-by: Peter Drmota <peter.drmota@physics.ox.ac.uk>

* ad9910/set_mu: comment on caveats when setting register

* ad9910: avoid unnecessary write/param

Credit: Solution proposed by @pmldrmota in https://github.com/m-labs/artiq/pull/1584#issuecomment-987774353

* revert 1064fdff (`set_mu()` comments)

158a7be7 had addressed this issue.

Co-authored-by: occheung <dc@m-labs.hk>
This commit is contained in:
Peter Drmota 2021-12-13 15:44:03 +00:00 committed by GitHub
parent 33a9ca2684
commit 7c664142a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 36 additions and 15 deletions

View File

@ -84,8 +84,11 @@ Highlights:
- Improved performance for kernel RPC involving list and array. - Improved performance for kernel RPC involving list and array.
* Coredevice SI to mu conversions now always return valid codes, or raise a ``ValueError``. * Coredevice SI to mu conversions now always return valid codes, or raise a ``ValueError``.
* Zotino now exposes ``voltage_to_mu()`` * Zotino now exposes ``voltage_to_mu()``
* ``ad9910``: The maximum amplitude scale factor is now ``0x3fff`` (was ``0x3ffe`` * ``ad9910``:
before). - The maximum amplitude scale factor is now ``0x3fff`` (was ``0x3ffe`` before).
- The default single-tone profile is now 7 (was 0).
- Added option to ``set_mu()`` that affects the ASF, FTW and POW registers
instead of the single-tone profile register.
* Mirny now supports HW revision independent, human readable ``clk_sel`` parameters: * Mirny now supports HW revision independent, human readable ``clk_sel`` parameters:
"XO", "SMA", and "MMCX". Passing an integer is backwards compatible. "XO", "SMA", and "MMCX". Passing an integer is backwards compatible.
* Dashboard: * Dashboard:

View File

@ -236,7 +236,7 @@ class AD9910:
""" """
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 24, self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 24,
urukul.SPIT_DDS_WR, self.chip_select) urukul.SPIT_DDS_WR, self.chip_select)
self.bus.write((addr << 24) | (data << 8)) self.bus.write((addr << 24) | ((data & 0xffff) << 8))
@kernel @kernel
def write32(self, addr: TInt32, data: TInt32): def write32(self, addr: TInt32, data: TInt32):
@ -516,10 +516,11 @@ class AD9910:
self.cpld.io_update.pulse(1 * us) self.cpld.io_update.pulse(1 * us)
@kernel @kernel
def set_mu(self, ftw: TInt32, pow_: TInt32 = 0, asf: TInt32 = 0x3fff, def set_mu(self, ftw: TInt32 = 0, pow_: TInt32 = 0, asf: TInt32 = 0x3fff,
phase_mode: TInt32 = _PHASE_MODE_DEFAULT, phase_mode: TInt32 = _PHASE_MODE_DEFAULT,
ref_time_mu: TInt64 = int64(-1), profile: TInt32 = 0) -> TInt32: ref_time_mu: TInt64 = int64(-1), profile: TInt32 = 7,
"""Set profile 0 data in machine units. ram_destination: TInt32 = -1) -> TInt32:
"""Set DDS data in machine units.
This uses machine units (FTW, POW, ASF). The frequency tuning word This uses machine units (FTW, POW, ASF). The frequency tuning word
width is 32, the phase offset word width is 16, and the amplitude width is 32, the phase offset word width is 16, and the amplitude
@ -538,7 +539,13 @@ class AD9910:
by :meth:`set_phase_mode` for this call. by :meth:`set_phase_mode` for this call.
:param ref_time_mu: Fiducial time used to compute absolute or tracking :param ref_time_mu: Fiducial time used to compute absolute or tracking
phase updates. In machine units as obtained by `now_mu()`. phase updates. In machine units as obtained by `now_mu()`.
:param profile: Profile number to set (0-7, default: 0). :param profile: Single tone profile number to set (0-7, default: 7).
Ineffective if `ram_destination` is specified.
:param ram_destination: RAM destination (:const:`RAM_DEST_FTW`,
:const:`RAM_DEST_POW`, :const:`RAM_DEST_ASF`,
:const:`RAM_DEST_POWASF`). If specified, write free DDS parameters
to the ASF/FTW/POW registers instead of to the single tone profile
register (default behaviour, see `profile`).
:return: Resulting phase offset word after application of phase :return: Resulting phase offset word after application of phase
tracking offset. When using :const:`PHASE_MODE_CONTINUOUS` in tracking offset. When using :const:`PHASE_MODE_CONTINUOUS` in
subsequent calls, use this value as the "current" phase. subsequent calls, use this value as the "current" phase.
@ -561,8 +568,17 @@ class AD9910:
# is equivalent to an output pipeline latency. # is equivalent to an output pipeline latency.
dt = int32(now_mu()) - int32(ref_time_mu) dt = int32(now_mu()) - int32(ref_time_mu)
pow_ += dt * ftw * self.sysclk_per_mu >> 16 pow_ += dt * ftw * self.sysclk_per_mu >> 16
if ram_destination == -1:
self.write64(_AD9910_REG_PROFILE0 + profile, self.write64(_AD9910_REG_PROFILE0 + profile,
(asf << 16) | (pow_ & 0xffff), ftw) (asf << 16) | (pow_ & 0xffff), ftw)
else:
if not ram_destination == RAM_DEST_FTW:
self.set_ftw(ftw)
if not ram_destination == RAM_DEST_POWASF:
if not ram_destination == RAM_DEST_ASF:
self.set_asf(asf)
if not ram_destination == RAM_DEST_POW:
self.set_pow(pow_)
delay_mu(int64(self.sync_data.io_update_delay)) delay_mu(int64(self.sync_data.io_update_delay))
self.cpld.io_update.pulse_mu(8) # assumes 8 mu > t_SYN_CCLK self.cpld.io_update.pulse_mu(8) # assumes 8 mu > t_SYN_CCLK
at_mu(now_mu() & ~7) # clear fine TSC again at_mu(now_mu() & ~7) # clear fine TSC again
@ -821,10 +837,11 @@ class AD9910:
return self.pow_to_turns(self.get_pow()) return self.pow_to_turns(self.get_pow())
@kernel @kernel
def set(self, frequency: TFloat, phase: TFloat = 0.0, def set(self, frequency: TFloat = 0.0, phase: TFloat = 0.0,
amplitude: TFloat = 1.0, phase_mode: TInt32 = _PHASE_MODE_DEFAULT, amplitude: TFloat = 1.0, phase_mode: TInt32 = _PHASE_MODE_DEFAULT,
ref_time_mu: TInt64 = int64(-1), profile: TInt32 = 0) -> TFloat: ref_time_mu: TInt64 = int64(-1), profile: TInt32 = 7,
"""Set profile 0 data in SI units. ram_destination: TInt32 = -1) -> TFloat:
"""Set DDS data in SI units.
.. seealso:: :meth:`set_mu` .. seealso:: :meth:`set_mu`
@ -833,13 +850,14 @@ class AD9910:
:param amplitude: Amplitude in units of full scale :param amplitude: Amplitude in units of full scale
:param phase_mode: Phase mode constant :param phase_mode: Phase mode constant
:param ref_time_mu: Fiducial time stamp in machine units :param ref_time_mu: Fiducial time stamp in machine units
:param profile: Profile to affect :param profile: Single tone profile to affect.
:param ram_destination: RAM destination.
:return: Resulting phase offset in turns :return: Resulting phase offset in turns
""" """
return self.pow_to_turns(self.set_mu( return self.pow_to_turns(self.set_mu(
self.frequency_to_ftw(frequency), self.turns_to_pow(phase), self.frequency_to_ftw(frequency), self.turns_to_pow(phase),
self.amplitude_to_asf(amplitude), phase_mode, ref_time_mu, self.amplitude_to_asf(amplitude), phase_mode, ref_time_mu,
profile)) profile, ram_destination))
@kernel @kernel
def get(self, profile: TInt32 = 0) -> TTuple([TFloat, TFloat, TFloat]): def get(self, profile: TInt32 = 0) -> TTuple([TFloat, TFloat, TFloat]):

View File

@ -188,7 +188,7 @@ class CPLD:
assert sync_div is None assert sync_div is None
sync_div = 0 sync_div = 0
self.cfg_reg = urukul_cfg(rf_sw=rf_sw, led=0, profile=0, self.cfg_reg = urukul_cfg(rf_sw=rf_sw, led=0, profile=7,
io_update=0, mask_nu=0, clk_sel=clk_sel, io_update=0, mask_nu=0, clk_sel=clk_sel,
sync_sel=sync_sel, sync_sel=sync_sel,
rst=0, io_rst=0, clk_div=clk_div) rst=0, io_rst=0, clk_div=clk_div)