From 0e41725e2df8733050819f024a0615e63bdf4fb1 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Mon, 17 Oct 2016 12:40:10 +0200 Subject: [PATCH] pdq2: sync with pdq2 --- artiq/devices/pdq2/driver.py | 200 +++++++++++++++++++++++++++++++---- 1 file changed, 180 insertions(+), 20 deletions(-) diff --git a/artiq/devices/pdq2/driver.py b/artiq/devices/pdq2/driver.py index ea16a205b..f15928c84 100644 --- a/artiq/devices/pdq2/driver.py +++ b/artiq/devices/pdq2/driver.py @@ -13,6 +13,17 @@ logger = logging.getLogger(__name__) class Segment: + """Serialize the lines for a single Segment. + + Attributes: + max_time (int): Maximum duration of a line. + max_val (int): Maximum absolute value (scale) of the DAC output. + max_out (float): Output voltage at :attr:`max_val`. In Volt. + out_scale (float): Steps per Volt. + cordic_gain (float): CORDIC amplitude gain. + addr (int): Address assigned to this segment. + data (bytes): Serialized segment data. + """ max_time = 1 << 16 # uint16 timer max_val = 1 << 15 # int16 DAC max_out = 10. # Volt @@ -27,6 +38,24 @@ class Segment: def line(self, typ, duration, data, trigger=False, silence=False, aux=False, shift=0, jump=False, clear=False, wait=False): + """Append a line to this segment. + + Args: + typ (int): Output module to target with this line. + duration (int): Duration of the line in units of + ``clock_period*2**shift``. + data (bytes): Opaque data for the output module. + trigger (bool): Wait for trigger assertion before executing + this line. + silence (bool): Disable DAC clocks for the duration of this line. + aux (bool): Assert the AUX (F5 TTL) output during this line. + shift (int): Duration and spline evolution exponent. + jump (bool): Return to the frame address table after this line. + clear (bool): Clear the DDS phase accumulator when starting to + exectute this line. + wait (bool): Wait for trigger assertion before executing the next + line. + """ assert len(data) % 2 == 0, data assert len(data)//2 <= 14 # assert dt*(1 << shift) > 1 + len(data)//2 @@ -39,6 +68,15 @@ class Segment: @staticmethod def pack(widths, values): + """Pack spline data. + + Args: + widths (list[int]): Widths of values in multiples of 16 bits. + values (list[int]): Values to pack. + + Returns: + data (bytes): Packed data. + """ fmt = "<" ud = [] for width, value in zip(widths, values): @@ -60,7 +98,11 @@ class Segment: def bias(self, amplitude=[], **kwargs): """Append a bias line to this segment. - Amplitude in volts + Args: + amplitude (list[float]): Amplitude coefficients in in Volts and + increasing powers of ``1/(2**shift*clock_period)``. + Discrete time compensation will be applied. + **kwargs: Passed to :meth:`line`. """ coef = [self.out_scale*a for a in amplitude] discrete_compensate(coef) @@ -68,12 +110,18 @@ class Segment: self.line(typ=0, data=data, **kwargs) def dds(self, amplitude=[], phase=[], **kwargs): - """Append a dds line to this segment. + """Append a DDS line to this segment. - Amplitude in volts, - phase[0] in turns, - phase[1] in turns*sample_rate, - phase[2] in turns*(sample_rate/2**shift)**2 + Args: + amplitude (list[float]): Amplitude coefficients in in Volts and + increasing powers of ``1/(2**shift*clock_period)``. + Discrete time compensation and CORDIC gain compensation + will be applied by this method. + phase (list[float]): Phase/frequency/chirp coefficients. + ``phase[0]`` in ``turns``, + ``phase[1]`` in ``turns/clock_period``, + ``phase[2]`` in ``turns/(clock_period**2*2**shift)``. + **kwargs: Passed to :meth:`line`. """ scale = self.out_scale/self.cordic_gain coef = [scale*a for a in amplitude] @@ -86,6 +134,13 @@ class Segment: class Channel: + """PDQ2 Channel. + + Attributes: + num_frames (int): Number of frames supported. + max_data (int): Number of 16 bit data words per channel. + segments (list[Segment]): Segments added to this channel. + """ num_frames = 8 max_data = 4*(1 << 10) # 8kx16 8kx16 4kx16 @@ -93,14 +148,27 @@ class Channel: self.segments = [] def clear(self): + """Remove all segments.""" self.segments.clear() def new_segment(self): + """Create and attach a new :class:`Segment` to this channel. + + Returns: + :class:`Segment` + """ segment = Segment() self.segments.append(segment) return segment def place(self): + """Place segments contiguously. + + Assign segment start addresses and determine length of data. + + Returns: + addr (int): Amount of memory in use on this channel. + """ addr = self.num_frames for segment in self.segments: segment.addr = addr @@ -109,6 +177,23 @@ class Channel: return addr def table(self, entry=None): + """Generate the frame address table. + + Unused frame indices are assigned the zero address in the frame address + table. + This will cause the memory parser to remain in the frame address table + until another frame is selected. + + The frame entry segments can be any segments in the channel. + + Args: + entry (list[Segment]): List of initial segments for each frame. + If not specified, the first :attr:`num_frames` segments are + used as frame entry points. + + Returns: + table (bytes): Frame address table. + """ table = [0] * self.num_frames if entry is None: entry = self.segments @@ -118,6 +203,18 @@ class Channel: return struct.pack("<" + "H"*self.num_frames, *table) def serialize(self, entry=None): + """Serialize the memory for this channel. + + Places the segments contiguously in memory after the frame table. + Allocates and assigns segment and frame table addresses. + Serializes segment data and prepends frame address table. + + Args: + entry (list[Segment]): See :meth:`table`. + + Returns: + data (bytes): Channel memory data. + """ self.place() data = b"".join([segment.data for segment in self.segments]) return self.table(entry) + data @@ -125,7 +222,22 @@ class Channel: class Pdq2: """ - PDQ DAC (a.k.a. QC_Waveform) + PDQ stack. + + Args: + url (str): Pyserial device URL. Can be ``hwgrep://`` style + (search for serial number, bus topology, USB VID:PID combination), + ``COM15`` for a Windows COM port number, + ``/dev/ttyUSB0`` for a Linux serial port. + dev (file-like): File handle to use as device. If passed, ``url`` is + ignored. + num_boards (int): Number of boards in this stack. + + Attributes: + num_dacs (int): Number of DAC outputs per board. + num_channels (int): Number of channels in this stack. + num_boards (int): Number of boards in this stack. + channels (list[Channel]): List of :class:`Channel` in this stack. """ num_dacs = 3 freq = 50e6 @@ -154,42 +266,58 @@ class Pdq2: self.freq = float(freq) def close(self): + """Close the USB device handle.""" self.dev.close() del self.dev def write(self, data): + """Write data to the PDQ2 board. + + Args: + data (bytes): Data to write. + """ logger.debug("> %r", data) written = self.dev.write(data) if isinstance(written, int): assert written == len(data) def cmd(self, cmd, enable): + """Execute a command. + + Args: + cmd (str): Command to execute. One of (``RESET``, ``TRIGGER``, + ``ARM``, ``DCM``, ``START``). + enable (bool): Enable (``True``) or disable (``False``) the + feature. + """ cmd = self._commands.index(cmd) << 1 if not enable: cmd |= 1 self.write(struct.pack("cb", self._escape, cmd)) def write_mem(self, channel, data, start_addr=0): + """Write to channel memory. + + Args: + channel (int): Channel index to write to. Assumes every board in + the stack has :attr:`num_dacs` DAC outputs. + data (bytes): Data to write to memory. + start_addr (int): Start address to write data to. + """ board, dac = divmod(channel, self.num_dacs) data = struct.pack("