forked from M-Labs/artiq
1
0
Fork 0

Merge pull request #1749 from airwoodix/phaser-frame-alignment-utils

Phaser: add helpers to align updates with RTIO timeline
This commit is contained in:
Robert Jördens 2021-09-03 14:00:17 +02:00 committed by GitHub
commit e7a46ec767
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 37 additions and 8 deletions

View File

@ -18,6 +18,7 @@ Highlights:
- Improved documentation - Improved documentation
- Expose the DAC coarse mixer and ``sif_sync`` - Expose the DAC coarse mixer and ``sif_sync``
- Exposes upconverter calibration and enabling/disabling of upconverter LO & RF outputs. - Exposes upconverter calibration and enabling/disabling of upconverter LO & RF outputs.
- Add helpers to align Phaser updates to the RTIO timeline (``get_next_frame_mu()``)
* ``get()``, ``get_mu()``, ``get_att()``, and ``get_att_mu()`` functions added for AD9910 and AD9912 * ``get()``, ``get_mu()``, ``get_att()``, and ``get_att_mu()`` functions added for AD9910 and AD9912
* New hardware support: * New hardware support:
- HVAMP_8CH 8 channel HV amplifier for Fastino / Zotino - HVAMP_8CH 8 channel HV amplifier for Fastino / Zotino

View File

@ -1,7 +1,7 @@
from numpy import int32, int64 from numpy import int32, int64
from artiq.language.core import kernel, delay_mu, delay from artiq.language.core import kernel, delay_mu, delay
from artiq.coredevice.rtio import rtio_output, rtio_input_data from artiq.coredevice.rtio import rtio_output, rtio_input_data, rtio_input_timestamp
from artiq.language.units import us, ns, ms, MHz from artiq.language.units import us, ns, ms, MHz
from artiq.language.types import TInt32 from artiq.language.types import TInt32
from artiq.coredevice.dac34h84 import DAC34H84 from artiq.coredevice.dac34h84 import DAC34H84
@ -92,7 +92,8 @@ class Phaser:
The latency/group delay from the RTIO events setting The latency/group delay from the RTIO events setting
:class:`PhaserOscillator` or :class:`PhaserChannel` DUC parameters all the :class:`PhaserOscillator` or :class:`PhaserChannel` DUC parameters all the
way to the DAC outputs is deterministic. This enables deterministic way to the DAC outputs is deterministic. This enables deterministic
absolute phase with respect to other RTIO input and output events. absolute phase with respect to other RTIO input and output events
(see `get_next_frame_mu()`).
The four analog DAC outputs are passed through anti-aliasing filters. The four analog DAC outputs are passed through anti-aliasing filters.
@ -160,6 +161,7 @@ class Phaser:
# self.core.seconds_to_mu(10*8*4*ns) # unfortunately this returns 319 # self.core.seconds_to_mu(10*8*4*ns) # unfortunately this returns 319
assert self.core.ref_period == 1*ns assert self.core.ref_period == 1*ns
self.t_frame = 10*8*4 self.t_frame = 10*8*4
self.frame_tstamp = int64(0)
self.clk_sel = clk_sel self.clk_sel = clk_sel
self.tune_fifo_offset = tune_fifo_offset self.tune_fifo_offset = tune_fifo_offset
self.sync_dly = sync_dly self.sync_dly = sync_dly
@ -188,7 +190,7 @@ class Phaser:
gw_rev = self.read8(PHASER_ADDR_GW_REV) gw_rev = self.read8(PHASER_ADDR_GW_REV)
if debug: if debug:
print(gw_rev) print("gw_rev:", gw_rev)
self.core.break_realtime() self.core.break_realtime()
delay(.1*ms) # slack delay(.1*ms) # slack
@ -197,6 +199,11 @@ class Phaser:
raise ValueError("large number of frame CRC errors") raise ValueError("large number of frame CRC errors")
delay(.1*ms) # slack delay(.1*ms) # slack
# determine the origin for frame-aligned timestamps
self.measure_frame_timestamp()
if self.frame_tstamp < 0:
raise ValueError("frame timestamp measurement timed out")
# reset # reset
self.set_cfg(dac_resetb=0, dac_sleep=1, dac_txena=0, self.set_cfg(dac_resetb=0, dac_sleep=1, dac_txena=0,
trf0_ps=1, trf1_ps=1, trf0_ps=1, trf1_ps=1,
@ -262,7 +269,7 @@ class Phaser:
if self.tune_fifo_offset: if self.tune_fifo_offset:
fifo_offset = self.dac_tune_fifo_offset() fifo_offset = self.dac_tune_fifo_offset()
if debug: if debug:
print(fifo_offset) print("fifo_offset:", fifo_offset)
self.core.break_realtime() self.core.break_realtime()
# self.dac_write(0x20, 0x0000) # stop fifo sync # self.dac_write(0x20, 0x0000) # stop fifo sync
@ -274,7 +281,7 @@ class Phaser:
delay(.1*ms) # slack delay(.1*ms) # slack
if alarms & ~0x0040: # ignore PLL alarms (see DS) if alarms & ~0x0040: # ignore PLL alarms (see DS)
if debug: if debug:
print(alarms) print("alarms:", alarms)
self.core.break_realtime() self.core.break_realtime()
# ignore alarms # ignore alarms
else: else:
@ -468,6 +475,27 @@ class Phaser:
""" """
return self.read8(PHASER_ADDR_CRC_ERR) return self.read8(PHASER_ADDR_CRC_ERR)
@kernel
def measure_frame_timestamp(self):
"""Measure the timestamp of an arbitrary frame and store it in `self.frame_tstamp`.
To be used as reference for aligning updates to the FastLink frames.
See `get_next_frame_mu()`.
"""
rtio_output(self.channel_base << 8, 0) # read any register
self.frame_tstamp = rtio_input_timestamp(now_mu() + 4 * self.t_frame, self.channel_base)
delay(100 * us)
@kernel
def get_next_frame_mu(self):
"""Return the timestamp of the frame strictly after `now_mu()`.
Register updates (DUC, DAC, TRF, etc.) scheduled at this timestamp and multiples
of `self.t_frame` later will have deterministic latency to output.
"""
n = int64((now_mu() - self.frame_tstamp) / self.t_frame)
return self.frame_tstamp + (n + 1) * self.t_frame
@kernel @kernel
def set_sync_dly(self, dly): def set_sync_dly(self, dly):
"""Set SYNC delay. """Set SYNC delay.
@ -860,7 +888,7 @@ class PhaserChannel:
By default, the new NCO phase applies on completion of the SPI By default, the new NCO phase applies on completion of the SPI
transfer. This also causes a staged NCO frequency to be applied. transfer. This also causes a staged NCO frequency to be applied.
Different triggers for applying nco settings may be configured through Different triggers for applying NCO settings may be configured through
the `syncsel_mixerxx` fields in the `dac` configuration dictionary (see the `syncsel_mixerxx` fields in the `dac` configuration dictionary (see
`__init__()`). `__init__()`).
@ -878,7 +906,7 @@ class PhaserChannel:
By default, the new NCO phase applies on completion of the SPI By default, the new NCO phase applies on completion of the SPI
transfer. This also causes a staged NCO frequency to be applied. transfer. This also causes a staged NCO frequency to be applied.
Different triggers for applying nco settings may be configured through Different triggers for applying NCO settings may be configured through
the `syncsel_mixerxx` fields in the `dac` configuration dictionary (see the `syncsel_mixerxx` fields in the `dac` configuration dictionary (see
`__init__()`). `__init__()`).
@ -1015,7 +1043,7 @@ class PhaserOscillator:
"""Phaser IQ channel oscillator (NCO/DDS). """Phaser IQ channel oscillator (NCO/DDS).
.. note:: Latencies between oscillators within a channel and between .. note:: Latencies between oscillators within a channel and between
oscillator paramters (amplitude and phase/frequency) are deterministic oscillator parameters (amplitude and phase/frequency) are deterministic
(with respect to the 25 MS/s sample clock) but not matched. (with respect to the 25 MS/s sample clock) but not matched.
""" """
kernel_invariants = {"channel", "base_addr"} kernel_invariants = {"channel", "base_addr"}