forked from M-Labs/artiq
1
0
Fork 0

Merge remote-tracking branch 'origin/master' into new-py2llvm

This commit is contained in:
whitequark 2015-07-23 21:36:17 +03:00
commit c581af29d7
178 changed files with 6187 additions and 3174 deletions

View File

@ -7,37 +7,37 @@ branches:
sudo: false
env:
global:
- MSCDIR=$TRAVIS_BUILD_DIR/misoc
- PATH=$HOME/miniconda/bin:/usr/local/llvm-or1k/bin:$PATH
- BUILD_SOC=1
- secure: "DUk/Ihg8KbbzEgPF0qrHqlxU8e8eET9i/BtzNvFddIGX4HP/P2qz0nk3cVkmjuWhqJXSbC22RdKME9qqPzw6fJwJ6dpJ3OR6dDmSd7rewavq+niwxu52PVa+yK8mL4yf1terM7QQ5tIRf+yUL9qGKrZ2xyvEuRit6d4cFep43Ws="
before_install:
- mkdir -p $HOME/.mlabs
- if [ $TRAVIS_PULL_REQUEST != false ]; then BUILD_SOC=0; fi
- . ./.travis/get-toolchain.sh
- if [ $BUILD_SOC -ne 0 ]; then ./.travis/get-xilinx.sh; fi
- ./.travis/get-anaconda.sh
- . ./.travis/get-toolchain.sh
- . ./.travis/get-anaconda.sh
- source $HOME/miniconda/bin/activate py34
- conda install pip coverage binstar migen cython
- conda install -q pip coverage binstar migen cython
- pip install coveralls
install:
- conda build conda/artiq
- conda install $HOME/miniconda/conda-bld/linux-64/artiq-*.tar.bz2
- conda install -q artiq --use-local
script:
- coverage run --source=artiq setup.py test
- make -C doc/manual html
after_success:
- binstar login --hostname $(hostname) --username $binstar_login --password $binstar_password
- binstar upload --user $binstar_login --channel dev --force $HOME/miniconda/conda-bld/linux-64/artiq-*.tar.bz2
- binstar -q login --hostname $(hostname) --username $binstar_login --password $binstar_password
- binstar -q upload --user $binstar_login --channel dev --force $HOME/miniconda/conda-bld/linux-64/artiq-*.tar.bz2
- binstar -q logout
- coveralls
notifications:
email: false
email:
recipients:
- rjordens@nist.gov
on_success: always
on_failure: never
irc:
channels:
- chat.freenode.net#m-labs
template:
- "%{repository}#%{build_number} (%{branch} - %{commit} : %{author}): %{message}"
- "Build details : %{build_url}"
webhooks:
urls:
- https://webhooks.gitter.im/e/d26782523952bfa53814

View File

@ -1,6 +1,7 @@
#!/bin/sh
wget http://repo.continuum.io/miniconda/Miniconda3-3.7.3-Linux-x86_64.sh -O miniconda.sh
export PATH=$HOME/miniconda/bin:$PATH
wget http://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh
bash miniconda.sh -b -p $HOME/miniconda
hash -r
conda config --set always_yes yes --set changeps1 no
@ -9,4 +10,4 @@ conda info -a
conda install conda-build jinja2
conda create -q -n py34 python=$TRAVIS_PYTHON_VERSION
conda config --add channels fallen
conda config --add channels https://conda.binstar.org/fallen/channel/dev
conda config --add channels https://conda.anaconda.org/fallen/channel/dev

View File

@ -24,12 +24,13 @@ echo "$secret" | gpg --passphrase-fd 0 Xilinx.lic.gpg
mkdir -p ~/.Xilinx
mv Xilinx.lic ~/.Xilinx/Xilinx.lic
# Tell mibuild where Vivado is installed
echo "MISOC_EXTRA_VIVADO_CMDLINE=\"-Ob vivado_path $HOME/Xilinx/Vivado\"" >> $HOME/.mlabs/build_settings.sh
echo "MISOC_EXTRA_ISE_CMDLINE=\"-Ob ise_path $HOME/opt/Xilinx/\"" >> $HOME/.mlabs/build_settings.sh
# Lie to Vivado by hooking the ioctl used to retrieve mac address for license verification
git clone https://github.com/fallen/impersonate_macaddress
make -C impersonate_macaddress
echo "export MACADDR=$macaddress" >> $HOME/.mlabs/build_settings.sh
echo "export LD_PRELOAD=$PWD/impersonate_macaddress/impersonate_macaddress.so" >> $HOME/.mlabs/build_settings.sh
# Tell mibuild where Xilinx toolchains are installed
# and feed it the mac address corresponding to the license
cat > $HOME/.mlabs/build_settings.sh << EOF
MISOC_EXTRA_VIVADO_CMDLINE="-Ob vivado_path $HOME/Xilinx/Vivado"
MISOC_EXTRA_ISE_CMDLINE="-Ob ise_path $HOME/opt/Xilinx/"
export MACADDR=$macaddress
export LD_PRELOAD=$PWD/impersonate_macaddress/impersonate_macaddress.so
EOF

View File

@ -1,6 +1,5 @@
from artiq.language.core import *
from artiq.language.experiment import Experiment
from artiq.language.db import *
from artiq.language.units import check_unit
from artiq.language.units import ps, ns, us, ms, s
from artiq.language.units import Hz, kHz, MHz, GHz
from artiq import language
from artiq.language import *
__all__ = []
__all__.extend(language.__all__)

View File

@ -1,21 +1,9 @@
from operator import itemgetter
from artiq.language.db import AutoDB
from artiq.language.units import ms
from artiq.coredevice.runtime import LinkInterface
class _RuntimeEnvironment(LinkInterface):
def __init__(self):
self.warmup_time = 1*ms
def emit_object(self):
return str(self.llvm_module)
class Comm(AutoDB):
def get_runtime_env(self):
return _RuntimeEnvironment()
class Comm:
def __init__(self, dmgr):
pass
def switch_clock(self, external):
pass

View File

@ -21,6 +21,11 @@ class _H2DMsgType(Enum):
RPC_REPLY = 6
FLASH_READ_REQUEST = 7
FLASH_WRITE_REQUEST = 8
FLASH_ERASE_REQUEST = 9
FLASH_REMOVE_REQUEST = 10
class _D2HMsgType(Enum):
LOG_REPLY = 1
@ -37,6 +42,10 @@ class _D2HMsgType(Enum):
RPC_REQUEST = 10
FLASH_READ_REPLY = 11
FLASH_OK_REPLY = 12
FLASH_ERROR_REPLY = 13
class UnsupportedDevice(Exception):
pass
@ -86,7 +95,12 @@ class CommGeneric:
def _write_header(self, length, ty):
self.open()
logger.debug("sending message: type=%r length=%d", ty, length)
self.write(struct.pack(">llB", 0x5a5a5a5a, length, ty.value))
self.write(struct.pack(">ll", 0x5a5a5a5a, length))
if ty is not None:
self.write(struct.pack("B", ty.value))
def reset_session(self):
self._write_header(0, None)
def check_ident(self):
self._write_header(9, _H2DMsgType.IDENT_REQUEST)
@ -116,12 +130,46 @@ class CommGeneric:
if ty != _D2HMsgType.LOAD_COMPLETED:
raise IOError("Incorrect reply from device: "+str(ty))
def run(self, kname, reset_now):
self._write_header(len(kname) + 10, _H2DMsgType.RUN_KERNEL)
self.write(struct.pack("B", reset_now))
def run(self, kname):
self._write_header(len(kname) + 9, _H2DMsgType.RUN_KERNEL)
self.write(bytes(kname, "ascii"))
logger.debug("running kernel: %s", kname)
def flash_storage_read(self, key):
self._write_header(9+len(key), _H2DMsgType.FLASH_READ_REQUEST)
self.write(key)
length, ty = self._read_header()
if ty != _D2HMsgType.FLASH_READ_REPLY:
raise IOError("Incorrect reply from device: {}".format(ty))
value = self.read(length - 9)
return value
def flash_storage_write(self, key, value):
self._write_header(9+len(key)+1+len(value),
_H2DMsgType.FLASH_WRITE_REQUEST)
self.write(key)
self.write(b"\x00")
self.write(value)
_, ty = self._read_header()
if ty != _D2HMsgType.FLASH_OK_REPLY:
if ty == _D2HMsgType.FLASH_ERROR_REPLY:
raise IOError("Flash storage is full")
else:
raise IOError("Incorrect reply from device: {}".format(ty))
def flash_storage_erase(self):
self._write_header(9, _H2DMsgType.FLASH_ERASE_REQUEST)
_, ty = self._read_header()
if ty != _D2HMsgType.FLASH_OK_REPLY:
raise IOError("Incorrect reply from device: {}".format(ty))
def flash_storage_remove(self, key):
self._write_header(9+len(key), _H2DMsgType.FLASH_REMOVE_REQUEST)
self.write(key)
_, ty = self._read_header()
if ty != _D2HMsgType.FLASH_OK_REPLY:
raise IOError("Incorrect reply from device: {}".format(ty))
def _receive_rpc_value(self, type_tag):
if type_tag == "n":
return None

View File

@ -3,22 +3,22 @@ import serial
import struct
from artiq.coredevice.comm_generic import CommGeneric
from artiq.language.db import *
logger = logging.getLogger(__name__)
class Comm(CommGeneric, AutoDB):
class DBKeys:
serial_dev = Argument()
baud_rate = Argument(115200)
class Comm(CommGeneric):
def __init__(self, dmgr, serial_dev, baud_rate=115200):
self.serial_dev = serial_dev
self.baud_rate = baud_rate
def open(self):
if hasattr(self, "port"):
return
self.port = serial.serial_for_url(self.serial_dev,
baudrate=self.baud_rate)
self.reset_session()
def close(self):
if not hasattr(self, "port"):

View File

@ -2,16 +2,15 @@ import logging
import socket
from artiq.coredevice.comm_generic import CommGeneric
from artiq.language.db import *
logger = logging.getLogger(__name__)
class Comm(CommGeneric, AutoDB):
class DBKeys:
host = Argument()
port = Argument(1381)
class Comm(CommGeneric):
def __init__(self, dmgr, host, port=1381):
self.host = host
self.port = port
def open(self):
if hasattr(self, "socket"):

View File

@ -1,11 +1,9 @@
import os
from artiq.language.core import *
from artiq.language.db import *
from artiq.language.units import ns
from artiq.transforms.inline import inline
from artiq.transforms.lower_units import lower_units
from artiq.transforms.quantize_time import quantize_time
from artiq.transforms.remove_inter_assigns import remove_inter_assigns
from artiq.transforms.fold_constants import fold_constants
@ -15,7 +13,7 @@ from artiq.transforms.interleave import interleave
from artiq.transforms.lower_time import lower_time
from artiq.transforms.unparse import unparse
from artiq.coredevice.runtime import Environment
from artiq.coredevice.runtime import Runtime
from artiq.py2llvm import get_runtime_binary
@ -47,27 +45,23 @@ def _no_debug_unparse(label, node):
pass
class Core(AutoDB):
class DBKeys:
comm = Device()
ref_period = Argument(8*ns)
external_clock = Argument(False)
class Core:
def __init__(self, dmgr, ref_period=8*ns, external_clock=False):
self.comm = dmgr.get("comm")
self.ref_period = ref_period
self.external_clock = external_clock
def build(self):
self.first_run = True
self.core = self
self.comm.core = self
self.runtime_env = Environment()
self.runtime = Runtime()
def transform_stack(self, func_def, rpc_map, exception_map,
debug_unparse=_no_debug_unparse):
lower_units(func_def, rpc_map)
debug_unparse("lower_units", func_def)
remove_inter_assigns(func_def)
debug_unparse("remove_inter_assigns_1", func_def)
quantize_time(func_def, self.ref_period.amount)
quantize_time(func_def, self.ref_period)
debug_unparse("quantize_time", func_def)
fold_constants(func_def)
@ -108,7 +102,7 @@ class Core(AutoDB):
debug_unparse("inline", func_def)
self.transform_stack(func_def, rpc_map, exception_map, debug_unparse)
binary = get_runtime_binary(self.runtime_env, func_def)
binary = get_runtime_binary(self.runtime, func_def)
return binary, rpc_map, exception_map
@ -120,15 +114,14 @@ class Core(AutoDB):
binary, rpc_map, exception_map = self.compile(
k_function, k_args, k_kwargs)
self.comm.load(binary)
self.comm.run(k_function.__name__, self.first_run)
self.comm.run(k_function.__name__)
self.comm.serve(rpc_map, exception_map)
self.first_run = False
@kernel
def get_rtio_time(self):
return cycles_to_time(syscall("rtio_get_counter"))
def get_rtio_counter_mu(self):
return syscall("rtio_get_counter")
@kernel
def break_realtime(self):
t = syscall("rtio_get_counter") + 125000
at(cycles_to_time(t))
at_mu(syscall("rtio_get_counter") + 125000)

View File

@ -1,9 +1,8 @@
from artiq.language.core import *
from artiq.language.db import *
from artiq.language.units import *
PHASE_MODE_DEFAULT = -1
_PHASE_MODE_DEFAULT = -1
# keep in sync with dds.h
PHASE_MODE_CONTINUOUS = 0
PHASE_MODE_ABSOLUTE = 1
@ -23,21 +22,19 @@ class _BatchContextManager:
self.dds_bus.batch_exit()
class DDSBus(AutoDB):
class DDSBus:
"""Core device Direct Digital Synthesis (DDS) bus batching driver.
Manages batching of DDS commands on a DDS shared bus."""
class DBKeys:
core = Device()
def build(self):
def __init__(self, dmgr):
self.core = dmgr.get("core")
self.batch = _BatchContextManager(self)
@kernel
def batch_enter(self):
"""Starts a DDS command batch. All DDS commands are buffered
after this call, until ``batch_exit`` is called."""
syscall("dds_batch_enter", time_to_cycles(now()))
syscall("dds_batch_enter", now_mu())
@kernel
def batch_exit(self):
@ -46,21 +43,21 @@ class DDSBus(AutoDB):
syscall("dds_batch_exit")
class DDS(AutoDB):
class _DDSGeneric:
"""Core device Direct Digital Synthesis (DDS) driver.
Controls one DDS channel managed directly by the core device's runtime.
:param dds_sysclk: DDS system frequency, used for computing the frequency
tuning words.
This class should not be used directly, instead, use the chip-specific
drivers such as ``AD9858`` and ``AD9914``.
:param sysclk: DDS system frequency.
:param channel: channel number of the DDS device to control.
"""
class DBKeys:
core = Device()
dds_sysclk = Argument(1*GHz)
channel = Argument()
def build(self):
def __init__(self, dmgr, sysclk, channel):
self.core = dmgr.get("core")
self.sysclk = sysclk
self.channel = channel
self.phase_mode = PHASE_MODE_CONTINUOUS
@portable
@ -68,21 +65,33 @@ class DDS(AutoDB):
"""Returns the frequency tuning word corresponding to the given
frequency.
"""
return round(2**32*frequency/self.dds_sysclk)
return round(2**32*frequency/self.sysclk)
@portable
def ftw_to_frequency(self, ftw):
"""Returns the frequency corresponding to the given frequency tuning
word.
"""
return ftw*self.dds_sysclk/2**32
return ftw*self.sysclk/2**32
@portable
def turns_to_pow(self, turns):
"""Returns the phase offset word corresponding to the given phase
in turns."""
return round(turns*2**self.pow_width)
@portable
def pow_to_turns(self, pow):
"""Returns the phase in turns corresponding to the given phase offset
word."""
return pow/2**self.pow_width
@kernel
def init(self):
"""Resets and initializes the DDS channel.
The runtime does this for all channels upon core device startup."""
syscall("dds_init", time_to_cycles(now()), self.channel)
syscall("dds_init", now_mu(), self.channel)
@kernel
def set_phase_mode(self, phase_mode):
@ -105,17 +114,37 @@ class DDS(AutoDB):
self.phase_mode = phase_mode
@kernel
def set(self, frequency, phase_mode=PHASE_MODE_DEFAULT, phase_offset=0):
def set_mu(self, frequency, phase=0, phase_mode=_PHASE_MODE_DEFAULT):
"""Sets the DDS channel to the specified frequency and phase.
This uses machine units (FTW and POW). The frequency tuning word width
is 32, whereas the phase offset word width depends on the type of DDS
chip and can be retrieved via the ``pow_width`` attribute.
:param frequency: frequency to generate.
:param phase: adds an offset, in turns, to the phase.
:param phase_mode: if specified, overrides the default phase mode set
by ``set_phase_mode`` for this call.
:param phase_offset: adds an offset, in turns, to the phase.
"""
if phase_mode == PHASE_MODE_DEFAULT:
if phase_mode == _PHASE_MODE_DEFAULT:
phase_mode = self.phase_mode
syscall("dds_set", now_mu(), self.channel,
frequency, round(phase*2**self.pow_width), phase_mode)
syscall("dds_set", time_to_cycles(now()), self.channel,
self.frequency_to_ftw(frequency), round(phase_offset*2**14),
self.phase_mode)
@kernel
def set(self, frequency, phase=0, phase_mode=_PHASE_MODE_DEFAULT):
"""Like ``set_mu``, but uses Hz and turns."""
self.set_mu(self.frequency_to_ftw(frequency),
self.turns_to_pow(phase), phase_mode)
class AD9858(_DDSGeneric):
"""Driver for AD9858 DDS chips. See ``_DDSGeneric`` for a description
of the functionality."""
pow_width = 14
class AD9914(_DDSGeneric):
"""Driver for AD9914 DDS chips. See ``_DDSGeneric`` for a description
of the functionality."""
pow_width = 16

View File

@ -1,7 +1,7 @@
import os
import llvmlite.ir as ll
import llvmlite.binding as llvm
import llvmlite_or1k.ir as ll
import llvmlite_or1k.binding as llvm
from artiq.py2llvm import base_types, fractions, lists
from artiq.language import units
@ -21,6 +21,7 @@ _syscalls = {
"ttl_set_oe": "Iib:n",
"ttl_set_sensitivity": "Iii:n",
"ttl_get": "iI:I",
"ttl_clock_set": "Iii:n",
"dds_init": "Ii:n",
"dds_batch_enter": "I:n",
"dds_batch_exit": "n:n",
@ -195,7 +196,7 @@ def _debug_dump_obj(obj):
raise IOError
class Environment(LinkInterface):
class Runtime(LinkInterface):
def __init__(self):
self.cpu_type = "or1k"
# allow 1ms for all initial DDS programming
@ -208,4 +209,4 @@ class Environment(LinkInterface):
return obj
def __repr__(self):
return "<Environment {}>".format(self.cpu_type)
return "<Runtime {}>".format(self.cpu_type)

View File

@ -1,49 +1,7 @@
from artiq.language.core import *
from artiq.language.db import *
class LLTTLOut(AutoDB):
"""Low-level RTIO TTL output driver.
Allows setting RTIO TTL outputs at arbitrary times, without time
unit conversion.
This is meant to be used mostly in drivers; consider using
``TTLOut`` instead.
This should be used with output-only channels.
"""
class DBKeys:
core = Device()
channel = Argument()
@kernel
def set_o(self, t, value):
"""Sets the output value of the RTIO channel.
:param t: timestamp in RTIO cycles (64-bit integer).
:param value: value to set at the output.
"""
syscall("ttl_set_o", t, self.channel, value)
@kernel
def on(self, t):
"""Turns the RTIO channel on.
:param t: timestamp in RTIO cycles (64-bit integer).
"""
self.set_o(t, True)
@kernel
def off(self, t):
"""Turns the RTIO channel off.
:param t: timestamp in RTIO cycles (64-bit integer).
"""
self.set_o(t, False)
class TTLOut(AutoDB):
class TTLOut:
"""RTIO TTL output driver.
This should be used with output-only channels.
@ -51,45 +9,53 @@ class TTLOut(AutoDB):
:param core: core device
:param channel: channel number
"""
class DBKeys:
core = Device()
channel = Argument()
def __init__(self, dmgr, channel):
self.core = dmgr.get("core")
self.channel = channel
def build(self):
# in RTIO cycles
self.o_previous_timestamp = int64(0)
@kernel
def _set_o(self, o):
syscall("ttl_set_o", time_to_cycles(now()), self.channel, o)
self.o_previous_timestamp = time_to_cycles(now())
def set_o(self, o):
syscall("ttl_set_o", now_mu(), self.channel, o)
self.o_previous_timestamp = now_mu()
@kernel
def sync(self):
"""Busy-waits until all programmed level switches have been effected."""
"""Busy-wait until all programmed level switches have been
effected."""
while syscall("rtio_get_counter") < self.o_previous_timestamp:
pass
@kernel
def on(self):
"""Sets the output to a logic high state."""
self._set_o(True)
self.set_o(True)
@kernel
def off(self):
"""Sets the output to a logic low state."""
self._set_o(False)
"""Set the output to a logic low state."""
self.set_o(False)
@kernel
def pulse_mu(self, duration):
"""Pulse the output high for the specified duration
(in machine units)."""
self.on()
delay_mu(duration)
self.off()
@kernel
def pulse(self, duration):
"""Pulses the output high for the specified duration."""
"""Pulse the output high for the specified duration
(in seconds)."""
self.on()
delay(duration)
self.off()
class TTLInOut(AutoDB):
class TTLInOut:
"""RTIO TTL input/output driver.
In output mode, provides functions to set the logic level on the signal.
@ -107,78 +73,113 @@ class TTLInOut(AutoDB):
:param core: core device
:param channel: channel number
"""
class DBKeys:
core = Device()
channel = Argument()
def __init__(self, dmgr, channel):
self.core = dmgr.get("core")
self.channel = channel
def build(self):
# in RTIO cycles
self.o_previous_timestamp = int64(0)
self.i_previous_timestamp = int64(0)
@kernel
def _set_oe(self, oe):
syscall("ttl_set_oe", time_to_cycles(now()), self.channel, oe)
def set_oe(self, oe):
syscall("ttl_set_oe", now_mu(), self.channel, oe)
@kernel
def output(self):
self._set_oe(True)
self.set_oe(True)
@kernel
def input(self):
self._set_oe(False)
self.set_oe(False)
@kernel
def _set_o(self, o):
syscall("ttl_set_o", time_to_cycles(now()), self.channel, o)
self.o_previous_timestamp = time_to_cycles(now())
def set_o(self, o):
syscall("ttl_set_o", now_mu(), self.channel, o)
self.o_previous_timestamp = now_mu()
@kernel
def sync(self):
"""Busy-waits until all programmed level switches have been effected."""
"""Busy-wait until all programmed level switches have been
effected."""
while syscall("rtio_get_counter") < self.o_previous_timestamp:
pass
@kernel
def on(self):
"""Sets the output to a logic high state."""
self._set_o(True)
"""Set the output to a logic high state."""
self.set_o(True)
@kernel
def off(self):
"""Sets the output to a logic low state."""
self._set_o(False)
"""Set the output to a logic low state."""
self.set_o(False)
@kernel
def pulse_mu(self, duration):
"""Pulses the output high for the specified duration
(in machine units)."""
self.on()
delay_mu(duration)
self.off()
@kernel
def pulse(self, duration):
"""Pulses the output high for the specified duration."""
"""Pulses the output high for the specified duration
(in seconds)."""
self.on()
delay(duration)
self.off()
@kernel
def _set_sensitivity(self, value):
syscall("ttl_set_sensitivity", time_to_cycles(now()), self.channel, value)
self.i_previous_timestamp = time_to_cycles(now())
syscall("ttl_set_sensitivity", now_mu(), self.channel, value)
self.i_previous_timestamp = now_mu()
@kernel
def gate_rising_mu(self, duration):
"""Register rising edge events for the specified duration
(in machine units)."""
self._set_sensitivity(1)
delay_mu(duration)
self._set_sensitivity(0)
@kernel
def gate_falling_mu(self, duration):
"""Register falling edge events for the specified duration
(in machine units)."""
self._set_sensitivity(2)
delay_mu(duration)
self._set_sensitivity(0)
@kernel
def gate_both_mu(self, duration):
"""Register both rising and falling edge events for the specified
duration (in machine units)."""
self._set_sensitivity(3)
delay_mu(duration)
self._set_sensitivity(0)
@kernel
def gate_rising(self, duration):
"""Register rising edge events for the specified duration."""
"""Register rising edge events for the specified duration
(in seconds)."""
self._set_sensitivity(1)
delay(duration)
self._set_sensitivity(0)
@kernel
def gate_falling(self, duration):
"""Register falling edge events for the specified duration."""
"""Register falling edge events for the specified duration
(in seconds)."""
self._set_sensitivity(2)
delay(duration)
self._set_sensitivity(0)
@kernel
def gate_both(self, duration):
def gate_both_mu(self, duration):
"""Register both rising and falling edge events for the specified
duration."""
duration (in seconds)."""
self._set_sensitivity(3)
delay(duration)
self._set_sensitivity(0)
@ -194,11 +195,81 @@ class TTLInOut(AutoDB):
return count
@kernel
def timestamp(self):
def timestamp_mu(self):
"""Poll the RTIO input and returns an event timestamp, according to
the gating.
If the gate is permanently closed, returns a negative value.
"""
return cycles_to_time(syscall("ttl_get", self.channel,
self.i_previous_timestamp))
return syscall("ttl_get", self.channel, self.i_previous_timestamp)
class TTLClockGen:
"""RTIO TTL clock generator driver.
This should be used with TTL channels that have a clock generator
built into the gateware (not compatible with regular TTL channels).
:param core: core device
:param channel: channel number
"""
def __init__(self, dmgr, channel):
self.core = dmgr.get("core")
self.channel = channel
def build(self):
# in RTIO cycles
self.previous_timestamp = int64(0)
self.acc_width = 24
@portable
def frequency_to_ftw(self, frequency):
"""Returns the frequency tuning word corresponding to the given
frequency.
"""
return round(2**self.acc_width*frequency*self.core.ref_period)
@portable
def ftw_to_frequency(self, ftw):
"""Returns the frequency corresponding to the given frequency tuning
word.
"""
return ftw/self.core.ref_period/2**self.acc_width
@kernel
def set_mu(self, frequency):
"""Set the frequency of the clock, in machine units.
This also sets the phase, as the time of the first generated rising
edge corresponds to the time of the call.
The clock generator contains a 24-bit phase accumulator operating on
the RTIO clock. At each RTIO clock tick, the frequency tuning word is
added to the phase accumulator. The most significant bit of the phase
accumulator is connected to the TTL line. Setting the frequency tuning
word has the additional effect of setting the phase accumulator to
0x800000.
Due to the way the clock generator operates, frequency tuning words
that are not powers of two cause jitter of one RTIO clock cycle at the
output.
"""
syscall("ttl_clock_set", now_mu(), self.channel, frequency)
self.previous_timestamp = now_mu()
@kernel
def set(self, frequency):
"""Like ``set_mu``, but using Hz."""
self.set_mu(self.frequency_to_ftw(frequency))
@kernel
def stop(self):
"""Stop the toggling of the clock and set the output level to 0."""
self.set_mu(0)
@kernel
def sync(self):
"""Busy-wait until all programmed frequency switches and stops have
been effected."""
while syscall("rtio_get_counter") < self.o_previous_timestamp:
pass

View File

@ -2,7 +2,7 @@ import logging
import ctypes
import struct
from artiq.language.units import dB, check_unit, Quantity
from artiq.language.units import dB
logger = logging.getLogger("lda")
@ -47,14 +47,7 @@ class Ldasim:
"""
step = self.get_att_step_size()
if isinstance(attenuation, Quantity):
check_unit(attenuation, "dB")
att = attenuation
else:
att = attenuation*dB
att = round(att/step)*step
att = round(attenuation/step)*step
if att > self.get_att_max():
raise ValueError("Cannot set attenuation {} > {}"
@ -62,7 +55,7 @@ class Ldasim:
elif att < 0*dB:
raise ValueError("Cannot set attenuation {} < 0".format(att))
else:
att = round(att.amount*4)/4. * dB
att = round(att*4)/4. * dB
self._attenuation = att
def ping(self):
@ -117,7 +110,7 @@ class Lda:
self._product_ids[self.product],
self.serial)
if not self._dev:
raise IOError
raise IOError("Device not found")
def close(self):
"""Close the device."""
@ -218,14 +211,7 @@ class Lda:
"""
step = self.get_att_step_size()
if isinstance(attenuation, Quantity):
check_unit(attenuation, "dB")
att = attenuation
else:
att = attenuation*dB
att = round(att/step)*step
att = round(attenuation/step)*step
if att > self.get_att_max():
raise ValueError("Cannot set attenuation {} > {}"
@ -233,7 +219,7 @@ class Lda:
elif att < 0*dB:
raise ValueError("Cannot set attenuation {} < 0".format(att))
else:
self.set(0x8d, bytes([int(round(att.amount*4))]))
self.set(0x8d, bytes([int(round(att*4))]))
def ping(self):
try:

View File

@ -15,10 +15,26 @@ class UnexpectedResponse(Exception):
class Novatech409B:
"""Driver for Novatech 409B 4-channel DDS"""
"""Driver for Novatech 409B 4-channel DDS.
# maximum frequency of Novatech 409B when using PLL and external reference
max_freq_with_pll = 171.1276031
All output channels are in range [0, 1, 2, 3].
All frequencies are in Hz.
All phases are in turns.
All amplitudes are in volts.
"""
error_codes = {
"?0": "Unrecognized Command",
"?1": "Bad Frequency",
"?2": "Bad AM Command",
"?3": "Input line too long",
"?4": "Bad Phase",
"?5": "Bad Time",
"?6": "Bad Mode",
"?7": "Bad Amp",
"?8": "Bad Constant",
"?f": "Bad Byte"
}
def __init__(self, serial_dev):
if serial_dev is None:
@ -32,68 +48,67 @@ class Novatech409B:
parity="N",
stopbits=1,
xonxoff=0,
timeout=0.2)
timeout=1.0)
self.setup()
def close(self):
"""Close the serial port"""
"""Close the serial port."""
if not self.simulation:
self.port.close()
def _ser_send(self, cmd, get_response=True):
"""send a string to the serial port
"""Send a string to the serial port."""
Routine for sending serial commands to device. It sends strings
and listens for a response terminated by a carriage return.
# Low-level routine for sending serial commands to device. It sends
# strings and listens for a response terminated by a carriage return.
# example:
# ser_send("F0 1.0") # sets the freq of channel 0 to 1.0 MHz
example:
ser_send("F0 1.0") # sets the freq of channel 0 to 1.0 MHz
:param cmd: a character string to send to device
:returns: None
"""
if self.simulation:
print(cmd)
else:
self.port.flush()
self.port.flushInput()
self.port.write((cmd + "\r\n").encode())
if get_response:
result = self.port.readline().rstrip().decode()
if result != "OK":
raise UnexpectedResponse(result)
if get_response:
logger.debug("got response from device: %s", result)
if result == "OK":
pass
elif result == "":
raise UnexpectedResponse("Response from device timed out")
else:
try:
errstr = self.error_codes[result]
except KeyError:
errstr = "Unrecognized reply: '{}'".format(result)
s = "Error Code = {ec}, {ecs}".format(ec=result, ecs=errstr)
raise UnexpectedResponse(s)
else:
pass
def reset(self):
"""command hardware reset of 409B
returns: None
"""
"""Hardware reset of 409B."""
self._ser_send("R", get_response=False)
time.sleep(1)
self.setup()
def setup(self):
"""initial setup of 409B
"""Initial setup of 409B."""
Setup the Novatech 409B with the following defaults.
* command echo off ("E d")
* external clock ("") 10 MHz sinusoid -1 to +7 dBm
# Setup the Novatech 409B with the following defaults:
# * command echo off ("E d")
# * external clock ("") 10 MHz sinusoid -1 to +7 dBm
:returns: None
"""
# disable command echo
self._ser_send("E d", get_response=False)
self.set_phase_continuous(True)
self.set_simultaneous_update(False)
def save_state_to_eeprom(self):
"""save current state to EEPROM
Saves current state into EEPROM and sets valid flag.
State used as default upon next power up or reset. """
"""Save current state to EEPROM."""
self._ser_send("S")
def set_phase_continuous(self, is_continuous):
"""toggle phase continuous mode
"""Toggle phase continuous mode.
Sends the "M n" command. This turns off the automatic
clearing of the phase register. In this mode, the phase
@ -109,7 +124,9 @@ class Novatech409B:
self._ser_send("M a")
def set_simultaneous_update(self, simultaneous):
"""Sends the "I m" command. In this mode an update
"""Set simultaneous update mode.
Sends the "I m" command. In this mode an update
pulse will not be sent to the DDS chip until
an "I p" command is sent. This is useful when it is
important to change all the outputs to new values
@ -121,140 +138,75 @@ class Novatech409B:
self._ser_send("I a")
def set_freq(self, ch_no, freq):
"""set_freq(ch_no,freq):
Set ch_no to frequency freq MHz"""
if ch_no < 0 or ch_no > 3:
raise ValueError("Incorrect channel number {}".format(ch_no))
if freq < 0.0 or freq > self.max_freq_with_pll:
raise ValueError("Incorrect frequency {}".format(freq))
# do this immediately, disable SimultaneousUpdate mode
"""Set frequency of one channel."""
self.set_simultaneous_update(False)
self._ser_send("F{:d} {:f}".format(ch_no, freq))
# Novatech expects MHz
self._ser_send("F{:d} {:f}".format(ch_no, freq/1e6))
def set_phase(self, ch_no, phase):
"""set DDS phase
:param ch_no: 0 to 3
:param phase: phase angle in cycles [0, 1]
:returns: None
"""
if ch_no < 0 or ch_no > 3:
raise ValueError("Incorrect channel number {}".format(ch_no))
if phase < 0 or phase > 1:
raise ValueError("Incorrect phase {}".format(phase))
"""Set phase of one channel."""
# do this immediately, disable SimultaneousUpdate mode
self.set_simultaneous_update(False)
# phase word is required by device
# N is an integer from 0 to 16383. Phase is set to
# N*360/16384 deg; in ARTIQ represent phase in cycles [0, 1]
phase_word = round(phase*16384)
if phase_word >= 16384:
phase_word -= 16384
phase_word = round(phase*16383)
cmd = "P{:d} {:d}".format(ch_no, phase_word)
self._ser_send(cmd)
def set_freq_all_phase_continuous(self, freq):
"""set frequency of all channels simultaneously
"""Set frequency of all channels simultaneously.
Set frequency of all channels simultaneously.
1) all DDSs are set to phase continuous mode
2) all DDSs are simultaneously set to new frequency
Together 1 and 2 ensure phase continuous frequency switching.
:param freq: frequency in MHz
:returns: None
"""
self.set_simultaneous_update(True)
self.set_phase_continuous(True)
for channel_num in range(4):
self.set_freq(channel_num, freq)
for i in range(4):
self.set_freq(i, freq)
# send command necessary to update all channels at the same time
self._ser_send("I p")
def set_phase_all(self, phase):
"""set phase of all DDS channels simultaneously
"""Set phase of all channels simultaneously."""
Set phase of all DDS channels at the same time. For example,::
set_phase_all([0, .25, 0.5, 0.75])
:param phase: vector of four phases (in cycles [0, 1])
:returns: None
"""
self.set_simultaneous_update(True)
# Note that this only works if the continuous
# phase switching is turned off.
self.set_phase_continuous(False)
for ch_no in range(4):
self.set_phase(ch_no, phase[ch_no])
for i in range(4):
self.set_phase(i, phase)
# send command necessary to update all channels at the same time
self._ser_send("I p")
def freq_sweep_all_phase_continuous(self, f0, f1, t):
""" sweep phase of all DDSs, phase continuous
def set_gain(self, ch_no, volts):
"""Set amplitude of one channel."""
Sweep frequency in a phase continuous fashion.
# due to error in Novatech it doesn't generate an error for
# dac_value>1024, so need to trap.
dac_value = int(math.floor(volts/0.51*1024))
if dac_value < 0 or dac_value > 1023:
s = "Amplitude out of range {v}".format(v=volts)
raise ValueError(s)
:param f0: starting frequency (MHz)
:param f1: ending frequency (MHz)
:param t: sweep duration (seconds)
:returns: None
"""
# TODO: consider using artiq.language.units
if f0 == f1:
return
# get sign of sweep
if f1 > f0:
df_sign = 1
else:
df_sign = -1
self.set_phase_continuous(True)
self.set_simultaneous_update(True)
# calculate delay
# note that a single call to self.set_freq_all_phase_continuous()
# takes time t_for_one_freq_set; fix duration empirically
t_for_one_freq_set = 0.264
dt = t_for_one_freq_set
n_steps = int(math.ceil(t/dt))
df = abs(f0-f1)/n_steps
for n in range(n_steps):
fnow = f0+n*df_sign*df
self.set_freq_all_phase_continuous(fnow)
self.set_freq_all_phase_continuous(f1)
def output_scale(self, ch_no, frac):
"""changes amplitude of a DDS
:param ch_no: DDS channel 0, 1, 2 or 3
:param frac: 0 to 1 (full attenuation to no attenuation)
:returns: None
"""
self.set_simultaneous_update(False)
dac_ch_no = int(math.floor(frac*1024))
s = "V{:d} {:d}".format(ch_no, dac_ch_no)
s = "V{:d} {:d}".format(ch_no, dac_value)
self._ser_send(s)
def output_scale_all(self, frac):
"""changes amplitude of all DDSs
:param frac: 0 to 1 (full attenuation to no attenuation)
"""
for ch_no in range(4):
self.output_scale(ch_no, frac)
def output_on_off(self, ch_no, on):
"""turns on or off the DDS
:param ch_no: DDS channel 0, 1, 2 or 3
"""
if on:
self.output_scale(ch_no, 1.0)
def get_status(self):
if self.simulation:
return ["00989680 2000 01F5 0000 00000000 00000000 000301",
"00989680 2000 01F5 0000 00000000 00000000 000301",
"00989680 2000 01F5 0000 00000000 00000000 000301",
"00989680 2000 01F5 0000 00000000 00000000 000301",
"80 BC0000 0000 0102 21"]
else:
self.output_scale(ch_no, 0.0)
def output_on_off_all(self, on):
"""turns on or off the all the DDSs"""
if on:
self.output_scale_all(1.0)
else:
self.output_scale_all(0.0)
# status message is multi-line
self.port.flushInput()
self.port.write(("QUE" + "\r\n").encode())
result = self.port.readlines()
result = [r.rstrip().decode() for r in result]
logger.debug("got device status: %s", result)
return result

View File

@ -1,7 +1,5 @@
from artiq.language.core import *
from artiq.language.db import *
from artiq.language.units import *
from artiq.coredevice import ttl
frame_setup = 20*ns
@ -76,7 +74,7 @@ class _Frame:
self.pdq = pdq
self.frame_number = frame_number
self.segments = []
self.segment_count = 0
self.segment_count = 0 # == len(self.segments), used in kernel
self.invalidated = False
@ -99,7 +97,7 @@ class _Frame:
def _arm(self):
self.segment_delays = [
time_to_cycles(s.get_duration()*delay_margin_factor, self.core)
seconds_to_mu(s.get_duration()*delay_margin_factor, self.core)
for s in self.segments]
def _invalidate(self):
@ -125,7 +123,8 @@ class _Frame:
if not self.pdq.armed:
raise ArmError
t = time_to_cycles(now()) - time_to_cycles(trigger_duration/2)
call_t = now_mu()
trigger_start_t = call_t - seconds_to_mu(trigger_duration/2)
if self.pdq.current_frame >= 0:
# PDQ is in the middle of a frame. Check it is us.
@ -136,15 +135,16 @@ class _Frame:
# to play our first segment.
self.pdq.current_frame = self.frame_number
self.pdq.next_segment = 0
t2 = t - time_to_cycles(frame_setup)
self.pdq.frame0.set_value(t2, self.frame_number & 1)
self.pdq.frame1.set_value(t2, (self.frame_number & 2) >> 1)
self.pdq.frame2.set_value(t2, (self.frame_number & 4) >> 2)
at_mu(trigger_start_t - seconds_to_mu(frame_setup))
self.pdq.frame0.set_o(bool(self.frame_number & 1))
self.pdq.frame1.set_o(bool((self.frame_number & 2) >> 1))
self.pdq.frame2.set_o(bool((self.frame_number & 4) >> 2))
self.pdq.trigger.on(t)
self.pdq.trigger.off(t + time_to_cycles(trigger_duration))
at_mu(trigger_start_t)
self.pdq.trigger.pulse(trigger_duration)
delay(cycles_to_time(self.segment_delays[self.pdq.next_segment]))
at_mu(call_t)
delay_mu(self.segment_delays[self.pdq.next_segment])
self.pdq.next_segment += 1
# test for end of frame
@ -153,23 +153,14 @@ class _Frame:
self.pdq.next_segment = -1
class CompoundPDQ2(AutoDB):
class DBKeys:
core = Device()
pdq2_devices = Argument()
rtio_trigger = Argument()
rtio_frame = Argument()
def build(self):
self.pdq2s = [self.dbh.get_device(d) for d in self.pdq2_devices]
self.trigger = ttl.LLTTLOut(
core=self.core, channel=self.rtio_trigger)
self.frame0 = ttl.LLTTLOut(
core=self.core, channel=self.rtio_frame[0])
self.frame1 = ttl.LLTTLOut(
core=self.core, channel=self.rtio_frame[1])
self.frame2 = ttl.LLTTLOut(
core=self.core, channel=self.rtio_frame[2])
class CompoundPDQ2:
def __init__(self, dmgr, pdq2_devices, trigger_device, frame_devices):
self.core = dmgr.get("core")
self.pdq2s = [dmgr.get(d) for d in self.pdq2_devices]
self.trigger = dmgr.get(trigger_device)
self.frame0 = dmgr.get(frame_devices[0])
self.frame1 = dmgr.get(frame_devices[1])
self.frame2 = dmgr.get(frame_devices[2])
self.frames = []
self.current_frame = -1
@ -187,6 +178,7 @@ class CompoundPDQ2(AutoDB):
raise ArmError
for frame in self.frames:
frame._arm()
self.armed = True
full_program = [f._get_program() for f in self.frames]
for n, pdq2 in enumerate(self.pdq2s):

View File

@ -1,9 +1,14 @@
# Yann Sionneau <ys@m-labs.hk>, 2015
from ctypes import byref
from ctypes import byref, c_ulong
import logging
import numpy as np
logger = logging.getLogger(__name__)
class DAQmxSim:
def load_sample_values(self, values):
pass
@ -11,61 +16,124 @@ class DAQmxSim:
def close(self):
pass
def ping(self):
return True
class DAQmx:
"""NI PXI6733 DAQ interface."""
def __init__(self, device, analog_output, clock):
import PyDAQmx as daq
def __init__(self, channels, clock):
"""
:param channels: List of channels as a string, following
the physical channels lists and ranges NI-DAQmx syntax.
self.device = device
self.analog_output = analog_output
self.clock = clock
self.tasks = []
self.daq = daq
Example: Dev1/ao0, Dev1/ao1:ao3
:param clock: Clock source terminal as a string, following
NI-DAQmx terminal names syntax.
def done_callback_py(self, taskhandle, status, callback_data):
self.daq.DAQmxClearTask(taskhandle)
self.tasks.remove(taskhandle)
def load_sample_values(self, values):
"""Load sample values into PXI 6733 device.
This loads sample values into the PXI 6733 device and then
configures a task to output those samples at each clock rising
edge.
A callback is registered to clear the task (deallocate resources)
when the task has completed.
:param values: A numpy array of sample values to load in the device.
Example: PFI5
"""
import PyDAQmx as daq
self.channels = channels.encode()
self.clock = clock.encode()
self.task = None
self.daq = daq
def _done_callback(self, taskhandle, status, callback_data):
if taskhandle != self.task:
logger.warning("done callback called with unexpected task")
else:
self.clear_pending_task()
def ping(self):
try:
data = (c_ulong*1)()
self.daq.DAQmxGetDevSerialNum(self.device, data)
except:
return False
return True
def load_sample_values(self, sampling_freq, values):
"""Load sample values into PXI 6733 device.
This loads sample values into the PXI 6733 device.
The device will output samples at each clock rising edge.
The device waits for a clock rising edge to output the first sample.
When using several channels simultaneously, you can either concatenate
the values for the different channels in a 1-dimensional ``values``
numpy ndarray.
Example:
>>> values = np.array([ch0_samp0, ch0_samp1, ch1_samp0, ch1_samp1],
dtype=float)
In this example the first two samples will be output via the first
channel and the two following samples will be output via the second
channel.
Or you can use a 2-dimensional numpy ndarray like this:
>>> values = np.array([[ch0_samp0, ch0_samp1],[ch1_samp0, ch1_samp1]],
dtype=float)
Any call to this method will cancel any previous task even if it has
not yet completed.
:param sampling_freq: The sampling frequency in samples per second.
:param values: A numpy ndarray of sample values (in volts) to load in
the device.
"""
self.clear_pending_task()
values = values.flatten()
t = self.daq.Task()
t.CreateAOVoltageChan(self.device+b"/"+self.analog_output, b"",
t.CreateAOVoltageChan(self.channels, b"",
min(values), max(values),
self.daq.DAQmx_Val_Volts, None)
t.CfgSampClkTiming(self.clock, 1000.0, self.daq.DAQmx_Val_Rising,
self.daq.DAQmx_Val_FiniteSamps, len(values))
channel_number = (c_ulong*1)()
t.GetTaskNumChans(channel_number)
nb_values = len(values)
if nb_values % channel_number[0]:
self.daq.DAQmxClearTask(t.taskHandle)
raise ValueError("The size of the values array must be a multiple "
"of the number of channels ({})"
.format(channel_number[0]))
samps_per_channel = nb_values // channel_number[0]
t.CfgSampClkTiming(self.clock, sampling_freq,
self.daq.DAQmx_Val_Rising,
self.daq.DAQmx_Val_FiniteSamps, samps_per_channel)
num_samps_written = self.daq.int32()
values = np.require(values, dtype=float,
requirements=["C_CONTIGUOUS", "WRITEABLE"])
ret = t.WriteAnalogF64(len(values), False, 0,
ret = t.WriteAnalogF64(samps_per_channel, False, 0,
self.daq.DAQmx_Val_GroupByChannel, values,
byref(num_samps_written), None)
if num_samps_written.value != len(values):
if num_samps_written.value != nb_values:
raise IOError("Error: only {} sample values were written"
.format(num_samps_written.value))
if ret:
raise IOError("Error while writing samples to the channel buffer")
done_cb = self.daq.DAQmxDoneEventCallbackPtr(self.done_callback_py)
self.tasks.append(t.taskHandle)
done_cb = self.daq.DAQmxDoneEventCallbackPtr(self._done_callback)
self.task = t.taskHandle
self.daq.DAQmxRegisterDoneEvent(t.taskHandle, 0, done_cb, None)
t.StartTask()
def close(self):
"""Clear all pending tasks."""
def clear_pending_task(self):
"""Clear any pending task."""
for t in self.tasks:
self.daq.DAQmxClearTask(t)
if self.task is not None:
self.daq.DAQmxClearTask(self.task)
self.task = None
def close(self):
"""Free any allocated resources."""
self.clear_pending_task()

View File

@ -0,0 +1,177 @@
import numpy as np
from artiq.language.core import *
from artiq.language.units import *
from artiq.wavesynth.compute_samples import Synthesizer
class SegmentSequenceError(Exception):
"""Raised when attempting to play back a named segment which is not the
next in the sequence."""
pass
class InvalidatedError(Exception):
"""Raised when attemting to use a frame or segment that has been
invalidated (due to disarming the DAQmx)."""
pass
class ArmError(Exception):
"""Raised when attempting to arm an already armed DAQmx, to modify the
program of an armed DAQmx, or to play a segment on a disarmed DAQmx."""
pass
def _ceil_div(a, b):
return (a + b - 1)//b
def _compute_duration_mu(nsamples, ftw, acc_width):
# This returns the precise duration so that the clock can be stopped
# exactly at the next rising edge (RTLink commands take precedence over
# toggling from the accumulator).
# If segments are played continuously, replacement of the stop command
# will keep the clock running. If the FTW is not a power of two, note that
# the accumulator is reset at that time, which causes jitter and frequency
# inaccuracy.
# Formally:
# duration *ftw >= nsamples*2**acc_width
# (duration - 1)*ftw < nsamples*2**acc_width
return _ceil_div(nsamples*2**acc_width, ftw)
class _Segment:
def __init__(self, frame, segment_number):
self.frame = frame
self.segment_number = segment_number
self.lines = []
# for @kernel
self.core = frame.daqmx.core
def add_line(self, duration, channel_data):
if self.frame.invalidated:
raise InvalidatedError
if self.frame.daqmx.armed:
raise ArmError
self.lines.append((duration, channel_data))
@kernel
def advance(self):
if self.frame.invalidated:
raise InvalidatedError
if not self.frame.daqmx.armed:
raise ArmError
# If the frame is currently being played, check that we are next.
if (self.frame.daqmx.next_segment >= 0
and self.frame.daqmx.next_segment != self.segment_number):
raise SegmentSequenceError
self.frame.advance()
class _Frame:
def __init__(self, daqmx):
self.daqmx = daqmx
self.segments = []
self.segment_count = 0 # == len(self.segments), used in kernel
self.invalidated = False
# for @kernel
self.core = self.daqmx.core
def create_segment(self, name=None):
if self.invalidated:
raise InvalidatedError
if self.daqmx.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 = [
_compute_duration_mu(s.get_sample_count(),
self.daqmx.sample_rate,
self.daqmx.clock.acc_width)
for s in self.segments]
def _invalidate(self):
self.invalidated = True
def _get_samples(self):
program = [
{
"dac_divider": 1,
"duration": duration,
"channel_data": channel_data,
} for duration, channel_data in segment.lines
for segment in self.segments]
synth = Synthesizer(self.daqmx.channel_count, program)
synth.select(0)
# not setting any trigger flag in the program causes the whole
# waveform to be computed here for all segments.
# slicing the segments is done by stopping the clock.
return synth.trigger()
@kernel
def advance(self):
if self.invalidated:
raise InvalidatedError
if not self.daqmx.armed:
raise ArmError
self.daqmx.clock.set(self.daqmx.sample_rate)
delay_mu(self.segment_delays[self.daqmx.next_segment])
self.daqmx.next_segment += 1
self.daqmx.clock.stop()
# test for end of frame
if self.daqmx.next_segment == self.segment_count:
self.daqmx.next_segment = -1
class CompoundDAQmx:
def __init__(self, dmgr, daqmx_device, clock_device, channel_count,
sample_rate, sample_rate_in_mu=False):
self.core = dmgr.get("core")
self.daqmx = dmgr.get(daqmx_device)
self.clock = dmgr.get(clock_device)
self.channel_count = channel_count
if self.sample_rate_in_mu:
self.sample_rate = sample_rate
else:
self.sample_rate = self.clock.frequency_to_ftw(sample_rate)
self.frame = None
self.next_segment = -1
self.armed = False
def disarm(self):
if self.frame is not None:
self.frame._invalidate()
self.frame = None
self.armed = False
def arm(self):
if self.armed:
raise ArmError
if self.frame is not None:
self.frame._arm()
self.daqmx.load_sample_values(
self.clock.ftw_to_frequency(self.sample_rate),
np.array(self.frame._get_samples()))
self.armed = True
def create_frame(self):
if self.armed:
raise ArmError
self.frame = _Frame(self)
return self.frame

View File

@ -4,7 +4,7 @@ import struct as st
import serial
from artiq.language.units import V, strip_unit
from artiq.language.units import V
logger = logging.getLogger(__name__)
@ -221,10 +221,10 @@ class Tcube:
def handle_message(self, msg):
pass
def send_request(self, msgreq_id, msgget_id, param1=0, param2=0):
Message(msgreq_id, param1, param2).send(self.port)
def send_request(self, msgreq_id, wait_for_msgs, param1=0, param2=0, data=None):
Message(msgreq_id, param1, param2, data=data).send(self.port)
msg = msg_id = None
while msg is None or msg_id != msgget_id:
while msg is None or msg_id not in wait_for_msgs:
msg = Message.recv(self.port)
self.handle_message(msg)
msg_id = msg.id
@ -247,7 +247,7 @@ class Tcube:
def get_channel_enable_state(self):
get_msg = self.send_request(MGMSG.MOD_REQ_CHANENABLESTATE,
MGMSG.MOD_GET_CHANENABLESTATE, 1)
[MGMSG.MOD_GET_CHANENABLESTATE], 1)
self.chan_enabled = get_msg.param2
if self.chan_enabled == 1:
self.chan_enabled = True
@ -286,7 +286,7 @@ class Tcube:
def hardware_request_information(self):
return self.send_request(MGMSG.HW_REQ_INFO,
MGMSG.HW_GET_INFO)
[MGMSG.HW_GET_INFO])
def is_channel_enabled(self):
return self.chan_enabled
@ -302,7 +302,7 @@ class Tcube:
class Tpz(Tcube):
def __init__(self, serial_dev):
Tcube.__init__(self, serial_dev)
self.voltage_limit = self.get_tpz_io_settings()[0].amount
self.voltage_limit = self.get_tpz_io_settings()[0]
def handle_message(self, msg):
msg_id = msg.id
@ -355,7 +355,7 @@ class Tpz(Tcube):
"""
get_msg = self.send_request(MGMSG.PZ_REQ_POSCONTROLMODE,
MGMSG.PZ_GET_POSCONTROLMODE, 1)
[MGMSG.PZ_GET_POSCONTROLMODE], 1)
return get_msg.param2
def set_output_volts(self, voltage):
@ -372,8 +372,6 @@ class Tpz(Tcube):
between the three values 75 V, 100 V and 150 V.
"""
voltage = strip_unit(voltage, "V")
if voltage < 0 or voltage > self.voltage_limit:
raise ValueError("Voltage must be in range [0;{}]"
.format(self.voltage_limit))
@ -389,8 +387,8 @@ class Tpz(Tcube):
"""
get_msg = self.send_request(MGMSG.PZ_REQ_OUTPUTVOLTS,
MGMSG.PZ_GET_OUTPUTVOLTS, 1)
return st.unpack("<H", get_msg.data[2:])[0]*self.voltage_limit*V/32767
[MGMSG.PZ_GET_OUTPUTVOLTS], 1)
return st.unpack("<H", get_msg.data[2:])[0]*self.voltage_limit/32767
def set_output_position(self, position_sw):
"""Set output position of the piezo actuator.
@ -417,7 +415,7 @@ class Tpz(Tcube):
"""
get_msg = self.send_request(MGMSG.PZ_REQ_OUTPUTPOS,
MGMSG.PZ_GET_OUTPUTPOS, 1)
[MGMSG.PZ_GET_OUTPUTPOS], 1)
return st.unpack("<H", get_msg.data[2:])[0]
def set_input_volts_source(self, volt_src):
@ -459,7 +457,7 @@ class Tpz(Tcube):
"""
get_msg = self.send_request(MGMSG.PZ_REQ_INPUTVOLTSSRC,
MGMSG.PZ_GET_INPUTVOLTSSRC, 1)
[MGMSG.PZ_GET_INPUTVOLTSSRC], 1)
return st.unpack("<H", get_msg.data[2:])[0]
def set_pi_constants(self, prop_const, int_const):
@ -488,7 +486,7 @@ class Tpz(Tcube):
"""
get_msg = self.send_request(MGMSG.PZ_REQ_PICONSTS,
MGMSG.PZ_GET_PICONSTS, 1)
[MGMSG.PZ_GET_PICONSTS], 1)
return st.unpack("<HH", get_msg.data[2:])
def set_output_lut(self, lut_index, output):
@ -532,7 +530,6 @@ class Tpz(Tcube):
<artiq.devices.thorlabs.driver.Tpz.set_tpz_io_settings>` method.
"""
output = strip_unit(output, "V")
volt = round(output*32767/self.voltage_limit)
payload = st.pack("<HHH", 1, lut_index, volt)
self.send(Message(MGMSG.PZ_SET_OUTPUTLUT, data=payload))
@ -546,9 +543,9 @@ class Tpz(Tcube):
"""
get_msg = self.send_request(MGMSG.PZ_REQ_OUTPUTLUT,
MGMSG.PZ_GET_OUTPUTLUT, 1)
[MGMSG.PZ_GET_OUTPUTLUT], 1)
(index, output) = st.unpack("<Hh", get_msg.data[2:])
return index, output*self.voltage_limit*V/32767
return index, output*self.voltage_limit/32767
def set_output_lut_parameters(self, mode, cycle_length, num_cycles,
delay_time, precycle_rest, postcycle_rest):
@ -626,7 +623,7 @@ class Tpz(Tcube):
"""
get_msg = self.send_request(MGMSG.PZ_REQ_OUTPUTLUTPARAMS,
MGMSG.PZ_GET_OUTPUTLUTPARAMS, 1)
[MGMSG.PZ_GET_OUTPUTLUTPARAMS], 1)
return st.unpack("<HHLLLL", get_msg.data[2:22])
def start_lut_output(self):
@ -669,7 +666,7 @@ class Tpz(Tcube):
"""
get_msg = self.send_request(MGMSG.PZ_REQ_TPZ_DISPSETTINGS,
MGMSG.PZ_GET_TPZ_DISPSETTINGS, 1)
[MGMSG.PZ_GET_TPZ_DISPSETTINGS], 1)
return st.unpack("<H", get_msg.data)[0]
def set_tpz_io_settings(self, voltage_limit, hub_analog_input):
@ -684,9 +681,6 @@ class Tpz(Tcube):
100 V limit.
150 V limit.
You can either provide this parameter as an integer or as a
:class:`artiq.language.units` Volt quantity (e.g. 75*V).
:param hub_analog_input: When the T-Cube piezo driver unit is used in
conjunction with the T-Cube Strain Gauge Reader (TSG001) on the
T-Cube Controller Hub (TCH001), a feedback signal can be passed
@ -706,7 +700,7 @@ class Tpz(Tcube):
connectors.
"""
self.voltage_limit = strip_unit(voltage_limit, "V")
self.voltage_limit = voltage_limit
if self.voltage_limit == 75:
voltage_limit = 1
@ -727,21 +721,21 @@ class Tpz(Tcube):
Hub analog input. Refer to :py:meth:`set_tpz_io_settings()
<artiq.devices.thorlabs.driver.Tpz.set_tpz_io_settings>` for the
meaning of those parameters.
:rtype: a 2 elements tuple (Quantity, int)
:rtype: a 2 elements tuple (int, int)
"""
get_msg = self.send_request(MGMSG.PZ_REQ_TPZ_IOSETTINGS,
MGMSG.PZ_GET_TPZ_IOSETTINGS, 1)
[MGMSG.PZ_GET_TPZ_IOSETTINGS], 1)
voltage_limit, hub_analog_input = st.unpack("<HH", get_msg.data[2:6])
if voltage_limit == 1:
voltage_limit = 75*V
voltage_limit = 75
elif voltage_limit == 2:
voltage_limit = 100*V
voltage_limit = 100
elif voltage_limit == 3:
voltage_limit = 150*V
voltage_limit = 150
else:
raise ValueError("Voltage limit should be in range [1; 3]")
self.voltage_limit = voltage_limit.amount
self.voltage_limit = voltage_limit
return voltage_limit, hub_analog_input
@ -763,8 +757,6 @@ class Tdc(Tcube):
raise MsgError("Hardware error {}: {}"
.format(code,
data[4:].decode(encoding="ascii")))
elif msg_id == MGMSG.MOT_MOVE_HOMED:
pass
elif (msg_id == MGMSG.MOT_MOVE_COMPLETED or
msg_id == MGMSG.MOT_MOVE_STOPPED or
msg_id == MGMSG.MOT_GET_DCSTATUSUPDATE):
@ -777,6 +769,10 @@ class Tdc(Tcube):
(self.position, self.velocity, r, self.status) = st.unpack(
"<LHHL", data[2:])
def is_moving(self):
status_bits = self.get_status_bits()
return (status_bits & 0x2F0) != 0
def set_pot_parameters(self, zero_wnd, vel1, wnd1, vel2, wnd2, vel3,
wnd3, vel4):
"""Set pot parameters.
@ -811,12 +807,12 @@ class Tdc(Tcube):
"""
get_msg = self.send_request(MGMSG.MOT_REQ_POTPARAMS,
MGMSG.MOT_GET_POTPARAMS, 1)
[MGMSG.MOT_GET_POTPARAMS], 1)
return st.unpack("<HLHLHLHL", get_msg.data[2:])
def hub_get_bay_used(self):
get_msg = self.send_request(MGMSG.HUB_REQ_BAYUSED,
MGMSG.HUB_GET_BAYUSED)
[MGMSG.HUB_GET_BAYUSED])
return get_msg.param1
def set_position_counter(self, position):
@ -841,7 +837,7 @@ class Tdc(Tcube):
"""
get_msg = self.send_request(MGMSG.MOT_REQ_POSCOUNTER,
MGMSG.MOT_GET_POSCOUNTER, 1)
[MGMSG.MOT_GET_POSCOUNTER], 1)
return st.unpack("<l", get_msg.data[2:])[0]
def set_encoder_counter(self, encoder_count):
@ -865,7 +861,7 @@ class Tdc(Tcube):
"""
get_msg = self.send_request(MGMSG.MOT_REQ_ENCCOUNTER,
MGMSG.MOT_GET_ENCCOUNTER, 1)
[MGMSG.MOT_GET_ENCCOUNTER], 1)
return st.unpack("<l", get_msg.data[2:])[0]
def set_velocity_parameters(self, acceleration, max_velocity):
@ -886,7 +882,7 @@ class Tdc(Tcube):
"""
get_msg = self.send_request(MGMSG.MOT_REQ_VELPARAMS,
MGMSG.MOT_GET_VELPARAMS, 1)
[MGMSG.MOT_GET_VELPARAMS], 1)
return st.unpack("<LL", get_msg.data[6:])
def set_jog_parameters(self, mode, step_size, acceleration,
@ -915,7 +911,7 @@ class Tdc(Tcube):
"""
get_msg = self.send_request(MGMSG.MOT_REQ_JOGPARAMS,
MGMSG.MOT_GET_JOGPARAMS, 1)
[MGMSG.MOT_GET_JOGPARAMS], 1)
(jog_mode, step_size, _, acceleration, max_velocity,
stop_mode) = st.unpack("<HLLLLH", get_msg.data[2:])
return jog_mode, step_size, acceleration, max_velocity, stop_mode
@ -938,7 +934,7 @@ class Tdc(Tcube):
"""
get_msg = self.send_request(MGMSG.MOT_REQ_GENMOVEPARAMS,
MGMSG.MOT_GET_GENMOVEPARAMS, 1)
[MGMSG.MOT_GET_GENMOVEPARAMS], 1)
return st.unpack("<l", get_msg.data[2:])[0]
def set_move_relative_parameters(self, relative_distance):
@ -960,7 +956,7 @@ class Tdc(Tcube):
"""
get_msg = self.send_request(MGMSG.MOT_REQ_MOVERELPARAMS,
MGMSG.MOT_GET_MOVERELPARAMS, 1)
[MGMSG.MOT_GET_MOVERELPARAMS], 1)
return st.unpack("<l", get_msg.data[2:])[0]
def set_move_absolute_parameters(self, absolute_position):
@ -982,7 +978,7 @@ class Tdc(Tcube):
"""
get_msg = self.send_request(MGMSG.MOT_REQ_MOVEABSPARAMS,
MGMSG.MOT_GET_MOVEABSPARAMS, 1)
[MGMSG.MOT_GET_MOVEABSPARAMS], 1)
return st.unpack("<l", get_msg.data[2:])[0]
def set_home_parameters(self, home_velocity):
@ -1002,17 +998,17 @@ class Tdc(Tcube):
"""
get_msg = self.send_request(MGMSG.MOT_REQ_HOMEPARAMS,
MGMSG.MOT_GET_HOMEPARAMS, 1)
[MGMSG.MOT_GET_HOMEPARAMS], 1)
return st.unpack("<L", get_msg.data[6:10])[0]
def move_home(self):
"""Start a home move sequence.
This call is blocking until device is homed.
This call is blocking until device is homed or move is stopped.
"""
self.send_request(MGMSG.MOT_MOVE_HOME,
MGMSG.MOT_MOVE_HOMED, 1)
[MGMSG.MOT_MOVE_HOMED, MGMSG.MOT_MOVE_STOPPED], 1)
def set_limit_switch_parameters(self, cw_hw_limit, ccw_hw_limit):
"""Set the limit switch parameters.
@ -1057,7 +1053,7 @@ class Tdc(Tcube):
"""
get_msg = self.send_request(MGMSG.MOT_REQ_LIMSWITCHPARAMS,
MGMSG.MOT_GET_LIMSWITCHPARAMS, 1)
[MGMSG.MOT_GET_LIMSWITCHPARAMS], 1)
return st.unpack("<HH", get_msg.data[2:6])
def move_relative_memory(self):
@ -1069,7 +1065,8 @@ class Tdc(Tcube):
command.
"""
self.send(Message(MGMSG.MOT_MOVE_RELATIVE, param1=1))
self.send_request(MGMSG.MOT_MOVE_RELATIVE,
[MGMSG.MOT_MOVE_COMPLETED, MGMSG.MOT_MOVE_STOPPED], 1)
def move_relative(self, relative_distance):
"""Start a relative move
@ -1079,7 +1076,9 @@ class Tdc(Tcube):
"""
payload = st.pack("<Hl", 1, relative_distance)
self.send(Message(MGMSG.MOT_MOVE_RELATIVE, data=payload))
self.send_request(MGMSG.MOT_MOVE_RELATIVE,
[MGMSG.MOT_MOVE_COMPLETED, MGMSG.MOT_MOVE_STOPPED],
data=payload)
def move_absolute_memory(self):
"""Start an absolute move of distance in the controller's memory.
@ -1090,7 +1089,9 @@ class Tdc(Tcube):
command.
"""
self.send(Message(MGMSG.MOT_MOVE_ABSOLUTE, param1=1))
self.send_request(MGMSG.MOT_MOVE_ABSOLUTE,
[MGMSG.MOT_MOVE_COMPLETED, MGMSG.MOT_MOVE_STOPPED],
param1=1)
def move_absolute(self, absolute_distance):
"""Start an absolute move.
@ -1101,21 +1102,19 @@ class Tdc(Tcube):
"""
payload = st.pack("<Hl", 1, absolute_distance)
self.send(Message(MGMSG.MOT_MOVE_ABSOLUTE, data=payload))
self.send_request(MGMSG.MOT_MOVE_ABSOLUTE,
[MGMSG.MOT_MOVE_COMPLETED, MGMSG.MOT_MOVE_STOPPED],
data=payload)
def move_jog(self, direction, async=False):
def move_jog(self, direction):
"""Start a job move.
:param direction: The direction to jog. 1 is forward, 2 is backward.
:param async: If True then the command does not wait for the move to
finish. If False the command only returns when move is finished.
"""
if async:
self.send_request(MGMSG.MOT_MOVE_JOG,
MGMSG.MOT_MOVE_COMPLETED, 1, direction)
else:
self.send(Message(MGMSG.MOT_MOVE_JOG, param1=1, param2=direction))
[MGMSG.MOT_MOVE_COMPLETED, MGMSG.MOT_MOVE_STOPPED],
param1=1, param2=direction)
def move_velocity(self, direction):
"""Start a move.
@ -1134,7 +1133,7 @@ class Tdc(Tcube):
self.send(Message(MGMSG.MOT_MOVE_VELOCITY, param1=1, param2=direction))
def move_stop(self, stop_mode, async=False):
def move_stop(self, stop_mode):
"""Stop any type of motor move.
Stops any of those motor move: relative, absolute, homing or move at
@ -1143,15 +1142,13 @@ class Tdc(Tcube):
:param stop_mode: The stop mode defines either an immediate (abrupt)
or profiled stop. Set this byte to 1 to stop immediately, or to 2
to stop in a controlled (profiled) manner.
:param async: If set to False, this method will block until motor
is really stopped. Returns immediately if set to True.
"""
if async:
self.send(Message(MGMSG.MOT_MOVE_STOP, param1=1, param2=stop_mode))
else:
if self.is_moving():
self.send_request(MGMSG.MOT_MOVE_STOP,
MGMSG.MOT_MOVE_STOPPED, 1, stop_mode)
[MGMSG.MOT_MOVE_STOPPED,
MGMSG.MOT_MOVE_COMPLETED],
1, stop_mode)
def set_dc_pid_parameters(self, proportional, integral, differential,
integral_limit, filter_control=0x0F):
@ -1185,7 +1182,7 @@ class Tdc(Tcube):
"""
get_msg = self.send_request(MGMSG.MOT_REQ_DCPIDPARAMS,
MGMSG.MOT_GET_DCPIDPARAMS, 1)
[MGMSG.MOT_GET_DCPIDPARAMS], 1)
return st.unpack("<LLLLH", get_msg.data[2:])
def set_av_modes(self, mode_bits):
@ -1212,7 +1209,7 @@ class Tdc(Tcube):
"""
get_msg = self.send_request(MGMSG.MOT_REQ_AVMODES,
MGMSG.MOT_GET_AVMODES, 1)
[MGMSG.MOT_GET_AVMODES], 1)
return st.unpack("<H", get_msg.data[2:])[0]
def set_button_parameters(self, mode, position1, position2):
@ -1250,7 +1247,7 @@ class Tdc(Tcube):
"""
get_msg = self.send_request(MGMSG.MOT_REQ_BUTTONPARAMS,
MGMSG.MOT_GET_BUTTONPARAMS, 1)
[MGMSG.MOT_GET_BUTTONPARAMS], 1)
return st.unpack("<Hll", get_msg.data[2:12])
def set_eeprom_parameters(self, msg_id):
@ -1274,7 +1271,7 @@ class Tdc(Tcube):
"""
get_msg = self.send_request(MGMSG.MOT_REQ_DCSTATUSUPDATE,
MGMSG.MOT_GET_DCSTATUSUPDATE, 1)
[MGMSG.MOT_GET_DCSTATUSUPDATE], 1)
pos, vel, _, stat = st.unpack("<LHHL", get_msg.data[2:])
return pos, vel, stat
@ -1286,7 +1283,7 @@ class Tdc(Tcube):
"""
get_msg = self.send_request(MGMSG.MOT_REQ_STATUSBITS,
MGMSG.MOT_GET_STATUSBITS, 1)
[MGMSG.MOT_GET_STATUSBITS], 1)
return st.unpack("<L", get_msg.data[2:])[0]
def suspend_end_of_move_messages(self):
@ -1320,6 +1317,9 @@ class TpzSim:
self.voltage_limit = 150
self.hub_analog_input = 1
def close(self):
pass
def module_identify(self):
pass
@ -1392,17 +1392,19 @@ class TpzSim:
return self.intensity
def set_tpz_io_settings(self, voltage_limit, hub_analog_input):
self.voltage_limit = strip_unit(voltage_limit, "V")
if self.voltage_limit not in [75, 100, 150]:
if voltage_limit not in [75, 100, 150]:
raise ValueError("voltage_limit must be 75 V, 100 V or 150 V")
self.voltage_limit = voltage_limit
self.hub_analog_input = hub_analog_input
def get_tpz_io_settings(self):
return self.voltage_limit*V, self.hub_analog_input
return self.voltage_limit, self.hub_analog_input
class TdcSim:
def close(self):
pass
def module_identify(self):
pass

View File

@ -12,7 +12,6 @@ from prettytable import PrettyTable
from artiq.protocols.pc_rpc import Client
from artiq.protocols.sync_struct import Subscriber
from artiq.protocols import pyon
from artiq.tools import format_arguments
def clear_screen():
@ -32,16 +31,19 @@ def get_argparser():
subparsers.required = True
parser_add = subparsers.add_parser("submit", help="submit an experiment")
parser_add.add_argument("-t", "--timed", default=None, type=str,
help="set a due date for the experiment")
parser_add.add_argument("-p", "--pipeline", default="main", type=str,
help="pipeline to run the experiment in "
"(default: %(default)s)")
parser_add.add_argument("-P", "--priority", default=0, type=int,
help="priority (higher value means sooner "
"scheduling, default: %(default)s)")
parser_add.add_argument("-e", "--experiment", default=None,
help="experiment to run")
parser_add.add_argument("-t", "--timed", default=None, type=str,
help="set a due date for the experiment")
parser_add.add_argument("-f", "--flush", default=False, action="store_true",
help="flush the pipeline before preparing "
"the experiment")
parser_add.add_argument("-c", "--class-name", default=None,
help="name of the class to run")
parser_add.add_argument("file",
help="file containing the experiment to run")
parser_add.add_argument("arguments", nargs="*",
@ -79,6 +81,9 @@ def get_argparser():
"what",
help="select object to show: schedule/devices/parameters")
parser_scan_repository = subparsers.add_parser(
"scan-repository", help="rescan repository")
return parser
@ -99,14 +104,15 @@ def _action_submit(remote, args):
expid = {
"file": args.file,
"experiment": args.experiment,
"class_name": args.class_name,
"arguments": arguments,
}
if args.timed is None:
due_date = None
else:
due_date = time.mktime(parse_date(args.timed).timetuple())
rid = remote.submit(args.pipeline, expid, args.priority, due_date)
rid = remote.submit(args.pipeline, expid,
args.priority, due_date, args.flush)
print("RID: {}".format(rid))
@ -130,15 +136,19 @@ def _action_del_parameter(remote, args):
remote.delete(args.name)
def _action_scan_repository(remote, args):
remote.scan_async()
def _show_schedule(schedule):
clear_screen()
if schedule:
l = sorted(schedule.items(),
key=lambda x: (x[1]["due_date"] or 0,
-x[1]["priority"],
key=lambda x: (-x[1]["priority"],
x[1]["due_date"] or 0,
x[0]))
table = PrettyTable(["RID", "Pipeline", " Status ", "Prio",
"Due date", "File", "Experiment", "Arguments"])
"Due date", "File", "Class name"])
for rid, v in l:
row = [rid, v["pipeline"], v["status"], v["priority"]]
if v["due_date"] is None:
@ -147,11 +157,10 @@ def _show_schedule(schedule):
row.append(time.strftime("%m/%d %H:%M:%S",
time.localtime(v["due_date"])))
row.append(v["expid"]["file"])
if v["expid"]["experiment"] is None:
if v["expid"]["class_name"] is None:
row.append("")
else:
row.append(v["expid"]["experiment"])
row.append(format_arguments(v["expid"]["arguments"]))
row.append(v["expid"]["class_name"])
table.add_row(row)
print(table)
else:
@ -224,6 +233,7 @@ def main():
"del_device": "master_ddb",
"set_parameter": "master_pdb",
"del_parameter": "master_pdb",
"scan_repository": "master_repository"
}[action]
remote = Client(args.server, port, target_name)
try:

View File

@ -4,7 +4,7 @@ import logging
import argparse
from artiq.protocols.file_db import FlatFileDB
from artiq.master.worker_db import DBHub
from artiq.master.worker_db import DeviceManager
from artiq.tools import *
@ -36,15 +36,14 @@ def main():
args = get_argparser().parse_args()
init_logger(args)
ddb = FlatFileDB(args.ddb)
dmgr = DeviceManager(FlatFileDB(args.ddb))
pdb = FlatFileDB(args.pdb)
dbh = DBHub(ddb, pdb, rdb=None, read_only=True)
try:
module = file_import(args.file)
exp = get_experiment(module, args.experiment)
arguments = parse_arguments(args.arguments)
exp_inst = exp(dbh, **arguments)
exp_inst = exp(dmgr, pdb, **arguments)
if (not hasattr(exp.run, "k_function_info")
or not exp.run.k_function_info):
@ -56,7 +55,7 @@ def main():
[exp_inst], {},
with_attr_writeback=False)
finally:
dbh.close_devices()
dmgr.close_devices()
if rpc_map:
raise ValueError("Experiment must not use RPC")

View File

@ -0,0 +1,72 @@
#!/usr/bin/env python3
import argparse
from artiq.master.worker_db import DeviceManager
from artiq.protocols.file_db import FlatFileDB
def to_bytes(string):
return bytes(string, encoding="ascii")
def get_argparser():
parser = argparse.ArgumentParser(description="ARTIQ core device config "
"remote access")
subparsers = parser.add_subparsers(dest="action")
subparsers.required = True
p_read = subparsers.add_parser("read",
help="read key from core device config")
p_read.add_argument("key", type=to_bytes,
help="key to be read from core device config")
p_write = subparsers.add_parser("write",
help="write key-value records to core "
"device config")
p_write.add_argument("-s", "--string", nargs=2, action="append",
default=[], metavar=("KEY", "STRING"), type=to_bytes,
help="key-value records to be written to core device "
"config")
p_write.add_argument("-f", "--file", nargs=2, action="append",
type=to_bytes, default=[],
metavar=("KEY", "FILENAME"),
help="key and file whose content to be written to "
"core device config")
subparsers.add_parser("erase", help="erase core device config")
p_delete = subparsers.add_parser("delete",
help="delete key from core device config")
p_delete.add_argument("key", nargs=argparse.REMAINDER,
default=[], type=to_bytes,
help="key to be deleted from core device config")
parser.add_argument("--ddb", default="ddb.pyon",
help="device database file")
return parser
def main():
args = get_argparser().parse_args()
dmgr = DeviceManager(FlatFileDB(args.ddb))
try:
comm = dmgr.get("comm")
if args.action == "read":
value = comm.flash_storage_read(args.key)
if not value:
print("Key {} does not exist".format(args.key))
else:
print(value)
elif args.action == "erase":
comm.flash_storage_erase()
elif args.action == "delete":
for key in args.key:
comm.flash_storage_remove(key)
elif args.action == "write":
for key, value in args.string:
comm.flash_storage_write(key, value)
for key, filename in args.file:
with open(filename, "rb") as fi:
comm.flash_storage_write(key, fi.read())
finally:
dmgr.close_devices()
if __name__ == "__main__":
main()

View File

@ -1,13 +1,16 @@
#!/bin/bash
# exit on error
set -e
# print commands
#set -x
ARTIQ_PREFIX=$(python3 -c "import artiq; print(artiq.__path__[0])")
# Default is kc705
BOARD=kc705
while getopts "bBrht:d:" opt
while getopts "bBrht:d:f:" opt
do
case $opt in
b)
@ -19,6 +22,15 @@ do
r)
FLASH_RUNTIME=1
;;
f)
if [ -f $OPTARG ]
then
FILENAME=$OPTARG
else
echo "You specified a non-existing file to flash: $OPTARG"
exit 1
fi
;;
t)
if [ "$OPTARG" == "kc705" ]
then
@ -52,6 +64,7 @@ do
echo "-r Flash ARTIQ runtime"
echo "-h Show this help message"
echo "-t Target (kc705, pipistrello, default is: kc705)"
echo "-f Flash storage image generated with artiq_mkfs"
echo "-d Directory containing the binaries to be flashed"
exit 1
;;
@ -95,22 +108,24 @@ then
PROXY=bscan_spi_kc705.bit
BIOS_ADDR=0xaf0000
RUNTIME_ADDR=0xb00000
FS_ADDR=0xb40000
if [ -z "$BIN_PREFIX" ]; then BIN_PREFIX=$ARTIQ_PREFIX/binaries/kc705; fi
search_for_proxy $PROXY
elif [ "$BOARD" == "pipistrello" ]
then
UDEV_RULES=99-papilio.rules
BITSTREAM=artiq_pipistrello-nist_qc1-pipistrello.bin
BITSTREAM=artiq_pipistrello-nist_qc1-pipistrello.bit
CABLE=papilio
PROXY=bscan_spi_lx45_csg324.bit
BIOS_ADDR=0x170000
RUNTIME_ADDR=0x180000
FS_ADDR=0x1c0000
if [ -z "$BIN_PREFIX" ]; then BIN_PREFIX=$ARTIQ_PREFIX/binaries/pipistrello; fi
search_for_proxy $PROXY
fi
# Check if neither of -b|-B|-r have been used
if [ -z "$FLASH_RUNTIME" -a -z "$FLASH_BIOS" -a -z "$FLASH_BITSTREAM" ]
if [ -z "$FLASH_RUNTIME" -a -z "$FLASH_BIOS" -a -z "$FLASH_BITSTREAM" -a -z "$FILENAME" ]
then
FLASH_RUNTIME=1
FLASH_BIOS=1
@ -132,10 +147,16 @@ then
fi
set -e
if [ ! -z "$FILENAME" ]
then
echo "Flashing file $FILENAME at address $FS_ADDR"
xc3sprog -v -c $CABLE -I$PROXY_PATH/$PROXY $FILENAME:w:$FS_ADDR:BIN
fi
if [ "${FLASH_BITSTREAM}" == "1" ]
then
echo "Flashing FPGA bitstream..."
xc3sprog -v -c $CABLE -I$PROXY_PATH/$PROXY $BIN_PREFIX/$BITSTREAM:w:0x0:BIN
xc3sprog -v -c $CABLE -I$PROXY_PATH/$PROXY $BIN_PREFIX/$BITSTREAM:w:0x0:BIT
fi
if [ "${FLASH_BIOS}" == "1" ]

View File

@ -3,6 +3,7 @@
import argparse
import asyncio
import atexit
import os
# Quamash must be imported first so that pyqtgraph picks up the Qt binding
# it has chosen.
@ -12,9 +13,15 @@ from pyqtgraph import dockarea
from artiq.protocols.file_db import FlatFileDB
from artiq.protocols.pc_rpc import AsyncioClient
from artiq.gui.explorer import ExplorerDock
from artiq.gui.moninj import MonInj
from artiq.gui.results import ResultsDock
from artiq.gui.parameters import ParametersDock
from artiq.gui.log import LogDock
from artiq.gui.schedule import ScheduleDock
from artiq.gui.log import LogDock
data_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)),
"..", "gui")
def get_argparser():
@ -34,6 +41,17 @@ def get_argparser():
return parser
class _MainWindow(QtGui.QMainWindow):
def __init__(self, app):
QtGui.QMainWindow.__init__(self)
self.setWindowIcon(QtGui.QIcon(os.path.join(data_dir, "icon.png")))
self.resize(1400, 800)
self.setWindowTitle("ARTIQ")
self.exit_request = asyncio.Event()
def closeEvent(self, *args):
self.exit_request.set()
def main():
args = get_argparser().parse_args()
@ -49,38 +67,53 @@ def main():
args.server, args.port_control, "master_schedule"))
atexit.register(lambda: schedule_ctl.close_rpc())
win = QtGui.QMainWindow()
win = _MainWindow(app)
area = dockarea.DockArea()
win.setCentralWidget(area)
status_bar = QtGui.QStatusBar()
status_bar.showMessage("Connected to {}".format(args.server))
win.setStatusBar(status_bar)
win.resize(1400, 800)
win.setWindowTitle("ARTIQ")
d_explorer = ExplorerDock(status_bar, schedule_ctl)
area.addDock(d_explorer, "top")
d_explorer = ExplorerDock(win, status_bar, schedule_ctl)
loop.run_until_complete(d_explorer.sub_connect(
args.server, args.port_notify))
atexit.register(lambda: loop.run_until_complete(d_explorer.sub_close()))
d_results = ResultsDock(win, area)
loop.run_until_complete(d_results.sub_connect(
args.server, args.port_notify))
atexit.register(lambda: loop.run_until_complete(d_results.sub_close()))
d_ttl_dds = MonInj()
loop.run_until_complete(d_ttl_dds.start(args.server, args.port_notify))
atexit.register(lambda: loop.run_until_complete(d_ttl_dds.stop()))
d_params = ParametersDock()
area.addDock(d_params, "right", d_explorer)
loop.run_until_complete(d_params.sub_connect(
args.server, args.port_notify))
atexit.register(lambda: loop.run_until_complete(d_params.sub_close()))
d_log = LogDock()
area.addDock(d_log, "bottom")
area.addDock(d_ttl_dds.dds_dock, "top")
area.addDock(d_ttl_dds.ttl_dock, "above", d_ttl_dds.dds_dock)
area.addDock(d_results, "above", d_ttl_dds.ttl_dock)
area.addDock(d_params, "above", d_results)
area.addDock(d_explorer, "above", d_params)
d_schedule = ScheduleDock(schedule_ctl)
area.addDock(d_schedule, "above", d_log)
loop.run_until_complete(d_schedule.sub_connect(
args.server, args.port_notify))
atexit.register(lambda: loop.run_until_complete(d_schedule.sub_close()))
d_log = LogDock()
loop.run_until_complete(d_log.sub_connect(
args.server, args.port_notify))
atexit.register(lambda: loop.run_until_complete(d_log.sub_close()))
area.addDock(d_log, "bottom")
area.addDock(d_schedule, "above", d_log)
win.show()
loop.run_forever()
loop.run_until_complete(win.exit_request.wait())
if __name__ == "__main__":
main()

View File

@ -6,10 +6,10 @@ import atexit
import os
from artiq.protocols.pc_rpc import Server
from artiq.protocols.sync_struct import Publisher
from artiq.protocols.file_db import FlatFileDB, SimpleHistory
from artiq.protocols.sync_struct import Notifier, Publisher, process_mod
from artiq.protocols.file_db import FlatFileDB
from artiq.master.scheduler import Scheduler
from artiq.master.results import RTResults, get_last_rid
from artiq.master.worker_db import get_last_rid
from artiq.master.repository import Repository
from artiq.tools import verbosity_args, init_logger
@ -30,17 +30,21 @@ def get_argparser():
return parser
class Log:
def __init__(self, depth):
self.depth = depth
self.data = Notifier([])
def log(self, rid, message):
if len(self.data.read) >= self.depth:
del self.data[0]
self.data.append((rid, message))
log.worker_pass_rid = True
def main():
args = get_argparser().parse_args()
init_logger(args)
ddb = FlatFileDB("ddb.pyon")
pdb = FlatFileDB("pdb.pyon")
simplephist = SimpleHistory(30)
pdb.hooks.append(simplephist)
rtr = RTResults()
repository = Repository()
if os.name == "nt":
loop = asyncio.ProactorEventLoop()
asyncio.set_event_loop(loop)
@ -48,23 +52,31 @@ def main():
loop = asyncio.get_event_loop()
atexit.register(lambda: loop.close())
ddb = FlatFileDB("ddb.pyon")
pdb = FlatFileDB("pdb.pyon")
rtr = Notifier(dict())
log = Log(1000)
worker_handlers = {
"req_device": ddb.request,
"req_parameter": pdb.request,
"get_device": ddb.get,
"get_parameter": pdb.get,
"set_parameter": pdb.set,
"init_rt_results": rtr.init,
"update_rt_results": rtr.update,
"update_rt_results": lambda mod: process_mod(rtr, mod),
"log": log.log
}
scheduler = Scheduler(get_last_rid() + 1, worker_handlers)
worker_handlers["scheduler_submit"] = scheduler.submit
scheduler.start()
atexit.register(lambda: loop.run_until_complete(scheduler.stop()))
repository = Repository(log.log)
repository.scan_async()
server_control = Server({
"master_ddb": ddb,
"master_pdb": pdb,
"master_schedule": scheduler,
"master_repository": repository,
"master_repository": repository
})
loop.run_until_complete(server_control.start(
args.bind, args.port_control))
@ -74,9 +86,9 @@ def main():
"schedule": scheduler.notifier,
"devices": ddb.data,
"parameters": pdb.data,
"parameters_simplehist": simplephist.history,
"rt_results": rtr.groups,
"explist": repository.explist
"rt_results": rtr,
"explist": repository.explist,
"log": log.data
})
loop.run_until_complete(server_notify.start(
args.bind, args.port_notify))

View File

@ -20,16 +20,13 @@ def get_argparser():
def write_record(f, key, value):
key_size = len(key) + 1
value_size = len(value)
record_size = key_size + value_size + 4
f.write(struct.pack(">l", record_size))
f.write(key.encode())
f.write(b"\x00")
key_size = len(key) + 1
if key_size % 4:
f.write(bytes(4 - (key_size % 4)))
f.write(struct.pack(">l", len(value)))
f.write(value)
value_size = len(value)
if value_size % 4:
f.write(bytes(4 - (value_size % 4)))
def write_end_marker(f):

View File

@ -36,8 +36,11 @@ def list_targets(target_names, id_parameters):
def list_methods(remote):
methods = remote.get_rpc_method_list()
for name, (argspec, docstring) in sorted(methods.items()):
doc = remote.get_rpc_method_list()
if doc["docstring"] is not None:
print(doc["docstring"])
print()
for name, (argspec, docstring) in sorted(doc["methods"].items()):
args = ""
for arg in argspec["args"]:
args += arg

View File

@ -9,20 +9,19 @@ import logging
import h5py
from artiq.language.db import *
from artiq.language.experiment import Experiment
from artiq.language.environment import EnvExperiment
from artiq.protocols.file_db import FlatFileDB
from artiq.master.worker_db import DBHub, ResultDB
from artiq.master.worker_db import DeviceManager, ResultDB
from artiq.tools import *
logger = logging.getLogger(__name__)
class ELFRunner(Experiment, AutoDB):
class DBKeys:
core = Device()
file = Argument()
class ELFRunner(EnvExperiment):
def build(self):
self.attr_device("core")
self.attr_argument("file")
def run(self):
with open(self.file, "rb") as f:
@ -36,42 +35,21 @@ class SimpleParamLogger:
logger.info("Parameter change: {} = {}".format(name, value))
class DummyWatchdog:
def __init__(self, t):
pass
def __enter__(self):
pass
def __exit__(self, type, value, traceback):
pass
class DummyScheduler:
def __init__(self):
self.next_rid = 0
self.next_trid = 0
self.pipeline_name = "main"
self.priority = 0
self.expid = None
def run_queued(self, run_params):
def submit(self, pipeline_name, expid, priority, due_date, flush):
rid = self.next_rid
self.next_rid += 1
logger.info("Queuing: %s, RID=%s", run_params, rid)
logger.info("Submitting: %s, RID=%s", expid, rid)
return rid
def cancel_queued(self, rid):
logger.info("Cancelling RID %s", rid)
def run_timed(self, run_params, next_run):
trid = self.next_trid
self.next_trid += 1
next_run_s = time.strftime("%m/%d %H:%M:%S", time.localtime(next_run))
logger.info("Timing: %s at %s, TRID=%s", run_params, next_run_s, trid)
return trid
def cancel_timed(self, trid):
logger.info("Cancelling TRID %s", trid)
watchdog = DummyWatchdog
def delete(self, rid):
logger.info("Deleting RID %s", rid)
def get_argparser(with_file=True):
@ -98,7 +76,7 @@ def get_argparser(with_file=True):
return parser
def _build_experiment(dbh, args):
def _build_experiment(dmgr, pdb, rdb, args):
if hasattr(args, "file"):
if args.file.endswith(".elf"):
if args.arguments:
@ -106,7 +84,7 @@ def _build_experiment(dbh, args):
if args.experiment:
raise ValueError("experiment-by-name not supported "
"for ELF kernels")
return ELFRunner(dbh, file=args.file)
return ELFRunner(dmgr, pdb, rdb, file=args.file)
else:
module = file_import(args.file)
file = args.file
@ -115,37 +93,38 @@ def _build_experiment(dbh, args):
file = getattr(module, "__file__")
exp = get_experiment(module, args.experiment)
arguments = parse_arguments(args.arguments)
return exp(dbh,
scheduler=DummyScheduler(),
run_params=dict(file=file,
experiment=args.experiment,
arguments=arguments),
**arguments)
expid = {
"file": file,
"experiment": args.experiment,
"arguments": arguments
}
dmgr.virtual_devices["scheduler"].expid = expid
return exp(dmgr, pdb, rdb, **arguments)
def run(with_file=False):
args = get_argparser(with_file).parse_args()
init_logger(args)
ddb = FlatFileDB(args.ddb)
dmgr = DeviceManager(FlatFileDB(args.ddb),
virtual_devices={"scheduler": DummyScheduler()})
pdb = FlatFileDB(args.pdb)
pdb.hooks.append(SimpleParamLogger())
rdb = ResultDB(lambda description: None, lambda mod: None)
dbh = DBHub(ddb, pdb, rdb)
rdb = ResultDB()
try:
exp_inst = _build_experiment(dbh, args)
rdb.build()
exp_inst = _build_experiment(dmgr, pdb, rdb, args)
exp_inst.prepare()
exp_inst.run()
exp_inst.analyze()
finally:
dbh.close_devices()
dmgr.close_devices()
if args.hdf5 is not None:
with h5py.File(args.hdf5, "w") as f:
rdb.write_hdf5(f)
elif rdb.data.read or rdb.realtime_data.read:
r = chain(rdb.realtime_data.read.items(), rdb.data.read.items())
elif rdb.rt.read or rdb.nrt:
r = chain(rdb.rt.read.items(), rdb.nrt.items())
for k, v in sorted(r, key=itemgetter(0)):
print("{}: {}".format(k, v))

View File

@ -16,7 +16,13 @@ def get_argparser():
simple_network_args(parser, 3253)
parser.add_argument("-d", "--device", default=None,
help="USB serial number of the device. "
" Omit for simulation mode.")
"The serial number is written on a sticker under "
"the device, you should write for example "
"-d \"SN:03461\". You must prepend enough 0s for "
"it to be 5 digits. If omitted, the first "
"available device will be used.")
parser.add_argument("--simulation", action="store_true",
help="Put the driver in simulation mode.")
verbosity_args(parser)
return parser
@ -24,7 +30,7 @@ def get_argparser():
def main():
args = get_argparser().parse_args()
init_logger(args)
if args.device is None:
if args.simulation:
lda = Ldasim()
else:
lda = Lda(args.device, args.product)

View File

@ -4,6 +4,7 @@
import argparse
import logging
import sys
from artiq.devices.novatech409b.driver import Novatech409B
from artiq.protocols.pc_rpc import simple_server_loop
@ -19,7 +20,10 @@ def get_argparser():
simple_network_args(parser, 3254)
parser.add_argument(
"-d", "--device", default=None,
help="serial port. Omit for simulation mode.")
help="serial port.")
parser.add_argument(
"--simulation", action="store_true",
help="Put the driver in simulation mode, even if --device is used.")
verbosity_args(parser)
return parser
@ -28,7 +32,12 @@ def main():
args = get_argparser().parse_args()
init_logger(args)
dev = Novatech409B(args.device)
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)
dev = Novatech409B(args.device if not args.simulation else None)
try:
simple_server_loop(
{"novatech409b": dev}, args.bind, args.port)

View File

@ -1,6 +1,7 @@
#!/usr/bin/env python3
import argparse
import sys
from artiq.devices.pdq2.driver import Pdq2
from artiq.protocols.pc_rpc import simple_server_loop
@ -12,7 +13,10 @@ def get_argparser():
simple_network_args(parser, 3252)
parser.add_argument(
"-d", "--device", default=None,
help="serial port. Omit for simulation mode.")
help="serial port.")
parser.add_argument(
"--simulation", action="store_true",
help="Put the driver in simulation mode, even if --device is used.")
parser.add_argument(
"--dump", default="pdq2_dump.bin",
help="file to dump pdq2 data into, for later simulation")
@ -24,7 +28,13 @@ def main():
args = get_argparser().parse_args()
init_logger(args)
port = None
if args.device is 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 = Pdq2(url=args.device, dev=port)
try:

View File

@ -2,6 +2,7 @@
# Yann Sionneau <ys@m-labs.hk>, 2015
import argparse
import sys
from artiq.protocols.pc_rpc import simple_server_loop
from artiq.devices.pxi6733.driver import DAQmx, DAQmxSim
@ -11,13 +12,13 @@ from artiq.tools import verbosity_args, init_logger, simple_network_args
def get_argparser():
parser = argparse.ArgumentParser(description="NI PXI 6733 controller")
simple_network_args(parser, 3256)
parser.add_argument("-d", "--device", default=None,
help="Device name (e.g. Dev1)."
" Omit for simulation mode.")
parser.add_argument("-C", "--channels", default=None,
help="List of channels (e.g. Dev1/ao0, Dev1/ao1:3).")
parser.add_argument("-c", "--clock", default="PFI5",
help="Input clock pin name (default: PFI5)")
parser.add_argument("-a", "--analog-output", default="ao0",
help="Analog output pin name (default: ao0)")
parser.add_argument("--simulation", action='store_true',
help="Put the driver in simulation mode, even if "
"--channels is used.")
verbosity_args(parser)
return parser
@ -26,12 +27,16 @@ def main():
args = get_argparser().parse_args()
init_logger(args)
if args.device is None:
if not args.simulation and args.channels is None:
print("You need to specify either --simulation or -C/--channels "
"argument. Use --help for more information.")
sys.exit(1)
if args.simulation:
daq = DAQmxSim()
else:
daq = DAQmx(bytes(args.device, "ascii"),
bytes(args.analog_output, "ascii"),
bytes(args.clock, "ascii"))
daq = DAQmx(args.channels,
args.clock)
try:
simple_server_loop({"pxi6733": daq},

View File

@ -1,6 +1,7 @@
#!/usr/bin/env python3
import argparse
import sys
from artiq.devices.thorlabs_tcube.driver import Tdc, Tpz, TdcSim, TpzSim
from artiq.protocols.pc_rpc import simple_server_loop
@ -13,7 +14,11 @@ def get_argparser():
help="type of the Thorlabs T-Cube device to control",
choices=["TDC001", "TPZ001"])
parser.add_argument("-d", "--device", default=None,
help="serial port. Omit for simulation mode.")
help="serial device. See documentation for how to "
"specify a USB Serial Number.")
parser.add_argument("--simulation", action="store_true",
help="Put the driver in simulation mode, even if "
"--device is used.")
simple_network_args(parser, 3255)
verbosity_args(parser)
return parser
@ -23,7 +28,12 @@ def main():
args = get_argparser().parse_args()
init_logger(args)
if args.device is 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:
if args.product == "TDC001":
dev = TdcSim()
elif args.product == "TPZ001":

View File

@ -6,15 +6,14 @@ from migen.bus.transactions import *
from migen.sim.generic import run_simulation
class AD9858(Module):
"""Wishbone interface to the AD9858 DDS chip.
class AD9xxx(Module):
"""Wishbone interface to the AD9858 and AD9914 DDS chips.
Addresses 0-63 map the AD9858 registers.
Data is zero-padded.
Addresses 0-2**flen(pads.a)-1 map the AD9xxx registers.
Write to address 64 to pulse the FUD signal.
Address 65 is a GPIO register that controls the sel, p and reset signals.
sel is mapped to the lower bits, followed by p and reset.
Write to address 2**flen(pads.a) to pulse the FUD signal.
Address 2**flen(pads.a)+1 is a GPIO register that controls the
sel and reset signals. rst is mapped to bit 0, followed by sel.
Write timing:
Address is set one cycle before assertion of we_n.
@ -28,6 +27,7 @@ class AD9858(Module):
Design:
All IO pads are registered.
With QC1 adapter:
LVDS driver/receiver propagation delays are 3.6+4.5 ns max
LVDS state transition delays are 20, 15 ns max
Schmitt trigger delays are 6.4ns max
@ -38,15 +38,15 @@ class AD9858(Module):
read_wait_cycles=10, hiz_wait_cycles=3,
bus=None):
if bus is None:
bus = wishbone.Interface()
bus = wishbone.Interface(data_width=flen(pads.d))
self.bus = bus
# # #
dts = TSTriple(8)
dts = TSTriple(flen(pads.d))
self.specials += dts.get_tristate(pads.d)
hold_address = Signal()
dr = Signal(8)
dr = Signal(flen(pads.d))
rx = Signal()
self.sync += [
If(~hold_address, pads.a.eq(bus.adr)),
@ -55,13 +55,14 @@ class AD9858(Module):
dts.oe.eq(~rx)
]
gpio = Signal(flen(pads.sel) + flen(pads.p) + 1)
gpio = Signal(flen(pads.sel) + 1)
gpio_load = Signal()
self.sync += If(gpio_load, gpio.eq(bus.dat_w))
self.comb += [
Cat(pads.sel, pads.p).eq(gpio),
pads.rst_n.eq(~gpio[-1]),
]
if hasattr(pads, "rst"):
self.comb += pads.rst.eq(gpio[0])
else:
self.comb += pads.rst_n.eq(~gpio[0])
self.comb += pads.sel.eq(gpio[1:])
bus_r_gpio = Signal()
self.comb += If(bus_r_gpio,
@ -71,6 +72,9 @@ class AD9858(Module):
)
fud = Signal()
if hasattr(pads, "fud"):
self.sync += pads.fud.eq(fud)
else:
self.sync += pads.fud_n.eq(~fud)
pads.wr_n.reset = 1
@ -87,7 +91,7 @@ class AD9858(Module):
fsm.act("IDLE",
If(bus.cyc & bus.stb,
If(bus.adr[6],
If(bus.adr[flen(pads.a)],
If(bus.adr[0],
NextState("GPIO")
).Else(
@ -168,7 +172,6 @@ class _TestPads:
self.a = Signal(6)
self.d = Signal(8)
self.sel = Signal(5)
self.p = Signal(2)
self.fud_n = Signal()
self.wr_n = Signal()
self.rd_n = Signal()
@ -178,11 +181,11 @@ class _TestPads:
class _TB(Module):
def __init__(self):
pads = _TestPads()
self.submodules.dut = AD9858(pads, drive_fud=True)
self.submodules.dut = AD9xxx(pads, drive_fud=True)
self.submodules.initiator = wishbone.Initiator(_test_gen())
self.submodules.interconnect = wishbone.InterconnectPointToPoint(
self.initiator.bus, self.dut.bus)
if __name__ == "__main__":
run_simulation(_TB(), vcd_name="ad9858.vcd")
run_simulation(_TB(), vcd_name="ad9xxx.vcd")

View File

@ -3,12 +3,11 @@ from migen.bank.description import *
from migen.bus import wishbone
from misoclib.cpu import mor1kx
from misoclib.mem.sdram.frontend.wishbone2lasmi import WB2LASMI
from misoclib.soc import mem_decoder
class KernelCPU(Module):
def __init__(self, platform, lasmim,
def __init__(self, platform,
exec_address=0x40400000,
main_mem_origin=0x40000000,
l2_size=8192):
@ -29,16 +28,8 @@ class KernelCPU(Module):
"sys_kernel")
# DRAM access
# XXX Vivado 2014.X workaround
from mibuild.xilinx.vivado import XilinxVivadoToolchain
if isinstance(platform.toolchain, XilinxVivadoToolchain):
from migen.fhdl.simplify import FullMemoryWE
self.submodules.wishbone2lasmi = FullMemoryWE()(
WB2LASMI(l2_size//4, lasmim))
else:
self.submodules.wishbone2lasmi = WB2LASMI(l2_size//4, lasmim)
self.add_wb_slave(mem_decoder(main_mem_origin),
self.wishbone2lasmi.wishbone)
self.wb_sdram = wishbone.Interface()
self.add_wb_slave(mem_decoder(main_mem_origin), self.wb_sdram)
def get_csrs(self):
return [self._reset]

View File

@ -4,10 +4,22 @@ from mibuild.generic_platform import *
papilio_adapter_io = [
("ext_led", 0, Pins("B:7"), IOStandard("LVTTL")),
# to feed the 125 MHz clock (preferrably from DDS SYNC_CLK)
# to the FPGA, use the xtrig pair.
#
# on papiliopro-adapter, xtrig (C:12) is connected to a GCLK
#
# on pipistrello, C:15 is the only GCLK in proximity, used as a button
# input, BTN2/PMT2 in papiliopro-adapter
# either improve the DDS box to feed 125MHz into the PMT2 pair, or:
#
# * disconnect C:15 from its periphery on the adapter board
# * bridge C:15 to the xtrig output of the transciever
# * optionally, disconnect C:12 from its periphery
("xtrig", 0, Pins("C:12"), IOStandard("LVTTL")),
("pmt", 0, Pins("C:13"), IOStandard("LVTTL")),
("pmt", 1, Pins("C:14"), IOStandard("LVTTL")),
("xtrig", 0, Pins("C:12"), IOStandard("LVTTL")),
("dds_clock", 0, Pins("C:15"), IOStandard("LVTTL")), # PMT2
("pmt", 2, Pins("C:15"), IOStandard("LVTTL")), # rarely equipped
("ttl", 0, Pins("C:11"), IOStandard("LVTTL")),
("ttl", 1, Pins("C:10"), IOStandard("LVTTL")),

View File

@ -0,0 +1,78 @@
from mibuild.generic_platform import *
fmc_adapter_io = [
("ttl", 0, Pins("LPC:LA00_CC_P"), IOStandard("LVTTL")),
("ttl", 1, Pins("LPC:LA02_P"), IOStandard("LVTTL")),
("ttl", 2, Pins("LPC:LA00_CC_N"), IOStandard("LVTTL")),
("ttl", 3, Pins("LPC:LA02_N"), IOStandard("LVTTL")),
("ttl", 4, Pins("LPC:LA01_CC_P"), IOStandard("LVTTL")),
("ttl", 5, Pins("LPC:LA01_CC_N"), IOStandard("LVTTL")),
("ttl", 6, Pins("LPC:LA06_P"), IOStandard("LVTTL")),
("ttl", 7, Pins("LPC:LA06_N"), IOStandard("LVTTL")),
("ttl", 8, Pins("LPC:LA05_P"), IOStandard("LVTTL")),
("ttl", 9, Pins("LPC:LA05_N"), IOStandard("LVTTL")),
("ttl", 10, Pins("LPC:LA10_P"), IOStandard("LVTTL")),
("ttl", 11, Pins("LPC:LA09_P"), IOStandard("LVTTL")),
("ttl", 12, Pins("LPC:LA10_N"), IOStandard("LVTTL")),
("ttl", 13, Pins("LPC:LA09_N"), IOStandard("LVTTL")),
("ttl", 14, Pins("LPC:LA13_P"), IOStandard("LVTTL")),
("ttl", 15, Pins("LPC:LA14_P"), IOStandard("LVTTL")),
("dds", 0,
Subsignal("a", Pins("LPC:LA22_N LPC:LA21_P LPC:LA22_P LPC:LA19_N "
"LPC:LA20_N LPC:LA19_P LPC:LA20_P")),
Subsignal("d", Pins("LPC:LA15_N LPC:LA16_N LPC:LA15_P LPC:LA16_P "
"LPC:LA11_N LPC:LA12_N LPC:LA11_P LPC:LA12_P "
"LPC:LA07_N LPC:LA08_N LPC:LA07_P LPC:LA08_P "
"LPC:LA04_N LPC:LA03_N LPC:LA04_P LPC:LA03_P")),
Subsignal("sel", Pins("LPC:LA24_N LPC:LA29_P LPC:LA28_P LPC:LA29_N "
"LPC:LA28_N LPC:LA31_P LPC:LA30_P LPC:LA31_N "
"LPC:LA30_N LPC:LA33_P LPC:LA33_N")),
Subsignal("fud", Pins("LPC:LA21_N")),
Subsignal("wr_n", Pins("LPC:LA24_P")),
Subsignal("rd_n", Pins("LPC:LA25_N")),
Subsignal("rst", Pins("LPC:LA25_P")),
IOStandard("LVTTL")),
("i2c", 0,
Subsignal("scl", Pins("LPC:IIC_SLC")),
Subsignal("sda", Pins("LPC:IIC_SDA")),
IOStandard("LVCMOS25")),
("clk_m2c", 0,
Subsignal("p", Pins("LPC:CLK0_M2C_P")),
Subsignal("n", Pins("LPC:CLK0_M2C_N")),
IOStandard("LVDS")),
("clk_m2c", 1,
Subsignal("p", Pins("LPC:CLK1_M2C_P")),
Subsignal("n", Pins("LPC:CLK1_M2C_N")),
IOStandard("LVDS")),
("la32", 0,
Subsignal("p", Pins("LPC:LA32_P")),
Subsignal("n", Pins("LPC:LA32_N")),
IOStandard("LVDS")),
("spi", 0,
Subsignal("clk", Pins("LPC:LA13_N")),
Subsignal("ce", Pins("LPC:LA14_N")),
Subsignal("mosi", Pins("LPC:LA17_CC_P")),
Subsignal("miso", Pins("LPC:LA17_CC_N")),
IOStandard("LVTTL")),
("spi", 1,
Subsignal("clk", Pins("LPC:LA18_CC_P")),
Subsignal("ce", Pins("LPC:LA18_CC_N")),
Subsignal("mosi", Pins("LPC:LA23_P")),
Subsignal("miso", Pins("LPC:LA23_N")),
IOStandard("LVTTL")),
("spi", 2,
Subsignal("clk", Pins("LPC:LA27_P")),
Subsignal("ce", Pins("LPC:LA26_P")),
Subsignal("mosi", Pins("LPC:LA27_N")),
Subsignal("miso", Pins("LPC:LA26_N")),
IOStandard("LVTTL")),
]

View File

@ -1 +1,2 @@
from artiq.gateware.rtio.core import Channel, RTIO
from artiq.gateware.rtio.moninj import MonInj

View File

@ -44,7 +44,8 @@ class _RTIOCounter(Module):
# # #
self.sync.rio += self.value_rio.eq(self.value_rio + 1),
# note: counter is in rtio domain and never affected by the reset CSRs
self.sync.rtio += self.value_rio.eq(self.value_rio + 1)
gt = _GrayCodeTransfer(width)
self.submodules += gt
self.comb += gt.i.eq(self.value_rio), self.value_sys.eq(gt.o)
@ -121,12 +122,20 @@ class _OutputManager(Module):
sequence_error.eq(self.ev.timestamp < buf.timestamp[fine_ts_width:])
]
if interface.suppress_nop:
self.sync.rsys += nop.eq(
# disable NOP at reset: do not suppress a first write with all 0s
nop_en = Signal(reset=0)
self.sync.rsys += [
nop.eq(nop_en &
optree("&",
[getattr(self.ev, a) == getattr(buf, a)
for a in ("data", "address")
if hasattr(self.ev, a)],
default=0))
default=0)),
# buf now contains valid data. enable NOP.
If(self.we & ~sequence_error, nop_en.eq(1)),
# underflows cancel the write. allow it to be retried.
If(self.underflow, nop_en.eq(0))
]
self.comb += self.sequence_error.eq(self.we & sequence_error)
# Buffer read and FIFO write
@ -247,11 +256,20 @@ class _InputManager(Module):
class Channel:
def __init__(self, interface, ofifo_depth=64, ififo_depth=64):
def __init__(self, interface, probes=[], overrides=[],
ofifo_depth=64, ififo_depth=64):
self.interface = interface
self.probes = probes
self.overrides = overrides
self.ofifo_depth = ofifo_depth
self.ififo_depth = ififo_depth
@classmethod
def from_phy(cls, phy, **kwargs):
probes = getattr(phy, "probes", [])
overrides = getattr(phy, "overrides", [])
return cls(phy.rtlink, probes, overrides, **kwargs)
class _KernelCSRs(AutoCSR):
def __init__(self, chan_sel_width,

View File

@ -0,0 +1,68 @@
from migen.fhdl.std import *
from migen.bank.description import *
from migen.genlib.cdc import BusSynchronizer, MultiReg
class Monitor(Module, AutoCSR):
def __init__(self, channels):
chan_probes = [c.probes for c in channels]
max_chan_probes = max(len(cp) for cp in chan_probes)
max_probe_len = max(flen(p) for cp in chan_probes for p in cp)
self.chan_sel = CSRStorage(bits_for(len(chan_probes)-1))
self.probe_sel = CSRStorage(bits_for(max_chan_probes-1))
self.value_update = CSR()
self.value = CSRStatus(max_probe_len)
# # #
chan_probes_sys = []
for cp in chan_probes:
cp_sys = []
for p in cp:
vs = BusSynchronizer(flen(p), "rio", "rsys")
self.submodules += vs
self.comb += vs.i.eq(p)
cp_sys.append(vs.o)
cp_sys += [0]*(max_chan_probes-len(cp))
chan_probes_sys.append(Array(cp_sys)[self.probe_sel.storage])
self.sync += If(self.value_update.re,
self.value.status.eq(
Array(chan_probes_sys)[self.chan_sel.storage]))
class Injector(Module, AutoCSR):
def __init__(self, channels):
chan_overrides = [c.overrides for c in channels]
max_chan_overrides = max(len(co) for co in chan_overrides)
max_override_len = max(flen(o) for co in chan_overrides for o in co)
self.chan_sel = CSRStorage(bits_for(len(chan_overrides)-1))
self.override_sel = CSRStorage(bits_for(max_chan_overrides-1))
self.value = CSR(max_override_len)
# # #
chan_overrides_sys = []
for n_channel, co in enumerate(chan_overrides):
co_sys = []
for n_override, o in enumerate(co):
# We do the clock domain transfer with a simple double-latch.
# Software has to ensure proper timing of any strobe signal etc.
# to avoid problematic glitches.
o_sys = Signal.like(o)
self.specials += MultiReg(o_sys, o, "rio")
self.sync += If(self.value.re & (self.chan_sel.storage == n_channel)
& (self.override_sel.storage == n_override),
o_sys.eq(self.value.r))
co_sys.append(o_sys)
co_sys += [0]*(max_chan_overrides-len(co))
chan_overrides_sys.append(Array(co_sys)[self.override_sel.storage])
self.comb += self.value.w.eq(
Array(chan_overrides_sys)[self.chan_sel.storage])
class MonInj(Module, AutoCSR):
def __init__(self, channels):
self.submodules.mon = Monitor(channels)
self.submodules.inj = Injector(channels)

View File

@ -0,0 +1,60 @@
from migen.fhdl.std import *
from artiq.gateware import ad9xxx
from artiq.gateware.rtio.phy.wishbone import RT2WB
class _AD9xxx(Module):
def __init__(self, ftw_base, pads, nchannels, **kwargs):
self.submodules._ll = RenameClockDomains(
ad9xxx.AD9xxx(pads, **kwargs), "rio")
self.submodules._rt2wb = RT2WB(flen(pads.a)+1, self._ll.bus)
self.rtlink = self._rt2wb.rtlink
self.probes = [Signal(32) for i in range(nchannels)]
# # #
# buffer the current address/data on the rtlink output
current_address = Signal.like(self.rtlink.o.address)
current_data = Signal.like(self.rtlink.o.data)
self.sync.rio += If(self.rtlink.o.stb,
current_address.eq(self.rtlink.o.address),
current_data.eq(self.rtlink.o.data))
# keep track of the currently selected channel
current_channel = Signal(max=nchannels)
self.sync.rio += If(current_address == 2**flen(pads.a) + 1,
current_channel.eq(current_data))
# keep track of frequency tuning words, before they are FUDed
ftws = [Signal(32) for i in range(nchannels)]
for c, ftw in enumerate(ftws):
if flen(pads.d) == 8:
self.sync.rio += \
If(current_channel == c, [
If(current_address == ftw_base+i,
ftw[i*8:(i+1)*8].eq(current_data))
for i in range(4)])
elif flen(pads.d) == 16:
self.sync.rio += \
If(current_channel == c, [
If(current_address == ftw_base+2*i,
ftw[i*16:(i+1)*16].eq(current_data))
for i in range(2)])
else:
raise NotImplementedError
# FTW to probe on FUD
self.sync.rio += If(current_address == 2**flen(pads.a), [
If(current_channel == c, probe.eq(ftw))
for c, (probe, ftw) in enumerate(zip(self.probes, ftws))])
class AD9858(_AD9xxx):
def __init__(self, pads, nchannels, **kwargs):
_AD9xxx.__init__(self, 0x0a, pads, nchannels, **kwargs)
class AD9914(_AD9xxx):
def __init__(self, pads, nchannels, **kwargs):
_AD9xxx.__init__(self, 0x2d, pads, nchannels, **kwargs)

View File

@ -7,10 +7,24 @@ from artiq.gateware.rtio import rtlink
class Output(Module):
def __init__(self, pad):
self.rtlink = rtlink.Interface(rtlink.OInterface(1))
self.probes = [pad]
override_en = Signal()
override_o = Signal()
self.overrides = [override_en, override_o]
# # #
self.sync.rio_phy += If(self.rtlink.o.stb, pad.eq(self.rtlink.o.data))
pad_k = Signal()
self.sync.rio_phy += [
If(self.rtlink.o.stb,
pad_k.eq(self.rtlink.o.data)
),
If(override_en,
pad.eq(override_o)
).Else(
pad.eq(pad_k)
)
]
class Inout(Module):
@ -18,6 +32,11 @@ class Inout(Module):
self.rtlink = rtlink.Interface(
rtlink.OInterface(2, 2),
rtlink.IInterface(1))
override_en = Signal()
override_o = Signal()
override_oe = Signal()
self.overrides = [override_en, override_o, override_oe]
self.probes = []
# # #
@ -25,10 +44,21 @@ class Inout(Module):
self.specials += ts.get_tristate(pad)
sensitivity = Signal(2)
self.sync.rio_phy += If(self.rtlink.o.stb,
If(self.rtlink.o.address == 0, ts.o.eq(self.rtlink.o.data[0])),
If(self.rtlink.o.address == 1, ts.oe.eq(self.rtlink.o.data[0])),
o_k = Signal()
oe_k = Signal()
self.sync.rio_phy += [
If(self.rtlink.o.stb,
If(self.rtlink.o.address == 0, o_k.eq(self.rtlink.o.data[0])),
If(self.rtlink.o.address == 1, oe_k.eq(self.rtlink.o.data[0])),
),
If(override_en,
ts.o.eq(override_o),
ts.oe.eq(override_oe)
).Else(
ts.o.eq(o_k),
ts.oe.eq(oe_k)
)
]
self.sync.rio += If(self.rtlink.o.stb & (self.rtlink.o.address == 2),
sensitivity.eq(self.rtlink.o.data))
@ -43,3 +73,31 @@ class Inout(Module):
),
self.rtlink.i.data.eq(i)
]
self.probes += [i, ts.oe]
class ClockGen(Module):
def __init__(self, pad, ftw_width=24):
self.rtlink = rtlink.Interface(
rtlink.OInterface(ftw_width, suppress_nop=False))
# # #
ftw = Signal(ftw_width)
acc = Signal(ftw_width)
self.sync.rio += If(self.rtlink.o.stb, ftw.eq(self.rtlink.o.data))
self.sync.rio_phy += [
acc.eq(acc + ftw),
# rtlink takes precedence over regular acc increments
If(self.rtlink.o.stb,
If(self.rtlink.o.data != 0,
# known phase on frequency write: at rising edge
acc.eq(2**(ftw_width - 1))
).Else(
# set output to 0 on stop
acc.eq(0)
)
),
pad.eq(acc[-1])
]

View File

@ -19,8 +19,9 @@ class AMPSoC:
self.submodules.timer0 = timer.Timer(width=64)
self.submodules.kernel_cpu = amp.KernelCPU(
self.platform, self.sdram.crossbar.get_master())
self.submodules.kernel_cpu = amp.KernelCPU(self.platform)
self.add_wb_sdram_if(self.kernel_cpu.wb_sdram)
self.submodules.mailbox = amp.Mailbox()
self.add_wb_slave(mem_decoder(self.mem_map["mailbox"]),
self.mailbox.i1)

130
artiq/gui/displays.py Normal file
View File

@ -0,0 +1,130 @@
from collections import OrderedDict
from quamash import QtGui
import pyqtgraph as pg
from pyqtgraph import dockarea
class _SimpleSettings(QtGui.QDialog):
def __init__(self, parent, prev_name, prev_settings,
result_list, create_cb):
QtGui.QDialog.__init__(self, parent=parent)
self.setWindowTitle(self._window_title)
grid = QtGui.QGridLayout()
self.setLayout(grid)
grid.addWidget(QtGui.QLabel("Name:"), 0, 0)
self.name = name = QtGui.QLineEdit()
grid.addWidget(name, 0, 1)
if prev_name is not None:
name.insert(prev_name)
grid.addWidget(QtGui.QLabel("Result:"))
self.result = result = QtGui.QComboBox()
grid.addWidget(result, 1, 1)
result.addItems(result_list)
result.setEditable(True)
if "result" in prev_settings:
result.setEditText(prev_settings["result"])
buttons = QtGui.QDialogButtonBox(
QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
grid.addWidget(buttons, 2, 0, 1, 2)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
def on_accept():
create_cb(name.text(), {"result": result.currentText()})
self.accepted.connect(on_accept)
def accept(self):
if self.name.text() and self.result.currentText():
QtGui.QDialog.accept(self)
class NumberDisplaySettings(_SimpleSettings):
_window_title = "Number display"
class NumberDisplay(dockarea.Dock):
def __init__(self, name, settings):
dockarea.Dock.__init__(self, "Display: " + name, size=(250, 250),
closable=True)
self.settings = settings
self.number = QtGui.QLCDNumber()
self.number.setDigitCount(10)
self.addWidget(self.number)
def data_sources(self):
return {self.settings["result"]}
def update_data(self, data):
result = self.settings["result"]
try:
n = float(data[result])
except:
n = "---"
self.number.display(n)
class XYDisplaySettings(_SimpleSettings):
_window_title = "XY plot"
class XYDisplay(dockarea.Dock):
def __init__(self, name, settings):
dockarea.Dock.__init__(self, "XY: " + name, size=(640, 480),
closable=True)
self.settings = settings
self.plot = pg.PlotWidget()
self.addWidget(self.plot)
def data_sources(self):
return {self.settings["result"]}
def update_data(self, data):
result = self.settings["result"]
try:
y = data[result]
except KeyError:
return
self.plot.clear()
if not y:
return
self.plot.plot(y)
class HistogramDisplaySettings(_SimpleSettings):
_window_title = "Histogram"
class HistogramDisplay(dockarea.Dock):
def __init__(self, name, settings):
dockarea.Dock.__init__(self, "Histogram: " + name, size=(640, 480),
closable=True)
self.settings = settings
self.plot = pg.PlotWidget()
self.addWidget(self.plot)
def data_sources(self):
return {self.settings["result"]}
def update_data(self, data):
result = self.settings["result"]
try:
y = data[result]
except KeyError:
return
x = list(range(len(y)+1))
self.plot.clear()
if not y:
return
self.plot.plot(x, y, stepMode=True, fillLevel=0, brush=(0, 0, 255, 150))
display_types = OrderedDict([
("Number", (NumberDisplaySettings, NumberDisplay)),
("XY", (XYDisplaySettings, XYDisplay)),
("Histogram", (HistogramDisplaySettings, HistogramDisplay))
])

View File

@ -1,11 +1,14 @@
import asyncio
import traceback
from quamash import QtGui, QtCore
from pyqtgraph import dockarea
from pyqtgraph import LayoutWidget
from artiq.protocols.sync_struct import Subscriber
from artiq.protocols import pyon
from artiq.gui.tools import DictSyncModel
from artiq.gui.scan import ScanController
class _ExplistModel(DictSyncModel):
@ -21,46 +24,175 @@ class _ExplistModel(DictSyncModel):
return k
class ExplorerDock(dockarea.Dock):
def __init__(self, status_bar, schedule_ctl):
dockarea.Dock.__init__(self, "Explorer", size=(1100, 400))
class _FreeValueEntry(QtGui.QLineEdit):
def __init__(self, procdesc):
QtGui.QLineEdit.__init__(self)
if "default" in procdesc:
self.insert(pyon.encode(procdesc["default"]))
def get_argument_value(self):
return pyon.decode(self.text())
class _BooleanEntry(QtGui.QCheckBox):
def __init__(self, procdesc):
QtGui.QCheckBox.__init__(self)
if "default" in procdesc:
self.setChecked(procdesc["default"])
def get_argument_value(self):
return self.isChecked()
class _EnumerationEntry(QtGui.QComboBox):
def __init__(self, procdesc):
QtGui.QComboBox.__init__(self)
self.choices = procdesc["choices"]
self.addItems(self.choices)
if "default" in procdesc:
try:
idx = self.choices.index(procdesc["default"])
except:
pass
else:
self.setCurrentIndex(idx)
def get_argument_value(self):
return self.choices[self.currentIndex()]
class _NumberEntry(QtGui.QDoubleSpinBox):
def __init__(self, procdesc):
QtGui.QDoubleSpinBox.__init__(self)
if procdesc["step"] is not None:
self.setSingleStep(procdesc["step"])
if procdesc["min"] is not None:
self.setMinimum(procdesc["min"])
if procdesc["max"] is not None:
self.setMinimum(procdesc["max"])
if procdesc["unit"]:
self.setSuffix(" " + procdesc["unit"])
if "default" in procdesc:
self.setValue(procdesc["default"])
def get_argument_value(self):
return self.value()
class _StringEntry(QtGui.QLineEdit):
def __init__(self, procdesc):
QtGui.QLineEdit.__init__(self)
if "default" in procdesc:
self.insert(procdesc["default"])
def get_argument_value(self):
return self.text()
_procty_to_entry = {
"FreeValue": _FreeValueEntry,
"BooleanValue": _BooleanEntry,
"EnumerationValue": _EnumerationEntry,
"NumberValue": _NumberEntry,
"StringValue": _StringEntry,
"Scannable": ScanController
}
class _ArgumentSetter(LayoutWidget):
def __init__(self, dialog_parent, arguments):
LayoutWidget.__init__(self)
self.dialog_parent = dialog_parent
if not arguments:
self.addWidget(QtGui.QLabel("No arguments"), 0, 0)
self._args_to_entries = dict()
for n, (name, procdesc) in enumerate(arguments):
self.addWidget(QtGui.QLabel(name), n, 0)
entry = _procty_to_entry[procdesc["ty"]](procdesc)
self.addWidget(entry, n, 1)
self._args_to_entries[name] = entry
def get_argument_values(self):
r = dict()
for arg, entry in self._args_to_entries.items():
try:
r[arg] = entry.get_argument_value()
except:
msgbox = QtGui.QMessageBox(self.dialog_parent)
msgbox.setWindowTitle("Error")
msgbox.setText("Failed to obtain value for argument '{}'.\n{}"
.format(arg, traceback.format_exc()))
msgbox.setStandardButtons(QtGui.QMessageBox.Ok)
msgbox.show()
return None
return r
class ExplorerDock(dockarea.Dock):
def __init__(self, dialog_parent, status_bar, schedule_ctl):
dockarea.Dock.__init__(self, "Explorer", size=(1500, 500))
self.dialog_parent = dialog_parent
self.status_bar = status_bar
self.schedule_ctl = schedule_ctl
splitter = QtGui.QSplitter(QtCore.Qt.Horizontal)
self.addWidget(splitter)
self.splitter = QtGui.QSplitter(QtCore.Qt.Horizontal)
self.addWidget(self.splitter)
grid = LayoutWidget()
splitter.addWidget(grid)
self.splitter.addWidget(grid)
self.el = QtGui.QListView()
self.el.selectionChanged = self.update_argsetter
grid.addWidget(self.el, 0, 0, colspan=4)
self.datetime = QtGui.QDateTimeEdit()
self.datetime.setDisplayFormat("MMM d yyyy hh:mm:ss")
self.datetime.setCalendarPopup(True)
self.datetime.setDate(QtCore.QDate.currentDate())
self.datetime_en = QtGui.QCheckBox("Set due date:")
self.datetime.dateTimeChanged.connect(self.enable_duedate)
self.datetime_en = QtGui.QCheckBox("Due date:")
grid.addWidget(self.datetime_en, 1, 0)
grid.addWidget(self.datetime, 1, 1, colspan=3)
self.pipeline = QtGui.QLineEdit()
self.pipeline.insert("main")
grid.addLabel("Pipeline:", 2, 0)
grid.addWidget(self.pipeline, 2, 1)
grid.addWidget(self.datetime, 1, 1)
self.priority = QtGui.QSpinBox()
self.priority.setRange(-99, 99)
grid.addLabel("Priority:", 2, 2)
grid.addWidget(self.priority, 2, 3)
grid.addWidget(QtGui.QLabel("Priority:"), 1, 2)
grid.addWidget(self.priority, 1, 3)
self.pipeline = QtGui.QLineEdit()
self.pipeline.insert("main")
grid.addWidget(QtGui.QLabel("Pipeline:"), 2, 0)
grid.addWidget(self.pipeline, 2, 1)
self.flush = QtGui.QCheckBox("Flush")
grid.addWidget(self.flush, 2, 2, colspan=2)
submit = QtGui.QPushButton("Submit")
grid.addWidget(submit, 3, 0, colspan=4)
submit.clicked.connect(self.submit_clicked)
placeholder = QtGui.QWidget()
splitter.addWidget(placeholder)
self.argsetter = _ArgumentSetter(self.dialog_parent, [])
self.splitter.addWidget(self.argsetter)
self.splitter.setSizes([grid.minimumSizeHint().width(), 1000])
def update_argsetter(self, selected, deselected):
selected = selected.indexes()
if selected:
row = selected[0].row()
key = self.explist_model.row_to_key[row]
expinfo = self.explist_model.backing_store[key]
arguments = expinfo["arguments"]
sizes = self.splitter.sizes()
self.argsetter.deleteLater()
self.argsetter = _ArgumentSetter(self.dialog_parent, arguments)
self.splitter.insertWidget(1, self.argsetter)
self.splitter.setSizes(sizes)
def enable_duedate(self):
self.datetime_en.setChecked(True)
@asyncio.coroutine
def sub_connect(self, host, port):
@ -78,15 +210,15 @@ class ExplorerDock(dockarea.Dock):
return self.explist_model
@asyncio.coroutine
def submit(self, pipeline_name, file, experiment, arguments,
priority, due_date):
def submit(self, pipeline_name, file, class_name, arguments,
priority, due_date, flush):
expid = {
"file": file,
"experiment": experiment,
"class_name": class_name,
"arguments": arguments,
}
rid = yield from self.schedule_ctl.submit(pipeline_name, expid,
priority, due_date)
priority, due_date, flush)
self.status_bar.showMessage("Submitted RID {}".format(rid))
def submit_clicked(self):
@ -94,11 +226,15 @@ class ExplorerDock(dockarea.Dock):
if idx:
row = idx[0].row()
key = self.explist_model.row_to_key[row]
expinfo = self.explist_model.data[key]
expinfo = self.explist_model.backing_store[key]
if self.datetime_en.isChecked():
due_date = self.datetime.dateTime().toMSecsSinceEpoch()/1000
else:
due_date = None
arguments = self.argsetter.get_argument_values()
if arguments is None:
return
asyncio.async(self.submit(self.pipeline.text(),
expinfo["file"], expinfo["experiment"],
dict(), self.priority.value(), due_date))
expinfo["file"], expinfo["class_name"],
arguments, self.priority.value(),
due_date, self.flush.isChecked()))

BIN
artiq/gui/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,7 +1,57 @@
from quamash import QtGui
import asyncio
from quamash import QtGui, QtCore
from pyqtgraph import dockarea
from artiq.protocols.sync_struct import Subscriber
from artiq.gui.tools import ListSyncModel
class _LogModel(ListSyncModel):
def __init__(self, parent, init):
ListSyncModel.__init__(self,
["RID", "Message"],
parent, init)
def convert(self, v, column):
return v[column]
class LogDock(dockarea.Dock):
def __init__(self):
dockarea.Dock.__init__(self, "Log", size=(1000, 300))
self.log = QtGui.QTableView()
self.log.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
self.log.horizontalHeader().setResizeMode(
QtGui.QHeaderView.ResizeToContents)
self.log.setHorizontalScrollMode(
QtGui.QAbstractItemView.ScrollPerPixel)
self.log.setShowGrid(False)
self.log.setTextElideMode(QtCore.Qt.ElideNone)
self.addWidget(self.log)
self.scroll_at_bottom = False
@asyncio.coroutine
def sub_connect(self, host, port):
self.subscriber = Subscriber("log", self.init_log_model)
yield from self.subscriber.connect(host, port)
@asyncio.coroutine
def sub_close(self):
yield from self.subscriber.close()
def rows_inserted_before(self):
scrollbar = self.log.verticalScrollBar()
self.scroll_at_bottom = scrollbar.value() == scrollbar.maximum()
def rows_inserted_after(self):
if self.scroll_at_bottom:
self.log.scrollToBottom()
def init_log_model(self, init):
table_model = _LogModel(self.log, init)
self.log.setModel(table_model)
table_model.rowsAboutToBeInserted.connect(self.rows_inserted_before)
table_model.rowsInserted.connect(self.rows_inserted_after)
return table_model

300
artiq/gui/moninj.py Normal file
View File

@ -0,0 +1,300 @@
import asyncio
import logging
import socket
import struct
from operator import itemgetter
from quamash import QtGui, QtCore
from pyqtgraph import dockarea
from artiq.tools import TaskObject
from artiq.protocols.sync_struct import Subscriber
logger = logging.getLogger(__name__)
_mode_enc = {
"exp": 0,
"1": 1,
"0": 2,
"in": 3
}
class _TTLWidget(QtGui.QFrame):
def __init__(self, send_to_device, channel, force_out, name):
self.send_to_device = send_to_device
self.channel = channel
self.force_out = force_out
QtGui.QFrame.__init__(self)
self.setFrameShape(QtGui.QFrame.Panel)
self.setFrameShadow(QtGui.QFrame.Raised)
grid = QtGui.QGridLayout()
self.setLayout(grid)
label = QtGui.QLabel(name)
label.setAlignment(QtCore.Qt.AlignCenter)
grid.addWidget(label, 1, 1)
self._direction = QtGui.QLabel()
self._direction.setAlignment(QtCore.Qt.AlignCenter)
grid.addWidget(self._direction, 2, 1)
self._override = QtGui.QLabel()
self._override.setAlignment(QtCore.Qt.AlignCenter)
grid.addWidget(self._override, 3, 1)
self._value = QtGui.QLabel()
self._value.setAlignment(QtCore.Qt.AlignCenter)
grid.addWidget(self._value, 4, 1, 6, 1)
self._value.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
menu = QtGui.QActionGroup(self._value)
menu.setExclusive(True)
self._expctl_action = QtGui.QAction("Experiment controlled", self._value)
self._expctl_action.setCheckable(True)
menu.addAction(self._expctl_action)
self._value.addAction(self._expctl_action)
self._expctl_action.triggered.connect(lambda: self.set_mode("exp"))
separator = QtGui.QAction(self._value)
separator.setSeparator(True)
self._value.addAction(separator)
self._force1_action = QtGui.QAction("Force 1", self._value)
self._force1_action.setCheckable(True)
menu.addAction(self._force1_action)
self._value.addAction(self._force1_action)
self._force1_action.triggered.connect(lambda: self.set_mode("1"))
self._force0_action = QtGui.QAction("Force 0", self._value)
self._force0_action.setCheckable(True)
menu.addAction(self._force0_action)
self._value.addAction(self._force0_action)
self._force0_action.triggered.connect(lambda: self.set_mode("0"))
self._forcein_action = QtGui.QAction("Force input", self._value)
self._forcein_action.setCheckable(True)
self._forcein_action.setEnabled(not force_out)
menu.addAction(self._forcein_action)
self._value.addAction(self._forcein_action)
self._forcein_action.triggered.connect(lambda: self.set_mode("in"))
self.set_value(0, False, False)
def set_mode(self, mode):
data = struct.pack("bbb",
2, # MONINJ_REQ_TTLSET
self.channel, _mode_enc[mode])
self.send_to_device(data)
def set_value(self, value, oe, override):
value_s = "1" if value else "0"
if override:
value_s = "<b>" + value_s + "</b>"
color = " color=\"red\""
self._override.setText("<font size=\"1\" color=\"red\">OVERRIDE</font>")
else:
color = ""
self._override.setText("")
self._value.setText("<font size=\"9\"{}>{}</font>".format(
color, value_s))
oe = oe or self.force_out
direction = "OUT" if oe else "IN"
self._direction.setText("<font size=\"1\">" + direction + "</font>")
if override:
if oe:
if value:
self._force1_action.setChecked(True)
else:
self._force0_action.setChecked(True)
else:
self._forcein_action.setChecked(True)
else:
self._expctl_action.setChecked(True)
class _DDSWidget(QtGui.QFrame):
def __init__(self, send_to_device, channel, sysclk, name):
self.send_to_device = send_to_device
self.channel = channel
self.sysclk = sysclk
self.name = name
QtGui.QFrame.__init__(self)
self.setFrameShape(QtGui.QFrame.Panel)
self.setFrameShadow(QtGui.QFrame.Raised)
grid = QtGui.QGridLayout()
self.setLayout(grid)
label = QtGui.QLabel(name)
label.setAlignment(QtCore.Qt.AlignCenter)
grid.addWidget(label, 1, 1)
self._value = QtGui.QLabel()
self._value.setAlignment(QtCore.Qt.AlignCenter)
grid.addWidget(self._value, 2, 1, 6, 1)
self.set_value(0)
def set_value(self, ftw):
frequency = ftw*self.sysclk/2**32
self._value.setText("<font size=\"6\">{:.7f} MHz</font>"
.format(float(frequency)/1e6))
class _DeviceManager:
def __init__(self, send_to_device, init):
self.send_to_device = send_to_device
self.ddb = dict()
self.ttl_cb = lambda: None
self.ttl_widgets = dict()
self.dds_cb = lambda: None
self.dds_widgets = dict()
for k, v in init.items():
self[k] = v
def __setitem__(self, k, v):
self.ddb[k] = v
if k in self.ttl_widgets:
del self[k]
if not isinstance(v, dict):
return
try:
if v["type"] == "local":
if v["module"] == "artiq.coredevice.ttl":
channel = v["arguments"]["channel"]
force_out = v["class"] == "TTLOut"
self.ttl_widgets[channel] = _TTLWidget(
self.send_to_device, channel, force_out, k)
self.ttl_cb()
if (v["module"] == "artiq.coredevice.dds"
and v["class"] in {"AD9858", "AD9914"}):
channel = v["arguments"]["channel"]
sysclk = v["arguments"]["sysclk"]
self.dds_widgets[channel] = _DDSWidget(
self.send_to_device, channel, sysclk, k)
self.dds_cb()
except KeyError:
pass
def __delitem__(self, k):
del self.ddb[k]
if k in self.ttl_widgets:
del self.ttl_widgets[k]
self.ttl_cb()
def get_core_addr(self):
try:
comm = self.ddb["comm"]
while isinstance(comm, str):
comm = self.ddb[comm]
return comm["arguments"]["host"]
except KeyError:
return None
class _MonInjDock(dockarea.Dock):
def __init__(self, name):
dockarea.Dock.__init__(self, name, size=(1500, 500))
self.grid = QtGui.QGridLayout()
gridw = QtGui.QWidget()
gridw.setLayout(self.grid)
self.addWidget(gridw)
def layout_widgets(self, widgets):
w = self.grid.itemAt(0)
while w is not None:
self.grid.removeItem(w)
w = self.grid.itemAt(0)
for i, (_, w) in enumerate(sorted(widgets, key=itemgetter(0))):
self.grid.addWidget(w, i // 4, i % 4)
class MonInj(TaskObject):
def __init__(self):
self.ttl_dock = _MonInjDock("TTL")
self.dds_dock = _MonInjDock("DDS")
self.subscriber = Subscriber("devices", self.init_devices)
self.dm = _DeviceManager(self.send_to_device, dict())
self.transport = None
@asyncio.coroutine
def start(self, server, port):
loop = asyncio.get_event_loop()
yield from loop.create_datagram_endpoint(lambda: self,
family=socket.AF_INET)
try:
yield from self.subscriber.connect(server, port)
try:
TaskObject.start(self)
except:
yield from self.subscriber.close()
raise
except:
self.transport.close()
raise
@asyncio.coroutine
def stop(self):
yield from TaskObject.stop(self)
yield from self.subscriber.close()
if self.transport is not None:
self.transport.close()
self.transport = None
def connection_made(self, transport):
self.transport = transport
def datagram_received(self, data, addr):
try:
ttl_levels, ttl_oes, ttl_overrides = \
struct.unpack(">QQQ", data[:8*3])
for channel, w in self.dm.ttl_widgets.items():
w.set_value(ttl_levels & (1 << channel),
ttl_oes & (1 << channel),
ttl_overrides & (1 << channel))
dds_data = data[8*3:]
ndds = len(dds_data)//4
ftws = struct.unpack(">" + "I"*ndds, dds_data)
for channel, w in self.dm.dds_widgets.items():
try:
ftw = ftws[channel]
except KeyError:
pass
else:
w.set_value(ftw)
except:
logger.warning("failed to process datagram", exc_info=True)
def error_received(self, exc):
logger.warning("datagram endpoint error")
def connection_lost(self, exc):
self.transport = None
def send_to_device(self, data):
ca = self.dm.get_core_addr()
if ca is None:
logger.warning("could not find core device address")
elif self.transport is None:
logger.warning("datagram endpoint not available")
else:
self.transport.sendto(data, (ca, 3250))
@asyncio.coroutine
def _do(self):
while True:
yield from asyncio.sleep(0.2)
# MONINJ_REQ_MONITOR
self.send_to_device(b"\x01")
def init_devices(self, d):
self.dm = _DeviceManager(self.send_to_device, d)
self.dm.ttl_cb = lambda: self.ttl_dock.layout_widgets(
self.dm.ttl_widgets.items())
self.dm.dds_cb = lambda: self.dds_dock.layout_widgets(
self.dm.dds_widgets.items())
self.dm.ttl_cb()
self.dm.dds_cb()
return self.dm

View File

@ -1,10 +1,11 @@
import asyncio
from quamash import QtGui
from quamash import QtGui, QtCore
from pyqtgraph import dockarea
from pyqtgraph import LayoutWidget
from artiq.protocols.sync_struct import Subscriber
from artiq.gui.tools import DictSyncModel
from artiq.gui.tools import DictSyncModel, short_format
class ParametersModel(DictSyncModel):
@ -19,18 +20,41 @@ class ParametersModel(DictSyncModel):
if column == 0:
return k
elif column == 1:
return str(v)
return short_format(v)
else:
raise ValueError
class ParametersDock(dockarea.Dock):
def __init__(self):
dockarea.Dock.__init__(self, "Parameters", size=(500, 300))
dockarea.Dock.__init__(self, "Parameters", size=(400, 300))
grid = LayoutWidget()
self.addWidget(grid)
self.search = QtGui.QLineEdit()
self.search.setPlaceholderText("search...")
self.search.editingFinished.connect(self.search_parameters)
grid.addWidget(self.search, 0, 0)
self.table = QtGui.QTableView()
self.table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
self.addWidget(self.table)
self.table.horizontalHeader().setResizeMode(
QtGui.QHeaderView.ResizeToContents)
grid.addWidget(self.table, 1, 0)
def search_parameters(self):
model = self.table.model()
parentIndex = model.index(0, 0)
numRows = model.rowCount(parentIndex)
for row in range(numRows):
index = model.index(row, 0)
parameter = model.data(index, QtCore.Qt.DisplayRole)
if parameter.startswith(self.search.displayText()):
self.table.showRow(row)
else:
self.table.hideRow(row)
@asyncio.coroutine
def sub_connect(self, host, port):

109
artiq/gui/results.py Normal file
View File

@ -0,0 +1,109 @@
import asyncio
from collections import OrderedDict
from functools import partial
from quamash import QtGui, QtCore
from pyqtgraph import dockarea
from pyqtgraph import LayoutWidget
from artiq.protocols.sync_struct import Subscriber
from artiq.gui.tools import DictSyncModel, short_format
from artiq.gui.displays import *
class ResultsModel(DictSyncModel):
def __init__(self, parent, init):
DictSyncModel.__init__(self, ["Result", "Value"],
parent, init)
def sort_key(self, k, v):
return k
def convert(self, k, v, column):
if column == 0:
return k
elif column == 1:
return short_format(v)
else:
raise ValueError
class ResultsDock(dockarea.Dock):
def __init__(self, dialog_parent, dock_area):
dockarea.Dock.__init__(self, "Results", size=(1500, 500))
self.dialog_parent = dialog_parent
self.dock_area = dock_area
grid = LayoutWidget()
self.addWidget(grid)
self.table = QtGui.QTableView()
self.table.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
self.table.horizontalHeader().setResizeMode(
QtGui.QHeaderView.ResizeToContents)
grid.addWidget(self.table, 0, 0)
add_display_box = QtGui.QGroupBox("Add display")
grid.addWidget(add_display_box, 0, 1)
display_grid = QtGui.QGridLayout()
add_display_box.setLayout(display_grid)
for n, name in enumerate(display_types.keys()):
btn = QtGui.QPushButton(name)
display_grid.addWidget(btn, n, 0)
btn.clicked.connect(partial(self.create_dialog, name))
self.displays = dict()
@asyncio.coroutine
def sub_connect(self, host, port):
self.subscriber = Subscriber("rt_results", self.init_results_model,
self.on_mod)
yield from self.subscriber.connect(host, port)
@asyncio.coroutine
def sub_close(self):
yield from self.subscriber.close()
def init_results_model(self, init):
self.table_model = ResultsModel(self.table, init)
self.table.setModel(self.table_model)
return self.table_model
def on_mod(self, mod):
if mod["action"] == "init":
for display in self.displays.values():
display.update_data(self.table_model.backing_store)
return
if mod["action"] == "setitem":
source = mod["key"]
elif mod["path"]:
source = mod["path"][0]
else:
return
for display in self.displays.values():
if source in display.data_sources():
display.update_data(self.table_model.backing_store)
def create_dialog(self, ty):
dlg_class = display_types[ty][0]
dlg = dlg_class(self.dialog_parent, None, dict(),
sorted(self.table_model.backing_store.keys()),
partial(self.create_display, ty, None))
dlg.open()
def create_display(self, ty, prev_name, name, settings):
if prev_name is not None and prev_name in self.displays:
raise NotImplementedError
dsp_class = display_types[ty][1]
dsp = dsp_class(name, settings)
self.displays[name] = dsp
dsp.update_data(self.table_model.backing_store)
def on_close():
del self.displays[name]
dsp.sigClosed.connect(on_close)
self.dock_area.addDock(dsp)
self.dock_area.floatDock(dsp)

137
artiq/gui/scan.py Normal file
View File

@ -0,0 +1,137 @@
from quamash import QtGui
from pyqtgraph import LayoutWidget
class _Range(LayoutWidget):
def __init__(self, global_min, global_max, global_step, unit):
LayoutWidget.__init__(self)
def apply_properties(spinbox):
if global_min is not None:
spinbox.setMinimum(global_min)
if global_max is not None:
spinbox.setMaximum(global_max)
if global_step is not None:
spinbox.setSingleStep(global_step)
if unit:
spinbox.setSuffix(" " + unit)
self.addWidget(QtGui.QLabel("Min:"), 0, 0)
self.min = QtGui.QDoubleSpinBox()
apply_properties(self.min)
self.addWidget(self.min, 0, 1)
self.addWidget(QtGui.QLabel("Max:"), 0, 2)
self.max = QtGui.QDoubleSpinBox()
apply_properties(self.max)
self.addWidget(self.max, 0, 3)
self.addWidget(QtGui.QLabel("#Points:"), 0, 4)
self.npoints = QtGui.QSpinBox()
self.npoints.setMinimum(2)
self.npoints.setValue(10)
self.addWidget(self.npoints, 0, 5)
def set_values(self, min, max, npoints):
self.min.setValue(min)
self.max.setValue(max)
self.npoints.setValue(npoints)
def get_values(self):
return {
"min": self.min.value(),
"max": self.max.value(),
"npoints": self.npoints.value()
}
class ScanController(LayoutWidget):
def __init__(self, procdesc):
LayoutWidget.__init__(self)
self.stack = QtGui.QStackedWidget()
self.addWidget(self.stack, 1, 0, colspan=4)
gmin, gmax = procdesc["global_min"], procdesc["global_max"]
gstep = procdesc["global_step"]
unit = procdesc["unit"]
self.v_noscan = QtGui.QDoubleSpinBox()
if gmin is not None:
self.v_noscan.setMinimum(gmin)
if gmax is not None:
self.v_noscan.setMaximum(gmax)
if gstep is not None:
self.v_noscan.setSingleStep(gstep)
if unit:
self.v_noscan.setSuffix(" " + unit)
self.v_noscan_gr = LayoutWidget()
self.v_noscan_gr.addWidget(QtGui.QLabel("Value:"), 0, 0)
self.v_noscan_gr.addWidget(self.v_noscan, 0, 1)
self.stack.addWidget(self.v_noscan_gr)
self.v_linear = _Range(gmin, gmax, gstep, unit)
self.stack.addWidget(self.v_linear)
self.v_random = _Range(gmin, gmax, gstep, unit)
self.stack.addWidget(self.v_random)
self.v_explicit = QtGui.QLineEdit()
self.v_explicit_gr = LayoutWidget()
self.v_explicit_gr.addWidget(QtGui.QLabel("Sequence:"), 0, 0)
self.v_explicit_gr.addWidget(self.v_explicit, 0, 1)
self.stack.addWidget(self.v_explicit_gr)
self.noscan = QtGui.QRadioButton("No scan")
self.linear = QtGui.QRadioButton("Linear")
self.random = QtGui.QRadioButton("Random")
self.explicit = QtGui.QRadioButton("Explicit")
radiobuttons = QtGui.QButtonGroup()
for n, b in enumerate([self.noscan, self.linear,
self.random, self.explicit]):
self.addWidget(b, 0, n)
radiobuttons.addButton(b)
b.toggled.connect(self.select_page)
if "default" in procdesc:
d = procdesc["default"]
if d["ty"] == "NoScan":
self.noscan.setChecked(True)
self.v_noscan.setValue(d["value"])
elif d["ty"] == "LinearScan":
self.linear.setChecked(True)
self.v_linear.set_values(d["min"], d["max"], d["step"])
elif d["ty"] == "RandomScan":
self.random.setChecked(True)
self.v_random.set_values(d["min"], d["max"], d["step"])
elif d["ty"] == "ExplicitScan":
self.explicit.setChecked(True)
self.v_explicit.insert(" ".join(
[str(x) for x in d["sequence"]]))
else:
self.noscan.setChecked(True)
def select_page(self):
if self.noscan.isChecked():
self.stack.setCurrentWidget(self.v_noscan_gr)
elif self.linear.isChecked():
self.stack.setCurrentWidget(self.v_linear)
elif self.random.isChecked():
self.stack.setCurrentWidget(self.v_random)
elif self.explicit.isChecked():
self.stack.setCurrentWidget(self.v_explicit_gr)
def get_argument_value(self):
if self.noscan.isChecked():
return {"ty": "NoScan", "value": self.v_noscan.value()}
elif self.linear.isChecked():
d = {"ty": "LinearScan"}
d.update(self.v_linear.get_values())
return d
elif self.random.isChecked():
d = {"ty": "RandomScan"}
d.update(self.v_random.get_values())
return d
elif self.explicit.isChecked():
sequence = [float(x) for x in self.v_explicit.text().split()]
return {"ty": "ExplicitScan", "sequence": sequence}

View File

@ -6,19 +6,18 @@ from pyqtgraph import dockarea
from artiq.protocols.sync_struct import Subscriber
from artiq.gui.tools import DictSyncModel
from artiq.tools import format_arguments
class _ScheduleModel(DictSyncModel):
def __init__(self, parent, init):
DictSyncModel.__init__(self,
["RID", "Pipeline", "Status", "Prio", "Due date",
"File", "Experiment", "Arguments"],
"File", "Class name"],
parent, init)
def sort_key(self, k, v):
# order by due date, and then by priority and RID
return (v["due_date"] or 0, -v["priority"], k)
# order by priority, and then by due date and RID
return (-v["priority"], v["due_date"] or 0, k)
def convert(self, k, v, column):
if column == 0:
@ -38,12 +37,10 @@ class _ScheduleModel(DictSyncModel):
elif column == 5:
return v["expid"]["file"]
elif column == 6:
if v["expid"]["experiment"] is None:
if v["expid"]["class_name"] is None:
return ""
else:
return v["expid"]["experiment"]
elif column == 7:
return format_arguments(v["expid"]["arguments"])
return v["expid"]["class_name"]
else:
raise ValueError
@ -56,6 +53,9 @@ class ScheduleDock(dockarea.Dock):
self.table = QtGui.QTableView()
self.table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
self.table.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
self.table.horizontalHeader().setResizeMode(
QtGui.QHeaderView.ResizeToContents)
self.addWidget(self.table)
self.table.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)

View File

@ -1,29 +1,56 @@
from quamash import QtCore
class _DictSyncSubstruct:
def short_format(v):
t = type(v)
if t is int or t is float:
return str(v)
else:
r = t.__name__
if t is list or t is dict or t is set:
r += " ({})".format(len(v))
return r
class _SyncSubstruct:
def __init__(self, update_cb, ref):
self.update_cb = update_cb
self.ref = ref
def __getitem__(self, key):
return _DictSyncSubstruct(self.update_cb, self.ref[key])
def append(self, x):
self.ref.append(x)
self.update_cb()
def insert(self, i, x):
self.ref.insert(i, x)
self.update_cb()
def pop(self, i=-1):
self.ref.pop(i)
self.update_cb()
def __setitem__(self, key, value):
self.ref[key] = value
self.update_cb()
def __delitem__(self, key):
self.ref.__delitem__(key)
self.update_cb()
def __getitem__(self, key):
return _SyncSubstruct(self.update_cb, self.ref[key])
class DictSyncModel(QtCore.QAbstractTableModel):
def __init__(self, headers, parent, init):
self.headers = headers
self.data = init
self.row_to_key = sorted(self.data.keys(),
key=lambda k: self.sort_key(k, self.data[k]))
self.backing_store = init
self.row_to_key = sorted(self.backing_store.keys(),
key=lambda k: self.sort_key(k, self.backing_store[k]))
QtCore.QAbstractTableModel.__init__(self, parent)
def rowCount(self, parent):
return len(self.data)
return len(self.backing_store)
def columnCount(self, parent):
return len(self.headers)
@ -34,7 +61,7 @@ class DictSyncModel(QtCore.QAbstractTableModel):
elif role != QtCore.Qt.DisplayRole:
return None
k = self.row_to_key[index.row()]
return self.convert(k, self.data[k], index.column())
return self.convert(k, self.backing_store[k], index.column())
def headerData(self, col, orientation, role):
if (orientation == QtCore.Qt.Horizontal
@ -48,7 +75,7 @@ class DictSyncModel(QtCore.QAbstractTableModel):
while lo < hi:
mid = (lo + hi)//2
if (self.sort_key(self.row_to_key[mid],
self.data[self.row_to_key[mid]])
self.backing_store[self.row_to_key[mid]])
< self.sort_key(k, v)):
lo = mid + 1
else:
@ -56,7 +83,7 @@ class DictSyncModel(QtCore.QAbstractTableModel):
return lo
def __setitem__(self, k, v):
if k in self.data:
if k in self.backing_store:
old_row = self.row_to_key.index(k)
new_row = self._find_row(k, v)
if old_row == new_row:
@ -65,7 +92,7 @@ class DictSyncModel(QtCore.QAbstractTableModel):
else:
self.beginMoveRows(QtCore.QModelIndex(), old_row, old_row,
QtCore.QModelIndex(), new_row)
self.data[k] = v
self.backing_store[k] = v
self.row_to_key[old_row], self.row_to_key[new_row] = \
self.row_to_key[new_row], self.row_to_key[old_row]
if old_row != new_row:
@ -73,7 +100,7 @@ class DictSyncModel(QtCore.QAbstractTableModel):
else:
row = self._find_row(k, v)
self.beginInsertRows(QtCore.QModelIndex(), row, row)
self.data[k] = v
self.backing_store[k] = v
self.row_to_key.insert(row, k)
self.endInsertRows()
@ -81,16 +108,66 @@ class DictSyncModel(QtCore.QAbstractTableModel):
row = self.row_to_key.index(k)
self.beginRemoveRows(QtCore.QModelIndex(), row, row)
del self.row_to_key[row]
del self.data[k]
del self.backing_store[k]
self.endRemoveRows()
def __getitem__(self, key):
def __getitem__(self, k):
def update():
self[key] = self.data[key]
return _DictSyncSubstruct(update, self.data[key])
self[k] = self.backing_store[k]
return _SyncSubstruct(update, self.backing_store[k])
def sort_key(self, k, v):
raise NotImplementedError
def convert(self, k, v, column):
raise NotImplementedError
class ListSyncModel(QtCore.QAbstractTableModel):
def __init__(self, headers, parent, init):
self.headers = headers
self.backing_store = init
QtCore.QAbstractTableModel.__init__(self, parent)
def rowCount(self, parent):
return len(self.backing_store)
def columnCount(self, parent):
return len(self.headers)
def data(self, index, role):
if not index.isValid():
return None
elif role != QtCore.Qt.DisplayRole:
return None
return self.convert(self.backing_store[index.row()], index.column())
def headerData(self, col, orientation, role):
if (orientation == QtCore.Qt.Horizontal
and role == QtCore.Qt.DisplayRole):
return self.headers[col]
return None
def __setitem__(self, k, v):
self.dataChanged.emit(self.index(k, 0),
self.index(k, len(self.headers)))
self.backing_store[k] = v
def __delitem__(self, k):
self.beginRemoveRows(QtCore.QModelIndex(), k, k)
del self.backing_store[k]
self.endRemoveRows()
def __getitem__(self, k):
def update():
self[k] = self.backing_store[k]
return _SyncSubstruct(update, self.backing_store[k])
def append(self, v):
row = len(self.backing_store)
self.beginInsertRows(QtCore.QModelIndex(), row, row)
self.backing_store.append(v)
self.endInsertRows()
def convert(self, v, column):
raise NotImplementedError

View File

@ -0,0 +1,12 @@
from artiq.language import core, environment, units, scan
from artiq.language.core import *
from artiq.language.environment import *
from artiq.language.units import *
from artiq.language.scan import *
__all__ = []
__all__.extend(core.__all__)
__all__.extend(environment.__all__)
__all__.extend(units.__all__)
__all__.extend(scan.__all__)

View File

@ -2,8 +2,20 @@
Core ARTIQ extensions to the Python language.
"""
from collections import namedtuple as _namedtuple
from functools import wraps as _wraps
from collections import namedtuple
from functools import wraps
__all__ = ["int64", "round64", "kernel", "portable",
"set_time_manager", "set_syscall_manager", "set_watchdog_factory",
"RuntimeException", "EncodedException"]
# global namespace for kernels
kernel_globals = ("sequential", "parallel",
"delay_mu", "now_mu", "at_mu", "delay",
"seconds_to_mu", "mu_to_seconds",
"syscall", "watchdog")
__all__.extend(kernel_globals)
class int64(int):
@ -28,7 +40,6 @@ class int64(int):
True
>>> a + b
6
"""
pass
@ -62,12 +73,11 @@ def round64(x):
This function is equivalent to ``int64(round(x))`` but, when targeting
static compilation, prevents overflow when the rounded value is too large
to fit in a 32-bit integer.
"""
return int64(round(x))
_KernelFunctionInfo = _namedtuple("_KernelFunctionInfo", "core_name k_function")
_KernelFunctionInfo = namedtuple("_KernelFunctionInfo", "core_name k_function")
def kernel(arg):
@ -88,11 +98,10 @@ def kernel(arg):
The decorator takes an optional parameter that defaults to ``core`` and
specifies the name of the attribute to use as core device driver.
"""
if isinstance(arg, str):
def real_decorator(k_function):
@_wraps(k_function)
@wraps(k_function)
def run_on_core(exp, *k_args, **k_kwargs):
return getattr(exp, arg).run(k_function,
((exp,) + k_args), k_kwargs)
@ -101,7 +110,7 @@ def kernel(arg):
return run_on_core
return real_decorator
else:
@_wraps(arg)
@wraps(arg)
def run_on_core(exp, *k_args, **k_kwargs):
return exp.core.run(arg, ((exp,) + k_args), k_kwargs)
run_on_core.k_function_info = _KernelFunctionInfo(
@ -117,7 +126,6 @@ def portable(f):
host will be executed on the host (no compilation and execution on the
core device). A decorated function called from a kernel will be executed
on the core device (no RPC).
"""
f.k_function_info = _KernelFunctionInfo(core_name="", k_function=f)
return f
@ -131,9 +139,10 @@ class _DummyTimeManager:
enter_sequential = _not_implemented
enter_parallel = _not_implemented
exit = _not_implemented
take_time_mu = _not_implemented
get_time_mu = _not_implemented
set_time_mu = _not_implemented
take_time = _not_implemented
get_time = _not_implemented
set_time = _not_implemented
_time_manager = _DummyTimeManager()
@ -143,7 +152,6 @@ def set_time_manager(time_manager):
directly inside the Python interpreter. The time manager responds to the
entering and leaving of parallel/sequential blocks, delays, etc. and
provides a time-stamped logging facility for events.
"""
global _time_manager
_time_manager = time_manager
@ -160,17 +168,10 @@ _syscall_manager = _DummySyscallManager()
def set_syscall_manager(syscall_manager):
"""Set the system call manager used for simulating the core device's
runtime in the Python interpreter.
"""
global _syscall_manager
_syscall_manager = syscall_manager
# global namespace for kernels
kernel_globals = ("sequential", "parallel",
"delay", "now", "at", "time_to_cycles", "cycles_to_time",
"syscall", "watchdog")
class _Sequential:
"""In a sequential block, statements are executed one after another, with
@ -190,7 +191,6 @@ class _Parallel:
The execution time of a parallel block is the execution time of its longest
statement. A parallel block may contain sequential blocks, which themselves
may contain parallel blocks, etc.
"""
def __enter__(self):
_time_manager.enter_parallel()
@ -200,50 +200,49 @@ class _Parallel:
parallel = _Parallel()
def delay(duration):
"""Increases the RTIO time by the given amount.
def delay_mu(duration):
"""Increases the RTIO time by the given amount (in machine units)."""
_time_manager.take_time_mu(duration)
"""
def now_mu():
"""Retrieves the current RTIO time, in machine units."""
return _time_manager.get_time_mu()
def at_mu(time):
"""Sets the RTIO time to the specified absolute value, in machine units."""
_time_manager.set_time_mu(time)
def delay(duration):
"""Increases the RTIO time by the given amount (in seconds)."""
_time_manager.take_time(duration)
def now():
"""Retrieves the current RTIO time, in seconds.
def seconds_to_mu(seconds, core=None):
"""Converts seconds to the corresponding number of machine units
(RTIO cycles).
"""
return _time_manager.get_time()
def at(time):
"""Sets the RTIO time to the specified absolute value.
"""
_time_manager.set_time(time)
def time_to_cycles(time, core=None):
"""Converts time to the corresponding number of RTIO cycles.
:param time: Time (in seconds) to convert.
:param core: Core device for which to perform the conversion. Specify only
when running in the interpreter (not in kernel).
"""
if core is None:
raise ValueError("Core device must be specified for time conversion")
return round64(time.amount//core.ref_period)
def cycles_to_time(cycles, core=None):
"""Converts RTIO cycles to the corresponding time.
:param time: Cycle count to convert.
:param core: Core device for which to perform the conversion. Specify only
:param seconds: time (in seconds) to convert.
:param core: core device for which to perform the conversion. Specify only
when running in the interpreter (not in kernel).
"""
if core is None:
raise ValueError("Core device must be specified for time conversion")
return cycles*core.ref_period
return round64(seconds//core.ref_period)
def mu_to_seconds(mu, core=None):
"""Converts machine units (RTIO cycles) to seconds.
:param mu: cycle count to convert.
:param core: core device for which to perform the conversion. Specify only
when running in the interpreter (not in kernel).
"""
if core is None:
raise ValueError("Core device must be specified for time conversion")
return mu*core.ref_period
def syscall(*args):

View File

@ -1,130 +0,0 @@
"""
Connection to device, parameter and result database.
"""
class _AttributeKind:
pass
class Device(_AttributeKind):
"""Represents a device for ``AutoDB`` to process."""
pass
class NoDefault:
"""Represents the absence of a default value for ``Parameter``
and ``Argument``.
"""
pass
class Parameter(_AttributeKind):
"""Represents a parameter (from the database) for ``AutoDB``
to process.
:param default: Default value of the parameter to be used if not found
in the database.
"""
def __init__(self, default=NoDefault):
self.default = default
class Argument(_AttributeKind):
"""Represents an argument (specifiable at instance creation) for
``AutoDB`` to process.
:param default: Default value of the argument to be used if not specified
at instance creation.
"""
def __init__(self, default=NoDefault):
self.default = default
class Result(_AttributeKind):
"""Represents a result for ``AutoDB`` to process."""
pass
class AutoDB:
"""Base class to automate device, parameter and result database access.
Drivers and experiments should in most cases overload this class to
obtain the parameters and devices (including the core device) that they
need, report results, and modify parameters.
:param dbh: database hub to use. If ``None``, all devices and parameters
must be supplied as keyword arguments, and reporting results and
modifying parameters is not supported.
"""
class DBKeys:
pass
realtime_results = dict()
def __init__(self, dbh=None, **kwargs):
self.dbh = dbh
for k, v in kwargs.items():
object.__setattr__(self, k, v)
for k in dir(self.DBKeys):
if k not in self.__dict__:
ak = getattr(self.DBKeys, k)
if isinstance(ak, Argument):
if ak.default is NoDefault:
raise AttributeError(
"No value specified for argument '{}'".format(k))
object.__setattr__(self, k, ak.default)
elif isinstance(ak, Device):
try:
dev = self.dbh.get_device(k)
except KeyError:
raise KeyError("Device '{}' not found".format(k))
object.__setattr__(self, k, dev)
self.build()
if self.dbh is not None and self.realtime_results:
self.dbh.add_rt_results(self.realtime_results)
def __getattr__(self, name):
ak = getattr(self.DBKeys, name)
if isinstance(ak, Parameter):
try:
if self.dbh is None:
raise KeyError
return self.dbh.get_parameter(name)
except KeyError:
if ak.default is not NoDefault:
return ak.default
else:
raise AttributeError("Parameter '{}' not in database"
" and without default value"
.format(name))
elif isinstance(ak, Result):
try:
return self.dbh.get_result(name)
except KeyError:
raise AttributeError("Result '{}' not found".format(name))
else:
raise ValueError
def __setattr__(self, name, value):
try:
ak = getattr(self.DBKeys, name)
except AttributeError:
object.__setattr__(self, name, value)
else:
if isinstance(ak, Parameter):
self.dbh.set_parameter(name, value)
elif isinstance(ak, Result):
self.dbh.set_result(name, value)
else:
raise ValueError
def build(self):
"""This is called by ``__init__`` after the parameter initialization
is done.
The user may overload this method to complete the object's
initialization with all parameters available.
"""
pass

View File

@ -0,0 +1,298 @@
from collections import OrderedDict
from inspect import isclass
__all__ = ["NoDefault",
"FreeValue", "BooleanValue", "EnumerationValue",
"NumberValue", "StringValue",
"HasEnvironment",
"Experiment", "EnvExperiment", "is_experiment"]
class NoDefault:
"""Represents the absence of a default value."""
pass
class DefaultMissing(Exception):
"""Raised by the ``default`` method of argument processors when no default
value is available."""
pass
class _SimpleArgProcessor:
def __init__(self, default=NoDefault):
if default is not NoDefault:
self.default_value = default
def default(self):
if not hasattr(self, "default_value"):
raise DefaultMissing
return self.default_value
def process(self, x):
return x
def describe(self):
d = {"ty": self.__class__.__name__}
if hasattr(self, "default_value"):
d["default"] = self.default_value
return d
class FreeValue(_SimpleArgProcessor):
"""An argument that can be an arbitrary Python value."""
pass
class BooleanValue(_SimpleArgProcessor):
"""A boolean argument."""
pass
class EnumerationValue(_SimpleArgProcessor):
"""An argument that can take a string value among a predefined set of
values.
:param choices: A list of string representing the possible values of the
argument.
"""
def __init__(self, choices, default=NoDefault):
_SimpleArgProcessor.__init__(self, default)
assert default is NoDefault or default in choices
self.choices = choices
def describe(self):
d = _SimpleArgProcessor.describe(self)
d["choices"] = self.choices
return d
class NumberValue(_SimpleArgProcessor):
"""An argument that can take a numerical value (typically floating point).
:param unit: A string representing the unit of the value, for user
interface (UI) purposes.
:param step: The step with with the value should be modified by up/down
buttons in a UI.
:param min: The minimum value of the argument.
:param max: The maximum value of the argument.
"""
def __init__(self, default=NoDefault, unit="", step=None,
min=None, max=None):
_SimpleArgProcessor.__init__(self, default)
self.unit = unit
self.step = step
self.min = min
self.max = max
def describe(self):
d = _SimpleArgProcessor.describe(self)
d["unit"] = self.unit
d["step"] = self.step
d["min"] = self.min
d["max"] = self.max
return d
class StringValue(_SimpleArgProcessor):
"""A string argument."""
pass
class HasEnvironment:
"""Provides methods to manage the environment of an experiment (devices,
parameters, results, arguments)."""
def __init__(self, dmgr=None, pdb=None, rdb=None, *,
param_override=dict(), default_arg_none=False, **kwargs):
self.requested_args = OrderedDict()
self.__dmgr = dmgr
self.__pdb = pdb
self.__rdb = rdb
self.__param_override = param_override
self.__default_arg_none = default_arg_none
self.__kwargs = kwargs
self.__in_build = True
self.build()
self.__in_build = False
for key in self.__kwargs.keys():
if key not in self.requested_args:
raise TypeError("Got unexpected argument: " + key)
del self.__kwargs
def build(self):
"""Must be implemented by the user to request arguments.
Other initialization steps such as requesting devices and parameters
or initializing real-time results may also be performed here.
When the repository is scanned, any requested devices and parameters
are set to ``None``."""
raise NotImplementedError
def dbs(self):
return self.__dmgr, self.__pdb, self.__rdb
def get_argument(self, key, processor=None):
"""Retrieves and returns the value of an argument.
:param key: Name of the argument.
:param processor: A description of how to process the argument, such
as instances of ``BooleanValue`` and ``NumberValue``.
"""
if not self.__in_build:
raise TypeError("get_argument() should only "
"be called from build()")
if processor is None:
processor = FreeValue()
self.requested_args[key] = processor
try:
argval = self.__kwargs[key]
except KeyError:
try:
return processor.default()
except DefaultMissing:
if self.__default_arg_none:
return None
else:
raise
return processor.process(argval)
def attr_argument(self, key, processor=None):
"""Sets an argument as attribute. The names of the argument and of the
attribute are the same."""
setattr(self, key, self.get_argument(key, processor))
def get_device(self, key):
"""Creates and returns a device driver."""
if self.__dmgr is None:
raise ValueError("Device manager not present")
return self.__dmgr.get(key)
def attr_device(self, key):
"""Sets a device driver as attribute. The names of the device driver
and of the attribute are the same."""
setattr(self, key, self.get_device(key))
def get_parameter(self, key, default=NoDefault):
"""Retrieves and returns a parameter."""
if self.__pdb is None:
raise ValueError("Parameter database not present")
if key in self.__param_override:
return self.__param_override[key]
try:
return self.__pdb.get(key)
except KeyError:
if default is not NoDefault:
return default
else:
raise
def attr_parameter(self, key, default=NoDefault):
"""Sets a parameter as attribute. The names of the argument and of the
parameter are the same."""
setattr(self, key, self.get_parameter(key, default))
def set_parameter(self, key, value):
"""Writes the value of a parameter into the parameter database."""
if self.__pdb is None:
raise ValueError("Parameter database not present")
self.__pdb.set(key, value)
def set_result(self, key, value, realtime=False):
"""Writes the value of a result.
:param realtime: Marks the result as real-time, making it immediately
available to clients such as the user interface. Returns a
``Notifier`` instance that can be used to modify mutable results
(such as lists) and synchronize the modifications with the clients.
"""
if self.__rdb is None:
raise ValueError("Result database not present")
if realtime:
if key in self.__rdb.nrt:
raise ValueError("Result is already non-realtime")
self.__rdb.rt[key] = value
notifier = self.__rdb.rt[key]
notifier.kernel_attr_init = False
return notifier
else:
if key in self.__rdb.rt.read:
raise ValueError("Result is already realtime")
self.__rdb.nrt[key] = value
def attr_rtresult(self, key, init_value):
"""Writes the value of a real-time result and sets the corresponding
``Notifier`` as attribute. The names of the result and of the
attribute are the same."""
setattr(self, key, set_result(key, init_value, True))
def get_result(self, key):
"""Retrieves the value of a result.
There is no difference between real-time and non-real-time results
(this function does not return ``Notifier`` instances).
"""
if self.__rdb is None:
raise ValueError("Result database not present")
return self.__rdb.get(key)
class Experiment:
"""Base class for experiments.
Deriving from this class enables automatic experiment discovery in
Python modules.
"""
def prepare(self):
"""Entry point for pre-computing data necessary for running the
experiment.
Doing such computations outside of ``run`` enables more efficient
scheduling of multiple experiments that need to access the shared
hardware during part of their execution.
This method must not interact with the hardware.
"""
pass
def run(self):
"""The main entry point of the experiment.
This method must be overloaded by the user to implement the main
control flow of the experiment.
This method may interact with the hardware.
The experiment may call the scheduler's ``pause`` method while in
``run``.
"""
raise NotImplementedError
def analyze(self):
"""Entry point for analyzing the results of the experiment.
This method may be overloaded by the user to implement the analysis
phase of the experiment, for example fitting curves.
Splitting this phase from ``run`` enables tweaking the analysis
algorithm on pre-existing data, and CPU-bound analyses to be run
overlapped with the next experiment in a pipelined manner.
This method must not interact with the hardware.
"""
pass
class EnvExperiment(Experiment, HasEnvironment):
pass
def is_experiment(o):
"""Checks if a Python object is an instantiable user experiment."""
return (isclass(o)
and issubclass(o, Experiment)
and o is not Experiment
and o is not EnvExperiment)

View File

@ -1,40 +0,0 @@
from inspect import isclass
class Experiment:
"""Base class for experiments.
Deriving from this class enables automatic experiment discovery in
Python modules.
"""
def run(self):
"""The main entry point of the experiment.
This method must be overloaded by the user to implement the main
control flow of the experiment.
"""
raise NotImplementedError
def analyze(self):
"""Entry point for analyzing the results of the experiment.
This method may be overloaded by the user to implement the analysis
phase of the experiment, for example fitting curves.
Splitting this phase from ``run`` enables tweaking the analysis
algorithm on pre-existing data, and CPU-bound analyses to be run
overlapped with the next experiment in a pipelined manner.
This method must not interact with the hardware.
"""
pass
def has_analyze(experiment):
"""Checks if an experiment instance overloaded its ``analyze`` method."""
return experiment.analyze.__func__ is not Experiment.analyze
def is_experiment(o):
"""Checks if a Python object is an instantiable experiment."""
return isclass(o) and issubclass(o, Experiment) and o is not Experiment

114
artiq/language/scan.py Normal file
View File

@ -0,0 +1,114 @@
from random import Random, shuffle
import inspect
from artiq.language.core import *
from artiq.language.environment import NoDefault, DefaultMissing
__all__ = ["NoScan", "LinearScan", "RandomScan", "ExplicitScan", "Scannable"]
class NoScan:
def __init__(self, value):
self.value = value
@portable
def _gen(self):
yield self.value
@portable
def __iter__(self):
return self._gen()
def describe(self):
return {"ty": "NoScan", "value": self.value}
class LinearScan:
def __init__(self, min, max, npoints):
self.min = min
self.max = max
self.npoints = npoints
@portable
def _gen(self):
r = self.max - self.min
d = self.npoints - 1
for i in range(self.npoints):
yield r*i/d + self.min
@portable
def __iter__(self):
return self._gen()
def describe(self):
return {"ty": "LinearScan",
"min": self.min, "max": self.max, "npoints": self.npoints}
class RandomScan:
def __init__(self, min, max, npoints, seed=0):
self.sequence = list(LinearScan(min, max, npoints))
shuffle(self.sequence, Random(seed).random)
@portable
def __iter__(self):
return iter(self.sequence)
def describe(self):
return {"ty": "RandomScan",
"min": self.min, "max": self.max, "npoints": self.npoints}
class ExplicitScan:
def __init__(self, sequence):
self.sequence = sequence
@portable
def __iter__(self):
return iter(self.sequence)
def describe(self):
return {"ty": "ExplicitScan", "sequence": self.sequence}
_ty_to_scan = {
"NoScan": NoScan,
"LinearScan": LinearScan,
"RandomScan": RandomScan,
"ExplicitScan": ExplicitScan
}
class Scannable:
def __init__(self, global_min=None, global_max=None, global_step=None,
unit="", default=NoDefault):
self.global_min = global_min
self.global_max = global_max
self.global_step = global_step
self.unit = unit
if default is not NoDefault:
self.default_value = default
def default(self):
if not hasattr(self, "default_value"):
raise DefaultMissing
return self.default_value
def process(self, x):
cls = _ty_to_scan[x["ty"]]
args = dict()
for arg in inspect.getargspec(cls).args[1:]:
if arg in x:
args[arg] = x[arg]
return cls(**args)
def describe(self):
d = {"ty": "Scannable"}
d["global_min"] = self.global_min
d["global_max"] = self.global_max
d["global_step"] = self.global_step
d["unit"] = self.unit
if hasattr(self, "default_value"):
d["default"] = self.default_value.describe()
return d

View File

@ -1,266 +1,20 @@
"""
Definition and management of physical units.
"""
from fractions import Fraction as _Fraction
class DimensionError(Exception):
"""Raised when attempting an operation with incompatible units.
When targeting the core device, all units are statically managed at
compilation time. Thus, when raised by functions in this module, this
exception cannot be caught in the kernel as it is raised by the compiler
instead.
"""
pass
def mul_dimension(l, r):
"""Returns the unit obtained by multiplying unit ``l`` with unit ``r``.
Raises ``DimensionError`` if the resulting unit is not implemented.
"""
if l is None:
return r
if r is None:
return l
if {l, r} == {"Hz", "s"}:
return None
raise DimensionError
def _rmul_dimension(l, r):
return mul_dimension(r, l)
def div_dimension(l, r):
"""Returns the unit obtained by dividing unit ``l`` with unit ``r``.
Raises ``DimensionError`` if the resulting unit is not implemented.
"""
if l == r:
return None
if r is None:
return l
if l is None:
if r == "s":
return "Hz"
if r == "Hz":
return "s"
raise DimensionError
def _rdiv_dimension(l, r):
return div_dimension(r, l)
def addsub_dimension(x, y):
"""Returns the unit obtained by adding or subtracting unit ``l`` with
unit ``r``.
Raises ``DimensionError`` if ``l`` and ``r`` are different.
"""
if x == y:
return x
else:
raise DimensionError
__all__ = []
_prefixes_str = "pnum_kMG"
_smallest_prefix = _Fraction(1, 10**12)
def _format(amount, unit):
if amount is NotImplemented:
return NotImplemented
if unit is None:
return amount
else:
return Quantity(amount, unit)
class Quantity:
"""Represents an amount in a given fundamental unit (identified by a
string).
The amount can be of any Python numerical type (integer, float,
Fraction, ...).
Arithmetic operations and comparisons are directly delegated to the
underlying numerical types.
"""
def __init__(self, amount, unit):
self.amount = amount
self.unit = unit
def __repr__(self):
r_amount = self.amount
if isinstance(r_amount, int) or isinstance(r_amount, _Fraction):
r_prefix = 0
r_amount = r_amount/_smallest_prefix
if r_amount:
numerator = r_amount.numerator
while numerator % 1000 == 0 and r_prefix < len(_prefixes_str):
numerator /= 1000
r_amount /= 1000
r_prefix += 1
prefix_str = _prefixes_str[r_prefix]
if prefix_str == "_":
prefix_str = ""
return str(r_amount) + " " + prefix_str + self.unit
else:
return str(r_amount) + " " + self.unit
def __float__(self):
return float(self.amount)
# mul/div
def _binop(self, other, opf_name, dim_function):
opf = getattr(self.amount, opf_name)
if isinstance(other, Quantity):
amount = opf(other.amount)
unit = dim_function(self.unit, other.unit)
else:
amount = opf(other)
unit = dim_function(self.unit, None)
return _format(amount, unit)
def __mul__(self, other):
return self._binop(other, "__mul__", mul_dimension)
def __rmul__(self, other):
return self._binop(other, "__rmul__", _rmul_dimension)
def __truediv__(self, other):
return self._binop(other, "__truediv__", div_dimension)
def __rtruediv__(self, other):
return self._binop(other, "__rtruediv__", _rdiv_dimension)
def __floordiv__(self, other):
return self._binop(other, "__floordiv__", div_dimension)
def __rfloordiv__(self, other):
return self._binop(other, "__rfloordiv__", _rdiv_dimension)
# unary ops
def __neg__(self):
return Quantity(self.amount.__neg__(), self.unit)
def __pos__(self):
return Quantity(self.amount.__pos__(), self.unit)
def __abs__(self):
return Quantity(abs(self.amount), self.unit)
# add/sub
def __add__(self, other):
return self._binop(other, "__add__", addsub_dimension)
def __radd__(self, other):
return self._binop(other, "__radd__", addsub_dimension)
def __sub__(self, other):
return self._binop(other, "__sub__", addsub_dimension)
def __rsub__(self, other):
return self._binop(other, "__rsub__", addsub_dimension)
def __mod__(self, other):
return self._binop(other, "__mod__", addsub_dimension)
def __rmod__(self, other):
return self._binop(other, "__rmod__", addsub_dimension)
# comparisons
def _cmp(self, other, opf_name):
if not isinstance(other, Quantity) or other.unit != self.unit:
raise DimensionError
return getattr(self.amount, opf_name)(other.amount)
def __lt__(self, other):
return self._cmp(other, "__lt__")
def __le__(self, other):
return self._cmp(other, "__le__")
def __eq__(self, other):
return self._cmp(other, "__eq__")
def __ne__(self, other):
return self._cmp(other, "__ne__")
def __gt__(self, other):
return self._cmp(other, "__gt__")
def __ge__(self, other):
return self._cmp(other, "__ge__")
_smallest_prefix_exp = -12
def _register_unit(unit, prefixes):
amount = _smallest_prefix
exponent = _smallest_prefix_exp
for prefix in _prefixes_str:
if prefix in prefixes:
quantity = Quantity(amount, unit)
full_name = prefix + unit if prefix != "_" else unit
globals()[full_name] = quantity
amount *= 1000
globals()[full_name] = 10.**exponent
__all__.append(full_name)
exponent += 3
_register_unit("s", "pnum_")
_register_unit("Hz", "_kMG")
_register_unit("dB", "_")
_register_unit("V", "um_k")
def check_unit(value, unit):
"""Checks that the value has the specified unit. Unit specification is
a string representing the unit without any prefix (e.g. ``s``, ``Hz``).
Checking for a dimensionless value (not a ``Quantity`` instance) is done
by setting ``unit`` to ``None``.
If the units do not match, ``DimensionError`` is raised.
This function can be used in kernels and is executed at compilation time.
There is already unit checking built into the arithmetic, so you typically
need to use this function only when using the ``amount`` property of
``Quantity``.
"""
if unit is None:
if isinstance(value, Quantity):
raise DimensionError
else:
if not isinstance(value, Quantity) or value.unit != unit:
raise DimensionError
def strip_unit(value, unit):
"""Check that the value has the specified unit and returns its amount.
Raises ``DimensionError`` if the units does not match.
If the passed value is not a ``Quantity``, it is assumed to be in the
specified unit and is returned unchanged.
If ``unit`` is ``None``, passing a ``Quantity`` as value raises
``DimensionError``.
"""
if unit is None:
if isinstance(value, Quantity):
raise DimensionError
else:
return value
else:
if isinstance(value, Quantity):
if value.unit != unit:
raise DimensionError
else:
return value.amount
else:
return value

View File

@ -1,39 +1,70 @@
import os
import logging
import asyncio
from artiq.protocols.sync_struct import Notifier
from artiq.tools import file_import
from artiq.language.experiment import is_experiment
from artiq.master.worker import Worker
def scan_experiments():
logger = logging.getLogger(__name__)
@asyncio.coroutine
def _scan_experiments(log):
r = dict()
for f in os.listdir("repository"):
if f.endswith(".py"):
try:
m = file_import(os.path.join("repository", f))
except:
continue
for k, v in m.__dict__.items():
if is_experiment(v):
if v.__doc__ is None:
name = k
else:
name = v.__doc__.splitlines()[0].strip()
if name[-1] == ".":
name = name[:-1]
full_name = os.path.join("repository", f)
worker = Worker({"log": lambda message: log("scan", message)})
try:
description = yield from worker.examine(full_name)
finally:
yield from worker.close()
for class_name, class_desc in description.items():
name = class_desc["name"]
arguments = class_desc["arguments"]
if name in r:
logger.warning("Duplicate experiment name: '%s'", name)
basename = name
i = 1
while name in r:
name = basename + str(i)
i += 1
entry = {
"file": os.path.join("repository", f),
"experiment": k,
"gui_file": getattr(v, "__artiq_gui_file__", None)
"file": full_name,
"class_name": class_name,
"arguments": arguments
}
r[name] = entry
except:
logger.warning("Skipping file '%s'", f, exc_info=True)
return r
class Repository:
def __init__(self):
self.explist = Notifier(scan_experiments())
def _sync_explist(target, source):
for k in list(target.read.keys()):
if k not in source:
del target[k]
for k in source.keys():
if k not in target.read or target.read[k] != source[k]:
target[k] = source[k]
def get_data(self, filename):
with open(os.path.join("repository", filename)) as f:
return f.read()
class Repository:
def __init__(self, log_fn):
self.explist = Notifier(dict())
self._scanning = False
self.log_fn = log_fn
@asyncio.coroutine
def scan(self):
if self._scanning:
return
self._scanning = True
new_explist = yield from _scan_experiments(self.log_fn)
_sync_explist(self.explist, new_explist)
self._scanning = False
def scan_async(self):
asyncio.async(self.scan())

View File

@ -1,103 +0,0 @@
import os
import time
import re
import numpy
import h5py
from artiq.protocols.sync_struct import Notifier, process_mod
def get_hdf5_output(start_time, rid, name):
dirname = os.path.join("results",
time.strftime("%Y-%m-%d", start_time),
time.strftime("%H-%M", start_time))
filename = "{:09}-{}.h5".format(rid, name)
os.makedirs(dirname, exist_ok=True)
return h5py.File(os.path.join(dirname, filename), "w")
def get_last_rid():
r = -1
try:
day_folders = os.listdir("results")
except:
return r
day_folders = filter(lambda x: re.fullmatch('\d\d\d\d-\d\d-\d\d', x),
day_folders)
for df in day_folders:
day_path = os.path.join("results", df)
try:
minute_folders = os.listdir(day_path)
except:
continue
minute_folders = filter(lambda x: re.fullmatch('\d\d-\d\d', x),
minute_folders)
for mf in minute_folders:
minute_path = os.path.join(day_path, mf)
try:
h5files = os.listdir(minute_path)
except:
continue
for x in h5files:
m = re.fullmatch('(\d\d\d\d\d\d\d\d\d)-.*\.h5', x)
rid = int(m.group(1))
if rid > r:
r = rid
return r
_type_to_hdf5 = {
int: h5py.h5t.STD_I64BE,
float: h5py.h5t.IEEE_F64BE
}
def result_dict_to_hdf5(f, rd):
for name, data in rd.items():
if isinstance(data, list):
el_ty = type(data[0])
for d in data:
if type(d) != el_ty:
raise TypeError("All list elements must have the same"
" type for HDF5 output")
try:
el_ty_h5 = _type_to_hdf5[el_ty]
except KeyError:
raise TypeError("List element type {} is not supported for"
" HDF5 output".format(el_ty))
dataset = f.create_dataset(name, (len(data), ), el_ty_h5)
dataset[:] = data
elif isinstance(data, numpy.ndarray):
f.create_dataset(name, data=data)
else:
ty = type(data)
try:
ty_h5 = _type_to_hdf5[ty]
except KeyError:
raise TypeError("Type {} is not supported for HDF5 output"
.format(ty))
dataset = f.create_dataset(name, (), ty_h5)
dataset[()] = data
class RTResults:
def __init__(self):
self.groups = Notifier(dict())
self.current_group = "default"
def init(self, description):
data = dict()
for rtr in description.keys():
if isinstance(rtr, tuple):
for e in rtr:
data[e] = []
else:
data[rtr] = []
self.groups[self.current_group] = {
"description": description,
"data": data
}
def update(self, mod):
target = self.groups[self.current_group]["data"]
process_mod(target, mod)

View File

@ -4,7 +4,8 @@ from enum import Enum
from time import time
from artiq.master.worker import Worker
from artiq.tools import asyncio_wait_or_cancel, asyncio_queue_peek
from artiq.tools import (asyncio_wait_or_cancel, asyncio_queue_peek,
TaskObject, WaitSet)
from artiq.protocols.sync_struct import Notifier
@ -13,27 +14,28 @@ logger = logging.getLogger(__name__)
class RunStatus(Enum):
pending = 0
preparing = 1
prepare_done = 2
running = 3
run_done = 4
analyzing = 5
analyze_done = 6
paused = 7
flushing = 1
preparing = 2
prepare_done = 3
running = 4
run_done = 5
analyzing = 6
analyze_done = 7
paused = 8
def _mk_worker_method(name):
@asyncio.coroutine
def worker_method(self, *args, **kwargs):
if self._terminated:
if self.worker.closed.is_set():
return True
m = getattr(self._worker, name)
m = getattr(self.worker, name)
try:
return (yield from m(*args, **kwargs))
except Exception as e:
if isinstance(e, asyncio.CancelledError):
raise
if self._terminated:
if self.worker.closed.is_set():
logger.debug("suppressing worker exception of terminated run",
exc_info=True)
# Return completion on termination
@ -45,7 +47,7 @@ def _mk_worker_method(name):
class Run:
def __init__(self, rid, pipeline_name,
expid, priority, due_date,
expid, priority, due_date, flush,
worker_handlers, notifier):
# called through pool
self.rid = rid
@ -53,10 +55,11 @@ class Run:
self.expid = expid
self.priority = priority
self.due_date = due_date
self.flush = flush
self.worker = Worker(worker_handlers)
self._status = RunStatus.pending
self._terminated = False
self._worker = Worker(worker_handlers)
self._notifier = notifier
self._notifier[self.rid] = {
@ -64,6 +67,7 @@ class Run:
"expid": self.expid,
"priority": self.priority,
"due_date": self.due_date,
"flush": self.flush,
"status": self._status.name
}
@ -74,33 +78,35 @@ class Run:
@status.setter
def status(self, value):
self._status = value
if not self._terminated:
if not self.worker.closed.is_set():
self._notifier[self.rid]["status"] = self._status.name
# The run with the largest priority_key is to be scheduled first
def priority_key(self, now):
def priority_key(self, now=None):
if self.due_date is None:
overdue = 0
due_date_k = 0
else:
overdue = int(now > self.due_date)
due_date_k = -self.due_date
return (overdue, self.priority, due_date_k, -self.rid)
if now is not None and self.due_date is not None:
runnable = int(now > self.due_date)
else:
runnable = 1
return (runnable, self.priority, due_date_k, -self.rid)
@asyncio.coroutine
def close(self):
# called through pool
self._terminated = True
yield from self._worker.close()
yield from self.worker.close()
del self._notifier[self.rid]
_prepare = _mk_worker_method("prepare")
_build = _mk_worker_method("build")
@asyncio.coroutine
def prepare(self):
yield from self._prepare(self.rid, self.pipeline_name, self.expid,
def build(self):
yield from self._build(self.rid, self.pipeline_name, self.expid,
self.priority)
prepare = _mk_worker_method("prepare")
run = _mk_worker_method("run")
resume = _mk_worker_method("resume")
analyze = _mk_worker_method("analyze")
@ -120,20 +126,20 @@ class RIDCounter:
class RunPool:
def __init__(self, ridc, worker_handlers, notifier):
self.runs = dict()
self.submitted_callback = None
self.submitted_cb = None
self._ridc = ridc
self._worker_handlers = worker_handlers
self._notifier = notifier
def submit(self, expid, priority, due_date, pipeline_name):
def submit(self, expid, priority, due_date, flush, pipeline_name):
# called through scheduler
rid = self._ridc.get()
run = Run(rid, pipeline_name, expid, priority, due_date,
run = Run(rid, pipeline_name, expid, priority, due_date, flush,
self._worker_handlers, self._notifier)
self.runs[rid] = run
if self.submitted_callback is not None:
self.submitted_callback()
if self.submitted_cb is not None:
self.submitted_cb()
return rid
@asyncio.coroutine
@ -145,29 +151,15 @@ class RunPool:
del self.runs[rid]
class TaskObject:
def start(self):
self.task = asyncio.async(self._do())
@asyncio.coroutine
def stop(self):
self.task.cancel()
yield from asyncio.wait([self.task])
del self.task
@asyncio.coroutine
def _do(self):
raise NotImplementedError
class PrepareStage(TaskObject):
def __init__(self, deleter, pool, outq):
self.deleter = deleter
def __init__(self, flush_tracker, delete_cb, pool, outq):
self.flush_tracker = flush_tracker
self.delete_cb = delete_cb
self.pool = pool
self.outq = outq
self.pool_submitted = asyncio.Event()
self.pool.submitted_callback = lambda: self.pool_submitted.set()
self.pool.submitted_cb = lambda: self.pool_submitted.set()
@asyncio.coroutine
def _push_runs(self):
@ -186,14 +178,24 @@ class PrepareStage(TaskObject):
# pending_runs is an empty sequence
return None
if run.due_date is None or run.due_date < now:
if run.flush:
run.status = RunStatus.flushing
yield from asyncio_wait_or_cancel(
[self.flush_tracker.wait_empty(),
run.worker.closed.wait()],
return_when=asyncio.FIRST_COMPLETED)
if run.worker.closed.is_set():
continue
run.status = RunStatus.preparing
self.flush_tracker.add(run.rid)
try:
yield from run.build()
yield from run.prepare()
except:
logger.warning("got worker exception in prepare stage, "
"deleting RID %d",
run.rid, exc_info=True)
self.deleter.delete(run.rid)
self.delete_cb(run.rid)
run.status = RunStatus.prepare_done
yield from self.outq.put(run)
else:
@ -214,8 +216,8 @@ class PrepareStage(TaskObject):
class RunStage(TaskObject):
def __init__(self, deleter, inq, outq):
self.deleter = deleter
def __init__(self, delete_cb, inq, outq):
self.delete_cb = delete_cb
self.inq = inq
self.outq = outq
@ -228,10 +230,9 @@ class RunStage(TaskObject):
next_irun = asyncio_queue_peek(self.inq)
except asyncio.QueueEmpty:
next_irun = None
now = time()
if not stack or (
next_irun is not None and
next_irun.priority_key(now) > stack[-1].priority_key(now)):
next_irun.priority_key() > stack[-1].priority_key()):
stack.append((yield from self.inq.get()))
run = stack.pop()
@ -246,7 +247,7 @@ class RunStage(TaskObject):
logger.warning("got worker exception in run stage, "
"deleting RID %d",
run.rid, exc_info=True)
self.deleter.delete(run.rid)
self.delete_cb(run.rid)
else:
if completed:
run.status = RunStatus.run_done
@ -257,8 +258,8 @@ class RunStage(TaskObject):
class AnalyzeStage(TaskObject):
def __init__(self, deleter, inq):
self.deleter = deleter
def __init__(self, delete_cb, inq):
self.delete_cb = delete_cb
self.inq = inq
@asyncio.coroutine
@ -273,17 +274,23 @@ class AnalyzeStage(TaskObject):
logger.warning("got worker exception in analyze stage, "
"deleting RID %d",
run.rid, exc_info=True)
self.deleter.delete(run.rid)
self.delete_cb(run.rid)
run.status = RunStatus.analyze_done
self.deleter.delete(run.rid)
self.delete_cb(run.rid)
class Pipeline:
def __init__(self, ridc, deleter, worker_handlers, notifier):
flush_tracker = WaitSet()
def delete_cb(rid):
deleter.delete(rid)
flush_tracker.discard(rid)
self.pool = RunPool(ridc, worker_handlers, notifier)
self._prepare = PrepareStage(deleter, self.pool, asyncio.Queue(maxsize=1))
self._run = RunStage(deleter, self._prepare.outq, asyncio.Queue(maxsize=1))
self._analyze = AnalyzeStage(deleter, self._run.outq)
self._prepare = PrepareStage(flush_tracker, delete_cb,
self.pool, asyncio.Queue(maxsize=1))
self._run = RunStage(delete_cb,
self._prepare.outq, asyncio.Queue(maxsize=1))
self._analyze = AnalyzeStage(delete_cb, self._run.outq)
def start(self):
self._prepare.start()
@ -366,7 +373,7 @@ class Scheduler:
if self._pipelines:
logger.warning("some pipelines were not garbage-collected")
def submit(self, pipeline_name, expid, priority, due_date):
def submit(self, pipeline_name, expid, priority, due_date, flush):
if self._terminated:
return
try:
@ -377,7 +384,7 @@ class Scheduler:
self._worker_handlers, self.notifier)
self._pipelines[pipeline_name] = pipeline
pipeline.start()
return pipeline.pool.submit(expid, priority, due_date, pipeline_name)
return pipeline.pool.submit(expid, priority, due_date, flush, pipeline_name)
def delete(self, rid):
self._deleter.delete(rid)

View File

@ -4,10 +4,11 @@ import logging
import subprocess
import traceback
import time
from functools import partial
from artiq.protocols import pyon
from artiq.language.units import strip_unit
from artiq.tools import asyncio_process_wait_timeout, asyncio_wait_or_cancel
from artiq.tools import (asyncio_process_wait_timeout, asyncio_process_wait,
asyncio_wait_or_cancel)
logger = logging.getLogger(__name__)
@ -26,14 +27,9 @@ class WorkerError(Exception):
class Worker:
def __init__(self, handlers,
send_timeout=0.5, term_timeout=1.0,
prepare_timeout=15.0, results_timeout=15.0):
def __init__(self, handlers=dict(), send_timeout=0.5):
self.handlers = handlers
self.send_timeout = send_timeout
self.term_timeout = term_timeout
self.prepare_timeout = prepare_timeout
self.results_timeout = results_timeout
self.rid = None
self.process = None
@ -49,7 +45,7 @@ class Worker:
avail = set(range(n_user_watchdogs + 1)) \
- set(self.watchdogs.keys())
wid = next(iter(avail))
self.watchdogs[wid] = time.monotonic() + strip_unit(t, "s")
self.watchdogs[wid] = time.monotonic() + t
return wid
def delete_watchdog(self, wid):
@ -74,7 +70,12 @@ class Worker:
self.io_lock.release()
@asyncio.coroutine
def close(self):
def close(self, term_timeout=1.0):
"""Interrupts any I/O with the worker process and terminates the
worker process.
This method should always be called by the user to clean up, even if
build() or examine() raises an exception."""
self.closed.set()
yield from self.io_lock.acquire()
try:
@ -83,33 +84,35 @@ class Worker:
logger.debug("worker was not created (RID %s)", self.rid)
return
if self.process.returncode is not None:
logger.debug("worker already terminated (RID %d)", self.rid)
logger.debug("worker already terminated (RID %s)", self.rid)
if self.process.returncode != 0:
logger.warning("worker finished with status code %d"
" (RID %d)", self.process.returncode,
" (RID %s)", self.process.returncode,
self.rid)
return
obj = {"action": "terminate"}
try:
yield from self._send(obj, self.send_timeout, cancellable=False)
yield from self._send(obj, cancellable=False)
except:
logger.warning("failed to send terminate command to worker"
" (RID %d), killing", self.rid, exc_info=True)
" (RID %s), killing", self.rid, exc_info=True)
self.process.kill()
yield from asyncio_process_wait(self.process)
return
try:
yield from asyncio_process_wait_timeout(self.process,
self.term_timeout)
term_timeout)
except asyncio.TimeoutError:
logger.warning("worker did not exit (RID %d), killing", self.rid)
logger.warning("worker did not exit (RID %s), killing", self.rid)
self.process.kill()
yield from asyncio_process_wait(self.process)
else:
logger.debug("worker exited gracefully (RID %d)", self.rid)
logger.debug("worker exited gracefully (RID %s)", self.rid)
finally:
self.io_lock.release()
@asyncio.coroutine
def _send(self, obj, timeout, cancellable=True):
def _send(self, obj, cancellable=True):
assert self.io_lock.locked()
line = pyon.encode(obj)
self.process.stdin.write(line.encode())
@ -118,7 +121,7 @@ class Worker:
if cancellable:
ifs.append(self.closed.wait())
fs = yield from asyncio_wait_or_cancel(
ifs, timeout=timeout,
ifs, timeout=self.send_timeout,
return_when=asyncio.FIRST_COMPLETED)
if all(f.cancelled() for f in fs):
raise WorkerTimeout("Timeout sending data to worker")
@ -135,7 +138,7 @@ class Worker:
[self.process.stdout.readline(), self.closed.wait()],
timeout=timeout, return_when=asyncio.FIRST_COMPLETED)
if all(f.cancelled() for f in fs):
raise WorkerTimeout("Timeout sending data to worker")
raise WorkerTimeout("Timeout receiving data from worker")
if self.closed.is_set():
raise WorkerError("Data transmission to worker cancelled")
line = fs[0].result()
@ -168,8 +171,12 @@ class Worker:
func = self.create_watchdog
elif action == "delete_watchdog":
func = self.delete_watchdog
elif action == "register_experiment":
func = self.register_experiment
else:
func = self.handlers[action]
if getattr(func, "worker_pass_rid", False):
func = partial(func, self.rid)
try:
data = func(**obj)
reply = {"status": "ok", "data": data}
@ -178,7 +185,7 @@ class Worker:
"message": traceback.format_exc()}
yield from self.io_lock.acquire()
try:
yield from self._send(reply, self.send_timeout)
yield from self._send(reply)
finally:
self.io_lock.release()
@ -189,7 +196,7 @@ class Worker:
try:
yield from self.io_lock.acquire()
try:
yield from self._send(obj, self.send_timeout)
yield from self._send(obj)
finally:
self.io_lock.release()
try:
@ -202,16 +209,20 @@ class Worker:
return completed
@asyncio.coroutine
def prepare(self, rid, pipeline_name, expid, priority):
def build(self, rid, pipeline_name, expid, priority, timeout=15.0):
self.rid = rid
yield from self._create_process()
yield from self._worker_action(
{"action": "prepare",
{"action": "build",
"rid": rid,
"pipeline_name": pipeline_name,
"expid": expid,
"priority": priority},
self.prepare_timeout)
timeout)
@asyncio.coroutine
def prepare(self):
yield from self._worker_action({"action": "prepare"})
@asyncio.coroutine
def run(self):
@ -236,6 +247,18 @@ class Worker:
yield from self._worker_action({"action": "analyze"})
@asyncio.coroutine
def write_results(self):
def write_results(self, timeout=15.0):
yield from self._worker_action({"action": "write_results"},
self.results_timeout)
timeout)
@asyncio.coroutine
def examine(self, file, timeout=20.0):
yield from self._create_process()
r = dict()
def register(class_name, name, arguments):
r[class_name] = {"name": name, "arguments": arguments}
self.register_experiment = register
yield from self._worker_action({"action": "examine",
"file": file}, timeout)
del self.register_experiment
return r

View File

@ -1,76 +1,114 @@
from collections import OrderedDict
import importlib
import logging
import os
import time
import re
import numpy
import h5py
from artiq.protocols.sync_struct import Notifier
from artiq.protocols.pc_rpc import Client, BestEffortClient
from artiq.master.results import result_dict_to_hdf5
logger = logging.getLogger(__name__)
class ResultDB:
def __init__(self, init_rt_results, update_rt_results):
self.init_rt_results = init_rt_results
self.update_rt_results = update_rt_results
self.rtr_description = dict()
def get_hdf5_output(start_time, rid, name):
dirname = os.path.join("results",
time.strftime("%Y-%m-%d", start_time),
time.strftime("%H-%M", start_time))
filename = "{:09}-{}.h5".format(rid, name)
os.makedirs(dirname, exist_ok=True)
return h5py.File(os.path.join(dirname, filename), "w")
def add_rt_results(self, rtr_description):
intr = set(self.rtr_description.keys()).intersection(
set(rtr_description.keys()))
if intr:
raise ValueError("Duplicate realtime results: " + ", ".join(intr))
self.rtr_description.update(rtr_description)
def build(self):
realtime_results_set = set()
for rtr in self.rtr_description.keys():
if isinstance(rtr, tuple):
for e in rtr:
realtime_results_set.add(e)
else:
realtime_results_set.add(rtr)
self.realtime_data = Notifier({x: [] for x in realtime_results_set})
self.data = Notifier(dict())
self.init_rt_results(self.rtr_description)
self.realtime_data.publish = lambda notifier, data: \
self.update_rt_results(data)
def _request(self, name):
def get_last_rid():
r = -1
try:
return self.realtime_data[name]
except KeyError:
day_folders = os.listdir("results")
except:
return r
day_folders = filter(lambda x: re.fullmatch('\d\d\d\d-\d\d-\d\d', x),
day_folders)
for df in day_folders:
day_path = os.path.join("results", df)
try:
return self.data[name]
except KeyError:
self.data[name] = []
return self.data[name]
def request(self, name):
r = self._request(name)
r.kernel_attr_init = False
minute_folders = os.listdir(day_path)
except:
continue
minute_folders = filter(lambda x: re.fullmatch('\d\d-\d\d', x),
minute_folders)
for mf in minute_folders:
minute_path = os.path.join(day_path, mf)
try:
h5files = os.listdir(minute_path)
except:
continue
for x in h5files:
m = re.fullmatch('(\d\d\d\d\d\d\d\d\d)-.*\.h5', x)
rid = int(m.group(1))
if rid > r:
r = rid
return r
def set(self, name, value):
if name in self.realtime_data.read:
self.realtime_data[name] = value
_type_to_hdf5 = {
int: h5py.h5t.STD_I64BE,
float: h5py.h5t.IEEE_F64BE
}
def result_dict_to_hdf5(f, rd):
for name, data in rd.items():
if isinstance(data, list):
el_ty = type(data[0])
for d in data:
if type(d) != el_ty:
raise TypeError("All list elements must have the same"
" type for HDF5 output")
try:
el_ty_h5 = _type_to_hdf5[el_ty]
except KeyError:
raise TypeError("List element type {} is not supported for"
" HDF5 output".format(el_ty))
dataset = f.create_dataset(name, (len(data), ), el_ty_h5)
dataset[:] = data
elif isinstance(data, numpy.ndarray):
f.create_dataset(name, data=data)
else:
self.data[name] = value
ty = type(data)
try:
ty_h5 = _type_to_hdf5[ty]
except KeyError:
raise TypeError("Type {} is not supported for HDF5 output"
.format(ty))
dataset = f.create_dataset(name, (), ty_h5)
dataset[()] = data
class ResultDB:
def __init__(self):
self.rt = Notifier(dict())
self.nrt = dict()
def get(self, key):
try:
return self.nrt[key]
except KeyError:
return self.rt[key].read
def write_hdf5(self, f):
result_dict_to_hdf5(f, self.realtime_data.read)
result_dict_to_hdf5(f, self.data.read)
result_dict_to_hdf5(f, self.rt.read)
result_dict_to_hdf5(f, self.nrt)
def _create_device(desc, dbh):
def _create_device(desc, dmgr):
ty = desc["type"]
if ty == "local":
module = importlib.import_module(desc["module"])
device_class = getattr(module, desc["class"])
return device_class(dbh, **desc["arguments"])
return device_class(dmgr, **desc["arguments"])
elif ty == "controller":
if desc["best_effort"]:
cl = BestEffortClient
@ -81,30 +119,26 @@ def _create_device(desc, dbh):
raise ValueError("Unsupported type in device DB: " + ty)
class DBHub:
"""Connects device, parameter and result databases to experiment.
Handle device driver creation and destruction.
"""
def __init__(self, ddb, pdb, rdb, read_only=False):
class DeviceManager:
"""Handles creation and destruction of local device drivers and controller
RPC clients."""
def __init__(self, ddb, virtual_devices=dict()):
self.ddb = ddb
self.virtual_devices = virtual_devices
self.active_devices = OrderedDict()
self.get_parameter = pdb.request
if not read_only:
self.set_parameter = pdb.set
self.add_rt_results = rdb.add_rt_results
self.get_result = rdb.request
self.set_result = rdb.set
def get_device(self, name):
def get(self, name):
"""Get the device driver or controller client corresponding to a
device database entry."""
if name in self.virtual_devices:
return self.virtual_devices[name]
if name in self.active_devices:
return self.active_devices[name]
else:
desc = self.ddb.request(name)
desc = self.ddb.get(name)
while isinstance(desc, str):
# alias
desc = self.ddb.request(desc)
desc = self.ddb.get(desc)
dev = _create_device(desc, self)
self.active_devices[name] = dev
return dev

View File

@ -3,9 +3,8 @@ import time
from artiq.protocols import pyon
from artiq.tools import file_import
from artiq.master.worker_db import DBHub, ResultDB
from artiq.master.results import get_hdf5_output
from artiq.language.experiment import is_experiment
from artiq.master.worker_db import DeviceManager, ResultDB, get_hdf5_output
from artiq.language.environment import is_experiment
from artiq.language.core import set_watchdog_factory
@ -45,16 +44,34 @@ def make_parent_action(action, argnames, exception=ParentActionError):
return parent_action
class LogForwarder:
def __init__(self):
self.buffer = ""
to_parent = staticmethod(make_parent_action("log", "message"))
def write(self, data):
self.buffer += data
while "\n" in self.buffer:
i = self.buffer.index("\n")
self.to_parent(self.buffer[:i])
self.buffer = self.buffer[i+1:]
def flush(self):
pass
class ParentDDB:
request = make_parent_action("req_device", "name", KeyError)
get = make_parent_action("get_device", "name", KeyError)
class ParentPDB:
request = make_parent_action("req_parameter", "name", KeyError)
get = make_parent_action("get_parameter", "name", KeyError)
set = make_parent_action("set_parameter", "name value")
init_rt_results = make_parent_action("init_rt_results", "description")
update_rt_results = make_parent_action("update_rt_results", "mod")
@ -79,18 +96,18 @@ class Scheduler:
pause = staticmethod(make_parent_action("pause", ""))
submit = staticmethod(make_parent_action("scheduler_submit",
"pipeline_name expid priority due_date"))
"pipeline_name expid priority due_date flush"))
cancel = staticmethod(make_parent_action("scheduler_cancel", "rid"))
def __init__(self, pipeline_name, expid, priority):
def set_run_info(self, pipeline_name, expid, priority):
self.pipeline_name = pipeline_name
self.expid = expid
self.priority = priority
def get_exp(file, exp):
def get_exp(file, class_name):
module = file_import(file)
if exp is None:
if class_name is None:
exps = [v for k, v in module.__dict__.items()
if is_experiment(v)]
if len(exps) != 1:
@ -98,11 +115,44 @@ def get_exp(file, exp):
.format(len(exps)))
return exps[0]
else:
return getattr(module, exp)
return getattr(module, class_name)
register_experiment = make_parent_action("register_experiment",
"class_name name arguments")
class DummyDMGR:
def get(self, name):
return None
class DummyPDB:
def get(self, name):
return None
def set(self, name, value):
pass
def examine(dmgr, pdb, rdb, file):
module = file_import(file)
for class_name, exp_class in module.__dict__.items():
if is_experiment(exp_class):
if exp_class.__doc__ is None:
name = class_name
else:
name = exp_class.__doc__.splitlines()[0].strip()
if name[-1] == ".":
name = name[:-1]
exp_inst = exp_class(dmgr, pdb, rdb, default_arg_none=True)
arguments = [(k, v.describe())
for k, v in exp_inst.requested_args.items()]
register_experiment(class_name, name, arguments)
def main():
sys.stdout = sys.stderr
sys.stdout = sys.stderr = LogForwarder()
start_time = None
rid = None
@ -110,26 +160,27 @@ def main():
exp = None
exp_inst = None
rdb = ResultDB(init_rt_results, update_rt_results)
dbh = DBHub(ParentDDB, ParentPDB, rdb)
dmgr = DeviceManager(ParentDDB,
virtual_devices={"scheduler": Scheduler()})
rdb = ResultDB()
rdb.rt.publish = update_rt_results
try:
while True:
obj = get_object()
action = obj["action"]
if action == "prepare":
if action == "build":
start_time = time.localtime()
rid = obj["rid"]
pipeline_name = obj["pipeline_name"]
expid = obj["expid"]
priority = obj["priority"]
exp = get_exp(expid["file"], expid["experiment"])
exp_inst = exp(dbh,
scheduler=Scheduler(pipeline_name,
expid,
priority),
exp = get_exp(expid["file"], expid["class_name"])
dmgr.virtual_devices["scheduler"].set_run_info(
obj["pipeline_name"], expid, obj["priority"])
exp_inst = exp(dmgr, ParentPDB, rdb,
**expid["arguments"])
rdb.build()
put_object({"action": "completed"})
elif action == "prepare":
exp_inst.prepare()
put_object({"action": "completed"})
elif action == "run":
exp_inst.run()
@ -144,10 +195,13 @@ def main():
finally:
f.close()
put_object({"action": "completed"})
elif action == "examine":
examine(DummyDMGR(), DummyPDB(), ResultDB(), obj["file"])
put_object({"action": "completed"})
elif action == "terminate":
break
finally:
dbh.close_devices()
dmgr.close_devices()
if __name__ == "__main__":
main()

View File

@ -20,7 +20,7 @@ class FlatFileDB:
def save(self):
pyon.store_file(self.filename, self.data.read)
def request(self, name):
def get(self, name):
return self.data.read[name]
def set(self, name, value):
@ -36,19 +36,3 @@ class FlatFileDB:
timestamp = time()
for hook in self.hooks:
hook.delete(timestamp, name)
class SimpleHistory:
def __init__(self, depth):
self.depth = depth
self.history = Notifier([])
def set(self, timestamp, name, value):
if len(self.history.read) >= self.depth:
del self.history[0]
self.history.append((timestamp, name, value))
def delete(self, timestamp, name):
if len(self.history.read) >= self.depth:
del self.history[0]
self.history.append((timestamp, name))

View File

@ -0,0 +1,45 @@
import threading
import logging
logger = logging.getLogger(__name__)
class FFProxy:
"""Proxies a target object and runs its methods in the background.
All method calls to this object are forwarded to the target and executed
in a background thread. Method calls return immediately. Exceptions from
the target method are turned into warnings. At most one method from the
target object may be executed in the background; if a new call is
submitted while the previous one is still executing, a warning is printed
and the new call is dropped.
This feature is typically used to wrap slow and non-critical RPCs in
experiments.
"""
def __init__(self, target):
self.target = target
self._thread = None
def ff_join(self):
"""Waits until any background method finishes its execution."""
if self._thread is not None:
self._thread.join()
def __getattr__(self, k):
def run_in_thread(*args, **kwargs):
if self._thread is not None and self._thread.is_alive():
logger.warning("skipping fire-and-forget call to %r.%s as "
"previous call did not complete",
self.target, k)
return
def thread_body():
try:
getattr(self.target, k)(*args, **kwargs)
except:
logger.warning("fire-and-forget call to %r.%s raised an "
"exception:", self.target, k, exc_info=True)
self._thread = threading.Thread(target=thread_body)
self._thread.start()
return run_in_thread

View File

@ -22,7 +22,6 @@ import inspect
from artiq.protocols import pyon
from artiq.protocols.asyncio_server import AsyncioServer as _AsyncioServer
from artiq.tools import format_arguments
logger = logging.getLogger(__name__)
@ -246,7 +245,8 @@ class AsyncioClient:
def __getattr__(self, name):
@asyncio.coroutine
def proxy(*args, **kwargs):
return self.__do_rpc(name, args, kwargs)
res = yield from self.__do_rpc(name, args, kwargs)
return res
return proxy
@ -374,6 +374,16 @@ class BestEffortClient:
return proxy
def _format_arguments(arguments):
fmtargs = []
for k, v in sorted(arguments.items(), key=itemgetter(0)):
fmtargs.append(k + "=" + repr(v))
if fmtargs:
return ", ".join(fmtargs)
else:
return ""
class _PrettyPrintCall:
def __init__(self, obj):
self.obj = obj
@ -382,7 +392,7 @@ class _PrettyPrintCall:
r = self.obj["name"] + "("
args = ", ".join([repr(a) for a in self.obj["args"]])
r += args
kwargs = format_arguments(self.obj["kwargs"])
kwargs = _format_arguments(self.obj["kwargs"])
if args and kwargs:
r += ", "
r += kwargs
@ -442,15 +452,18 @@ class Server(_AsyncioServer):
try:
if obj["action"] == "get_rpc_method_list":
members = inspect.getmembers(target, inspect.ismethod)
methods = {}
doc = {
"docstring": inspect.getdoc(target),
"methods": {}
}
for name, method in members:
if name.startswith("_"):
continue
method = getattr(target, name)
argspec = inspect.getfullargspec(method)
methods[name] = (dict(argspec.__dict__),
doc["methods"][name] = (dict(argspec.__dict__),
inspect.getdoc(method))
obj = {"status": "ok", "ret": methods}
obj = {"status": "ok", "ret": doc}
elif obj["action"] == "call":
logger.debug("calling %s", _PrettyPrintCall(obj))
method = getattr(target, obj["name"])

View File

@ -14,7 +14,6 @@ The main rationale for this new custom serializer (instead of using JSON) is
that JSON does not support Numpy and more generally cannot be extended with
other data types while keeping a concise syntax. Here we can use the Python
function call syntax to mark special data types.
"""
@ -25,8 +24,6 @@ import tempfile
import numpy
from artiq.language.units import Quantity
_encode_map = {
type(None): "none",
@ -39,7 +36,6 @@ _encode_map = {
list: "list",
dict: "dict",
Fraction: "fraction",
Quantity: "quantity",
numpy.ndarray: "nparray"
}
@ -110,10 +106,6 @@ class _Encoder:
return "Fraction({}, {})".format(encode(x.numerator),
encode(x.denominator))
def encode_quantity(self, x):
return "Quantity({}, {})".format(encode(x.amount),
encode(x.unit))
def encode_nparray(self, x):
r = "nparray("
r += encode(x.shape) + ", "
@ -147,7 +139,6 @@ _eval_dict = {
"true": True,
"Fraction": Fraction,
"Quantity": Quantity,
"nparray": _nparray
}

View File

@ -8,11 +8,11 @@ describing each modification made to the structure (*mods*).
Structures must be PYON serializable and contain only lists, dicts, and
immutable types. Lists and dicts can be nested arbitrarily.
"""
import asyncio
from operator import getitem
from functools import partial
from artiq.protocols import pyon
from artiq.protocols.asyncio_server import AsyncioServer
@ -22,9 +22,7 @@ _init_string = b"ARTIQ sync_struct\n"
def process_mod(target, mod):
"""Apply a *mod* to the target, mutating it.
"""
"""Apply a *mod* to the target, mutating it."""
for key in mod["path"]:
target = getitem(target, key)
action = mod["action"]
@ -52,8 +50,8 @@ class Subscriber:
Multiple functions can be specified in a list for the ``Subscriber``
to update several local objects simultaneously.
:param notify_cb: An optional function called every time a mod is received
from the publisher. The mod is passed as parameter.
from the publisher. The mod is passed as parameter. The function is
called after the mod has been processed.
"""
def __init__(self, notifier_name, target_builder, notify_cb=None):
self.notifier_name = notifier_name
@ -133,7 +131,6 @@ class Notifier:
:param backing_struct: Structure to encapsulate. For convenience, it
also becomes available as the ``read`` property of the ``Notifier``.
"""
def __init__(self, backing_struct, root=None, path=[]):
self.read = backing_struct
@ -149,34 +146,28 @@ class Notifier:
# All modifications must go through them!
def append(self, x):
"""Append to a list.
"""
"""Append to a list."""
self._backing_struct.append(x)
if self.root.publish is not None:
self.root.publish(self.root, {"action": "append",
self.root.publish({"action": "append",
"path": self._path,
"x": x})
def insert(self, i, x):
"""Insert an element into a list.
"""
"""Insert an element into a list."""
self._backing_struct.insert(i, x)
if self.root.publish is not None:
self.root.publish(self.root, {"action": "insert",
self.root.publish({"action": "insert",
"path": self._path,
"i": i, "x": x})
def pop(self, i=-1):
"""Pop an element from a list. The returned element is not
encapsulated in a ``Notifier`` and its mutations are no longer
tracked.
"""
tracked."""
r = self._backing_struct.pop(i)
if self.root.publish is not None:
self.root.publish(self.root, {"action": "pop",
self.root.publish({"action": "pop",
"path": self._path,
"i": i})
return r
@ -184,7 +175,7 @@ class Notifier:
def __setitem__(self, key, value):
self._backing_struct.__setitem__(key, value)
if self.root.publish is not None:
self.root.publish(self.root, {"action": "setitem",
self.root.publish({"action": "setitem",
"path": self._path,
"key": key,
"value": value})
@ -192,7 +183,7 @@ class Notifier:
def __delitem__(self, key):
self._backing_struct.__delitem__(key)
if self.root.publish is not None:
self.root.publish(self.root, {"action": "delitem",
self.root.publish({"action": "delitem",
"path": self._path,
"key": key})
@ -208,7 +199,6 @@ class Publisher(AsyncioServer):
:param notifiers: A dictionary containing the notifiers to associate with
the ``Publisher``. The keys of the dictionary are the names of the
notifiers to be used with ``Subscriber``.
"""
def __init__(self, notifiers):
AsyncioServer.__init__(self)
@ -217,7 +207,7 @@ class Publisher(AsyncioServer):
self._notifier_names = {id(v): k for k, v in notifiers.items()}
for notifier in notifiers.values():
notifier.publish = self.publish
notifier.publish = partial(self.publish, notifier)
@asyncio.coroutine
def _handle_connection_cr(self, reader, writer):
@ -256,8 +246,8 @@ class Publisher(AsyncioServer):
finally:
writer.close()
def publish(self, notifier, obj):
line = pyon.encode(obj) + "\n"
def publish(self, notifier, mod):
line = pyon.encode(mod) + "\n"
line = line.encode()
notifier_name = self._notifier_names[id(notifier)]
for recipient in self._recipients[notifier_name]:

View File

@ -1,6 +1,6 @@
from artiq.py2llvm.module import Module
def get_runtime_binary(env, func_def):
module = Module(env)
def get_runtime_binary(runtime, func_def):
module = Module(runtime)
module.compile_function(func_def, dict())
return module.emit_object()

View File

@ -1,6 +1,6 @@
from pythonparser import ast
import llvmlite.ir as ll
import llvmlite_or1k.ir as ll
from artiq.py2llvm import values, base_types, fractions, lists, iterators
from artiq.py2llvm.tools import is_terminated
@ -39,8 +39,8 @@ _ast_cmps = {
class Visitor:
def __init__(self, env, ns, builder=None):
self.env = env
def __init__(self, runtime, ns, builder=None):
self.runtime = runtime
self.ns = ns
self.builder = builder
self._break_stack = []
@ -182,7 +182,7 @@ class Visitor:
self.builder,
[self.visit_expression(arg) for arg in node.args])
elif fn == "syscall":
return self.env.build_syscall(
return self.runtime.build_syscall(
node.args[0].s,
[self.visit_expression(expr) for expr in node.args[1:]],
self.builder)
@ -420,7 +420,7 @@ class Visitor:
def _break_loop_body(self, target_block):
exception_levels = self._exception_level_stack[-1]
if exception_levels:
self.env.build_pop(self.builder, exception_levels)
self.runtime.build_pop(self.builder, exception_levels)
self.builder.branch(target_block)
def _visit_stmt_Break(self, node):
@ -436,7 +436,7 @@ class Visitor:
val = self.visit_expression(node.value)
exception_levels = sum(self._exception_level_stack)
if exception_levels:
self.env.build_pop(self.builder, exception_levels)
self.runtime.build_pop(self.builder, exception_levels)
if isinstance(val, base_types.VNone):
self.builder.ret_void()
else:
@ -456,11 +456,11 @@ class Visitor:
self.builder.branch(finally_block)
else:
eid = ll.Constant(ll.IntType(32), node.exc.args[0].n)
self.env.build_raise(self.builder, eid)
self.runtime.build_raise(self.builder, eid)
def _handle_exception(self, function, finally_block,
propagate, propagate_eid, handlers):
eid = self.env.build_getid(self.builder)
eid = self.runtime.build_getid(self.builder)
self._active_exception_stack.append(
(finally_block, propagate, propagate_eid))
self.builder.store(ll.Constant(ll.IntType(1), 1), propagate)
@ -509,7 +509,7 @@ class Visitor:
self.builder.store(ll.Constant(ll.IntType(1), 0), propagate)
propagate_eid = self.builder.alloca(ll.IntType(32),
name="propagate_eid")
exception_occured = self.env.build_catch(self.builder)
exception_occured = self.runtime.build_catch(self.builder)
self.builder.cbranch(exception_occured, exc_block, noexc_block)
self.builder.position_at_end(noexc_block)
@ -517,7 +517,7 @@ class Visitor:
self.visit_statements(node.body)
self._exception_level_stack[-1] -= 1
if not self._bb_terminated():
self.env.build_pop(self.builder, 1)
self.runtime.build_pop(self.builder, 1)
self.visit_statements(node.orelse)
if not self._bb_terminated():
self.builder.branch(finally_block)
@ -534,6 +534,6 @@ class Visitor:
self.builder.load(propagate),
propagate_block, merge_block)
self.builder.position_at_end(propagate_block)
self.env.build_raise(self.builder, self.builder.load(propagate_eid))
self.runtime.build_raise(self.builder, self.builder.load(propagate_eid))
self.builder.branch(merge_block)
self.builder.position_at_end(merge_block)

View File

@ -1,4 +1,4 @@
import llvmlite.ir as ll
import llvmlite_or1k.ir as ll
from artiq.py2llvm.values import VGeneric

View File

@ -1,7 +1,7 @@
import inspect
from pythonparser import parse, ast
import llvmlite.ir as ll
import llvmlite_or1k.ir as ll
from artiq.py2llvm.values import VGeneric, operators
from artiq.py2llvm.base_types import VBool, VInt, VFloat

View File

@ -1,4 +1,4 @@
import llvmlite.ir as ll
import llvmlite_or1k.ir as ll
from artiq.py2llvm.values import VGeneric
from artiq.py2llvm.base_types import VInt, VNone

View File

@ -1,16 +1,16 @@
import llvmlite.ir as ll
import llvmlite.binding as llvm
import llvmlite_or1k.ir as ll
import llvmlite_or1k.binding as llvm
from artiq.py2llvm import infer_types, ast_body, base_types, fractions, tools
class Module:
def __init__(self, env=None):
def __init__(self, runtime=None):
self.llvm_module = ll.Module("main")
self.env = env
self.runtime = runtime
if self.env is not None:
self.env.init_module(self)
if self.runtime is not None:
self.runtime.init_module(self)
fractions.init_module(self)
def finalize(self):
@ -30,10 +30,10 @@ class Module:
def emit_object(self):
self.finalize()
return self.env.emit_object()
return self.runtime.emit_object()
def compile_function(self, func_def, param_types):
ns = infer_types.infer_function_types(self.env, func_def, param_types)
ns = infer_types.infer_function_types(self.runtime, func_def, param_types)
retval = ns["return"]
function_type = ll.FunctionType(retval.get_llvm_type(),
@ -50,7 +50,7 @@ class Module:
for arg_ast, arg_llvm in zip(func_def.args.args, function.args):
ns[arg_ast.arg].auto_store(builder, arg_llvm)
visitor = ast_body.Visitor(self.env, ns, builder)
visitor = ast_body.Visitor(self.runtime, ns, builder)
visitor.visit_statements(func_def.body)
if not tools.is_terminated(builder.basic_block):

View File

@ -1,4 +1,4 @@
import llvmlite.ir as ll
import llvmlite_or1k.ir as ll
def is_terminated(basic_block):
return (basic_block.instructions

View File

@ -1,7 +1,7 @@
from types import SimpleNamespace
from copy import copy
import llvmlite.ir as ll
import llvmlite_or1k.ir as ll
class VGeneric:

View File

@ -1,29 +1,29 @@
from random import Random
from artiq.language.core import delay, kernel
from artiq.language.db import *
from artiq.language import units
from artiq.sim import time
class Core(AutoDB):
_level = 0
class Core:
def __init__(self, dmgr):
self.ref_period = 1
self._level = 0
def run(self, k_function, k_args, k_kwargs):
Core._level += 1
self._level += 1
r = k_function(*k_args, **k_kwargs)
Core._level -= 1
if Core._level == 0:
self._level -= 1
if self._level == 0:
print(time.manager.format_timeline())
return r
class Input(AutoDB):
class DBKeys:
core = Device()
name = Argument()
class Input:
def __init__(self, dmgr, name):
self.core = dmgr.get("core")
self.name = name
def build(self):
self.prng = Random()
@kernel
@ -40,10 +40,10 @@ class Input(AutoDB):
return result
class WaveOutput(AutoDB):
class DBKeys:
core = Device()
name = Argument()
class WaveOutput:
def __init__(self, dmgr, name):
self.core = dmgr.get("core")
self.name = name
@kernel
def pulse(self, frequency, duration):
@ -51,10 +51,10 @@ class WaveOutput(AutoDB):
delay(duration)
class VoltageOutput(AutoDB):
class DBKeys:
core = Device()
name = Argument()
class VoltageOutput:
def __init__(self, dmgr, name):
self.core = dmgr.get("core")
self.name = name
@kernel
def set(self, value):

View File

@ -30,37 +30,39 @@ class Manager:
self.timeline = []
def enter_sequential(self):
new_context = SequentialTimeContext(self.get_time())
new_context = SequentialTimeContext(self.get_time_mu())
self.stack.append(new_context)
def enter_parallel(self):
new_context = ParallelTimeContext(self.get_time())
new_context = ParallelTimeContext(self.get_time_mu())
self.stack.append(new_context)
def exit(self):
old_context = self.stack.pop()
self.take_time(old_context.block_duration)
def take_time(self, duration):
def take_time_mu(self, duration):
self.stack[-1].take_time(duration)
def get_time(self):
def get_time_mu(self):
return self.stack[-1].current_time
def set_time(self, t):
dt = t - self.get_time()
def set_time_mu(self, t):
dt = t - self.get_time_mu()
if dt < 0*s:
raise ValueError("Attempted to go back in time")
self.take_time(dt)
take_time = take_time_mu
def event(self, description):
self.timeline.append((self.get_time(), description))
self.timeline.append((self.get_time_mu(), description))
def format_timeline(self):
r = ""
prev_time = 0*s
for time, description in sorted(self.timeline, key=itemgetter(0)):
r += "@{:10} (+{:10}) ".format(str(time), str(time-prev_time))
r += "@{:.9f} (+{:.9f}) ".format(time, time-prev_time)
for item in description:
r += "{:16}".format(str(item))
r += "\n"

View File

@ -12,7 +12,7 @@ class TestSplineCoef(unittest.TestCase):
self.s = coefficients.SplineSource(self.x, self.y, order=4)
def test_get_segment(self):
return list(self.s.get_segment_data(1.5, 3.2, 1/100.))
return list(self.s.get_segment_data(start=1.5, stop=3.2, scale=.01))
def test_synth(self):
d = self.test_get_segment()
@ -32,7 +32,7 @@ class TestSplineCoef(unittest.TestCase):
@unittest.skip("manual/visual test")
def test_plot(self):
import cairoplot
import matplotlib.pyplot as plt
y = self.test_run()
x = list(range(len(y)))
cairoplot.scatter_plot("plot.png", [x, y])
plt.step(np.arange(len(y)), y)
plt.show()

276
artiq/test/coredevice.py Normal file
View File

@ -0,0 +1,276 @@
from math import sqrt
from artiq.language import *
from artiq.test.hardware_testbench import ExperimentCase
from artiq.coredevice.runtime_exceptions import RTIOUnderflow
from artiq.coredevice import runtime_exceptions
class RTT(EnvExperiment):
def build(self):
self.attr_device("core")
self.attr_device("ttl_inout")
def set_rtt(self, rtt):
self.set_result("rtt", rtt)
@kernel
def run(self):
self.ttl_inout.output()
delay(1*us)
with parallel:
# make sure not to send two commands into the same RTIO
# channel with the same timestamp
self.ttl_inout.gate_rising(5*us)
with sequential:
delay(1*us)
t0 = now_mu()
self.ttl_inout.pulse(1*us)
self.set_rtt(mu_to_seconds(self.ttl_inout.timestamp_mu() - t0))
class Loopback(EnvExperiment):
def build(self):
self.attr_device("core")
self.attr_device("loop_in")
self.attr_device("loop_out")
def set_rtt(self, rtt):
self.set_result("rtt", rtt)
@kernel
def run(self):
self.loop_in.input()
delay(1*us)
with parallel:
self.loop_in.gate_rising(2*us)
with sequential:
delay(1*us)
t0 = now_mu()
self.loop_out.pulse(1*us)
self.set_rtt(mu_to_seconds(self.loop_in.timestamp_mu() - t0))
class ClockGeneratorLoopback(EnvExperiment):
def build(self):
self.attr_device("core")
self.attr_device("loop_clock_in")
self.attr_device("loop_clock_out")
def set_count(self, count):
self.set_result("count", count)
@kernel
def run(self):
self.loop_clock_in.input()
self.loop_clock_out.stop()
delay(1*us)
with parallel:
self.loop_clock_in.gate_rising(10*us)
with sequential:
delay(200*ns)
self.loop_clock_out.set(1*MHz)
self.set_count(self.loop_clock_in.count())
class PulseRate(EnvExperiment):
def build(self):
self.attr_device("core")
self.attr_device("loop_out")
def set_pulse_rate(self, pulse_rate):
self.set_result("pulse_rate", pulse_rate)
@kernel
def run(self):
dt = seconds_to_mu(1000*ns)
while True:
try:
for i in range(1000):
self.loop_out.pulse_mu(dt)
delay_mu(dt)
except RTIOUnderflow:
dt += 1
self.core.break_realtime()
else:
self.set_pulse_rate(mu_to_seconds(2*dt))
break
class Watchdog(EnvExperiment):
def build(self):
self.attr_device("core")
@kernel
def run(self):
with watchdog(50*ms):
while True:
pass
class LoopbackCount(EnvExperiment):
def build(self):
self.attr_device("core")
self.attr_device("ttl_inout")
self.attr_argument("npulses")
def set_count(self, count):
self.set_result("count", count)
@kernel
def run(self):
self.ttl_inout.output()
delay(1*us)
with parallel:
self.ttl_inout.gate_rising(10*us)
with sequential:
for i in range(self.npulses):
delay(25*ns)
self.ttl_inout.pulse(25*ns)
self.set_count(self.ttl_inout.count())
class Underflow(EnvExperiment):
def build(self):
self.attr_device("core")
self.attr_device("ttl_out")
@kernel
def run(self):
while True:
delay(25*ns)
self.ttl_out.pulse(25*ns)
class SequenceError(EnvExperiment):
def build(self):
self.attr_device("core")
self.attr_device("ttl_out")
@kernel
def run(self):
t = now_mu()
self.ttl_out.pulse(25*us)
at_mu(t)
self.ttl_out.pulse(25*us)
class TimeKeepsRunning(EnvExperiment):
def build(self):
self.attr_device("core")
def set_time_at_start(self, time_at_start):
self.set_result("time_at_start", time_at_start)
@kernel
def run(self):
self.set_time_at_start(now_mu())
class Handover(EnvExperiment):
def build(self):
self.attr_device("core")
@kernel
def get_now(self):
self.time_at_start = now_mu()
def run(self):
self.get_now()
self.set_result("t1", self.time_at_start)
self.get_now()
self.set_result("t2", self.time_at_start)
class CoredeviceTest(ExperimentCase):
def test_rtt(self):
self.execute(RTT)
rtt = self.rdb.get("rtt")
print(rtt)
self.assertGreater(rtt, 0*ns)
self.assertLess(rtt, 100*ns)
def test_loopback(self):
self.execute(Loopback)
rtt = self.rdb.get("rtt")
print(rtt)
self.assertGreater(rtt, 0*ns)
self.assertLess(rtt, 50*ns)
def test_clock_generator_loopback(self):
self.execute(ClockGeneratorLoopback)
count = self.rdb.get("count")
self.assertEqual(count, 10)
def test_pulse_rate(self):
self.execute(PulseRate)
rate = self.rdb.get("pulse_rate")
print(rate)
self.assertGreater(rate, 100*ns)
self.assertLess(rate, 2500*ns)
def test_loopback_count(self):
npulses = 2
r = self.execute(LoopbackCount, npulses=npulses)
count = self.rdb.get("count")
self.assertEqual(count, npulses)
def test_underflow(self):
with self.assertRaises(runtime_exceptions.RTIOUnderflow):
self.execute(Underflow)
def test_sequence_error(self):
with self.assertRaises(runtime_exceptions.RTIOSequenceError):
self.execute(SequenceError)
def test_watchdog(self):
# watchdog only works on the device
with self.assertRaises(IOError):
self.execute(Watchdog)
def test_time_keeps_running(self):
self.execute(TimeKeepsRunning)
t1 = self.rdb.get("time_at_start")
self.execute(TimeKeepsRunning)
t2 = self.rdb.get("time_at_start")
dead_time = mu_to_seconds(t2 - t1, self.dmgr.get("core"))
print(dead_time)
self.assertGreater(dead_time, 1*ms)
self.assertLess(dead_time, 300*ms)
def test_handover(self):
self.execute(Handover)
self.assertEqual(self.rdb.get("t1"), self.rdb.get("t2"))
class RPCTiming(EnvExperiment):
def build(self):
self.attr_device("core")
self.attr_argument("repeats", FreeValue(100))
def nop(self, x):
pass
@kernel
def bench(self):
self.ts = [0. for _ in range(self.repeats)]
for i in range(self.repeats):
t1 = self.core.get_rtio_counter_mu()
self.nop(1)
t2 = self.core.get_rtio_counter_mu()
self.ts[i] = mu_to_seconds(t2 - t1)
def run(self):
self.bench()
mean = sum(self.ts)/self.repeats
self.set_result("rpc_time_stddev", sqrt(
sum([(t - mean)**2 for t in self.ts])/self.repeats))
self.set_result("rpc_time_mean", mean)
class RPCTest(ExperimentCase):
def test_rpc_timing(self):
self.execute(RPCTiming)
self.assertGreater(self.rdb.get("rpc_time_mean"), 100*ns)
self.assertLess(self.rdb.get("rpc_time_mean"), 15*ms)
self.assertLess(self.rdb.get("rpc_time_stddev"), 1*ms)

View File

@ -0,0 +1,220 @@
from operator import itemgetter
from fractions import Fraction
from artiq import *
from artiq.sim import devices as sim_devices
from artiq.test.hardware_testbench import ExperimentCase
def _run_on_host(k_class, **arguments):
dmgr = dict()
dmgr["core"] = sim_devices.Core(dmgr)
k_inst = k_class(dmgr, **arguments)
k_inst.run()
return k_inst
class _Primes(EnvExperiment):
def build(self):
self.attr_device("core")
self.attr_argument("output_list")
self.attr_argument("maximum")
@kernel
def run(self):
for x in range(1, self.maximum):
d = 2
prime = True
while d*d <= x:
if x % d == 0:
prime = False
break
d += 1
if prime:
self.output_list.append(x)
class _Misc(EnvExperiment):
def build(self):
self.attr_device("core")
self.input = 84
self.al = [1, 2, 3, 4, 5]
self.list_copy_in = [2*Hz, 10*MHz]
@kernel
def run(self):
self.half_input = self.input//2
self.decimal_fraction = Fraction("1.2")
self.acc = 0
for i in range(len(self.al)):
self.acc += self.al[i]
self.list_copy_out = self.list_copy_in
class _PulseLogger(EnvExperiment):
def build(self):
self.attr_device("core")
self.attr_argument("output_list")
self.attr_argument("name")
def _append(self, t, l, f):
if not hasattr(self, "first_timestamp"):
self.first_timestamp = t
self.output_list.append((self.name, t-self.first_timestamp, l, f))
def int_usec(self, mu):
return round(mu_to_seconds(mu, self.core)*1000000)
def on(self, t, f):
self._append(self.int_usec(t), True, f)
def off(self, t):
self._append(self.int_usec(t), False, 0)
@kernel
def pulse(self, f, duration):
self.on(now_mu(), f)
delay(duration)
self.off(now_mu())
class _Pulses(EnvExperiment):
def build(self):
self.attr_device("core")
self.attr_argument("output_list")
for name in "a", "b", "c", "d":
pl = _PulseLogger(*self.dbs(),
output_list=self.output_list,
name=name)
setattr(self, name, pl)
@kernel
def run(self):
for i in range(3):
with parallel:
with sequential:
self.a.pulse(100+i, 20*us)
self.b.pulse(200+i, 20*us)
with sequential:
self.c.pulse(300+i, 10*us)
self.d.pulse(400+i, 20*us)
class _MyException(Exception):
pass
class _Exceptions(EnvExperiment):
def build(self):
self.attr_device("core")
self.attr_argument("trace")
@kernel
def run(self):
for i in range(10):
self.trace.append(i)
if i == 4:
try:
self.trace.append(10)
try:
self.trace.append(11)
break
except:
pass
else:
self.trace.append(12)
try:
self.trace.append(13)
except:
pass
except _MyException:
self.trace.append(14)
for i in range(4):
try:
self.trace.append(100)
if i == 1:
raise _MyException
elif i == 2:
raise IndexError
except (TypeError, IndexError):
self.trace.append(101)
raise
except:
self.trace.append(102)
else:
self.trace.append(103)
finally:
self.trace.append(104)
class _RPCExceptions(EnvExperiment):
def build(self):
self.attr_device("core")
self.attr_argument("catch", FreeValue(False))
self.success = False
def exception_raiser(self):
raise _MyException
@kernel
def run(self):
if self.catch:
self.do_catch()
else:
self.do_not_catch()
@kernel
def do_not_catch(self):
self.exception_raiser()
@kernel
def do_catch(self):
try:
self.exception_raiser()
except _MyException:
self.success = True
class HostVsDeviceCase(ExperimentCase):
def test_primes(self):
l_device, l_host = [], []
self.execute(_Primes, maximum=100, output_list=l_device)
_run_on_host(_Primes, maximum=100, output_list=l_host)
self.assertEqual(l_device, l_host)
def test_misc(self):
for f in self.execute, _run_on_host:
uut = f(_Misc)
self.assertEqual(uut.half_input, 42)
self.assertEqual(uut.decimal_fraction, Fraction("1.2"))
self.assertEqual(uut.acc, sum(uut.al))
self.assertEqual(uut.list_copy_in, uut.list_copy_out)
def test_pulses(self):
l_device, l_host = [], []
self.execute(_Pulses, output_list=l_device)
_run_on_host(_Pulses, output_list=l_host)
l_host = sorted(l_host, key=itemgetter(1))
for channel in "a", "b", "c", "d":
c_device = [x for x in l_device if x[0] == channel]
c_host = [x for x in l_host if x[0] == channel]
self.assertEqual(c_device, c_host)
def test_exceptions(self):
t_device, t_host = [], []
with self.assertRaises(IndexError):
self.execute(_Exceptions, trace=t_device)
with self.assertRaises(IndexError):
_run_on_host(_Exceptions, trace=t_host)
self.assertEqual(t_device, t_host)
def test_rpc_exceptions(self):
for f in self.execute, _run_on_host:
with self.assertRaises(_MyException):
f(_RPCExceptions, catch=False)
uut = self.execute(_RPCExceptions, catch=True)
self.assertTrue(uut.success)

View File

@ -1,371 +0,0 @@
import unittest
from operator import itemgetter
import os
from fractions import Fraction
from artiq import *
from artiq.language.units import DimensionError
from artiq.coredevice import comm_tcp, core, runtime_exceptions, ttl
from artiq.sim import devices as sim_devices
core_device = os.getenv("ARTIQ_CORE_DEVICE")
def _run_on_device(k_class, **parameters):
comm = comm_tcp.Comm(host=core_device)
try:
coredev = core.Core(comm=comm)
k_inst = k_class(core=coredev, **parameters)
k_inst.run()
finally:
comm.close()
def _run_on_host(k_class, **parameters):
coredev = sim_devices.Core()
k_inst = k_class(core=coredev, **parameters)
k_inst.run()
class _Primes(AutoDB):
class DBKeys:
core = Device()
output_list = Argument()
maximum = Argument()
@kernel
def run(self):
for x in range(1, self.maximum):
d = 2
prime = True
while d*d <= x:
if x % d == 0:
prime = False
break
d += 1
if prime:
self.output_list.append(x)
class _Misc(AutoDB):
def build(self):
self.input = 84
self.inhomogeneous_units = []
self.al = [1, 2, 3, 4, 5]
self.list_copy_in = [2*Hz, 10*MHz]
@kernel
def run(self):
self.half_input = self.input//2
self.decimal_fraction = Fraction("1.2")
self.inhomogeneous_units.append(1000*Hz)
self.inhomogeneous_units.append(10*s)
self.acc = 0
for i in range(len(self.al)):
self.acc += self.al[i]
self.list_copy_out = self.list_copy_in
self.unit_comp = [1*MHz for _ in range(3)]
@kernel
def dimension_error1(self):
print(1*Hz + 1*s)
@kernel
def dimension_error2(self):
print(1*Hz < 1*s)
@kernel
def dimension_error3(self):
check_unit(1*Hz, "s")
@kernel
def dimension_error4(self):
delay(10*Hz)
class _PulseLogger(AutoDB):
class DBKeys:
core = Device()
output_list = Argument()
name = Argument()
def _append(self, t, l, f):
if not hasattr(self, "first_timestamp"):
self.first_timestamp = t
self.output_list.append((self.name, t-self.first_timestamp, l, f))
def on(self, t, f):
self._append(t, True, f)
def off(self, t):
self._append(t, False, 0)
@kernel
def pulse(self, f, duration):
self.on(int(now().amount*1000000000), f)
delay(duration)
self.off(int(now().amount*1000000000))
class _Pulses(AutoDB):
class DBKeys:
core = Device()
output_list = Argument()
def build(self):
for name in "a", "b", "c", "d":
pl = _PulseLogger(core=self.core,
output_list=self.output_list,
name=name)
setattr(self, name, pl)
@kernel
def run(self):
for i in range(3):
with parallel:
with sequential:
self.a.pulse(100+i, 20*us)
self.b.pulse(200+i, 20*us)
with sequential:
self.c.pulse(300+i, 10*us)
self.d.pulse(400+i, 20*us)
class _MyException(Exception):
pass
class _Exceptions(AutoDB):
class DBKeys:
core = Device()
trace = Argument()
@kernel
def run(self):
for i in range(10):
self.trace.append(i)
if i == 4:
try:
self.trace.append(10)
try:
self.trace.append(11)
break
except:
pass
else:
self.trace.append(12)
try:
self.trace.append(13)
except:
pass
except _MyException:
self.trace.append(14)
for i in range(4):
try:
self.trace.append(100)
if i == 1:
raise _MyException
elif i == 2:
raise IndexError
except (TypeError, IndexError):
self.trace.append(101)
raise
except:
self.trace.append(102)
else:
self.trace.append(103)
finally:
self.trace.append(104)
class _RPCExceptions(AutoDB):
class DBKeys:
core = Device()
def build(self):
self.success = False
def exception_raiser(self):
raise _MyException
@kernel
def do_not_catch(self):
self.exception_raiser()
@kernel
def catch(self):
try:
self.exception_raiser()
except _MyException:
self.success = True
class _Watchdog(AutoDB):
class DBKeys:
core = Device()
@kernel
def run(self):
with watchdog(50*ms):
while True:
pass
@unittest.skipUnless(core_device, "no hardware")
class ExecutionCase(unittest.TestCase):
def test_primes(self):
l_device, l_host = [], []
_run_on_device(_Primes, maximum=100, output_list=l_device)
_run_on_host(_Primes, maximum=100, output_list=l_host)
self.assertEqual(l_device, l_host)
def test_misc(self):
comm = comm_tcp.Comm(host=core_device)
try:
coredev = core.Core(comm=comm)
uut = _Misc(core=coredev)
uut.run()
self.assertEqual(uut.half_input, 42)
self.assertEqual(uut.decimal_fraction, Fraction("1.2"))
self.assertEqual(uut.inhomogeneous_units, [1000*Hz, 10*s])
self.assertEqual(uut.acc, sum(uut.al))
self.assertEqual(uut.list_copy_in, uut.list_copy_out)
self.assertEqual(uut.unit_comp, [1*MHz for _ in range(3)])
with self.assertRaises(DimensionError):
uut.dimension_error1()
with self.assertRaises(DimensionError):
uut.dimension_error2()
with self.assertRaises(DimensionError):
uut.dimension_error3()
with self.assertRaises(DimensionError):
uut.dimension_error4()
finally:
comm.close()
def test_pulses(self):
l_device, l_host = [], []
_run_on_device(_Pulses, output_list=l_device)
_run_on_host(_Pulses, output_list=l_host)
l_host = sorted(l_host, key=itemgetter(1))
for channel in "a", "b", "c", "d":
c_device = [x for x in l_device if x[0] == channel]
c_host = [x for x in l_host if x[0] == channel]
self.assertEqual(c_device, c_host)
def test_exceptions(self):
t_device, t_host = [], []
with self.assertRaises(IndexError):
_run_on_device(_Exceptions, trace=t_device)
with self.assertRaises(IndexError):
_run_on_host(_Exceptions, trace=t_host)
self.assertEqual(t_device, t_host)
def test_rpc_exceptions(self):
comm = comm_tcp.Comm(host=core_device)
try:
uut = _RPCExceptions(core=core.Core(comm=comm))
with self.assertRaises(_MyException):
uut.do_not_catch()
uut.catch()
self.assertTrue(uut.success)
finally:
comm.close()
def test_watchdog(self):
with self.assertRaises(IOError):
_run_on_device(_Watchdog)
class _RTIOLoopback(AutoDB):
class DBKeys:
core = Device()
io = Device()
npulses = Argument()
def report(self, n):
self.result = n
@kernel
def run(self):
self.io.output()
delay(1*us)
with parallel:
self.io.gate_rising(10*us)
with sequential:
for i in range(self.npulses):
delay(25*ns)
self.io.pulse(25*ns)
self.report(self.io.count())
class _RTIOUnderflow(AutoDB):
class DBKeys:
core = Device()
o = Device()
@kernel
def run(self):
while True:
delay(25*ns)
self.o.pulse(25*ns)
class _RTIOSequenceError(AutoDB):
class DBKeys:
core = Device()
o = Device()
@kernel
def run(self):
t = now()
self.o.pulse(25*us)
at(t)
self.o.pulse(25*us)
@unittest.skipUnless(core_device, "no hardware")
class RTIOCase(unittest.TestCase):
# Connect channels 0 and 1 together for this test
# (C11 and C13 on Papilio Pro)
def test_loopback(self):
npulses = 4
comm = comm_tcp.Comm(host=core_device)
try:
coredev = core.Core(comm=comm)
uut = _RTIOLoopback(
core=coredev,
io=ttl.TTLInOut(core=coredev, channel=0),
npulses=npulses
)
uut.run()
self.assertEqual(uut.result, npulses)
finally:
comm.close()
def test_underflow(self):
comm = comm_tcp.Comm(host=core_device)
try:
coredev = core.Core(comm=comm)
uut = _RTIOUnderflow(
core=coredev,
o=ttl.TTLOut(core=coredev, channel=2)
)
with self.assertRaises(runtime_exceptions.RTIOUnderflow):
uut.run()
finally:
comm.close()
def test_sequence_error(self):
comm = comm_tcp.Comm(host=core_device)
try:
coredev = core.Core(comm=comm)
uut = _RTIOSequenceError(
core=coredev,
o=ttl.TTLOut(core=coredev, channel=2)
)
with self.assertRaises(runtime_exceptions.RTIOSequenceError):
uut.run()
finally:
comm.close()

View File

@ -0,0 +1,58 @@
import os
import sys
import unittest
import logging
from artiq.language import *
from artiq.protocols.file_db import FlatFileDB
from artiq.master.worker_db import DeviceManager, ResultDB
from artiq.frontend.artiq_run import DummyScheduler
artiq_root = os.getenv("ARTIQ_ROOT")
logger = logging.getLogger(__name__)
def get_from_ddb(*path, default="skip"):
if not artiq_root:
raise unittest.SkipTest("no ARTIQ_ROOT")
v = FlatFileDB(os.path.join(artiq_root, "ddb.pyon")).data
try:
for p in path:
v = v[p]
return v.read
except KeyError:
if default == "skip":
raise unittest.SkipTest("ddb path {} not found".format(path))
else:
return default
@unittest.skipUnless(artiq_root, "no ARTIQ_ROOT")
class ExperimentCase(unittest.TestCase):
def setUp(self):
self.ddb = FlatFileDB(os.path.join(artiq_root, "ddb.pyon"))
self.dmgr = DeviceManager(self.ddb,
virtual_devices={"scheduler": DummyScheduler()})
self.pdb = FlatFileDB(os.path.join(artiq_root, "pdb.pyon"))
self.rdb = ResultDB()
def execute(self, cls, **kwargs):
expid = {
"file": sys.modules[cls.__module__].__file__,
"class_name": cls.__name__,
"arguments": kwargs
}
self.dmgr.virtual_devices["scheduler"].expid = expid
try:
try:
exp = cls(self.dmgr, self.pdb, self.rdb, **kwargs)
except KeyError as e:
# skip if ddb does not match requirements
raise unittest.SkipTest(*e.args)
exp.prepare()
exp.run()
exp.analyze()
return exp
finally:
self.dmgr.close_devices()

View File

@ -1,34 +1,28 @@
import unittest
import os
from artiq.devices.lda.driver import Lda, Ldasim
from artiq.language.units import dB
lda_serial = os.getenv("ARTIQ_LDA_SERIAL")
from artiq.test.hardware_testbench import get_from_ddb
class GenericLdaTest:
def test_attenuation(self):
step = self.cont.get_att_step_size().amount
max = self.cont.get_att_max().amount
test_vector = [i*step*dB for i in range(0, int(max*int(1/step)+1))]
step = self.cont.get_att_step_size()
attmax = self.cont.get_att_max()
test_vector = [i*step*dB for i in range(0, int(attmax*int(1/step)+1))]
for i in test_vector:
with self.subTest(i=i):
self.cont.set_attenuation(i)
self.assertEqual(i, self.cont.get_attenuation())
@unittest.skipUnless(lda_serial, "no hardware")
class TestLda(GenericLdaTest, unittest.TestCase):
def setUp(self):
product = os.getenv("ARTIQ_LDA_PRODUCT")
self.cont = Lda(serial=lda_serial, product=product)
lda_serial = get_from_ddb("lda", "device")
lda_product = get_from_ddb("lda", "product")
self.cont = Lda(serial=lda_serial, product=lda_product)
class TestLdaSim(GenericLdaTest, unittest.TestCase):
def setUp(self):
self.cont = Ldasim()
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1,31 @@
import unittest
from artiq.devices.novatech409b.driver import Novatech409B
from artiq.test.hardware_testbench import get_from_ddb
class GenericNovatech409BTest:
def test_parameters_readback(self):
# write sample data and read it back
for i in range(4):
self.driver.set_freq(i, 1e6)
self.driver.set_phase(i, 0.5)
self.driver.set_gain(i, 0.25)
result = self.driver.get_status()
# check for expected status message; ignore all but first 23 bytes
# compare with previous result extracted from Novatech
for i in range(4):
r = result[i]
self.assertEqual(r[0:23], "00989680 2000 01F5 0000")
class TestNovatech409B(GenericNovatech409BTest, unittest.TestCase):
def setUp(self):
novatech409b_device = get_from_ddb("novatech409b", "device")
self.driver = Novatech409B(novatech409b_device)
class TestNovatech409BSim(GenericNovatech409BTest, unittest.TestCase):
def setUp(self):
self.driver = Novatech409B(None)

View File

@ -6,7 +6,7 @@ import time
import numpy as np
from artiq.protocols import pc_rpc
from artiq.protocols import pc_rpc, fire_and_forget
test_address = "::1"
@ -73,13 +73,29 @@ class RPCCase(unittest.TestCase):
remote.close_rpc()
def _loop_asyncio_echo(self):
loop = asyncio.get_event_loop()
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
loop.run_until_complete(self._asyncio_echo())
finally:
loop.close()
def test_asyncio_echo(self):
self._run_server_and_test(self._loop_asyncio_echo)
class FireAndForgetCase(unittest.TestCase):
def _set_ok(self):
self.ok = True
def test_fire_and_forget(self):
self.ok = False
p = fire_and_forget.FFProxy(self)
p._set_ok()
p.ff_join()
self.assertTrue(self.ok)
class Echo:
def __init__(self):
self.terminate_notify = asyncio.Semaphore(0)
@ -96,7 +112,8 @@ class Echo:
def run_server():
loop = asyncio.get_event_loop()
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
echo = Echo()
server = pc_rpc.Server({"test": echo})

View File

@ -5,7 +5,7 @@ from fractions import Fraction
from ctypes import CFUNCTYPE, c_int, c_int32, c_int64, c_double
import struct
import llvmlite.binding as llvm
import llvmlite_or1k.binding as llvm
from artiq.language.core import int64
from artiq.py2llvm.infer_types import infer_function_types

169
artiq/test/scheduler.py Normal file
View File

@ -0,0 +1,169 @@
import unittest
import asyncio
import sys
from time import time, sleep
from artiq import *
from artiq.master.scheduler import Scheduler
class EmptyExperiment(EnvExperiment):
def build(self):
pass
def run(self):
pass
class BackgroundExperiment(EnvExperiment):
def build(self):
self.attr_device("scheduler")
def run(self):
while True:
self.scheduler.pause()
sleep(0.2)
def _get_expid(name):
return {
"file": sys.modules[__name__].__file__,
"class_name": name,
"arguments": dict()
}
def _get_basic_steps(rid, expid, priority=0, flush=False):
return [
{"action": "setitem", "key": rid, "value":
{"pipeline": "main", "status": "pending", "priority": priority,
"expid": expid, "due_date": None, "flush": flush},
"path": []},
{"action": "setitem", "key": "status", "value": "preparing",
"path": [rid]},
{"action": "setitem", "key": "status", "value": "prepare_done",
"path": [rid]},
{"action": "setitem", "key": "status", "value": "running",
"path": [rid]},
{"action": "setitem", "key": "status", "value": "run_done",
"path": [rid]},
{"action": "setitem", "key": "status", "value": "analyzing",
"path": [rid]},
{"action": "setitem", "key": "status", "value": "analyze_done",
"path": [rid]},
{"action": "delitem", "key": rid, "path": []}
]
_handlers = {
"init_rt_results": lambda description: None
}
class SchedulerCase(unittest.TestCase):
def setUp(self):
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop)
def test_steps(self):
loop = self.loop
scheduler = Scheduler(0, _handlers)
expid = _get_expid("EmptyExperiment")
expect = _get_basic_steps(1, expid)
done = asyncio.Event()
expect_idx = 0
def notify(mod):
nonlocal expect_idx
self.assertEqual(mod, expect[expect_idx])
expect_idx += 1
if expect_idx >= len(expect):
done.set()
scheduler.notifier.publish = notify
scheduler.start()
# Verify that a timed experiment far in the future does not
# get run, even if it has high priority.
late = time() + 100000
expect.insert(0,
{"action": "setitem", "key": 0, "value":
{"pipeline": "main", "status": "pending", "priority": 99,
"expid": expid, "due_date": late, "flush": False},
"path": []})
scheduler.submit("main", expid, 99, late, False)
# This one (RID 1) gets run instead.
scheduler.submit("main", expid, 0, None, False)
loop.run_until_complete(done.wait())
scheduler.notifier.publish = None
loop.run_until_complete(scheduler.stop())
def test_pause(self):
loop = self.loop
scheduler = Scheduler(0, _handlers)
expid_bg = _get_expid("BackgroundExperiment")
expid = _get_expid("EmptyExperiment")
expect = _get_basic_steps(1, expid)
background_running = asyncio.Event()
done = asyncio.Event()
expect_idx = 0
def notify(mod):
nonlocal expect_idx
if mod == {"path": [0],
"value": "running",
"key": "status",
"action": "setitem"}:
background_running.set()
if mod["path"] == [1] or (mod["path"] == [] and mod["key"] == 1):
self.assertEqual(mod, expect[expect_idx])
expect_idx += 1
if expect_idx >= len(expect):
done.set()
scheduler.notifier.publish = notify
scheduler.start()
scheduler.submit("main", expid_bg, -99, None, False)
loop.run_until_complete(background_running.wait())
scheduler.submit("main", expid, 0, None, False)
loop.run_until_complete(done.wait())
loop.run_until_complete(scheduler.stop())
def test_flush(self):
loop = self.loop
scheduler = Scheduler(0, _handlers)
expid = _get_expid("EmptyExperiment")
expect = _get_basic_steps(1, expid, 1, True)
expect.insert(1, {"key": "status",
"path": [1],
"value": "flushing",
"action": "setitem"})
first_preparing = asyncio.Event()
done = asyncio.Event()
expect_idx = 0
def notify(mod):
nonlocal expect_idx
if mod == {"path": [0],
"value": "preparing",
"key": "status",
"action": "setitem"}:
first_preparing.set()
if mod["path"] == [1] or (mod["path"] == [] and mod["key"] == 1):
self.assertEqual(mod, expect[expect_idx])
expect_idx += 1
if expect_idx >= len(expect):
done.set()
scheduler.notifier.publish = notify
scheduler.start()
scheduler.submit("main", expid, 0, None, False)
loop.run_until_complete(first_preparing.wait())
scheduler.submit("main", expid, 1, None, True)
loop.run_until_complete(done.wait())
loop.run_until_complete(scheduler.stop())
def tearDown(self):
self.loop.close()

82
artiq/test/sync_struct.py Normal file
View File

@ -0,0 +1,82 @@
import unittest
import asyncio
import numpy as np
from artiq.protocols import sync_struct
test_address = "::1"
test_port = 7777
@asyncio.coroutine
def write_test_data(test_dict):
test_values = [5, 2.1, None, True, False,
{"a": 5, 2: np.linspace(0, 10, 1)},
(4, 5), (10,), "ab\nx\"'"]
for i in range(10):
test_dict[str(i)] = i
for key, value in enumerate(test_values):
test_dict[key] = value
test_dict[1.5] = 1.5
test_dict["array"] = []
test_dict["array"].append(42)
test_dict["array"].insert(1, 1)
test_dict[100] = 0
test_dict[100] = 1
test_dict[101] = 1
test_dict.pop(101)
test_dict[102] = 1
del test_dict[102]
test_dict["finished"] = True
@asyncio.coroutine
def start_server(publisher_future, test_dict_future):
test_dict = sync_struct.Notifier(dict())
publisher = sync_struct.Publisher(
{"test": test_dict})
yield from publisher.start(test_address, test_port)
publisher_future.set_result(publisher)
test_dict_future.set_result(test_dict)
class SyncStructCase(unittest.TestCase):
def init_test_dict(self, init):
self.test_dict = init
return init
def notify(self, mod):
if ((mod["action"] == "init" and "finished" in mod["struct"])
or (mod["action"] == "setitem" and mod["key"] == "finished")):
self.receiving_done.set()
def setUp(self):
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop)
def test_recv(self):
loop = self.loop
self.receiving_done = asyncio.Event()
publisher = asyncio.Future()
test_dict = asyncio.Future()
asyncio.async(start_server(publisher, test_dict))
loop.run_until_complete(publisher)
loop.run_until_complete(test_dict)
self.publisher = publisher.result()
test_dict = test_dict.result()
test_vector = dict()
loop.run_until_complete(write_test_data(test_vector))
asyncio.async(write_test_data(test_dict))
self.subscriber = sync_struct.Subscriber("test", self.init_test_dict,
self.notify)
loop.run_until_complete(self.subscriber.connect(test_address,
test_port))
loop.run_until_complete(self.receiving_done.wait())
self.assertEqual(self.test_dict, test_vector)
self.loop.run_until_complete(self.subscriber.close())
self.loop.run_until_complete(self.publisher.stop())
def tearDown(self):
self.loop.close()

View File

@ -1,9 +1,9 @@
import unittest
import os
import time
from artiq.devices.thorlabs_tcube.driver import Tdc, Tpz, TdcSim, TpzSim
from artiq.language.units import V
from artiq.test.hardware_testbench import get_from_ddb
class GenericTdcTest:
@ -88,7 +88,7 @@ class GenericTpzTest:
def test_ouput_volts(self):
for voltage in 5*V, 10*V, 15*V, \
round(self.cont.get_tpz_io_settings()[0].amount)*V:
round(self.cont.get_tpz_io_settings()[0])*V:
with self.subTest(voltage=voltage):
test_vector = voltage
self.cont.set_output_volts(test_vector)
@ -131,12 +131,9 @@ class GenericTpzTest:
self.assertEqual(test_vector, self.cont.get_tpz_io_settings())
tdc_serial = os.getenv("ARTIQ_TDC_SERIAL")
@unittest.skipUnless(tdc_serial, "no hardware")
class TestTdc(unittest.TestCase, GenericTdcTest):
def setUp(self):
tdc_serial = get_from_ddb("tdc", "device")
self.cont = Tdc(serial_dev=tdc_serial)
@ -145,12 +142,9 @@ class TestTdcSim(unittest.TestCase, GenericTdcTest):
self.cont = TdcSim()
tpz_serial = os.getenv("ARTIQ_TPZ_SERIAL")
@unittest.skipUnless(tpz_serial, "no hardware")
class TestTpz(unittest.TestCase, GenericTpzTest):
def setUp(self):
tpz_serial = get_from_ddb("tpz", "device")
self.cont = Tpz(serial_dev=tpz_serial)

View File

@ -6,28 +6,20 @@ from artiq.coredevice import comm_dummy, core
from artiq.transforms.unparse import unparse
# Original code before inline:
#
# n = time_to_cycles(1.2345*ns)
# ftw = self.dds.frequency_to_ftw(345*MHz)
# f = self.dds.ftw_to_frequency(ftw)
# phi = 1000*cycles_to_time(n)*f
# do_someting(int(phi))
#
optimize_in = """
def run():
dds_sysclk = Quantity(Fraction(1000000000, 1), 'Hz')
n = time_to_cycles((1.2345 * Quantity(Fraction(1, 1000000000), 's')))
dds_sysclk = Fraction(1000000000, 1)
n = seconds_to_mu((1.2345 * Fraction(1, 1000000000)))
with sequential:
frequency = (345 * Quantity(Fraction(1000000, 1), 'Hz'))
frequency = 345 * Fraction(1000000, 1)
frequency_to_ftw_return = int((((2 ** 32) * frequency) / dds_sysclk))
ftw = frequency_to_ftw_return
with sequential:
ftw2 = ftw
ftw_to_frequency_return = ((ftw2 * dds_sysclk) / (2 ** 32))
f = ftw_to_frequency_return
phi = ((1000 * cycles_to_time(n)) * f)
phi = ((1000 * mu_to_seconds(n)) * f)
do_something(int(phi))
"""
@ -44,7 +36,9 @@ def run():
class OptimizeCase(unittest.TestCase):
def test_optimize(self):
coredev = core.Core(comm=comm_dummy.Comm(), ref_period=1*ns)
dmgr = dict()
dmgr["comm"] = comm_dummy.Comm(dmgr)
coredev = core.Core(dmgr, ref_period=1*ns)
func_def = ast.parse(optimize_in).body[0]
coredev.transform_stack(func_def, dict(), dict())
self.assertEqual(unparse(func_def), optimize_out)

View File

@ -7,20 +7,26 @@ from artiq import *
from artiq.master.worker import *
class WatchdogNoTimeout(Experiment, AutoDB):
class WatchdogNoTimeout(EnvExperiment):
def build(self):
pass
def run(self):
for i in range(10):
with watchdog(0.5*s):
sleep(0.1)
class WatchdogTimeout(Experiment, AutoDB):
class WatchdogTimeout(EnvExperiment):
def build(self):
pass
def run(self):
with watchdog(0.1*s):
sleep(100.0)
class WatchdogTimeoutInBuild(Experiment, AutoDB):
class WatchdogTimeoutInBuild(EnvExperiment):
def build(self):
with watchdog(0.1*s):
sleep(100.0)
@ -31,30 +37,31 @@ class WatchdogTimeoutInBuild(Experiment, AutoDB):
@asyncio.coroutine
def _call_worker(worker, expid):
yield from worker.prepare(0, "main", expid, 0)
try:
yield from worker.build(0, "main", expid, 0)
yield from worker.prepare()
yield from worker.run()
yield from worker.analyze()
finally:
yield from worker.close()
def _run_experiment(experiment):
def _run_experiment(class_name):
expid = {
"file": sys.modules[__name__].__file__,
"experiment": experiment,
"class_name": class_name,
"arguments": dict()
}
handlers = {
"init_rt_results": lambda description: None
}
worker = Worker(handlers)
loop = asyncio.get_event_loop()
worker = Worker()
loop.run_until_complete(_call_worker(worker, expid))
class WatchdogCase(unittest.TestCase):
def setUp(self):
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop)
def test_watchdog_no_timeout(self):
_run_experiment("WatchdogNoTimeout")
@ -65,3 +72,6 @@ class WatchdogCase(unittest.TestCase):
def test_watchdog_timeout_in_build(self):
with self.assertRaises(WorkerWatchdogTimeout):
_run_experiment("WatchdogTimeoutInBuild")
def tearDown(self):
self.loop.close()

View File

@ -7,7 +7,7 @@ import asyncio
import time
import os.path
from artiq.language.experiment import is_experiment
from artiq.language.environment import is_experiment
from artiq.protocols import pyon
@ -18,15 +18,6 @@ def parse_arguments(arguments):
d[name] = pyon.decode(value)
return d
def format_arguments(arguments):
fmtargs = []
for k, v in sorted(arguments.items(), key=itemgetter(0)):
fmtargs.append(k + "=" + repr(v))
if fmtargs:
return ", ".join(fmtargs)
else:
return ""
def file_import(filename):
linecache.checkcache(filename)
@ -90,12 +81,21 @@ def asyncio_process_wait_timeout(process, timeout):
# causes a futures.InvalidStateError inside asyncio if and when the
# process terminates after the timeout.
# Work around this problem.
end_time = time.monotonic() + timeout
@asyncio.coroutine
def process_wait_returncode_timeout():
while True:
if process.returncode is not None:
break
yield from asyncio.sleep(0.1)
yield from asyncio.wait_for(process_wait_returncode_timeout(),
timeout=timeout)
@asyncio.coroutine
def asyncio_process_wait(process):
r = True
while r:
r = yield from asyncio.wait_for(
process.stdout.read(1024),
timeout=end_time - time.monotonic())
f, p = yield from asyncio.wait([process.stdout.read(1024)])
r = f.pop().result()
@asyncio.coroutine
@ -119,3 +119,42 @@ def asyncio_queue_peek(q):
return q._queue[0]
else:
raise asyncio.QueueEmpty
class TaskObject:
def start(self):
self.task = asyncio.async(self._do())
@asyncio.coroutine
def stop(self):
self.task.cancel()
yield from asyncio.wait([self.task])
del self.task
@asyncio.coroutine
def _do(self):
raise NotImplementedError
class WaitSet:
def __init__(self):
self._s = set()
self._ev = asyncio.Event()
def _update_ev(self):
if self._s:
self._ev.clear()
else:
self._ev.set()
def add(self, e):
self._s.add(e)
self._update_ev()
def discard(self, e):
self._s.discard(e)
self._update_ev()
@asyncio.coroutine
def wait_empty(self):
yield from self._ev.wait()

View File

@ -28,13 +28,14 @@ def _get_duration(stmt):
return -1
elif isinstance(stmt, ast.Call):
name = stmt.func.id
if name == "delay":
assert(name != "delay")
if name == "delay_mu":
try:
da = eval_constant(stmt.args[0])
except NotConstant:
da = -1
return da
elif name == "at":
elif name == "at_mu":
return -1
else:
return 0
@ -69,7 +70,7 @@ def _interleave_timelines(timelines):
ref_stmt = stmt.stmt
delay_stmt = ast.copy_location(
ast.Expr(ast.Call(
func=ast.Name("delay", ast.Load()),
func=ast.Name("delay_mu", ast.Load()),
args=[value_to_ast(dt)],
keywords=[], starargs=[], kwargs=[])),
ref_stmt)

View File

@ -1,15 +1,15 @@
"""
This transform implements time management functions (delay/now/at)
This transform implements time management functions (delay_mu/now_mu/at_mu)
using an accumulator 'now' and simple replacement rules:
delay(t) -> now += t
now() -> now
at(t) -> now = t
delay_mu(t) -> now += t
now_mu() -> now
at_mu(t) -> now = t
Time parameters must be quantized to integers before running this transform.
The function delay(), that uses seconds, must be lowered to delay_mu() before
invoking this transform.
The accumulator is initialized to an int64 value at the beginning of the
output function.
"""
import ast
@ -17,7 +17,7 @@ import ast
class _TimeLowerer(ast.NodeTransformer):
def visit_Call(self, node):
if node.func.id == "now":
if node.func.id == "now_mu":
return ast.copy_location(ast.Name("now", ast.Load()), node)
else:
self.generic_visit(node)
@ -27,13 +27,13 @@ class _TimeLowerer(ast.NodeTransformer):
r = node
if isinstance(node.value, ast.Call):
funcname = node.value.func.id
if funcname == "delay":
if funcname == "delay_mu":
r = ast.copy_location(
ast.AugAssign(target=ast.Name("now", ast.Store()),
op=ast.Add(),
value=node.value.args[0]),
node)
elif funcname == "at":
elif funcname == "at_mu":
r = ast.copy_location(
ast.Assign(targets=[ast.Name("now", ast.Store())],
value=node.value.args[0]),

View File

@ -1,190 +0,0 @@
import ast
from collections import defaultdict
from copy import copy
from artiq.language import units
from artiq.transforms.tools import embeddable_func_names
def _add_units(f, unit_list):
def wrapper(*args):
new_args = []
for arg, unit in zip(args, unit_list):
if unit is None:
new_args.append(arg)
else:
if isinstance(arg, list):
new_args.append([units.Quantity(x, unit) for x in arg])
else:
new_args.append(units.Quantity(arg, unit))
return f(*new_args)
return wrapper
class _UnitsLowerer(ast.NodeTransformer):
def __init__(self, rpc_map):
self.rpc_map = rpc_map
# (original rpc number, (unit list)) -> new rpc number
self.rpc_remap = defaultdict(lambda: len(self.rpc_remap))
self.variable_units = dict()
def visit_Name(self, node):
try:
unit = self.variable_units[node.id]
except KeyError:
pass
else:
if unit is not None:
node.unit = unit
return node
def visit_BoolOp(self, node):
self.generic_visit(node)
us = [getattr(value, "unit", None) for value in node.values]
if not all(u == us[0] for u in us[1:]):
raise units.DimensionError
return node
def visit_Compare(self, node):
self.generic_visit(node)
u0 = getattr(node.left, "unit", None)
us = [getattr(comparator, "unit", None)
for comparator in node.comparators]
if not all(u == u0 for u in us):
raise units.DimensionError
return node
def visit_UnaryOp(self, node):
self.generic_visit(node)
if hasattr(node.operand, "unit"):
node.unit = node.operand.unit
return node
def visit_BinOp(self, node):
self.generic_visit(node)
op = type(node.op)
left_unit = getattr(node.left, "unit", None)
right_unit = getattr(node.right, "unit", None)
if op in (ast.Add, ast.Sub, ast.Mod):
unit = units.addsub_dimension(left_unit, right_unit)
elif op == ast.Mult:
unit = units.mul_dimension(left_unit, right_unit)
elif op in (ast.Div, ast.FloorDiv):
unit = units.div_dimension(left_unit, right_unit)
else:
if left_unit is not None or right_unit is not None:
raise units.DimensionError
unit = None
if unit is not None:
node.unit = unit
return node
def visit_Attribute(self, node):
self.generic_visit(node)
if node.attr == "amount" and hasattr(node.value, "unit"):
del node.value.unit
return node.value
else:
return node
def visit_List(self, node):
self.generic_visit(node)
if node.elts:
us = [getattr(elt, "unit", None) for elt in node.elts]
if not all(u == us[0] for u in us[1:]):
raise units.DimensionError
node.unit = us[0]
return node
def visit_ListComp(self, node):
self.generic_visit(node)
if hasattr(node.elt, "unit"):
node.unit = node.elt.unit
return node
def visit_Call(self, node):
self.generic_visit(node)
if node.func.id == "Quantity":
amount, unit = node.args
amount.unit = unit.s
return amount
elif node.func.id in ("now", "cycles_to_time"):
node.unit = "s"
elif node.func.id == "syscall":
# only RPCs can have units
if node.args[0].s == "rpc":
unit_list = tuple(getattr(arg, "unit", None)
for arg in node.args[2:])
rpc_n = node.args[1].n
node.args[1].n = self.rpc_remap[(rpc_n, (unit_list))]
else:
if any(hasattr(arg, "unit") for arg in node.args):
raise units.DimensionError
elif node.func.id in ("delay", "at", "time_to_cycles", "watchdog"):
if getattr(node.args[0], "unit", None) != "s":
raise units.DimensionError
elif node.func.id == "check_unit":
self.generic_visit(node)
elif node.func.id in embeddable_func_names:
# must be last (some embeddable funcs may have units)
if any(hasattr(arg, "unit") for arg in node.args):
raise units.DimensionError
return node
def visit_Expr(self, node):
self.generic_visit(node)
if (isinstance(node.value, ast.Call)
and node.value.func.id == "check_unit"):
call = node.value
if (isinstance(call.args[1], ast.NameConstant)
and call.args[1].value is None):
if hasattr(call.value.args[0], "unit"):
raise units.DimensionError
elif isinstance(call.args[1], ast.Str):
if getattr(call.args[0], "unit", None) != call.args[1].s:
raise units.DimensionError
else:
raise NotImplementedError
return None
else:
return node
def _update_target(self, target, unit):
if isinstance(target, ast.Name):
if target.id in self.variable_units:
if self.variable_units[target.id] != unit:
raise TypeError(
"Inconsistent units for variable '{}': '{}' and '{}'"
.format(target.id,
self.variable_units[target.id],
unit))
else:
self.variable_units[target.id] = unit
def visit_Assign(self, node):
node.value = self.visit(node.value)
unit = getattr(node.value, "unit", None)
for target in node.targets:
self._update_target(target, unit)
return node
def visit_AugAssign(self, node):
value = self.visit_BinOp(ast.BinOp(
op=node.op, left=node.target, right=node.value))
unit = getattr(value, "unit", None)
self._update_target(node.target, unit)
return node
# Only dimensionless iterators are supported
def visit_For(self, node):
self.generic_visit(node)
self._update_target(node.target, None)
return node
def lower_units(func_def, rpc_map):
ul = _UnitsLowerer(rpc_map)
ul.visit(func_def)
original_map = copy(rpc_map)
for (original_rpcn, unit_list), new_rpcn in ul.rpc_remap.items():
rpc_map[new_rpcn] = _add_units(original_map[original_rpcn], unit_list)

View File

@ -1,12 +1,12 @@
"""
This transform turns calls to delay/now/at that use non-integer time
expressed in seconds into calls that use int64 time expressed in multiples of
ref_period.
This transform turns calls to delay() that use non-integer time
expressed in seconds into calls to delay_mu() that use int64 time
expressed in multiples of ref_period.
It does so by inserting multiplication/division/rounding operations around
those calls.
The time_to_cycles and cycles_to_time core language functions are also
The seconds_to_mu and mu_to_seconds core language functions are also
implemented here, as well as watchdog to syscall conversion.
"""
@ -15,14 +15,7 @@ import ast
from artiq.transforms.tools import value_to_ast
def _call_now(node):
return ast.copy_location(
ast.Call(func=ast.Name("now", ast.Load()),
args=[], keywords=[], starargs=[], kwargs=[]),
node)
def _time_to_cycles(ref_period, node):
def _seconds_to_mu(ref_period, node):
divided = ast.copy_location(
ast.BinOp(left=node,
op=ast.Div(),
@ -35,7 +28,7 @@ def _time_to_cycles(ref_period, node):
divided)
def _cycles_to_time(ref_period, node):
def _mu_to_seconds(ref_period, node):
return ast.copy_location(
ast.BinOp(left=node,
op=ast.Mult(),
@ -50,29 +43,22 @@ class _TimeQuantizer(ast.NodeTransformer):
def visit_Call(self, node):
funcname = node.func.id
if funcname == "now":
return _cycles_to_time(self.ref_period, _call_now(node))
elif funcname == "delay" or funcname == "at":
if funcname == "delay":
node.func.id = "delay_mu"
if (isinstance(node.args[0], ast.Call)
and node.args[0].func.id == "cycles_to_time"):
and node.args[0].func.id == "mu_to_seconds"):
# optimize:
# delay/at(cycles_to_time(x)) -> delay/at(x)
# delay(mu_to_seconds(x)) -> delay_mu(x)
node.args[0] = self.visit(node.args[0].args[0])
else:
node.args[0] = _time_to_cycles(self.ref_period,
node.args[0] = _seconds_to_mu(self.ref_period,
self.visit(node.args[0]))
return node
elif funcname == "time_to_cycles":
if (isinstance(node.args[0], ast.Call)
and node.args[0].func.id == "now"):
# optimize:
# time_to_cycles(now()) -> now()
return _call_now(node)
else:
return _time_to_cycles(self.ref_period,
elif funcname == "seconds_to_mu":
return _seconds_to_mu(self.ref_period,
self.visit(node.args[0]))
elif funcname == "cycles_to_time":
return _cycles_to_time(self.ref_period,
elif funcname == "mu_to_seconds":
return _mu_to_seconds(self.ref_period,
self.visit(node.args[0]))
else:
self.generic_visit(node)
@ -123,6 +109,5 @@ class _TimeQuantizer(ast.NodeTransformer):
return node
def quantize_time(func_def, ref_period):
_TimeQuantizer(ref_period).visit(func_def)

View File

@ -6,12 +6,13 @@ from artiq.language import units
embeddable_funcs = (
core_language.delay, core_language.at, core_language.now,
core_language.time_to_cycles, core_language.cycles_to_time,
core_language.delay_mu, core_language.at_mu, core_language.now_mu,
core_language.delay,
core_language.seconds_to_mu, core_language.mu_to_seconds,
core_language.syscall, core_language.watchdog,
range, bool, int, float, round, len,
core_language.int64, core_language.round64,
Fraction, units.Quantity, units.check_unit, core_language.EncodedException
Fraction, core_language.EncodedException
)
embeddable_func_names = {func.__name__ for func in embeddable_funcs}
@ -61,11 +62,6 @@ def value_to_ast(value):
for kg in core_language.kernel_globals:
if value is getattr(core_language, kg):
return ast.Name(kg, ast.Load())
if isinstance(value, units.Quantity):
return ast.Call(
func=ast.Name("Quantity", ast.Load()),
args=[value_to_ast(value.amount), ast.Str(value.unit)],
keywords=[], starargs=None, kwargs=None)
raise NotASTRepresentable(str(value))
@ -88,14 +84,6 @@ def eval_constant(node):
numerator = eval_constant(node.args[0])
denominator = eval_constant(node.args[1])
return Fraction(numerator, denominator)
elif funcname == "Quantity":
amount, unit = node.args
amount = eval_constant(amount)
try:
unit = getattr(units, unit.id)
except:
raise NotConstant
return units.Quantity(amount, unit)
else:
raise NotConstant
else:
@ -105,8 +93,7 @@ def eval_constant(node):
_replaceable_funcs = {
"bool", "int", "float", "round",
"int64", "round64", "Fraction",
"time_to_cycles", "cycles_to_time",
"Quantity"
"seconds_to_mu", "mu_to_seconds"
}

Some files were not shown because too many files have changed in this diff Show More