forked from M-Labs/artiq
pdq: get new host driver, adapt
This commit is contained in:
parent
2895448477
commit
2458da1ade
|
@ -0,0 +1 @@
|
||||||
|
from artiq.devices.pdq.mediator import *
|
|
@ -1,4 +1,19 @@
|
||||||
# Copyright (C) 2012-2015 Robert Jordens <jordens@gmail.com>
|
# Copyright 2013-2017 Robert Jordens <jordens@gmail.com>
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from math import log, sqrt
|
from math import log, sqrt
|
||||||
import logging
|
import logging
|
||||||
|
@ -12,6 +27,65 @@ from artiq.wavesynth.coefficients import discrete_compensate
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def discrete_compensate(c):
|
||||||
|
"""Compensate spline coefficients for discrete accumulators.
|
||||||
|
|
||||||
|
Given continuous-time b-spline coefficients, this function
|
||||||
|
compensates for the effect of discrete time steps in the
|
||||||
|
target devices.
|
||||||
|
|
||||||
|
The compensation is performed in-place.
|
||||||
|
"""
|
||||||
|
l = len(c)
|
||||||
|
if l > 2:
|
||||||
|
c[1] += c[2]/2.
|
||||||
|
if l > 3:
|
||||||
|
c[1] += c[3]/6.
|
||||||
|
c[2] += c[3]
|
||||||
|
if l > 4:
|
||||||
|
raise ValueError("Only splines up to cubic order are supported.")
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
crc8 = CRC(0x107)
|
||||||
|
|
||||||
|
|
||||||
class Segment:
|
class Segment:
|
||||||
"""Serialize the lines for a single Segment.
|
"""Serialize the lines for a single Segment.
|
||||||
|
|
||||||
|
@ -49,6 +123,8 @@ class Segment:
|
||||||
this line.
|
this line.
|
||||||
silence (bool): Disable DAC clocks for the duration of this line.
|
silence (bool): Disable DAC clocks for the duration of this line.
|
||||||
aux (bool): Assert the AUX (F5 TTL) output during 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.
|
shift (int): Duration and spline evolution exponent.
|
||||||
jump (bool): Return to the frame address table after this line.
|
jump (bool): Return to the frame address table after this line.
|
||||||
clear (bool): Clear the DDS phase accumulator when starting to
|
clear (bool): Clear the DDS phase accumulator when starting to
|
||||||
|
@ -134,17 +210,16 @@ class Segment:
|
||||||
|
|
||||||
|
|
||||||
class Channel:
|
class Channel:
|
||||||
"""PDQ2 Channel.
|
"""PDQ Channel.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
num_frames (int): Number of frames supported.
|
num_frames (int): Number of frames supported.
|
||||||
max_data (int): Number of 16 bit data words per channel.
|
max_data (int): Number of 16 bit data words per channel.
|
||||||
segments (list[Segment]): Segments added to this channel.
|
segments (list[Segment]): Segments added to this channel.
|
||||||
"""
|
"""
|
||||||
num_frames = 8
|
def __init__(self, max_data, num_frames):
|
||||||
max_data = 4*(1 << 10) # 8kx16 8kx16 4kx16
|
self.max_data = max_data
|
||||||
|
self.num_frames = num_frames
|
||||||
def __init__(self):
|
|
||||||
self.segments = []
|
self.segments = []
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
|
@ -220,38 +295,39 @@ class Channel:
|
||||||
return self.table(entry) + data
|
return self.table(entry) + data
|
||||||
|
|
||||||
|
|
||||||
class Pdq2:
|
class PdqBase:
|
||||||
"""
|
"""
|
||||||
PDQ stack.
|
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:
|
Attributes:
|
||||||
num_dacs (int): Number of DAC outputs per board.
|
checksum (int): Running checksum of data written.
|
||||||
num_channels (int): Number of channels in this stack.
|
num_channels (int): Number of channels in this stack.
|
||||||
num_boards (int): Number of boards 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.
|
channels (list[Channel]): List of :class:`Channel` in this stack.
|
||||||
"""
|
"""
|
||||||
num_dacs = 3
|
|
||||||
freq = 50e6
|
freq = 50e6
|
||||||
|
|
||||||
_escape = b"\xa5"
|
_mem_sizes = [None, (20,), (10, 10), (8, 6, 6)] # 10kx16 units
|
||||||
_commands = "RESET TRIGGER ARM DCM START".split()
|
|
||||||
|
|
||||||
def __init__(self, url=None, dev=None, num_boards=3):
|
def __init__(self, num_boards=3, num_dacs=3, num_frames=32):
|
||||||
if dev is None:
|
"""Initialize PDQ stack.
|
||||||
dev = serial.serial_for_url(url)
|
|
||||||
self.dev = dev
|
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_boards = num_boards
|
||||||
|
self.num_dacs = num_dacs
|
||||||
|
self.num_frames = num_frames
|
||||||
self.num_channels = self.num_dacs * self.num_boards
|
self.num_channels = self.num_dacs * self.num_boards
|
||||||
self.channels = [Channel() for i in range(self.num_channels)]
|
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)]
|
||||||
|
|
||||||
def get_num_boards(self):
|
def get_num_boards(self):
|
||||||
return self.num_boards
|
return self.num_boards
|
||||||
|
@ -259,41 +335,71 @@ class Pdq2:
|
||||||
def get_num_channels(self):
|
def get_num_channels(self):
|
||||||
return self.num_channels
|
return self.num_channels
|
||||||
|
|
||||||
|
def get_num_frames(self):
|
||||||
|
return self.num_frames
|
||||||
|
|
||||||
def get_freq(self):
|
def get_freq(self):
|
||||||
return self.freq
|
return self.freq
|
||||||
|
|
||||||
def set_freq(self, freq):
|
def set_freq(self, freq):
|
||||||
self.freq = float(freq)
|
self.freq = float(freq)
|
||||||
|
|
||||||
def close(self):
|
def _cmd(self, board, is_mem, adr, we):
|
||||||
"""Close the USB device handle."""
|
return (adr << 0) | (is_mem << 2) | (board << 3) | (we << 7)
|
||||||
self.dev.close()
|
|
||||||
del self.dev
|
|
||||||
|
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
"""Write data to the PDQ2 board.
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def write_reg(self, board, adr, data):
|
||||||
|
"""Write to a configuration register.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data (bytes): Data to write.
|
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)
|
||||||
"""
|
"""
|
||||||
logger.debug("> %r", data)
|
self.write(struct.pack(
|
||||||
written = self.dev.write(data)
|
"<BB", self._cmd(board, False, adr, True), data))
|
||||||
if isinstance(written, int):
|
|
||||||
assert written == len(data)
|
|
||||||
|
|
||||||
def cmd(self, cmd, enable):
|
def set_config(self, reset=False, clk2x=False, enable=True,
|
||||||
"""Execute a command.
|
trigger=False, aux_miso=False, aux_dac=0b111, board=0xf):
|
||||||
|
"""Set the configuration register.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
cmd (str): Command to execute. One of (``RESET``, ``TRIGGER``,
|
reset (bool): Reset the board. Memory is not reset. Self-clearing.
|
||||||
``ARM``, ``DCM``, ``START``).
|
clk2x (bool): Enable the clock multiplier (100 MHz instead of 50
|
||||||
enable (bool): Enable (``True``) or disable (``False``) the
|
MHz)
|
||||||
feature.
|
enable (bool): Enable the channel data parsers and spline
|
||||||
|
interpolators.
|
||||||
|
trigger (bool): Soft trigger. Logical or with the hardware trigger.
|
||||||
|
aux_miso (bool): Drive SPI MISO on the AUX/F5 ttl port of each
|
||||||
|
board. If `False`, drive the masked logical or of the DAC
|
||||||
|
channels' aux data.
|
||||||
|
aux_dac (int): 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.
|
||||||
"""
|
"""
|
||||||
cmd = self._commands.index(cmd) << 1
|
self.write_reg(board, 0, (reset << 0) | (clk2x << 1) | (enable << 2) |
|
||||||
if not enable:
|
(trigger << 3) | (aux_miso << 4) | (aux_dac << 5))
|
||||||
cmd |= 1
|
|
||||||
self.write(struct.pack("cb", self._escape, cmd))
|
def set_checksum(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.write_reg(board, 1, crc)
|
||||||
|
|
||||||
|
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.write_reg(board, 2, frame)
|
||||||
|
|
||||||
def write_mem(self, channel, data, start_addr=0):
|
def write_mem(self, channel, data, start_addr=0):
|
||||||
"""Write to channel memory.
|
"""Write to channel memory.
|
||||||
|
@ -305,10 +411,8 @@ class Pdq2:
|
||||||
start_addr (int): Start address to write data to.
|
start_addr (int): Start address to write data to.
|
||||||
"""
|
"""
|
||||||
board, dac = divmod(channel, self.num_dacs)
|
board, dac = divmod(channel, self.num_dacs)
|
||||||
data = struct.pack("<HHH", (board << 4) | dac, start_addr,
|
self.write(struct.pack("<BH", self._cmd(board, True, dac, True),
|
||||||
start_addr + len(data)//2 - 1) + data
|
start_addr) + data)
|
||||||
data = data.replace(self._escape, self._escape + self._escape)
|
|
||||||
self.write(data)
|
|
||||||
|
|
||||||
def program_segments(self, segments, data):
|
def program_segments(self, segments, data):
|
||||||
"""Append the wavesynth lines to the given segments.
|
"""Append the wavesynth lines to the given segments.
|
||||||
|
@ -372,18 +476,83 @@ class Pdq2:
|
||||||
for channel, ch in zip(channels, chs):
|
for channel, ch in zip(channels, chs):
|
||||||
self.write_mem(channel, ch.serialize())
|
self.write_mem(channel, ch.serialize())
|
||||||
|
|
||||||
def flush(self):
|
def disable(self, **kwargs):
|
||||||
self.dev.flush()
|
"""Disable the device."""
|
||||||
|
self.set_config(enable=False, **kwargs)
|
||||||
def park(self):
|
|
||||||
self.cmd("START", False)
|
|
||||||
self.cmd("TRIGGER", True)
|
|
||||||
self.flush()
|
self.flush()
|
||||||
|
|
||||||
def unpark(self):
|
def enable(self, **kwargs):
|
||||||
self.cmd("TRIGGER", False)
|
"""Enable the device."""
|
||||||
self.cmd("START", True)
|
self.set_config(enable=True, **kwargs)
|
||||||
self.flush()
|
self.flush()
|
||||||
|
|
||||||
def ping(self):
|
def ping(self):
|
||||||
|
"""Ping method returning True. Required for ARTIQ remote
|
||||||
|
controller."""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class Pdq(PdqBase):
|
||||||
|
def __init__(self, url=None, dev=None, **kwargs):
|
||||||
|
"""Initialize PDQ USB/Parallel device 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.
|
||||||
|
**kwargs: See :class:`PdqBase` .
|
||||||
|
"""
|
||||||
|
if dev is None:
|
||||||
|
dev = serial.serial_for_url(url)
|
||||||
|
self.dev = dev
|
||||||
|
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.checksum = crc8(data, self.checksum)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""Close the USB device handle."""
|
||||||
|
self.dev.close()
|
||||||
|
del self.dev
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
"""Flush pending data."""
|
||||||
|
self.dev.flush()
|
||||||
|
|
||||||
|
|
||||||
|
class PdqSPI(PdqBase):
|
||||||
|
def __init__(self, dev=None, **kwargs):
|
||||||
|
"""Initialize PDQ SPI device stack."""
|
||||||
|
self.dev = dev
|
||||||
|
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)
|
||||||
|
written = self.dev.write(data)
|
||||||
|
if isinstance(written, int):
|
||||||
|
assert written == len(data), (written, len(data))
|
||||||
|
self.checksum = crc8(data, self.checksum)
|
|
@ -154,10 +154,10 @@ class _Frame:
|
||||||
self.pdq.next_segment = -1
|
self.pdq.next_segment = -1
|
||||||
|
|
||||||
|
|
||||||
class CompoundPDQ2:
|
class CompoundPDQ:
|
||||||
def __init__(self, dmgr, pdq2_devices, trigger_device, frame_devices):
|
def __init__(self, dmgr, pdq_devices, trigger_device, frame_devices):
|
||||||
self.core = dmgr.get("core")
|
self.core = dmgr.get("core")
|
||||||
self.pdq2s = [dmgr.get(d) for d in pdq2_devices]
|
self.pdqs = [dmgr.get(d) for d in pdq_devices]
|
||||||
self.trigger = dmgr.get(trigger_device)
|
self.trigger = dmgr.get(trigger_device)
|
||||||
self.frame0 = dmgr.get(frame_devices[0])
|
self.frame0 = dmgr.get(frame_devices[0])
|
||||||
self.frame1 = dmgr.get(frame_devices[1])
|
self.frame1 = dmgr.get(frame_devices[1])
|
||||||
|
@ -172,7 +172,7 @@ class CompoundPDQ2:
|
||||||
for frame in self.frames:
|
for frame in self.frames:
|
||||||
frame._invalidate()
|
frame._invalidate()
|
||||||
self.frames.clear()
|
self.frames.clear()
|
||||||
for dev in self.pdq2s:
|
for dev in self.pdqs:
|
||||||
dev.park()
|
dev.park()
|
||||||
self.armed = False
|
self.armed = False
|
||||||
|
|
||||||
|
@ -187,8 +187,8 @@ class CompoundPDQ2:
|
||||||
|
|
||||||
full_program = self.get_program()
|
full_program = self.get_program()
|
||||||
n = 0
|
n = 0
|
||||||
for pdq2 in self.pdq2s:
|
for pdq in self.pdqs:
|
||||||
dn = pdq2.get_num_channels()
|
dn = pdq.get_num_channels()
|
||||||
program = []
|
program = []
|
||||||
for full_frame_program in full_program:
|
for full_frame_program in full_program:
|
||||||
frame_program = []
|
frame_program = []
|
||||||
|
@ -201,10 +201,10 @@ class CompoundPDQ2:
|
||||||
}
|
}
|
||||||
frame_program.append(line)
|
frame_program.append(line)
|
||||||
program.append(frame_program)
|
program.append(frame_program)
|
||||||
pdq2.program(program)
|
pdq.program(program)
|
||||||
n += dn
|
n += dn
|
||||||
for pdq2 in self.pdq2s:
|
for pdq in self.pdqs:
|
||||||
pdq2.unpark()
|
pdq.unpark()
|
||||||
self.armed = True
|
self.armed = True
|
||||||
|
|
||||||
def create_frame(self):
|
def create_frame(self):
|
|
@ -1 +0,0 @@
|
||||||
from artiq.devices.pdq2.mediator import *
|
|
|
@ -172,30 +172,30 @@ device_db = {
|
||||||
# that it always resolves to a network-visible IP address (see documentation).
|
# that it always resolves to a network-visible IP address (see documentation).
|
||||||
"host": "::1",
|
"host": "::1",
|
||||||
"port": 4000,
|
"port": 4000,
|
||||||
"command": "aqctl_pdq2 -p {port} --bind {bind} --simulation --dump qc_q1_0.bin"
|
"command": "aqctl_pdq -p {port} --bind {bind} --simulation --dump qc_q1_0.bin"
|
||||||
},
|
},
|
||||||
"qc_q1_1": {
|
"qc_q1_1": {
|
||||||
"type": "controller",
|
"type": "controller",
|
||||||
"host": "::1",
|
"host": "::1",
|
||||||
"port": 4001,
|
"port": 4001,
|
||||||
"command": "aqctl_pdq2 -p {port} --bind {bind} --simulation --dump qc_q1_1.bin"
|
"command": "aqctl_pdq -p {port} --bind {bind} --simulation --dump qc_q1_1.bin"
|
||||||
},
|
},
|
||||||
"qc_q1_2": {
|
"qc_q1_2": {
|
||||||
"type": "controller",
|
"type": "controller",
|
||||||
"host": "::1",
|
"host": "::1",
|
||||||
"port": 4002,
|
"port": 4002,
|
||||||
"command": "aqctl_pdq2 -p {port} --bind {bind} --simulation --dump qc_q1_2.bin"
|
"command": "aqctl_pdq -p {port} --bind {bind} --simulation --dump qc_q1_2.bin"
|
||||||
},
|
},
|
||||||
"qc_q1_3": {
|
"qc_q1_3": {
|
||||||
"type": "controller",
|
"type": "controller",
|
||||||
"host": "::1",
|
"host": "::1",
|
||||||
"port": 4003,
|
"port": 4003,
|
||||||
"command": "aqctl_pdq2 -p {port} --bind {bind} --simulation --dump qc_q1_3.bin"
|
"command": "aqctl_pdq -p {port} --bind {bind} --simulation --dump qc_q1_3.bin"
|
||||||
},
|
},
|
||||||
"electrodes": {
|
"electrodes": {
|
||||||
"type": "local",
|
"type": "local",
|
||||||
"module": "artiq.devices.pdq2",
|
"module": "artiq.devices.pdq",
|
||||||
"class": "CompoundPDQ2",
|
"class": "CompoundPDQ",
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"pdq2_devices": ["qc_q1_0", "qc_q1_1", "qc_q1_2", "qc_q1_3"],
|
"pdq2_devices": ["qc_q1_0", "qc_q1_1", "qc_q1_2", "qc_q1_3"],
|
||||||
"trigger_device": "ttl2",
|
"trigger_device": "ttl2",
|
||||||
|
|
|
@ -4,18 +4,18 @@ import argparse
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from artiq.devices.pdq2.driver import Pdq2
|
from artiq.devices.pdq.driver import Pdq
|
||||||
from artiq.protocols.pc_rpc import simple_server_loop
|
from artiq.protocols.pc_rpc import simple_server_loop
|
||||||
from artiq.tools import *
|
from artiq.tools import *
|
||||||
|
|
||||||
|
|
||||||
def get_argparser():
|
def get_argparser():
|
||||||
parser = argparse.ArgumentParser(description="PDQ2 controller")
|
parser = argparse.ArgumentParser(description="PDQ controller")
|
||||||
simple_network_args(parser, 3252)
|
simple_network_args(parser, 3252)
|
||||||
parser.add_argument("-d", "--device", default=None, help="serial port")
|
parser.add_argument("-d", "--device", default=None, help="serial port")
|
||||||
parser.add_argument("--simulation", action="store_true",
|
parser.add_argument("--simulation", action="store_true",
|
||||||
help="do not open any device but dump data")
|
help="do not open any device but dump data")
|
||||||
parser.add_argument("--dump", default="pdq2_dump.bin",
|
parser.add_argument("--dump", default="pdq_dump.bin",
|
||||||
help="file to dump simulation data into")
|
help="file to dump simulation data into")
|
||||||
parser.add_argument("-r", "--reset", default=False,
|
parser.add_argument("-r", "--reset", default=False,
|
||||||
action="store_true", help="reset device [%(default)s]")
|
action="store_true", help="reset device [%(default)s]")
|
||||||
|
@ -37,16 +37,17 @@ def main():
|
||||||
|
|
||||||
if args.simulation:
|
if args.simulation:
|
||||||
port = open(args.dump, "wb")
|
port = open(args.dump, "wb")
|
||||||
dev = Pdq2(url=args.device, dev=port, num_boards=args.boards)
|
dev = Pdq(url=args.device, dev=port, num_boards=args.boards)
|
||||||
try:
|
try:
|
||||||
if args.reset:
|
if args.reset:
|
||||||
dev.write(b"\x00\x00") # flush any escape
|
dev.write(b"") # flush eop
|
||||||
dev.cmd("RESET", True)
|
dev.set_config(reset=True)
|
||||||
dev.flush()
|
|
||||||
time.sleep(.1)
|
time.sleep(.1)
|
||||||
dev.cmd("ARM", True)
|
|
||||||
dev.park()
|
dev.set_checksum(0)
|
||||||
simple_server_loop({"pdq2": dev}, bind_address_from_args(args),
|
dev.checksum = 0
|
||||||
|
|
||||||
|
simple_server_loop({"pdq": dev}, bind_address_from_args(args),
|
||||||
args.port, description="device=" + str(args.device))
|
args.port, description="device=" + str(args.device))
|
||||||
finally:
|
finally:
|
||||||
dev.close()
|
dev.close()
|
||||||
|
|
|
@ -4,34 +4,31 @@ import unittest
|
||||||
import os
|
import os
|
||||||
import io
|
import io
|
||||||
|
|
||||||
from artiq.devices.pdq2.driver import Pdq2
|
from artiq.devices.pdq.driver import Pdq
|
||||||
from artiq.wavesynth.compute_samples import Synthesizer
|
from artiq.wavesynth.compute_samples import Synthesizer
|
||||||
|
|
||||||
|
|
||||||
pdq2_gateware = os.getenv("ARTIQ_PDQ2_GATEWARE")
|
pdq_gateware = os.getenv("ARTIQ_PDQ_GATEWARE")
|
||||||
|
|
||||||
|
|
||||||
class TestPdq2(unittest.TestCase):
|
class TestPdq(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.dev = Pdq2(dev=io.BytesIO())
|
self.dev = Pdq(dev=io.BytesIO())
|
||||||
self.synth = Synthesizer(3, _test_program)
|
self.synth = Synthesizer(3, _test_program)
|
||||||
|
|
||||||
def test_reset(self):
|
def test_reset(self):
|
||||||
self.dev.cmd("RESET", True)
|
self.dev.set_config(reset=True)
|
||||||
buf = self.dev.dev.getvalue()
|
buf = self.dev.dev.getvalue()
|
||||||
self.assertEqual(buf, b"\xa5\x00")
|
self.assertEqual(buf, b"\xa5\x02\xf8\xe5\xa5\x03")
|
||||||
|
|
||||||
def test_program(self):
|
def test_program(self):
|
||||||
# about 0.14 ms
|
# about 0.14 ms
|
||||||
self.dev.program(_test_program)
|
self.dev.program(_test_program)
|
||||||
|
|
||||||
def test_cmd_program(self):
|
def test_cmd_program(self):
|
||||||
self.dev.cmd("ARM", False)
|
self.dev.set_config(enable=False)
|
||||||
self.dev.cmd("START", False)
|
|
||||||
self.dev.program(_test_program)
|
self.dev.program(_test_program)
|
||||||
self.dev.cmd("START", True)
|
self.dev.set_config(enable=True, trigger=True)
|
||||||
self.dev.cmd("ARM", True)
|
|
||||||
# self.dev.cmd("TRIGGER", True)
|
|
||||||
return self.dev.dev.getvalue()
|
return self.dev.dev.getvalue()
|
||||||
|
|
||||||
def test_synth(self):
|
def test_synth(self):
|
||||||
|
@ -42,12 +39,12 @@ class TestPdq2(unittest.TestCase):
|
||||||
|
|
||||||
def run_gateware(self):
|
def run_gateware(self):
|
||||||
import sys
|
import sys
|
||||||
sys.path.append(pdq2_gateware)
|
sys.path.append(pdq_gateware)
|
||||||
from gateware.pdq2 import Pdq2Sim
|
from gateware.pdq import PdqSim
|
||||||
from migen.sim.generic import run_simulation
|
from migen import run_simulation
|
||||||
|
|
||||||
buf = self.test_cmd_program()
|
buf = self.test_cmd_program()
|
||||||
tb = Pdq2Sim(buf)
|
tb = PdqSim()
|
||||||
tb.ctrl_pads.trigger.reset = 1
|
tb.ctrl_pads.trigger.reset = 1
|
||||||
run_simulation(tb, ncycles=len(buf) + 250)
|
run_simulation(tb, ncycles=len(buf) + 250)
|
||||||
delays = 7, 10, 30
|
delays = 7, 10, 30
|
||||||
|
@ -57,7 +54,7 @@ class TestPdq2(unittest.TestCase):
|
||||||
self.assertEqual(len(y[0]), 3)
|
self.assertEqual(len(y[0]), 3)
|
||||||
return y
|
return y
|
||||||
|
|
||||||
@unittest.skipUnless(pdq2_gateware, "no pdq2 gateware")
|
@unittest.skipUnless(pdq_gateware, "no PDQ gateware")
|
||||||
def test_run_compare(self):
|
def test_run_compare(self):
|
||||||
y_ref = self.test_synth()
|
y_ref = self.test_synth()
|
||||||
y = self.run_gateware()
|
y = self.run_gateware()
|
||||||
|
@ -70,7 +67,7 @@ class TestPdq2(unittest.TestCase):
|
||||||
self.assertAlmostEqual(yij, yij_ref, 2, "disagreement at "
|
self.assertAlmostEqual(yij, yij_ref, 2, "disagreement at "
|
||||||
"t={}, c={}".format(i, j))
|
"t={}, c={}".format(i, j))
|
||||||
|
|
||||||
@unittest.skipUnless(pdq2_gateware, "no pdq2 gateware")
|
@unittest.skipUnless(pdq_gateware, "no PDQ gateware")
|
||||||
@unittest.skip("manual/visual test")
|
@unittest.skip("manual/visual test")
|
||||||
def test_run_plot(self):
|
def test_run_plot(self):
|
||||||
from matplotlib import pyplot as plt
|
from matplotlib import pyplot as plt
|
2
setup.py
2
setup.py
|
@ -39,7 +39,7 @@ console_scripts = [
|
||||||
"aqctl_lda=artiq.frontend.aqctl_lda:main",
|
"aqctl_lda=artiq.frontend.aqctl_lda:main",
|
||||||
"aqctl_novatech409b=artiq.frontend.aqctl_novatech409b:main",
|
"aqctl_novatech409b=artiq.frontend.aqctl_novatech409b:main",
|
||||||
"aqctl_korad_ka3005p=artiq.frontend.aqctl_korad_ka3005p:main",
|
"aqctl_korad_ka3005p=artiq.frontend.aqctl_korad_ka3005p:main",
|
||||||
"aqctl_pdq2=artiq.frontend.aqctl_pdq2:main",
|
"aqctl_pdq=artiq.frontend.aqctl_pdq:main",
|
||||||
"aqctl_thorlabs_tcube=artiq.frontend.aqctl_thorlabs_tcube:main",
|
"aqctl_thorlabs_tcube=artiq.frontend.aqctl_thorlabs_tcube:main",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue