def set_dac_mu(self, values, channels=list(range(40))): """Program multiple DAC channels and pulse LDAC to update the DAC outputs. This method does not advance the timeline; write events are scheduled in the past. The DACs will synchronously start changing their output levels `now`. If no LDAC device was defined, the LDAC pulse is skipped. See :meth load:. :param values: list of DAC values to program :param channels: list of DAC channels to program. If not specified, we program the DAC channels sequentially, starting at 0. """ t0 = now_mu() # t10: max busy period after writing to DAC registers t_10 = self.core.seconds_to_mu(1500 * ns) # compensate all delays that will be applied delay_mu(-t_10 - len(values) * self.bus.xfer_duration_mu) for i in range(len(values)): self.write_dac_mu(channels[i], values[i]) delay_mu(t_10) self.load() at_mu(t0)
def set_dac_mu(self, values, channels=list(range(40))): """Program multiple DAC channels and pulse LDAC to update the DAC outputs. This method does not advance the timeline; write events are scheduled in the past. The DACs will synchronously start changing their output levels `now`. If no LDAC device was defined, the LDAC pulse is skipped. See :meth load:. :param values: list of DAC values to program :param channels: list of DAC channels to program. If not specified, we program the DAC channels sequentially, starting at 0. """ t0 = now_mu() # t10: max busy period after writing to DAC registers t_10 = self.core.seconds_to_mu(1500*ns) # compensate all delays that will be applied delay_mu(-t_10-len(values)*self.bus.xfer_duration_mu) for i in range(len(values)): self.write_dac_mu(channels[i], values[i]) delay_mu(t_10) self.load() at_mu(t0)
def set_mu(self, ftw, pow_=0, asf=0x3fff, phase_mode=_PHASE_MODE_DEFAULT, ref_time_mu=int64(-1), profile=0): """Set profile 0 data in machine units. This uses machine units (FTW, POW, ASF). The frequency tuning word width is 32, the phase offset word width is 16, and the amplitude scale factor width is 12. After the SPI transfer, the shared IO update pin is pulsed to activate the data. .. seealso: :meth:`set_phase_mode` for a definition of the different phase modes. :param ftw: Frequency tuning word: 32 bit. :param pow_: Phase tuning word: 16 bit unsigned. :param asf: Amplitude scale factor: 14 bit unsigned. :param phase_mode: If specified, overrides the default phase mode set by :meth:`set_phase_mode` for this call. :param ref_time_mu: Fiducial time used to compute absolute or tracking phase updates. In machine units as obtained by `now_mu()`. :param profile: Profile number to set (0-7, default: 0). :return: Resulting phase offset word after application of phase tracking offset. When using :const:`PHASE_MODE_CONTINUOUS` in subsequent calls, use this value as the "current" phase. """ if phase_mode == _PHASE_MODE_DEFAULT: phase_mode = self.phase_mode # Align to coarse RTIO which aligns SYNC_CLK. I.e. clear fine TSC # This will not cause a collision or sequence error. at_mu(now_mu() & ~7) if phase_mode != PHASE_MODE_CONTINUOUS: # Auto-clear phase accumulator on IO_UPDATE. # This is active already for the next IO_UPDATE self.set_cfr1(phase_autoclear=1) if phase_mode == PHASE_MODE_TRACKING and ref_time_mu < 0: # set default fiducial time stamp ref_time_mu = 0 if ref_time_mu >= 0: # 32 LSB are sufficient. # Also no need to use IO_UPDATE time as this # is equivalent to an output pipeline latency. dt = int32(now_mu()) - int32(ref_time_mu) pow_ += dt * ftw * self.sysclk_per_mu >> 16 self.write64(_AD9910_REG_PROFILE0 + profile, (asf << 16) | (pow_ & 0xffff), ftw) delay_mu(int64(self.sync_data.io_update_delay)) self.cpld.io_update.pulse_mu(8) # assumes 8 mu > t_SYN_CCLK at_mu(now_mu() & ~7) # clear fine TSC again if phase_mode != PHASE_MODE_CONTINUOUS: self.set_cfr1() # future IO_UPDATE will activate return pow_
def test_at_mu(self): for i in range(_NUM_SAMPLES): with self.subTest('at_mu() sub test', i=i): # Random time ref_time = self.rnd.randrange(1000000000) # Set time at_mu(ref_time) # Compare time self.assertEqual(now_mu(), ref_time, 'Reference does not match now_mu()')
def set_mu(self, ftw, pow_=0, asf=0x3fff, phase_mode=_PHASE_MODE_DEFAULT, ref_time_mu=int64(-1), profile=0): """Set profile 0 data in machine units. This uses machine units (FTW, POW, ASF). The frequency tuning word width is 32, the phase offset word width is 16, and the amplitude scale factor width is 12. After the SPI transfer, the shared IO update pin is pulsed to activate the data. .. seealso: :meth:`set_phase_mode` for a definition of the different phase modes. :param ftw: Frequency tuning word: 32 bit. :param pow_: Phase tuning word: 16 bit unsigned. :param asf: Amplitude scale factor: 14 bit unsigned. :param phase_mode: If specified, overrides the default phase mode set by :meth:`set_phase_mode` for this call. :param ref_time_mu: Fiducial time used to compute absolute or tracking phase updates. In machine units as obtained by `now_mu()`. :param profile: Profile number to set (0-7, default: 0). :return: Resulting phase offset word after application of phase tracking offset. When using :const:`PHASE_MODE_CONTINUOUS` in subsequent calls, use this value as the "current" phase. """ if phase_mode == _PHASE_MODE_DEFAULT: phase_mode = self.phase_mode # Align to coarse RTIO which aligns SYNC_CLK. I.e. clear fine TSC # This will not cause a collision or sequence error. at_mu(now_mu() & ~7) if phase_mode != PHASE_MODE_CONTINUOUS: # Auto-clear phase accumulator on IO_UPDATE. # This is active already for the next IO_UPDATE self.set_cfr1(phase_autoclear=1) if phase_mode == PHASE_MODE_TRACKING and ref_time_mu < 0: # set default fiducial time stamp ref_time_mu = 0 if ref_time_mu >= 0: # 32 LSB are sufficient. # Also no need to use IO_UPDATE time as this # is equivalent to an output pipeline latency. dt = int32(now_mu()) - int32(ref_time_mu) pow_ += dt*ftw*self.sysclk_per_mu >> 16 self.write64(_AD9910_REG_PROFILE0 + profile, (asf << 16) | (pow_ & 0xffff), ftw) delay_mu(int64(self.io_update_delay)) self.cpld.io_update.pulse_mu(8) # assumes 8 mu > t_SYN_CCLK at_mu(now_mu() & ~7) # clear fine TSC again if phase_mode != PHASE_MODE_CONTINUOUS: self.set_cfr1() # future IO_UPDATE will activate return pow_
def measure_io_update_alignment(self, delay_start: TInt64, delay_stop: TInt64) -> TInt32: """Use the digital ramp generator to locate the alignment between IO_UPDATE and SYNC_CLK. The ramp generator is set up to a linear frequency ramp (dFTW/t_SYNC_CLK=1) and started at a coarse RTIO time stamp plus `delay_start` and stopped at a coarse RTIO time stamp plus `delay_stop`. :param delay_start: Start IO_UPDATE delay in machine units. :param delay_stop: Stop IO_UPDATE delay in machine units. :return: Odd/even SYNC_CLK cycle indicator. """ # set up DRG self.set_cfr1(drg_load_lrr=1, drg_autoclear=1) # DRG -> FTW, DRG enable self.set_cfr2(drg_enable=1) # no limits self.write64(_AD9910_REG_RAMP_LIMIT, -1, 0) # DRCTL=0, dt=1 t_SYNC_CLK self.write32(_AD9910_REG_RAMP_RATE, 0x00010000) # dFTW = 1, (work around negative slope) self.write64(_AD9910_REG_RAMP_STEP, -1, 0) # delay io_update after RTIO edge t = now_mu() + 8 & ~7 at_mu(t + delay_start) # assumes a maximum t_SYNC_CLK period self.cpld.io_update.pulse_mu(16 - delay_start) # realign # disable DRG autoclear and LRR on io_update self.set_cfr1() # stop DRG self.write64(_AD9910_REG_RAMP_STEP, 0, 0) at_mu(t + 0x1000 + delay_stop) self.cpld.io_update.pulse_mu(16 - delay_stop) # realign ftw = self.read32(_AD9910_REG_FTW) # read out effective FTW delay(100 * us) # slack # disable DRG self.set_cfr2(drg_enable=0) self.cpld.io_update.pulse_mu(8) return ftw & 1
def measure_io_update_alignment(self, delay_start, delay_stop): """Use the digital ramp generator to locate the alignment between IO_UPDATE and SYNC_CLK. The ramp generator is set up to a linear frequency ramp (dFTW/t_SYNC_CLK=1) and started at a coarse RTIO time stamp plus `delay_start` and stopped at a coarse RTIO time stamp plus `delay_stop`. :param delay_start: Start IO_UPDATE delay in machine units. :param delay_stop: Stop IO_UPDATE delay in machine units. :return: Odd/even SYNC_CLK cycle indicator. """ # set up DRG self.set_cfr1(drg_load_lrr=1, drg_autoclear=1) # DRG -> FTW, DRG enable self.write32(_AD9910_REG_CFR2, 0x01090000) # no limits self.write64(_AD9910_REG_RAMP_LIMIT, -1, 0) # DRCTL=0, dt=1 t_SYNC_CLK self.write32(_AD9910_REG_RAMP_RATE, 0x00010000) # dFTW = 1, (work around negative slope) self.write64(_AD9910_REG_RAMP_STEP, -1, 0) # delay io_update after RTIO edge t = now_mu() + 8 & ~7 at_mu(t + delay_start) # assumes a maximum t_SYNC_CLK period self.cpld.io_update.pulse_mu(16 - delay_start) # realign # disable DRG autoclear and LRR on io_update self.set_cfr1() # stop DRG self.write64(_AD9910_REG_RAMP_STEP, 0, 0) at_mu(t + 0x1000 + delay_stop) self.cpld.io_update.pulse_mu(16 - delay_stop) # realign ftw = self.read32(_AD9910_REG_FTW) # read out effective FTW delay(100*us) # slack # disable DRG self.write32(_AD9910_REG_CFR2, 0x01010000) self.cpld.io_update.pulse_mu(8) return ftw & 1
def measure_io_update_alignment(self, delay_start, delay_stop): """Use the digital ramp generator to locate the alignment between IO_UPDATE and SYNC_CLK. The ramp generator is set up to a linear frequency ramp (dFTW/t_SYNC_CLK=1) and started at a coarse RTIO time stamp plus `delay_start` and stopped at a coarse RTIO time stamp plus `delay_stop`. :param delay_start: Start IO_UPDATE delay in machine units. :param delay_stop: Stop IO_UPDATE delay in machine units. :return: Odd/even SYNC_CLK cycle indicator. """ # set up DRG # DRG ACC autoclear and LRR on io update self.write32(_AD9910_REG_CFR1, 0x0000c002) # DRG -> FTW, DRG enable self.write32(_AD9910_REG_CFR2, 0x01090000) # no limits self.write64(_AD9910_REG_RAMP_LIMIT, -1, 0) # DRCTL=0, dt=1 t_SYNC_CLK self.write32(_AD9910_REG_RAMP_RATE, 0x00010000) # dFTW = 1, (work around negative slope) self.write64(_AD9910_REG_RAMP_STEP, -1, 0) # delay io_update after RTIO/2 edge t = now_mu() + 0x10 & ~0xf at_mu(t + delay_start) self.cpld.io_update.pulse_mu(32 - delay_start) # realign # disable DRG autoclear and LRR on io_update self.write32(_AD9910_REG_CFR1, 0x00000002) # stop DRG self.write64(_AD9910_REG_RAMP_STEP, 0, 0) at_mu(t + 0x1000 + delay_stop) self.cpld.io_update.pulse_mu(32 - delay_stop) # realign ftw = self.read32(_AD9910_REG_FTW) # read out effective FTW delay(100 * us) # slack # disable DRG self.write32(_AD9910_REG_CFR2, 0x01010000) self.cpld.io_update.pulse_mu(8) return ftw & 1
def set(self, values, op=_AD5360_CMD_DATA): """Write to several channels and pulse LDAC to update the channels. This method does not advance the timeline. Write events are scheduled in the past. The DACs will synchronously start changing their output levels `now`. :param values: List of 16 bit values to write to the channels. :param op: Operation to perform, one of :const:`_AD5360_CMD_DATA`, :const:`_AD5360_CMD_OFFSET`, :const:`_AD5360_CMD_GAIN` (default: :const:`_AD5360_CMD_DATA`). """ t0 = now_mu() # t10 max busy low for one channel t_10 = self.core.seconds_to_mu(1.5 * us) # compensate all delays that will be applied delay_mu(-len(values) * self.bus.xfer_period_mu - t_10) for i in range(len(values)): self.write_channel(i, values[i], op) delay_mu(t_10) self.load() at_mu(t0)
def init(self, blind=False): """Initialize and detect Urukul. Resets the DDS I/O interface and verifies correct CPLD gateware version. Does not pulse the DDS MASTER_RESET as that confuses the AD9910. :param blind: Do not attempt to verify presence and compatibility. """ cfg = self.cfg_reg # Don't pulse MASTER_RESET (m-labs/artiq#940) self.cfg_reg = cfg | (0 << CFG_RST) | (1 << CFG_IO_RST) if blind: self.cfg_write(self.cfg_reg) else: proto_rev = urukul_sta_proto_rev(self.sta_read()) if proto_rev != STA_PROTO_REV_MATCH: raise ValueError("Urukul proto_rev mismatch") delay(100 * us) # reset, slack self.cfg_write(cfg) if self.sync_div: at_mu(now_mu() & ~0xf) # align to RTIO/2 self.set_sync_div(self.sync_div) # 125 MHz/2 = 1 GHz/16 delay(1 * ms) # DDS wake up
def init(self, blind=False): """Initialize and detect Urukul. Resets the DDS I/O interface and verifies correct CPLD gateware version. Does not pulse the DDS MASTER_RESET as that confuses the AD9910. :param blind: Do not attempt to verify presence and compatibility. """ cfg = self.cfg_reg # Don't pulse MASTER_RESET (m-labs/artiq#940) self.cfg_reg = cfg | (0 << CFG_RST) | (1 << CFG_IO_RST) if blind: self.cfg_write(self.cfg_reg) else: proto_rev = urukul_sta_proto_rev(self.sta_read()) if proto_rev != STA_PROTO_REV_MATCH: raise ValueError("Urukul proto_rev mismatch") delay(100*us) # reset, slack self.cfg_write(cfg) if self.sync_div: at_mu(now_mu() & ~0xf) # align to RTIO/2 self.set_sync_div(self.sync_div) # 125 MHz/2 = 1 GHz/16 delay(1*ms) # DDS wake up
def test_manual_deep_parallel(self): # Manual deep parallel matches the timing model of ARTIQ, see https://github.com/m-labs/artiq/issues/1555 for i in range(_NUM_SAMPLES): with self.subTest('parallel sub test', i=i): # Reference time ref_time = now_mu() # Block duration block_duration = 0 # Single random duration duration = self.rnd.randrange(1000000000) for _ in range(10): at_mu(ref_time) # Manually revert timeline for deep parallel emulation # Delay delay_mu(duration) # Update block duration block_duration = max(duration, block_duration) # Update ref time ref_time += block_duration # Compare to reference time self.assertEqual(now_mu(), ref_time, 'Reference does not match now_mu()')
def timestamp_mu(self): result = time.manager.get_time_mu() result += self.prng.randrange(100, 1000) time.manager.event(("timestamp_mu", self.name, result)) at_mu(result) return result
def run_daq(self, duration, daqs, chunk=200): self.core.break_realtime() self.trigger_controller.reset.off() at_mu(now_mu() + self.core.seconds_to_mu(duration)) self.trigger_controller.reset.on() self.daq_transfer_loop(daqs, duration, chunk)