diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 7ca8e68fa..0f4327e01 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -48,7 +48,9 @@ Release notes * Experiments scheduled with the "flush pipeline" option now proceed when there are lower-priority experiments in the pipeline. Only experiments at the current (or higher) priority level are flushed. -* "pdq2" has been renamed "pdq" (in controller and module names). +* The PDQ(2/3) driver has been removed and is now being maintained out-of tree + at https://github.com/m-labs/pdq. All SPI/USB driver layers, Mediator, + CompoundPDQ and examples/documentation has been moved. 2.4 diff --git a/artiq/coredevice/pdq.py b/artiq/coredevice/pdq.py deleted file mode 100644 index b3f2501a2..000000000 --- a/artiq/coredevice/pdq.py +++ /dev/null @@ -1,139 +0,0 @@ -from artiq.language.core import kernel, portable, delay_mu -from artiq.coredevice import spi -from artiq.devices.pdq.protocol import PDQBase, PDQ_CMD - - -_PDQ_SPI_CONFIG = ( - 0*spi.SPI_OFFLINE | 0*spi.SPI_CS_POLARITY | - 0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE | - 0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX - ) - - - -class PDQ(PDQBase): - """PDQ smart arbitrary waveform generator stack. - - Provides access to a stack of PDQ boards connected via SPI using PDQ - gateware version 3 or later. - - The SPI bus is wired with ``CS_N`` from the core device connected to - ``F2 IN`` on the master PDQ, ``CLK`` connected to ``F3 IN``, ``MOSI`` - connected to ``F4 IN`` and ``MISO`` (optionally) connected to ``F5 OUT``. - ``F1 TTL Input Trigger`` remains as waveform trigger input. - Due to hardware constraints, there can only be one board connected to the - core device's MISO line and therefore there can only be SPI readback - from one board at any time. - - :param spi_device: Name of the SPI bus this device is on. - :param chip_select: Value to drive on the chip select lines of the SPI bus - during transactions. - """ - - kernel_invariants = {"core", "chip_select", "bus"} - - def __init__(self, dmgr, spi_device, chip_select=1, **kwargs): - self.core = dmgr.get("core") - self.bus = dmgr.get(spi_device) - self.chip_select = chip_select - PDQBase.__init__(self, **kwargs) - - @kernel - def setup_bus(self, write_div=24, read_div=64): - """Configure the SPI bus and the SPI transaction parameters - for this device. This method has to be called before any other method - if the bus has been used to access a different device in the meantime. - - This method advances the timeline by the duration of two - RTIO-to-Wishbone bus transactions. - - :param write_div: Write clock divider. - :param read_div: Read clock divider. - """ - # write: 4*8ns >= 20ns = 2*clk (clock de-glitching 50MHz) - # read: 15*8*ns >= ~100ns = 5*clk (clk de-glitching latency + miso - # latency) - self.bus.set_config_mu(_PDQ_SPI_CONFIG, write_div, read_div) - self.bus.set_xfer(self.chip_select, 16, 0) - - @kernel - def set_reg(self, adr, data, board): - """Set a PDQ register. - - :param adr: Address of the register (``_PDQ_ADR_CONFIG``, - ``_PDQ_ADR_FRAME``, ``_PDQ_ADR_CRC``). - :param data: Register data (8 bit). - :param board: Board to access, ``0xf`` to write to all boards. - """ - self.bus.write((PDQ_CMD(board, 0, adr, 1) << 24) | (data << 16)) - delay_mu(self.bus.ref_period_mu) # get to 20ns min cs high - - @kernel - def get_reg(self, adr, board): - """Get a PDQ register. - - :param adr: Address of the register (``_PDQ_ADR_CONFIG``, - ``_PDQ_ADR_FRAME``, ``_PDQ_ADR_CRC``). - :param board: Board to access, ``0xf`` to write to all boards. - - :return: Register data (8 bit). - """ - self.bus.set_xfer(self.chip_select, 16, 8) - self.bus.write(PDQ_CMD(board, 0, adr, 0) << 24) - delay_mu(self.bus.ref_period_mu) # get to 20ns min cs high - self.bus.read_async() - self.bus.set_xfer(self.chip_select, 16, 0) - return int(self.bus.input_async() & 0xff) # FIXME: m-labs/artiq#713 - - @kernel - def write_mem(self, mem, adr, data, board=0xf): # FIXME: m-labs/artiq#714 - """Write to DAC channel waveform data memory. - - :param mem: DAC channel memory to access (0 to 2). - :param adr: Start address. - :param data: Memory data. List of 16 bit integers. - :param board: Board to access (0-15) with ``0xf = 15`` being broadcast - to all boards. - """ - self.bus.set_xfer(self.chip_select, 24, 0) - self.bus.write((PDQ_CMD(board, 1, mem, 1) << 24) | - ((adr & 0x00ff) << 16) | (adr & 0xff00)) - delay_mu(-self.bus.write_period_mu-3*self.bus.ref_period_mu) - self.bus.set_xfer(self.chip_select, 16, 0) - for i in data: - self.bus.write(i << 16) - delay_mu(-self.bus.write_period_mu) - delay_mu(self.bus.write_period_mu + self.bus.ref_period_mu) - # get to 20ns min cs high - - @kernel - def read_mem(self, mem, adr, data, board=0xf, buffer=8): - """Read from DAC channel waveform data memory. - - :param mem: DAC channel memory to access (0 to 2). - :param adr: Start address. - :param data: Memory data. List of 16 bit integers. - :param board: Board to access (0-15) with ``0xf = 15`` being broadcast - to all boards. - """ - n = len(data) - if not n: - return - self.bus.set_xfer(self.chip_select, 24, 8) - self.bus.write((PDQ_CMD(board, 1, mem, 0) << 24) | - ((adr & 0x00ff) << 16) | (adr & 0xff00)) - delay_mu(-self.bus.write_period_mu-3*self.bus.ref_period_mu) - self.bus.set_xfer(self.chip_select, 0, 16) - for i in range(n): - self.bus.write(0) - delay_mu(-self.bus.write_period_mu) - if i > 0: - delay_mu(-3*self.bus.ref_period_mu) - self.bus.read_async() - if i > buffer: - data[i - 1 - buffer] = self.bus.input_async() & 0xffff - delay_mu(self.bus.write_period_mu) - self.bus.set_xfer(self.chip_select, 16, 0) - self.bus.read_async() - for i in range(max(0, n - buffer - 1), n): - data[i] = self.bus.input_async() & 0xffff diff --git a/artiq/devices/pdq/__init__.py b/artiq/devices/pdq/__init__.py deleted file mode 100644 index 7d6bbe501..000000000 --- a/artiq/devices/pdq/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from artiq.devices.pdq.mediator import * diff --git a/artiq/devices/pdq/crc.py b/artiq/devices/pdq/crc.py deleted file mode 100644 index 27469949f..000000000 --- a/artiq/devices/pdq/crc.py +++ /dev/null @@ -1,35 +0,0 @@ -class CRC: - """Generic and simple table driven CRC calculator. - - This implementation is: - - * MSB first data - * "un-reversed" full polynomial (i.e. starts with 0x1) - * no initial complement - * no final complement - - Handle any variation on those details outside this class. - - >>> r = CRC(0x1814141AB)(b"123456789") # crc-32q - >>> assert r == 0x3010BF7F, hex(r) - """ - def __init__(self, poly, data_width=8): - self.poly = poly - self.crc_width = poly.bit_length() - 1 - self.data_width = data_width - self._table = [self._one(i << self.crc_width - data_width) - for i in range(1 << data_width)] - - def _one(self, i): - for j in range(self.data_width): - i <<= 1 - if i & 1 << self.crc_width: - i ^= self.poly - return i - - def __call__(self, msg, crc=0): - for data in msg: - p = data ^ crc >> self.crc_width - self.data_width - q = crc << self.data_width & (1 << self.crc_width) - 1 - crc = self._table[p] ^ q - return crc diff --git a/artiq/devices/pdq/driver.py b/artiq/devices/pdq/driver.py deleted file mode 100644 index 3f2ff5015..000000000 --- a/artiq/devices/pdq/driver.py +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright 2013-2017 Robert Jordens -# -# This file is part of pdq. -# -# pdq is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# pdq is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with pdq. If not, see . - -from math import log, sqrt -import logging -import struct - -import serial - -from artiq.wavesynth.coefficients import discrete_compensate -from artiq.language.core import kernel, portable, delay_mu - -from .crc import CRC -from .protocol import PDQBase, PDQ_CMD - - -logger = logging.getLogger(__name__) - -crc8 = CRC(0x107) - - -class PDQ(PDQBase): - def __init__(self, url=None, dev=None, **kwargs): - """Initialize PDQ USB/Parallel device stack. - - .. note:: This device should only be used if the PDQ is intended to be - configured using the USB connection and **not** via SPI. - - 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. - **kwargs: See :class:`PDQBase` . - """ - if dev is None: - dev = serial.serial_for_url(url) - self.dev = dev - self.crc = 0 - PDQBase.__init__(self, **kwargs) - - def write(self, data): - """Write data to the PDQ board over USB/parallel. - - SOF/EOF control sequences are appended/prepended to - the (escaped) data. The running checksum is updated. - - Args: - data (bytes): Data to write. - """ - logger.debug("> %r", data) - msg = b"\xa5\x02" + data.replace(b"\xa5", b"\xa5\xa5") + b"\xa5\x03" - written = self.dev.write(msg) - if isinstance(written, int): - assert written == len(msg), (written, len(msg)) - self.crc = crc8(data, self.crc) - - def set_reg(self, adr, data, board): - """Set a register. - - Args: - board (int): Board to write to (0-0xe), 0xf for all boards. - adr (int): Register address to write to (0-3). - data (int): Data to write (1 byte) - """ - self.write(struct.pack( - "= 0 and - self.frame.pdq.next_segment != self.segment_number): - raise SegmentSequenceError() - self.frame.advance() - - -class _Frame: - def __init__(self, pdq, frame_number): - self.pdq = pdq - self.frame_number = frame_number - self.segments = [] - self.segment_count = 0 # == len(self.segments), used in kernel - - self.invalidated = False - - # for @kernel - self.core = self.pdq.core - - def create_segment(self, name=None): - if self.invalidated: - raise InvalidatedError() - if self.pdq.armed: - raise ArmError() - segment = _Segment(self, self.segment_count) - if name is not None: - if hasattr(self, name): - raise NameError("Segment name already exists") - setattr(self, name, segment) - self.segments.append(segment) - self.segment_count += 1 - return segment - - def _arm(self): - self.segment_delays = [ - self.core.seconds_to_mu(s.duration*delay_margin_factor) - for s in self.segments] - - def _invalidate(self): - self.invalidated = True - - def _get_program(self): - r = [] - for segment in self.segments: - if segment.duration < 2*trigger_duration: - raise ValueError(("Segment too short ({:g} s), trigger might " - "spill").format(segment.duration)) - segment_program = [ - { - "dac_divider": dac_divider, - "duration": duration, - "channel_data": channel_data, - "trigger": False, - } for dac_divider, duration, channel_data in segment.lines] - segment_program[0]["trigger"] = True - r += segment_program - return r - - @portable - def advance(self): - if self.invalidated: - raise InvalidatedError() - if not self.pdq.armed: - raise ArmError() - - call_t = now_mu() - trigger_start_t = call_t - self.core.seconds_to_mu(trigger_duration/2) - - if self.pdq.current_frame >= 0: - # PDQ is in the middle of a frame. Check it is us. - if self.pdq.current_frame != self.frame_number: - raise FrameActiveError() - else: - # PDQ is in the jump table - set the selection signals - # to play our first segment. - self.pdq.current_frame = self.frame_number - self.pdq.next_segment = 0 - at_mu(trigger_start_t - self.core.seconds_to_mu(frame_setup)) - self.pdq.set_frame(self.frame_number) - - at_mu(trigger_start_t) - self.pdq.trigger.pulse(trigger_duration) - - at_mu(call_t) - delay_mu(self.segment_delays[self.pdq.next_segment]) - self.pdq.next_segment += 1 - - # test for end of frame - if self.pdq.next_segment == self.segment_count: - self.pdq.current_frame = -1 - self.pdq.next_segment = -1 - - -class CompoundPDQ: - def __init__(self, dmgr, pdq_devices, trigger_device, - aux_miso=0, aux_dac=0b111, clk2x=0): - self.core = dmgr.get("core") - self.pdqs = [dmgr.get(d) for d in pdq_devices] - self.trigger = dmgr.get(trigger_device) - self.aux_miso = aux_miso - self.aux_dac = aux_dac - self.clk2x = clk2x - - self.frames = [] - self.current_frame = -1 - self.next_segment = -1 - self.armed = False - - @portable - def disarm(self): - for frame in self.frames: - frame._invalidate() - self.frames.clear() - for dev in self.pdqs: - dev.set_config(reset=0, clk2x=self.clk2x, enable=0, trigger=0, - aux_miso=self.aux_miso, aux_dac=self.aux_dac, board=0xf) - self.armed = False - - def get_program(self): - return [f._get_program() for f in self.frames] - - @portable - def arm(self): - if self.armed: - raise ArmError() - for frame in self.frames: - frame._arm() - - full_program = self.get_program() - n = 0 - for pdq in self.pdqs: - dn = pdq.get_num_channels() - program = [] - for full_frame_program in full_program: - frame_program = [] - for full_line in full_frame_program: - line = { - "dac_divider": full_line["dac_divider"], - "duration": full_line["duration"], - "channel_data": full_line["channel_data"][n:n + dn], - "trigger": full_line["trigger"], - } - frame_program.append(line) - program.append(frame_program) - pdq.program(program) - n += dn - for pdq in self.pdqs: - dev.set_config(reset=0, clk2x=self.clk2x, enable=1, trigger=0, - aux_miso=self.aux_miso, aux_dac=self.aux_dac, board=0xf) - self.armed = True - - def create_frame(self): - if self.armed: - raise ArmError() - r = _Frame(self, len(self.frames)) - self.frames.append(r) - return r - - @kernel - def set_frame(self, frame): - for pdq in self.pdqs: - pdq.set_frame(self.frame_number) diff --git a/artiq/devices/pdq/protocol.py b/artiq/devices/pdq/protocol.py deleted file mode 100644 index 82b3b96bc..000000000 --- a/artiq/devices/pdq/protocol.py +++ /dev/null @@ -1,446 +0,0 @@ -from math import log, sqrt -import logging -import struct - -import serial - -from artiq.wavesynth.coefficients import discrete_compensate -from artiq.language.core import portable - - -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 - out_scale = max_val/max_out - cordic_gain = 1. - for i in range(16): - cordic_gain *= sqrt(1 + 2**(-2*i)) - - def __init__(self): - self.data = b"" - self.addr = None - - 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. - The corresponding global AUX routing setting determines which - channels control AUX. - 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 - header = ( - 1 + len(data)//2 | (typ << 4) | (trigger << 6) | (silence << 7) | - (aux << 8) | (shift << 9) | (jump << 13) | (clear << 14) | - (wait << 15) - ) - self.data += struct.pack(">= 16 - width -= 1 - ud.append(value) - fmt += "hi"[width] - try: - return struct.pack(fmt, *ud) - except struct.error as e: - logger.error("can not pack %s as %s (%s as %s): %s", - values, widths, ud, fmt, e) - raise e - - def bias(self, amplitude=[], **kwargs): - """Append a bias line to this segment. - - 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) - data = self.pack([0, 1, 2, 2], coef) - self.line(typ=0, data=data, **kwargs) - - def dds(self, amplitude=[], phase=[], **kwargs): - """Append a DDS line to this segment. - - 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] - discrete_compensate(coef) - if phase: - assert len(amplitude) == 4 - coef += [p*self.max_val*2 for p in phase] - data = self.pack([0, 1, 2, 2, 0, 1, 1], coef) - self.line(typ=1, data=data, **kwargs) - - -class Channel: - """PDQ 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. - """ - def __init__(self, max_data, num_frames): - self.max_data = max_data - self.num_frames = num_frames - 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 - addr += len(segment.data)//2 - assert addr <= self.max_data, addr - 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 - for i, frame in enumerate(entry): - if frame is not None: - table[i] = frame.addr - 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 - - -@portable -def PDQ_CMD(board, is_mem, adr, we): - """Pack PDQ command fields into command byte. - - :param board: Board address, 0 to 15, with ``15 = 0xf`` denoting broadcast - to all boards connected. - :param is_mem: If ``1``, ``adr`` denote the address of the memory to access - (0 to 2). Otherwise ``adr`` denotes the register to access. - :param adr: Address of the register or memory to access. - (``PDQ_ADR_CONFIG``, ``PDQ_ADR_FRAME``, ``PDQ_ADR_CRC``). - :param we: If ``1`` then write, otherwise read. - """ - return (adr << 0) | (is_mem << 2) | (board << 3) | (we << 7) - - -PDQ_ADR_CONFIG = 0 -PDQ_ADR_CRC = 1 -PDQ_ADR_FRAME = 2 - - -class PDQBase: - """ - PDQ stack. - - Attributes: - checksum (int): Running checksum of data written. - num_channels (int): Number of channels in this stack. - num_boards (int): Number of boards in this stack. - num_dacs (int): Number of DAC outputs per board. - num_frames (int): Number of frames supported. - channels (list[Channel]): List of :class:`Channel` in this stack. - """ - freq = 50e6 - - _mem_sizes = [None, (20,), (10, 10), (8, 6, 6)] # 10kx16 units - - def __init__(self, num_boards=3, num_dacs=3, num_frames=32): - """Initialize PDQ stack. - - Args: - num_boards (int): Number of boards in this stack. - num_dacs (int): Number of DAC outputs per board. - num_frames (int): Number of frames supported. - """ - self.checksum = 0 - self.num_boards = num_boards - self.num_dacs = num_dacs - self.num_frames = num_frames - self.num_channels = self.num_dacs * self.num_boards - m = self._mem_sizes[num_dacs] - self.channels = [Channel(m[j] << 11, num_frames) - for i in range(num_boards) - for j in range(num_dacs)] - - @portable - def get_num_boards(self): - return self.num_boards - - @portable - def get_num_channels(self): - return self.num_channels - - @portable - def get_num_frames(self): - return self.num_frames - - def get_freq(self): - return self.freq - - def set_freq(self, freq): - self.freq = float(freq) - - @portable - def set_reg(self, adr, data, board): - raise NotImplementedError - - @portable - def get_reg(self, adr, board): - raise NotImplementedError - - @portable - def write_mem(self, mem, adr, data, board=0xf): - raise NotImplementedError - - @portable - def read_mem(self, mem, adr, data, board=0xf, buffer=8): - raise NotImplementedError - - @portable - def set_config(self, reset=0, clk2x=0, enable=1, - trigger=0, aux_miso=0, aux_dac=0b111, board=0xf): - """Set the configuration register. - - Args: - reset (bool): Reset the board. Memory is not reset. Self-clearing. - clk2x (bool): Enable the clock multiplier (100 MHz instead of 50 - MHz) - enable (bool): Enable the reading and execution of waveform data - from memory. - trigger (bool): Soft trigger. Logical or with the ``F1 TTL Input`` - hardware trigger. - aux_miso (bool): Drive SPI MISO on the AUX/``F5 OUT`` TTL port of - each board. If ``False``/``0``, drive the masked logical OR of - the DAC channels' aux data. - aux_dac (int): DAC channel mask for AUX/F5. Each bit represents - one channel. AUX/F5 is: ``aux_miso ? spi_miso : - (aux_dac & Cat(_.aux for _ in channels) != 0)`` - board (int): Board to write to (0-0xe), 0xf for all boards. - """ - config = ((reset << 0) | (clk2x << 1) | (enable << 2) | - (trigger << 3) | (aux_miso << 4) | (aux_dac << 5)) - self.set_reg(PDQ_ADR_CONFIG, config, board) - - @portable - def get_config(self, board=0xf): - """Read configuration register. - - .. seealso: :meth:`set_config` - """ - return self.get_reg(PDQ_ADR_CONFIG, board) - - @portable - def set_crc(self, crc=0, board=0xf): - """Set/reset the checksum register. - - Args: - crc (int): Checksum value to write. - board (int): Board to write to (0-0xe), 0xf for all boards. - """ - self.set_reg(PDQ_ADR_CRC, crc, board) - - @portable - def get_crc(self, board=0xf): - """Read checksum register. - - .. seealso:: :meth:`set_crc` - """ - return self.get_reg(PDQ_ADR_CRC, board) - - @portable - def set_frame(self, frame, board=0xf): - """Set the current frame. - - Args: - frame (int): Frame to select. - board (int): Board to write to (0-0xe), 0xf for all boards. - """ - self.set_reg(PDQ_ADR_FRAME, frame, board) - - @portable - def get_frame(self, board=0xf): - """Read frame selection register. - - .. seealso:: :meth:`set_frame` - """ - return self.get_reg(PDQ_ADR_FRAME, board) - - def program_segments(self, segments, data): - """Append the wavesynth lines to the given segments. - - Args: - segments (list[Segment]): List of :class:`Segment` to append the - lines to. - data (list): List of wavesynth lines. - """ - for i, line in enumerate(data): - dac_divider = line.get("dac_divider", 1) - shift = int(log(dac_divider, 2)) - if 2**shift != dac_divider: - raise ValueError("only power-of-two dac_dividers supported") - duration = line["duration"] - trigger = line.get("trigger", False) - for segment, data in zip(segments, line["channel_data"]): - silence = data.pop("silence", False) - if len(data) != 1: - raise ValueError("only one target per channel and line " - "supported") - for target, target_data in data.items(): - getattr(segment, target)( - shift=shift, duration=duration, trigger=trigger, - silence=silence, **target_data) - - def program(self, program, channels=None): - """Serialize a wavesynth program and write it to the channels - in the stack. - - The :class:`Channel` targeted are cleared and each frame in the - wavesynth program is appended to a fresh set of :class:`Segment` - of the channels. All segments are allocated, the frame address tale - is generated, the channels are serialized and their memories are - written. - - Short single-cycle lines are prepended and appended to each frame to - allow proper write interlocking and to assure that the memory reader - can be reliably parked in the frame address table. - The first line of each frame is mandatorily triggered. - - Args: - program (list): Wavesynth program to serialize. - channels (list[int]): Channel indices to use. If unspecified, all - channels are used. - """ - if channels is None: - channels = range(self.num_channels) - chs = [self.channels[i] for i in channels] - for channel in chs: - channel.clear() - for frame in program: - segments = [c.new_segment() for c in chs] - self.program_segments(segments, frame) - # append an empty line to stall the memory reader before jumping - # through the frame table (`wait` does not prevent reading - # the next line) - for segment in segments: - segment.line(typ=3, data=b"", trigger=True, duration=1, aux=1, - jump=True) - for channel, ch in zip(channels, chs): - self.write_mem(channel, ch.serialize()) - - def ping(self): - """Ping method returning True. Required for ARTIQ remote - controller.""" - return True diff --git a/artiq/examples/master/device_db.py b/artiq/examples/master/device_db.py index d3b4c102f..bbafc111c 100644 --- a/artiq/examples/master/device_db.py +++ b/artiq/examples/master/device_db.py @@ -172,44 +172,6 @@ device_db = { "arguments": {"bus_channel": 27, "channel": 2} }, - "qc_q1_0": { - "type": "controller", - # ::1 is the IPv6 localhost address. If this controller is running on a remote machine, - # replace it with the IP or hostname of the machine. If using the hostname, make sure - # that it always resolves to a network-visible IP address (see documentation). - "host": "::1", - "port": 4000, - "command": "aqctl_pdq -p {port} --bind {bind} --simulation --dump qc_q1_0.bin" - }, - "qc_q1_1": { - "type": "controller", - "host": "::1", - "port": 4001, - "command": "aqctl_pdq -p {port} --bind {bind} --simulation --dump qc_q1_1.bin" - }, - "qc_q1_2": { - "type": "controller", - "host": "::1", - "port": 4002, - "command": "aqctl_pdq -p {port} --bind {bind} --simulation --dump qc_q1_2.bin" - }, - "qc_q1_3": { - "type": "controller", - "host": "::1", - "port": 4003, - "command": "aqctl_pdq -p {port} --bind {bind} --simulation --dump qc_q1_3.bin" - }, - "electrodes": { - "type": "local", - "module": "artiq.devices.pdq", - "class": "CompoundPDQ", - "arguments": { - "pdq2_devices": ["qc_q1_0", "qc_q1_1", "qc_q1_2", "qc_q1_3"], - "trigger_device": "ttl2", - "frame_devices": ["ttl4", "ttl5", "ttl6"] - } - }, - "lda": { "type": "controller", "best_effort": True, diff --git a/artiq/examples/master/repository/coredevice_examples/pdq2_simple.py b/artiq/examples/master/repository/coredevice_examples/pdq2_simple.py deleted file mode 100644 index 3b237bc87..000000000 --- a/artiq/examples/master/repository/coredevice_examples/pdq2_simple.py +++ /dev/null @@ -1,45 +0,0 @@ -import numpy as np - -from artiq.experiment import * -from artiq.wavesynth.coefficients import build_segment - - -class PDQ2Simple(EnvExperiment): - """Set PDQ2 voltages.""" - def build(self): - self.setattr_device("core") - self.setattr_device("pmt") - self.setattr_device("electrodes") - - # 1 device, 3 board each, 3 dacs each - self.u = np.arange(4*3)[None, :, None]*.1 - - def setup(self, offset): - self.electrodes.disarm() - self.load = self.electrodes.create_frame() - segment = self.load.create_segment() - for line in build_segment([100], self.u + offset): - segment.add_line(**line) - self.detect = self.electrodes.create_frame() - segment = self.detect.create_segment() - for line in build_segment([100], -self.u + offset): - segment.add_line(**line) - self.electrodes.arm() - - @kernel - def one(self): - self.core.break_realtime() - delay(1*ms) - self.load.advance() - delay(1*ms) - self.detect.advance() - delay(1*ms) - self.pmt.gate_rising(100*us) - return self.pmt.count() - - def run(self): - self.core.reset() - offsets = np.arange(0, 3) - for o in offsets: - self.setup(o) - self.one() diff --git a/artiq/examples/master/repository/coredevice_examples/transport.py b/artiq/examples/master/repository/coredevice_examples/transport.py deleted file mode 100644 index 99e363c55..000000000 --- a/artiq/examples/master/repository/coredevice_examples/transport.py +++ /dev/null @@ -1,107 +0,0 @@ -# Copyright (C) 2014, 2015 Robert Jordens - -import numpy as np - -from artiq.experiment import * -from artiq.wavesynth.coefficients import SplineSource - - -class Transport(EnvExperiment): - """Transport""" - - def build(self): - self.setattr_device("core") - self.setattr_device("bd_sw") - self.setattr_device("pmt") - self.setattr_device("electrodes") - - self.setattr_argument("wait_at_stop", NumberValue(100*us)) - self.setattr_argument("speed", NumberValue(1/(10*us))) - self.setattr_argument("repeats", NumberValue(100, step=1, ndecimals=0)) - self.setattr_argument("bins", NumberValue(100, step=1, ndecimals=0)) - - t = np.linspace(0, 10, 101) # waveform time - u = 1 - np.cos(np.pi*t/t[-1]) - # 4 devices, 3 board each, 3 dacs each - u = np.arange(4*3*3)[:, None]*.1 + u - self.data = SplineSource(x=t, y=u) - - def calc_waveforms(self, stop): - scale = self.speed/(50*MHz) - self.electrodes.disarm() - self.tf = self.electrodes.create_frame() - self.data.extend_segment(self.tf.create_segment("to_stop"), - 0, stop, scale=scale) - # append the reverse transport (from stop to 0) - # both durations are the same in this case - self.data.extend_segment(self.tf.create_segment("from_stop"), - stop, 0, scale=scale) - # distributes frames to the sub-devices in CompoundPDQ2 - # and uploads them - self.electrodes.arm() - - @kernel - def cool(self): - self.bd_sw.pulse(1*ms) - - @kernel - def transport(self): - # selects transport frame - # triggers pdqs to start transport frame segment - # plays the transport waveform from 0 to stop - # delay()s the core by the duration of the waveform segment - self.tf.to_stop.advance() - # leaves the ion in the dark at the transport endpoint - delay(self.wait_at_stop) - # transport back (again: trigger, delay()) - # segments can only be advance()ed in order - # since this is the last segment, pdq will go back to jump table - self.tf.from_stop.advance() - - @kernel - def detect(self): - self.bd_sw.on() - self.pmt.gate_rising(100*us) - return self.pmt.count() - - @kernel - def one(self): - delay(1*ms) - self.cool() - self.transport() - return self.detect() - - @kernel - def repeat(self): - self.core.break_realtime() - hist = [0 for _ in range(self.bins)] - for i in range(self.repeats): - n = self.one() - if n >= self.bins: - n = self.bins - 1 - hist[n] += 1 - self.set_dataset("hist", hist) - - def scan(self, stops): - for s in stops: - # non-kernel, build frames - # could also be rpc'ed from repeat() - self.calc_waveforms(s) - # kernel part - self.repeat() - - def run(self): - self.core.reset() - # scan transport endpoint - stops = np.linspace(0, 10, 10) - self.scan(stops) - - -# class Benchmark(Transport): -# def build(self): -# Transport.build(self) -# self.calc_waveforms(.3) -# -# @kernel -# def run(self): -# self.repeat() diff --git a/artiq/frontend/aqctl_pdq.py b/artiq/frontend/aqctl_pdq.py deleted file mode 100755 index a59ae103c..000000000 --- a/artiq/frontend/aqctl_pdq.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import sys -import time - -from artiq.devices.pdq.driver import PDQ -from artiq.protocols.pc_rpc import simple_server_loop -from artiq.tools import * - - -def get_argparser(): - parser = argparse.ArgumentParser(description="""PDQ controller. - - Use this controller for PDQ stacks that are connected via USB.""") - simple_network_args(parser, 3252) - parser.add_argument("-d", "--device", default=None, help="serial port") - parser.add_argument("--simulation", action="store_true", - help="do not open any device but dump data") - parser.add_argument("--dump", default="pdq_dump.bin", - help="file to dump simulation data into") - parser.add_argument("-r", "--reset", default=False, - action="store_true", help="reset device [%(default)s]") - parser.add_argument("-b", "--boards", default=3, type=int, - help="number of boards [%(default)s]") - verbosity_args(parser) - return parser - - -def main(): - args = get_argparser().parse_args() - init_logger(args) - port = None - - if not args.simulation and args.device is None: - print("You need to specify either --simulation or -d/--device " - "argument. Use --help for more information.") - sys.exit(1) - - if args.simulation: - port = open(args.dump, "wb") - dev = PDQ(url=args.device, dev=port, num_boards=args.boards) - try: - if args.reset: - dev.write(b"") # flush eop - dev.set_config(reset=True) - time.sleep(.1) - - dev.set_crc(0) - dev.checksum = 0 - - simple_server_loop({"pdq": dev}, bind_address_from_args(args), - args.port, description="device=" + str(args.device)) - finally: - dev.close() - - -if __name__ == "__main__": - main() diff --git a/artiq/test/test_pdq.py b/artiq/test/test_pdq.py deleted file mode 100644 index 76ab6d573..000000000 --- a/artiq/test/test_pdq.py +++ /dev/null @@ -1,130 +0,0 @@ -# Copyright (C) 2014, 2015 Robert Jordens - -import unittest -import os -import io - -from artiq.devices.pdq.driver import PDQ -from artiq.wavesynth.compute_samples import Synthesizer - - -pdq_gateware = os.getenv("ARTIQ_PDQ_GATEWARE") - - -class TestPdq(unittest.TestCase): - def setUp(self): - self.dev = PDQ(dev=io.BytesIO()) - self.synth = Synthesizer(3, _test_program) - - def test_reset(self): - self.dev.set_config(reset=True) - buf = self.dev.dev.getvalue() - self.assertEqual(buf, b"\xa5\x02\xf8\xe5\xa5\x03") - - def test_program(self): - # about 0.14 ms - self.dev.program(_test_program) - - def test_cmd_program(self): - self.dev.set_config(enable=False) - self.dev.program(_test_program) - self.dev.set_config(enable=True, trigger=True) - return self.dev.dev.getvalue() - - def test_synth(self): - s = self.synth - s.select(0) - y = s.trigger() - return list(zip(*y)) - - def run_gateware(self): - import sys - sys.path.append(pdq_gateware) - from gateware.pdq import PdqSim - from migen import run_simulation - - def ncycles(n): - for i in range(n): - yield - - buf = self.test_cmd_program() - tb = PdqSim() - tb.ctrl_pads.trigger.reset = 1 - run_simulation(tb, [ncycles(len(buf) + 250)]) - delays = 7, 10, 30 - y = list(zip(*tb.outputs[len(buf) + 130:])) - y = list(zip(*(yi[di:] for yi, di in zip(y, delays)))) - self.assertGreaterEqual(len(y), 80) - self.assertEqual(len(y[0]), 3) - return y - - @unittest.skipUnless(pdq_gateware, "no PDQ gateware") - def test_run_compare(self): - y_ref = self.test_synth() - y = self.run_gateware() - - for i, (yi, yi_ref) in enumerate(zip(y, y_ref)): - for j, (yij, yij_ref) in enumerate(zip(yi, yi_ref)): - yij = yij*20./2**16 - if yij > 10: - yij -= 20 - self.assertAlmostEqual(yij, yij_ref, 2, "disagreement at " - "t={}, c={}".format(i, j)) - - @unittest.skipUnless(pdq_gateware, "no PDQ gateware") - @unittest.skip("manual/visual test") - def test_run_plot(self): - from matplotlib import pyplot as plt - import numpy as np - y_ref = self.test_synth() - y_ref = np.array(y_ref) - y = self.run_gateware() - y = np.array(y, dtype=np.uint16).view(np.int16) - y = y*20./2**16 - plt.step(np.arange(len(y)), y) - plt.step(np.arange(len(y_ref)), y_ref, "k") - plt.show() - - -_test_program = [ - [ - { - "trigger": True, - "duration": 20, - "channel_data": [ - {"bias": {"amplitude": [0, 0, 2e-3]}}, - {"bias": {"amplitude": [1, 0, -7.5e-3, 7.5e-4]}}, - {"dds": { - "amplitude": [0, 0, 4e-3, 0], - "phase": [.25, .025], - }}, - ], - }, - { - "duration": 40, - "channel_data": [ - {"bias": {"amplitude": [.4, .04, -2e-3]}}, - { - "bias": {"amplitude": [.5]}, - "silence": True, - }, - {"dds": { - "amplitude": [.8, .08, -4e-3, 0], - "phase": [.25, .025, .02/40], - "clear": True, - }}, - ], - }, - { - "duration": 20, - "channel_data": [ - {"bias": {"amplitude": [.4, -.04, 2e-3]}}, - {"bias": {"amplitude": [.5, 0, -7.5e-3, 7.5e-4]}}, - {"dds": { - "amplitude": [.8, -.08, 4e-3, 0], - "phase": [-.25], - }}, - ], - }, - ] -] diff --git a/doc/manual/core_drivers_reference.rst b/doc/manual/core_drivers_reference.rst index bbd0e39c6..6def86ae8 100644 --- a/doc/manual/core_drivers_reference.rst +++ b/doc/manual/core_drivers_reference.rst @@ -68,9 +68,3 @@ These drivers are for the core device and the peripherals closely integrated int .. automodule:: artiq.coredevice.sawg :members: - -:mod:`artiq.coredevice.pdq` module ------------------------------------ - -.. automodule:: artiq.coredevice.pdq - :members: diff --git a/doc/manual/default_network_ports.rst b/doc/manual/default_network_ports.rst index a1d83dd07..fb3da836d 100644 --- a/doc/manual/default_network_ports.rst +++ b/doc/manual/default_network_ports.rst @@ -26,7 +26,7 @@ Default network ports +--------------------------------+--------------+ | Master (control) | 3251 | +--------------------------------+--------------+ -| PDQ2 | 3252 | +| PDQ2 (out-of-tree) | 3252 | +--------------------------------+--------------+ | LDA | 3253 | +--------------------------------+--------------+ diff --git a/doc/manual/ndsp_reference.rst b/doc/manual/ndsp_reference.rst index d1e4176f2..721c15abe 100644 --- a/doc/manual/ndsp_reference.rst +++ b/doc/manual/ndsp_reference.rst @@ -8,35 +8,6 @@ Core device logging controller :ref: artiq.frontend.aqctl_corelog.get_argparser :prog: aqctl_corelog -PDQ2 ----- - -Protocol -++++++++ - -.. automodule:: artiq.devices.pdq.protocol - :members: - -Driver -++++++ - -.. automodule:: artiq.devices.pdq.driver - :members: - -Mediator -++++++++ - -.. automodule:: artiq.devices.pdq.mediator - :members: - -Controller -++++++++++ - -.. argparse:: - :ref: artiq.frontend.aqctl_pdq.get_argparser - :prog: aqctl_pdq - - Lab Brick Digital Attenuator (LDA) ---------------------------------- diff --git a/setup.py b/setup.py index 53621c1cf..ae3c59cdc 100755 --- a/setup.py +++ b/setup.py @@ -40,7 +40,6 @@ console_scripts = [ "aqctl_korad_ka3005p = artiq.frontend.aqctl_korad_ka3005p:main", "aqctl_lda = artiq.frontend.aqctl_lda:main", "aqctl_novatech409b = artiq.frontend.aqctl_novatech409b:main", - "aqctl_pdq = artiq.frontend.aqctl_pdq:main", "aqctl_thorlabs_tcube = artiq.frontend.aqctl_thorlabs_tcube:main", ]