diff --git a/artiq/coredevice/sawg.py b/artiq/coredevice/sawg.py index df1e34c93..319bf781c 100644 --- a/artiq/coredevice/sawg.py +++ b/artiq/coredevice/sawg.py @@ -1,218 +1,4 @@ -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 - - -class Spline: - """Spline interpolating RTIO channel. - - One knot of a polynomial basis spline (B-spline) :math:`u(t)` - is defined by the coefficients :math:`u_n` up to order :math:`n = k`. - If the knot is evaluated starting at time :math:`t_0`, the output - :math:`u(t)` for :math:`t > t_0, t_0` is: - - .. math:: - u(t) = \sum_{n=0}^k \frac{u_n}{n!} (t - t_0)^n - = u_0 + u_1 (t - t_0) + \frac{u_2}{2} (t - t_0)^2 + \dots - """ - - 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 = float((int64(1) << width) * scale) - self.time_width = time_width - self.time_scale = float((1 << time_width) * - core_device.coarse_ref_period) - - @portable(flags={"fast-math"}) - def to_mu(self, value: TFloat) -> TInt32: - """Convert floating point `value` from physical units to 32 bit - integer machine units.""" - return int32(round(value*self.scale)) - - @portable(flags={"fast-math"}) - def from_mu(self, value: TInt32) -> TFloat: - """Convert 32 bit integer `value` from machine units to floating point - physical units.""" - return value/self.scale - - @portable(flags={"fast-math"}) - def to_mu64(self, value: TFloat) -> TInt64: - """Convert floating point `value` from physical units to 64 bit - integer machine units.""" - return int64(round(value*self.scale)) - - @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(flags={"fast-math"}) - def set(self, value: TFloat): - """Set spline value. - - :param value: Spline value relative to full-scale. - """ - if self.width > 32: - l = [int32(0)] * 2 - self.pack_coeff_mu([self.to_mu64(value)], l) - rtio_output_wide(now_mu(), self.channel, 0, l) - else: - rtio_output(now_mu(), self.channel, 0, self.to_mu(value)) - - @kernel - def set_coeff_mu(self, value): # TList(TInt32) - """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): # TList(TInt64), TList(TInt32) - """Pack coefficients into RTIO data - - :param coeff: TList(TInt64) list of machine units spline coefficients. - Lowest (zeroth) order first. The coefficient list is zero-extended - by the RTIO gateware. - :param packed: TList(TInt32) list for packed RTIO data. Must be - pre-allocated. Length in bits is - `n*width + (n - 1)*n//2*time_width` - """ - 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 - cij = int32(ci) - if avail != 32: - cij &= (1 << avail) - 1 - packed[j] |= cij << used - ci >>= avail - wi -= avail - pos += avail - - @portable(flags={"fast-math"}) - def coeff_to_mu(self, coeff, coeff64): # TList(TFloat), TList(TInt64) - """Convert a floating point list of coefficients into a 64 bit - integer (preallocated). - - :param coeff: TList(TFloat) list of coefficients in physical units. - :param coeff64: TList(TInt64) preallocated list of coefficients in - machine units. - """ - for i in range(len(coeff)): - vi = coeff[i] * self.scale - for j in range(i): - vi *= self.time_scale - ci = int64(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 // 6 >> 2*self.time_width - - def coeff_as_packed_mu(self, coeff64): - """Pack 64 bit integer machine units coefficients into 32 bit integer - RTIO data list. - - This is a host-only method that can be used to generate packed - spline knot data to be frozen into kernels at compile time. - """ - n = len(coeff64) - width = n*self.width + (n - 1)*n//2*self.time_width - packed = [int32(0)] * ((width + 31)//32) - self.pack_coeff_mu(coeff64, packed) - return packed - - def coeff_as_packed(self, coeff): - """Convert floating point spline coefficients into 32 bit integer - packed data. - - This is a host-only method that can be used to generate packed - spline knot data to be frozen into kernels at compile time. - """ - coeff64 = [int64(0)] * len(coeff) - self.coeff_to_mu(coeff, coeff64) - return self.coeff_as_packed_mu(coeff64) - - @kernel(flags={"fast-math"}) - def set_coeff(self, coeff): # TList(TFloat) - """Set spline coefficients. - - Missing coefficients (high order) are zero-extended byt the RTIO - gateware. - - If more coefficients are supplied than the gateware supports the extra - coefficients are ignored. - - :param value: List of floating point spline knot coefficients, - lowest order (constant) coefficient first. Units are the - unit of this spline's value times increasing powers of 1/s. - """ - n = len(coeff) - coeff64 = [int64(0)] * n - self.coeff_to_mu(coeff, coeff64) - width = n*self.width + (n - 1)*n//2*self.time_width - packed = [int32(0)] * ((width + 31)//32) - 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. - - For zeroth order (step) interpolation, the step is at - `start + duration/2`. - - First order interpolation corresponds to a linear value ramp from - `start` to `stop` over `duration`. - - The third order interpolation is constrained to have zero first - order derivative at both `start` and `stop`. - - 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. In physical units. - :param stop: Final value of the change. In physical units. - :param duration: Duration of the interpolation. In physical units. - :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.") +from artiq.coredevice.spline import Spline class SAWG: diff --git a/artiq/coredevice/spline.py b/artiq/coredevice/spline.py new file mode 100644 index 000000000..a6c99ddd9 --- /dev/null +++ b/artiq/coredevice/spline.py @@ -0,0 +1,215 @@ +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 + + +class Spline: + """Spline interpolating RTIO channel. + + One knot of a polynomial basis spline (B-spline) :math:`u(t)` + is defined by the coefficients :math:`u_n` up to order :math:`n = k`. + If the knot is evaluated starting at time :math:`t_0`, the output + :math:`u(t)` for :math:`t > t_0, t_0` is: + + .. math:: + u(t) = \sum_{n=0}^k \frac{u_n}{n!} (t - t_0)^n + = u_0 + u_1 (t - t_0) + \frac{u_2}{2} (t - t_0)^2 + \dots + """ + + 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 = float((int64(1) << width) * scale) + self.time_width = time_width + self.time_scale = float((1 << time_width) * + core_device.coarse_ref_period) + + @portable(flags={"fast-math"}) + def to_mu(self, value: TFloat) -> TInt32: + """Convert floating point `value` from physical units to 32 bit + integer machine units.""" + return int32(round(value*self.scale)) + + @portable(flags={"fast-math"}) + def from_mu(self, value: TInt32) -> TFloat: + """Convert 32 bit integer `value` from machine units to floating point + physical units.""" + return value/self.scale + + @portable(flags={"fast-math"}) + def to_mu64(self, value: TFloat) -> TInt64: + """Convert floating point `value` from physical units to 64 bit + integer machine units.""" + return int64(round(value*self.scale)) + + @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(flags={"fast-math"}) + def set(self, value: TFloat): + """Set spline value. + + :param value: Spline value relative to full-scale. + """ + if self.width > 32: + l = [int32(0)] * 2 + self.pack_coeff_mu([self.to_mu64(value)], l) + rtio_output_wide(now_mu(), self.channel, 0, l) + else: + rtio_output(now_mu(), self.channel, 0, self.to_mu(value)) + + @kernel + def set_coeff_mu(self, value): # TList(TInt32) + """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): # TList(TInt64), TList(TInt32) + """Pack coefficients into RTIO data + + :param coeff: TList(TInt64) list of machine units spline coefficients. + Lowest (zeroth) order first. The coefficient list is zero-extended + by the RTIO gateware. + :param packed: TList(TInt32) list for packed RTIO data. Must be + pre-allocated. Length in bits is + `n*width + (n - 1)*n//2*time_width` + """ + 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 + cij = int32(ci) + if avail != 32: + cij &= (1 << avail) - 1 + packed[j] |= cij << used + ci >>= avail + wi -= avail + pos += avail + + @portable(flags={"fast-math"}) + def coeff_to_mu(self, coeff, coeff64): # TList(TFloat), TList(TInt64) + """Convert a floating point list of coefficients into a 64 bit + integer (preallocated). + + :param coeff: TList(TFloat) list of coefficients in physical units. + :param coeff64: TList(TInt64) preallocated list of coefficients in + machine units. + """ + for i in range(len(coeff)): + vi = coeff[i] * self.scale + for j in range(i): + vi *= self.time_scale + ci = int64(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 // 6 >> 2*self.time_width + + def coeff_as_packed_mu(self, coeff64): + """Pack 64 bit integer machine units coefficients into 32 bit integer + RTIO data list. + + This is a host-only method that can be used to generate packed + spline knot data to be frozen into kernels at compile time. + """ + n = len(coeff64) + width = n*self.width + (n - 1)*n//2*self.time_width + packed = [int32(0)] * ((width + 31)//32) + self.pack_coeff_mu(coeff64, packed) + return packed + + def coeff_as_packed(self, coeff): + """Convert floating point spline coefficients into 32 bit integer + packed data. + + This is a host-only method that can be used to generate packed + spline knot data to be frozen into kernels at compile time. + """ + coeff64 = [int64(0)] * len(coeff) + self.coeff_to_mu(coeff, coeff64) + return self.coeff_as_packed_mu(coeff64) + + @kernel(flags={"fast-math"}) + def set_coeff(self, coeff): # TList(TFloat) + """Set spline coefficients. + + Missing coefficients (high order) are zero-extended byt the RTIO + gateware. + + If more coefficients are supplied than the gateware supports the extra + coefficients are ignored. + + :param value: List of floating point spline knot coefficients, + lowest order (constant) coefficient first. Units are the + unit of this spline's value times increasing powers of 1/s. + """ + n = len(coeff) + coeff64 = [int64(0)] * n + self.coeff_to_mu(coeff, coeff64) + width = n*self.width + (n - 1)*n//2*self.time_width + packed = [int32(0)] * ((width + 31)//32) + 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. + + For zeroth order (step) interpolation, the step is at + `start + duration/2`. + + First order interpolation corresponds to a linear value ramp from + `start` to `stop` over `duration`. + + The third order interpolation is constrained to have zero first + order derivative at both `start` and `stop`. + + 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. In physical units. + :param stop: Final value of the change. In physical units. + :param duration: Duration of the interpolation. In physical units. + :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.")