UPDATE: I leave this post here so it can be looked up, but I isolated constituent bugs better in these posts:


The experiment below is supposed to

(a) ramp the DDS amplitude from 0.1 to 0.05,
(b) stay there for a few microseconds and then
(c) ramp back up to 0.1.

Everything happens within the first 50 microseconds after the TTL trigger's rising flank.

Typically, the ramp-down is missing, even though it should be there. In that case:

  • Set flipped = True inside the run-function.
  • Run once and ignore whatever the scope shows you.
  • Set flipped = False inside the run-function.
  • Run once and see the expected waveform on the scope. The ramp-down has magically appeared.
  • Run again as many times as you want and the ramp-down will be missing every time. The only way to make it re-appear is to run once with flipped = True.

Wtf?

The system is the same as in https://forum.m-labs.hk/d/958-proto-rev-in-system-description-file-required-to-use-new-urukul-features.

from artiq.language.environment import EnvExperiment
from artiq.language.core import kernel, delay, now_mu
from artiq.language.units import ns, us, ms, s, MHz, V
from artiq.language.types import TInt32, TFloat
from artiq.coredevice.i2c import i2c_write_byte
from artiq.coredevice.kasli_i2c import port_mapping
from artiq.coredevice.ad9910 import PHASE_MODE_CONTINUOUS
from artiq.coredevice.ad9910 import _AD9910_REG_RAMP_LIMIT, _AD9910_REG_RAMP_STEP, _AD9910_REG_RAMP_RATE

import numpy as np


# Maps Kasli EEM port indices that are visible on the PCB
# to actual electrical port(?) indices that need to be passed to the FPGA.
KASLI_I2C_BOARD_TO_PORT_MAPPING = [port%8 for port in port_mapping.values()]
# for `artiq.coredevice.i2c.i2c_write_byte(busno, busaddr, data, ack=True)`
# and `artiq.coredevice.i2c.i2c_read_byte(busno, busaddr)`
DIO_SMA_BUS_NUMBER = 0
DIO_SMA_BUS_ADDRESS = 0x7c # = 124 (decimal) or 01111100 (binary)

@kernel(flags={"fast-math"})
def amplitude_to_asf_32(amplitude: TFloat) -> TInt32:
    r"""Linearly maps amplitude ∈ [0.0, 1.0] to an unsigned 32-bit integer {0,1,..., 2**32-1}.
    Hacking is necessary because the ARTIQ compiler does *not* know unsigned integers."""
    if amplitude < 0.0:
        raise ValueError("Invalid AD9910 fractional amplitude!")
    elif amplitude <= 0.4999999998: # increasing the last digit from 8 to 9 fails
        # no clue why minus sign is inverted compared to `rpc_numpy_benchmark`
        return np.int32(-round(amplitude * 2 * (1 << 31))) # (1 << 32) produces gargabe
    elif amplitude <= 0.9999999998: # increasing the last digit from 8 to 9 fails
        # no clue why minus sign is inverted compared to `rpc_numpy_benchmark`
        return np.int32(round((1-amplitude) * 2 * (1 << 31))) # (1 << 32) produces gargabe
    elif amplitude <= 1.0:
        return np.int32(round((1-0.9999999998) * 2 * (1 << 31))) # (1 << 32) produces gargabe
    else:
        raise ValueError("Invalid AD9910 fractional amplitude!")
    return np.int32(0) # prevents compiler crash


class DRGAmplitudeTest(EnvExperiment):

    def build(self):
        self.setattr_device("core") # artiq.coredevice.core.Core
        device_db = self.get_device_db() # dict, DO NOT EDIT!
        self.n_kasli_socs = 1 + len(device_db["core"]["arguments"]["satellite_cpu_targets"])
        self.setattr_device("i2c_switch0") # artiq.coredevice.i2c.I2CSwitch
        self.setattr_device("ttl0") # artiq.coredevice.ttl.TTLInOut
        self.setattr_device("urukul0_cpld") # artiq.coredevice.urukul.CPLD
        self.setattr_device("urukul0_ch0") # artiq.coredevice.ad9910.AD9910

    @kernel
    def init(self):
        for i in range(self.n_kasli_socs):
            while not self.core.get_rtio_destination_status(i):
                pass
        self.core.reset()
        self.core.break_realtime()
        self.i2c_switch0.set(channel = KASLI_I2C_BOARD_TO_PORT_MAPPING[0])
        delay(1*us)
        i2c_write_byte(
            busno   = DIO_SMA_BUS_NUMBER,
            busaddr = DIO_SMA_BUS_ADDRESS,
            data    = 0
        )
        delay(1*us)
        self.i2c_switch0.unset()
        self.core.break_realtime()
        self.ttl0.output()
        delay(1*us)
        self.ttl0.off()
        delay(1*us)
        self.urukul0_cpld.init()
        delay(1*us)
        self.urukul0_cpld.cfg_att_en_all(1)
        delay(1*us)
        self.urukul0_ch0.sw.off()
        delay(1*us)
        self.urukul0_ch0.init()
        delay(1*us)
        self.urukul0_ch0.set_phase_mode(PHASE_MODE_CONTINUOUS)
        delay(1*us)
        self.urukul0_ch0.set_att(0.0)
        delay(1*us)
        self.core.wait_until_mu(now_mu())

    @kernel
    def run(self):
        self.init()
        self.core.reset()
        self.core.break_realtime()
        self.urukul0_ch0.set(180*MHz, 0.0, 0.1)
        self.urukul0_ch0.sw.on()
        self.ttl0.on()
        delay(2*us)
        self.urukul0_ch0.cfg_drctl(True)
        delay(2*us)
        self.urukul0_ch0.write32(_AD9910_REG_RAMP_RATE, (1 << 16) | (1 << 0))
        time_step = 4 / self.urukul0_ch0.sysclk # seconds
        flipped = False
        if not flipped:
            self.urukul0_ch0.write64(
                _AD9910_REG_RAMP_LIMIT,
                amplitude_to_asf_32(0.1), # upper
                amplitude_to_asf_32(0.05), # lower
            )
        else:
            self.urukul0_ch0.write64(
                _AD9910_REG_RAMP_LIMIT,
                amplitude_to_asf_32(0.05), # lower
                amplitude_to_asf_32(0.1), # upper
            )
        self.urukul0_ch0.write64(
            _AD9910_REG_RAMP_STEP,
            amplitude_to_asf_32(0.05 * time_step / (10*us)), # decrement
            amplitude_to_asf_32(0.05 * time_step / (5*us)), # increment
        )
        delay(6*us)
        self.urukul0_ch0.set_cfr2(drg_enable=1, drg_destination=2)
        delay(2*us)
        self.urukul0_ch0.io_update.pulse_mu(8)
        delay(5*us)
        self.ttl0.off()
        delay(-1*us)
        self.urukul0_ch0.cfg_drctl(False)
        delay(12*us)
        self.urukul0_ch0.cfg_drctl(True)
        delay(2*us)
        self.urukul0_ch0.set_cfr2()
        delay(2*us)
        delay(10*us)
        self.urukul0_ch0.io_update.pulse_mu(8)