From 695eb705b35b50c6908edd7de8d0c97b48778c66 Mon Sep 17 00:00:00 2001
From: Robert Jordens <rj@m-labs.hk>
Date: Sun, 4 Dec 2016 16:52:08 +0100
Subject: [PATCH] sawg: extract spline

---
 artiq/coredevice/sawg.py   | 216 +------------------------------------
 artiq/coredevice/spline.py | 215 ++++++++++++++++++++++++++++++++++++
 2 files changed, 216 insertions(+), 215 deletions(-)
 create mode 100644 artiq/coredevice/spline.py

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.")