- Edited
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)