artiq/artiq/coredevice/sawg.py

200 lines
7.3 KiB
Python

from numpy import int32, int64
from artiq.language.core import kernel, now_mu, portable, delay
from artiq.coredevice.rtio import rtio_output, rtio_output_wide
from artiq.language.types import TInt32, TInt64, TFloat, TList
class Spline:
kernel_invariants = {"channel", "core", "scale", "width",
"time_width", "time_scale"}
def __init__(self, width, time_width, channel, core_device, scale=1.):
self.core = core_device
self.channel = channel
self.width = width
self.scale = (1 << width) * scale
self.time_width = time_width
self.time_scale = (1 << time_width) * core_device.coarse_ref_period
@portable(flags={"fast-math"})
def to_mu(self, value: TFloat) -> TInt32:
return int(round(value*self.scale))
@portable(flags={"fast-math"})
def from_mu(self, value: TInt32) -> TFloat:
return value/self.scale
@portable(flags={"fast-math"})
def to_mu64(self, value: TFloat) -> TList(TInt32):
v = int64(round(value*self.scale))
return [int32(v), int32((v >> 32) & 0xffffffff)]
@kernel
def set_mu(self, value: TInt32):
"""Set spline value (machine units).
:param value: Spline value in integer machine units.
"""
rtio_output(now_mu(), self.channel, 0, value)
@kernel
def set(self, value: TFloat):
"""Set spline value.
:param value: Spline value relative to full-scale.
"""
rtio_output(now_mu(), self.channel, 0, self.to_mu(value))
@kernel
def set64(self, value: TFloat):
"""Set spline value.
:param value: Spline value relative to full-scale.
"""
rtio_output_wide(now_mu(), self.channel, 0, self.to_mu64(value))
@kernel
def set_coeff_mu(self, value):
"""Set spline raw values.
:param value: Spline packed raw values.
"""
rtio_output_wide(now_mu(), self.channel, 0, value)
@portable(flags={"fast-math"})
def pack_coeff_mu(self, coeff, packed):
pos = 0
for i in range(len(coeff)):
wi = self.width + i*self.time_width
ci = coeff[i]
while wi != 0:
j = pos//32
used = pos - 32*j
avail = 32 - used
if avail > wi:
avail = wi
packed[j] |= int32(ci & ((1 << avail) - 1)) << used
ci >>= avail
wi -= avail
pos += avail
@portable(flags={"fast-math"})
def coeff_to_mu(self, coeff, coeff64):
for i in range(len(coeff)):
vi = coeff[i] * self.scale
for j in range(i):
vi *= self.time_scale
ci = int(round(vi))
coeff64[i] = ci
# artiq.wavesynth.coefficients.discrete_compensate:
if i == 2:
coeff64[1] += ci >> (self.time_width + 1)
elif i == 3:
coeff64[2] += ci >> self.time_width
coeff64[1] += (ci // 3) >> (2*self.time_width + 1)
@kernel
def set_coeff(self, value):
"""Set spline coefficients.
:param value: List of floating point spline knot coefficients,
lowest order (constant) coefficient first.
"""
n = len(value)
width = n*self.width + (n - 1)*n//2*self.time_width
coeff64 = [int64(0)] * n
packed = [int32(0)] * ((width + 31)//32)
self.coeff_to_mu(value, coeff64)
self.pack_coeff_mu(coeff64, packed)
self.set_coeff_mu(packed)
@kernel(flags={"fast-math"})
def smooth(self, start: TFloat, stop: TFloat, duration: TFloat,
order: TInt32):
"""Initiate an interpolated value change.
The third order interpolation is constrained to have zero first
order derivative at both start and stop.
For zeroth order (step) interpolation, the step is at duration/2.
For first order and third order interpolation (linear and cubic)
the interpolator needs to be stopped (or fed a new spline knot)
explicitly at the stop time.
This method advances the timeline by `duration`.
:param start: Initial value of the change.
:param stop: Final value of the change.
:param duration: Duration of the interpolation.
:param order: Order of the interpolation. Only 0, 1,
and 3 are valid: step, linear, cubic.
"""
if order == 0:
delay(duration/2)
self.set_coeff([stop])
delay(duration/2)
elif order == 1:
self.set_coeff([start, (stop - start)/duration])
delay(duration)
elif order == 3:
v2 = 6*(stop - start)/(duration*duration)
self.set_coeff([start, 0., v2, -2*v2/duration])
delay(duration)
else:
raise ValueError("Invalid interpolation order. "
"Supported orders are: 0, 1, 3.")
class SAWG:
"""Smart arbitrary waveform generator channel.
The channel is parametrized as: ::
oscillators = exp(2j*pi*(frequency0*t + phase0))*(
amplitude1*exp(2j*pi*(frequency1*t + phase1)) +
amplitude2*exp(2j*pi*(frequency2*t + phase2))
output = (offset +
i_enable*Re(oscillators) +
q_enable*Im(buddy_oscillators))
Where:
* offset, amplitude1, amplitude1: in units of full scale
* phase0, phase1, phase2: in units of turns
* frequency0, frequency1, frequency2: in units of Hz
:param channel_base: RTIO channel number of the first channel (amplitude).
Frequency and Phase are then assumed to be successive channels.
"""
kernel_invariants = {"channel_base", "core",
"amplitude1", "frequency1", "phase1",
"amplitude2", "frequency2", "phase2",
"frequency0", "phase0", "offset"}
def __init__(self, dmgr, channel_base, parallelism, core_device="core"):
self.core = dmgr.get(core_device)
self.channel_base = channel_base
width = 16
time_width = 16
cordic_gain = 1.646760258057163 # Cordic(width=16, guard=None).gain
# cfg: channel_base
self.offset = Spline(width, time_width, channel_base + 1,
self.core, 1/2)
self.amplitude1 = Spline(width, time_width, channel_base + 2,
self.core, 1/(2*cordic_gain**2))
self.frequency1 = Spline(3*width, time_width, channel_base + 3,
self.core, 1/self.core.coarse_ref_period)
self.phase1 = Spline(width, time_width, channel_base + 4,
self.core, 1.)
self.amplitude2 = Spline(width, time_width, channel_base + 5,
self.core, 1/(2*cordic_gain**2))
self.frequency2 = Spline(3*width, time_width, channel_base + 6,
self.core, 1/self.core.coarse_ref_period)
self.phase2 = Spline(width, time_width, channel_base + 7,
self.core, 1.)
self.frequency0 = Spline(2*width, time_width, channel_base + 8,
self.core,
self.core.coarse_ref_period/parallelism)
self.phase0 = Spline(width, time_width, channel_base + 9,
self.core, 1.)