From 65f8a97b56656e321e3a36b7d187b805723034cb Mon Sep 17 00:00:00 2001 From: Etienne Wodey Date: Wed, 1 Sep 2021 17:39:08 +0200 Subject: [PATCH 1/5] phaser: add helpers to align updates to the RTIO timeline Signed-off-by: Etienne Wodey --- RELEASE_NOTES.rst | 1 + artiq/coredevice/phaser.py | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 862ed148b..b1a762752 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -18,6 +18,7 @@ Highlights: - Improved documentation - Expose the DAC coarse mixer and ``sif_sync`` - Exposes upconverter calibration and enabling/disabling of upconverter LO & RF outputs. + - Add helpers to align Phaser updates to the RTIO timeline (``get_next_frame_timestamp()``) * ``get()``, ``get_mu()``, ``get_att()``, and ``get_att_mu()`` functions added for AD9910 and AD9912 * New hardware support: - HVAMP_8CH 8 channel HV amplifier for Fastino / Zotino diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index c8c3c0938..f6a84fd72 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -1,7 +1,8 @@ from numpy import int32, int64 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.coredevice.core import rtio_get_counter from artiq.language.units import us, ns, ms, MHz from artiq.language.types import TInt32 from artiq.coredevice.dac34h84 import DAC34H84 @@ -160,6 +161,7 @@ class Phaser: # self.core.seconds_to_mu(10*8*4*ns) # unfortunately this returns 319 assert self.core.ref_period == 1*ns self.t_frame = 10*8*4 + self.frame_tstamp = int64(0) self.clk_sel = clk_sel self.tune_fifo_offset = tune_fifo_offset self.sync_dly = sync_dly @@ -197,6 +199,9 @@ class Phaser: raise ValueError("large number of frame CRC errors") delay(.1*ms) # slack + # determine the origin for frame-aligned timestamps + self.measure_frame_timestamp() + # reset self.set_cfg(dac_resetb=0, dac_sleep=1, dac_txena=0, trf0_ps=1, trf1_ps=1, @@ -468,6 +473,32 @@ class Phaser: """ return self.read8(PHASER_ADDR_CRC_ERR) + @kernel + def measure_frame_timestamp(self): + """Perform a register read and record the exact frame timing in `self.frame_tstamp`. + + Deterministic timing requires updates to be schedule at multiples of `self.t_frame` later. + See `get_next_frame_timestamp()`. + """ + rtio_output((self.channel_base << 8) | (PHASER_ADDR_BOARD_ID & 0x7f), 0) # can read any register + delay_mu(int64(self.t_frame)) + self.frame_tstamp = rtio_input_timestamp(rtio_get_counter() + 0xffffff, self.channel_base) + delay(10*ms) + + @kernel + def get_next_frame_timestamp(self, after_timestamp_mu = int64(-1)): + """Return the RTIO timestamp of the next frame after `after_timestamp_mu`. + + If `after_timestamp_mu < 0`, return the next frame after `now_mu()`. + + Updates scheduled at this timestamp and multiples of `self.t_frame` later will + have deterministic latency with respect to the RTIO timeline. + """ + if after_timestamp_mu < 0: + after_timestamp_mu = now_mu() + n = int64((after_timestamp_mu - self.frame_tstamp) / self.t_frame) + return self.frame_tstamp + (n + 1) * self.t_frame + @kernel def set_sync_dly(self, dly): """Set SYNC delay. From 61b44d40ddb6c51debcb0ced6f49729ec2fa4b77 Mon Sep 17 00:00:00 2001 From: Etienne Wodey Date: Wed, 1 Sep 2021 17:43:30 +0200 Subject: [PATCH 2/5] phaser: add labels to debug init prints Signed-off-by: Etienne Wodey --- artiq/coredevice/phaser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index f6a84fd72..efd0a895f 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -190,7 +190,7 @@ class Phaser: gw_rev = self.read8(PHASER_ADDR_GW_REV) if debug: - print(gw_rev) + print("gw_rev:", gw_rev) self.core.break_realtime() delay(.1*ms) # slack @@ -267,7 +267,7 @@ class Phaser: if self.tune_fifo_offset: fifo_offset = self.dac_tune_fifo_offset() if debug: - print(fifo_offset) + print("fifo_offset:", fifo_offset) self.core.break_realtime() # self.dac_write(0x20, 0x0000) # stop fifo sync @@ -279,7 +279,7 @@ class Phaser: delay(.1*ms) # slack if alarms & ~0x0040: # ignore PLL alarms (see DS) if debug: - print(alarms) + print("alarms:", alarms) self.core.break_realtime() # ignore alarms else: From 7aebf02f8435e8c2c58c23b02ed6b75d8206fafb Mon Sep 17 00:00:00 2001 From: Etienne Wodey Date: Wed, 1 Sep 2021 17:44:46 +0200 Subject: [PATCH 3/5] phaser: docs: add reference to get_next_frame_timestamps(), fix typo Signed-off-by: Etienne Wodey --- artiq/coredevice/phaser.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index efd0a895f..cd9bc8d74 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -93,7 +93,8 @@ class Phaser: The latency/group delay from the RTIO events setting :class:`PhaserOscillator` or :class:`PhaserChannel` DUC parameters all the 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_timestamp()`). The four analog DAC outputs are passed through anti-aliasing filters. @@ -891,7 +892,7 @@ class PhaserChannel: By default, the new NCO phase applies on completion of the SPI 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 `__init__()`). @@ -909,7 +910,7 @@ class PhaserChannel: By default, the new NCO phase applies on completion of the SPI 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 `__init__()`). @@ -1046,7 +1047,7 @@ class PhaserOscillator: """Phaser IQ channel oscillator (NCO/DDS). .. 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. """ kernel_invariants = {"channel", "base_addr"} From 075cb26dd798383bdd99d447134d8dfc799d2341 Mon Sep 17 00:00:00 2001 From: Etienne Wodey Date: Fri, 3 Sep 2021 09:58:01 +0200 Subject: [PATCH 4/5] phaser: rename get_next_frame_timestamp() to get_next_frame_mu() and implement review comments (PR #1749) Signed-off-by: Etienne Wodey --- artiq/coredevice/phaser.py | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index cd9bc8d74..782306208 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -94,7 +94,7 @@ class Phaser: :class:`PhaserOscillator` or :class:`PhaserChannel` DUC parameters all the way to the DAC outputs is deterministic. This enables deterministic absolute phase with respect to other RTIO input and output events - (see `get_next_frame_timestamp()`). + (see `get_next_frame_mu()`). The four analog DAC outputs are passed through anti-aliasing filters. @@ -476,28 +476,23 @@ class Phaser: @kernel def measure_frame_timestamp(self): - """Perform a register read and record the exact frame timing in `self.frame_tstamp`. + """Measure the timestamp of an arbitrary frame and store it in `self.frame_tstamp`. - Deterministic timing requires updates to be schedule at multiples of `self.t_frame` later. - See `get_next_frame_timestamp()`. + To be used as reference for aligning updates to the FastLink frames. + See `get_next_frame_mu()`. """ - rtio_output((self.channel_base << 8) | (PHASER_ADDR_BOARD_ID & 0x7f), 0) # can read any register - delay_mu(int64(self.t_frame)) - self.frame_tstamp = rtio_input_timestamp(rtio_get_counter() + 0xffffff, self.channel_base) - delay(10*ms) + rtio_output(self.channel_base << 8, 0) # read any register + self.frame_tstamp = rtio_input_timestamp(rtio_get_counter() + 125_000, self.channel_base) + delay(100 * us) @kernel - def get_next_frame_timestamp(self, after_timestamp_mu = int64(-1)): - """Return the RTIO timestamp of the next frame after `after_timestamp_mu`. + def get_next_frame_mu(self): + """Return the timestamp of the frame strictly after `now_mu()`. - If `after_timestamp_mu < 0`, return the next frame after `now_mu()`. - - Updates scheduled at this timestamp and multiples of `self.t_frame` later will - have deterministic latency with respect to the RTIO timeline. + Register updates (DUC, DAC, TRF, etc.) scheduled at this timestamp and multiples + of `self.t_frame` later will have deterministic latency to output. """ - if after_timestamp_mu < 0: - after_timestamp_mu = now_mu() - n = int64((after_timestamp_mu - self.frame_tstamp) / self.t_frame) + n = int64((now_mu() - self.frame_tstamp) / self.t_frame) return self.frame_tstamp + (n + 1) * self.t_frame @kernel From 4d7bd3ee32f0b21c13542457d6a14a04e4fcd2f9 Mon Sep 17 00:00:00 2001 From: Etienne Wodey Date: Fri, 3 Sep 2021 10:58:11 +0200 Subject: [PATCH 5/5] phaser: fail init() if frame timestamp measurement times out Signed-off-by: Etienne Wodey --- RELEASE_NOTES.rst | 2 +- artiq/coredevice/phaser.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index b1a762752..e011739d8 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -18,7 +18,7 @@ Highlights: - Improved documentation - Expose the DAC coarse mixer and ``sif_sync`` - Exposes upconverter calibration and enabling/disabling of upconverter LO & RF outputs. - - Add helpers to align Phaser updates to the RTIO timeline (``get_next_frame_timestamp()``) + - 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 * New hardware support: - HVAMP_8CH 8 channel HV amplifier for Fastino / Zotino diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 782306208..daaed659d 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -2,7 +2,6 @@ from numpy import int32, int64 from artiq.language.core import kernel, delay_mu, delay from artiq.coredevice.rtio import rtio_output, rtio_input_data, rtio_input_timestamp -from artiq.coredevice.core import rtio_get_counter from artiq.language.units import us, ns, ms, MHz from artiq.language.types import TInt32 from artiq.coredevice.dac34h84 import DAC34H84 @@ -202,6 +201,8 @@ class Phaser: # determine the origin for frame-aligned timestamps self.measure_frame_timestamp() + if self.frame_tstamp < 0: + raise ValueError("frame timestamp measurement timed out") # reset self.set_cfg(dac_resetb=0, dac_sleep=1, dac_txena=0, @@ -482,7 +483,7 @@ class Phaser: See `get_next_frame_mu()`. """ rtio_output(self.channel_base << 8, 0) # read any register - self.frame_tstamp = rtio_input_timestamp(rtio_get_counter() + 125_000, self.channel_base) + self.frame_tstamp = rtio_input_timestamp(now_mu() + 4 * self.t_frame, self.channel_base) delay(100 * us) @kernel