Ejemplo n.º 1
0
    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)
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
    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_
Ejemplo n.º 4
0
    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()')
Ejemplo n.º 5
0
    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_
Ejemplo n.º 6
0
    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
Ejemplo n.º 7
0
    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
Ejemplo n.º 8
0
    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
Ejemplo n.º 9
0
    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)
Ejemplo n.º 10
0
    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
Ejemplo n.º 11
0
    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
Ejemplo n.º 12
0
    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()')
Ejemplo n.º 13
0
 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
Ejemplo n.º 14
0
 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)