From 36cda96df07f5a358069283d2f35346ba125a5f0 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Mon, 25 May 2015 21:36:14 +0200 Subject: [PATCH 001/227] pxi6733: add ping method to the driver --- artiq/devices/pxi6733/driver.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/artiq/devices/pxi6733/driver.py b/artiq/devices/pxi6733/driver.py index 118169ec2..f4ffd4f52 100644 --- a/artiq/devices/pxi6733/driver.py +++ b/artiq/devices/pxi6733/driver.py @@ -1,6 +1,6 @@ # Yann Sionneau , 2015 -from ctypes import byref +from ctypes import byref, c_ulong import numpy as np @@ -11,6 +11,9 @@ class DAQmxSim: def close(self): pass + def ping(self): + return True + class DAQmx: """NI PXI6733 DAQ interface.""" @@ -28,6 +31,14 @@ class DAQmx: self.daq.DAQmxClearTask(taskhandle) self.tasks.remove(taskhandle) + def ping(self): + try: + data = (c_ulong*1)() + self.daq.DAQmxGetDevSerialNum(self.device, data) + except: + return False + return True + def load_sample_values(self, values): """Load sample values into PXI 6733 device. From 4bf7875b87f97ad59970da53e9cb87a2a0907a9f Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Thu, 7 May 2015 17:47:48 +0200 Subject: [PATCH 002/227] flash_storage: refactor + unit tests + artiq_coreconfig.py CLI + doc --- artiq/coredevice/comm_generic.py | 50 +++- artiq/frontend/artiq_coreconfig.py | 61 +++++ artiq/frontend/artiq_flash.sh | 22 +- artiq/frontend/artiq_mkfs.py | 11 +- artiq/master/worker_db.py | 4 +- doc/manual/core_device_flash_storage.rst | 14 + doc/manual/index.rst | 1 + doc/manual/installing.rst | 44 +++ doc/manual/utilities.rst | 55 ++++ soc/runtime/flash_storage.c | 335 +++++++++++++++-------- soc/runtime/flash_storage.h | 3 +- soc/runtime/session.c | 81 +++++- soc/runtime/test_mode.c | 203 +++++++++++++- 13 files changed, 752 insertions(+), 132 deletions(-) create mode 100755 artiq/frontend/artiq_coreconfig.py create mode 100644 doc/manual/core_device_flash_storage.rst diff --git a/artiq/coredevice/comm_generic.py b/artiq/coredevice/comm_generic.py index afd57bcb8..3de7b40e1 100644 --- a/artiq/coredevice/comm_generic.py +++ b/artiq/coredevice/comm_generic.py @@ -20,7 +20,12 @@ class _H2DMsgType(Enum): RUN_KERNEL = 5 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,11 @@ class _D2HMsgType(Enum): RPC_REQUEST = 10 + FLASH_READ_REPLY = 11 + FLASH_WRITE_REPLY = 12 + FLASH_OK_REPLY = 13 + FLASH_ERROR_REPLY = 14 + class UnsupportedDevice(Exception): pass @@ -122,6 +132,44 @@ class CommGeneric: 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_WRITE_REPLY: + if ty == _D2HMsgType.FLASH_ERROR_REPLY: + raise IOError("Invalid key: not a null-terminated string") + else: + raise IOError("Incorrect reply from device: {}".format(ty)) + ret = self.read(1) + if ret != b"\x01": + raise IOError("Flash storage is full") + + 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 diff --git a/artiq/frontend/artiq_coreconfig.py b/artiq/frontend/artiq_coreconfig.py new file mode 100755 index 000000000..7540b7f32 --- /dev/null +++ b/artiq/frontend/artiq_coreconfig.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 + +import argparse + +from artiq.master.worker_db import create_device +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") + parser.add_argument("-r", "--read", type=to_bytes, + help="read key from core device config") + parser.add_argument("-w", "--write", nargs=2, action="append", default=[], + metavar=("KEY", "STRING"), type=to_bytes, + help="write key-value records to core device config") + parser.add_argument("-f", "--write-file", nargs=2, action="append", + type=to_bytes, default=[], metavar=("KEY", "FILENAME"), + help="write the content of a file into core device " + "config") + parser.add_argument("-e", "--erase", action="store_true", + help="erase core device config") + parser.add_argument("-d", "--delete", action="append", default=[], + type=to_bytes, + help="delete key from core device config") + parser.add_argument("--ddb", default="ddb.pyon", + help="device database file") + return parser + + +def main(): + args = get_argparser().parse_args() + ddb = FlatFileDB(args.ddb) + comm = create_device(ddb.request("comm"), None) + + if args.read: + value = comm.flash_storage_read(args.read) + if not value: + print("Key {} does not exist".format(args.read)) + else: + print(value) + elif args.erase: + comm.flash_storage_erase() + elif args.delete: + for key in args.delete: + comm.flash_storage_remove(key) + else: + if args.write: + for key, value in args.write: + comm.flash_storage_write(key, value) + if args.write_file: + for key, filename in args.write_file: + with open(filename, "rb") as fi: + comm.flash_storage_write(key, fi.read()) + +if __name__ == "__main__": + main() diff --git a/artiq/frontend/artiq_flash.sh b/artiq/frontend/artiq_flash.sh index 93bc710c2..a5738cfe9 100755 --- a/artiq/frontend/artiq_flash.sh +++ b/artiq/frontend/artiq_flash.sh @@ -7,7 +7,7 @@ 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 +19,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 +61,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,6 +105,7 @@ 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" ] @@ -105,12 +116,13 @@ then 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,6 +144,12 @@ 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..." diff --git a/artiq/frontend/artiq_mkfs.py b/artiq/frontend/artiq_mkfs.py index ac00f632d..22c21f1d0 100755 --- a/artiq/frontend/artiq_mkfs.py +++ b/artiq/frontend/artiq_mkfs.py @@ -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): diff --git a/artiq/master/worker_db.py b/artiq/master/worker_db.py index e3c5865bc..2ba8b84f2 100644 --- a/artiq/master/worker_db.py +++ b/artiq/master/worker_db.py @@ -65,7 +65,7 @@ class ResultDB: result_dict_to_hdf5(f, self.data.read) -def _create_device(desc, dbh): +def create_device(desc, dbh): ty = desc["type"] if ty == "local": module = importlib.import_module(desc["module"]) @@ -105,7 +105,7 @@ class DBHub: while isinstance(desc, str): # alias desc = self.ddb.request(desc) - dev = _create_device(desc, self) + dev = create_device(desc, self) self.active_devices[name] = dev return dev diff --git a/doc/manual/core_device_flash_storage.rst b/doc/manual/core_device_flash_storage.rst new file mode 100644 index 000000000..fb2bb8d9a --- /dev/null +++ b/doc/manual/core_device_flash_storage.rst @@ -0,0 +1,14 @@ +.. _core-device-flash-storage: + +Core device flash storage +========================= + +The core device contains some flash space that can be used to store +some configuration data. + +This storage area is used to store the core device MAC address, IP address and even the idle kernel. + +The flash storage area is one sector (64 kB) large and is organized as a list +of key-value records. + +This flash storage space can be accessed by using the artiq_coreconfig.py :ref:`core-device-configuration-tool`. diff --git a/doc/manual/index.rst b/doc/manual/index.rst index b17348981..502ea2443 100644 --- a/doc/manual/index.rst +++ b/doc/manual/index.rst @@ -15,6 +15,7 @@ Contents: core_drivers_reference protocols_reference ndsp_reference + core_device_flash_storage utilities fpga_board_ports default_network_ports diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index 7aa7c9ea4..92e54b55e 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -124,6 +124,50 @@ These steps are required to generate bitstream (``.bit``) files, build the MiSoC The communication parameters are 115200 8-N-1. +* Set the MAC and IP address in the :ref:`core device configuration flash storage `: + + * You can either set it by generating a flash storage image and then flash it: :: + + $ ~/artiq-dev/artiq/frontend/artiq_mkfs.py flash_storage.img -s mac xx:xx:xx:xx:xx:xx -s ip xx.xx.xx.xx + $ ~/artiq-dev/artiq/frontend/artiq_flash.sh -f flash_storage.img + + * Or you can set it via the runtime test mode command line + + * Boot the board. + + * Quickly run flterm (in ``path/to/misoc/tools``) to access the serial console. + + * If you weren't quick enough to see anything in the serial console, press the reset button. + + * Wait for "Press 't' to enter test mode..." to appear and hit the ``t`` key. + + * Enter the following commands (which will erase the flash storage content). + + :: + + test> fserase + test> fswrite ip xx.xx.xx.xx + test> fswrite mac xx:xx:xx:xx:xx:xx + + * Then reboot. + + You should see something like this in the serial console: :: + + ~/dev/misoc$ ./tools/flterm --port /dev/ttyUSB1 + [FLTERM] Starting... + + MiSoC BIOS http://m-labs.hk + (c) Copyright 2007-2014 Sebastien Bourdeauducq + [...] + Press 't' to enter test mode... + Entering test mode. + test> fserase + test> fswrite ip 192.168.10.2 + test> fswrite mac 11:22:33:44:55:66 + +.. note:: The reset button of the KC705 board is the "CPU_RST" labeled button. +.. warning:: Both those instructions will result in the flash storage being wiped out. However you can use the test mode to change the IP/MAC without erasing everything if you skip the "fserase" command. + Installing the host-side software --------------------------------- diff --git a/doc/manual/utilities.rst b/doc/manual/utilities.rst index ebf6c2bb9..740fa81b1 100644 --- a/doc/manual/utilities.rst +++ b/doc/manual/utilities.rst @@ -92,3 +92,58 @@ This tool compiles key/value pairs into a binary image suitable for flashing int .. argparse:: :ref: artiq.frontend.artiq_mkfs.get_argparser :prog: artiq_mkfs + +.. _core-device-configuration-tool: + +Core device configuration tool +------------------------------ + +The artiq_coreconfig tool allows to read, write and remove key-value records from the :ref:`core-device-flash-storage`. + +It also allows to erase the entire flash storage area. + +To use this tool, you need to specify a ``ddb.pyon`` DDB file which contains a ``comm`` device (an example is provided in ``artiq/examples/master/ddb.pyon``). +This tells the tool how to connect to the core device (via serial or via TCP) and with which parameters (baudrate, serial device, IP address, TCP port). +When not specified, the artiq_coreconfig tool will assume that there is a file named ``ddb.pyon`` in the current directory. + + +To read the record whose key is ``mac``:: + + $ artiq_coreconfig -r mac + +To write the value ``test_value`` in the key ``my_key``:: + + $ artiq_coreconfig -w my_key test_value + $ artiq_coreconfig -r my_key + b'test_value' + +You can also write entire files in a record using the ``-f`` parameter:: + + $ echo "this_is_a_test" > my_filename + $ artiq_coreconfig -f my_key my_filename + $ artiq_coreconfig -r my_key + b'this_is_a_test\n' + +You can write several records at once:: + + $ artiq_coreconfig -w key1 value1 -f key2 filename -w key3 value3 + +To remove the previously written key ``my_key``:: + + $ artiq_coreconfig -d my_key + +To erase the entire flash storage area:: + + $ artiq_coreconfig -e + +You don't need to remove a record in order to change its value, just overwrite +it:: + + $ artiq_coreconfig -w my_key some_value + $ artiq_coreconfig -w my_key some_other_value + $ artiq_coreconfig -r my_key + b'some_other_value' + +.. argparse:: + :ref: artiq.frontend.artiq_coreconfig.get_argparser + :prog: artiq_coreconfig diff --git a/soc/runtime/flash_storage.c b/soc/runtime/flash_storage.c index 430a48536..a8e01436a 100644 --- a/soc/runtime/flash_storage.c +++ b/soc/runtime/flash_storage.c @@ -3,6 +3,7 @@ */ #include +#include #include #include #include @@ -19,133 +20,239 @@ #define min(a, b) (a>b?b:a) #define max(a, b) (a>b?a:b) -#define goto_next_record(buff, addr) do { \ - unsigned int key_size = strlen(&buff[addr])+1; \ - if(key_size % 4) \ - key_size += 4 - (key_size % 4); \ - unsigned int *buflen_p = (unsigned int *)&buff[addr + key_size]; \ - unsigned int buflen = *buflen_p; \ - if(buflen % 4) \ - buflen += 4 - (buflen % 4); \ - addr += key_size + sizeof(int) + buflen; \ - } while (0) - -union seek { - unsigned int integer; - char bytes[4]; +struct record { + char *key; + unsigned int key_len; + char *value; + unsigned int value_len; + char *raw_record; + unsigned int size; }; -static void write_at_offset(char *key, void *buffer, int buflen, unsigned int sector_offset); -static char key_exists(char *buff, char *key, char *end); -static char check_for_duplicates(char *buff); -static unsigned int try_to_flush_duplicates(void); +struct iter_state { + char *buffer; + unsigned int seek; + unsigned int buf_len; +}; -static char key_exists(char *buff, char *key, char *end) +static unsigned int get_record_size(char *buff) { - unsigned int addr; + unsigned int record_size; - addr = 0; - while(&buff[addr] < end && *(unsigned int*)&buff[addr] != END_MARKER) { - if(strcmp(&buff[addr], key) == 0) - return 1; - goto_next_record(buff, addr); + memcpy(&record_size, buff, 4); + return record_size; +} + +static void record_iter_init(struct iter_state *is, char *buffer, unsigned int buf_len) +{ + is->buffer = buffer; + is->seek = 0; + is->buf_len = buf_len; +} + +static int record_iter_next(struct iter_state *is, struct record *record, int *fatal) +{ + if(is->seek >= is->buf_len) + return 0; + + record->raw_record = &is->buffer[is->seek]; + record->size = get_record_size(record->raw_record); + + if(record->size == END_MARKER) + return 0; + + if(is->seek > is->buf_len - sizeof(record->size) - 2) { /* 2 is the minimum key length */ + printf("flash_storage might be corrupted: END_MARKER missing at the end of the storage sector\n"); + if(fatal) + *fatal = 1; + return 0; } + + if(record->size > is->buf_len - is->seek) { + printf("flash_storage might be corrupted: invalid record_size %d at address %08x\n", record->size, record->raw_record); + if(fatal) + *fatal = 1; + return 0; + } + + record->key = record->raw_record + sizeof(record->size); + record->key_len = strnlen(record->key, record->size - sizeof(record->size)) + 1; + + if(record->key_len == record->size - sizeof(record->size) + 1) { + printf("flash_storage might be corrupted: invalid key length at address %08x\n", record->raw_record); + if(fatal) + *fatal = 1; + return 0; + } + + record->value = record->key + record->key_len; + record->value_len = record->size - record->key_len - sizeof(record->size); + + is->seek += record->size; + return 1; +} + +static unsigned int get_free_space(void) +{ + struct iter_state is; + struct record record; + + record_iter_init(&is, STORAGE_ADDRESS, STORAGE_SIZE); + while(record_iter_next(&is, &record, NULL)); + return STORAGE_SIZE - is.seek; +} + +static int is_empty(struct record *record) +{ + return record->value_len == 0; +} + +static int key_exists(char *buff, char *key, char *end, char accept_empty, struct record *found_record) +{ + struct iter_state is; + struct record iter_record; + int found = 0; + + record_iter_init(&is, buff, end - buff); + while(record_iter_next(&is, &iter_record, NULL)) { + if(strcmp(iter_record.key, key) == 0) { + found = 1; + if(found_record) + *found_record = iter_record; + } + } + + if(found && is_empty(found_record) && !accept_empty) + return 0; + + if(found) + return 1; + return 0; } static char check_for_duplicates(char *buff) { - unsigned int addr; - char *key_name; + struct record record, following_record; + struct iter_state is; + int no_error; - addr = 0; - while(addr < STORAGE_SIZE && *(unsigned int *)&buff[addr] != END_MARKER) { - key_name = &buff[addr]; - goto_next_record(buff, addr); - if(key_exists(&buff[addr], key_name, &buff[STORAGE_SIZE])) + record_iter_init(&is, buff, STORAGE_SIZE); + no_error = record_iter_next(&is, &record, NULL); + while(no_error) { + no_error = record_iter_next(&is, &following_record, NULL); + if(no_error && key_exists(following_record.raw_record, record.key, &buff[STORAGE_SIZE], 1, NULL)) return 1; + record = following_record; } return 0; } -static unsigned int try_to_flush_duplicates(void) +static char check_for_empty_records(char *buff) { - unsigned int addr, i, key_size, buflen; - char *key_name, *last_duplicate; - char sector_buff[STORAGE_SIZE]; - union seek *seeker = (union seek *)sector_buff; + struct iter_state is; + struct record record; - memcpy(sector_buff, STORAGE_ADDRESS, STORAGE_SIZE); - if(check_for_duplicates(sector_buff)) { - fs_erase(); - for(addr = 0; addr < STORAGE_SIZE && seeker[addr >> 2].integer != END_MARKER;) { - key_name = §or_buff[addr]; - key_size = strlen(key_name)+1; - if(key_size % 4) - key_size += 4 - (key_size % 4); - if(!key_exists((char *)STORAGE_ADDRESS, key_name, STORAGE_ADDRESS+STORAGE_SIZE)) { - last_duplicate = key_name; - for(i = addr; i < STORAGE_SIZE;) { - goto_next_record(sector_buff, i); - if(strcmp(§or_buff[i], key_name) == 0) - last_duplicate = §or_buff[i]; - } - buflen = *(unsigned int *)&last_duplicate[key_size]; - fs_write(key_name, &last_duplicate[key_size+sizeof(int)], buflen); - } - goto_next_record(sector_buff, addr); - } - return 0; - } else - return 1; + record_iter_init(&is, buff, STORAGE_SIZE); + while(record_iter_next(&is, &record, NULL)) + if(is_empty(&record)) + return 1; + + return 0; } -static void write_at_offset(char *key, void *buffer, int buflen, unsigned int sector_offset) +static unsigned int try_to_flush_duplicates(char *new_key, unsigned int buf_len) +{ + unsigned int key_size, new_record_size, ret = 0, can_rollback = 0; + struct record record, previous_record; + char sector_buff[STORAGE_SIZE]; + struct iter_state is; + + memcpy(sector_buff, STORAGE_ADDRESS, STORAGE_SIZE); + if(check_for_duplicates(sector_buff) + || key_exists(sector_buff, new_key, §or_buff[STORAGE_SIZE], 0, NULL) + || check_for_empty_records(sector_buff)) { + fs_erase(); + record_iter_init(&is, sector_buff, STORAGE_SIZE); + while(record_iter_next(&is, &record, NULL)) { + if(is_empty(&record)) + continue; + if(!key_exists((char *)STORAGE_ADDRESS, record.key, STORAGE_ADDRESS + STORAGE_SIZE, 1, NULL)) { + struct record rec; + + if(!key_exists(sector_buff, record.key, §or_buff[STORAGE_SIZE], 0, &rec)) + continue; + if(strcmp(new_key, record.key) == 0) { // If we are about to write this key we don't keep the old value. + previous_record = rec; // This holds the old record in case we need it back (for instance if new record is too long) + can_rollback = 1; + } else + fs_write(record.key, rec.value, rec.value_len); + } + } + ret = 1; + } + + key_size = strlen(new_key) + 1; + new_record_size = key_size + buf_len + sizeof(new_record_size); + if(can_rollback && new_record_size > get_free_space()) { + fs_write(new_key, previous_record.value, previous_record.value_len); + } + + return ret; +} + +static void write_at_offset(char *key, void *buffer, int buf_len, unsigned int sector_offset) { int key_len = strlen(key) + 1; - int key_len_alignment = 0, buflen_alignment = 0; - unsigned char padding[3] = {0, 0, 0}; + unsigned int record_size = key_len + buf_len + sizeof(record_size); + unsigned int flash_addr = (unsigned int)STORAGE_ADDRESS + sector_offset; - if(key_len % 4) - key_len_alignment = 4 - (key_len % 4); - - if(buflen % 4) - buflen_alignment = 4 - (buflen % 4); - - write_to_flash(sector_offset, (unsigned char *)key, key_len); - write_to_flash(sector_offset+key_len, padding, key_len_alignment); - write_to_flash(sector_offset+key_len+key_len_alignment, (unsigned char *)&buflen, sizeof(buflen)); - write_to_flash(sector_offset+key_len+key_len_alignment+sizeof(buflen), buffer, buflen); - write_to_flash(sector_offset+key_len+key_len_alignment+sizeof(buflen)+buflen, padding, buflen_alignment); + write_to_flash(flash_addr, (unsigned char *)&record_size, sizeof(record_size)); + write_to_flash(flash_addr+sizeof(record_size), (unsigned char *)key, key_len); + write_to_flash(flash_addr+sizeof(record_size)+key_len, buffer, buf_len); flush_cpu_dcache(); } -void fs_write(char *key, void *buffer, unsigned int buflen) +int fs_write(char *key, void *buffer, unsigned int buf_len) { - char *addr; - unsigned int key_size = strlen(key)+1; - unsigned int record_size = key_size + sizeof(int) + buflen; + struct record record; + unsigned int key_size = strlen(key) + 1; + unsigned int new_record_size = key_size + sizeof(int) + buf_len; + int no_error, fatal = 0; + struct iter_state is; - for(addr = STORAGE_ADDRESS; addr < STORAGE_ADDRESS + STORAGE_SIZE - record_size; addr += 4) { - if(*(unsigned int *)addr == END_MARKER) { - write_at_offset(key, buffer, buflen, (unsigned int)addr); - break; - } - } - if(addr >= STORAGE_ADDRESS + STORAGE_SIZE - record_size) { // Flash is full? Try to flush duplicates. - if(try_to_flush_duplicates()) - return; // No duplicates found, cannot write the new key-value record: sector is full. + record_iter_init(&is, STORAGE_ADDRESS, STORAGE_SIZE); + while((no_error = record_iter_next(&is, &record, &fatal))); - // Now retrying to write, hoping enough flash was freed. - for(addr = STORAGE_ADDRESS; addr < STORAGE_ADDRESS + STORAGE_SIZE - record_size; addr += 4) { - if(*(unsigned int *)addr == END_MARKER) { - write_at_offset(key, buffer, buflen, (unsigned int)addr); - break; - } - } + if(fatal) + goto fatal_error; + + if(STORAGE_SIZE - is.seek >= new_record_size) { + write_at_offset(key, buffer, buf_len, is.seek); + return 1; } + + if(!try_to_flush_duplicates(key, buf_len)) // storage is full, let's try to free some space up. + return 0; // No duplicates found, cannot write the new key-value record: sector is full. + // Now retrying to write, hoping enough flash was freed. + + record_iter_init(&is, STORAGE_ADDRESS, STORAGE_SIZE); + while((no_error = record_iter_next(&is, &record, &fatal))); + + if(fatal) + goto fatal_error; + + if(STORAGE_SIZE - is.seek >= new_record_size) { + write_at_offset(key, buffer, buf_len, is.seek); + return 1; // We eventually succeeded in writing the record + } else + return 0; // Storage is definitely full. + +fatal_error: + printf("fatal error: flash storage might be corrupted\n"); + return 0; } void fs_erase(void) @@ -154,33 +261,35 @@ void fs_erase(void) flush_cpu_dcache(); } -unsigned int fs_read(char *key, void *buffer, unsigned int buflen, unsigned int *remain) +unsigned int fs_read(char *key, void *buffer, unsigned int buf_len, unsigned int *remain) { unsigned int read_length = 0; - char *addr; + struct iter_state is; + struct record record; + int fatal = 0; - addr = STORAGE_ADDRESS; - while(addr < (STORAGE_ADDRESS + STORAGE_SIZE) && (*addr != END_MARKER)) { - unsigned int key_len, value_len; - char *key_addr = addr; + if(remain) + *remain = 0; - key_len = strlen(addr) + 1; - if(key_len % 4) - key_len += 4 - (key_len % 4); - addr += key_len; - value_len = *(unsigned int *)addr; - addr += sizeof(value_len); - if(strcmp(key_addr, key) == 0) { - memcpy(buffer, addr, min(value_len, buflen)); - read_length = min(value_len, buflen); + record_iter_init(&is, STORAGE_ADDRESS, STORAGE_SIZE); + while(record_iter_next(&is, &record, &fatal)) { + if(strcmp(record.key, key) == 0) { + memcpy(buffer, record.value, min(record.value_len, buf_len)); + read_length = min(record.value_len, buf_len); if(remain) - *remain = max(0, (int)value_len - (int)buflen); + *remain = max(0, (int)(record.value_len) - (int)buf_len); } - addr += value_len; - if((int)addr % 4) - addr += 4 - ((int)addr % 4); } + + if(fatal) + printf("fatal error: flash storage might be corrupted\n"); + return read_length; } +void fs_remove(char *key) +{ + fs_write(key, NULL, 0); +} + #endif /* CSR_SPIFLASH_BASE && SPIFLASH_PAGE_SIZE */ diff --git a/soc/runtime/flash_storage.h b/soc/runtime/flash_storage.h index 065dd82f3..9994fef37 100644 --- a/soc/runtime/flash_storage.h +++ b/soc/runtime/flash_storage.h @@ -5,8 +5,9 @@ #ifndef __FLASH_STORAGE_H #define __FLASH_STORAGE_H +void fs_remove(char *key); void fs_erase(void); -void fs_write(char *key, void *buffer, unsigned int buflen); +int fs_write(char *key, void *buffer, unsigned int buflen); unsigned int fs_read(char *key, void *buffer, unsigned int buflen, unsigned int *remain); #endif /* __FLASH_STORAGE_H */ diff --git a/soc/runtime/session.c b/soc/runtime/session.c index a0f2d3ab7..9e9ee2726 100644 --- a/soc/runtime/session.c +++ b/soc/runtime/session.c @@ -12,6 +12,7 @@ #include "kloader.h" #include "exceptions.h" #include "session.h" +#include "flash_storage.h" #define BUFFER_IN_SIZE (1024*1024) #define BUFFER_OUT_SIZE (1024*1024) @@ -86,7 +87,12 @@ enum { REMOTEMSG_TYPE_LOAD_OBJECT, REMOTEMSG_TYPE_RUN_KERNEL, - REMOTEMSG_TYPE_RPC_REPLY + REMOTEMSG_TYPE_RPC_REPLY, + + REMOTEMSG_TYPE_FLASH_READ_REQUEST, + REMOTEMSG_TYPE_FLASH_WRITE_REQUEST, + REMOTEMSG_TYPE_FLASH_ERASE_REQUEST, + REMOTEMSG_TYPE_FLASH_REMOVE_REQUEST }; /* device to host */ @@ -104,8 +110,24 @@ enum { REMOTEMSG_TYPE_KERNEL_EXCEPTION, REMOTEMSG_TYPE_RPC_REQUEST, + + REMOTEMSG_TYPE_FLASH_READ_REPLY, + REMOTEMSG_TYPE_FLASH_WRITE_REPLY, + REMOTEMSG_TYPE_FLASH_OK_REPLY, + REMOTEMSG_TYPE_FLASH_ERROR_REPLY }; +static int check_flash_storage_key_len(char *key, unsigned int key_len) +{ + if(key_len == get_in_packet_len() - 8) { + log("Invalid key: not a null-terminated string"); + buffer_out[8] = REMOTEMSG_TYPE_FLASH_ERROR_REPLY; + submit_output(9); + return 0; + } + return 1; +} + static int process_input(void) { switch(buffer_in[8]) { @@ -204,6 +226,63 @@ static int process_input(void) user_kernel_state = USER_KERNEL_RUNNING; break; } + case REMOTEMSG_TYPE_FLASH_READ_REQUEST: { +#if SPIFLASH_SECTOR_SIZE - 4 > BUFFER_OUT_SIZE - 9 +#error Output buffer cannot hold the flash storage data +#elif SPIFLASH_SECTOR_SIZE - 4 > BUFFER_IN_SIZE - 9 +#error Input buffer cannot hold the flash storage data +#endif + unsigned int ret, in_packet_len; + char *key; + + in_packet_len = get_in_packet_len(); + key = &buffer_in[9]; + buffer_in[in_packet_len] = '\0'; + + buffer_out[8] = REMOTEMSG_TYPE_FLASH_READ_REPLY; + ret = fs_read(key, &buffer_out[9], sizeof(buffer_out) - 9, NULL); + submit_output(9 + ret); + break; + } + case REMOTEMSG_TYPE_FLASH_WRITE_REQUEST: { + char *key, *value; + unsigned int key_len, value_len, in_packet_len; + int ret; + + in_packet_len = get_in_packet_len(); + key = &buffer_in[9]; + key_len = strnlen(key, in_packet_len - 9) + 1; + if(!check_flash_storage_key_len(key, key_len)) + break; + + value_len = in_packet_len - key_len - 9; + value = key + key_len; + ret = fs_write(key, value, value_len); + + buffer_out[8] = REMOTEMSG_TYPE_FLASH_WRITE_REPLY; + buffer_out[9] = ret; + submit_output(10); + break; + } + case REMOTEMSG_TYPE_FLASH_ERASE_REQUEST: { + fs_erase(); + buffer_out[8] = REMOTEMSG_TYPE_FLASH_OK_REPLY; + submit_output(9); + break; + } + case REMOTEMSG_TYPE_FLASH_REMOVE_REQUEST: { + char *key; + unsigned int in_packet_len; + + in_packet_len = get_in_packet_len(); + key = &buffer_in[9]; + buffer_in[in_packet_len] = '\0'; + + fs_remove(key); + buffer_out[8] = REMOTEMSG_TYPE_FLASH_OK_REPLY; + submit_output(9); + break; + } default: return 0; } diff --git a/soc/runtime/test_mode.c b/soc/runtime/test_mode.c index 0b0ee6426..f2bb36970 100644 --- a/soc/runtime/test_mode.c +++ b/soc/runtime/test_mode.c @@ -259,13 +259,201 @@ static void ddstest(char *n) #if (defined CSR_SPIFLASH_BASE && defined SPIFLASH_PAGE_SIZE) static void fsread(char *key) { - char buf[256]; + char readbuf[SPIFLASH_SECTOR_SIZE]; int r; - r = fs_read(key, buf, sizeof(buf)-1, NULL); - buf[r] = 0; - puts(buf); + r = fs_read(key, readbuf, sizeof(readbuf)-1, NULL); + readbuf[r] = 0; + if(r == 0) + printf("key %s does not exist\n", key); + else + puts(readbuf); } + +static void fswrite(char *key, void *buffer, unsigned int length) +{ + if(!fs_write(key, buffer, length)) + printf("cannot write key %s because flash storage is full\n", key); +} + +static void fsfull(void) +{ + int i; + char value[4096]; + memset(value, '@', sizeof(value)); + + for(i = 0; i < SPIFLASH_SECTOR_SIZE/sizeof(value); i++) + fs_write("plip", value, sizeof(value)); +} + +static void check_read(char *key, char *expected, unsigned int length, unsigned int testnum) +{ + char readbuf[SPIFLASH_SECTOR_SIZE]; + unsigned int remain, readlength; + + memset(readbuf, '\0', sizeof(readbuf)); + + readlength = fs_read(key, readbuf, sizeof(readbuf), &remain); + if(remain > 0) + printf("KO[%u] remain == %u, expected 0\n", testnum, remain); + if(readlength != length) + printf("KO[%u] read length == %u, expected %u\n", testnum, readlength, length); + if(remain == 0 && readlength == length) + printf("."); + + readbuf[readlength] = 0; + if(memcmp(expected, readbuf, readlength) == 0) + printf(".\n"); + else + printf("KO[%u] read %s instead of %s\n", testnum, readbuf, expected); +} + +static void check_doesnt_exist(char *key, unsigned int testnum) +{ + char readbuf; + unsigned int remain, readlength; + + readlength = fs_read(key, &readbuf, sizeof(readbuf), &remain); + if(remain > 0) + printf("KO[%u] remain == %u, expected 0\n", testnum, remain); + if(readlength > 0) + printf("KO[%u] readlength == %d, expected 0\n", testnum, readlength); + if(remain == 0 && readlength == 0) + printf(".\n"); +} + +static void check_write(unsigned int ret) +{ + if(!ret) + printf("KO"); + else + printf("."); +} + +static inline void test_sector_is_full(void) +{ + char c; + char value[4096]; + char key[2] = {0, 0}; + + fs_erase(); + memset(value, '@', sizeof(value)); + for(c = 1; c <= SPIFLASH_SECTOR_SIZE/sizeof(value); c++) { + key[0] = c; + check_write(fs_write(key, value, sizeof(value) - 6)); + } + check_write(!fs_write("this_should_fail", "fail", 5)); + printf("\n"); +} + +static void test_one_big_record(int testnum) +{ + char value[SPIFLASH_SECTOR_SIZE]; + memset(value, '@', sizeof(value)); + + fs_erase(); + check_write(fs_write("a", value, sizeof(value) - 6)); + check_read("a", value, sizeof(value) - 6, testnum); + check_write(fs_write("a", value, sizeof(value) - 6)); + check_read("a", value, sizeof(value) - 6, testnum); + check_write(!fs_write("b", value, sizeof(value) - 6)); + check_read("a", value, sizeof(value) - 6, testnum); + fs_remove("a"); + check_doesnt_exist("a", testnum); + check_write(fs_write("a", value, sizeof(value) - 6)); + check_read("a", value, sizeof(value) - 6, testnum); + fs_remove("a"); + check_doesnt_exist("a", testnum); + value[0] = '!'; + check_write(fs_write("b", value, sizeof(value) - 6)); + check_read("b", value, sizeof(value) - 6, testnum); +} + +static void test_flush_duplicate_rollback(int testnum) +{ + char value[SPIFLASH_SECTOR_SIZE]; + memset(value, '@', sizeof(value)); + + fs_erase(); + /* This makes the flash storage full with one big record */ + check_write(fs_write("a", value, SPIFLASH_SECTOR_SIZE - 6)); + /* This should trigger the try_to_flush_duplicate code which + * at first will not keep the old "a" record value because we are + * overwriting it. But then it should roll back to the old value + * because the new record is too large. + */ + value[0] = '!'; + check_write(!fs_write("a", value, sizeof(value))); + /* check we still have the old record value */ + value[0] = '@'; + check_read("a", value, SPIFLASH_SECTOR_SIZE - 6, testnum); +} + +static void test_too_big_fails(int testnum) +{ + char value[SPIFLASH_SECTOR_SIZE]; + memset(value, '@', sizeof(value)); + + fs_erase(); + check_write(!fs_write("a", value, sizeof(value) - 6 + /* TOO BIG */ 1)); + check_doesnt_exist("a", testnum); +} + +static void fs_test(void) +{ + int i; + char writebuf[] = "abcdefghijklmnopqrst"; + char read_check[4096]; + int vect_length = sizeof(writebuf); + + memset(read_check, '@', sizeof(read_check)); + printf("testing...\n"); + for(i = 0; i < vect_length; i++) { + printf("%u.0:", i); + fs_erase(); + check_write(fs_write("a", writebuf, i)); + check_read("a", writebuf, i, i); + + printf("%u.1:", i); + fsfull(); + check_read("a", writebuf, i, i); + + printf("%u.2:", i); + check_read("plip", read_check, sizeof(read_check), i); + + printf("%u.3:", i); + check_write(fs_write("a", "b", 2)); + check_read("a", "b", 2, i); + + printf("%u.4:", i); + fsfull(); + check_read("a", "b", 2, i); + + printf("%u.5:", i); + check_doesnt_exist("notfound", i); + + printf("%u.6:", i); + fs_remove("a"); + check_doesnt_exist("a", i); + + printf("%u.7:", i); + fsfull(); + check_doesnt_exist("a", i); + } + + printf("%u:", vect_length); + test_sector_is_full(); + + printf("%u:", vect_length+1); + test_one_big_record(vect_length+1); + + printf("%u:", vect_length+2); + test_flush_duplicate_rollback(vect_length+2); + + printf("%u:", vect_length+3); + test_too_big_fails(vect_length+3); +} + #endif static void help(void) @@ -288,6 +476,8 @@ static void help(void) puts("fserase - erase flash storage"); puts("fswrite - write to flash storage"); puts("fsread - read flash storage"); + puts("fsremove - remove a key-value record from flash storage"); + puts("fstest - run flash storage tests. WARNING: erases the storage area"); #endif } @@ -340,6 +530,7 @@ static char *get_token(char **str) return d; } + static void do_command(char *c) { char *token; @@ -365,8 +556,10 @@ static void do_command(char *c) #if (defined CSR_SPIFLASH_BASE && defined SPIFLASH_PAGE_SIZE) else if(strcmp(token, "fserase") == 0) fs_erase(); - else if(strcmp(token, "fswrite") == 0) fs_write(get_token(&c), c, strlen(c)); + else if(strcmp(token, "fswrite") == 0) fswrite(get_token(&c), c, strlen(c)); else if(strcmp(token, "fsread") == 0) fsread(get_token(&c)); + else if(strcmp(token, "fsremove") == 0) fs_remove(get_token(&c)); + else if(strcmp(token, "fstest") == 0) fs_test(); #endif else if(strcmp(token, "") != 0) From c32133b81557a1665b877756aee4bce836c98e32 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Wed, 27 May 2015 12:55:52 +0200 Subject: [PATCH 003/227] flash_storage: avoid crash if a record size gets corrupted to be less than 6 --- soc/runtime/flash_storage.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/soc/runtime/flash_storage.c b/soc/runtime/flash_storage.c index a8e01436a..40f062884 100644 --- a/soc/runtime/flash_storage.c +++ b/soc/runtime/flash_storage.c @@ -61,6 +61,13 @@ static int record_iter_next(struct iter_state *is, struct record *record, int *f if(record->size == END_MARKER) return 0; + if(record->size < 6) { + printf("flash_storage might be corrupted: record size is %u (<6) at address %08x\n", record->size, record->raw_record); + if(fatal) + *fatal = 1; + return 0; + } + if(is->seek > is->buf_len - sizeof(record->size) - 2) { /* 2 is the minimum key length */ printf("flash_storage might be corrupted: END_MARKER missing at the end of the storage sector\n"); if(fatal) From 0b05b54a87077014561135cbbdd6840213ba9a9f Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Wed, 27 May 2015 19:25:50 +0800 Subject: [PATCH 004/227] test: add scheduler unittest --- artiq/test/scheduler.py | 107 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 artiq/test/scheduler.py diff --git a/artiq/test/scheduler.py b/artiq/test/scheduler.py new file mode 100644 index 000000000..8588a82a2 --- /dev/null +++ b/artiq/test/scheduler.py @@ -0,0 +1,107 @@ +import unittest +import asyncio +import sys +from time import sleep + +from artiq import * +from artiq.master.scheduler import Scheduler + + +class EmptyExperiment(Experiment, AutoDB): + def run(self): + pass + + +class BackgroundExperiment(Experiment, AutoDB): + def run(self): + while True: + self.scheduler.pause() + sleep(0.2) + + +def _get_expid(name): + return { + "file": sys.modules[__name__].__file__, + "experiment": name, + "arguments": dict() + } + + +def _get_basic_steps(rid, expid): + return [ + {"action": "setitem", "key": rid, "value": + {"pipeline": "main", "status": "pending", "priority": 0, + "expid": expid, "due_date": None}, "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 test_steps(self): + scheduler = Scheduler(0, _handlers) + expid = _get_expid("EmptyExperiment") + + expect = _get_basic_steps(0, expid) + done = asyncio.Event() + expect_idx = 0 + def notify(notifier, mod): + nonlocal expect_idx + self.assertEqual(mod, expect[expect_idx]) + expect_idx += 1 + if expect_idx >= len(expect): + done.set() + scheduler.notifier.publish = notify + + loop = asyncio.get_event_loop() + scheduler.start() + scheduler.submit("main", expid, 0, None) + loop.run_until_complete(done.wait()) + loop.run_until_complete(scheduler.stop()) + + def test_pause(self): + 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(notifier, 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 + + loop = asyncio.get_event_loop() + scheduler.start() + scheduler.submit("main", expid_bg, -99, None) + loop.run_until_complete(background_running.wait()) + scheduler.submit("main", expid, 0, None) + loop.run_until_complete(done.wait()) + loop.run_until_complete(scheduler.stop()) From 4da377eef09b175b38f8fbe277f76c827847b36e Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Wed, 27 May 2015 21:42:49 +0200 Subject: [PATCH 005/227] setup/conda: update frontends --- conda/artiq/meta.yaml | 4 +++- setup.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/conda/artiq/meta.yaml b/conda/artiq/meta.yaml index 8322af1af..dec979308 100644 --- a/conda/artiq/meta.yaml +++ b/conda/artiq/meta.yaml @@ -11,8 +11,11 @@ build: entry_points: - artiq_client = artiq.frontend.artiq_client:main - artiq_compile = artiq.frontend.artiq_compile:main + - artiq_coreconfig = artiq.frontend.artiq_coreconfig:main - artiq_ctlmgr = artiq.frontend.artiq_ctlmgr:main + - artiq_gui = artiq.frontend.artiq_gui:main - artiq_master = artiq.frontend.artiq_master:main + - artiq_mkfs = artiq.frontend.artiq_mkfs:main - artiq_rpctool = artiq.frontend.artiq_rpctool:main - artiq_run = artiq.frontend.artiq_run:main - lda_controller = artiq.frontend.lda_controller:main @@ -21,7 +24,6 @@ build: - pdq2_controller = artiq.frontend.pdq2_controller:main - pxi6733_controller = artiq.frontend.pxi6733_controller:main - thorlabs_tcube_controller = artiq.frontend.thorlabs_tcube_controller:main - - artiq_gui = artiq.frontend.artiq_gui:main requirements: build: diff --git a/setup.py b/setup.py index 13f35efd8..ff98ab0ba 100755 --- a/setup.py +++ b/setup.py @@ -12,6 +12,7 @@ requirements = [ scripts = [ "artiq_client=artiq.frontend.artiq_client:main", "artiq_compile=artiq.frontend.artiq_compile:main", + "artiq_coreconfig=artiq.frontend.artiq_coreconfig:main", "artiq_ctlmgr=artiq.frontend.artiq_ctlmgr:main", "artiq_gui=artiq.frontend.artiq_gui:main", "artiq_master=artiq.frontend.artiq_master:main", From 737f6d4485eda029e3ac023c27cc153f17a78cc3 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 28 May 2015 17:20:58 +0800 Subject: [PATCH 006/227] scheduler: support pipeline flush --- artiq/frontend/artiq_client.py | 10 ++- artiq/gui/explorer.py | 22 +++--- artiq/master/scheduler.py | 74 +++++++++++-------- artiq/master/worker_impl.py | 2 +- artiq/test/scheduler.py | 9 ++- artiq/tools.py | 24 ++++++ .../repository/flopping_f_simulation.py | 2 +- 7 files changed, 95 insertions(+), 48 deletions(-) diff --git a/artiq/frontend/artiq_client.py b/artiq/frontend/artiq_client.py index 443b72b40..7499412b0 100755 --- a/artiq/frontend/artiq_client.py +++ b/artiq/frontend/artiq_client.py @@ -32,14 +32,17 @@ 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("-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("-e", "--experiment", default=None, help="experiment to run") parser_add.add_argument("file", @@ -106,7 +109,8 @@ def _action_submit(remote, args): 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)) diff --git a/artiq/gui/explorer.py b/artiq/gui/explorer.py index 9d5603193..3f1e590fd 100644 --- a/artiq/gui/explorer.py +++ b/artiq/gui/explorer.py @@ -41,19 +41,22 @@ class ExplorerDock(dockarea.Dock): 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_en = QtGui.QCheckBox("Due date:") grid.addWidget(self.datetime_en, 1, 0) - grid.addWidget(self.datetime, 1, 1, colspan=3) + grid.addWidget(self.datetime, 1, 1) + + self.priority = QtGui.QSpinBox() + self.priority.setRange(-99, 99) + grid.addLabel("Priority:", 1, 2) + grid.addWidget(self.priority, 1, 3) self.pipeline = QtGui.QLineEdit() self.pipeline.insert("main") grid.addLabel("Pipeline:", 2, 0) grid.addWidget(self.pipeline, 2, 1) - self.priority = QtGui.QSpinBox() - self.priority.setRange(-99, 99) - grid.addLabel("Priority:", 2, 2) - grid.addWidget(self.priority, 2, 3) + self.flush = QtGui.QCheckBox("Flush") + grid.addWidget(self.flush, 2, 2, colspan=2) submit = QtGui.QPushButton("Submit") grid.addWidget(submit, 3, 0, colspan=4) @@ -79,14 +82,14 @@ class ExplorerDock(dockarea.Dock): @asyncio.coroutine def submit(self, pipeline_name, file, experiment, arguments, - priority, due_date): + priority, due_date, flush): expid = { "file": file, "experiment": experiment, "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): @@ -101,4 +104,5 @@ class ExplorerDock(dockarea.Dock): due_date = None asyncio.async(self.submit(self.pipeline.text(), expinfo["file"], expinfo["experiment"], - dict(), self.priority.value(), due_date)) + dict(), self.priority.value(), due_date, + self.flush.isChecked())) diff --git a/artiq/master/scheduler.py b/artiq/master/scheduler.py index 44b14765d..dc6dda68e 100644 --- a/artiq/master/scheduler.py +++ b/artiq/master/scheduler.py @@ -4,7 +4,7 @@ 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, WaitSet from artiq.protocols.sync_struct import Notifier @@ -13,13 +13,14 @@ 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): @@ -45,7 +46,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,6 +54,7 @@ class Run: self.expid = expid self.priority = priority self.due_date = due_date + self.flush = flush self._status = RunStatus.pending self._terminated = False @@ -64,6 +66,7 @@ class Run: "expid": self.expid, "priority": self.priority, "due_date": self.due_date, + "flush": self.flush, "status": self._status.name } @@ -120,20 +123,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 @@ -161,13 +164,14 @@ class TaskObject: 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 +190,18 @@ 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 self.flush_tracker.wait_empty() run.status = RunStatus.preparing + self.flush_tracker.add(run.rid) try: 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 +222,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 @@ -246,7 +254,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 +265,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 +281,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 +380,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 +391,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) diff --git a/artiq/master/worker_impl.py b/artiq/master/worker_impl.py index 18e0e95ec..e82a79b1b 100644 --- a/artiq/master/worker_impl.py +++ b/artiq/master/worker_impl.py @@ -79,7 +79,7 @@ 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): diff --git a/artiq/test/scheduler.py b/artiq/test/scheduler.py index 8588a82a2..dd993d516 100644 --- a/artiq/test/scheduler.py +++ b/artiq/test/scheduler.py @@ -31,7 +31,8 @@ def _get_basic_steps(rid, expid): return [ {"action": "setitem", "key": rid, "value": {"pipeline": "main", "status": "pending", "priority": 0, - "expid": expid, "due_date": None}, "path": []}, + "expid": expid, "due_date": None, "flush": False}, + "path": []}, {"action": "setitem", "key": "status", "value": "preparing", "path": [rid]}, {"action": "setitem", "key": "status", "value": "prepare_done", @@ -71,7 +72,7 @@ class SchedulerCase(unittest.TestCase): loop = asyncio.get_event_loop() scheduler.start() - scheduler.submit("main", expid, 0, None) + scheduler.submit("main", expid, 0, None, False) loop.run_until_complete(done.wait()) loop.run_until_complete(scheduler.stop()) @@ -100,8 +101,8 @@ class SchedulerCase(unittest.TestCase): loop = asyncio.get_event_loop() scheduler.start() - scheduler.submit("main", expid_bg, -99, None) + scheduler.submit("main", expid_bg, -99, None, False) loop.run_until_complete(background_running.wait()) - scheduler.submit("main", expid, 0, None) + scheduler.submit("main", expid, 0, None, False) loop.run_until_complete(done.wait()) loop.run_until_complete(scheduler.stop()) diff --git a/artiq/tools.py b/artiq/tools.py index 2b5400a93..a6ee390f8 100644 --- a/artiq/tools.py +++ b/artiq/tools.py @@ -119,3 +119,27 @@ def asyncio_queue_peek(q): return q._queue[0] else: raise asyncio.QueueEmpty + + +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() diff --git a/examples/master/repository/flopping_f_simulation.py b/examples/master/repository/flopping_f_simulation.py index 9aa2bc9d8..23cf42239 100644 --- a/examples/master/repository/flopping_f_simulation.py +++ b/examples/master/repository/flopping_f_simulation.py @@ -51,7 +51,7 @@ class FloppingF(Experiment, AutoDB): self.brightness.append(brightness) time.sleep(0.1) self.scheduler.submit(self.scheduler.pipeline_name, self.scheduler.expid, - self.scheduler.priority, time.time() + 20) + self.scheduler.priority, time.time() + 20, False) def analyze(self): popt, pcov = curve_fit(model_numpy, From 4a7c6954c350fc56dc19537feedf673e92bc6648 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 28 May 2015 17:21:20 +0800 Subject: [PATCH 007/227] artiq_run: implement mockups of new scheduler API --- artiq/frontend/artiq_run.py | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/artiq/frontend/artiq_run.py b/artiq/frontend/artiq_run.py index 9b499fb96..4aa66a91a 100755 --- a/artiq/frontend/artiq_run.py +++ b/artiq/frontend/artiq_run.py @@ -48,28 +48,21 @@ class DummyWatchdog: class DummyScheduler: - def __init__(self): + def __init__(self, expid): self.next_rid = 0 self.next_trid = 0 + self.pipeline_name = "main" + self.priority = 0 + self.expid = expid - 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) + def delete(self, rid): + logger.info("Deleting RID %s", rid) watchdog = DummyWatchdog @@ -115,11 +108,13 @@ def _build_experiment(dbh, args): file = getattr(module, "__file__") exp = get_experiment(module, args.experiment) arguments = parse_arguments(args.arguments) + expid = { + "file": file, + "experiment": args.experiment, + "arguments": arguments + } return exp(dbh, - scheduler=DummyScheduler(), - run_params=dict(file=file, - experiment=args.experiment, - arguments=arguments), + scheduler=DummyScheduler(expid), **arguments) From e752e57fa5274aaec5aec815029336043318ca97 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 28 May 2015 17:37:08 +0800 Subject: [PATCH 008/227] scheduler: do not duplicate 'run terminated' information --- artiq/master/scheduler.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/artiq/master/scheduler.py b/artiq/master/scheduler.py index dc6dda68e..94b3a9622 100644 --- a/artiq/master/scheduler.py +++ b/artiq/master/scheduler.py @@ -26,7 +26,7 @@ class RunStatus(Enum): 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) try: @@ -34,7 +34,7 @@ def _mk_worker_method(name): 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 @@ -57,7 +57,6 @@ class Run: self.flush = flush self._status = RunStatus.pending - self._terminated = False self._worker = Worker(worker_handlers) self._notifier = notifier @@ -77,7 +76,7 @@ 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 @@ -93,7 +92,6 @@ class Run: @asyncio.coroutine def close(self): # called through pool - self._terminated = True yield from self._worker.close() del self._notifier[self.rid] From b0f8141018f10d7c98f580d7051eb760b240c796 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 28 May 2015 17:48:33 +0800 Subject: [PATCH 009/227] scheduler: cancel flush when run is cancelled --- artiq/master/scheduler.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/artiq/master/scheduler.py b/artiq/master/scheduler.py index 94b3a9622..30c69352b 100644 --- a/artiq/master/scheduler.py +++ b/artiq/master/scheduler.py @@ -26,15 +26,15 @@ class RunStatus(Enum): def _mk_worker_method(name): @asyncio.coroutine def worker_method(self, *args, **kwargs): - if self._worker.closed.is_set(): + 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._worker.closed.is_set(): + if self.worker.closed.is_set(): logger.debug("suppressing worker exception of terminated run", exc_info=True) # Return completion on termination @@ -56,8 +56,9 @@ class Run: self.due_date = due_date self.flush = flush + self.worker = Worker(worker_handlers) + self._status = RunStatus.pending - self._worker = Worker(worker_handlers) self._notifier = notifier self._notifier[self.rid] = { @@ -76,7 +77,7 @@ class Run: @status.setter def status(self, value): self._status = value - if not self._worker.closed.is_set(): + 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 @@ -92,7 +93,7 @@ class Run: @asyncio.coroutine def close(self): # called through pool - yield from self._worker.close() + yield from self.worker.close() del self._notifier[self.rid] _prepare = _mk_worker_method("prepare") @@ -190,7 +191,12 @@ class PrepareStage(TaskObject): if run.due_date is None or run.due_date < now: if run.flush: run.status = RunStatus.flushing - yield from self.flush_tracker.wait_empty() + 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: From aa242f7c66ccfc160fa4b75305bd63b84ed11480 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 28 May 2015 18:24:26 +0800 Subject: [PATCH 010/227] scheduler: simplify priority policy Remove overdueness. User must submit calibration experiments with higher priority values for them to take precedence. --- artiq/frontend/artiq_client.py | 4 ++-- artiq/gui/schedule.py | 4 ++-- artiq/master/scheduler.py | 13 +++++++------ 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/artiq/frontend/artiq_client.py b/artiq/frontend/artiq_client.py index 7499412b0..78e4a49ac 100755 --- a/artiq/frontend/artiq_client.py +++ b/artiq/frontend/artiq_client.py @@ -138,8 +138,8 @@ 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"]) diff --git a/artiq/gui/schedule.py b/artiq/gui/schedule.py index f8cbb9d36..459547f86 100644 --- a/artiq/gui/schedule.py +++ b/artiq/gui/schedule.py @@ -17,8 +17,8 @@ class _ScheduleModel(DictSyncModel): 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: diff --git a/artiq/master/scheduler.py b/artiq/master/scheduler.py index 30c69352b..85815209b 100644 --- a/artiq/master/scheduler.py +++ b/artiq/master/scheduler.py @@ -81,14 +81,16 @@ class Run: 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): @@ -240,10 +242,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() From 575dfade38c70154fa3f4936e5f7e034c03ce294 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Fri, 29 May 2015 11:10:40 +0200 Subject: [PATCH 011/227] flash_storage comm: use OK/ERROR replies instead of specific FLASH_WRITE_REPLY --- artiq/coredevice/comm_generic.py | 12 ++++-------- soc/runtime/session.c | 9 +++++---- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/artiq/coredevice/comm_generic.py b/artiq/coredevice/comm_generic.py index 3de7b40e1..a737dfb84 100644 --- a/artiq/coredevice/comm_generic.py +++ b/artiq/coredevice/comm_generic.py @@ -43,9 +43,8 @@ class _D2HMsgType(Enum): RPC_REQUEST = 10 FLASH_READ_REPLY = 11 - FLASH_WRITE_REPLY = 12 - FLASH_OK_REPLY = 13 - FLASH_ERROR_REPLY = 14 + FLASH_OK_REPLY = 12 + FLASH_ERROR_REPLY = 13 class UnsupportedDevice(Exception): @@ -148,14 +147,11 @@ class CommGeneric: self.write(b"\x00") self.write(value) _, ty = self._read_header() - if ty != _D2HMsgType.FLASH_WRITE_REPLY: + if ty != _D2HMsgType.FLASH_OK_REPLY: if ty == _D2HMsgType.FLASH_ERROR_REPLY: - raise IOError("Invalid key: not a null-terminated string") + raise IOError("Flash storage is full") else: raise IOError("Incorrect reply from device: {}".format(ty)) - ret = self.read(1) - if ret != b"\x01": - raise IOError("Flash storage is full") def flash_storage_erase(self): self._write_header(9, _H2DMsgType.FLASH_ERASE_REQUEST) diff --git a/soc/runtime/session.c b/soc/runtime/session.c index 9e9ee2726..1a42bb9ad 100644 --- a/soc/runtime/session.c +++ b/soc/runtime/session.c @@ -112,7 +112,6 @@ enum { REMOTEMSG_TYPE_RPC_REQUEST, REMOTEMSG_TYPE_FLASH_READ_REPLY, - REMOTEMSG_TYPE_FLASH_WRITE_REPLY, REMOTEMSG_TYPE_FLASH_OK_REPLY, REMOTEMSG_TYPE_FLASH_ERROR_REPLY }; @@ -259,9 +258,11 @@ static int process_input(void) value = key + key_len; ret = fs_write(key, value, value_len); - buffer_out[8] = REMOTEMSG_TYPE_FLASH_WRITE_REPLY; - buffer_out[9] = ret; - submit_output(10); + if(ret) + buffer_out[8] = REMOTEMSG_TYPE_FLASH_OK_REPLY; + else + buffer_out[8] = REMOTEMSG_TYPE_FLASH_ERROR_REPLY; + submit_output(9); break; } case REMOTEMSG_TYPE_FLASH_ERASE_REQUEST: { From ed95038681805f4a3fb788cf4a9a25708fb3d5f4 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Fri, 29 May 2015 11:11:29 +0200 Subject: [PATCH 012/227] flash_storage: remove useless parentheses --- soc/runtime/flash_storage.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/soc/runtime/flash_storage.c b/soc/runtime/flash_storage.c index 40f062884..547087fd0 100644 --- a/soc/runtime/flash_storage.c +++ b/soc/runtime/flash_storage.c @@ -284,7 +284,7 @@ unsigned int fs_read(char *key, void *buffer, unsigned int buf_len, unsigned int memcpy(buffer, record.value, min(record.value_len, buf_len)); read_length = min(record.value_len, buf_len); if(remain) - *remain = max(0, (int)(record.value_len) - (int)buf_len); + *remain = max(0, (int)record.value_len - (int)buf_len); } } From 6ff2e1a0833dabfec3c0b06cbb63c71b34622fd7 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 29 May 2015 19:43:39 +0800 Subject: [PATCH 013/227] test/scheduler: verify that a high priority timed experiment in the future is not run --- artiq/test/scheduler.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/artiq/test/scheduler.py b/artiq/test/scheduler.py index dd993d516..36e11b302 100644 --- a/artiq/test/scheduler.py +++ b/artiq/test/scheduler.py @@ -1,7 +1,7 @@ import unittest import asyncio import sys -from time import sleep +from time import time, sleep from artiq import * from artiq.master.scheduler import Scheduler @@ -59,7 +59,7 @@ class SchedulerCase(unittest.TestCase): scheduler = Scheduler(0, _handlers) expid = _get_expid("EmptyExperiment") - expect = _get_basic_steps(0, expid) + expect = _get_basic_steps(1, expid) done = asyncio.Event() expect_idx = 0 def notify(notifier, mod): @@ -72,8 +72,22 @@ class SchedulerCase(unittest.TestCase): loop = asyncio.get_event_loop() 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): From 048782e26cd99f0fd34af7e3cd1d82e3e97db5f6 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 29 May 2015 20:16:47 +0800 Subject: [PATCH 014/227] test/scheduler: test flush --- artiq/test/scheduler.py | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/artiq/test/scheduler.py b/artiq/test/scheduler.py index 36e11b302..436f9316a 100644 --- a/artiq/test/scheduler.py +++ b/artiq/test/scheduler.py @@ -27,11 +27,11 @@ def _get_expid(name): } -def _get_basic_steps(rid, expid): +def _get_basic_steps(rid, expid, priority=0, flush=False): return [ {"action": "setitem", "key": rid, "value": - {"pipeline": "main", "status": "pending", "priority": 0, - "expid": expid, "due_date": None, "flush": False}, + {"pipeline": "main", "status": "pending", "priority": priority, + "expid": expid, "due_date": None, "flush": flush}, "path": []}, {"action": "setitem", "key": "status", "value": "preparing", "path": [rid]}, @@ -120,3 +120,37 @@ class SchedulerCase(unittest.TestCase): scheduler.submit("main", expid, 0, None, False) loop.run_until_complete(done.wait()) loop.run_until_complete(scheduler.stop()) + + def test_flush(self): + 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(notifier, 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 + + loop = asyncio.get_event_loop() + 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()) From 7ec0bc047028eb7dc0a593783df9ca2b72898f13 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Fri, 29 May 2015 16:30:32 +0200 Subject: [PATCH 015/227] manual: explain how to compile and flash the idle kernel --- doc/manual/getting_started.rst | 2 ++ doc/manual/installing.rst | 20 ++++++++++++++++++++ doc/manual/utilities.rst | 9 ++++----- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/doc/manual/getting_started.rst b/doc/manual/getting_started.rst index eb3166f6c..da2952fd3 100644 --- a/doc/manual/getting_started.rst +++ b/doc/manual/getting_started.rst @@ -1,6 +1,8 @@ Getting started =============== +.. _connecting-to-the-core-device: + Connecting to the core device ----------------------------- diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index 92e54b55e..647d31232 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -168,6 +168,26 @@ The communication parameters are 115200 8-N-1. .. note:: The reset button of the KC705 board is the "CPU_RST" labeled button. .. warning:: Both those instructions will result in the flash storage being wiped out. However you can use the test mode to change the IP/MAC without erasing everything if you skip the "fserase" command. +* (optional) Flash the ``idle`` kernel + +The ``idle`` kernel is the kernel (some piece of code running on the core device) which the core device runs whenever it is not connected to a PC via ethernet. +This kernel is therefore stored in the :ref:`core device configuration flash storage `. +To flash the ``idle`` kernel: + + * Compile the ``idle`` experiment: + The ``idle`` experiment's ``run()`` method must be a kernel: it must be decorated with the ``@kernel`` decorator (see :ref:`next topic ` for more information about kernels). + + Moreover, since the core device is not connected to the PC: RPC are forbidden in this ``idle`` experiment. + :: + + $ artiq_compile idle.py + + * Write it into the core device configuration flash storage: :: + + $ artiq_coreconfig -f idle_kernel idle.elf + +.. note:: You can find more information about how to use the ``artiq_coreconfig`` tool on the :ref:`Utilities ` page. + Installing the host-side software --------------------------------- diff --git a/doc/manual/utilities.rst b/doc/manual/utilities.rst index 740fa81b1..027d22470 100644 --- a/doc/manual/utilities.rst +++ b/doc/manual/utilities.rst @@ -117,12 +117,11 @@ To write the value ``test_value`` in the key ``my_key``:: $ artiq_coreconfig -r my_key b'test_value' -You can also write entire files in a record using the ``-f`` parameter:: +You can also write entire files in a record using the ``-f`` parameter. This is useful for instance to write the ``idle`` kernel in the flash storage:: - $ echo "this_is_a_test" > my_filename - $ artiq_coreconfig -f my_key my_filename - $ artiq_coreconfig -r my_key - b'this_is_a_test\n' + $ artiq_coreconfig -f idle_kernel idle.elf + $ artiq_coreconfig -r idle_kernel | head -c9 + b'\x7fELF You can write several records at once:: From d5fb50b497af988bbeb72a0a5e1a5c626bcb1636 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Fri, 29 May 2015 17:44:07 +0200 Subject: [PATCH 016/227] manual: after artiq is installed, frontends can be used without path prefix except non-python frontends like artiq_flash.sh --- doc/manual/installing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index 647d31232..8e2e9b6d8 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -128,7 +128,7 @@ The communication parameters are 115200 8-N-1. * You can either set it by generating a flash storage image and then flash it: :: - $ ~/artiq-dev/artiq/frontend/artiq_mkfs.py flash_storage.img -s mac xx:xx:xx:xx:xx:xx -s ip xx.xx.xx.xx + $ artiq_mkfs flash_storage.img -s mac xx:xx:xx:xx:xx:xx -s ip xx.xx.xx.xx $ ~/artiq-dev/artiq/frontend/artiq_flash.sh -f flash_storage.img * Or you can set it via the runtime test mode command line From a84f76bf0991a2b73bd83ebd8f56ef29184eebe8 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Fri, 29 May 2015 17:45:13 +0200 Subject: [PATCH 017/227] manual: cleanup, removing Gtk GUI related instructions --- doc/manual/installing.rst | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index 8e2e9b6d8..79ef78e66 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -217,31 +217,13 @@ Installing the host-side software .. note:: Compilation of LLVM can take more than 30 min on some machines. -* Install ARTIQ (without the GUI): :: +* Install ARTIQ: :: $ cd ~/artiq-dev $ git clone https://github.com/m-labs/artiq # if not already done $ cd artiq $ python3 setup.py develop --user -* Install ARTIQ (with the GUI): :: - - $ cd ~/artiq-dev - $ git clone https://github.com/m-labs/cairoplot3 - $ cd cairoplot3 - $ python3 setup.py install --user - $ cd - - $ git clone https://github.com/m-labs/gbulb - $ cd gbulb - $ python3 setup.py install --user - $ cd - - $ git clone https://github.com/m-labs/artiq # if not already done - $ cd artiq - $ ARTIQ_GUI=1 python3 setup.py develop --user - -.. note:: - Use ARTIQ_GUI=1 to install GUI dependencies which are only supported on Linux for now, to install ARTIQ on Windows do not set ARTIQ_GUI. - * Build the documentation: :: $ cd ~/artiq-dev/artiq/doc/manual From b81151eb42669fa6eb3181f4dd31b542fac06ec6 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Tue, 2 Jun 2015 17:41:40 +0800 Subject: [PATCH 018/227] soc: rtio monitor --- artiq/gateware/rtio/__init__.py | 1 + artiq/gateware/rtio/core.py | 3 ++- artiq/gateware/rtio/monitor.py | 28 +++++++++++++++++++++++++++ artiq/gateware/rtio/phy/ttl_simple.py | 4 ++++ soc/runtime/session.c | 2 +- soc/runtime/test_mode.c | 2 +- soc/targets/artiq_kc705.py | 16 ++++++++------- soc/targets/artiq_pipistrello.py | 19 ++++++++++-------- 8 files changed, 57 insertions(+), 18 deletions(-) create mode 100644 artiq/gateware/rtio/monitor.py diff --git a/artiq/gateware/rtio/__init__.py b/artiq/gateware/rtio/__init__.py index 9b34976c0..6e6cc3550 100644 --- a/artiq/gateware/rtio/__init__.py +++ b/artiq/gateware/rtio/__init__.py @@ -1 +1,2 @@ from artiq.gateware.rtio.core import Channel, RTIO +from artiq.gateware.rtio.monitor import Monitor diff --git a/artiq/gateware/rtio/core.py b/artiq/gateware/rtio/core.py index cfa7ea01e..b9d02246a 100644 --- a/artiq/gateware/rtio/core.py +++ b/artiq/gateware/rtio/core.py @@ -247,8 +247,9 @@ class _InputManager(Module): class Channel: - def __init__(self, interface, ofifo_depth=64, ififo_depth=64): + def __init__(self, interface, probes=[], ofifo_depth=64, ififo_depth=64): self.interface = interface + self.probes = probes self.ofifo_depth = ofifo_depth self.ififo_depth = ififo_depth diff --git a/artiq/gateware/rtio/monitor.py b/artiq/gateware/rtio/monitor.py new file mode 100644 index 000000000..7b1fd8358 --- /dev/null +++ b/artiq/gateware/rtio/monitor.py @@ -0,0 +1,28 @@ +from migen.fhdl.std import * +from migen.bank.description import * +from migen.genlib.cdc import BusSynchronizer + +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.probe_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.comb += self.probe_value.status.eq( + Array(chan_probes_sys)[self.chan_sel.storage]) diff --git a/artiq/gateware/rtio/phy/ttl_simple.py b/artiq/gateware/rtio/phy/ttl_simple.py index ce6f953cb..747f7ca9e 100644 --- a/artiq/gateware/rtio/phy/ttl_simple.py +++ b/artiq/gateware/rtio/phy/ttl_simple.py @@ -7,6 +7,7 @@ from artiq.gateware.rtio import rtlink class Output(Module): def __init__(self, pad): self.rtlink = rtlink.Interface(rtlink.OInterface(1)) + self.probes = [pad] # # # @@ -18,6 +19,7 @@ class Inout(Module): self.rtlink = rtlink.Interface( rtlink.OInterface(2, 2), rtlink.IInterface(1)) + self.probes = [] # # # @@ -43,3 +45,5 @@ class Inout(Module): ), self.rtlink.i.data.eq(i) ] + + self.probes += [i, ts.oe] diff --git a/soc/runtime/session.c b/soc/runtime/session.c index 9e9ee2726..d8852f533 100644 --- a/soc/runtime/session.c +++ b/soc/runtime/session.c @@ -154,7 +154,7 @@ static int process_input(void) submit_output(9); break; } - rtiocrg_clock_sel_write(buffer_in[9]); + rtio_crg_clock_sel_write(buffer_in[9]); buffer_out[8] = REMOTEMSG_TYPE_CLOCK_SWITCH_COMPLETED; submit_output(9); break; diff --git a/soc/runtime/test_mode.c b/soc/runtime/test_mode.c index f2bb36970..fbdc0ce52 100644 --- a/soc/runtime/test_mode.c +++ b/soc/runtime/test_mode.c @@ -47,7 +47,7 @@ static void clksrc(char *value) return; } - rtiocrg_clock_sel_write(value2); + rtio_crg_clock_sel_write(value2); } static void ttloe(char *n, char *value) diff --git a/soc/targets/artiq_kc705.py b/soc/targets/artiq_kc705.py index b42f445c7..5d0cefa68 100644 --- a/soc/targets/artiq_kc705.py +++ b/soc/targets/artiq_kc705.py @@ -35,8 +35,9 @@ class _RTIOCRG(Module, AutoCSR): class NIST_QC1(MiniSoC, AMPSoC): csr_map = { "rtio": None, # mapped on Wishbone instead - "rtiocrg": 13, - "kernel_cpu": 14 + "rtio_crg": 13, + "kernel_cpu": 14, + "rtio_mon": 15 } csr_map.update(MiniSoC.csr_map) mem_map = { @@ -65,15 +66,16 @@ class NIST_QC1(MiniSoC, AMPSoC): for i in range(2): phy = ttl_simple.Inout(platform.request("pmt", i)) self.submodules += phy - rtio_channels.append(rtio.Channel(phy.rtlink, ififo_depth=512)) + rtio_channels.append(rtio.Channel(phy.rtlink, phy.probes, + ififo_depth=512)) for i in range(16): phy = ttl_simple.Output(platform.request("ttl", i)) self.submodules += phy - rtio_channels.append(rtio.Channel(phy.rtlink)) + rtio_channels.append(rtio.Channel(phy.rtlink, phy.probes)) phy = ttl_simple.Output(platform.request("user_led", 2)) self.submodules += phy - rtio_channels.append(rtio.Channel(phy.rtlink)) + rtio_channels.append(rtio.Channel(phy.rtlink, phy.probes)) self.add_constant("RTIO_DDS_CHANNEL", len(rtio_channels)) self.submodules.dds = RenameClockDomains( @@ -84,11 +86,11 @@ class NIST_QC1(MiniSoC, AMPSoC): rtio_channels.append(rtio.Channel(phy.rtlink, ififo_depth=4)) # RTIO core - self.submodules.rtiocrg = _RTIOCRG(platform, self.crg.pll_sys) + self.submodules.rtio_crg = _RTIOCRG(platform, self.crg.pll_sys) self.submodules.rtio = rtio.RTIO(rtio_channels, clk_freq=125000000) self.add_constant("RTIO_FINE_TS_WIDTH", self.rtio.fine_ts_width) - + self.submodules.rtio_mon = rtio.Monitor(rtio_channels) if isinstance(platform.toolchain, XilinxVivadoToolchain): platform.add_platform_command(""" diff --git a/soc/targets/artiq_pipistrello.py b/soc/targets/artiq_pipistrello.py index 29259d529..532ec0f85 100644 --- a/soc/targets/artiq_pipistrello.py +++ b/soc/targets/artiq_pipistrello.py @@ -56,8 +56,9 @@ TIMESPEC "TSfix_ise6" = FROM "GRPint_clk" TO "GRPext_clk" TIG; class NIST_QC1(BaseSoC, AMPSoC): csr_map = { "rtio": None, # mapped on Wishbone instead - "rtiocrg": 13, - "kernel_cpu": 14 + "rtio_crg": 13, + "kernel_cpu": 14, + "rtio_mon": 15 } csr_map.update(BaseSoC.csr_map) mem_map = { @@ -90,25 +91,26 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd for i in range(2): phy = ttl_simple.Inout(platform.request("pmt", i)) self.submodules += phy - rtio_channels.append(rtio.Channel(phy.rtlink, ififo_depth=512)) + rtio_channels.append(rtio.Channel(phy.rtlink, phy.probes, + ififo_depth=512)) phy = ttl_simple.Inout(platform.request("xtrig", 0)) self.submodules += phy - rtio_channels.append(rtio.Channel(phy.rtlink)) + rtio_channels.append(rtio.Channel(phy.rtlink, phy.probes)) for i in range(16): phy = ttl_simple.Output(platform.request("ttl", i)) self.submodules += phy - rtio_channels.append(rtio.Channel(phy.rtlink)) + rtio_channels.append(rtio.Channel(phy.rtlink, phy.probes)) phy = ttl_simple.Output(platform.request("ext_led", 0)) self.submodules += phy - rtio_channels.append(rtio.Channel(phy.rtlink)) + rtio_channels.append(rtio.Channel(phy.rtlink, phy.probes)) for i in range(2, 5): phy = ttl_simple.Output(platform.request("user_led", i)) self.submodules += phy - rtio_channels.append(rtio.Channel(phy.rtlink)) + rtio_channels.append(rtio.Channel(phy.rtlink, phy.probes)) self.add_constant("RTIO_DDS_CHANNEL", len(rtio_channels)) self.submodules.dds = RenameClockDomains( @@ -119,10 +121,11 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd rtio_channels.append(rtio.Channel(phy.rtlink, ififo_depth=4)) # RTIO core - self.submodules.rtiocrg = _RTIOCRG(platform) + self.submodules.rtio_crg = _RTIOCRG(platform) self.submodules.rtio = rtio.RTIO(rtio_channels, clk_freq=125000000) self.add_constant("RTIO_FINE_TS_WIDTH", self.rtio.fine_ts_width) + self.submodules.rtio_mon = rtio.Monitor(rtio_channels) # CPU connections rtio_csrs = self.rtio.get_csrs() From 140239fee4eecd20cb65610961c57cbb8d13b4ff Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Wed, 3 Jun 2015 00:30:59 +0200 Subject: [PATCH 019/227] pxi6733: allow to set the sampling frequency --- artiq/devices/pxi6733/driver.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/artiq/devices/pxi6733/driver.py b/artiq/devices/pxi6733/driver.py index f4ffd4f52..b18030a62 100644 --- a/artiq/devices/pxi6733/driver.py +++ b/artiq/devices/pxi6733/driver.py @@ -39,7 +39,7 @@ class DAQmx: return False return True - def load_sample_values(self, values): + def load_sample_values(self, sampling_freq, values): """Load sample values into PXI 6733 device. This loads sample values into the PXI 6733 device and then @@ -49,6 +49,7 @@ class DAQmx: A callback is registered to clear the task (deallocate resources) when the task has completed. + :param sampling_freq: The sampling frequency in samples per second. :param values: A numpy array of sample values to load in the device. """ @@ -56,7 +57,8 @@ class DAQmx: t.CreateAOVoltageChan(self.device+b"/"+self.analog_output, b"", min(values), max(values), self.daq.DAQmx_Val_Volts, None) - t.CfgSampClkTiming(self.clock, 1000.0, self.daq.DAQmx_Val_Rising, + t.CfgSampClkTiming(self.clock, sampling_freq, + self.daq.DAQmx_Val_Rising, self.daq.DAQmx_Val_FiniteSamps, len(values)) num_samps_written = self.daq.int32() values = np.require(values, dtype=float, From c7953da7e1cde7492cecc643da0b0f3edc1601a6 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Fri, 29 May 2015 17:17:10 +0200 Subject: [PATCH 020/227] test: add unittest for sync_struct --- artiq/test/sync_struct.py | 62 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 artiq/test/sync_struct.py diff --git a/artiq/test/sync_struct.py b/artiq/test/sync_struct.py new file mode 100644 index 000000000..6b324d345 --- /dev/null +++ b/artiq/test/sync_struct.py @@ -0,0 +1,62 @@ +import unittest +import asyncio + +from artiq.protocols import sync_struct + +test_address = "::1" +test_port = 7777 + + +@asyncio.coroutine +def write_test_data(test_dict): + for i in range(10): + test_dict[str(i)] = i + 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 RPCCase(unittest.TestCase): + def init_test_dict(self, init): + self.test_dict = init + return init + + @asyncio.coroutine + def do_recv(self): + while not hasattr(self, "test_dict")\ + or "Finished" not in self.test_dict.keys(): + yield from asyncio.sleep(0.5) + + def test_recv(self): + self.loop = loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + 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) + loop.run_until_complete(self.subscriber.connect(test_address, + test_port)) + loop.run_until_complete(self.do_recv()) + self.assertEqual(self.test_dict, test_vector) + + def tearDown(self): + self.loop.run_until_complete(self.subscriber.close()) + self.loop.run_until_complete(self.publisher.stop()) + self.loop.close() From e5f16b29fd8092343c45e2d21fd5c03065a44557 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Wed, 3 Jun 2015 10:46:09 +0200 Subject: [PATCH 021/227] sync_struct: fix test case name --- artiq/test/sync_struct.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/test/sync_struct.py b/artiq/test/sync_struct.py index 6b324d345..e4e6003a1 100644 --- a/artiq/test/sync_struct.py +++ b/artiq/test/sync_struct.py @@ -24,7 +24,7 @@ def start_server(publisher_future, test_dict_future): test_dict_future.set_result(test_dict) -class RPCCase(unittest.TestCase): +class SyncStructCase(unittest.TestCase): def init_test_dict(self, init): self.test_dict = init return init From a2ae5e470689dd12c133b9e8c20436e0d1b66fcb Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Wed, 3 Jun 2015 18:26:19 +0800 Subject: [PATCH 022/227] runtime: report TTL status over UDP --- artiq/gateware/rtio/__init__.py | 2 +- artiq/gateware/rtio/{monitor.py => moninj.py} | 1 + soc/runtime/Makefile | 2 +- soc/runtime/liblwip/lwipopts.h | 4 +- soc/runtime/main.c | 2 + soc/runtime/moninj.c | 83 +++++++++++++++++++ soc/runtime/moninj.h | 6 ++ soc/targets/artiq_kc705.py | 1 + soc/targets/artiq_pipistrello.py | 1 + 9 files changed, 98 insertions(+), 4 deletions(-) rename artiq/gateware/rtio/{monitor.py => moninj.py} (99%) create mode 100644 soc/runtime/moninj.c create mode 100644 soc/runtime/moninj.h diff --git a/artiq/gateware/rtio/__init__.py b/artiq/gateware/rtio/__init__.py index 6e6cc3550..2ca9db8aa 100644 --- a/artiq/gateware/rtio/__init__.py +++ b/artiq/gateware/rtio/__init__.py @@ -1,2 +1,2 @@ from artiq.gateware.rtio.core import Channel, RTIO -from artiq.gateware.rtio.monitor import Monitor +from artiq.gateware.rtio.moninj import Monitor diff --git a/artiq/gateware/rtio/monitor.py b/artiq/gateware/rtio/moninj.py similarity index 99% rename from artiq/gateware/rtio/monitor.py rename to artiq/gateware/rtio/moninj.py index 7b1fd8358..03494c2a1 100644 --- a/artiq/gateware/rtio/monitor.py +++ b/artiq/gateware/rtio/moninj.py @@ -2,6 +2,7 @@ from migen.fhdl.std import * from migen.bank.description import * from migen.genlib.cdc import BusSynchronizer + class Monitor(Module, AutoCSR): def __init__(self, channels): chan_probes = [c.probes for c in channels] diff --git a/soc/runtime/Makefile b/soc/runtime/Makefile index 3cf2708dc..241448f37 100644 --- a/soc/runtime/Makefile +++ b/soc/runtime/Makefile @@ -1,6 +1,6 @@ include $(MSCDIR)/software/common.mak -OBJECTS := isr.o flash_storage.o clock.o elf_loader.o services.o session.o log.o test_mode.o kloader.o bridge_ctl.o mailbox.o ksupport_data.o kserver.o main.o +OBJECTS := isr.o flash_storage.o clock.o elf_loader.o services.o session.o log.o test_mode.o kloader.o bridge_ctl.o mailbox.o ksupport_data.o kserver.o moninj.o main.o OBJECTS_KSUPPORT := ksupport.o exception_jmp.o exceptions.o mailbox.o bridge.o rtio.o ttl.o dds.o CFLAGS += -Ilwip/src/include -Iliblwip diff --git a/soc/runtime/liblwip/lwipopts.h b/soc/runtime/liblwip/lwipopts.h index 14c2c2677..eff5a4aae 100644 --- a/soc/runtime/liblwip/lwipopts.h +++ b/soc/runtime/liblwip/lwipopts.h @@ -66,7 +66,7 @@ a lot of data that needs to be copied, this should be set high. */ #define MEMP_NUM_PBUF 64 /* MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One per active UDP "connection". */ -#define MEMP_NUM_UDP_PCB 1 +#define MEMP_NUM_UDP_PCB 2 /* MEMP_NUM_TCP_PCB: the number of simulatenously active TCP connections. */ #define MEMP_NUM_TCP_PCB 8 @@ -159,7 +159,7 @@ a lot of data that needs to be copied, this should be set high. */ #define DHCP_DOES_ARP_CHECK 0 /* ---------- UDP options ---------- */ -#define LWIP_UDP 0 +#define LWIP_UDP 1 #define UDP_TTL 255 diff --git a/soc/runtime/main.c b/soc/runtime/main.c index 95889fd07..fec357a0f 100644 --- a/soc/runtime/main.c +++ b/soc/runtime/main.c @@ -28,6 +28,7 @@ #include "test_mode.h" #include "kserver.h" #include "session.h" +#include "moninj.h" static void common_init(void) { @@ -139,6 +140,7 @@ static void regular_main(void) puts("Accepting sessions on Ethernet."); network_init(); kserver_init(); + moninj_init(); session_end(); while(1) { diff --git a/soc/runtime/moninj.c b/soc/runtime/moninj.c new file mode 100644 index 000000000..c379f97b8 --- /dev/null +++ b/soc/runtime/moninj.c @@ -0,0 +1,83 @@ +#include + +#ifdef CSR_ETHMAC_BASE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "moninj.h" + +enum { + MONINJ_REQ_MONITOR = 1 +}; + +static struct udp_pcb *listen_pcb; + +struct monitor_reply { + long long int ttl_levels; + long long int ttl_oes; +}; + +static void moninj_monitor(const ip_addr_t *addr, u16_t port) +{ + struct monitor_reply reply; + int i; + struct pbuf *reply_p; + + reply.ttl_levels = 0; + reply.ttl_oes = 0; + for(i=0;ipayload, &reply, sizeof(struct monitor_reply)); + udp_sendto(listen_pcb, reply_p, addr, port); + pbuf_free(reply_p); +} + +static void moninj_recv(void *arg, struct udp_pcb *upcb, struct pbuf *req, + const ip_addr_t *addr, u16_t port) +{ + if(req->len >= 1) { + switch(*(char *)req->payload) { + case MONINJ_REQ_MONITOR: + moninj_monitor(addr, port); + break; + default: + break; + } + } + pbuf_free(req); /* beware: addr may point into the req pbuf */ +} + +void moninj_init(void) +{ + listen_pcb = udp_new(); + if(!listen_pcb) { + log("Failed to create UDP listening PCB"); + return; + } + udp_bind(listen_pcb, IP_ADDR_ANY, 3250); + udp_recv(listen_pcb, moninj_recv, NULL); +} + +#endif /* CSR_ETHMAC_BASE */ diff --git a/soc/runtime/moninj.h b/soc/runtime/moninj.h new file mode 100644 index 000000000..1224b3b6e --- /dev/null +++ b/soc/runtime/moninj.h @@ -0,0 +1,6 @@ +#ifndef __MONINJ_H +#define __MONINJ_H + +void moninj_init(void); + +#endif /* __MONINJ_H */ diff --git a/soc/targets/artiq_kc705.py b/soc/targets/artiq_kc705.py index 5d0cefa68..439934cb9 100644 --- a/soc/targets/artiq_kc705.py +++ b/soc/targets/artiq_kc705.py @@ -76,6 +76,7 @@ class NIST_QC1(MiniSoC, AMPSoC): phy = ttl_simple.Output(platform.request("user_led", 2)) self.submodules += phy rtio_channels.append(rtio.Channel(phy.rtlink, phy.probes)) + self.add_constant("RTIO_TTL_COUNT", len(rtio_channels)) self.add_constant("RTIO_DDS_CHANNEL", len(rtio_channels)) self.submodules.dds = RenameClockDomains( diff --git a/soc/targets/artiq_pipistrello.py b/soc/targets/artiq_pipistrello.py index 532ec0f85..e9be3d4b3 100644 --- a/soc/targets/artiq_pipistrello.py +++ b/soc/targets/artiq_pipistrello.py @@ -111,6 +111,7 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd phy = ttl_simple.Output(platform.request("user_led", i)) self.submodules += phy rtio_channels.append(rtio.Channel(phy.rtlink, phy.probes)) + self.add_constant("RTIO_TTL_COUNT", len(rtio_channels)) self.add_constant("RTIO_DDS_CHANNEL", len(rtio_channels)) self.submodules.dds = RenameClockDomains( From 21d88d834535cbbd5d74b81d4d5e94ad019a6361 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Wed, 3 Jun 2015 15:23:34 +0200 Subject: [PATCH 023/227] tests: use a different event loop for each test --- artiq/test/pc_rpc.py | 7 +++++-- artiq/test/scheduler.py | 13 ++++++++++--- artiq/test/sync_struct.py | 11 +++++++---- artiq/test/worker.py | 4 +++- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/artiq/test/pc_rpc.py b/artiq/test/pc_rpc.py index bbd4dde80..c0afcf6f9 100644 --- a/artiq/test/pc_rpc.py +++ b/artiq/test/pc_rpc.py @@ -73,8 +73,10 @@ 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) loop.run_until_complete(self._asyncio_echo()) + loop.close() def test_asyncio_echo(self): self._run_server_and_test(self._loop_asyncio_echo) @@ -96,7 +98,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}) diff --git a/artiq/test/scheduler.py b/artiq/test/scheduler.py index 436f9316a..a70342486 100644 --- a/artiq/test/scheduler.py +++ b/artiq/test/scheduler.py @@ -55,7 +55,12 @@ _handlers = { 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") @@ -70,7 +75,6 @@ class SchedulerCase(unittest.TestCase): done.set() scheduler.notifier.publish = notify - loop = asyncio.get_event_loop() scheduler.start() # Verify that a timed experiment far in the future does not @@ -91,6 +95,7 @@ class SchedulerCase(unittest.TestCase): 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") @@ -113,7 +118,6 @@ class SchedulerCase(unittest.TestCase): done.set() scheduler.notifier.publish = notify - loop = asyncio.get_event_loop() scheduler.start() scheduler.submit("main", expid_bg, -99, None, False) loop.run_until_complete(background_running.wait()) @@ -122,6 +126,7 @@ class SchedulerCase(unittest.TestCase): loop.run_until_complete(scheduler.stop()) def test_flush(self): + loop = self.loop scheduler = Scheduler(0, _handlers) expid = _get_expid("EmptyExperiment") @@ -147,10 +152,12 @@ class SchedulerCase(unittest.TestCase): done.set() scheduler.notifier.publish = notify - loop = asyncio.get_event_loop() 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() diff --git a/artiq/test/sync_struct.py b/artiq/test/sync_struct.py index e4e6003a1..d04b5d127 100644 --- a/artiq/test/sync_struct.py +++ b/artiq/test/sync_struct.py @@ -35,9 +35,12 @@ class SyncStructCase(unittest.TestCase): or "Finished" not in self.test_dict.keys(): yield from asyncio.sleep(0.5) + def setUp(self): + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + def test_recv(self): - self.loop = loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) + loop = self.loop publisher = asyncio.Future() test_dict = asyncio.Future() asyncio.async(start_server(publisher, test_dict)) @@ -55,8 +58,8 @@ class SyncStructCase(unittest.TestCase): test_port)) loop.run_until_complete(self.do_recv()) self.assertEqual(self.test_dict, test_vector) - - def tearDown(self): self.loop.run_until_complete(self.subscriber.close()) self.loop.run_until_complete(self.publisher.stop()) + + def tearDown(self): self.loop.close() diff --git a/artiq/test/worker.py b/artiq/test/worker.py index ecde74104..7f5c7ba0c 100644 --- a/artiq/test/worker.py +++ b/artiq/test/worker.py @@ -40,6 +40,8 @@ def _call_worker(worker, expid): def _run_experiment(experiment): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) expid = { "file": sys.modules[__name__].__file__, "experiment": experiment, @@ -50,8 +52,8 @@ def _run_experiment(experiment): } worker = Worker(handlers) - loop = asyncio.get_event_loop() loop.run_until_complete(_call_worker(worker, expid)) + loop.close() class WatchdogCase(unittest.TestCase): From b8bdce5bd1ff06f95d4d159866defc5c195d0e93 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Wed, 3 Jun 2015 15:40:58 +0200 Subject: [PATCH 024/227] sync_struct test: don't poll, use Event instead --- artiq/test/sync_struct.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/artiq/test/sync_struct.py b/artiq/test/sync_struct.py index d04b5d127..d059727f4 100644 --- a/artiq/test/sync_struct.py +++ b/artiq/test/sync_struct.py @@ -29,11 +29,11 @@ class SyncStructCase(unittest.TestCase): self.test_dict = init return init - @asyncio.coroutine - def do_recv(self): - while not hasattr(self, "test_dict")\ - or "Finished" not in self.test_dict.keys(): - yield from asyncio.sleep(0.5) + def notify(self, mod): + print("mod: {}".format(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() @@ -41,6 +41,7 @@ class SyncStructCase(unittest.TestCase): 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)) @@ -53,10 +54,11 @@ class SyncStructCase(unittest.TestCase): 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.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.do_recv()) + 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()) From b27254ba8039f7469ed5ac5be3292ca3a8a541cf Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Wed, 3 Jun 2015 15:54:50 +0200 Subject: [PATCH 025/227] sync_struct test: test more cases, pep8 fix, remove print --- artiq/test/sync_struct.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/artiq/test/sync_struct.py b/artiq/test/sync_struct.py index d059727f4..a66185ce3 100644 --- a/artiq/test/sync_struct.py +++ b/artiq/test/sync_struct.py @@ -1,5 +1,6 @@ import unittest import asyncio +import numpy as np from artiq.protocols import sync_struct @@ -9,8 +10,14 @@ 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["Finished"] = True @@ -30,9 +37,8 @@ class SyncStructCase(unittest.TestCase): return init def notify(self, mod): - print("mod: {}".format(mod)) if (mod["action"] == "init" and "Finished" in mod["struct"])\ - or (mod["action"] == "setitem" and mod["key"] == "Finished"): + or (mod["action"] == "setitem" and mod["key"] == "Finished"): self.receiving_done.set() def setUp(self): From 0bf3b7a32beb2df766c6d7cbd91f70ae988f615c Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Wed, 3 Jun 2015 15:56:54 +0200 Subject: [PATCH 026/227] conda/setuptools: artiq needs python >= 3.4.3 --- conda/artiq/meta.yaml | 4 ++-- setup.py | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/conda/artiq/meta.yaml b/conda/artiq/meta.yaml index dec979308..9a2a3daec 100644 --- a/conda/artiq/meta.yaml +++ b/conda/artiq/meta.yaml @@ -27,13 +27,13 @@ build: requirements: build: - - python + - python >=3.4.3 - setuptools - numpy - migen - pyelftools run: - - python + - python >=3.4.3 - llvmlite-or1k - scipy - numpy diff --git a/setup.py b/setup.py index ff98ab0ba..b783092fb 100755 --- a/setup.py +++ b/setup.py @@ -1,7 +1,10 @@ #!/usr/bin/env python3 from setuptools import setup, find_packages +import sys +if sys.version_info[:3] < (3, 4, 3): + raise Exception("You need at least Python 3.4.3 to run ARTIQ") requirements = [ "sphinx", "sphinx-argparse", "pyserial", "numpy", "scipy", From 82a2beaa3237905d953dae9c3fb4c5ad88056726 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 4 Jun 2015 10:42:37 +0800 Subject: [PATCH 027/227] style fixes --- artiq/test/sync_struct.py | 6 +++--- doc/manual/installing.rst | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/artiq/test/sync_struct.py b/artiq/test/sync_struct.py index a66185ce3..3509bae23 100644 --- a/artiq/test/sync_struct.py +++ b/artiq/test/sync_struct.py @@ -18,7 +18,7 @@ def write_test_data(test_dict): for key, value in enumerate(test_values): test_dict[key] = value test_dict[1.5] = 1.5 - test_dict["Finished"] = True + test_dict["finished"] = True @asyncio.coroutine @@ -37,8 +37,8 @@ class SyncStructCase(unittest.TestCase): return init def notify(self, mod): - if (mod["action"] == "init" and "Finished" in mod["struct"])\ - or (mod["action"] == "setitem" and mod["key"] == "Finished"): + if (mod["action"] == "init" and "finished" in mod["struct"])\ + or (mod["action"] == "setitem" and mod["key"] == "finished"): self.receiving_done.set() def setUp(self): diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index 79ef78e66..f746e6fba 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -177,7 +177,7 @@ To flash the ``idle`` kernel: * Compile the ``idle`` experiment: The ``idle`` experiment's ``run()`` method must be a kernel: it must be decorated with the ``@kernel`` decorator (see :ref:`next topic ` for more information about kernels). - Moreover, since the core device is not connected to the PC: RPC are forbidden in this ``idle`` experiment. + Since the core device is not connected to the PC, RPCs (calling Python code running on the PC from the kernel) are forbidden in the ``idle`` experiment. :: $ artiq_compile idle.py From d73006652b5d0e64785baf9c73a83348eabfaaaa Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 4 Jun 2015 11:22:03 +0800 Subject: [PATCH 028/227] test/worker: always close even if prepare() raises --- artiq/test/worker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/test/worker.py b/artiq/test/worker.py index 7f5c7ba0c..2bc3a30e3 100644 --- a/artiq/test/worker.py +++ b/artiq/test/worker.py @@ -31,8 +31,8 @@ class WatchdogTimeoutInBuild(Experiment, AutoDB): @asyncio.coroutine def _call_worker(worker, expid): - yield from worker.prepare(0, "main", expid, 0) try: + yield from worker.prepare(0, "main", expid, 0) yield from worker.run() yield from worker.analyze() finally: @@ -58,7 +58,7 @@ def _run_experiment(experiment): class WatchdogCase(unittest.TestCase): def test_watchdog_no_timeout(self): - _run_experiment("WatchdogNoTimeout") + _run_experiment("WatchdogNoTimeout") def test_watchdog_timeout(self): with self.assertRaises(WorkerWatchdogTimeout): From 78f92682772293445af1e3f9e55cbf7f8784b158 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 4 Jun 2015 11:30:34 +0800 Subject: [PATCH 029/227] worker: add note about correct use of close() --- artiq/master/worker.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/artiq/master/worker.py b/artiq/master/worker.py index af7a08990..bb89907f4 100644 --- a/artiq/master/worker.py +++ b/artiq/master/worker.py @@ -75,6 +75,11 @@ class Worker: @asyncio.coroutine def close(self): + """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 + prepare() raises an exception.""" self.closed.set() yield from self.io_lock.acquire() try: From 60bdf74137dff57562595403a0da150822e8bb29 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Thu, 4 Jun 2015 13:40:13 +0200 Subject: [PATCH 030/227] tests: use try/finally to close event loop + wait for process to die after killing it --- artiq/master/worker.py | 4 ++++ artiq/test/pc_rpc.py | 6 ++++-- artiq/test/worker.py | 35 ++++++++++++++++++++++------------- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/artiq/master/worker.py b/artiq/master/worker.py index bb89907f4..30890d485 100644 --- a/artiq/master/worker.py +++ b/artiq/master/worker.py @@ -101,6 +101,8 @@ class Worker: logger.warning("failed to send terminate command to worker" " (RID %d), killing", self.rid, exc_info=True) self.process.kill() + # Wait for process to terminate + yield from self.process.communicate() return try: yield from asyncio_process_wait_timeout(self.process, @@ -108,6 +110,8 @@ class Worker: except asyncio.TimeoutError: logger.warning("worker did not exit (RID %d), killing", self.rid) self.process.kill() + # Wait for process to terminate + yield from self.process.communicate() else: logger.debug("worker exited gracefully (RID %d)", self.rid) finally: diff --git a/artiq/test/pc_rpc.py b/artiq/test/pc_rpc.py index c0afcf6f9..b9e76e19a 100644 --- a/artiq/test/pc_rpc.py +++ b/artiq/test/pc_rpc.py @@ -75,8 +75,10 @@ class RPCCase(unittest.TestCase): def _loop_asyncio_echo(self): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) - loop.run_until_complete(self._asyncio_echo()) - loop.close() + 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) diff --git a/artiq/test/worker.py b/artiq/test/worker.py index 2bc3a30e3..a72547b8b 100644 --- a/artiq/test/worker.py +++ b/artiq/test/worker.py @@ -40,23 +40,29 @@ def _call_worker(worker, expid): def _run_experiment(experiment): - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - expid = { - "file": sys.modules[__name__].__file__, - "experiment": experiment, - "arguments": dict() - } - handlers = { - "init_rt_results": lambda description: None - } + try: + expid = { + "file": sys.modules[__name__].__file__, + "experiment": experiment, + "arguments": dict() + } + handlers = { + "init_rt_results": lambda description: None + } - worker = Worker(handlers) - loop.run_until_complete(_call_worker(worker, expid)) - loop.close() + loop = asyncio.get_event_loop() + worker = Worker(handlers) + loop.run_until_complete(_call_worker(worker, expid)) + finally: + loop.close() 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") @@ -67,3 +73,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() From 50a6da994e8bd931b18721da8c60acfbb13d4685 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Thu, 4 Jun 2015 13:44:07 +0200 Subject: [PATCH 031/227] worker test: do not close the event loop twice --- artiq/test/worker.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/artiq/test/worker.py b/artiq/test/worker.py index a72547b8b..fede84dfd 100644 --- a/artiq/test/worker.py +++ b/artiq/test/worker.py @@ -40,21 +40,18 @@ def _call_worker(worker, expid): def _run_experiment(experiment): - try: - expid = { - "file": sys.modules[__name__].__file__, - "experiment": experiment, - "arguments": dict() - } - handlers = { - "init_rt_results": lambda description: None - } + expid = { + "file": sys.modules[__name__].__file__, + "experiment": experiment, + "arguments": dict() + } + handlers = { + "init_rt_results": lambda description: None + } - loop = asyncio.get_event_loop() - worker = Worker(handlers) - loop.run_until_complete(_call_worker(worker, expid)) - finally: - loop.close() + loop = asyncio.get_event_loop() + worker = Worker(handlers) + loop.run_until_complete(_call_worker(worker, expid)) class WatchdogCase(unittest.TestCase): From c843c353d7c4725e676cebaf2c50084595951369 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 5 Jun 2015 00:05:38 +0800 Subject: [PATCH 032/227] worker: remove useless process wait --- artiq/master/worker.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/artiq/master/worker.py b/artiq/master/worker.py index 30890d485..bb89907f4 100644 --- a/artiq/master/worker.py +++ b/artiq/master/worker.py @@ -101,8 +101,6 @@ class Worker: logger.warning("failed to send terminate command to worker" " (RID %d), killing", self.rid, exc_info=True) self.process.kill() - # Wait for process to terminate - yield from self.process.communicate() return try: yield from asyncio_process_wait_timeout(self.process, @@ -110,8 +108,6 @@ class Worker: except asyncio.TimeoutError: logger.warning("worker did not exit (RID %d), killing", self.rid) self.process.kill() - # Wait for process to terminate - yield from self.process.communicate() else: logger.debug("worker exited gracefully (RID %d)", self.rid) finally: From a6a476593e3c3a7306a74ce942d7497cb7c67497 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 5 Jun 2015 00:37:26 +0800 Subject: [PATCH 033/227] worker: wait for process termination This prevents stray SIGCHLDs from crashing the program e.g. if the asyncio event loop is closed before the process actually terminates. --- artiq/master/worker.py | 5 ++++- artiq/tools.py | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/artiq/master/worker.py b/artiq/master/worker.py index bb89907f4..acdca7908 100644 --- a/artiq/master/worker.py +++ b/artiq/master/worker.py @@ -7,7 +7,8 @@ import time 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__) @@ -101,6 +102,7 @@ class Worker: logger.warning("failed to send terminate command to worker" " (RID %d), 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, @@ -108,6 +110,7 @@ class Worker: except asyncio.TimeoutError: logger.warning("worker did not exit (RID %d), killing", self.rid) self.process.kill() + yield from asyncio_process_wait(self.process) else: logger.debug("worker exited gracefully (RID %d)", self.rid) finally: diff --git a/artiq/tools.py b/artiq/tools.py index a6ee390f8..b7e6e8dc8 100644 --- a/artiq/tools.py +++ b/artiq/tools.py @@ -18,6 +18,7 @@ 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)): @@ -98,6 +99,14 @@ def asyncio_process_wait_timeout(process, timeout): timeout=end_time - time.monotonic()) +@asyncio.coroutine +def asyncio_process_wait(process): + r = True + while r: + f, p = yield from asyncio.wait([process.stdout.read(1024)]) + r = f.pop().result() + + @asyncio.coroutine def asyncio_wait_or_cancel(fs, **kwargs): fs = [asyncio.async(f) for f in fs] From 9f9079589e43687e4b1bd25c472b0250d2f399e8 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 5 Jun 2015 14:52:41 +0800 Subject: [PATCH 034/227] gui: send monitor requests to core device --- artiq/frontend/artiq_gui.py | 29 +++++++++--- artiq/gui/explorer.py | 2 +- artiq/gui/moninj.py | 89 +++++++++++++++++++++++++++++++++++++ artiq/gui/parameters.py | 2 +- artiq/master/scheduler.py | 18 +------- artiq/tools.py | 15 +++++++ 6 files changed, 131 insertions(+), 24 deletions(-) create mode 100644 artiq/gui/moninj.py diff --git a/artiq/frontend/artiq_gui.py b/artiq/frontend/artiq_gui.py index 40bf6658a..65c1a0fee 100755 --- a/artiq/frontend/artiq_gui.py +++ b/artiq/frontend/artiq_gui.py @@ -11,10 +11,12 @@ from pyqtgraph import dockarea from artiq.protocols.file_db import FlatFileDB from artiq.protocols.pc_rpc import AsyncioClient +from artiq.protocols.sync_struct import Subscriber from artiq.gui.explorer import ExplorerDock +from artiq.gui.moninj import MonInjTTLDock, MonInjDDSDock from artiq.gui.parameters import ParametersDock -from artiq.gui.log import LogDock from artiq.gui.schedule import ScheduleDock +from artiq.gui.log import LogDock def get_argparser(): @@ -59,26 +61,41 @@ def main(): win.setWindowTitle("ARTIQ") d_explorer = ExplorerDock(status_bar, schedule_ctl) - area.addDock(d_explorer, "top") 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_ttl = MonInjTTLDock() + loop.run_until_complete(d_ttl.start()) + atexit.register(lambda: loop.run_until_complete(d_ttl.stop())) + d_dds = MonInjDDSDock() + devices_sub = Subscriber("devices", + [d_ttl.init_devices, d_dds.init_devices]) + loop.run_until_complete( + devices_sub.connect(args.server, args.port_notify)) + atexit.register( + lambda: loop.run_until_complete(devices_sub.close())) + + area.addDock(d_dds, "top") + area.addDock(d_ttl, "above", d_dds) + area.addDock(d_explorer, "above", d_ttl) + 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") - 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() + + area.addDock(d_log, "bottom") + area.addDock(d_schedule, "above", d_log) + win.show() loop.run_forever() diff --git a/artiq/gui/explorer.py b/artiq/gui/explorer.py index 3f1e590fd..2e878f507 100644 --- a/artiq/gui/explorer.py +++ b/artiq/gui/explorer.py @@ -23,7 +23,7 @@ class _ExplistModel(DictSyncModel): class ExplorerDock(dockarea.Dock): def __init__(self, status_bar, schedule_ctl): - dockarea.Dock.__init__(self, "Explorer", size=(1100, 400)) + dockarea.Dock.__init__(self, "Explorer", size=(1500, 500)) self.status_bar = status_bar self.schedule_ctl = schedule_ctl diff --git a/artiq/gui/moninj.py b/artiq/gui/moninj.py new file mode 100644 index 000000000..8391b761b --- /dev/null +++ b/artiq/gui/moninj.py @@ -0,0 +1,89 @@ +import asyncio +import logging +import socket +import struct + +from quamash import QtGui +from pyqtgraph import dockarea + +from artiq.tools import TaskObject + + +logger = logging.getLogger(__name__) + + +class _DeviceManager: + def __init__(self, init): + self.comm = None + if "comm" in init: + self.comm = init["comm"] + + def __setitem__(self, k, v): + if k == "comm": + self.comm = v + + def get_core_addr(self): + if self.comm is None: + return None + try: + return self.comm["arguments"]["host"] + except KeyError: + return None + + +class MonInjTTLDock(dockarea.Dock, TaskObject): + def __init__(self): + dockarea.Dock.__init__(self, "TTL", size=(1500, 500)) + self.dm = _DeviceManager(dict()) + self.transport = None + + @asyncio.coroutine + def start(self): + loop = asyncio.get_event_loop() + yield from loop.create_datagram_endpoint(lambda: self, family=socket.AF_INET) + TaskObject.start(self) + + @asyncio.coroutine + def stop(self): + yield from TaskObject.stop(self) + 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): + levels, oe = struct.unpack(">QQ", data) + print("Received:", hex(levels), hex(oe)) + + def error_received(self, exc): + logger.warning("datagram endpoint error") + + def connection_lost(self, exc): + self.transport = None + + @asyncio.coroutine + def _do(self): + while True: + yield from asyncio.sleep(0.2) + 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: + # MONINJ_REQ_MONITOR + self.transport.sendto(b"\x01", (ca, 3250)) + + def init_devices(self, d): + self.dm = _DeviceManager(d) + return self.dm + + +class MonInjDDSDock(dockarea.Dock): + def __init__(self): + dockarea.Dock.__init__(self, "DDS", size=(1500, 500)) + + def init_devices(self, d): + return d diff --git a/artiq/gui/parameters.py b/artiq/gui/parameters.py index c23bbeb6e..873656eff 100644 --- a/artiq/gui/parameters.py +++ b/artiq/gui/parameters.py @@ -26,7 +26,7 @@ class ParametersModel(DictSyncModel): class ParametersDock(dockarea.Dock): def __init__(self): - dockarea.Dock.__init__(self, "Parameters", size=(500, 300)) + dockarea.Dock.__init__(self, "Parameters", size=(400, 300)) self.table = QtGui.QTableView() self.table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) diff --git a/artiq/master/scheduler.py b/artiq/master/scheduler.py index 85815209b..9d989af73 100644 --- a/artiq/master/scheduler.py +++ b/artiq/master/scheduler.py @@ -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, WaitSet +from artiq.tools import (asyncio_wait_or_cancel, asyncio_queue_peek, + TaskObject, WaitSet) from artiq.protocols.sync_struct import Notifier @@ -149,21 +150,6 @@ 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, flush_tracker, delete_cb, pool, outq): self.flush_tracker = flush_tracker diff --git a/artiq/tools.py b/artiq/tools.py index b7e6e8dc8..c7ddcab48 100644 --- a/artiq/tools.py +++ b/artiq/tools.py @@ -130,6 +130,21 @@ def asyncio_queue_peek(q): 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() From 14cf244c0a9465e60440be15584d0ab908b64bcd Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 5 Jun 2015 19:11:41 +0800 Subject: [PATCH 035/227] gui: display status of TTL channels --- artiq/gui/moninj.py | 102 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 90 insertions(+), 12 deletions(-) diff --git a/artiq/gui/moninj.py b/artiq/gui/moninj.py index 8391b761b..dfc7b1632 100644 --- a/artiq/gui/moninj.py +++ b/artiq/gui/moninj.py @@ -2,8 +2,9 @@ import asyncio import logging import socket import struct +from operator import itemgetter -from quamash import QtGui +from quamash import QtGui, QtCore from pyqtgraph import dockarea from artiq.tools import TaskObject @@ -12,21 +13,78 @@ from artiq.tools import TaskObject logger = logging.getLogger(__name__) +class _TTLWidget(QtGui.QFrame): + def __init__(self, force_out, name): + 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._value = QtGui.QLabel() + self._direction.setAlignment(QtCore.Qt.AlignCenter) + self._value.setAlignment(QtCore.Qt.AlignCenter) + self.set_value(0, False, False) + grid.addWidget(self._direction, 2, 1) + grid.addWidget(self._value, 3, 1, 6, 1) + + def set_value(self, value, oe, override): + value = "1" if value else "0" + if override: + value = "" + value + "" + color = " color=\"red\"" + else: + color = "" + self._value.setText("{}".format( + color, value)) + oe = oe or self.force_out + direction = "OUT" if oe else "IN" + self._direction.setText("" + direction + "") + + class _DeviceManager: def __init__(self, init): - self.comm = None - if "comm" in init: - self.comm = init["comm"] + self.ddb = dict() + self.ttl_cb = lambda: None + self.ttl_widgets = dict() + for k, v in init.items(): + self[k] = v def __setitem__(self, k, v): - if k == "comm": - self.comm = 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" and v["module"] == "artiq.coredevice.ttl": + channel = v["arguments"]["channel"] + force_out = v["class"] == "TTLOut" + self.ttl_widgets[channel] = _TTLWidget(force_out, k) + self.ttl_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): - if self.comm is None: - return None try: - return self.comm["arguments"]["host"] + comm = self.ddb["comm"] + while isinstance(comm, str): + comm = self.ddb[comm] + return comm["arguments"]["host"] except KeyError: return None @@ -37,10 +95,16 @@ class MonInjTTLDock(dockarea.Dock, TaskObject): self.dm = _DeviceManager(dict()) self.transport = None + self.grid = QtGui.QGridLayout() + gridw = QtGui.QWidget() + gridw.setLayout(self.grid) + self.addWidget(gridw) + @asyncio.coroutine def start(self): loop = asyncio.get_event_loop() - yield from loop.create_datagram_endpoint(lambda: self, family=socket.AF_INET) + yield from loop.create_datagram_endpoint(lambda: self, + family=socket.AF_INET) TaskObject.start(self) @asyncio.coroutine @@ -54,8 +118,11 @@ class MonInjTTLDock(dockarea.Dock, TaskObject): self.transport = transport def datagram_received(self, data, addr): - levels, oe = struct.unpack(">QQ", data) - print("Received:", hex(levels), hex(oe)) + ttl_levels, ttl_oes = struct.unpack(">QQ", data) + for channel, w in self.dm.ttl_widgets.items(): + w.set_value(ttl_levels & (1 << channel), + ttl_oes & (1 << channel), + False) def error_received(self, exc): logger.warning("datagram endpoint error") @@ -76,8 +143,19 @@ class MonInjTTLDock(dockarea.Dock, TaskObject): # MONINJ_REQ_MONITOR self.transport.sendto(b"\x01", (ca, 3250)) + def layout_ttl_widgets(self): + 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(self.dm.ttl_widgets.items(), + key=itemgetter(0))): + self.grid.addWidget(w, i // 4, i % 4) + def init_devices(self, d): self.dm = _DeviceManager(d) + self.dm.ttl_cb = self.layout_ttl_widgets + self.layout_ttl_widgets() return self.dm From 37c7ea31c36945de24bdc6688ccaf5ef90f14193 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 6 Jun 2015 00:03:30 +0800 Subject: [PATCH 036/227] gui: TTL override support --- artiq/gui/moninj.py | 101 +++++++++++++++++++++++++++++++++---------- artiq/test/worker.py | 1 - soc/runtime/moninj.c | 24 +++++++++- 3 files changed, 101 insertions(+), 25 deletions(-) diff --git a/artiq/gui/moninj.py b/artiq/gui/moninj.py index dfc7b1632..f5c18903c 100644 --- a/artiq/gui/moninj.py +++ b/artiq/gui/moninj.py @@ -13,8 +13,18 @@ from artiq.tools import TaskObject logger = logging.getLogger(__name__) +_mode_enc = { + "exp": 0, + "1": 1, + "0": 2, + "in": 3 +} + + class _TTLWidget(QtGui.QFrame): - def __init__(self, force_out, name): + 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) @@ -29,29 +39,72 @@ class _TTLWidget(QtGui.QFrame): grid.addWidget(label, 1, 1) self._direction = QtGui.QLabel() - self._value = QtGui.QLabel() self._direction.setAlignment(QtCore.Qt.AlignCenter) - self._value.setAlignment(QtCore.Qt.AlignCenter) - self.set_value(0, False, False) grid.addWidget(self._direction, 2, 1) + self._value = QtGui.QLabel() + self._value.setAlignment(QtCore.Qt.AlignCenter) grid.addWidget(self._value, 3, 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_force("exp")) + 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_force("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_force("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_force("in")) + + self.set_value(0, False, False) + + def set_force(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 = "1" if value else "0" + value_s = "1" if value else "0" if override: - value = "" + value + "" + value_s = "" + value_s + "" color = " color=\"red\"" else: color = "" self._value.setText("{}".format( - color, value)) + color, value_s)) oe = oe or self.force_out direction = "OUT" if oe else "IN" self._direction.setText("" + direction + "") + 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 _DeviceManager: - def __init__(self, init): + 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() @@ -68,7 +121,8 @@ class _DeviceManager: if v["type"] == "local" and v["module"] == "artiq.coredevice.ttl": channel = v["arguments"]["channel"] force_out = v["class"] == "TTLOut" - self.ttl_widgets[channel] = _TTLWidget(force_out, k) + self.ttl_widgets[channel] = _TTLWidget( + self.send_to_device, channel, force_out, k) self.ttl_cb() except KeyError: pass @@ -92,7 +146,7 @@ class _DeviceManager: class MonInjTTLDock(dockarea.Dock, TaskObject): def __init__(self): dockarea.Dock.__init__(self, "TTL", size=(1500, 500)) - self.dm = _DeviceManager(dict()) + self.dm = _DeviceManager(self.send_to_device, dict()) self.transport = None self.grid = QtGui.QGridLayout() @@ -118,11 +172,11 @@ class MonInjTTLDock(dockarea.Dock, TaskObject): self.transport = transport def datagram_received(self, data, addr): - ttl_levels, ttl_oes = struct.unpack(">QQ", data) + ttl_levels, ttl_oes, ttl_overrides = struct.unpack(">QQQ", data) for channel, w in self.dm.ttl_widgets.items(): w.set_value(ttl_levels & (1 << channel), ttl_oes & (1 << channel), - False) + ttl_overrides & (1 << channel)) def error_received(self, exc): logger.warning("datagram endpoint error") @@ -130,18 +184,21 @@ class MonInjTTLDock(dockarea.Dock, TaskObject): 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) - 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: - # MONINJ_REQ_MONITOR - self.transport.sendto(b"\x01", (ca, 3250)) + # MONINJ_REQ_MONITOR + self.send_to_device(b"\x01") def layout_ttl_widgets(self): w = self.grid.itemAt(0) @@ -149,11 +206,11 @@ class MonInjTTLDock(dockarea.Dock, TaskObject): self.grid.removeItem(w) w = self.grid.itemAt(0) for i, (_, w) in enumerate(sorted(self.dm.ttl_widgets.items(), - key=itemgetter(0))): + key=itemgetter(0))): self.grid.addWidget(w, i // 4, i % 4) def init_devices(self, d): - self.dm = _DeviceManager(d) + self.dm = _DeviceManager(self.send_to_device, d) self.dm.ttl_cb = self.layout_ttl_widgets self.layout_ttl_widgets() return self.dm diff --git a/artiq/test/worker.py b/artiq/test/worker.py index fede84dfd..7e0e1d3ab 100644 --- a/artiq/test/worker.py +++ b/artiq/test/worker.py @@ -55,7 +55,6 @@ def _run_experiment(experiment): class WatchdogCase(unittest.TestCase): - def setUp(self): self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) diff --git a/soc/runtime/moninj.c b/soc/runtime/moninj.c index c379f97b8..9dc0811aa 100644 --- a/soc/runtime/moninj.c +++ b/soc/runtime/moninj.c @@ -16,7 +16,8 @@ #include "moninj.h" enum { - MONINJ_REQ_MONITOR = 1 + MONINJ_REQ_MONITOR = 1, + MONINJ_REQ_TTLSET = 2 }; static struct udp_pcb *listen_pcb; @@ -24,8 +25,11 @@ static struct udp_pcb *listen_pcb; struct monitor_reply { long long int ttl_levels; long long int ttl_oes; + long long int ttl_overrides; }; +static long long int ttl_overrides; + static void moninj_monitor(const ip_addr_t *addr, u16_t port) { struct monitor_reply reply; @@ -43,6 +47,7 @@ static void moninj_monitor(const ip_addr_t *addr, u16_t port) if(rtio_mon_probe_value_read()) reply.ttl_oes |= 1LL << i; } + reply.ttl_overrides = ttl_overrides; reply_p = pbuf_alloc(PBUF_TRANSPORT, sizeof(struct monitor_reply), PBUF_RAM); if(!reply_p) { @@ -54,14 +59,29 @@ static void moninj_monitor(const ip_addr_t *addr, u16_t port) pbuf_free(reply_p); } +static void moninj_ttlset(int channel, int mode) +{ + if(mode) + ttl_overrides |= (1LL << channel); + else + ttl_overrides &= ~(1LL << channel); +} + static void moninj_recv(void *arg, struct udp_pcb *upcb, struct pbuf *req, const ip_addr_t *addr, u16_t port) { + char *p = (char *)req->payload; + if(req->len >= 1) { - switch(*(char *)req->payload) { + switch(p[0]) { case MONINJ_REQ_MONITOR: moninj_monitor(addr, port); break; + case MONINJ_REQ_TTLSET: + if(req->len < 3) + break; + moninj_ttlset(p[1], p[2]); + break; default: break; } From 26e737f61ffe594552ac2f88471a92f6088806ec Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 6 Jun 2015 00:55:36 +0800 Subject: [PATCH 037/227] style --- artiq/test/sync_struct.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/test/sync_struct.py b/artiq/test/sync_struct.py index 3509bae23..9644becf6 100644 --- a/artiq/test/sync_struct.py +++ b/artiq/test/sync_struct.py @@ -37,8 +37,8 @@ class SyncStructCase(unittest.TestCase): return init def notify(self, mod): - if (mod["action"] == "init" and "finished" in mod["struct"])\ - or (mod["action"] == "setitem" and mod["key"] == "finished"): + if ((mod["action"] == "init" and "finished" in mod["struct"]) + or (mod["action"] == "setitem" and mod["key"] == "finished")): self.receiving_done.set() def setUp(self): From 398940f5ac5975bf687423efaaed2b160642a41f Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 6 Jun 2015 00:55:48 +0800 Subject: [PATCH 038/227] fix doc build --- doc/manual/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/manual/conf.py b/doc/manual/conf.py index e6d0afc43..d550ee3ff 100644 --- a/doc/manual/conf.py +++ b/doc/manual/conf.py @@ -25,7 +25,7 @@ class Mock(MagicMock): return Mock() -mock_modules = ["quamash", "pyqtgraph", "matplotlib"] +mock_modules = ["artiq.gui.moninj", "quamash", "pyqtgraph", "matplotlib"] for module in mock_modules: sys.modules[module] = Mock() @@ -118,7 +118,7 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = 'classic' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the From c251601204f3f7148ff11c8aff87eb7a5d1412b0 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Fri, 5 Jun 2015 20:12:41 +0200 Subject: [PATCH 039/227] pxi6733: refactor, allow multiple channels in one task, cancel any previous task --- artiq/devices/pxi6733/driver.py | 90 +++++++++++++++++++++------- artiq/frontend/pxi6733_controller.py | 13 ++-- 2 files changed, 74 insertions(+), 29 deletions(-) diff --git a/artiq/devices/pxi6733/driver.py b/artiq/devices/pxi6733/driver.py index b18030a62..8b25c0443 100644 --- a/artiq/devices/pxi6733/driver.py +++ b/artiq/devices/pxi6733/driver.py @@ -14,22 +14,38 @@ class DAQmxSim: def ping(self): return True +def string_to_bytes(string, name): + if isinstance(string, str): + string = bytes(string, encoding="ascii") + elif not isinstance(string, bytes): + raise ValueError("{} must be of either str or bytes type".format(name)) + return string class DAQmx: """NI PXI6733 DAQ interface.""" - def __init__(self, device, analog_output, clock): + def __init__(self, channels, clock): + """ + :param channels: List of channels as a string or bytes(), following + the physical channels lists and ranges NI-DAQmx syntax. + + Example: Dev1/ao0, Dev1/ao1:ao3 + :param clock: Clock source terminal as a string or bytes(), following + NI-DAQmx terminal names syntax. + + Example: PFI5 + """ + import PyDAQmx as daq - self.device = device - self.analog_output = analog_output - self.clock = clock - self.tasks = [] + self.channels = string_to_bytes(channels, "channels") + self.clock = string_to_bytes(clock, "clock") + self.task = None self.daq = daq def done_callback_py(self, taskhandle, status, callback_data): - self.daq.DAQmxClearTask(taskhandle) - self.tasks.remove(taskhandle) + if taskhandle == self.task: + self.clear_pending_task() def ping(self): try: @@ -42,43 +58,75 @@ class DAQmx: def load_sample_values(self, sampling_freq, 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. + This loads sample values into the PXI 6733 device. + The device will output samples at each clock rising edge. + The first sample is output at the first clock rising edge. - A callback is registered to clear the task (deallocate resources) - when the task has completed. + When using several channels simultaneously, you must concatenate the + values for the different channels in the ``values`` array. + The sample values for the same channel must be grouped together. + + 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. + + 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 array of sample values to load in the device. """ + self.clear_pending_task() + 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) + + channel_number = self.daq.int32() + t.GetTaskNumChans(byref(channel_number)) + nb_values = len(values) + if nb_values % channel_number.value > 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.value)) + samps_per_channel = nb_values // channel_number + t.CfgSampClkTiming(self.clock, sampling_freq, self.daq.DAQmx_Val_Rising, - self.daq.DAQmx_Val_FiniteSamps, len(values)) + 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) + 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() diff --git a/artiq/frontend/pxi6733_controller.py b/artiq/frontend/pxi6733_controller.py index 755dfa2bd..33948d02d 100755 --- a/artiq/frontend/pxi6733_controller.py +++ b/artiq/frontend/pxi6733_controller.py @@ -11,13 +11,11 @@ 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)." + parser.add_argument("-C", "--channels", default=None, + help="List of channels (e.g. Dev1/ao0, Dev1/ao1:3)." " Omit for simulation mode.") 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)") verbosity_args(parser) return parser @@ -26,12 +24,11 @@ def main(): args = get_argparser().parse_args() init_logger(args) - if args.device is None: + if args.channels is None: 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}, From d66117ed9996bbe0ce99fae924a7496c6f31dc6f Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Mon, 8 Jun 2015 11:54:39 +0200 Subject: [PATCH 040/227] pxi6733: cleanup --- artiq/devices/pxi6733/driver.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/artiq/devices/pxi6733/driver.py b/artiq/devices/pxi6733/driver.py index 8b25c0443..bc28098b9 100644 --- a/artiq/devices/pxi6733/driver.py +++ b/artiq/devices/pxi6733/driver.py @@ -2,6 +2,9 @@ from ctypes import byref, c_ulong import numpy as np +import logging + +logger = logging.getLogger(__name__) class DAQmxSim: @@ -14,12 +17,6 @@ class DAQmxSim: def ping(self): return True -def string_to_bytes(string, name): - if isinstance(string, str): - string = bytes(string, encoding="ascii") - elif not isinstance(string, bytes): - raise ValueError("{} must be of either str or bytes type".format(name)) - return string class DAQmx: """NI PXI6733 DAQ interface.""" @@ -38,13 +35,15 @@ class DAQmx: import PyDAQmx as daq - self.channels = string_to_bytes(channels, "channels") - self.clock = string_to_bytes(clock, "clock") + self.channels = channels.encode() + self.clock = clock.encode() self.task = None self.daq = daq def done_callback_py(self, taskhandle, status, callback_data): - if taskhandle == self.task: + if taskhandle != self.task: + logger.warning("done callback called with unexpected task") + else: self.clear_pending_task() def ping(self): @@ -60,7 +59,7 @@ class DAQmx: This loads sample values into the PXI 6733 device. The device will output samples at each clock rising edge. - The first sample is output at the first clock rising edge. + The device waits for a clock rising edge to output the first sample. When using several channels simultaneously, you must concatenate the values for the different channels in the ``values`` array. @@ -92,7 +91,7 @@ class DAQmx: channel_number = self.daq.int32() t.GetTaskNumChans(byref(channel_number)) nb_values = len(values) - if nb_values % channel_number.value > 0: + if nb_values % channel_number.value: self.daq.DAQmxClearTask(t.taskHandle) raise ValueError("The size of the values array must be a multiple " "of the number of channels ({})" From 6c094b500de30885e47a67d53ee368db01b3b8d2 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Mon, 8 Jun 2015 17:56:44 +0200 Subject: [PATCH 041/227] pxi6733: fix type issue --- artiq/devices/pxi6733/driver.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/artiq/devices/pxi6733/driver.py b/artiq/devices/pxi6733/driver.py index bc28098b9..804535c6b 100644 --- a/artiq/devices/pxi6733/driver.py +++ b/artiq/devices/pxi6733/driver.py @@ -88,15 +88,15 @@ class DAQmx: min(values), max(values), self.daq.DAQmx_Val_Volts, None) - channel_number = self.daq.int32() - t.GetTaskNumChans(byref(channel_number)) + channel_number = (c_ulong*1)() + t.GetTaskNumChans(channel_number) nb_values = len(values) - if nb_values % channel_number.value: + 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.value)) - samps_per_channel = nb_values // channel_number + .format(channel_number[0])) + samps_per_channel = nb_values // channel_number[0] t.CfgSampClkTiming(self.clock, sampling_freq, self.daq.DAQmx_Val_Rising, From b2af0f6cc352faa3509793dc644ccf6751f28f40 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Tue, 9 Jun 2015 19:51:02 +0800 Subject: [PATCH 042/227] soc,runtime: support TTL override --- artiq/gateware/rtio/__init__.py | 2 +- artiq/gateware/rtio/core.py | 10 +++- artiq/gateware/rtio/moninj.py | 43 +++++++++++++++-- artiq/gateware/rtio/phy/ttl_simple.py | 36 ++++++++++++-- soc/runtime/moninj.c | 67 ++++++++++++++++++++++----- soc/targets/artiq_kc705.py | 13 +++--- soc/targets/artiq_pipistrello.py | 15 +++--- 7 files changed, 150 insertions(+), 36 deletions(-) diff --git a/artiq/gateware/rtio/__init__.py b/artiq/gateware/rtio/__init__.py index 2ca9db8aa..b02ac0d3a 100644 --- a/artiq/gateware/rtio/__init__.py +++ b/artiq/gateware/rtio/__init__.py @@ -1,2 +1,2 @@ from artiq.gateware.rtio.core import Channel, RTIO -from artiq.gateware.rtio.moninj import Monitor +from artiq.gateware.rtio.moninj import MonInj diff --git a/artiq/gateware/rtio/core.py b/artiq/gateware/rtio/core.py index b9d02246a..9aad47127 100644 --- a/artiq/gateware/rtio/core.py +++ b/artiq/gateware/rtio/core.py @@ -247,12 +247,20 @@ class _InputManager(Module): class Channel: - def __init__(self, interface, probes=[], 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, diff --git a/artiq/gateware/rtio/moninj.py b/artiq/gateware/rtio/moninj.py index 03494c2a1..a09422945 100644 --- a/artiq/gateware/rtio/moninj.py +++ b/artiq/gateware/rtio/moninj.py @@ -1,6 +1,6 @@ from migen.fhdl.std import * from migen.bank.description import * -from migen.genlib.cdc import BusSynchronizer +from migen.genlib.cdc import BusSynchronizer, MultiReg class Monitor(Module, AutoCSR): @@ -11,7 +11,7 @@ class Monitor(Module, AutoCSR): 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.probe_value = CSRStatus(max_probe_len) + self.value = CSRStatus(max_probe_len) # # # @@ -25,5 +25,42 @@ class Monitor(Module, AutoCSR): 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.comb += self.probe_value.status.eq( + self.comb += 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) diff --git a/artiq/gateware/rtio/phy/ttl_simple.py b/artiq/gateware/rtio/phy/ttl_simple.py index 747f7ca9e..3250a2659 100644 --- a/artiq/gateware/rtio/phy/ttl_simple.py +++ b/artiq/gateware/rtio/phy/ttl_simple.py @@ -8,10 +8,23 @@ 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): @@ -19,6 +32,10 @@ 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 = [] # # # @@ -27,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)) diff --git a/soc/runtime/moninj.c b/soc/runtime/moninj.c index 9dc0811aa..336c88ce1 100644 --- a/soc/runtime/moninj.c +++ b/soc/runtime/moninj.c @@ -20,6 +20,19 @@ enum { MONINJ_REQ_TTLSET = 2 }; +enum { + MONINJ_TTL_MODE_EXP = 0, + MONINJ_TTL_MODE_1 = 1, + MONINJ_TTL_MODE_0 = 2, + MONINJ_TTL_MODE_IN = 3 +}; + +enum { + MONINJ_TTL_OVERRIDE_ENABLE = 0, + MONINJ_TTL_OVERRIDE_O = 1, + MONINJ_TTL_OVERRIDE_OE = 2 +}; + static struct udp_pcb *listen_pcb; struct monitor_reply { @@ -28,8 +41,6 @@ struct monitor_reply { long long int ttl_overrides; }; -static long long int ttl_overrides; - static void moninj_monitor(const ip_addr_t *addr, u16_t port) { struct monitor_reply reply; @@ -38,16 +49,20 @@ static void moninj_monitor(const ip_addr_t *addr, u16_t port) reply.ttl_levels = 0; reply.ttl_oes = 0; + reply.ttl_overrides = 0; for(i=0;i Date: Tue, 9 Jun 2015 20:05:39 +0800 Subject: [PATCH 043/227] gui: add TTL override label --- artiq/gui/moninj.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/artiq/gui/moninj.py b/artiq/gui/moninj.py index f5c18903c..97ced770c 100644 --- a/artiq/gui/moninj.py +++ b/artiq/gui/moninj.py @@ -41,9 +41,12 @@ class _TTLWidget(QtGui.QFrame): 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, 3, 1, 6, 1) + grid.addWidget(self._value, 4, 1, 6, 1) self._value.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) menu = QtGui.QActionGroup(self._value) @@ -83,8 +86,10 @@ class _TTLWidget(QtGui.QFrame): if override: value_s = "" + value_s + "" color = " color=\"red\"" + self._override.setText("OVERRIDE") else: color = "" + self._override.setText("") self._value.setText("{}".format( color, value_s)) oe = oe or self.force_out From 92999d0cc0057e60114017857930e38e1494a922 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Wed, 10 Jun 2015 00:52:46 +0200 Subject: [PATCH 044/227] sync_struct: test append, insert, delitem and pop actions --- artiq/test/sync_struct.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/artiq/test/sync_struct.py b/artiq/test/sync_struct.py index 9644becf6..d8b229d7e 100644 --- a/artiq/test/sync_struct.py +++ b/artiq/test/sync_struct.py @@ -18,6 +18,15 @@ def write_test_data(test_dict): 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 From f84c51fed5a08c0303b271341ee75a3b2a28daf9 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 11 Jun 2015 15:15:13 +0800 Subject: [PATCH 045/227] gui: do not use broken pyqtgraph addLabel method --- artiq/gui/explorer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/gui/explorer.py b/artiq/gui/explorer.py index 2e878f507..8f2fbd7c8 100644 --- a/artiq/gui/explorer.py +++ b/artiq/gui/explorer.py @@ -47,12 +47,12 @@ class ExplorerDock(dockarea.Dock): self.priority = QtGui.QSpinBox() self.priority.setRange(-99, 99) - grid.addLabel("Priority:", 1, 2) + grid.addWidget(QtGui.QLabel("Priority:"), 1, 2) grid.addWidget(self.priority, 1, 3) self.pipeline = QtGui.QLineEdit() self.pipeline.insert("main") - grid.addLabel("Pipeline:", 2, 0) + grid.addWidget(QtGui.QLabel("Pipeline:"), 2, 0) grid.addWidget(self.pipeline, 2, 1) self.flush = QtGui.QCheckBox("Flush") From 86fbe3837123c4407e828015566b235b7b27b5e7 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 12 Jun 2015 17:41:04 +0800 Subject: [PATCH 046/227] gui: DDS monitor/injection (WIP) --- artiq/frontend/artiq_gui.py | 23 ++---- artiq/gui/moninj.py | 161 +++++++++++++++++++++++++++--------- 2 files changed, 131 insertions(+), 53 deletions(-) diff --git a/artiq/frontend/artiq_gui.py b/artiq/frontend/artiq_gui.py index 65c1a0fee..9f2b2f48a 100755 --- a/artiq/frontend/artiq_gui.py +++ b/artiq/frontend/artiq_gui.py @@ -11,9 +11,8 @@ from pyqtgraph import dockarea from artiq.protocols.file_db import FlatFileDB from artiq.protocols.pc_rpc import AsyncioClient -from artiq.protocols.sync_struct import Subscriber from artiq.gui.explorer import ExplorerDock -from artiq.gui.moninj import MonInjTTLDock, MonInjDDSDock +from artiq.gui.moninj import MonInj from artiq.gui.parameters import ParametersDock from artiq.gui.schedule import ScheduleDock from artiq.gui.log import LogDock @@ -65,20 +64,12 @@ def main(): args.server, args.port_notify)) atexit.register(lambda: loop.run_until_complete(d_explorer.sub_close())) - d_ttl = MonInjTTLDock() - loop.run_until_complete(d_ttl.start()) - atexit.register(lambda: loop.run_until_complete(d_ttl.stop())) - d_dds = MonInjDDSDock() - devices_sub = Subscriber("devices", - [d_ttl.init_devices, d_dds.init_devices]) - loop.run_until_complete( - devices_sub.connect(args.server, args.port_notify)) - atexit.register( - lambda: loop.run_until_complete(devices_sub.close())) - - area.addDock(d_dds, "top") - area.addDock(d_ttl, "above", d_dds) - area.addDock(d_explorer, "above", d_ttl) + 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())) + area.addDock(d_ttl_dds.dds_dock, "top") + area.addDock(d_ttl_dds.ttl_dock, "above", d_ttl_dds.dds_dock) + area.addDock(d_explorer, "above", d_ttl_dds.ttl_dock) d_params = ParametersDock() area.addDock(d_params, "right", d_explorer) diff --git a/artiq/gui/moninj.py b/artiq/gui/moninj.py index 97ced770c..9683754d3 100644 --- a/artiq/gui/moninj.py +++ b/artiq/gui/moninj.py @@ -8,6 +8,7 @@ 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__) @@ -55,27 +56,30 @@ class _TTLWidget(QtGui.QFrame): self._expctl_action.setCheckable(True) menu.addAction(self._expctl_action) self._value.addAction(self._expctl_action) - self._expctl_action.triggered.connect(lambda: self.set_force("exp")) + 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_force("1")) + 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_force("0")) + 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_force("in")) + self._forcein_action.triggered.connect(lambda: self.set_mode("in")) self.set_value(0, False, False) - def set_force(self, mode): + def set_mode(self, mode): data = struct.pack("bbb", 2, # MONINJ_REQ_TTLSET self.channel, _mode_enc[mode]) @@ -107,12 +111,75 @@ class _TTLWidget(QtGui.QFrame): self._expctl_action.setChecked(True) +class _DDSWidget(QtGui.QFrame): + def __init__(self, send_to_device, channel, name): + self.send_to_device = send_to_device + self.channel = channel + 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._override = QtGui.QLabel() + self._override.setAlignment(QtCore.Qt.AlignCenter) + grid.addWidget(self._override, 2, 1) + + self._value = QtGui.QLabel() + self._value.setAlignment(QtCore.Qt.AlignCenter) + grid.addWidget(self._value, 3, 1, 6, 1) + + self._value.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) + self._override_action = QtGui.QAction("Override", self._value) + self._override_action.setCheckable(True) + self._value.addAction(self._override_action) + self._override_action.triggered.connect(self._override_clicked) + + self.set_value(0.0, False) + + def _override_clicked(self): + override_en = self._override_action.isChecked() + if override_en: + frequency, ok = QtGui.QInputDialog.getDouble( + None, "DDS override", + "Frequency in MHz for {}:".format(self.name), + value=self._frequency, min=0, decimals=3) + self._override_action.setChecked(ok) + if ok: + print("override set to", frequency) + else: + print("override disabled") + + def set_value(self, frequency, override): + self._frequency = frequency + self._override_action.setChecked(override) + value_s = "{:.3f} MHz".format(frequency) + if override: + value_s = "" + value_s + "" + color = " color=\"red\"" + self._override.setText("OVERRIDE") + else: + color = "" + self._override.setText("") + self._value.setText("{}" + .format(color, value_s)) + + 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 @@ -123,12 +190,19 @@ class _DeviceManager: if not isinstance(v, dict): return try: - if v["type"] == "local" and 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["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"] == "DDS"): + channel = v["arguments"]["channel"] + self.dds_widgets[channel] = _DDSWidget( + self.send_to_device, channel, k) + self.dds_cb() except KeyError: pass @@ -148,27 +222,53 @@ class _DeviceManager: return None -class MonInjTTLDock(dockarea.Dock, TaskObject): - def __init__(self): - dockarea.Dock.__init__(self, "TTL", size=(1500, 500)) - self.dm = _DeviceManager(self.send_to_device, dict()) - self.transport = 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): + def start(self, server, port): loop = asyncio.get_event_loop() yield from loop.create_datagram_endpoint(lambda: self, family=socket.AF_INET) - TaskObject.start(self) + 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 @@ -205,25 +305,12 @@ class MonInjTTLDock(dockarea.Dock, TaskObject): # MONINJ_REQ_MONITOR self.send_to_device(b"\x01") - def layout_ttl_widgets(self): - 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(self.dm.ttl_widgets.items(), - key=itemgetter(0))): - self.grid.addWidget(w, i // 4, i % 4) - def init_devices(self, d): self.dm = _DeviceManager(self.send_to_device, d) - self.dm.ttl_cb = self.layout_ttl_widgets - self.layout_ttl_widgets() + 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 - - -class MonInjDDSDock(dockarea.Dock): - def __init__(self): - dockarea.Dock.__init__(self, "DDS", size=(1500, 500)) - - def init_devices(self, d): - return d From 2ed81f75a79e5350f60c49f69b239fe9fcb7795d Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Fri, 12 Jun 2015 18:16:19 +0200 Subject: [PATCH 047/227] conda: remove recipes we dont need anymore since the switch to Qt --- conda/cairo/build.sh | 15 --------------- conda/cairo/meta.yaml | 25 ------------------------- conda/cairoplot3-artiq/meta.yaml | 29 ----------------------------- conda/gbulb-artiq/meta.yaml | 28 ---------------------------- conda/pycairo/bld.bat | 1 - conda/pycairo/build.sh | 3 --- conda/pycairo/meta.yaml | 29 ----------------------------- conda/pygobject/build.sh | 6 ------ conda/pygobject/meta.yaml | 30 ------------------------------ 9 files changed, 166 deletions(-) delete mode 100644 conda/cairo/build.sh delete mode 100644 conda/cairo/meta.yaml delete mode 100644 conda/cairoplot3-artiq/meta.yaml delete mode 100644 conda/gbulb-artiq/meta.yaml delete mode 100644 conda/pycairo/bld.bat delete mode 100755 conda/pycairo/build.sh delete mode 100644 conda/pycairo/meta.yaml delete mode 100755 conda/pygobject/build.sh delete mode 100644 conda/pygobject/meta.yaml diff --git a/conda/cairo/build.sh b/conda/cairo/build.sh deleted file mode 100644 index f390466ca..000000000 --- a/conda/cairo/build.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -export CFLAGS="-I$PREFIX/include -L$PREFIX/lib" -./configure \ ---prefix=$PREFIX \ ---disable-static \ ---enable-warnings \ ---enable-ft \ ---enable-ps \ ---enable-pdf \ ---enable-svg \ ---disable-gtk-doc -make -make install -rm -rf $PREFIX/share -# vim:set ts=8 sw=4 sts=4 tw=78 et: diff --git a/conda/cairo/meta.yaml b/conda/cairo/meta.yaml deleted file mode 100644 index 001dd9c87..000000000 --- a/conda/cairo/meta.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# This conda recipe is based on this one: https://github.com/tpn/trent-conda-recipes/tree/master/cairo - -package: - name: cairo - version: 1.14.0 -source: - fn: cairo-1.14.0.tar.xz - url: http://cairographics.org/releases/cairo-1.14.0.tar.xz -build: - number: 0 -requirements: - build: - - freetype - - libpng - - pixman >=0.30 - - zlib - run: - - freetype - - libpng - - pixman >=0.30 - - zlib -about: - home: http://cairographics.org/ - license: LGPL 2.1 and MPL 1.1 -# vim:set ts=8 sw=2 sts=2 tw=78 et: diff --git a/conda/cairoplot3-artiq/meta.yaml b/conda/cairoplot3-artiq/meta.yaml deleted file mode 100644 index 00a171fa6..000000000 --- a/conda/cairoplot3-artiq/meta.yaml +++ /dev/null @@ -1,29 +0,0 @@ -package: - name: cairoplot3-artiq - version: "3.1.2" - -source: - git_url: https://github.com/m-labs/cairoplot3 - git_tag: master - -build: - number: 0 - script: $PYTHON setup.py install - -requirements: - build: - - python - - setuptools - run: - - python - - pycairo - -test: - imports: - - cairoplot - - series - -about: - home: https://github.com/m-labs/cairoplot3 - license: LGPL - summary: 'Module to easily plot beautiful graphics using PyCairo' diff --git a/conda/gbulb-artiq/meta.yaml b/conda/gbulb-artiq/meta.yaml deleted file mode 100644 index 20baf2e8e..000000000 --- a/conda/gbulb-artiq/meta.yaml +++ /dev/null @@ -1,28 +0,0 @@ -package: - name: gbulb-artiq - version: "0.1" - -source: - git_url: https://github.com/m-labs/gbulb - git_tag: master - -build: - number: 0 - script: $PYTHON setup.py install - -requirements: - build: - - python - - setuptools - run: - - python - - pygobject - -test: - imports: - - gbulb - -about: - home: https://github.com/m-labs/gbulb - license: Apache 2.0 - summary: 'GLib event loop for tulip (PEP 3156)' diff --git a/conda/pycairo/bld.bat b/conda/pycairo/bld.bat deleted file mode 100644 index 39b5e1fee..000000000 --- a/conda/pycairo/bld.bat +++ /dev/null @@ -1 +0,0 @@ -%PYTHON% setup.py install diff --git a/conda/pycairo/build.sh b/conda/pycairo/build.sh deleted file mode 100755 index 8e25a1455..000000000 --- a/conda/pycairo/build.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -$PYTHON setup.py install diff --git a/conda/pycairo/meta.yaml b/conda/pycairo/meta.yaml deleted file mode 100644 index ae7cccea6..000000000 --- a/conda/pycairo/meta.yaml +++ /dev/null @@ -1,29 +0,0 @@ -package: - name: pycairo - version: "1.10.0" - -source: - git_url: git://git.cairographics.org/git/pycairo - git_tag: master - -build: - number: 2 - script: $PYTHON setup.py install - -requirements: - build: - - python - - setuptools - - cairo 1.14.0 # [not win] - run: - - python - - cairo 1.14.0 # [not win] - -test: - imports: - - cairo - -about: - home: http://cairographics.org/pycairo/ - license: LGPL 3 - summary: 'Python 3 bindings for cairo' diff --git a/conda/pygobject/build.sh b/conda/pygobject/build.sh deleted file mode 100755 index 74249fff2..000000000 --- a/conda/pygobject/build.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -TERM=xterm ./autogen.sh -export CFLAGS="-L$PREFIX/lib -I$PREFIX/include" -./configure --prefix=$PREFIX -make -make install diff --git a/conda/pygobject/meta.yaml b/conda/pygobject/meta.yaml deleted file mode 100644 index 4fcc319f0..000000000 --- a/conda/pygobject/meta.yaml +++ /dev/null @@ -1,30 +0,0 @@ -package: - name: pygobject - version: "3.14.0" - -source: - git_url: git://git.gnome.org/pygobject - git_tag: 3.14.0 - -build: - number: 0 - -requirements: - build: - - python - run: - - python - -test: - imports: - - gi - - gi.repository - - gi.repository.GObject - - gi.repository.GLib - - gi.repository.Gio - - gi.repository.Gtk - -about: - home: https://wiki.gnome.org/action/show/Projects/PyGObject?action=show&redirect=PyGObject - license: LGPL 2.1 - summary: 'PyGObject is a Python extension module that gives clean and consistent access to the entire GNOME software platform through the use of GObject Introspection.' From 23116420d758d1ffa98f401b6cca67fbd0175847 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Sun, 14 Jun 2015 20:55:58 +0200 Subject: [PATCH 048/227] manual: add instructions to install from conda --- doc/manual/installing.rst | 83 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index f746e6fba..4e8f2caaa 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -1,8 +1,81 @@ Installing ARTIQ ================ +The preferred way of installing ARTIQ is through the use of the conda package manager. +The conda package contains pre-built binaries that you can directly flash to your board. +But you can also :ref:`install from sources `. + +.. note:: Only the linux-64 conda package contains the FPGA/BIOS/runtime pre-built binaries. + +Installing using conda +---------------------- + +Installing Anaconda or Miniconda +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* You can either install Anaconda (chose Python 3.4) from https://store.continuum.io/cshop/anaconda/ + +* Or install the more minimalistic Miniconda (chose Python3.4) from http://conda.pydata.org/miniconda.html + +After installing either Anaconda or Miniconda, open a new terminal and make sure the following command works:: + + $ conda + +If it prints the help of the ``conda`` command, your install is OK. +If not, then make sure your ``$PATH`` environment variable contains the path to anaconda3/bin (or miniconda3/bin):: + + $ echo $PATH + /home/fallen/miniconda3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games + +If your ``$PATH`` misses reference the miniconda3/bin or anaconda3/bin you can fix this by typing:: + + $ export PATH=$HOME/miniconda3:$PATH + +Installing the host side software +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For this, you need to add our binstar repositories to your conda configuration:: + + $ conda config --add channels fallen + $ conda config --add channels http://conda.binstar.org/fallen/channel/dev + +Then you can install the ARTIQ package, it will pull all the necessary dependencies:: + + $ conda install artiq + Preparing the core device FPGA board ------------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You now need to flash 3 things on the FPGA board: + +* The FPGA bitstream +* The BIOS +* The ARTIQ runtime + +To do that: + +* On Pipistrello board:: + + $ artiq_flash.sh -t pipistrello + +* On KC705 board:: + + $ artiq_flash.sh + +Next step (for KC705) is to flash MAC and IP addresses to the board: + +* Get the :ref:`Flash proxy ` for your board. +* See :ref:`those instructions ` to flash MAC and IP addresses. + +.. _install-from-sources: + +Installing from source +---------------------- + +You can skip this if you already installed from conda. + +Preparing the core device FPGA board +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ These steps are required to generate bitstream (``.bit``) files, build the MiSoC BIOS and ARTIQ runtime, and flash FPGA boards. If the board is already flashed, you may skip those steps and go directly to `Installing the host-side software`. @@ -65,6 +138,8 @@ These steps are required to generate bitstream (``.bit``) files, build the MiSoC .. note:: It is safe to ignore the message "Could NOT find LIBFTD2XX" (libftd2xx is different from libftdi, and is not required). +.. _install-flash-proxy: + * Install the required flash proxy bitstreams: The purpose of the flash proxy bitstream is to give programming software fast JTAG access to the flash connected to the FPGA. @@ -124,6 +199,8 @@ These steps are required to generate bitstream (``.bit``) files, build the MiSoC The communication parameters are 115200 8-N-1. +.. _flash-mac-ip-addr: + * Set the MAC and IP address in the :ref:`core device configuration flash storage `: * You can either set it by generating a flash storage image and then flash it: :: @@ -189,7 +266,7 @@ To flash the ``idle`` kernel: .. note:: You can find more information about how to use the ``artiq_coreconfig`` tool on the :ref:`Utilities ` page. Installing the host-side software ---------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Install LLVM and the llvmlite Python bindings: :: @@ -234,7 +311,7 @@ Ubuntu 14.04 specific instructions This command installs all the required packages: :: - $ sudo apt-get install build-essential autotools-dev file git patch perl xutils-devs python3-pip texinfo flex bison libmpc-dev python3-serial python3-dateutil python3-prettytable python3-setuptools python3-numpy python3-scipy python3-sphinx python3-h5py python3-gi python3-dev python-dev subversion cmake libusb-dev libftdi-dev pkg-config + $ sudo apt-get install build-essential autotools-dev file git patch perl xutils-devs python3-pip texinfo flex bison libmpc-dev python3-serial python3-dateutil python3-prettytable python3-setuptools python3-numpy python3-scipy python3-sphinx python3-h5py python3-dev python-dev subversion cmake libusb-dev libftdi-dev pkg-config Note that ARTIQ requires Python 3.4 or above. From b14ca2ecc9be1d565da9ff2cd6e6baeb869b2293 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Sun, 14 Jun 2015 22:05:06 +0200 Subject: [PATCH 049/227] manual: remove reference to ppro board --- doc/manual/installing.rst | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index 4e8f2caaa..6f7c2e3d8 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -174,10 +174,18 @@ These steps are required to generate bitstream (``.bit``) files, build the MiSoC $ git clone https://github.com/m-labs/artiq $ python3 setup.py develop --user -* Build and flash the bitstream and BIOS by running `from the MiSoC top-level directory`: :: +* Build and flash the bitstream and BIOS by running `from the MiSoC top-level directory`: + :: $ cd ~/artiq-dev/misoc - $ ./make.py -X ~/artiq-dev/artiq/soc -t artiq_ppro all + + * For Pipistrello:: + + $ ./make.py -X ~/artiq-dev/artiq/soc -t artiq_pipistrello all + + * For KC705:: + + $ ./make.py -X ~/artiq-dev/artiq/soc -t artiq_kc705 all * Then, build and flash the ARTIQ runtime: :: From 00c4101f1230db7a3a73bb57b51cc41bc4153f77 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Sun, 14 Jun 2015 22:23:55 +0200 Subject: [PATCH 050/227] manual: install flash proxy before flashing bitstream/bios/runtime --- doc/manual/installing.rst | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index 6f7c2e3d8..aa14a93fd 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -48,23 +48,22 @@ Preparing the core device FPGA board You now need to flash 3 things on the FPGA board: -* The FPGA bitstream -* The BIOS -* The ARTIQ runtime +1. The FPGA bitstream +2. The BIOS +3. The ARTIQ runtime -To do that: +* First, install the :ref:`Flash proxy ` for your board. -* On Pipistrello board:: +* Then, on Pipistrello board:: $ artiq_flash.sh -t pipistrello -* On KC705 board:: +* or on KC705 board:: $ artiq_flash.sh Next step (for KC705) is to flash MAC and IP addresses to the board: -* Get the :ref:`Flash proxy ` for your board. * See :ref:`those instructions ` to flash MAC and IP addresses. .. _install-from-sources: From a5b34beffa8207a88bb0c818093e9131c0429a96 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Sun, 14 Jun 2015 23:27:36 +0200 Subject: [PATCH 051/227] manual: linux-32 conda pkg contains binaries + use only dev channel --- doc/manual/installing.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index aa14a93fd..55e9f9712 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -5,7 +5,7 @@ The preferred way of installing ARTIQ is through the use of the conda package ma The conda package contains pre-built binaries that you can directly flash to your board. But you can also :ref:`install from sources `. -.. note:: Only the linux-64 conda package contains the FPGA/BIOS/runtime pre-built binaries. +.. note:: Only the linux-64 and linux-32 conda packages contain the FPGA/BIOS/runtime pre-built binaries. Installing using conda ---------------------- @@ -34,9 +34,8 @@ If your ``$PATH`` misses reference the miniconda3/bin or anaconda3/bin you can f Installing the host side software ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -For this, you need to add our binstar repositories to your conda configuration:: +For this, you need to add our binstar repository to your conda configuration:: - $ conda config --add channels fallen $ conda config --add channels http://conda.binstar.org/fallen/channel/dev Then you can install the ARTIQ package, it will pull all the necessary dependencies:: From 76e034c91336878bc7dc239540ee48a637aec677 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sun, 14 Jun 2015 22:03:34 -0600 Subject: [PATCH 052/227] protocols: add fire-and-forget RPC --- artiq/protocols/fire_and_forget.py | 45 ++++++++++++++++++++++++++++++ artiq/test/pc_rpc.py | 14 +++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 artiq/protocols/fire_and_forget.py diff --git a/artiq/protocols/fire_and_forget.py b/artiq/protocols/fire_and_forget.py new file mode 100644 index 000000000..556774531 --- /dev/null +++ b/artiq/protocols/fire_and_forget.py @@ -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 diff --git a/artiq/test/pc_rpc.py b/artiq/test/pc_rpc.py index b9e76e19a..1b60d245f 100644 --- a/artiq/test/pc_rpc.py +++ b/artiq/test/pc_rpc.py @@ -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" @@ -84,6 +84,18 @@ class RPCCase(unittest.TestCase): 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) From ea04c981bb2215812dd72ffa4fc08fa1942b0db4 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Mon, 15 Jun 2015 16:15:36 +0200 Subject: [PATCH 053/227] conda: add flterm package --- conda/artiq/meta.yaml | 1 + conda/flterm/build.sh | 5 +++++ conda/flterm/meta.yaml | 12 ++++++++++++ 3 files changed, 18 insertions(+) create mode 100644 conda/flterm/build.sh create mode 100644 conda/flterm/meta.yaml diff --git a/conda/artiq/meta.yaml b/conda/artiq/meta.yaml index 9a2a3daec..97c9a5e51 100644 --- a/conda/artiq/meta.yaml +++ b/conda/artiq/meta.yaml @@ -47,6 +47,7 @@ requirements: - pyelftools - quamash - pyqtgraph + - flterm # [linux] test: imports: diff --git a/conda/flterm/build.sh b/conda/flterm/build.sh new file mode 100644 index 000000000..1121beb65 --- /dev/null +++ b/conda/flterm/build.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +make -C $SRC_DIR/tools flterm +mkdir -p $PREFIX/bin +cp $SRC_DIR/tools/flterm $PREFIX/bin/ diff --git a/conda/flterm/meta.yaml b/conda/flterm/meta.yaml new file mode 100644 index 000000000..16afb47cb --- /dev/null +++ b/conda/flterm/meta.yaml @@ -0,0 +1,12 @@ +package: + name: flterm + version: 0 + +source: + git_url: https://github.com/m-labs/misoc + git_tag: master + +about: + home: https://github.com/m-labs/misoc/blob/master/tools/flterm.c + license: 3-clause BSD + summary: 'Serial terminal to connect to MiSoC uart.' From 56f0a82c541dce5e136f5e2c382d5159131a1db0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Bourdeauducq?= Date: Mon, 15 Jun 2015 17:31:33 -0600 Subject: [PATCH 054/227] conda: install .bit file instead of .bin --- conda/artiq/build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conda/artiq/build.sh b/conda/artiq/build.sh index e6c049b18..ea582b029 100755 --- a/conda/artiq/build.sh +++ b/conda/artiq/build.sh @@ -32,12 +32,12 @@ cd $SRC_DIR/misoc; python make.py -X ../soc -t artiq_pipistrello $MISOC_EXTRA_IS cp soc/runtime/runtime.fbi $BIN_PREFIX/kc705/ cp $SRC_DIR/misoc/software/bios/bios.bin $BIN_PREFIX/kc705/ -cp $SRC_DIR/misoc/build/artiq_kc705-nist_qc1-kc705.bin $BIN_PREFIX/kc705/ +cp $SRC_DIR/misoc/build/artiq_kc705-nist_qc1-kc705.bit $BIN_PREFIX/kc705/ # install Pipistrello binaries cp soc/runtime/runtime.fbi $BIN_PREFIX/pipistrello/ cp $SRC_DIR/misoc/software/bios/bios.bin $BIN_PREFIX/pipistrello/ -cp $SRC_DIR/misoc/build/artiq_pipistrello-nist_qc1-pipistrello.bin $BIN_PREFIX/pipistrello/ +cp $SRC_DIR/misoc/build/artiq_pipistrello-nist_qc1-pipistrello.bit $BIN_PREFIX/pipistrello/ cp artiq/frontend/artiq_flash.sh $PREFIX/bin From 4c8917afbdbd330ecd425c0d22af25438c813545 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Tue, 16 Jun 2015 01:39:30 +0200 Subject: [PATCH 055/227] artiq_flash: use BIT files instead of BIN files --- artiq/frontend/artiq_flash.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/frontend/artiq_flash.sh b/artiq/frontend/artiq_flash.sh index a5738cfe9..b5447b197 100755 --- a/artiq/frontend/artiq_flash.sh +++ b/artiq/frontend/artiq_flash.sh @@ -111,7 +111,7 @@ then 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 @@ -153,7 +153,7 @@ 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" ] From 2bdf5f3ba9fa6ec4750b7d5b7d007026c40d1ec4 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Mon, 15 Jun 2015 17:28:13 +0200 Subject: [PATCH 056/227] conda: embed flash proxies in the artiq package --- conda/artiq/build.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/conda/artiq/build.sh b/conda/artiq/build.sh index ea582b029..fbb85ae12 100755 --- a/conda/artiq/build.sh +++ b/conda/artiq/build.sh @@ -33,11 +33,15 @@ cd $SRC_DIR/misoc; python make.py -X ../soc -t artiq_pipistrello $MISOC_EXTRA_IS cp soc/runtime/runtime.fbi $BIN_PREFIX/kc705/ cp $SRC_DIR/misoc/software/bios/bios.bin $BIN_PREFIX/kc705/ cp $SRC_DIR/misoc/build/artiq_kc705-nist_qc1-kc705.bit $BIN_PREFIX/kc705/ +wget http://sionneau.net/artiq/binaries/kc705/flash_proxy/bscan_spi_kc705.bit +mv bscan_spi_kc705.bit $BIN_PREFIX/kc705/ # install Pipistrello binaries cp soc/runtime/runtime.fbi $BIN_PREFIX/pipistrello/ cp $SRC_DIR/misoc/software/bios/bios.bin $BIN_PREFIX/pipistrello/ cp $SRC_DIR/misoc/build/artiq_pipistrello-nist_qc1-pipistrello.bit $BIN_PREFIX/pipistrello/ +wget http://www.phys.ethz.ch/~robertjo/bscan_spi_lx45_csg324.bit +mv bscan_spi_lx45_csg324.bit $BIN_PREFIX/pipistrello/ cp artiq/frontend/artiq_flash.sh $PREFIX/bin From a3f0663823326091df4205ae327504d2992bdc2b Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Tue, 16 Jun 2015 02:32:22 +0200 Subject: [PATCH 057/227] manual: flash proxies are installed via conda, don't tell to install them --- doc/manual/installing.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index 55e9f9712..145eb585b 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -51,13 +51,11 @@ You now need to flash 3 things on the FPGA board: 2. The BIOS 3. The ARTIQ runtime -* First, install the :ref:`Flash proxy ` for your board. - -* Then, on Pipistrello board:: +* For the Pipistrello board:: $ artiq_flash.sh -t pipistrello -* or on KC705 board:: +* For the KC705 board:: $ artiq_flash.sh From 95ff21d829d432a0487fa5b25c38f6d4a6f79d19 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Mon, 1 Jun 2015 18:20:22 +0200 Subject: [PATCH 058/227] gui: add ARTIQ icon --- artiq/frontend/artiq_gui.py | 4 ++++ artiq/gui/icon.png | Bin 0 -> 13730 bytes setup.py | 2 ++ 3 files changed, 6 insertions(+) create mode 100644 artiq/gui/icon.png diff --git a/artiq/frontend/artiq_gui.py b/artiq/frontend/artiq_gui.py index 9f2b2f48a..a6278a28c 100755 --- a/artiq/frontend/artiq_gui.py +++ b/artiq/frontend/artiq_gui.py @@ -8,6 +8,7 @@ import atexit # it has chosen. from quamash import QEventLoop, QtGui from pyqtgraph import dockarea +import os from artiq.protocols.file_db import FlatFileDB from artiq.protocols.pc_rpc import AsyncioClient @@ -17,6 +18,8 @@ from artiq.gui.parameters import ParametersDock 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(): parser = argparse.ArgumentParser(description="ARTIQ GUI client") @@ -51,6 +54,7 @@ def main(): atexit.register(lambda: schedule_ctl.close_rpc()) win = QtGui.QMainWindow() + win.setWindowIcon(QtGui.QIcon(os.path.join(data_dir, "icon.png"))) area = dockarea.DockArea() win.setCentralWidget(area) status_bar = QtGui.QStatusBar() diff --git a/artiq/gui/icon.png b/artiq/gui/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..33c071886b3da87c51f7cfaad0dde65b4cb82551 GIT binary patch literal 13730 zcmb_@^;Z5y1m7_pWew zAOyDRDvAK?|3*<~dD;Vq*i+TS=V8ap|0Ym@JJI3Fnj*I?kuFye z@hgahLSq|(qMB%TL(S4Hw9kYhnWb!#TXLR#RvuT)2`X?b$j+Zqy(oA;_WFVf6^Tzu z!)M%}6J*Ut!uJhdwTG?*yH9FO4R{2A0-V|uQ-R22Fl#t`RWd;h}n zCyWROs!7$IQXy`kZ2x;*ZzDeGju9R_5PD~Km#+Bi;zn&>H1(}Zf(cw%_qpswVEYX z|6l(8AhwnAKGwp~ijk%*a5Mk7gO-ouK|b;@t%f$@!|HV_-tyK}ELv0+G&idEGcp=u zbRWjEiqzqW0rfR}mA+trS@0S18LPhE2!j%1B_Xx9&SW1!ZJtbMrY|Bid?5I-?6)ZF zDJa_Nv%T1pdrnxj@vd&d-nHS6v1unfp?P~v(n1wcx(M%$KJso!IDMqv(H{1#8)ruCrvBNA+#+GT(FTh~R^P_UipnBGuiv_GDk z8ON{!;p!W2y)m>%7n9iaqcb}Ek-Hu2=#@(#SO=H z^Zb7O7^P}A45JG8V}>RL(3n)=|1Juv$Z=~_u6t>(@sWeXGwBij4{KKXuk=+>*!ve? zupxsdJ-Y5oAPt`Zr%pA@oJBz_`t8h%*csS@a^H2S1Rp;hU=OePm%?T5UYKk+Ie^Hz zRoKCOFt+PU#~5zIq@D=fteqTbXiu0p0Iu7=-`<8<;_w3Iw%Mg2 z+}EOmI2^h$ZaF~mzFEhXCtf{KA4zlt`i6bj!ACv|PX$~9x7j$k0WW}A+TN_;&j4L` zLc}PQzN|6n*BfqQmlP}yY6HLrjI`oCl|)Zd0#{kI5J81+SVIZy(Kswz-lA~@s;cWRtxI5Iq? z8nOn2e+7^bU-1FbfE~Ci@^-`P(^5o=$qWF-NBukSF5%5ZvJsnCYV|dcp|NGNCj0h|I0_Kv)PQi6wv>Jr+XMZ4P5j?MQjMWom?1oN`lra&IIel?-0o ze*^tqo~uL4Z6ht~Zkzg7;A@RwN*<xZf6vDE%;Xn*{TY!d34{&v*fd%1pdLWvs3WLeELkXTIj(<$6pBsa9q z+4(!1oAZ%6qzmHxUmkB{ugF+()4ZDc_Kdn@ni2d4drCPJeK*m{Hn^o(T6$*0WPfM{ zQ+d~K(`SSYnp&71xy*atz~4Qt{S6qu8dGK{B(2cMfUcCekMvpp`#v}P(Bs>tw=uc@ zo;RGQBBS|~(h0ZMH>8n`?>K-oxhYZ4e@y^Z($xH#WlsP3m1a$hASXfedFk*L6WD2o zW15>ox>H0d_B{wI@Q7QiL)n`e=EuW~WWCCw|L}(Vvg%{b*b;R7K?5@JI(zn>x7R-6 zuvp&mGEyujLfw=f2X-?cVU!}x?P8XN<1=rvx;*_uRr0&}EdTn+ufqO8;WZL5q>*Y} zHcbqbHUf$|z{p?PlZW|M12*in0;qy7LK-Rd#`8w%AE9eiyY{~mEwNlBIGf^;TwGL_ zG?Ghp2jy1*4;>$#$~Bv>P!mQ2-cR*Pu$F}tUPm$1*G38N9PL0Y2{-Y#D3PmDfDbK{ zUqXxj)v^7X-x@L3$65MUE1|;WVg+@AhO6%S4e?s;VYV?5(|@tv-)eYNTL(8;Z*8KRTXSRV!x zMb@I?V0r_u^s$o)SL*xGM%aT~i(%BOjxRstnAEpH$&|(#$Fpj8xP!Z`a`{&m!>lrJ zgVrWDR3iw__8Nv63Roh*L%rdGWmf$%hut=eTeEq6LU;yf8DV|ihFVRrJ+{yHH%l`Y z!24@aBJr!xOnpSb!@#EBJIid!%oof&GAevu(71)2+DW!%hbC9MF!&^vFuYCjsd-_a zn{oq=vG`;A)9A^I!5?zvEX%tjt!?hDYO3>sVI z{07PDs9DpXpH5*HXCue#!6`<0HMO1%UpHWXpZZ$(S#}~9Nzx7QmhAq6t6G-aFR^X4 zN+@q$ecZ^nH3qsz3?gX25r@+%_E6_m?gItZQB7EfC~>qIskL4ap08a$lELmAt|^_c zu8|Y4PqLzWR{sn@F4lMZ#wxQ3A^34({GI~v!9YYdu4%i-^4~`1^Kf@S+fX$_xYjW; z=@{`e9D3{=KA|&3st)ACX#k*X14i%;I|DUwg&0u5fD??^AG)R*ibbDNz$8Ws$CD$R z6Ho%|aHE!kLI03WMbfR3^YN(RKX@HOkl})##+8vtpn4b6GKY%Z_(ppGM zRip+5O61IxH{O~poE9MMk(iyOPKs(O?j*E+4q&FUoBqybNonPQGfRQm1Dq3jiiX!> z8aV+-9M5XuGM>lr;h1VbAK-F;u89UM1~|p}#h{gNWy7{oJ+!=W?rF>t373QJNAHGI zc6>o&X$QBckKxSTd%$KxsFInzee1NMBj9s!iW*0M2-%7~pdN6kN`qEHd_M;%&PM0f zRM7TLg+`X!gT&Jg$k2uAD=DN0FxNscWXe|il3tKk+^|#XWQH-@iB-3CLlowf-rQ9% zjBsE_O}kQH!BU}ekHe-~3=P!_99B%Pbi#zzV(!oM+}d8F>tHWK<1s_QmTv_#MG75N z-Sy+JZ2V?`4~FeHC=bPU_qT}wf39&&iBxF;vIFr)9(KdiOpJ9nE8gEp%r9$&H;<{t zsvm~~0R%X7WY+v=yoT;~uK>${OH<3`Kui74!V{5)RxMXZ6xKXA5wAfe{0vaDgK%XE zj|DC`CI*N!4fE&BP6bcY48YBwzZRq3O)!Ud_S0waaS_GUW84#CJLXlJGEP*xYUtAm zBIdmRN`ApuBR#k1cXk)#j^DxxuU6g(phH9%NVL2YbB3R{C`^hF9gqy;D-Anbbf#iH zGff;_xIazfKH6nJ^&6+yROR`2E=YZ8NK}@}C^*Bc?X3d4GpO%N;D6kVI+26*Ux8qq z*$4-oc&{;?r_Xfv)Z|t-0$K|HS(0!uxmbVi*->drQI>2yvq3={KkRdZZwg6jGz>x$ zlp6Iqxxb(`qoI7-As;pWiRff8fe0a^1K!b1VXcdWqO5NQHExYL8-z&j@SdXL>LQyw zh&FyN+CAbUZ7+^rca?<<2gpycaASfQw2y6JK`D$TTF;55MqR&9U!dZx?O@tqdW{dX z<9wPGx|%DpjpLId5mw%28Ain;y?ljbp06s6GaO9jf*>bpULZ%BC#NRkWjDIQcQ*zV zr)?>>mit&U@TUFxA)WU2rM8F-afRU?t@9!6?55AJnifi6sO4mgb8}@Gfs!Kv z$_SQq>=dOH3{g%sibc|+#V3kITT~R+lAwb$_ENEh@eMEHkO;4KE~c>NHFIHso9Z^o zatF0$H(6m-Y$O8>?Z(_v~6Z z9~zD(t$t8n$Hd-7qTC};J@*rl_J#G%539&=b!$PG1sna0QbAdn3THSj{Ag>hW&DkZ zyv(cFe^rW&ZqSi+EoZ~Lb%MONQzwB6EhFe#_Wt^ysu#V2*?j6xpK)`tLAnYIzZ!)l>zwcqJhcyM;6jJWYhDwj%a!cz zZ9`d>M0U4e9X~_Dsb`EY_sbR}j0|na)P9&KE&4sBI-g!^HPe4_90J4(Yg~G4)D1r9 zD%^iI^F%iha`rNh`=WC+-i6QCR}}0qHdJus-uigJq!vx4nnz;}=*S(? z7GBrhKhhpXjW>))rHddx3iU0I8MNMQ5c!d9|zaha& zg4)YdbqWIZ*CE1Ppy4bflQz#%>N(>Yd-%u60E8aE7QdPG>W6KyTUyRH6_&n~;KEfK zdUTfwONwm`uplcUHnLglVqq_H_rjufR#~gM#MfxU{MxuT?i*=b%@a5a59>C+p?~fm z)g`AkekOkt%dO1G0rwS&LBGa-k;6(*Y&qtG_U5Q`ZP$*h@zE@9KtTog(*!kp-7}6i z0ni&M7()dp!FxqiQd?G$ck?!DKUZnfD1Jpz`|3OHix86AA0<*ai@ot~m=^)&)MT}B z#20$tqsa;drq?}T-xw?T!1{+)T!^^bX$|kJ`29ZuT$jw-m5L{Fam)Etbj{@!ckHEc zb@f+63uZ>~#Bc+Lu~^Ch$~HjR<+D(Q<9P}NZP?>9#&yxrF+u;k`7vqi#s zoy*&H>N!=X282viZFX&j0*bsX<2@Z-#d=zFL1*AEoka-92&@ARGxGbF)v>zlh;Ldc zkl*1eWJC+@O8s4wKOY|g^?_#S;Sj##U8mbp-L8&~(i1r%kvw%zNm(ol??DC% z$9$O$Gpf9muE)~@hZt#}t~52OAtWxNsaFST>L9v!?>02SOyw!ZBIxPeoPVWG-^6>* zt=38pIPK9igcSGK)W3|@=2op7qt2gb{CJ;5gOG+A^K*6g)a!6|^keDU%i>-Q(Ma+W ziH-M1Xq1`avAf67Z-85^>TztiSz>L5tjz>{5Ax>TgCGxZNN?H=@Rm?Ga#oB?fbmWktCk7O~T>|luy-#_sGN;9&~ zzfPKX3AR?aDS9i%d3(uTw>rllr4^RV8js3?C{AS1PbdA57rwe~>mLu3fKcpb97>N0 zZxEBte~H0`dOzLsKMqmJIHoeX^GDG5SEr4R_O59y)tpgd)uwdDqi4R2yD~8Q>`MDB zl3J?EMeouKEmaHZ+RTI?4V3O$DwU)lZr9Om&F$CNb0)BxY&9Nqtq_^9(%U|6Ai)Xw z%KUBc`CjLpC`g<*d^YVh=Q|a@ktL2485z|LQ?!Ba>KeVnn%%-nkAj26i$@%3zoPAG zR+@C%q^Rdl1U)~FI`dD$Q!!~wDwGgQcE^jd#GoCC2=Pg5GWaWi>Cq+Q;zqf}BhjWm zhwJq+91{x{?v!1-JytFwlsc{3^=&uT-726UZqjPb6jAfQ8K*4Xn}J4BN8Y!4FBg~x zuXZars8DyM!(p(+(vj(8n+6CiFNAopSxk%#b=EfdXWiXrlEf^joZgBl+TW8{8~fk> znm+hh=<5(uEB>_BJE?lfXUET81vc;LJ{+`O$_Jnhe^E^zvfn#*g~*y-Ur_A@Wh*!G zAj$Ol7TNJ;xXr_!{P@zjRlr_~Tod*F*{)5Jv2jTm{K1hw`%N{ZTQxuCCL_IKpN?7- z%+XNK)mCqOJa$qV45;c~4uRC&>P7o|0X>dvAf{}f8N;(DGlD)${Xuzk?2$;A5(frTe;O3cj5Xv6q#PQ@?aZJKi#U(Zh+)e96xq5@aLRx(CYhbtfb$)q&YzE`KagpWZ;m;Y$k2b8{D5ZA$Gw)~~S~lk_<2bKf70>4f zf`d!6QAV&g%w_Sq)kXQSlqf4&zta!zqt!-2Jhr}j-dnC*{{eH4b+PQ8&oPl+Br}o1 zjiW?oTjI_Q%3A%5`5;mIb}i1%>-uoJ~~6Le|v0=5JE(HA)l|@AApKx7j5}Y zAm^WEO{z0cKJLNMyP~Z{IQR_)YJ*CWOHy-v8YB`i0icVKslkKak?Y^?GDr`020E$E zE?=>;bhm*CIt&d7tI}E{g*+_=ggxk1mfE?(+XLE`!pJUU(G!x0mSb9jVu0%L0#S5W zt70~RpYkXxLd!jBnfBb1o4@|pZU zDo{+h9S1<769-zUd=h=JiqZPHtvi3`TmfvF?(Hj@s+ko42E9 z!}E&0%`VJ6sUIS5Lko$ryi{Wj&-BIi^Q#)RYXti%GJ0Nf6u`;R%MFMgA4EeQ<}*Tl zJN0gPuK?|C1ZmV#+w{{XV#CTuhHV-RB))RQQ;EUYiEMtTeiv{~jG%XsC`E~$)8LCD zak|SDb2dkR!|S=sPewbk#0#448avSTd3Q9C`l68~ON}FD^4>GN$@Z%FmzH%kQ+_`y z{T=b*^SpU~$ry1W-`(#?uO~^q+CbmHJ!fDyLjOVd_rrSs^i|pWBR;ApX6WT zZ6tlDBQnO#->h=~W|LVFLW-}&Fh4|7Du|AteB@7_UnZztUG&XSjo@8*4N)g?#3dA9 zNx1TZu?vWT^ks2?Y!cD!INO9PApi@ut}{x28&CsyNDFuh3?{H{6n2o$Yo%6p!(EXB zh!1XgTXO>7f@^O9;<%SMs7#SUlfJBI8>lDXHKv6=JOU7dv;DY8_izeO#1&3%=hCc5}h!yFc4{aWuyxc_%;c5bjxtrAF}R!+0#QQ6|JqJoS)2;(&y)^_CDA`|zhY*cS^IjKUPf9!27QgGIy^`xe0>oO)mFv z>PTF0{2Anogbgpm`=op4UFQrQv&0R4ul9R6Xt8-7vNF2C@bOx)Qex)Tk4 z|7&$r1FtNSIXr0RG+Z1q-edR37soU2Hh?4iAD{-Oh&_A}xKKvbGtPM2T@sL`xToO2 zj5brc1g;3#4JSY^fq)Wyp%^IOC9Y5|X0ApFCeeiT_B#cN{n$Ie`4jgj zD}VrCOpT&HM%XD|t+YrBHSw_A-r#tu^B3ABA_hpX$r3quTZ~eghm7I*iNfknj~|i6 ztW)g+R*39|`Q;1Dw&@YeF@P9cV?vav=jh#^_%notfE)XAX~dJgL}7%wua`cvg}>x} zh(q7u1aEJ@?m-$OHCN|kWeUUD1!6KF@#&Tu2hUC>CG$~8hQfC-zQJ1aUzPP*FwvSr zIBH}zy$~YEewC2rRRm_lO7(3uf7M^S2>!uWgD|iaP9k~I!-y}PZ$u!gsOfE04KQ8V zLMP&tFL1AauAyPhZ3iJ*{&Dq2t;?{T*57~*g4rSJ47*hJuuP7`<>k%arw0`;{xLuo z%vQ=HF#U#VJdbd-l`_Iczhs0$D!0wicNm|1gD3NaBcBcYdVc##xlIvX-C5o<g8_H15Rb!)ZJ}0dA?qlPasGJudbzeA_xwamV4ALg^5LH0+jG71<^Ne z-qyI8cb}IRUA3Y857;uL91&8nsN zVA9*XoR!}bALX;+F`K^hb?LY_>Hjsj&aHkR&Tk7Vg|2_ba|{ik;+)ud>?LO$aS^cj zl9)IA&CXX|6lrA*OrV|=UCylEbdgnpK3jBKKXnA3^Fxs>PuFUndG9}WsQIXP z8QBwB8Z}1k{luKMt@7@c4#uT^#V>mqt~HbUyu@@TA@Qsr>YZY$3e~TvtTHai+v1)cH!47b@DzMce4a~+ z6ethNt%XA@!3B3yRf;9#GDo;xs2Aw))3qBpzY+dy1Dv%u4zys+Z2emt*))LNh)wg9 z_o9A65xD{!W9%|l$ZT${WWhS<+@`qemk{LlSYnw;qio>P6yftwo91<^3lQEI_7coy03Z|6`NlZ#-v{WZAz9{v_!RqsS*nZ|Gn$oJkreZW(SQX$5PT0Nrws0uPu|H|I` zjR64E?Ek|8q!CN%eUdiJKYL&8HHN*?C{y%X=1%hq?qYx(lfkf$guW<oZ3x#~z{ zHk1?EY?=bd*CLZBn}P^@S-yQGN?Q4@LBQ30uQO|Wv2>ab2)m;+y~9$iX8lR|%VW7C z3h6Rg$u5n|USbb`UAG4fI!(O-O7Rz`;XF5BeO~7yDZ=pvG&AAw*yY<|re35QhaSIs zmZ1}jJpUdqR*hmNRxRMBTOX9nA(>ZtvbFfdbfzpnfPLL7%+BVy#DUf+c_&3=%$c4S)t`&AeymNwh5vf!e+EZb}2 z+xmCBcYe;QmN^w{_^;k-QuO)vNH7;uSzCv`!jfwboLb2rJ1XIIeg4UO8;vQ4aljh} z?qt4Blm+eQi zpiv&qeq2nJjK$FfRv0hY4xI!WYWU2vd=(ncP*8&~LA=9Pnv-Oxv*>)pt)oAJ zahRUKFrz%WI7oHwtMWIB5=$DZ7e(f-g^>Fwt;HYkct$Qwsu-m4s7jc!Hxc^WCiPi{ z-dBAkhxa`_NL5io5A{wv>%pxu4KrqV_6+O*M60Tio z7Chix_KTT7|A2Q~4+L3BSU6VwUhx5jPkj~^t>(K7pRjRc%X@FJOmBA$>!6oQF~v@- zG;`i4NI^KJ#o{pz086bE-G>^(9UIe)ZTF2oKo+;5cz*GRhK3?NUipDp7+`P6C56a_ z|34By=V#$%BmGOIcl|v(2XICdx83lcV{8!MFYV#`4*-cix+d=KBW;3)K%C_31v*h~ z+dM3zmcA~o(~n#e(jrFE?voHJz#fo}Pt7KaJ6`!a-9o1BLD_&YkDw8%VkvHO$$J@? zMP80Imo=zInp@co!wr7`q_`7ypVO9ov+Xpu<2P%HSr4AC-qg9E;`yxYhz+_-iJ zy+Vh~`|pR@DL`a;t!JFayTk#2uV$e}>RdKN>YiI!=PBtvg;1>9gH}5XJGA@5;v=Wxwh#h;DoA{p58yT$xT; z$N`J@Mt0{;1LQS6#OU%|n@$o9; zco{UqZBZFhG{h2tV)L(`&eHovS$lM^_eB zU*X7~R@2#NM@aR2xt6WgLG6VV;|s4XKlmBGqTp@0Q0F0ksgH+35^|dN_cmB6zsSz- z6t*zOWxb^+PEnOu+YjL>n%NPS6n}hHW@YxteT=z(ka^>EJ%oapwt9=4&rd<$CaN^l z9gO}oai7UdS2S9*P@c>^^g_-;quDD&q~44;iQs$KC;3aGPezxjFe?@R<*#13Axslk z5!wDc#w!br<9g7=C+8M@jipSCW&uK0%Xty7M<)mO`59~ciBwJwvyPsW*xvT}2j%(m zV}t_%CHoUD1=C+(Hvo^UW_3hhkhW~Mg^L)DvyKwBjxa3U(Q(+Wq*(>lL%qE04XZIt zQ<$~v%MT^q)ZG#?O)e!hSZc7@e{VD{Q^9?N1Ae75m3N zORs2><3?P{E0UY=sf#%x`5mjFWCu~ewJHMpL8F4rU=tdA6^y?Q#vF|mY5dD+1ic!f z`KHI=X`t9TueW%@3lmQljv>V`F{D0tl5DKnLwkk3*UO)uIy`^&QS8t)s+m!D<RO)+g1 z$@?$rYxL)#Bc)%Jo>Vka%G~Pi@rP?Q#8?}dC}Ykthsx#i(ur4Yx@2S*pSjDXl{9`` zfV}`T*)zh_i8rHqgQ~9`^R5>y@b}?aZ)pM7$*PdX3i zGk_7&{DJW@o?Bl|dxjngL*1>>!KEGRN8aT;OT@t)GPxbX)F`IyJ&g*U-Q=}b~>JZ&4PuZ(z z*=liKm2I{0*nY0FP(a3uCfEnGzv&jfKOh1T6z(+pvfKW-$_k{OIxre>&i z3})Z)6}cYXYCV=x%YFY?JIYHf)Syx$4(EOCZ`^TrU^)qZr;$aw1sPeR&ZkzT=>DYK zp*v1}J^OW?EXoZrj-P7c4dI7{#Z>>?T}OEYx0dP?;+T7Fb+nIHv-l%>H~Jv{>D`vT z3i+MTUvOVG28LPlA2kb@ubjFsGgdqW{hS*)C+Lake#I#s)h#m#KkZQB+I6i}!?U_O zmp~l6@}1A^jf0;l3}09?E6*w4VTrHm7T7pue!l3pQDRKZVB*-PP|_gDA8r>7_WY5h zW!XRHEAo1%1zKMvcNxpwduos~a=*{2Iid!=-YM3A#wl#SKJhMDwX`LBW+`*YV77T% zcyZfRB6aX#;uGoAP@$!@k|pmT_h`UA-Pb11i1vPmD8qQnNtt())SrG6v%2NYrWCvF z-c6lT`^45$Ke;N zj$EYcf-b#~R6`W%B?|~dTx3sxp8w8Z5!m_?Q5bs6kUs@aQ>o0`zZ#Kn`~4{{K&q6$ z3P#4+A8(`QDeHBCfp9w}E6gTh7JG6xP_NdEoVel-hP$ytjAOwjIV_V#Xoi407Q))fUYnNr%a}IM zz=@0&A5*C*G9)Jngk5BO#U)1%eKWfP8R>Lvz4`gaqyltdLAjhajo9gX`mgFm42z)? zQ*%!tNvKIFS?mg-Nw$$iIJSQ4BqR*LJaj)Ic(uu7Hyi=TTDu!b?q#oPtuJ6+$q5iq z21gs>J@~wh$j3OHJEvm99XNFOMGH-{m4GCTP9OH?LL4Zue7+n#TrRwhI?`~z6Kh}; zTFrEbu89S=2g!zSmqpeOcBPiPh69B9f^k;90y5z^4PG=fC_NHu-|UMU!~^D6`Mi3F z+N+v6?2ac~?>_b%qV&^*yA9t{wg7CltoN(9)|`eqeEIi;EkHfE3;7@2iDJxob<^Zl zA8#!;DUOi-D?n{6Q5k_uxKEiC$;;6czDtpJdpJy^CEJr;vbxlmSuxW2QBkD)($Aa+ zaQ2c4{(#gCXPqfNjlzV-Sa><`k9|mSXtpjxgK9)C*+{MYh|>@+5tfC(<rX@GjB>#vc3e*VyTP#z&+N;Q z_4;a?9w6!kKosDMC&bIElB)-Lq_=UVcy(g}2>C30lJSP3FU!O;FS6HK9PC~h)vk6M z_DM;%jk=qmxROhGhdFhAzm06VFyTt)8_fN84N3w`YeuAba#yVzxv1cax1jXIW#Us5 z)_miy3tPZD;?>x1&@t{FRndyTRTI@%YM%JeP#(g%QnNrfFlC({c58%CC`<4ecdEFu z8Ve2cq##I{C)WOAtoh@IfjS3sh}-klIVj*&MIVI<;nb`-f_U4oKn*FLZuW0V|J%i zX=?cR@sVDB|8Eq}FZ8B{p<>a-)GGJ60@GW zF*sz%`T4&wO$g&r3BRer%F=whX(`RcAJNd}-pmqALg@m>HFH%9T!gbF7GrLHt@*5N5^a48>PwE*U-)2akE|5?R{$|qh=VH zCp16ef+Y3N0?wGNnqO2~Fr%D+Svxw2_jG7%dh2^0v^&jz-^X3qb#bDpx?S@-c`}*{ z8(IIOArYSZ$C0(}|6En!n1n~tkf@+(_H+JwmGq*ySD>JvP#^0jE-S(bQ}hOa-7{6< zm_U_IAKofhj~!h`QEQ0quI9P;2(+~6Un1hz=k6+>zE|g&(9vTODRX)>vNB^p! zMvti8>##1m_LVPE));)>UL?udQA_y!mO3K1D+@!*>hwOjw+z1)Z~oJTzXFj%+p=w= zvs%$}^auxc1!L=+?YIJK8NJf68VpN1puuHARu<|0QMG_)sx01wKooMWZX5h#G??Jo zpqn*YguELd5!fL5zoN&-3?Jz_k-l4H?5KiYdU&x}{;vvhln}rLxJ_6j7|skx6zv;s zdC^4S?{{=%r2?x}w{T?5KW(z^{(n73|Nj;WnkXzTlI5#XSBY=gLpK!wqNJr*r(hlV Fe*lkXeuMx3 literal 0 HcmV?d00001 diff --git a/setup.py b/setup.py index b783092fb..8192d6de9 100755 --- a/setup.py +++ b/setup.py @@ -2,6 +2,7 @@ from setuptools import setup, find_packages import sys +import os if sys.version_info[:3] < (3, 4, 3): raise Exception("You need at least Python 3.4.3 to run ARTIQ") @@ -44,6 +45,7 @@ setup( packages=find_packages(), namespace_packages=[], test_suite="artiq.test", + package_data={"artiq": [os.path.join("gui", "icon.png")]}, ext_modules=[], entry_points={ "console_scripts": scripts, From 4bad5e464f1417d1a58289a40a3a6081953cb54d Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Tue, 16 Jun 2015 18:12:38 +0200 Subject: [PATCH 059/227] conda: fix KC705 BIOS and runtime --- conda/artiq/build.sh | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/conda/artiq/build.sh b/conda/artiq/build.sh index fbb85ae12..25db92abf 100755 --- a/conda/artiq/build.sh +++ b/conda/artiq/build.sh @@ -22,20 +22,19 @@ cd $SRC_DIR/misoc; python make.py -X ../soc -t artiq_kc705 build-headers build-b make -C soc/runtime clean runtime.fbi cd $SRC_DIR/misoc; python make.py -X ../soc -t artiq_kc705 $MISOC_EXTRA_VIVADO_CMDLINE build-bitstream; cd - +# install KC705 binaries + +cp $SRC_DIR/misoc/software/bios/bios.bin $BIN_PREFIX/kc705/ +cp $SRC_DIR/misoc/build/artiq_kc705-nist_qc1-kc705.bit $BIN_PREFIX/kc705/ +wget http://sionneau.net/artiq/binaries/kc705/flash_proxy/bscan_spi_kc705.bit +mv bscan_spi_kc705.bit $BIN_PREFIX/kc705/ + # build for Pipistrello cd $SRC_DIR/misoc; python make.py -X ../soc -t artiq_pipistrello build-headers build-bios; cd - make -C soc/runtime clean runtime.fbi cd $SRC_DIR/misoc; python make.py -X ../soc -t artiq_pipistrello $MISOC_EXTRA_ISE_CMDLINE build-bitstream; cd - -# install KC705 binaries - -cp soc/runtime/runtime.fbi $BIN_PREFIX/kc705/ -cp $SRC_DIR/misoc/software/bios/bios.bin $BIN_PREFIX/kc705/ -cp $SRC_DIR/misoc/build/artiq_kc705-nist_qc1-kc705.bit $BIN_PREFIX/kc705/ -wget http://sionneau.net/artiq/binaries/kc705/flash_proxy/bscan_spi_kc705.bit -mv bscan_spi_kc705.bit $BIN_PREFIX/kc705/ - # install Pipistrello binaries cp soc/runtime/runtime.fbi $BIN_PREFIX/pipistrello/ From 2f8a67c8b652264e9dedcaa472e93a3366a8f22a Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Tue, 16 Jun 2015 18:14:26 +0200 Subject: [PATCH 060/227] conda: fix missing runtime for KC705 in previous commit --- conda/artiq/build.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/conda/artiq/build.sh b/conda/artiq/build.sh index 25db92abf..03920a9dd 100755 --- a/conda/artiq/build.sh +++ b/conda/artiq/build.sh @@ -24,6 +24,7 @@ cd $SRC_DIR/misoc; python make.py -X ../soc -t artiq_kc705 $MISOC_EXTRA_VIVADO_C # install KC705 binaries +cp soc/runtime/runtime.fbi $BIN_PREFIX/kc705/ cp $SRC_DIR/misoc/software/bios/bios.bin $BIN_PREFIX/kc705/ cp $SRC_DIR/misoc/build/artiq_kc705-nist_qc1-kc705.bit $BIN_PREFIX/kc705/ wget http://sionneau.net/artiq/binaries/kc705/flash_proxy/bscan_spi_kc705.bit From afc70926d7e7d4229bb373aa12cf5518b2888411 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Wed, 17 Jun 2015 16:40:27 +0200 Subject: [PATCH 061/227] artiq_coreconfig: use subparsers for arg parsing --- artiq/frontend/artiq_coreconfig.py | 64 +++++++++++++++++------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/artiq/frontend/artiq_coreconfig.py b/artiq/frontend/artiq_coreconfig.py index 7540b7f32..d5289bdb4 100755 --- a/artiq/frontend/artiq_coreconfig.py +++ b/artiq/frontend/artiq_coreconfig.py @@ -13,20 +13,30 @@ def to_bytes(string): def get_argparser(): parser = argparse.ArgumentParser(description="ARTIQ core device config " "remote access") - parser.add_argument("-r", "--read", type=to_bytes, - help="read key from core device config") - parser.add_argument("-w", "--write", nargs=2, action="append", default=[], - metavar=("KEY", "STRING"), type=to_bytes, - help="write key-value records to core device config") - parser.add_argument("-f", "--write-file", nargs=2, action="append", - type=to_bytes, default=[], metavar=("KEY", "FILENAME"), - help="write the content of a file into core device " - "config") - parser.add_argument("-e", "--erase", action="store_true", - help="erase core device config") - parser.add_argument("-d", "--delete", action="append", default=[], - type=to_bytes, - help="delete key from core device config") + 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("-k", "--key", type=to_bytes, required=True, + 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("-k", "--key", action="append", default=[], + type=to_bytes, required=True, + help="key to be deleted from core device config") parser.add_argument("--ddb", default="ddb.pyon", help="device database file") return parser @@ -37,25 +47,23 @@ def main(): ddb = FlatFileDB(args.ddb) comm = create_device(ddb.request("comm"), None) - if args.read: - value = comm.flash_storage_read(args.read) + if args.action == "read": + value = comm.flash_storage_read(args.key) if not value: - print("Key {} does not exist".format(args.read)) + print("Key {} does not exist".format(args.key)) else: print(value) - elif args.erase: + elif args.action == "erase": comm.flash_storage_erase() - elif args.delete: - for key in args.delete: + elif args.action == "delete": + for key in args.key: comm.flash_storage_remove(key) - else: - if args.write: - for key, value in args.write: - comm.flash_storage_write(key, value) - if args.write_file: - for key, filename in args.write_file: - with open(filename, "rb") as fi: - comm.flash_storage_write(key, fi.read()) + 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()) if __name__ == "__main__": main() From 250f9e0bc072ee2a90d94ad8cda072edd3b0ae57 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Wed, 17 Jun 2015 16:46:39 +0200 Subject: [PATCH 062/227] manual: update artiq_coreconfig parameter's syntax --- doc/manual/installing.rst | 2 +- doc/manual/utilities.rst | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index 145eb585b..cfc8643b9 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -265,7 +265,7 @@ To flash the ``idle`` kernel: * Write it into the core device configuration flash storage: :: - $ artiq_coreconfig -f idle_kernel idle.elf + $ artiq_coreconfig write -f idle_kernel idle.elf .. note:: You can find more information about how to use the ``artiq_coreconfig`` tool on the :ref:`Utilities ` page. diff --git a/doc/manual/utilities.rst b/doc/manual/utilities.rst index 027d22470..a27d7d8d7 100644 --- a/doc/manual/utilities.rst +++ b/doc/manual/utilities.rst @@ -109,38 +109,38 @@ When not specified, the artiq_coreconfig tool will assume that there is a file n To read the record whose key is ``mac``:: - $ artiq_coreconfig -r mac + $ artiq_coreconfig read -k mac To write the value ``test_value`` in the key ``my_key``:: - $ artiq_coreconfig -w my_key test_value - $ artiq_coreconfig -r my_key + $ artiq_coreconfig write -s my_key test_value + $ artiq_coreconfig read -k my_key b'test_value' You can also write entire files in a record using the ``-f`` parameter. This is useful for instance to write the ``idle`` kernel in the flash storage:: - $ artiq_coreconfig -f idle_kernel idle.elf - $ artiq_coreconfig -r idle_kernel | head -c9 + $ artiq_coreconfig write -f idle_kernel idle.elf + $ artiq_coreconfig read -k idle_kernel | head -c9 b'\x7fELF You can write several records at once:: - $ artiq_coreconfig -w key1 value1 -f key2 filename -w key3 value3 + $ artiq_coreconfig write -s key1 value1 -f key2 filename -s key3 value3 To remove the previously written key ``my_key``:: - $ artiq_coreconfig -d my_key + $ artiq_coreconfig delete -k my_key To erase the entire flash storage area:: - $ artiq_coreconfig -e + $ artiq_coreconfig erase You don't need to remove a record in order to change its value, just overwrite it:: - $ artiq_coreconfig -w my_key some_value - $ artiq_coreconfig -w my_key some_other_value - $ artiq_coreconfig -r my_key + $ artiq_coreconfig write -s my_key some_value + $ artiq_coreconfig write -s my_key some_other_value + $ artiq_coreconfig read -k my_key b'some_other_value' .. argparse:: From 38a0f63bd2f4ae61fe76c95aac8a832eb577de06 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Wed, 17 Jun 2015 15:36:12 +0200 Subject: [PATCH 063/227] gateware/soc: use Minicon SDRAM controller and 128KB shared L2 cache --- artiq/gateware/amp/kernel_cpu.py | 15 +++------------ artiq/gateware/soc.py | 5 +++-- soc/targets/artiq_kc705.py | 5 ++++- soc/targets/artiq_pipistrello.py | 5 ++++- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/artiq/gateware/amp/kernel_cpu.py b/artiq/gateware/amp/kernel_cpu.py index b66641997..7afd5730d 100644 --- a/artiq/gateware/amp/kernel_cpu.py +++ b/artiq/gateware/amp/kernel_cpu.py @@ -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] diff --git a/artiq/gateware/soc.py b/artiq/gateware/soc.py index a074da772..15095ea92 100644 --- a/artiq/gateware/soc.py +++ b/artiq/gateware/soc.py @@ -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) diff --git a/soc/targets/artiq_kc705.py b/soc/targets/artiq_kc705.py index e90a67130..d45badfe7 100644 --- a/soc/targets/artiq_kc705.py +++ b/soc/targets/artiq_kc705.py @@ -6,6 +6,7 @@ from mibuild.xilinx.vivado import XilinxVivadoToolchain from misoclib.com import gpio from misoclib.soc import mem_decoder +from misoclib.mem.sdram.core.minicon import MiniconSettings from targets.kc705 import MiniSoC from artiq.gateware.soc import AMPSoC @@ -48,7 +49,9 @@ class NIST_QC1(MiniSoC, AMPSoC): def __init__(self, platform, cpu_type="or1k", **kwargs): MiniSoC.__init__(self, platform, - cpu_type=cpu_type, with_timer=False, **kwargs) + cpu_type=cpu_type, + sdram_controller_settings=MiniconSettings(l2_size=128*1024), + with_timer=False, **kwargs) AMPSoC.__init__(self) platform.add_extension(nist_qc1.fmc_adapter_io) diff --git a/soc/targets/artiq_pipistrello.py b/soc/targets/artiq_pipistrello.py index f967fed97..1b1dd71f9 100644 --- a/soc/targets/artiq_pipistrello.py +++ b/soc/targets/artiq_pipistrello.py @@ -4,6 +4,7 @@ from migen.bank import wbgen from misoclib.com import gpio from misoclib.soc import mem_decoder +from misoclib.mem.sdram.core.minicon import MiniconSettings from targets.pipistrello import BaseSoC from artiq.gateware.soc import AMPSoC @@ -69,7 +70,9 @@ class NIST_QC1(BaseSoC, AMPSoC): def __init__(self, platform, cpu_type="or1k", **kwargs): BaseSoC.__init__(self, platform, - cpu_type=cpu_type, with_timer=False, **kwargs) + cpu_type=cpu_type, + sdram_controller_settings=MiniconSettings(l2_size=128*1024), + with_timer=False, **kwargs) AMPSoC.__init__(self) platform.toolchain.ise_commands += """ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd {build_name}.pcf From 449964cce83805c5152bbbc343ded5abbe7ddd04 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Thu, 18 Jun 2015 12:16:57 +0200 Subject: [PATCH 064/227] runtime/mailbox: remove flush of L2 cache (L2 cache is now shared between CPUs) --- soc/runtime/mailbox.c | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/soc/runtime/mailbox.c b/soc/runtime/mailbox.c index 2f0786893..98e49045b 100644 --- a/soc/runtime/mailbox.c +++ b/soc/runtime/mailbox.c @@ -29,22 +29,8 @@ static void _flush_cpu_dcache(void) mtspr(SPR_DCBIR, i); } -/* TODO: do not use L2 cache in AMP systems */ -static void _flush_l2_cache(void) -{ - unsigned int i; - register unsigned int addr; - register unsigned int dummy; - - for(i=0;i<2*8192/4;i++) { - addr = 0x40000000 + i*4; - __asm__ volatile("l.lwz %0, 0(%1)\n":"=r"(dummy):"r"(addr)); - } -} - void mailbox_send(void *ptr) { - _flush_l2_cache(); last_transmission = (unsigned int)ptr; KERNELCPU_MAILBOX = last_transmission; } @@ -72,7 +58,6 @@ void *mailbox_receive(void) return NULL; else { if(r) { - _flush_l2_cache(); _flush_cpu_dcache(); } return (void *)r; From 77ca8bbf0f077fc8b5058e97ccdde8c40bc641f7 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Thu, 18 Jun 2015 17:07:03 +0200 Subject: [PATCH 065/227] artiq_coreconfig: better arg parsing --- artiq/frontend/artiq_coreconfig.py | 6 +++--- doc/manual/utilities.rst | 14 +++++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/artiq/frontend/artiq_coreconfig.py b/artiq/frontend/artiq_coreconfig.py index d5289bdb4..9eb39d7fc 100755 --- a/artiq/frontend/artiq_coreconfig.py +++ b/artiq/frontend/artiq_coreconfig.py @@ -17,7 +17,7 @@ def get_argparser(): subparsers.required = True p_read = subparsers.add_parser("read", help="read key from core device config") - p_read.add_argument("-k", "--key", type=to_bytes, required=True, + 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 " @@ -34,8 +34,8 @@ def get_argparser(): 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("-k", "--key", action="append", default=[], - type=to_bytes, required=True, + 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") diff --git a/doc/manual/utilities.rst b/doc/manual/utilities.rst index a27d7d8d7..a507291ae 100644 --- a/doc/manual/utilities.rst +++ b/doc/manual/utilities.rst @@ -109,18 +109,18 @@ When not specified, the artiq_coreconfig tool will assume that there is a file n To read the record whose key is ``mac``:: - $ artiq_coreconfig read -k mac + $ artiq_coreconfig read mac To write the value ``test_value`` in the key ``my_key``:: $ artiq_coreconfig write -s my_key test_value - $ artiq_coreconfig read -k my_key + $ artiq_coreconfig read my_key b'test_value' You can also write entire files in a record using the ``-f`` parameter. This is useful for instance to write the ``idle`` kernel in the flash storage:: $ artiq_coreconfig write -f idle_kernel idle.elf - $ artiq_coreconfig read -k idle_kernel | head -c9 + $ artiq_coreconfig read idle_kernel | head -c9 b'\x7fELF You can write several records at once:: @@ -129,7 +129,11 @@ You can write several records at once:: To remove the previously written key ``my_key``:: - $ artiq_coreconfig delete -k my_key + $ artiq_coreconfig delete my_key + +You can remove several keys at once:: + + $ artiq_coreconfig delete key1 key2 To erase the entire flash storage area:: @@ -140,7 +144,7 @@ it:: $ artiq_coreconfig write -s my_key some_value $ artiq_coreconfig write -s my_key some_other_value - $ artiq_coreconfig read -k my_key + $ artiq_coreconfig read my_key b'some_other_value' .. argparse:: From 3636025e69c8c299280a0650be2b2053298136aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Bourdeauducq?= Date: Thu, 18 Jun 2015 09:49:52 -0600 Subject: [PATCH 066/227] pipistrello: smaller L2 cache --- soc/targets/artiq_pipistrello.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/soc/targets/artiq_pipistrello.py b/soc/targets/artiq_pipistrello.py index 1b1dd71f9..f18c7cfe0 100644 --- a/soc/targets/artiq_pipistrello.py +++ b/soc/targets/artiq_pipistrello.py @@ -71,7 +71,7 @@ class NIST_QC1(BaseSoC, AMPSoC): def __init__(self, platform, cpu_type="or1k", **kwargs): BaseSoC.__init__(self, platform, cpu_type=cpu_type, - sdram_controller_settings=MiniconSettings(l2_size=128*1024), + sdram_controller_settings=MiniconSettings(l2_size=64*1024), with_timer=False, **kwargs) AMPSoC.__init__(self) platform.toolchain.ise_commands += """ From 1ad5165cc77130ef812f8db9836ee049dca5b6ff Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Thu, 18 Jun 2015 18:02:40 +0200 Subject: [PATCH 067/227] artiq_gui: add a search box in parameters dock --- artiq/gui/parameters.py | 28 ++++++++++++++++++++++++++-- artiq/gui/tools.py | 24 ++++++++++++------------ 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/artiq/gui/parameters.py b/artiq/gui/parameters.py index 873656eff..8cf8d2f61 100644 --- a/artiq/gui/parameters.py +++ b/artiq/gui/parameters.py @@ -1,7 +1,8 @@ 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 @@ -28,9 +29,32 @@ class ParametersDock(dockarea.Dock): def __init__(self): dockarea.Dock.__init__(self, "Parameters", size=(400, 300)) + splitter = QtGui.QSplitter(QtCore.Qt.Horizontal) + self.addWidget(splitter) + grid = LayoutWidget() + splitter.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) + 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): diff --git a/artiq/gui/tools.py b/artiq/gui/tools.py index 99af51dca..f38ebcc09 100644 --- a/artiq/gui/tools.py +++ b/artiq/gui/tools.py @@ -17,13 +17,13 @@ class _DictSyncSubstruct: 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 +34,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 +48,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 +56,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 +65,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 +73,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,13 +81,13 @@ 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 update(): - self[key] = self.data[key] - return _DictSyncSubstruct(update, self.data[key]) + self[key] = self.backing_store[key] + return _DictSyncSubstruct(update, self.backing_store[key]) def sort_key(self, k, v): raise NotImplementedError From b05972059ee6f2f207562d1a8bde4aa652bcadc4 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Thu, 18 Jun 2015 18:34:38 +0200 Subject: [PATCH 068/227] artiq_gui: check due date upon date selection --- artiq/gui/explorer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/artiq/gui/explorer.py b/artiq/gui/explorer.py index 8f2fbd7c8..80fbdd5f8 100644 --- a/artiq/gui/explorer.py +++ b/artiq/gui/explorer.py @@ -41,6 +41,7 @@ class ExplorerDock(dockarea.Dock): self.datetime.setDisplayFormat("MMM d yyyy hh:mm:ss") self.datetime.setCalendarPopup(True) self.datetime.setDate(QtCore.QDate.currentDate()) + 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) @@ -65,6 +66,9 @@ class ExplorerDock(dockarea.Dock): placeholder = QtGui.QWidget() splitter.addWidget(placeholder) + def enable_duedate(self): + self.datetime_en.setChecked(True) + @asyncio.coroutine def sub_connect(self, host, port): self.explist_subscriber = Subscriber("explist", From 4d077f50c6621ad771818f489a5dd611e320ea6d Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Fri, 19 Jun 2015 01:01:25 +0200 Subject: [PATCH 069/227] thorlabs tdc driver: all moves are now blocking and wait for move to end or be stopped --- artiq/devices/thorlabs_tcube/driver.py | 109 +++++++++++++------------ 1 file changed, 56 insertions(+), 53 deletions(-) diff --git a/artiq/devices/thorlabs_tcube/driver.py b/artiq/devices/thorlabs_tcube/driver.py index 649b051c7..f023d41d0 100644 --- a/artiq/devices/thorlabs_tcube/driver.py +++ b/artiq/devices/thorlabs_tcube/driver.py @@ -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 @@ -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): @@ -389,7 +389,7 @@ class Tpz(Tcube): """ get_msg = self.send_request(MGMSG.PZ_REQ_OUTPUTVOLTS, - MGMSG.PZ_GET_OUTPUTVOLTS, 1) + [MGMSG.PZ_GET_OUTPUTVOLTS], 1) return st.unpack(" Date: Fri, 19 Jun 2015 01:07:37 +0200 Subject: [PATCH 070/227] gui: forgot to rename one data to backing_store --- artiq/gui/explorer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/gui/explorer.py b/artiq/gui/explorer.py index 80fbdd5f8..df2057b05 100644 --- a/artiq/gui/explorer.py +++ b/artiq/gui/explorer.py @@ -101,7 +101,7 @@ 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: From 1d052095ee5143255922319a32110a1dd00f45ec Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Fri, 19 Jun 2015 08:10:33 +0200 Subject: [PATCH 071/227] doc/manual/installing: minor cleanups --- doc/manual/installing.rst | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index cfc8643b9..4cf186931 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -91,7 +91,7 @@ These steps are required to generate bitstream (``.bit``) files, build the MiSoC $ cd ~/artiq-dev $ git clone https://github.com/m-labs/migen - $ cd ~/artiq-dev/migen + $ cd migen $ python3 setup.py develop --user .. note:: @@ -99,11 +99,11 @@ These steps are required to generate bitstream (``.bit``) files, build the MiSoC * Install OpenRISC GCC/binutils toolchain (or1k-elf-...): :: - $ mkdir ~/artiq-dev $ cd ~/artiq-dev $ git clone https://github.com/openrisc/or1k-src - $ mkdir ~/artiq-dev/or1k-src/build - $ cd ~/artiq-dev/or1k-src/build + $ cd or1k-src + $ mkdir build + $ cd build $ ../configure --target=or1k-elf --enable-shared --disable-itcl \ --disable-tk --disable-tcl --disable-winsup \ --disable-gdbtk --disable-libgui --disable-rda \ @@ -114,8 +114,9 @@ These steps are required to generate bitstream (``.bit``) files, build the MiSoC $ cd ~/artiq-dev $ git clone https://github.com/openrisc/or1k-gcc - $ mkdir ~/artiq-dev/or1k-gcc/build - $ cd ~/artiq-dev/or1k-gcc/build + $ cd or1k-gcc + $ mkdir build + $ cd build $ ../configure --target=or1k-elf --enable-languages=c \ --disable-shared --disable-libssp $ make -j4 @@ -127,7 +128,7 @@ These steps are required to generate bitstream (``.bit``) files, build the MiSoC $ cd ~/artiq-dev $ svn co https://xc3sprog.svn.sourceforge.net/svnroot/xc3sprog/trunk xc3sprog - $ cd ~/artiq-dev/xc3sprog + $ cd xc3sprog $ cmake . && make $ sudo make install @@ -155,8 +156,10 @@ These steps are required to generate bitstream (``.bit``) files, build the MiSoC $ cd ~/artiq-dev $ git clone https://github.com/m-labs/bscan_spi_kc705 + $ cd bscan_spi_kc705 + $ make - Build the bitstream and copy it to one of the folders above. + Then copy the generated ``bscan_spi_kc705.bit`` to ``~/.migen``, ``/usr/local/share/migen`` or ``/usr/share/migen``. * Download MiSoC: :: @@ -276,19 +279,19 @@ Installing the host-side software $ cd ~/artiq-dev $ git clone https://github.com/openrisc/llvm-or1k - $ cd ~/artiq-dev/llvm-or1k/tools + $ cd llvm-or1k/tools $ git clone https://github.com/openrisc/clang-or1k clang - $ cd ~/artiq-dev/llvm-or1k + $ cd .. $ mkdir build - $ cd ~/artiq-dev/llvm-or1k/build + $ cd build $ cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local/llvm-or1k -DLLVM_TARGETS_TO_BUILD=OR1K -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=ON $ make -j4 $ sudo make install $ cd ~/artiq-dev $ git clone https://github.com/numba/llvmlite - $ cd ~/artiq-dev/llvmlite + $ cd llvmlite $ patch -p1 < ~/artiq-dev/artiq/misc/llvmlite-add-all-targets.patch $ PATH=/usr/local/llvm-or1k/bin:$PATH sudo -E python3 setup.py install From 988ec5c7e249a0405b21adfd013e621047ecce12 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Fri, 19 Jun 2015 10:40:10 +0200 Subject: [PATCH 072/227] tdc/tpz driver: fix missing close method in simulated device --- artiq/devices/thorlabs_tcube/driver.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/artiq/devices/thorlabs_tcube/driver.py b/artiq/devices/thorlabs_tcube/driver.py index f023d41d0..b3403a03d 100644 --- a/artiq/devices/thorlabs_tcube/driver.py +++ b/artiq/devices/thorlabs_tcube/driver.py @@ -1323,6 +1323,9 @@ class TpzSim: self.voltage_limit = 150 self.hub_analog_input = 1 + def close(self): + pass + def module_identify(self): pass @@ -1406,6 +1409,9 @@ class TpzSim: class TdcSim: + def close(self): + pass + def module_identify(self): pass From 4cdf1c4b81787fe5de1e7a0de307668c04137144 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Fri, 19 Jun 2015 16:41:44 +0200 Subject: [PATCH 073/227] manual: serial number explanations + udev rule for LDA --- artiq/frontend/lda_controller.py | 6 +++++- doc/manual/ndsp_reference.rst | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/artiq/frontend/lda_controller.py b/artiq/frontend/lda_controller.py index 8efc3cbb4..cc70038e9 100755 --- a/artiq/frontend/lda_controller.py +++ b/artiq/frontend/lda_controller.py @@ -15,7 +15,11 @@ def get_argparser(): choices=["LDA-102", "LDA-602"]) simple_network_args(parser, 3253) parser.add_argument("-d", "--device", default=None, - help="USB serial number of the device." + help="USB serial number of the device. " + "The serial number is written on a sticker under " + "the device, you should write for example " + "-d \"SN:03461\". You must prepend enough 0 for it " + "to be 5 digits." " Omit for simulation mode.") verbosity_args(parser) return parser diff --git a/doc/manual/ndsp_reference.rst b/doc/manual/ndsp_reference.rst index d7e454126..c0f619f86 100644 --- a/doc/manual/ndsp_reference.rst +++ b/doc/manual/ndsp_reference.rst @@ -34,6 +34,28 @@ Client Lab Brick Digital Attenuator (LDA) ---------------------------------- +On Linux, you need to give your user access to the usb device. + +You can do that by creating a file under /etc/udev/rules.d/ named 99-lda.rules +with the following content:: + + SUBSYSTEM=="usb", ATTR{idVendor}=="041f", MODE="0666" + +Then you need to tell udev to reload its rules:: + + $ sudo invoke-rc.d udev reload + +You must also unplug/replug your device if it was already plugged in. + +Then, to run the Lab Brick Digital Attenuator (LDA) controller:: + + $ lda_controller -d SN:xxxxx + +The serial number must contain 5 digits, prepend any number of 0 necessary. +Also, the "SN:" prefix is mandatory. + +You can chose the exact LDA model with the -P parameter. The default being LDA-102. + Driver ++++++ From 979f3522cb341c370c9e4439c7e096bfd8c3c9c4 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Fri, 19 Jun 2015 17:06:14 +0200 Subject: [PATCH 074/227] manual: style --- doc/manual/ndsp_reference.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/manual/ndsp_reference.rst b/doc/manual/ndsp_reference.rst index c0f619f86..8ac75ac7b 100644 --- a/doc/manual/ndsp_reference.rst +++ b/doc/manual/ndsp_reference.rst @@ -36,8 +36,8 @@ Lab Brick Digital Attenuator (LDA) On Linux, you need to give your user access to the usb device. -You can do that by creating a file under /etc/udev/rules.d/ named 99-lda.rules -with the following content:: +You can do that by creating a file under ``/etc/udev/rules.d/`` named +``99-lda.rules`` with the following content:: SUBSYSTEM=="usb", ATTR{idVendor}=="041f", MODE="0666" @@ -52,9 +52,9 @@ Then, to run the Lab Brick Digital Attenuator (LDA) controller:: $ lda_controller -d SN:xxxxx The serial number must contain 5 digits, prepend any number of 0 necessary. -Also, the "SN:" prefix is mandatory. +Also, the ``SN:`` prefix is mandatory. -You can chose the exact LDA model with the -P parameter. The default being LDA-102. +You can chose the exact LDA model with the ``-P`` parameter. The default being LDA-102. Driver ++++++ From b8bb3d8ca7fc1fcf62f2255c5b6a76c3622f7526 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Fri, 19 Jun 2015 17:06:50 +0200 Subject: [PATCH 075/227] manual: explain how to select TCube device from serial number --- artiq/frontend/thorlabs_tcube_controller.py | 4 +++- doc/manual/ndsp_reference.rst | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/artiq/frontend/thorlabs_tcube_controller.py b/artiq/frontend/thorlabs_tcube_controller.py index d4dc18388..d6aea8cbf 100755 --- a/artiq/frontend/thorlabs_tcube_controller.py +++ b/artiq/frontend/thorlabs_tcube_controller.py @@ -13,7 +13,9 @@ 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. Omit for simulation " + "mode.") simple_network_args(parser, 3255) verbosity_args(parser) return parser diff --git a/doc/manual/ndsp_reference.rst b/doc/manual/ndsp_reference.rst index 8ac75ac7b..829b1263d 100644 --- a/doc/manual/ndsp_reference.rst +++ b/doc/manual/ndsp_reference.rst @@ -88,6 +88,8 @@ Controller Thorlabs T-Cube --------------- +.. _tdc001-controller-usage-example: + TDC001 controller usage example +++++++++++++++++++++++++++++++ @@ -98,6 +100,15 @@ First, run the TDC001 controller:: .. note:: On Windows the serial port (the ``-d`` argument) will be of the form ``COMx``. +.. note:: + Anything compatible `with serial_for_url `_ + can be given as a device in ``-d`` argument. + + For instance, if you want to specify the Vendor/Product ID and the USB Serial Number, you can do: + + ``-d hwgrep://: SNR=``. + The hwgrep URL works on both Linux and Windows. + Then, send commands to it via the ``artiq_rpctool`` utility:: $ artiq_rpctool ::1 3255 list-targets @@ -118,6 +129,11 @@ First, run the TPZ001 controller:: .. note:: On Windows the serial port (the ``-d`` argument) will be of the form ``COMx``. +.. note:: + See the :ref:`TDC001 documentation ` for + how to specify the USB Serial Number of the device instead of the + /dev/ttyUSBx (or the COMx name). + Then, send commands to it via the ``artiq_rpctool`` utility:: $ artiq_rpctool ::1 3255 list-targets From 3933ff5cba6c241761f9d91be8348ac0b2791ab1 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Fri, 19 Jun 2015 17:44:57 +0200 Subject: [PATCH 076/227] manual: add missing quotes --- doc/manual/ndsp_reference.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/ndsp_reference.rst b/doc/manual/ndsp_reference.rst index 829b1263d..bac983014 100644 --- a/doc/manual/ndsp_reference.rst +++ b/doc/manual/ndsp_reference.rst @@ -106,7 +106,7 @@ First, run the TDC001 controller:: For instance, if you want to specify the Vendor/Product ID and the USB Serial Number, you can do: - ``-d hwgrep://: SNR=``. + ``-d "hwgrep://: SNR="``. The hwgrep URL works on both Linux and Windows. Then, send commands to it via the ``artiq_rpctool`` utility:: From 03fe71228b051a624e7429efc68f25064675dcfc Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 19 Jun 2015 11:01:43 -0600 Subject: [PATCH 077/227] dds: phase computation fixes --- soc/runtime/dds.c | 24 ++++++++++++------------ soc/targets/artiq_kc705.py | 1 + soc/targets/artiq_pipistrello.py | 1 + 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/soc/runtime/dds.c b/soc/runtime/dds.c index fc871fa26..5e94d5e1c 100644 --- a/soc/runtime/dds.c +++ b/soc/runtime/dds.c @@ -55,26 +55,26 @@ static void dds_set_one(long long int now, long long int ref_time, int channel, { DDS_WRITE(DDS_GPIO, channel); - if(phase_mode == PHASE_MODE_CONTINUOUS) - /* Do not clear phase accumulator on FUD */ - DDS_WRITE(0x02, 0x00); - else - /* Clear phase accumulator on FUD */ - DDS_WRITE(0x02, 0x40); - DDS_WRITE(DDS_FTW0, ftw & 0xff); DDS_WRITE(DDS_FTW1, (ftw >> 8) & 0xff); DDS_WRITE(DDS_FTW2, (ftw >> 16) & 0xff); DDS_WRITE(DDS_FTW3, (ftw >> 24) & 0xff); - /* We assume that the RTIO clock is DDS SYNCLK */ - if(phase_mode == PHASE_MODE_TRACKING) - pow += (ref_time >> RTIO_FINE_TS_WIDTH)*ftw >> 18; - if(phase_mode != PHASE_MODE_CONTINUOUS) { + /* We need the RTIO fine timestamp clock to be phase-locked + * to DDS SYNCLK, and divided by an integer DDS_RTIO_CLK_RATIO. + */ + if(phase_mode == PHASE_MODE_CONTINUOUS) { + /* Do not clear phase accumulator on FUD */ + DDS_WRITE(0x02, 0x00); + } else { long long int fud_time; + /* Clear phase accumulator on FUD */ + DDS_WRITE(0x02, 0x40); fud_time = now + 2*DURATION_WRITE; - pow -= ((ref_time - fud_time) >> RTIO_FINE_TS_WIDTH)*ftw >> 18; + pow -= (ref_time - fud_time)*DDS_RTIO_CLK_RATIO*ftw >> 18; + if(phase_mode == PHASE_MODE_TRACKING) + pow += ref_time*DDS_RTIO_CLK_RATIO*ftw >> 18; } DDS_WRITE(DDS_POW0, pow & 0xff); diff --git a/soc/targets/artiq_kc705.py b/soc/targets/artiq_kc705.py index d45badfe7..15ce34908 100644 --- a/soc/targets/artiq_kc705.py +++ b/soc/targets/artiq_kc705.py @@ -93,6 +93,7 @@ class NIST_QC1(MiniSoC, AMPSoC): self.submodules.rtio = rtio.RTIO(rtio_channels, clk_freq=125000000) self.add_constant("RTIO_FINE_TS_WIDTH", self.rtio.fine_ts_width) + self.add_constant("DDS_RTIO_CLK_RATIO", 8 >> self.rtio.fine_ts_width) self.submodules.rtio_moninj = rtio.MonInj(rtio_channels) if isinstance(platform.toolchain, XilinxVivadoToolchain): diff --git a/soc/targets/artiq_pipistrello.py b/soc/targets/artiq_pipistrello.py index f18c7cfe0..9a149a748 100644 --- a/soc/targets/artiq_pipistrello.py +++ b/soc/targets/artiq_pipistrello.py @@ -128,6 +128,7 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd self.submodules.rtio = rtio.RTIO(rtio_channels, clk_freq=125000000) self.add_constant("RTIO_FINE_TS_WIDTH", self.rtio.fine_ts_width) + self.add_constant("DDS_RTIO_CLK_RATIO", 8 >> self.rtio.fine_ts_width) self.submodules.rtio_mon = rtio.MonInj(rtio_channels) # CPU connections From 188a9fb313f3155c1a92c0073c5f5501de765e69 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Fri, 19 Jun 2015 23:03:47 +0200 Subject: [PATCH 078/227] manual: add hwgrep example for TDC --- doc/manual/ndsp_reference.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/manual/ndsp_reference.rst b/doc/manual/ndsp_reference.rst index bac983014..fd66639b1 100644 --- a/doc/manual/ndsp_reference.rst +++ b/doc/manual/ndsp_reference.rst @@ -107,6 +107,10 @@ First, run the TDC001 controller:: For instance, if you want to specify the Vendor/Product ID and the USB Serial Number, you can do: ``-d "hwgrep://: SNR="``. + for instance: + + ``-d "hwgrep://0403:faf0 SNR=83852734"`` + The hwgrep URL works on both Linux and Windows. Then, send commands to it via the ``artiq_rpctool`` utility:: From d646984471e239f6326a90628b504cfe36623420 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Fri, 19 Jun 2015 23:09:19 +0200 Subject: [PATCH 079/227] manual: link cleanup --- doc/manual/ndsp_reference.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/ndsp_reference.rst b/doc/manual/ndsp_reference.rst index fd66639b1..4871b3cad 100644 --- a/doc/manual/ndsp_reference.rst +++ b/doc/manual/ndsp_reference.rst @@ -101,7 +101,7 @@ First, run the TDC001 controller:: On Windows the serial port (the ``-d`` argument) will be of the form ``COMx``. .. note:: - Anything compatible `with serial_for_url `_ + Anything compatible with `serial_for_url `_ can be given as a device in ``-d`` argument. For instance, if you want to specify the Vendor/Product ID and the USB Serial Number, you can do: From 5a9bdb2e33d1ab3f26d94d11c61413d150e8475c Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 19 Jun 2015 15:30:17 -0600 Subject: [PATCH 080/227] DDS monitoring --- artiq/coredevice/dds.py | 9 ++-- artiq/gateware/rtio/moninj.py | 6 ++- artiq/gateware/rtio/phy/dds.py | 36 +++++++++++++++ artiq/gui/moninj.py | 75 +++++++++++++------------------- examples/master/ddb.pyon | 9 ++-- soc/runtime/dds.c | 2 +- soc/runtime/dds.h | 3 -- soc/runtime/moninj.c | 10 +++++ soc/targets/artiq_kc705.py | 11 ++--- soc/targets/artiq_pipistrello.py | 11 ++--- 10 files changed, 99 insertions(+), 73 deletions(-) create mode 100644 artiq/gateware/rtio/phy/dds.py diff --git a/artiq/coredevice/dds.py b/artiq/coredevice/dds.py index 8ab434664..903112680 100644 --- a/artiq/coredevice/dds.py +++ b/artiq/coredevice/dds.py @@ -51,13 +51,12 @@ class DDS(AutoDB): 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. + :param sysclk: DDS system frequency. :param channel: channel number of the DDS device to control. """ class DBKeys: core = Device() - dds_sysclk = Argument(1*GHz) + sysclk = Argument() channel = Argument() def build(self): @@ -68,14 +67,14 @@ 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 @kernel def init(self): diff --git a/artiq/gateware/rtio/moninj.py b/artiq/gateware/rtio/moninj.py index a09422945..9bb0d0283 100644 --- a/artiq/gateware/rtio/moninj.py +++ b/artiq/gateware/rtio/moninj.py @@ -11,6 +11,7 @@ class Monitor(Module, AutoCSR): 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) # # # @@ -25,8 +26,9 @@ class Monitor(Module, AutoCSR): 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.comb += self.value.status.eq( - Array(chan_probes_sys)[self.chan_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): diff --git a/artiq/gateware/rtio/phy/dds.py b/artiq/gateware/rtio/phy/dds.py new file mode 100644 index 000000000..b39b86180 --- /dev/null +++ b/artiq/gateware/rtio/phy/dds.py @@ -0,0 +1,36 @@ +from migen.fhdl.std import * + +from artiq.gateware import ad9858 as ad9858_ll +from artiq.gateware.rtio.phy.wishbone import RT2WB + + +class AD9858(Module): + def __init__(self, pads, nchannels=8, **kwargs): + self.submodules._ll = RenameClockDomains( + ad9858_ll.AD9858(pads, **kwargs), "rio") + self.submodules._rt2wb = RT2WB(7, self._ll.bus) + self.rtlink = self._rt2wb.rtlink + self.probes = [Signal(32) for i in range(nchannels)] + + # # # + + # keep track of the currently selected channel + current_channel = Signal(max=nchannels) + self.sync.rio += If(self.rtlink.o.stb & (self.rtlink.o.address == 65), + current_channel.eq(self.rtlink.o.data)) + + # keep track of frequency tuning words, before they are FUDed + ftws = [Signal(32) for i in range(nchannels)] + for i in range(4): + for c, ftw in enumerate(ftws): + self.sync.rio += \ + If(self.rtlink.o.stb & \ + (self.rtlink.o.address == 0x0a+i) & \ + (current_channel == c), + ftw[i*8:(i+1)*8].eq(self.rtlink.o.data) + ) + + # FTW to probe on FUD + for c, (probe, ftw) in enumerate(zip(self.probes, ftw)): + fud = self.rtlink.o.stb & (self.rtlink.o.address == 64) + self.sync.rio += If(fud & (current_channel == c), probe.eq(ftw)) diff --git a/artiq/gui/moninj.py b/artiq/gui/moninj.py index 9683754d3..c106d1a5a 100644 --- a/artiq/gui/moninj.py +++ b/artiq/gui/moninj.py @@ -9,6 +9,7 @@ from pyqtgraph import dockarea from artiq.tools import TaskObject from artiq.protocols.sync_struct import Subscriber +from artiq.language.units import strip_unit logger = logging.getLogger(__name__) @@ -112,9 +113,10 @@ class _TTLWidget(QtGui.QFrame): class _DDSWidget(QtGui.QFrame): - def __init__(self, send_to_device, channel, name): + 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) @@ -128,48 +130,16 @@ class _DDSWidget(QtGui.QFrame): label.setAlignment(QtCore.Qt.AlignCenter) grid.addWidget(label, 1, 1) - self._override = QtGui.QLabel() - self._override.setAlignment(QtCore.Qt.AlignCenter) - grid.addWidget(self._override, 2, 1) - self._value = QtGui.QLabel() self._value.setAlignment(QtCore.Qt.AlignCenter) - grid.addWidget(self._value, 3, 1, 6, 1) + grid.addWidget(self._value, 2, 1, 6, 1) - self._value.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) - self._override_action = QtGui.QAction("Override", self._value) - self._override_action.setCheckable(True) - self._value.addAction(self._override_action) - self._override_action.triggered.connect(self._override_clicked) + self.set_value(0) - self.set_value(0.0, False) - - def _override_clicked(self): - override_en = self._override_action.isChecked() - if override_en: - frequency, ok = QtGui.QInputDialog.getDouble( - None, "DDS override", - "Frequency in MHz for {}:".format(self.name), - value=self._frequency, min=0, decimals=3) - self._override_action.setChecked(ok) - if ok: - print("override set to", frequency) - else: - print("override disabled") - - def set_value(self, frequency, override): - self._frequency = frequency - self._override_action.setChecked(override) - value_s = "{:.3f} MHz".format(frequency) - if override: - value_s = "" + value_s + "" - color = " color=\"red\"" - self._override.setText("OVERRIDE") - else: - color = "" - self._override.setText("") - self._value.setText("{}" - .format(color, value_s)) + def set_value(self, ftw): + frequency = ftw*self.sysclk/2**32 + self._value.setText("{:.3f} MHz" + .format(float(frequency))) class _DeviceManager: @@ -200,8 +170,9 @@ class _DeviceManager: if (v["module"] == "artiq.coredevice.dds" and v["class"] == "DDS"): channel = v["arguments"]["channel"] + sysclk = strip_unit(v["arguments"]["sysclk"], "Hz") self.dds_widgets[channel] = _DDSWidget( - self.send_to_device, channel, k) + self.send_to_device, channel, sysclk, k) self.dds_cb() except KeyError: pass @@ -277,11 +248,25 @@ class MonInj(TaskObject): self.transport = transport def datagram_received(self, data, addr): - ttl_levels, ttl_oes, ttl_overrides = struct.unpack(">QQQ", data) - for channel, w in self.dm.ttl_widgets.items(): - w.set_value(ttl_levels & (1 << channel), - ttl_oes & (1 << channel), - ttl_overrides & (1 << channel)) + 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") diff --git a/examples/master/ddb.pyon b/examples/master/ddb.pyon index 7e7778f66..5dd3db64c 100644 --- a/examples/master/ddb.pyon +++ b/examples/master/ddb.pyon @@ -60,19 +60,22 @@ "type": "local", "module": "artiq.coredevice.dds", "class": "DDS", - "arguments": {"channel": 0} + "arguments": {"sysclk": Quantity(Fraction(1000000000, 1), "Hz"), + "channel": 0} }, "dds1": { "type": "local", "module": "artiq.coredevice.dds", "class": "DDS", - "arguments": {"channel": 1} + "arguments": {"sysclk": Quantity(Fraction(1000000000, 1), "Hz"), + "channel": 1} }, "dds2": { "type": "local", "module": "artiq.coredevice.dds", "class": "DDS", - "arguments": {"channel": 2} + "arguments": {"sysclk": Quantity(Fraction(1000000000, 1), "Hz"), + "channel": 2} }, "qc_q1_0": { diff --git a/soc/runtime/dds.c b/soc/runtime/dds.c index 5e94d5e1c..9c0d17e61 100644 --- a/soc/runtime/dds.c +++ b/soc/runtime/dds.c @@ -61,7 +61,7 @@ static void dds_set_one(long long int now, long long int ref_time, int channel, DDS_WRITE(DDS_FTW3, (ftw >> 24) & 0xff); /* We need the RTIO fine timestamp clock to be phase-locked - * to DDS SYNCLK, and divided by an integer DDS_RTIO_CLK_RATIO. + * to DDS SYSCLK, and divided by an integer DDS_RTIO_CLK_RATIO. */ if(phase_mode == PHASE_MODE_CONTINUOUS) { /* Do not clear phase accumulator on FUD */ diff --git a/soc/runtime/dds.h b/soc/runtime/dds.h index e6adc326c..e145b5b01 100644 --- a/soc/runtime/dds.h +++ b/soc/runtime/dds.h @@ -4,9 +4,6 @@ #include #include -/* Number of DDS channels to initialize */ -#define DDS_CHANNEL_COUNT 8 - /* Maximum number of commands in a batch */ #define DDS_MAX_BATCH 16 diff --git a/soc/runtime/moninj.c b/soc/runtime/moninj.c index 336c88ce1..34507d9a6 100644 --- a/soc/runtime/moninj.c +++ b/soc/runtime/moninj.c @@ -39,6 +39,7 @@ struct monitor_reply { long long int ttl_levels; long long int ttl_oes; long long int ttl_overrides; + unsigned int dds_ftws[DDS_CHANNEL_COUNT]; }; static void moninj_monitor(const ip_addr_t *addr, u16_t port) @@ -53,9 +54,11 @@ static void moninj_monitor(const ip_addr_t *addr, u16_t port) for(i=0;i Date: Fri, 19 Jun 2015 15:58:06 -0600 Subject: [PATCH 081/227] novatech409b: cleanup --- artiq/devices/novatech409b/driver.py | 220 +++++++++++---------------- 1 file changed, 86 insertions(+), 134 deletions(-) diff --git a/artiq/devices/novatech409b/driver.py b/artiq/devices/novatech409b/driver.py index 945b959a3..5bea7164e 100644 --- a/artiq/devices/novatech409b/driver.py +++ b/artiq/devices/novatech409b/driver.py @@ -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()) + result = self.port.readline().rstrip().decode() if get_response: - result = self.port.readline().rstrip().decode() - if result != "OK": - raise UnexpectedResponse(result) + 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 From 83fd5fdf168926e56ba359a07feeaf9973ff162b Mon Sep 17 00:00:00 2001 From: Joe Britton Date: Fri, 19 Jun 2015 15:59:21 -0600 Subject: [PATCH 082/227] novatech409b: add unit test --- artiq/test/novatech409b.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 artiq/test/novatech409b.py diff --git a/artiq/test/novatech409b.py b/artiq/test/novatech409b.py new file mode 100644 index 000000000..165b81fad --- /dev/null +++ b/artiq/test/novatech409b.py @@ -0,0 +1,34 @@ +import unittest +import os + +from artiq.devices.novatech409b.driver import Novatech409B + + +novatech409b_device = os.getenv("NOVATECH409B_DEVICE") + + +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") + + +@unittest.skipUnless(novatech409b_device, "no hardware") +class TestNovatech409B(GenericNovatech409BTest, unittest.TestCase): + def setUp(self): + self.driver = Novatech409B(novatech409b_device) + + +class TestNovatech409BSim(GenericNovatech409BTest, unittest.TestCase): + def setUp(self): + self.driver = Novatech409B(None) From f47c2e54e1efa28b3a5e87a7362ef2adda7c5243 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 19 Jun 2015 17:36:46 -0600 Subject: [PATCH 083/227] DDS monitor fixes --- artiq/gateware/rtio/phy/dds.py | 6 +++--- artiq/gui/moninj.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/artiq/gateware/rtio/phy/dds.py b/artiq/gateware/rtio/phy/dds.py index b39b86180..a0daec62b 100644 --- a/artiq/gateware/rtio/phy/dds.py +++ b/artiq/gateware/rtio/phy/dds.py @@ -21,8 +21,8 @@ class AD9858(Module): # keep track of frequency tuning words, before they are FUDed ftws = [Signal(32) for i in range(nchannels)] - for i in range(4): - for c, ftw in enumerate(ftws): + for c, ftw in enumerate(ftws): + for i in range(4): self.sync.rio += \ If(self.rtlink.o.stb & \ (self.rtlink.o.address == 0x0a+i) & \ @@ -31,6 +31,6 @@ class AD9858(Module): ) # FTW to probe on FUD - for c, (probe, ftw) in enumerate(zip(self.probes, ftw)): + for c, (probe, ftw) in enumerate(zip(self.probes, ftws)): fud = self.rtlink.o.stb & (self.rtlink.o.address == 64) self.sync.rio += If(fud & (current_channel == c), probe.eq(ftw)) diff --git a/artiq/gui/moninj.py b/artiq/gui/moninj.py index c106d1a5a..9d8495c5e 100644 --- a/artiq/gui/moninj.py +++ b/artiq/gui/moninj.py @@ -139,7 +139,7 @@ class _DDSWidget(QtGui.QFrame): def set_value(self, ftw): frequency = ftw*self.sysclk/2**32 self._value.setText("{:.3f} MHz" - .format(float(frequency))) + .format(float(frequency)/1e6)) class _DeviceManager: From 827f99e7d3ef01552cdea0345654b97ad678b833 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 19 Jun 2015 17:55:01 -0600 Subject: [PATCH 084/227] gui: more digits in DDS monitor --- artiq/gui/moninj.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/gui/moninj.py b/artiq/gui/moninj.py index 9d8495c5e..280c2f909 100644 --- a/artiq/gui/moninj.py +++ b/artiq/gui/moninj.py @@ -138,7 +138,7 @@ class _DDSWidget(QtGui.QFrame): def set_value(self, ftw): frequency = ftw*self.sysclk/2**32 - self._value.setText("{:.3f} MHz" + self._value.setText("{:.7f} MHz" .format(float(frequency)/1e6)) From 87ea1433d3bef86ad5b3a06651b6d4d880cdc343 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 20 Jun 2015 18:42:39 -0600 Subject: [PATCH 085/227] dds: all working --- artiq/coredevice/dds.py | 2 +- soc/runtime/dds.c | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/artiq/coredevice/dds.py b/artiq/coredevice/dds.py index 903112680..0d27cbf01 100644 --- a/artiq/coredevice/dds.py +++ b/artiq/coredevice/dds.py @@ -117,4 +117,4 @@ class DDS(AutoDB): syscall("dds_set", time_to_cycles(now()), self.channel, self.frequency_to_ftw(frequency), round(phase_offset*2**14), - self.phase_mode) + phase_mode) diff --git a/soc/runtime/dds.c b/soc/runtime/dds.c index 9c0d17e61..9a3790832 100644 --- a/soc/runtime/dds.c +++ b/soc/runtime/dds.c @@ -3,9 +3,10 @@ #include "exceptions.h" #include "rtio.h" +#include "log.h" #include "dds.h" -#define DURATION_WRITE 5 +#define DURATION_WRITE (5 << RTIO_FINE_TS_WIDTH) #define DURATION_INIT (7*DURATION_WRITE) /* not counting FUD */ #define DURATION_PROGRAM (8*DURATION_WRITE) /* not counting FUD */ @@ -50,9 +51,17 @@ void dds_init(long long int timestamp, int channel) DDS_WRITE(DDS_FUD, 0); } -static void dds_set_one(long long int now, long long int ref_time, int channel, +/* Compensation to keep phase continuity when switching from absolute or tracking + * to continuous phase mode. */ +static unsigned int continuous_phase_comp[DDS_CHANNEL_COUNT]; + +static void dds_set_one(long long int now, long long int ref_time, unsigned int channel, unsigned int ftw, unsigned int pow, int phase_mode) { + if(channel >= DDS_CHANNEL_COUNT) { + log("Attempted to set invalid DDS channel"); + return; + } DDS_WRITE(DDS_GPIO, channel); DDS_WRITE(DDS_FTW0, ftw & 0xff); @@ -66,6 +75,7 @@ static void dds_set_one(long long int now, long long int ref_time, int channel, if(phase_mode == PHASE_MODE_CONTINUOUS) { /* Do not clear phase accumulator on FUD */ DDS_WRITE(0x02, 0x00); + pow += continuous_phase_comp[channel]; } else { long long int fud_time; @@ -75,6 +85,7 @@ static void dds_set_one(long long int now, long long int ref_time, int channel, pow -= (ref_time - fud_time)*DDS_RTIO_CLK_RATIO*ftw >> 18; if(phase_mode == PHASE_MODE_TRACKING) pow += ref_time*DDS_RTIO_CLK_RATIO*ftw >> 18; + continuous_phase_comp[channel] = pow; } DDS_WRITE(DDS_POW0, pow & 0xff); From e94e7c6a75d521dae250ed201028526074e962db Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Tue, 5 May 2015 16:25:31 +0200 Subject: [PATCH 086/227] examples/ddb: make rtio_frame a list (json compat) --- examples/master/ddb.pyon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/master/ddb.pyon b/examples/master/ddb.pyon index 5dd3db64c..8cd545873 100644 --- a/examples/master/ddb.pyon +++ b/examples/master/ddb.pyon @@ -117,7 +117,7 @@ "arguments": { "pdq2_devices": ["qc_q1_0", "qc_q1_1", "qc_q1_2", "qc_q1_3"], "rtio_trigger": 7, - "rtio_frame": (2, 3, 4) + "rtio_frame": [2, 3, 4] }, "comment": "Conflicts with dds2 and ttl0-2" }, From 07a58dc0da6331442a5ce18d33945eb61edfbf18 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Tue, 5 May 2015 17:25:09 +0200 Subject: [PATCH 087/227] wavesynth: cleanup, refactor --- artiq/test/coefficients.py | 8 ++--- artiq/wavesynth/coefficients.py | 55 +++++++++++++++------------------ 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/artiq/test/coefficients.py b/artiq/test/coefficients.py index 20fd9ba99..35bb3f35f 100644 --- a/artiq/test/coefficients.py +++ b/artiq/test/coefficients.py @@ -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() diff --git a/artiq/wavesynth/coefficients.py b/artiq/wavesynth/coefficients.py index f0f489359..ee9efb351 100644 --- a/artiq/wavesynth/coefficients.py +++ b/artiq/wavesynth/coefficients.py @@ -5,11 +5,19 @@ from scipy.special import binom class UnivariateMultiSpline: """Multidimensional wrapper around `scipy.interpolate.sp*` functions. - `scipy.inteprolate.splprep` unfortunately does only up to 12 dimsions. + `scipy.inteprolate.splprep` is limited to 12 dimensions. """ - def __init__(self, x, y, order=4): + def __init__(self, x, y, *, x0=None, order=4, **kwargs): self.order = order - self.s = [splrep(x, yi, k=order - 1) for yi in y] + self.x = x + self.s = [] + for i, yi in enumerate(y): + if x0 is not None: + yi = self.upsample_knots(x0[i], yi, x) + self.s.append(splrep(x, yi, k=order - 1, **kwargs)) + + def upsample_knots(self, x0, y0, x): + return splev(x, splrep(x0, y0, k=self.order - 1)) def lev(self, x, *a, **k): return np.array([splev(x, si) for si in self.s]) @@ -30,20 +38,6 @@ class UnivariateMultiSpline: return np.array([self.lev(x, der=i) for i in range(self.order)]) -class UnivariateMultiSparseSpline(UnivariateMultiSpline): - def __init__(self, d, x0=None, order=4): - self.order = order - self.n = sorted(set(n for x, n, y in d)) - self.s = [] - for n in self.n: - x, y = np.array([(x, y) for x, ni, y in d if n == ni]).T - if x0 is not None: - y0 = splev(x0, splrep(x, y, k=order - 1)) - x, y = x0, y0 - s = splrep(x, y, k=order - 1) - self.s.append(s) - - def pad_const(x, n, axis=0): """Prefix and postfix the array `x` by `n` repetitions of the first and last value along `axis`. @@ -58,28 +52,28 @@ def pad_const(x, n, axis=0): def build_segment(durations, coefficients, target="bias", - variable="amplitude"): + variable="amplitude", compress=True): """Build a wavesynth-style segment from homogeneous duration and coefficient data. :param durations: 1D sequence of line durations. - :param coefficients: 3D array with shape `(n, m, len(x))`, + :param coefficients: 3D array with shape `(n, m, len(durations))`, with `n` being the interpolation order + 1 and `m` the number of channels. :param target: The target component of the channel to affect. :param variable: The variable within the target component. + :param compress: If `True`, skip zero high order coefficients. """ for dxi, yi in zip(durations, coefficients.transpose()): - d = {"duration": int(dxi)} - d["channel_data"] = cd = [] + cd = [] for yij in yi: cdj = [] for yijk in reversed(yij): - if cdj or abs(yijk): + if cdj or abs(yijk) or not compress: cdj.append(float(yijk)) cdj.reverse() cd.append({target: {variable: cdj}}) - yield d + yield {"duration": int(dxi), "channel_data": cd} class CoefficientSource: @@ -125,7 +119,7 @@ class CoefficientSource: with `n` being the number of channels.""" raise NotImplementedError - def get_segment_data(self, start, stop, scale, cutoff=1e-12, + def get_segment_data(self, start, stop, scale, *, cutoff=1e-12, target="bias", variable="amplitude"): """Build wavesynth segment data. @@ -154,7 +148,7 @@ class CoefficientSource: """ for i, line in enumerate(self.get_segment_data(*args, **kwargs)): if i == 0: - line["trigger"] = True + line["trigger"] = trigger segment.add_line(**line) @@ -176,7 +170,7 @@ class SplineSource(CoefficientSource): self.y = pad_const(self.y, order, axis=1) assert self.y.shape[1] == self.x.shape[0] - self.spline = UnivariateMultiSpline(self.x, self.y, order) + self.spline = UnivariateMultiSpline(self.x, self.y, order=order) def crop_x(self, start, stop): ia, ib = np.searchsorted(self.x, (start, stop)) @@ -187,7 +181,8 @@ class SplineSource(CoefficientSource): return np.r_[start, x, stop] def scale_x(self, x, scale, min_duration=1, min_length=20): - """ + """Enforce, round, and scale x to device-dependent values. + Due to minimum duration and/or minimum segment length constraints this method may drop samples from `x_sample` or adjust `durations` to comply. But `x_sample` and `durations` should be kept consistent. @@ -235,14 +230,14 @@ class ComposingSplineSource(SplineSource): self.y = pad_const(self.y, order, axis=2) assert self.y.shape[2] == self.x.shape[0] - self.splines = [UnivariateMultiSpline(self.x, yi, order) + self.splines = [UnivariateMultiSpline(self.x, yi, order=order) for yi in self.y] # need to resample/upsample the shim splines to the master spline knots # shim knot spacings can span an master spline knot and thus would # cross a highest order derivative boundary - self.components = UnivariateMultiSparseSpline( - components, self.x, order) + y0, x0 = zip(*components) + self.components = UnivariateMultiSpline(self.x, y0, x0=x0, order=order) def __call__(self, t, gain={}, offset={}): der = list((set(self.components.n) | set(offset)) From 45ec5dbe84872e340115cb4a9764f2826e37d389 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Sat, 20 Jun 2015 23:33:55 -0600 Subject: [PATCH 088/227] ad9858: make wb data 8 bit wide matches actual dds bus data width and saves bram --- artiq/gateware/ad9858.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/gateware/ad9858.py b/artiq/gateware/ad9858.py index d620cac43..5b475d628 100644 --- a/artiq/gateware/ad9858.py +++ b/artiq/gateware/ad9858.py @@ -38,7 +38,7 @@ 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=8) self.bus = bus # # # From b7d976e7dbfc4b6f4f3d32c0c1d976b71add4362 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sun, 21 Jun 2015 08:39:58 -0600 Subject: [PATCH 089/227] ddb: fix pdq comment --- examples/master/ddb.pyon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/master/ddb.pyon b/examples/master/ddb.pyon index 8cd545873..75c17fec8 100644 --- a/examples/master/ddb.pyon +++ b/examples/master/ddb.pyon @@ -119,7 +119,7 @@ "rtio_trigger": 7, "rtio_frame": [2, 3, 4] }, - "comment": "Conflicts with dds2 and ttl0-2" + "comment": "Uses ttl 0, 1, 2 and 5" }, "lda": { From 9f3f9255a2ab9121f851ee3bb6ef0cd9e8e03989 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sun, 21 Jun 2015 08:40:10 -0600 Subject: [PATCH 090/227] soc: increase DDS output FIFO sizes --- soc/targets/artiq_kc705.py | 4 +++- soc/targets/artiq_pipistrello.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/soc/targets/artiq_kc705.py b/soc/targets/artiq_kc705.py index 9794b6c5b..740b68b22 100644 --- a/soc/targets/artiq_kc705.py +++ b/soc/targets/artiq_kc705.py @@ -83,7 +83,9 @@ class NIST_QC1(MiniSoC, AMPSoC): self.add_constant("DDS_CHANNEL_COUNT", 8) phy = dds.AD9858(platform.request("dds")) self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4)) + rtio_channels.append(rtio.Channel.from_phy(phy, + ofifo_depth=512, + ififo_depth=4)) # RTIO core self.submodules.rtio_crg = _RTIOCRG(platform, self.crg.pll_sys) diff --git a/soc/targets/artiq_pipistrello.py b/soc/targets/artiq_pipistrello.py index 17ca3f6c6..96c595f5b 100644 --- a/soc/targets/artiq_pipistrello.py +++ b/soc/targets/artiq_pipistrello.py @@ -118,7 +118,9 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd self.add_constant("DDS_CHANNEL_COUNT", 8) phy = dds.AD9858(platform.request("dds")) self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4)) + rtio_channels.append(rtio.Channel.from_phy(phy, + ofifo_depth=512, + ififo_depth=4)) # RTIO core self.submodules.rtio_crg = _RTIOCRG(platform) From cd249b2f6615b8aac62b7a9b4e7fe0dd072ab7a7 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Mon, 22 Jun 2015 19:03:00 -0600 Subject: [PATCH 091/227] pipistrello: run at 83+1/3 MHz, cleanup CRG --- soc/targets/artiq_pipistrello.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/soc/targets/artiq_pipistrello.py b/soc/targets/artiq_pipistrello.py index 96c595f5b..309591b8b 100644 --- a/soc/targets/artiq_pipistrello.py +++ b/soc/targets/artiq_pipistrello.py @@ -1,3 +1,5 @@ +from fractions import Fraction + from migen.fhdl.std import * from migen.bank.description import * from migen.bank import wbgen @@ -13,18 +15,18 @@ from artiq.gateware.rtio.phy import ttl_simple, dds class _RTIOCRG(Module, AutoCSR): - def __init__(self, platform): + def __init__(self, platform, clk_freq): self._clock_sel = CSRStorage() self.clock_domains.cd_rtio = ClockDomain(reset_less=True) - # 75MHz -> 125MHz + f = Fraction(125*1000*1000, clk_freq) rtio_internal_clk = Signal() self.specials += Instance("DCM_CLKGEN", p_CLKFXDV_DIVIDE=2, - p_CLKFX_DIVIDE=3, + p_CLKFX_DIVIDE=f.denominator, p_CLKFX_MD_MAX=1.6, - p_CLKFX_MULTIPLY=5, - p_CLKIN_PERIOD=1e3/75, + p_CLKFX_MULTIPLY=f.numerator, + p_CLKIN_PERIOD=1e9/clk_freq, p_SPREAD_SPECTRUM="NONE", p_STARTUP_WAIT="FALSE", i_CLKIN=ClockSignal(), @@ -123,7 +125,7 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd ififo_depth=4)) # RTIO core - self.submodules.rtio_crg = _RTIOCRG(platform) + self.submodules.rtio_crg = _RTIOCRG(platform, self.clk_freq) self.submodules.rtio = rtio.RTIO(rtio_channels, clk_freq=125000000) self.add_constant("RTIO_FINE_TS_WIDTH", self.rtio.fine_ts_width) From 5b3eac1d965e4d1b5fbf37085cecbc751206766f Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Mon, 22 Jun 2015 23:24:30 -0600 Subject: [PATCH 092/227] pipistrello: tweak fifo depths a bit ise being dull again, inferring all but one 64x64 fifo as bram... minimum bram depth is 256 anyway --- soc/targets/artiq_pipistrello.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/soc/targets/artiq_pipistrello.py b/soc/targets/artiq_pipistrello.py index 309591b8b..5cdf977dc 100644 --- a/soc/targets/artiq_pipistrello.py +++ b/soc/targets/artiq_pipistrello.py @@ -99,21 +99,21 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd phy = ttl_simple.Inout(platform.request("xtrig", 0)) self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy)) + rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4)) for i in range(16): phy = ttl_simple.Output(platform.request("ttl", i)) self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy)) + rtio_channels.append(rtio.Channel.from_phy(phy, ofifo_depth=256)) phy = ttl_simple.Output(platform.request("ext_led", 0)) self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy)) + rtio_channels.append(rtio.Channel.from_phy(phy, ofifo_depth=4)) for i in range(2, 5): phy = ttl_simple.Output(platform.request("user_led", i)) self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy)) + rtio_channels.append(rtio.Channel.from_phy(phy, ofifo_depth=4)) self.add_constant("RTIO_TTL_COUNT", len(rtio_channels)) self.add_constant("RTIO_DDS_CHANNEL", len(rtio_channels)) From 7821d0f7c8e862eb8999bb3fc299ca21ad59850d Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Tue, 23 Jun 2015 17:05:46 +0200 Subject: [PATCH 093/227] manual: explain how to list attached serial devices on windows/linux --- doc/manual/faq.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/manual/faq.rst b/doc/manual/faq.rst index de9e59006..b98c3f2c1 100644 --- a/doc/manual/faq.rst +++ b/doc/manual/faq.rst @@ -74,3 +74,19 @@ sub-experiments difficult and fragile. You can however always use the scheduler API to achieve the same (``scheduler.yield(duration=0)``) or wrap your own generators/coroutines/tasks in regular functions that you then expose as part of the API. + +list the serial devices attached to my Linux/Windows system? +------------------------------------------------------------ + +You can do it by running:: + + $ python3 -m serial.tools.list_ports -v + +It will give you the ``/dev/ttyUSBxx`` (or the ``COMxx`` for Windows) device +names. +The ``hwid:`` field gives you the string you can pass via the ``hwgrep://`` +feature of pyserial +`serial_for_url() `_ +in order to open a serial device. + +See the :ref:`TDC001 documentation ` for an example of ``hwgrep://`` usage. \ No newline at end of file From f0dddd9f391d1da7b660e85f3ff92d353f532508 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Tue, 23 Jun 2015 18:14:25 +0200 Subject: [PATCH 094/227] manual: hwgrep is the preferred way of specifying a serial device --- doc/manual/faq.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/manual/faq.rst b/doc/manual/faq.rst index b98c3f2c1..a53b69689 100644 --- a/doc/manual/faq.rst +++ b/doc/manual/faq.rst @@ -89,4 +89,8 @@ feature of pyserial `serial_for_url() `_ in order to open a serial device. +The preferred way to specify a serial device is to make use of the ``hwgrep://`` +URL: it allows to select the serial device by its USB vendor ID, product +ID and/or serial number. Those never change, unlike the device file name. + See the :ref:`TDC001 documentation ` for an example of ``hwgrep://`` usage. \ No newline at end of file From 71721a152ed96352e8026596ee9bd77d14df945a Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Tue, 23 Jun 2015 18:35:32 +0200 Subject: [PATCH 095/227] artiq_rpctool: list-methods also prints class docstring --- artiq/frontend/artiq_rpctool.py | 6 ++++-- artiq/protocols/pc_rpc.py | 11 +++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/artiq/frontend/artiq_rpctool.py b/artiq/frontend/artiq_rpctool.py index bdecadb89..a97c5bb70 100755 --- a/artiq/frontend/artiq_rpctool.py +++ b/artiq/frontend/artiq_rpctool.py @@ -36,8 +36,10 @@ 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"]) + for name, (argspec, docstring) in sorted(doc["methods"].items()): args = "" for arg in argspec["args"]: args += arg diff --git a/artiq/protocols/pc_rpc.py b/artiq/protocols/pc_rpc.py index da2c5ae66..918c26c17 100644 --- a/artiq/protocols/pc_rpc.py +++ b/artiq/protocols/pc_rpc.py @@ -442,15 +442,18 @@ class Server(_AsyncioServer): try: if obj["action"] == "get_rpc_method_list": members = inspect.getmembers(target, inspect.ismethod) - methods = {} + doc = { + "docstring": target.__doc__, + "methods": {} + } for name, method in members: if name.startswith("_"): continue method = getattr(target, name) argspec = inspect.getfullargspec(method) - methods[name] = (dict(argspec.__dict__), - inspect.getdoc(method)) - obj = {"status": "ok", "ret": methods} + doc["methods"][name] = (dict(argspec.__dict__), + inspect.getdoc(method)) + obj = {"status": "ok", "ret": doc} elif obj["action"] == "call": logger.debug("calling %s", _PrettyPrintCall(obj)) method = getattr(target, obj["name"]) From 4ba8951ae17eb03d7b6d51c3328cd67452a4c48c Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Tue, 23 Jun 2015 19:31:52 +0000 Subject: [PATCH 096/227] rpc: fix indentation of class docstring --- artiq/frontend/artiq_rpctool.py | 1 + artiq/protocols/pc_rpc.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/artiq/frontend/artiq_rpctool.py b/artiq/frontend/artiq_rpctool.py index a97c5bb70..a26d70cf7 100755 --- a/artiq/frontend/artiq_rpctool.py +++ b/artiq/frontend/artiq_rpctool.py @@ -39,6 +39,7 @@ def list_methods(remote): 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"]: diff --git a/artiq/protocols/pc_rpc.py b/artiq/protocols/pc_rpc.py index 918c26c17..d50c13591 100644 --- a/artiq/protocols/pc_rpc.py +++ b/artiq/protocols/pc_rpc.py @@ -443,7 +443,7 @@ class Server(_AsyncioServer): if obj["action"] == "get_rpc_method_list": members = inspect.getmembers(target, inspect.ismethod) doc = { - "docstring": target.__doc__, + "docstring": inspect.getdoc(target), "methods": {} } for name, method in members: From 064ddb48bd161a8fc255415577a18626180cbd7c Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Tue, 23 Jun 2015 19:44:02 +0000 Subject: [PATCH 097/227] manual: minor fixes --- doc/manual/ndsp_reference.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/manual/ndsp_reference.rst b/doc/manual/ndsp_reference.rst index 4871b3cad..fa2f85e31 100644 --- a/doc/manual/ndsp_reference.rst +++ b/doc/manual/ndsp_reference.rst @@ -34,7 +34,7 @@ Client Lab Brick Digital Attenuator (LDA) ---------------------------------- -On Linux, you need to give your user access to the usb device. +On Linux, you need to give your user access to the USB device. You can do that by creating a file under ``/etc/udev/rules.d/`` named ``99-lda.rules`` with the following content:: @@ -51,10 +51,10 @@ Then, to run the Lab Brick Digital Attenuator (LDA) controller:: $ lda_controller -d SN:xxxxx -The serial number must contain 5 digits, prepend any number of 0 necessary. +The serial number must contain exactly 5 digits, prepend it with the necessary number of 0s. Also, the ``SN:`` prefix is mandatory. -You can chose the exact LDA model with the ``-P`` parameter. The default being LDA-102. +You can choose the LDA model with the ``-P`` parameter. The default is LDA-102. Driver ++++++ From 9c96ebf7d4224f06554d8d343b10dda90a116eab Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Thu, 25 Jun 2015 03:06:03 +0200 Subject: [PATCH 098/227] nist_qc2: add fmc adapter io file --- artiq/gateware/nist_qc2.py | 77 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 artiq/gateware/nist_qc2.py diff --git a/artiq/gateware/nist_qc2.py b/artiq/gateware/nist_qc2.py new file mode 100644 index 000000000..7f93e4e49 --- /dev/null +++ b/artiq/gateware/nist_qc2.py @@ -0,0 +1,77 @@ +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_n", Pins("LPC:LA21_N")), + Subsignal("wr_n", Pins("LPC:LA24_P")), + Subsignal("rd_n", Pins("LPC:LA25_N")), + Subsignal("rst_in", 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")), +] From 31d551e619a9fbb04bf1c50a86cd58dfb8c21a4d Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Thu, 25 Jun 2015 12:35:50 -0600 Subject: [PATCH 099/227] travis: cleanup get-xilinx.sh a bit --- .travis/get-xilinx.sh | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.travis/get-xilinx.sh b/.travis/get-xilinx.sh index c37aead48..88a2f1e6d 100755 --- a/.travis/get-xilinx.sh +++ b/.travis/get-xilinx.sh @@ -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/" +MACADDR=$macaddress +export LD_PRELOAD=$PWD/impersonate_macaddress/impersonate_macaddress.so +EOF From 1c7ab97b9199ab26bac854ff7f69f4d56cca88fa Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Thu, 25 Jun 2015 12:40:04 -0600 Subject: [PATCH 100/227] travis: cleanup unused/redundant env vars --- .travis.yml | 6 ++---- .travis/get-anaconda.sh | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index bec5ffcff..f89e4da82 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,16 +4,14 @@ python: 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 + - if [ $BUILD_SOC -ne 0 ]; then . ./.travis/get-xilinx.sh; fi - . ./.travis/get-toolchain.sh - - if [ $BUILD_SOC -ne 0 ]; then ./.travis/get-xilinx.sh; fi - - ./.travis/get-anaconda.sh + - . ./.travis/get-anaconda.sh - source $HOME/miniconda/bin/activate py34 - conda install pip coverage binstar migen cython - pip install coveralls diff --git a/.travis/get-anaconda.sh b/.travis/get-anaconda.sh index 992c3d31a..6c7d7d098 100755 --- a/.travis/get-anaconda.sh +++ b/.travis/get-anaconda.sh @@ -1,5 +1,6 @@ #!/bin/sh +export PATH=$HOME/miniconda/bin:$PATH wget http://repo.continuum.io/miniconda/Miniconda3-3.7.3-Linux-x86_64.sh -O miniconda.sh bash miniconda.sh -b -p $HOME/miniconda hash -r From 51eb8350302af7310f8ed8fe35d41f9239a191b5 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Thu, 25 Jun 2015 12:50:09 -0600 Subject: [PATCH 101/227] travis: use latest miniconda --- .travis/get-anaconda.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis/get-anaconda.sh b/.travis/get-anaconda.sh index 6c7d7d098..9f661b2d7 100755 --- a/.travis/get-anaconda.sh +++ b/.travis/get-anaconda.sh @@ -1,7 +1,7 @@ #!/bin/sh export PATH=$HOME/miniconda/bin:$PATH -wget http://repo.continuum.io/miniconda/Miniconda3-3.7.3-Linux-x86_64.sh -O miniconda.sh +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 From b820b7a3e1b7e59bb3eedca6386ea6b4ebdc2714 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Thu, 25 Jun 2015 12:56:50 -0600 Subject: [PATCH 102/227] Revert "travis: use latest miniconda" Seems to segfault on travis. This reverts commit 51eb8350302af7310f8ed8fe35d41f9239a191b5. --- .travis/get-anaconda.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis/get-anaconda.sh b/.travis/get-anaconda.sh index 9f661b2d7..6c7d7d098 100755 --- a/.travis/get-anaconda.sh +++ b/.travis/get-anaconda.sh @@ -1,7 +1,7 @@ #!/bin/sh export PATH=$HOME/miniconda/bin:$PATH -wget http://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh +wget http://repo.continuum.io/miniconda/Miniconda3-3.7.3-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 From 52030ab1b74f2d2e541772f13055f53c3bca9c93 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Thu, 25 Jun 2015 13:08:56 -0600 Subject: [PATCH 103/227] travis: export MACADDR, do not source get-xilinx --- .travis.yml | 2 +- .travis/get-xilinx.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index f89e4da82..1bee784bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ env: before_install: - mkdir -p $HOME/.mlabs - if [ $TRAVIS_PULL_REQUEST != false ]; then BUILD_SOC=0; fi - - if [ $BUILD_SOC -ne 0 ]; then . ./.travis/get-xilinx.sh; fi + - if [ $BUILD_SOC -ne 0 ]; then ./.travis/get-xilinx.sh; fi - . ./.travis/get-toolchain.sh - . ./.travis/get-anaconda.sh - source $HOME/miniconda/bin/activate py34 diff --git a/.travis/get-xilinx.sh b/.travis/get-xilinx.sh index 88a2f1e6d..95d50e41c 100755 --- a/.travis/get-xilinx.sh +++ b/.travis/get-xilinx.sh @@ -31,6 +31,6 @@ make -C impersonate_macaddress 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/" -MACADDR=$macaddress +export MACADDR=$macaddress export LD_PRELOAD=$PWD/impersonate_macaddress/impersonate_macaddress.so EOF From 48e0a2ad37b37c872ae09a049b7525e097db1ef8 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Thu, 25 Jun 2015 13:28:05 -0600 Subject: [PATCH 104/227] artiq_flash: echo commands for a bit more verbosity --- artiq/frontend/artiq_flash.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/artiq/frontend/artiq_flash.sh b/artiq/frontend/artiq_flash.sh index b5447b197..f2ff61f4f 100755 --- a/artiq/frontend/artiq_flash.sh +++ b/artiq/frontend/artiq_flash.sh @@ -1,6 +1,9 @@ #!/bin/bash +# exit on error set -e +# print commands +set -v ARTIQ_PREFIX=$(python3 -c "import artiq; print(artiq.__path__[0])") From dd2c1d1b72a0220585a469a0f862f15f81e8049e Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Thu, 25 Jun 2015 13:50:38 -0600 Subject: [PATCH 105/227] travis: try again with latest conda --- .travis/get-anaconda.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis/get-anaconda.sh b/.travis/get-anaconda.sh index 6c7d7d098..9f661b2d7 100755 --- a/.travis/get-anaconda.sh +++ b/.travis/get-anaconda.sh @@ -1,7 +1,7 @@ #!/bin/sh export PATH=$HOME/miniconda/bin:$PATH -wget http://repo.continuum.io/miniconda/Miniconda3-3.7.3-Linux-x86_64.sh -O miniconda.sh +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 From e6a4c2fb369cbf887606743b6595062126bf2f93 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 26 Jun 2015 12:05:11 +0200 Subject: [PATCH 106/227] dds: make it easier to specify phase --- artiq/coredevice/dds.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/artiq/coredevice/dds.py b/artiq/coredevice/dds.py index 0d27cbf01..f016b4cc0 100644 --- a/artiq/coredevice/dds.py +++ b/artiq/coredevice/dds.py @@ -3,7 +3,7 @@ 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 @@ -104,17 +104,17 @@ class DDS(AutoDB): self.phase_mode = phase_mode @kernel - def set(self, frequency, phase_mode=PHASE_MODE_DEFAULT, phase_offset=0): + def set(self, frequency, phase=0, phase_mode=_PHASE_MODE_DEFAULT): """Sets the DDS channel to the specified frequency and phase. :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", time_to_cycles(now()), self.channel, - self.frequency_to_ftw(frequency), round(phase_offset*2**14), + self.frequency_to_ftw(frequency), round(phase*2**14), phase_mode) From c71fe2979215727bf7aa20293ec2467147cd1b38 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 26 Jun 2015 16:20:13 +0200 Subject: [PATCH 107/227] simplify unit system and use floats by default --- artiq/__init__.py | 4 +- artiq/coredevice/core.py | 6 +- artiq/devices/lda/driver.py | 24 +-- artiq/devices/thorlabs_tcube/driver.py | 33 ++-- artiq/gui/moninj.py | 3 +- artiq/language/core.py | 2 +- artiq/language/units.py | 259 +------------------------ artiq/master/worker.py | 3 +- artiq/protocols/pyon.py | 9 - artiq/test/full_stack.py | 35 +--- artiq/test/lda.py | 6 +- artiq/test/thorlabs_tcube.py | 2 +- artiq/test/transforms.py | 14 +- artiq/transforms/lower_units.py | 190 ------------------ artiq/transforms/tools.py | 18 +- benchmarks/rpc_timing.py | 2 +- examples/master/ddb.pyon | 9 +- 17 files changed, 43 insertions(+), 576 deletions(-) delete mode 100644 artiq/transforms/lower_units.py diff --git a/artiq/__init__.py b/artiq/__init__.py index 934cfbf49..ebdb62601 100644 --- a/artiq/__init__.py +++ b/artiq/__init__.py @@ -1,6 +1,4 @@ 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.language.units import * diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index 2467980f5..7f7549115 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -5,7 +5,6 @@ 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 @@ -61,13 +60,10 @@ class Core(AutoDB): 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) diff --git a/artiq/devices/lda/driver.py b/artiq/devices/lda/driver.py index 3f5aaa2e2..c198f04c7 100644 --- a/artiq/devices/lda/driver.py +++ b/artiq/devices/lda/driver.py @@ -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): @@ -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: diff --git a/artiq/devices/thorlabs_tcube/driver.py b/artiq/devices/thorlabs_tcube/driver.py index b3403a03d..e7e759d5a 100644 --- a/artiq/devices/thorlabs_tcube/driver.py +++ b/artiq/devices/thorlabs_tcube/driver.py @@ -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__) @@ -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 @@ -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)) @@ -390,7 +388,7 @@ class Tpz(Tcube): get_msg = self.send_request(MGMSG.PZ_REQ_OUTPUTVOLTS, [MGMSG.PZ_GET_OUTPUTVOLTS], 1) - return st.unpack("` method. """ - output = strip_unit(output, "V") volt = round(output*32767/self.voltage_limit) payload = st.pack("` 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) voltage_limit, hub_analog_input = st.unpack(" 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) diff --git a/artiq/transforms/tools.py b/artiq/transforms/tools.py index d9daf838f..1b68fd0f3 100644 --- a/artiq/transforms/tools.py +++ b/artiq/transforms/tools.py @@ -11,7 +11,7 @@ embeddable_funcs = ( 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 +61,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 +83,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 +92,7 @@ def eval_constant(node): _replaceable_funcs = { "bool", "int", "float", "round", "int64", "round64", "Fraction", - "time_to_cycles", "cycles_to_time", - "Quantity" + "time_to_cycles", "cycles_to_time" } diff --git a/benchmarks/rpc_timing.py b/benchmarks/rpc_timing.py index b14da820a..33e8152d9 100644 --- a/benchmarks/rpc_timing.py +++ b/benchmarks/rpc_timing.py @@ -22,7 +22,7 @@ class RPCTiming(Experiment, AutoDB): t1 = self.core.get_rtio_time() self.nop(10) t2 = self.core.get_rtio_time() - self.ts[i] = float(t2.amount - t1.amount) + self.ts[i] = t2 - t1 def run(self): self.bench() diff --git a/examples/master/ddb.pyon b/examples/master/ddb.pyon index 75c17fec8..ff6772fcb 100644 --- a/examples/master/ddb.pyon +++ b/examples/master/ddb.pyon @@ -60,22 +60,19 @@ "type": "local", "module": "artiq.coredevice.dds", "class": "DDS", - "arguments": {"sysclk": Quantity(Fraction(1000000000, 1), "Hz"), - "channel": 0} + "arguments": {"sysclk": 1e9, "channel": 0} }, "dds1": { "type": "local", "module": "artiq.coredevice.dds", "class": "DDS", - "arguments": {"sysclk": Quantity(Fraction(1000000000, 1), "Hz"), - "channel": 1} + "arguments": {"sysclk": 1e9, "channel": 1} }, "dds2": { "type": "local", "module": "artiq.coredevice.dds", "class": "DDS", - "arguments": {"sysclk": Quantity(Fraction(1000000000, 1), "Hz"), - "channel": 2} + "arguments": {"sysclk": 1e9, "channel": 2} }, "qc_q1_0": { From c38110201942a6ed982c44e5ae3f600b42c64d02 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Fri, 26 Jun 2015 18:22:08 +0200 Subject: [PATCH 108/227] manual: fix faq item title about determining pyserial URL by serial number --- doc/manual/faq.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/manual/faq.rst b/doc/manual/faq.rst index a53b69689..2e95d2864 100644 --- a/doc/manual/faq.rst +++ b/doc/manual/faq.rst @@ -75,10 +75,11 @@ scheduler API to achieve the same (``scheduler.yield(duration=0)``) or wrap your own generators/coroutines/tasks in regular functions that you then expose as part of the API. -list the serial devices attached to my Linux/Windows system? ------------------------------------------------------------- +determine the pyserial URL to attach to a device by its serial number? +---------------------------------------------------------------------- -You can do it by running:: +You can list your system's serial devices and print their vendor/product +id and serial number by running:: $ python3 -m serial.tools.list_ports -v From 07ceed9512e57e6577c47333ebc61059071c8e9e Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Fri, 26 Jun 2015 11:09:08 -0600 Subject: [PATCH 109/227] artiq_flash.sh: back down on verbosity --- artiq/frontend/artiq_flash.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/frontend/artiq_flash.sh b/artiq/frontend/artiq_flash.sh index f2ff61f4f..2ac6163ef 100755 --- a/artiq/frontend/artiq_flash.sh +++ b/artiq/frontend/artiq_flash.sh @@ -3,7 +3,7 @@ # exit on error set -e # print commands -set -v +#set -x ARTIQ_PREFIX=$(python3 -c "import artiq; print(artiq.__path__[0])") From a7bbcdc1ad2841ffd090fbd5b6a02ff01a77b238 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 27 Jun 2015 21:15:17 +0200 Subject: [PATCH 110/227] targets/pipistrello: mon -> moninj --- soc/targets/artiq_pipistrello.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/soc/targets/artiq_pipistrello.py b/soc/targets/artiq_pipistrello.py index 5cdf977dc..5d6f900c1 100644 --- a/soc/targets/artiq_pipistrello.py +++ b/soc/targets/artiq_pipistrello.py @@ -60,7 +60,7 @@ class NIST_QC1(BaseSoC, AMPSoC): "rtio": None, # mapped on Wishbone instead "rtio_crg": 13, "kernel_cpu": 14, - "rtio_mon": 15 + "rtio_moninj": 15 } csr_map.update(BaseSoC.csr_map) mem_map = { @@ -130,7 +130,7 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd clk_freq=125000000) self.add_constant("RTIO_FINE_TS_WIDTH", self.rtio.fine_ts_width) self.add_constant("DDS_RTIO_CLK_RATIO", 8 >> self.rtio.fine_ts_width) - self.submodules.rtio_mon = rtio.MonInj(rtio_channels) + self.submodules.rtio_moninj = rtio.MonInj(rtio_channels) # CPU connections rtio_csrs = self.rtio.get_csrs() From 2d475e146bf4d90dfa6362cee3540051a1de3a0a Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 27 Jun 2015 22:47:36 +0200 Subject: [PATCH 111/227] runtime/flash_storage: use log not printf --- soc/runtime/flash_storage.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/soc/runtime/flash_storage.c b/soc/runtime/flash_storage.c index 547087fd0..34bc3a508 100644 --- a/soc/runtime/flash_storage.c +++ b/soc/runtime/flash_storage.c @@ -9,6 +9,7 @@ #include #include +#include "log.h" #include "flash_storage.h" #if (defined CSR_SPIFLASH_BASE && defined SPIFLASH_PAGE_SIZE) @@ -62,21 +63,21 @@ static int record_iter_next(struct iter_state *is, struct record *record, int *f return 0; if(record->size < 6) { - printf("flash_storage might be corrupted: record size is %u (<6) at address %08x\n", record->size, record->raw_record); + log("flash_storage might be corrupted: record size is %u (<6) at address %08x", record->size, record->raw_record); if(fatal) *fatal = 1; return 0; } if(is->seek > is->buf_len - sizeof(record->size) - 2) { /* 2 is the minimum key length */ - printf("flash_storage might be corrupted: END_MARKER missing at the end of the storage sector\n"); + log("flash_storage might be corrupted: END_MARKER missing at the end of the storage sector"); if(fatal) *fatal = 1; return 0; } if(record->size > is->buf_len - is->seek) { - printf("flash_storage might be corrupted: invalid record_size %d at address %08x\n", record->size, record->raw_record); + log("flash_storage might be corrupted: invalid record_size %d at address %08x", record->size, record->raw_record); if(fatal) *fatal = 1; return 0; @@ -86,7 +87,7 @@ static int record_iter_next(struct iter_state *is, struct record *record, int *f record->key_len = strnlen(record->key, record->size - sizeof(record->size)) + 1; if(record->key_len == record->size - sizeof(record->size) + 1) { - printf("flash_storage might be corrupted: invalid key length at address %08x\n", record->raw_record); + log("flash_storage might be corrupted: invalid key length at address %08x", record->raw_record); if(fatal) *fatal = 1; return 0; @@ -258,7 +259,7 @@ int fs_write(char *key, void *buffer, unsigned int buf_len) return 0; // Storage is definitely full. fatal_error: - printf("fatal error: flash storage might be corrupted\n"); + log("fatal error: flash storage might be corrupted"); return 0; } @@ -289,7 +290,7 @@ unsigned int fs_read(char *key, void *buffer, unsigned int buf_len, unsigned int } if(fatal) - printf("fatal error: flash storage might be corrupted\n"); + log("fatal error: flash storage might be corrupted"); return read_length; } From 3bd7f117375b537f02dca73700a62377b170f124 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 27 Jun 2015 22:48:41 +0200 Subject: [PATCH 112/227] update lwip --- soc/runtime/lwip | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/soc/runtime/lwip b/soc/runtime/lwip index 737a6921c..d3217718a 160000 --- a/soc/runtime/lwip +++ b/soc/runtime/lwip @@ -1 +1 @@ -Subproject commit 737a6921c3fd9ef6ace17685b208b1af394f42b6 +Subproject commit d3217718a904ffd3b6f6cd12d14d526813d46d7a From 8b5b219a188c5414f761b6aa75517df5b15ae57c Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 27 Jun 2015 23:51:48 +0200 Subject: [PATCH 113/227] runtime: provide fixdfdi --- soc/runtime/services.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/soc/runtime/services.c b/soc/runtime/services.c index 4e5292506..74bdeef71 100644 --- a/soc/runtime/services.c +++ b/soc/runtime/services.c @@ -17,8 +17,8 @@ extern __divsi3, __modsi3, __ledf2, __gedf2, __unorddf2, __eqdf2, __ltdf2, __divsf3, __lshrdi3, __muldi3, __divdi3, __ashldi3, __ashrdi3, __udivmoddi4, __floatsisf, __floatunsisf, __fixsfsi, __fixunssfsi, __adddf3, __subdf3, __muldf3, __divdf3, __floatsidf, __floatunsidf, - __floatdidf, __fixdfsi, __fixunsdfsi, __clzsi2, __ctzsi2, __udivdi3, - __umoddi3, __moddi3; + __floatdidf, __fixdfsi, __fixdfdi, __fixunsdfsi, __clzsi2, __ctzsi2, + __udivdi3, __umoddi3, __moddi3; #pragma GCC diagnostic pop static const struct symbol compiler_rt[] = { @@ -55,6 +55,7 @@ static const struct symbol compiler_rt[] = { {"floatunsidf", &__floatunsidf}, {"floatdidf", &__floatdidf}, {"fixdfsi", &__fixdfsi}, + {"fixdfdi", &__fixdfdi}, {"fixunsdfsi", &__fixunsdfsi}, {"clzsi2", &__clzsi2}, {"ctzsi2", &__ctzsi2}, From 85c5b157a0b8e15823e1054f0f4b58b03ef78463 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 27 Jun 2015 23:52:20 +0200 Subject: [PATCH 114/227] test/full_stack: tolerate FP rounding errors --- artiq/test/full_stack.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/test/full_stack.py b/artiq/test/full_stack.py index 91fb1e979..a75a73fcd 100644 --- a/artiq/test/full_stack.py +++ b/artiq/test/full_stack.py @@ -82,9 +82,9 @@ class _PulseLogger(AutoDB): @kernel def pulse(self, f, duration): - self.on(int(now()*1000000000), f) + self.on(round(now()*1000000000), f) delay(duration) - self.off(int(now()*1000000000)) + self.off(round(now()*1000000000)) class _Pulses(AutoDB): From b6310b72db2bc9b04ec088b6f113c4a1e91adde8 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sun, 28 Jun 2015 17:29:52 +0200 Subject: [PATCH 115/227] runtime: fix log formatting --- soc/runtime/session.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/soc/runtime/session.c b/soc/runtime/session.c index 036978bf3..2cb19a495 100644 --- a/soc/runtime/session.c +++ b/soc/runtime/session.c @@ -542,7 +542,7 @@ static int process_kmsg(struct msg_base *umsg) case MESSAGE_TYPE_LOG: { struct msg_log *msg = (struct msg_log *)umsg; - log(msg->fmt, msg->args); + log_va(msg->fmt, msg->args); mailbox_acknowledge(); break; } From 944bfafefabcfd1f0453e0c818ab616dead1bac2 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sun, 28 Jun 2015 21:37:27 +0200 Subject: [PATCH 116/227] soc: support QC2 and AD9914 (untested) --- artiq/gateware/{ad9858.py => ad9xxx.py} | 43 ++++++------ artiq/gateware/nist_qc2.py | 5 +- artiq/gateware/rtio/phy/dds.py | 53 ++++++++++---- soc/runtime/dds.c | 2 +- soc/targets/artiq_kc705.py | 91 +++++++++++++++++-------- soc/targets/artiq_pipistrello.py | 2 +- 6 files changed, 130 insertions(+), 66 deletions(-) rename artiq/gateware/{ad9858.py => ad9xxx.py} (81%) diff --git a/artiq/gateware/ad9858.py b/artiq/gateware/ad9xxx.py similarity index 81% rename from artiq/gateware/ad9858.py rename to artiq/gateware/ad9xxx.py index 5b475d628..f774f63ed 100644 --- a/artiq/gateware/ad9858.py +++ b/artiq/gateware/ad9xxx.py @@ -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. sel is mapped to the lower bits, followed by reset. 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(data_width=8) + 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]), - ] + self.comb += pads.sel.eq(gpio), + if hasattr(pads, "rst"): + self.comb += pads.rst.eq(gpio[-1]) + else: + self.comb += pads.rst_n.eq(~gpio[-1]) bus_r_gpio = Signal() self.comb += If(bus_r_gpio, @@ -71,7 +72,10 @@ class AD9858(Module): ) fud = Signal() - self.sync += pads.fud_n.eq(~fud) + if hasattr(pads, "fud"): + self.sync += pads.fud.eq(fud) + else: + self.sync += pads.fud_n.eq(~fud) pads.wr_n.reset = 1 pads.rd_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") diff --git a/artiq/gateware/nist_qc2.py b/artiq/gateware/nist_qc2.py index 7f93e4e49..9d6896781 100644 --- a/artiq/gateware/nist_qc2.py +++ b/artiq/gateware/nist_qc2.py @@ -1,5 +1,6 @@ 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")), @@ -28,10 +29,10 @@ fmc_adapter_io = [ 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_n", Pins("LPC:LA21_N")), + Subsignal("fud", Pins("LPC:LA21_N")), Subsignal("wr_n", Pins("LPC:LA24_P")), Subsignal("rd_n", Pins("LPC:LA25_N")), - Subsignal("rst_in", Pins("LPC:LA25_P")), + Subsignal("rst", Pins("LPC:LA25_P")), IOStandard("LVTTL")), ("i2c", 0, diff --git a/artiq/gateware/rtio/phy/dds.py b/artiq/gateware/rtio/phy/dds.py index a0daec62b..e9685223d 100644 --- a/artiq/gateware/rtio/phy/dds.py +++ b/artiq/gateware/rtio/phy/dds.py @@ -1,14 +1,14 @@ from migen.fhdl.std import * -from artiq.gateware import ad9858 as ad9858_ll +from artiq.gateware import ad9xxx from artiq.gateware.rtio.phy.wishbone import RT2WB -class AD9858(Module): - def __init__(self, pads, nchannels=8, **kwargs): +class _AD9xxx(Module): + def __init__(self, ftw_base, pads, nchannels, **kwargs): self.submodules._ll = RenameClockDomains( - ad9858_ll.AD9858(pads, **kwargs), "rio") - self.submodules._rt2wb = RT2WB(7, self._ll.bus) + 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)] @@ -16,21 +16,44 @@ class AD9858(Module): # keep track of the currently selected channel current_channel = Signal(max=nchannels) - self.sync.rio += If(self.rtlink.o.stb & (self.rtlink.o.address == 65), - current_channel.eq(self.rtlink.o.data)) + self.sync.rio += If(self.rtlink.o.stb & + (self.rtlink.o.address == 2**flen(pads.a)+1), + current_channel.eq(self.rtlink.o.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): - for i in range(4): - self.sync.rio += \ - If(self.rtlink.o.stb & \ - (self.rtlink.o.address == 0x0a+i) & \ - (current_channel == c), - ftw[i*8:(i+1)*8].eq(self.rtlink.o.data) - ) + if flen(pads.d) == 8: + for i in range(4): + self.sync.rio += \ + If(self.rtlink.o.stb & \ + (self.rtlink.o.address == ftw_base+i) & \ + (current_channel == c), + ftw[i*8:(i+1)*8].eq(self.rtlink.o.data) + ) + elif flen(pads.d) == 16: + for i in range(2): + self.sync.rio += \ + If(self.rtlink.o.stb & \ + (self.rtlink.o.address == ftw_base+2*i) & \ + (current_channel == c), + ftw[i*16:(i+1)*16].eq(self.rtlink.o.data) + ) + else: + raise NotImplementedError # FTW to probe on FUD for c, (probe, ftw) in enumerate(zip(self.probes, ftws)): - fud = self.rtlink.o.stb & (self.rtlink.o.address == 64) + fud = self.rtlink.o.stb & \ + (self.rtlink.o.address == 2**flen(pads.a)) self.sync.rio += If(fud & (current_channel == c), probe.eq(ftw)) + + +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) diff --git a/soc/runtime/dds.c b/soc/runtime/dds.c index 9a3790832..326c48845 100644 --- a/soc/runtime/dds.c +++ b/soc/runtime/dds.c @@ -40,7 +40,7 @@ void dds_init(long long int timestamp, int channel) now = timestamp - DURATION_INIT; DDS_WRITE(DDS_GPIO, channel); - DDS_WRITE(DDS_GPIO, channel | (1 << 7)); + DDS_WRITE(DDS_GPIO, channel | (1 << 5)); DDS_WRITE(DDS_GPIO, channel); DDS_WRITE(0x00, 0x78); diff --git a/soc/targets/artiq_kc705.py b/soc/targets/artiq_kc705.py index 740b68b22..d6631ef87 100644 --- a/soc/targets/artiq_kc705.py +++ b/soc/targets/artiq_kc705.py @@ -10,7 +10,7 @@ from misoclib.mem.sdram.core.minicon import MiniconSettings from targets.kc705 import MiniSoC from artiq.gateware.soc import AMPSoC -from artiq.gateware import rtio, nist_qc1 +from artiq.gateware import rtio, nist_qc1, nist_qc2 from artiq.gateware.rtio.phy import ttl_simple, dds @@ -32,7 +32,7 @@ class _RTIOCRG(Module, AutoCSR): o_O=self.cd_rtio.clk) -class NIST_QC1(MiniSoC, AMPSoC): +class _NIST_QCx(MiniSoC, AMPSoC): csr_map = { "rtio": None, # mapped on Wishbone instead "rtio_crg": 13, @@ -52,18 +52,44 @@ class NIST_QC1(MiniSoC, AMPSoC): sdram_controller_settings=MiniconSettings(l2_size=128*1024), with_timer=False, **kwargs) AMPSoC.__init__(self) - platform.add_extension(nist_qc1.fmc_adapter_io) - self.submodules.leds = gpio.GPIOOut(Cat( platform.request("user_led", 0), platform.request("user_led", 1))) + def add_rtio(self, rtio_channels): + self.submodules.rtio_crg = _RTIOCRG(self.platform, self.crg.pll_sys) + self.submodules.rtio = rtio.RTIO(rtio_channels, + clk_freq=125000000) + self.add_constant("RTIO_FINE_TS_WIDTH", self.rtio.fine_ts_width) + self.add_constant("DDS_RTIO_CLK_RATIO", 8 >> self.rtio.fine_ts_width) + self.submodules.rtio_moninj = rtio.MonInj(rtio_channels) + + if isinstance(self.platform.toolchain, XilinxVivadoToolchain): + self.platform.add_platform_command(""" +create_clock -name rsys_clk -period 8.0 [get_nets {rsys_clk}] +create_clock -name rio_clk -period 8.0 [get_nets {rio_clk}] +set_false_path -from [get_clocks rsys_clk] -to [get_clocks rio_clk] +set_false_path -from [get_clocks rio_clk] -to [get_clocks rsys_clk] +""", rsys_clk=self.rtio.cd_rsys.clk, rio_clk=self.rtio.cd_rio.clk) + + rtio_csrs = self.rtio.get_csrs() + self.submodules.rtiowb = wbgen.Bank(rtio_csrs) + self.kernel_cpu.add_wb_slave(mem_decoder(self.mem_map["rtio"]), + self.rtiowb.bus) + self.add_csr_region("rtio", self.mem_map["rtio"] | 0x80000000, 32, + rtio_csrs) + + +class NIST_QC1(_NIST_QCx): + def __init__(self, platform, cpu_type="or1k", **kwargs): + _NIST_QCx.__init__(self, platform, cpu_type, **kwargs) + platform.add_extension(nist_qc1.fmc_adapter_io) + self.comb += [ platform.request("ttl_l_tx_en").eq(1), platform.request("ttl_h_tx_en").eq(1) ] - # RTIO channels rtio_channels = [] for i in range(2): phy = ttl_simple.Inout(platform.request("pmt", i)) @@ -81,35 +107,46 @@ class NIST_QC1(MiniSoC, AMPSoC): self.add_constant("RTIO_DDS_CHANNEL", len(rtio_channels)) self.add_constant("DDS_CHANNEL_COUNT", 8) - phy = dds.AD9858(platform.request("dds")) + self.add_constant("DDS_AD9858") + phy = dds.AD9858(platform.request("dds"), 8) self.submodules += phy rtio_channels.append(rtio.Channel.from_phy(phy, ofifo_depth=512, ififo_depth=4)) + self.add_rtio(rtio_channels) - # RTIO core - self.submodules.rtio_crg = _RTIOCRG(platform, self.crg.pll_sys) - self.submodules.rtio = rtio.RTIO(rtio_channels, - clk_freq=125000000) - self.add_constant("RTIO_FINE_TS_WIDTH", self.rtio.fine_ts_width) - self.add_constant("DDS_RTIO_CLK_RATIO", 8 >> self.rtio.fine_ts_width) - self.submodules.rtio_moninj = rtio.MonInj(rtio_channels) - if isinstance(platform.toolchain, XilinxVivadoToolchain): - platform.add_platform_command(""" -create_clock -name rsys_clk -period 8.0 [get_nets {rsys_clk}] -create_clock -name rio_clk -period 8.0 [get_nets {rio_clk}] -set_false_path -from [get_clocks rsys_clk] -to [get_clocks rio_clk] -set_false_path -from [get_clocks rio_clk] -to [get_clocks rsys_clk] -""", rsys_clk=self.rtio.cd_rsys.clk, rio_clk=self.rtio.cd_rio.clk) +class NIST_QC2(_NIST_QCx): + def __init__(self, platform, cpu_type="or1k", **kwargs): + _NIST_QCx.__init__(self, platform, cpu_type, **kwargs) + platform.add_extension(nist_qc2.fmc_adapter_io) - # CPU connections - rtio_csrs = self.rtio.get_csrs() - self.submodules.rtiowb = wbgen.Bank(rtio_csrs) - self.kernel_cpu.add_wb_slave(mem_decoder(self.mem_map["rtio"]), - self.rtiowb.bus) - self.add_csr_region("rtio", self.mem_map["rtio"] | 0x80000000, 32, - rtio_csrs) + rtio_channels = [] + for i in range(16): + if i % 4 == 3: + phy = ttl_simple.Inout(platform.request("ttl", i)) + self.submodules += phy + rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=512)) + else: + phy = ttl_simple.Output(platform.request("ttl", i)) + self.submodules += phy + rtio_channels.append(rtio.Channel.from_phy(phy)) + + phy = ttl_simple.Output(platform.request("user_led", 2)) + self.submodules += phy + rtio_channels.append(rtio.Channel.from_phy(phy)) + self.add_constant("RTIO_TTL_COUNT", len(rtio_channels)) + + self.add_constant("RTIO_DDS_CHANNEL", len(rtio_channels)) + self.add_constant("DDS_CHANNEL_COUNT", 11) + self.add_constant("DDS_AD9914") + self.add_constant("DDS_ONEHOT_SEL") + phy = dds.AD9914(platform.request("dds"), 11) + self.submodules += phy + rtio_channels.append(rtio.Channel.from_phy(phy, + ofifo_depth=512, + ififo_depth=4)) + self.add_rtio(rtio_channels) default_subtarget = NIST_QC1 diff --git a/soc/targets/artiq_pipistrello.py b/soc/targets/artiq_pipistrello.py index 5d6f900c1..6f5e6f632 100644 --- a/soc/targets/artiq_pipistrello.py +++ b/soc/targets/artiq_pipistrello.py @@ -118,7 +118,7 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd self.add_constant("RTIO_DDS_CHANNEL", len(rtio_channels)) self.add_constant("DDS_CHANNEL_COUNT", 8) - phy = dds.AD9858(platform.request("dds")) + phy = dds.AD9858(platform.request("dds"), 8) self.submodules += phy rtio_channels.append(rtio.Channel.from_phy(phy, ofifo_depth=512, From 39e9e73ff34dbd1b81cfb47c7ac64c9e242363c2 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Fri, 26 Jun 2015 12:37:15 -0600 Subject: [PATCH 117/227] language: allow experiments to import from artiq.language this way the import stanza shows what is imported: just experiment language related components keep the imports also at top level until experiments have transitioned the top level __init__.py should build and expose the entire namespace of artiq related things, like hdf5 analysis tools, frontend components (like experiment running api), deployment tools etc. --- artiq/__init__.py | 6 ++---- artiq/language/__init__.py | 4 ++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/artiq/__init__.py b/artiq/__init__.py index ebdb62601..7e6404c1e 100644 --- a/artiq/__init__.py +++ b/artiq/__init__.py @@ -1,4 +1,2 @@ -from artiq.language.core import * -from artiq.language.experiment import Experiment -from artiq.language.db import * -from artiq.language.units import * +from artiq import language +from artiq.language import * diff --git a/artiq/language/__init__.py b/artiq/language/__init__.py index e69de29bb..ebdb62601 100644 --- a/artiq/language/__init__.py +++ b/artiq/language/__init__.py @@ -0,0 +1,4 @@ +from artiq.language.core import * +from artiq.language.experiment import Experiment +from artiq.language.db import * +from artiq.language.units import * From 593dafc118c64e23d1e73f22352a789916094271 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Thu, 25 Jun 2015 22:19:11 -0600 Subject: [PATCH 118/227] test: hardware testbench --- artiq/test/coredevice.py | 56 ++++++++++++++++++++++++++++++++ artiq/test/hardware_testbench.py | 42 ++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 artiq/test/coredevice.py create mode 100644 artiq/test/hardware_testbench.py diff --git a/artiq/test/coredevice.py b/artiq/test/coredevice.py new file mode 100644 index 000000000..78d30142a --- /dev/null +++ b/artiq/test/coredevice.py @@ -0,0 +1,56 @@ +from artiq import * +from artiq.test.hardware_testbench import * +from artiq.coredevice.runtime_exceptions import RTIOUnderflow + + +class RTTTest(ExperimentCase): + class RTT(Experiment, AutoDB): + class DBKeys: + core = Device() + ttl_inout = Device() + rtt = Result() + + @kernel + def run(self): + self.ttl_inout.output() + delay(1*us) + t0 = now() + with parallel: + self.ttl_inout.gate_falling(2*us) + self.ttl_inout.pulse(1*us) + self.rtt = self.ttl_inout.timestamp() - t0 + + def test_rtt(self): + rtt = self.execute(self.RTT)["rtt"] + self.assertGreater(rtt, 0*ns) + self.assertLess(rtt, 40*ns) + + +class PulseRateTest(ExperimentCase): + class PulseRate(Experiment, AutoDB): + class DBKeys: + core = Device() + loop_out = Device() + pulse_rate = Result() + + @kernel + def run(self): + dt = time_to_cycles(1000*ns) + while True: + try: + for i in range(1000): + self.loop_out.pulse(cycles_to_time(dt)) + delay(cycles_to_time(dt)) + except RTIOUnderflow: + dt += 1 + self.core.break_realtime() + else: + self.pulse_rate = cycles_to_time(2*dt) + break + + def test_rate(self): + rate = self.execute(self.PulseRate)["pulse_rate"] + self.assertGreater(rate, 100*ns) + self.assertLess(rate, 2000*ns) + + diff --git a/artiq/test/hardware_testbench.py b/artiq/test/hardware_testbench.py new file mode 100644 index 000000000..35bb100e9 --- /dev/null +++ b/artiq/test/hardware_testbench.py @@ -0,0 +1,42 @@ +import os +import sys +import unittest +import logging + +from artiq import * +from artiq.protocols.file_db import FlatFileDB +from artiq.master.worker_db import DBHub, ResultDB +from artiq.frontend.artiq_run import ( + DummyScheduler, DummyWatchdog, SimpleParamLogger) + + +artiq_root = os.getenv("ARTIQ_ROOT") +logger = logging.getLogger(__name__) + + +@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.pdb = FlatFileDB(os.path.join(artiq_root, "pdb.pyon")) + self.rdb = ResultDB(lambda description: None, lambda mod: None) + self.dbh = DBHub(self.ddb, self.pdb, self.rdb) + + def execute(self, cls, **kwargs): + expid = { + "file": sys.modules[cls.__module__].__file__, + "experiment": cls.__name__, + "arguments": kwargs + } + sched = DummyScheduler(expid) + try: + try: + exp = cls(self.dbh, scheduler=sched, **kwargs) + except KeyError as e: + raise unittest.SkipTest(*e.args) + self.rdb.build() + exp.run() + exp.analyze() + return self.rdb.data.read + finally: + self.dbh.close_devices() From f7427dda395d85c19f2c28ce0aa01534eb506f92 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Sun, 28 Jun 2015 20:08:39 -0600 Subject: [PATCH 119/227] test: make benchmarks unittest --- artiq/test/coredevice.py | 150 ++++++++++++++++++++++--------- artiq/test/hardware_testbench.py | 3 +- 2 files changed, 109 insertions(+), 44 deletions(-) diff --git a/artiq/test/coredevice.py b/artiq/test/coredevice.py index 78d30142a..552fc26e1 100644 --- a/artiq/test/coredevice.py +++ b/artiq/test/coredevice.py @@ -1,56 +1,120 @@ -from artiq import * -from artiq.test.hardware_testbench import * +from math import sqrt + +from artiq.language import * +from artiq.test.hardware_testbench import ExperimentCase from artiq.coredevice.runtime_exceptions import RTIOUnderflow -class RTTTest(ExperimentCase): - class RTT(Experiment, AutoDB): - class DBKeys: - core = Device() - ttl_inout = Device() - rtt = Result() +class RTT(Experiment, AutoDB): + class DBKeys: + core = Device() + ttl_inout = Device() + rtt = Result() - @kernel - def run(self): - self.ttl_inout.output() - delay(1*us) - t0 = now() - with parallel: - self.ttl_inout.gate_falling(2*us) + @kernel + def run(self): + self.ttl_inout.output() + delay(1*us) + with parallel: + self.ttl_inout.gate_rising(2*us) + with sequential: + delay(1*us) + t0 = now() self.ttl_inout.pulse(1*us) - self.rtt = self.ttl_inout.timestamp() - t0 + self.rtt = self.ttl_inout.timestamp() - t0 + +class Loopback(Experiment, AutoDB): + class DBKeys: + core = Device() + loop_in = Device() + loop_out = Device() + rtt = Result() + + @kernel + def run(self): + with parallel: + self.loop_in.gate_rising(2*us) + with sequential: + delay(1*us) + t0 = now() + self.loop_out.pulse(1*us) + self.rtt = self.loop_in.timestamp() - t0 + + +class PulseRate(Experiment, AutoDB): + class DBKeys: + core = Device() + loop_out = Device() + pulse_rate = Result() + + @kernel + def run(self): + dt = time_to_cycles(1000*ns) + while True: + try: + for i in range(1000): + self.loop_out.pulse(cycles_to_time(dt)) + delay(cycles_to_time(dt)) + except RTIOUnderflow: + dt += 1 + self.core.break_realtime() + else: + self.pulse_rate = cycles_to_time(2*dt) + break + + +class CoredeviceTest(ExperimentCase): def test_rtt(self): - rtt = self.execute(self.RTT)["rtt"] + rtt = self.execute(RTT)["rtt"] + print(rtt) + self.assertGreater(rtt, 0*ns) + self.assertLess(rtt, 100*ns) + + def test_loopback(self): + rtt = self.execute(Loopback)["rtt"] + print(rtt) self.assertGreater(rtt, 0*ns) self.assertLess(rtt, 40*ns) - -class PulseRateTest(ExperimentCase): - class PulseRate(Experiment, AutoDB): - class DBKeys: - core = Device() - loop_out = Device() - pulse_rate = Result() - - @kernel - def run(self): - dt = time_to_cycles(1000*ns) - while True: - try: - for i in range(1000): - self.loop_out.pulse(cycles_to_time(dt)) - delay(cycles_to_time(dt)) - except RTIOUnderflow: - dt += 1 - self.core.break_realtime() - else: - self.pulse_rate = cycles_to_time(2*dt) - break - - def test_rate(self): - rate = self.execute(self.PulseRate)["pulse_rate"] + def test_pulse_rate(self): + rate = self.execute(PulseRate)["pulse_rate"] + print(rate) self.assertGreater(rate, 100*ns) - self.assertLess(rate, 2000*ns) + self.assertLess(rate, 2500*ns) +class RPCTiming(Experiment, AutoDB): + class DBKeys: + core = Device() + repeats = Argument(100) + rpc_time_mean = Result() + rpc_time_stddev = Result() + + 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_time() + self.nop(1) + t2 = self.core.get_rtio_time() + self.ts[i] = t2 - t1 + + def run(self): + self.bench() + mean = sum(self.ts)/self.repeats + self.rpc_time_stddev = sqrt( + sum([(t - mean)**2 for t in self.ts])/self.repeats)*s + self.rpc_time_mean = mean*s + + +class RPCTest(ExperimentCase): + def test_rpc_timing(self): + res = self.execute(RPCTiming) + print(res) + self.assertGreater(res["rpc_time_mean"], 100*ns) + self.assertLess(res["rpc_time_mean"], 10*ms) + self.assertLess(res["rpc_time_stddev"], 1*ms) diff --git a/artiq/test/hardware_testbench.py b/artiq/test/hardware_testbench.py index 35bb100e9..bdb1e2e82 100644 --- a/artiq/test/hardware_testbench.py +++ b/artiq/test/hardware_testbench.py @@ -3,7 +3,7 @@ import sys import unittest import logging -from artiq import * +from artiq.language import * from artiq.protocols.file_db import FlatFileDB from artiq.master.worker_db import DBHub, ResultDB from artiq.frontend.artiq_run import ( @@ -33,6 +33,7 @@ class ExperimentCase(unittest.TestCase): try: exp = cls(self.dbh, scheduler=sched, **kwargs) except KeyError as e: + # skip if ddb does not match requirements raise unittest.SkipTest(*e.args) self.rdb.build() exp.run() From 5442ae312f5d2677620ac220941f14908dbfce6f Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Sun, 28 Jun 2015 20:09:38 -0600 Subject: [PATCH 120/227] benchmarks/*: remove Benchmarks should be shaped as unittests and run as part of CI. --- benchmarks/all.py | 18 ------------------ benchmarks/ddb.pyon | 28 ---------------------------- benchmarks/pdb.pyon | 1 - benchmarks/pulse_rate.py | 26 -------------------------- benchmarks/rpc_timing.py | 32 -------------------------------- benchmarks/rtio_skew.py | 29 ----------------------------- 6 files changed, 134 deletions(-) delete mode 100644 benchmarks/all.py delete mode 100644 benchmarks/ddb.pyon delete mode 100644 benchmarks/pdb.pyon delete mode 100644 benchmarks/pulse_rate.py delete mode 100644 benchmarks/rpc_timing.py delete mode 100644 benchmarks/rtio_skew.py diff --git a/benchmarks/all.py b/benchmarks/all.py deleted file mode 100644 index d0a34263b..000000000 --- a/benchmarks/all.py +++ /dev/null @@ -1,18 +0,0 @@ -from artiq import * - -import pulse_rate, rtio_skew, rpc_timing - - -_exps = [pulse_rate.PulseRate, rtio_skew.RTIOSkew, rpc_timing.RPCTiming] - -class AllBenchmarks(Experiment, AutoDB): - """All benchmarks""" - - def build(self): - self.se = [] - for exp in _exps: - self.se.append(exp(self.dbh)) - - def run(self): - for se in self.se: - se.run() diff --git a/benchmarks/ddb.pyon b/benchmarks/ddb.pyon deleted file mode 100644 index 8dd20362c..000000000 --- a/benchmarks/ddb.pyon +++ /dev/null @@ -1,28 +0,0 @@ -{ - "comm": { - "type": "local", - "module": "artiq.coredevice.comm_tcp", - "class": "Comm", - "arguments": {"host": "192.168.0.42"} - }, - "core": { - "type": "local", - "module": "artiq.coredevice.core", - "class": "Core", - "arguments": {} - }, - - "pmt0": { - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLInOut", - "arguments": {"channel": 0} - }, - - "ttl0": { - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLOut", - "arguments": {"channel": 2} - }, -} diff --git a/benchmarks/pdb.pyon b/benchmarks/pdb.pyon deleted file mode 100644 index 0967ef424..000000000 --- a/benchmarks/pdb.pyon +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/benchmarks/pulse_rate.py b/benchmarks/pulse_rate.py deleted file mode 100644 index 4de78fa06..000000000 --- a/benchmarks/pulse_rate.py +++ /dev/null @@ -1,26 +0,0 @@ -from artiq import * -from artiq.coredevice.runtime_exceptions import RTIOUnderflow - - -class PulseRate(Experiment, AutoDB): - """Sustained pulse rate""" - - class DBKeys: - core = Device() - ttl0 = Device() - pulse_rate = Result() - - @kernel - def run(self): - T = time_to_cycles(100*ns) - while True: - try: - for i in range(1000): - self.ttl0.pulse(cycles_to_time(T)) - delay(cycles_to_time(T)) - except RTIOUnderflow: - T += 1 - self.core.break_realtime() - else: - self.pulse_rate = cycles_to_time(2*T) - break diff --git a/benchmarks/rpc_timing.py b/benchmarks/rpc_timing.py deleted file mode 100644 index 33e8152d9..000000000 --- a/benchmarks/rpc_timing.py +++ /dev/null @@ -1,32 +0,0 @@ -from math import sqrt - -from artiq import * - - -class RPCTiming(Experiment, AutoDB): - """RPC timing""" - - class DBKeys: - core = Device() - repeats = Argument(100) - rpc_time_mean = Result() - rpc_time_stddev = Result() - - def nop(self, x): - pass - - @kernel - def bench(self): - self.ts = [0.0 for _ in range(self.repeats)] - for i in range(self.repeats): - t1 = self.core.get_rtio_time() - self.nop(10) - t2 = self.core.get_rtio_time() - self.ts[i] = t2 - t1 - - def run(self): - self.bench() - mean = sum(self.ts)/self.repeats - self.rpc_time_stddev = sqrt( - sum([(t - mean)**2 for t in self.ts])/self.repeats)*s - self.rpc_time_mean = mean*s diff --git a/benchmarks/rtio_skew.py b/benchmarks/rtio_skew.py deleted file mode 100644 index a5770864b..000000000 --- a/benchmarks/rtio_skew.py +++ /dev/null @@ -1,29 +0,0 @@ -from artiq import * - - -class PulseNotReceived(Exception): - pass - - -class RTIOSkew(Experiment, AutoDB): - """RTIO skew""" - - class DBKeys: - core = Device() - pmt0 = Device() - rtio_skew = Result() - - @kernel - def run(self): - self.pmt0.output() - delay(1*us) - with parallel: - self.pmt0.gate_rising(10*us) - with sequential: - delay(5*us) - out_t = now() - self.pmt0.pulse(5*us) - in_t = self.pmt0.timestamp() - if in_t < 0*s: - raise PulseNotReceived - self.rtio_skew = out_t - in_t From 23eee9445833d90e8bef1757914e8f0b5e1ba9ca Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Sun, 28 Jun 2015 20:33:50 -0600 Subject: [PATCH 121/227] pipistrello: add notes to nist_qc1 about dds_clock * remove xtrig from the target as it is not usually connected (used for dds_clock) and ignore PMT2/BTN2 as C:15 is used for dds_clock. * this also aligns the ttl channel numbers with kc705/nist_qc1 (two pmt inputs followed by 16 ttl outputs followed by leds) --- artiq/gateware/nist_qc1.py | 16 ++++++++++++++-- soc/targets/artiq_pipistrello.py | 10 ++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/artiq/gateware/nist_qc1.py b/artiq/gateware/nist_qc1.py index a2d68e44f..f75f52371 100644 --- a/artiq/gateware/nist_qc1.py +++ b/artiq/gateware/nist_qc1.py @@ -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")), diff --git a/soc/targets/artiq_pipistrello.py b/soc/targets/artiq_pipistrello.py index 6f5e6f632..65b6e4cea 100644 --- a/soc/targets/artiq_pipistrello.py +++ b/soc/targets/artiq_pipistrello.py @@ -34,7 +34,7 @@ class _RTIOCRG(Module, AutoCSR): i_FREEZEDCM=0, i_RST=ResetSignal()) - rtio_external_clk = platform.request("dds_clock") + rtio_external_clk = platform.request("pmt", 2) platform.add_period_constraint(rtio_external_clk, 8.0) self.specials += Instance("BUFGMUX", i_I0=rtio_internal_clk, @@ -95,11 +95,13 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd for i in range(2): phy = ttl_simple.Inout(platform.request("pmt", i)) self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=512)) + rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=512, + ofifo_depth=4)) - phy = ttl_simple.Inout(platform.request("xtrig", 0)) + phy = ttl_simple.Inout(platform.request("xtrig")) self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4)) + rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4, + ofifo_depth=4)) for i in range(16): phy = ttl_simple.Output(platform.request("ttl", i)) From e2cb0e107f5e3636459517659266c50ff2674844 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Sun, 28 Jun 2015 21:11:41 -0600 Subject: [PATCH 122/227] pipistrello: really do not request xtrig --- soc/targets/artiq_pipistrello.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/soc/targets/artiq_pipistrello.py b/soc/targets/artiq_pipistrello.py index 65b6e4cea..ddc42a8dd 100644 --- a/soc/targets/artiq_pipistrello.py +++ b/soc/targets/artiq_pipistrello.py @@ -98,11 +98,6 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=512, ofifo_depth=4)) - phy = ttl_simple.Inout(platform.request("xtrig")) - self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4, - ofifo_depth=4)) - for i in range(16): phy = ttl_simple.Output(platform.request("ttl", i)) self.submodules += phy From 165ef20ffa88829e7b9b9a4410eb33818ebce6e1 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Sun, 28 Jun 2015 21:24:57 -0600 Subject: [PATCH 123/227] pipistrello: drop rtio fifos for invisible leds the main board leds are all under the adapter board also tweak fifo depths a bit in a feeble attempt to circumvent a ISE hang (par phase 4) --- soc/targets/artiq_pipistrello.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/soc/targets/artiq_pipistrello.py b/soc/targets/artiq_pipistrello.py index ddc42a8dd..be7ba6312 100644 --- a/soc/targets/artiq_pipistrello.py +++ b/soc/targets/artiq_pipistrello.py @@ -105,12 +105,8 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd phy = ttl_simple.Output(platform.request("ext_led", 0)) self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy, ofifo_depth=4)) + rtio_channels.append(rtio.Channel.from_phy(phy, ofifo_depth=256)) - for i in range(2, 5): - phy = ttl_simple.Output(platform.request("user_led", i)) - self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy, ofifo_depth=4)) self.add_constant("RTIO_TTL_COUNT", len(rtio_channels)) self.add_constant("RTIO_DDS_CHANNEL", len(rtio_channels)) From d39382eca0623697e46714c82bff1e1e967fd903 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Sun, 28 Jun 2015 22:06:33 -0600 Subject: [PATCH 124/227] pipistrello: ext_led fifo depth 4 --- soc/targets/artiq_pipistrello.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/soc/targets/artiq_pipistrello.py b/soc/targets/artiq_pipistrello.py index be7ba6312..36d57f2e4 100644 --- a/soc/targets/artiq_pipistrello.py +++ b/soc/targets/artiq_pipistrello.py @@ -105,7 +105,7 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd phy = ttl_simple.Output(platform.request("ext_led", 0)) self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy, ofifo_depth=256)) + rtio_channels.append(rtio.Channel.from_phy(phy, ofifo_depth=4)) self.add_constant("RTIO_TTL_COUNT", len(rtio_channels)) From 515aa968194f7e7578ebec518d5a41371cef341e Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Mon, 29 Jun 2015 12:59:52 +0200 Subject: [PATCH 125/227] controllers: use --simulation for simulation --- artiq/frontend/lda_controller.py | 7 ++++--- artiq/frontend/novatech409b_controller.py | 7 +++++-- artiq/frontend/pdq2_controller.py | 7 +++++-- artiq/frontend/pxi6733_controller.py | 7 ++++--- artiq/frontend/thorlabs_tcube_controller.py | 7 ++++--- doc/manual/developing_a_ndsp.rst | 2 +- 6 files changed, 23 insertions(+), 14 deletions(-) diff --git a/artiq/frontend/lda_controller.py b/artiq/frontend/lda_controller.py index cc70038e9..0f3a0ab32 100755 --- a/artiq/frontend/lda_controller.py +++ b/artiq/frontend/lda_controller.py @@ -19,8 +19,9 @@ def get_argparser(): "The serial number is written on a sticker under " "the device, you should write for example " "-d \"SN:03461\". You must prepend enough 0 for it " - "to be 5 digits." - " Omit for simulation mode.") + "to be 5 digits.") + parser.add_argument("--simulation", action="store_true", + help="Put the driver in simulation mode.") verbosity_args(parser) return parser @@ -28,7 +29,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) diff --git a/artiq/frontend/novatech409b_controller.py b/artiq/frontend/novatech409b_controller.py index 30dcd75f7..8eb6b280a 100755 --- a/artiq/frontend/novatech409b_controller.py +++ b/artiq/frontend/novatech409b_controller.py @@ -19,7 +19,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.") verbosity_args(parser) return parser @@ -28,7 +31,7 @@ def main(): args = get_argparser().parse_args() init_logger(args) - dev = Novatech409B(args.device) + dev = Novatech409B(args.device if not args.simulation else None) try: simple_server_loop( {"novatech409b": dev}, args.bind, args.port) diff --git a/artiq/frontend/pdq2_controller.py b/artiq/frontend/pdq2_controller.py index 6404d7d7f..9a9885565 100755 --- a/artiq/frontend/pdq2_controller.py +++ b/artiq/frontend/pdq2_controller.py @@ -12,7 +12,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.") parser.add_argument( "--dump", default="pdq2_dump.bin", help="file to dump pdq2 data into, for later simulation") @@ -24,7 +27,7 @@ def main(): args = get_argparser().parse_args() init_logger(args) port = None - if args.device is None: + if args.simulation: port = open(args.dump, "wb") dev = Pdq2(url=args.device, dev=port) try: diff --git a/artiq/frontend/pxi6733_controller.py b/artiq/frontend/pxi6733_controller.py index 33948d02d..1c4a5ce64 100755 --- a/artiq/frontend/pxi6733_controller.py +++ b/artiq/frontend/pxi6733_controller.py @@ -12,10 +12,11 @@ def get_argparser(): parser = argparse.ArgumentParser(description="NI PXI 6733 controller") simple_network_args(parser, 3256) parser.add_argument("-C", "--channels", default=None, - help="List of channels (e.g. Dev1/ao0, Dev1/ao1:3)." - " Omit for simulation mode.") + 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("--simulation", action='store_true', + help="Put the driver in simulation mode.") verbosity_args(parser) return parser @@ -24,7 +25,7 @@ def main(): args = get_argparser().parse_args() init_logger(args) - if args.channels is None: + if args.simulation: daq = DAQmxSim() else: daq = DAQmx(args.channels, diff --git a/artiq/frontend/thorlabs_tcube_controller.py b/artiq/frontend/thorlabs_tcube_controller.py index d6aea8cbf..c8766dd6c 100755 --- a/artiq/frontend/thorlabs_tcube_controller.py +++ b/artiq/frontend/thorlabs_tcube_controller.py @@ -14,8 +14,9 @@ def get_argparser(): choices=["TDC001", "TPZ001"]) parser.add_argument("-d", "--device", default=None, help="serial device. See documentation for how to " - "specify a USB Serial Number. Omit for simulation " - "mode.") + "specify a USB Serial Number.") + parser.add_argument("--simulation", action="store_true", + help="Put the driver in simulation mode.") simple_network_args(parser, 3255) verbosity_args(parser) return parser @@ -25,7 +26,7 @@ def main(): args = get_argparser().parse_args() init_logger(args) - if args.device is None: + if args.simulation: if args.product == "TDC001": dev = TdcSim() elif args.product == "TPZ001": diff --git a/doc/manual/developing_a_ndsp.rst b/doc/manual/developing_a_ndsp.rst index 98e24e15e..0bc867892 100644 --- a/doc/manual/developing_a_ndsp.rst +++ b/doc/manual/developing_a_ndsp.rst @@ -177,7 +177,7 @@ General guidelines * Use new-style formatting (``str.format``) except for logging where it is not well supported, and double quotes for strings. * The device identification (e.g. serial number, or entry in ``/dev``) to attach to must be passed as a command-line parameter to the controller. We suggest using ``-d`` and ``--device`` as parameter name. * Controllers must be able to operate in "simulation" mode, where they behave properly even if the associated hardware is not connected. For example, they can print the data to the console instead of sending it to the device, or dump it into a file. -* We suggest that the simulation mode is entered whenever the ``-d/--device`` option is omitted. +* The simulation mode is entered whenever the ``--simulation`` option is specified. * Keep command line parameters consistent across clients/controllers. When adding new command line options, look for a client/controller that does a similar thing and follow its use of ``argparse``. If the original client/controller could use ``argparse`` in a better way, improve it. * Use docstrings for all public methods of the driver (note that those will be retrieved by ``artiq_rpctool``). * Choose a free default TCP port and add it to the default port list in this manual. From a73776bd7297cb8caf467be6ad28a3ec5c7b38f7 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Mon, 29 Jun 2015 19:21:32 +0200 Subject: [PATCH 126/227] controllers: enforce the usage of either --simulation or --device --- artiq/frontend/novatech409b_controller.py | 5 +++++ artiq/frontend/pdq2_controller.py | 6 ++++++ artiq/frontend/pxi6733_controller.py | 5 +++++ artiq/frontend/thorlabs_tcube_controller.py | 5 +++++ 4 files changed, 21 insertions(+) diff --git a/artiq/frontend/novatech409b_controller.py b/artiq/frontend/novatech409b_controller.py index 8eb6b280a..d3c5ddf75 100755 --- a/artiq/frontend/novatech409b_controller.py +++ b/artiq/frontend/novatech409b_controller.py @@ -31,6 +31,11 @@ def main(): args = get_argparser().parse_args() init_logger(args) + if not args.simulation and args.device is None: + raise ValueError("You need to specify either --simulation or " + "-d/--device argument. Use --help for more " + "information.") + dev = Novatech409B(args.device if not args.simulation else None) try: simple_server_loop( diff --git a/artiq/frontend/pdq2_controller.py b/artiq/frontend/pdq2_controller.py index 9a9885565..95b86caa5 100755 --- a/artiq/frontend/pdq2_controller.py +++ b/artiq/frontend/pdq2_controller.py @@ -27,6 +27,12 @@ def main(): args = get_argparser().parse_args() init_logger(args) port = None + + if not args.simulation and args.device is None: + raise ValueError("You need to specify either --simulation or " + "-d/--device argument. Use --help for more " + "information.") + if args.simulation: port = open(args.dump, "wb") dev = Pdq2(url=args.device, dev=port) diff --git a/artiq/frontend/pxi6733_controller.py b/artiq/frontend/pxi6733_controller.py index 1c4a5ce64..1307bede2 100755 --- a/artiq/frontend/pxi6733_controller.py +++ b/artiq/frontend/pxi6733_controller.py @@ -25,6 +25,11 @@ def main(): args = get_argparser().parse_args() init_logger(args) + if not args.simulation and args.channels is None: + raise ValueError("You need to specify either --simulation or " + "-C/--channels argument. Use --help for more " + "information.") + if args.simulation: daq = DAQmxSim() else: diff --git a/artiq/frontend/thorlabs_tcube_controller.py b/artiq/frontend/thorlabs_tcube_controller.py index c8766dd6c..fd879d0a5 100755 --- a/artiq/frontend/thorlabs_tcube_controller.py +++ b/artiq/frontend/thorlabs_tcube_controller.py @@ -26,6 +26,11 @@ def main(): args = get_argparser().parse_args() init_logger(args) + if not args.simulation and args.device is None: + raise ValueError("You need to specify either --simulation or " + "-d/--device argument. Use --help for more " + "information.") + if args.simulation: if args.product == "TDC001": dev = TdcSim() From ffe1355b1aac88e669f162cd63134d87dc3aba60 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Mon, 29 Jun 2015 19:24:55 +0200 Subject: [PATCH 127/227] lda_controller: improve help message for --device argument --- artiq/frontend/lda_controller.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/artiq/frontend/lda_controller.py b/artiq/frontend/lda_controller.py index 0f3a0ab32..5753af44e 100755 --- a/artiq/frontend/lda_controller.py +++ b/artiq/frontend/lda_controller.py @@ -18,8 +18,9 @@ def get_argparser(): help="USB serial number of the device. " "The serial number is written on a sticker under " "the device, you should write for example " - "-d \"SN:03461\". You must prepend enough 0 for it " - "to be 5 digits.") + "-d \"SN:03461\". You must prepend enough 0 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) From f0ac8cb3543b0a496b76d604a49d35b4e23de9ab Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Mon, 29 Jun 2015 11:26:38 -0600 Subject: [PATCH 128/227] pipistrello: add user_led:2 for debugging w/o adapter --- soc/targets/artiq_pipistrello.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/soc/targets/artiq_pipistrello.py b/soc/targets/artiq_pipistrello.py index 36d57f2e4..80b647785 100644 --- a/soc/targets/artiq_pipistrello.py +++ b/soc/targets/artiq_pipistrello.py @@ -107,6 +107,10 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd self.submodules += phy rtio_channels.append(rtio.Channel.from_phy(phy, ofifo_depth=4)) + phy = ttl_simple.Output(platform.request("user_led", 2)) + self.submodules += phy + rtio_channels.append(rtio.Channel.from_phy(phy, ofifo_depth=4)) + self.add_constant("RTIO_TTL_COUNT", len(rtio_channels)) self.add_constant("RTIO_DDS_CHANNEL", len(rtio_channels)) From d1c4cf0b78e78e335f3509344404b1b9bd1f25c2 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Mon, 29 Jun 2015 12:21:54 -0600 Subject: [PATCH 129/227] pipistrello: update rtio channel doc --- doc/manual/fpga_board_ports.rst | 32 ++++++++++++++++++-------------- soc/targets/artiq_pipistrello.py | 4 +++- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/doc/manual/fpga_board_ports.rst b/doc/manual/fpga_board_ports.rst index 13ba6b54a..442c3e691 100644 --- a/doc/manual/fpga_board_ports.rst +++ b/doc/manual/fpga_board_ports.rst @@ -13,20 +13,24 @@ The low-cost Pipistrello FPGA board can be used as a lower-cost but slower alter When plugged to an adapter, the NIST QC1 hardware can be used. The TTL lines are mapped to RTIO channels as follows: -+--------------+----------+-----------------+ -| RTIO channel | TTL line | Capability | -+==============+==========+=================+ -| 0 | PMT0 | Input only | -+--------------+----------+-----------------+ -| 1 | PMT1 | Input only | -+--------------+----------+-----------------+ -| 2-18 | TTL0-16 | Output only | -+--------------+----------+-----------------+ -| 19-21 | LEDs | Output only | -+--------------+----------+-----------------+ -| 22 | TTL2 | Output only | -+--------------+----------+-----------------+ ++--------------+----------+------------+ +| RTIO channel | TTL line | Capability | ++==============+==========+============+ +| 0 | PMT0 | Input | ++--------------+----------+------------+ +| 1 | PMT1 | Input | ++--------------+----------+------------+ +| 2-17 | TTL0-15 | Output | ++--------------+----------+------------+ +| 18 | EXT_LED | Output | ++--------------+----------+------------+ +| 19 | USER_LED | Output | ++--------------+----------+------------+ +| 20 | DDS | Output | ++--------------+----------+------------+ The input only limitation on channels 0 and 1 comes from the QC-DAQ adapter. When the adapter is not used (and physically unplugged from the Pipistrello board), the corresponding pins on the Pipistrello can be used as outputs. Do not configure these channels as outputs when the adapter is plugged, as this would cause electrical contention. -The board can accept an external RTIO clock connected to PMT2. +The board can accept an external RTIO clock connected to PMT2. If the DDS box +does not drive the PMT2 pair, use XTRIG and patch the XTRIG transciever output +on the adapter board onto C:15 disconnecting PMT2. diff --git a/soc/targets/artiq_pipistrello.py b/soc/targets/artiq_pipistrello.py index 80b647785..fbbb8d270 100644 --- a/soc/targets/artiq_pipistrello.py +++ b/soc/targets/artiq_pipistrello.py @@ -83,6 +83,8 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd self.submodules.leds = gpio.GPIOOut(Cat( platform.request("user_led", 0), platform.request("user_led", 1), + platform.request("user_led", 2), + platform.request("user_led", 3), )) self.comb += [ @@ -107,7 +109,7 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd self.submodules += phy rtio_channels.append(rtio.Channel.from_phy(phy, ofifo_depth=4)) - phy = ttl_simple.Output(platform.request("user_led", 2)) + phy = ttl_simple.Output(platform.request("user_led", 4)) self.submodules += phy rtio_channels.append(rtio.Channel.from_phy(phy, ofifo_depth=4)) From 3ee2bd5fa8cf56c5bc6aec9f0097b922508b798b Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Mon, 29 Jun 2015 12:59:59 -0600 Subject: [PATCH 130/227] pipistrello: set CLKFX_MD_MAX from MD ratio --- soc/targets/artiq_pipistrello.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/soc/targets/artiq_pipistrello.py b/soc/targets/artiq_pipistrello.py index fbbb8d270..b3bc058c3 100644 --- a/soc/targets/artiq_pipistrello.py +++ b/soc/targets/artiq_pipistrello.py @@ -24,7 +24,7 @@ class _RTIOCRG(Module, AutoCSR): self.specials += Instance("DCM_CLKGEN", p_CLKFXDV_DIVIDE=2, p_CLKFX_DIVIDE=f.denominator, - p_CLKFX_MD_MAX=1.6, + p_CLKFX_MD_MAX=float(f), p_CLKFX_MULTIPLY=f.numerator, p_CLKIN_PERIOD=1e9/clk_freq, p_SPREAD_SPECTRUM="NONE", From 0f06bac701de7f5a123d73b5efbf7575b822391d Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Mon, 29 Jun 2015 14:18:38 -0600 Subject: [PATCH 131/227] travis: use "use-local" for conda install http://conda.pydata.org/docs/build_tutorials/pkgs.html --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1bee784bc..5e9c750b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ before_install: - pip install coveralls install: - conda build conda/artiq - - conda install $HOME/miniconda/conda-bld/linux-64/artiq-*.tar.bz2 + - conda install artiq --use-local script: - coverage run --source=artiq setup.py test - make -C doc/manual html From 652f3359a2cf38213afc91f30e1c0bd10f8ac4ba Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Wed, 1 Jul 2015 11:40:48 +0200 Subject: [PATCH 132/227] lda_controller: fix typo --- artiq/frontend/lda_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/frontend/lda_controller.py b/artiq/frontend/lda_controller.py index 5753af44e..8286bc143 100755 --- a/artiq/frontend/lda_controller.py +++ b/artiq/frontend/lda_controller.py @@ -18,7 +18,7 @@ def get_argparser(): help="USB serial number of the device. " "The serial number is written on a sticker under " "the device, you should write for example " - "-d \"SN:03461\". You must prepend enough 0 for " + "-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", From d7ef885d9e8eb3f54b4adf256e6514e8ebd1cb3e Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Wed, 1 Jul 2015 11:54:15 +0200 Subject: [PATCH 133/227] controllers: print+exit instead of raising exception for argparse error, better doc for --simulation As long as you use --simulation, the driver will be in simulation mode. Even if you specify a --device or --channels. That can allow you to just switch to simulation mode by adding --simulation in the device database without having to remove the serial number or device path/name. --- artiq/frontend/novatech409b_controller.py | 9 +++++---- artiq/frontend/pdq2_controller.py | 9 +++++---- artiq/frontend/pxi6733_controller.py | 10 ++++++---- artiq/frontend/thorlabs_tcube_controller.py | 10 ++++++---- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/artiq/frontend/novatech409b_controller.py b/artiq/frontend/novatech409b_controller.py index d3c5ddf75..57846ae95 100755 --- a/artiq/frontend/novatech409b_controller.py +++ b/artiq/frontend/novatech409b_controller.py @@ -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 @@ -22,7 +23,7 @@ def get_argparser(): help="serial port.") parser.add_argument( "--simulation", action="store_true", - help="Put the driver in simulation mode.") + help="Put the driver in simulation mode, even if --device is used.") verbosity_args(parser) return parser @@ -32,9 +33,9 @@ def main(): init_logger(args) if not args.simulation and args.device is None: - raise ValueError("You need to specify either --simulation or " - "-d/--device argument. Use --help for more " - "information.") + 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: diff --git a/artiq/frontend/pdq2_controller.py b/artiq/frontend/pdq2_controller.py index 95b86caa5..577cdce2b 100755 --- a/artiq/frontend/pdq2_controller.py +++ b/artiq/frontend/pdq2_controller.py @@ -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 @@ -15,7 +16,7 @@ def get_argparser(): help="serial port.") parser.add_argument( "--simulation", action="store_true", - help="Put the driver in simulation mode.") + 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") @@ -29,9 +30,9 @@ def main(): port = None if not args.simulation and args.device is None: - raise ValueError("You need to specify either --simulation or " - "-d/--device argument. Use --help for more " - "information.") + 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") diff --git a/artiq/frontend/pxi6733_controller.py b/artiq/frontend/pxi6733_controller.py index 1307bede2..9658dc6bb 100755 --- a/artiq/frontend/pxi6733_controller.py +++ b/artiq/frontend/pxi6733_controller.py @@ -2,6 +2,7 @@ # Yann Sionneau , 2015 import argparse +import sys from artiq.protocols.pc_rpc import simple_server_loop from artiq.devices.pxi6733.driver import DAQmx, DAQmxSim @@ -16,7 +17,8 @@ def get_argparser(): parser.add_argument("-c", "--clock", default="PFI5", help="Input clock pin name (default: PFI5)") parser.add_argument("--simulation", action='store_true', - help="Put the driver in simulation mode.") + help="Put the driver in simulation mode, even if " + "--channels is used.") verbosity_args(parser) return parser @@ -26,9 +28,9 @@ def main(): init_logger(args) if not args.simulation and args.channels is None: - raise ValueError("You need to specify either --simulation or " - "-C/--channels argument. Use --help for more " - "information.") + print("You need to specify either --simulation or -C/--channels " + "argument. Use --help for more information.") + sys.exit(1) if args.simulation: daq = DAQmxSim() diff --git a/artiq/frontend/thorlabs_tcube_controller.py b/artiq/frontend/thorlabs_tcube_controller.py index fd879d0a5..85ccc6ebc 100755 --- a/artiq/frontend/thorlabs_tcube_controller.py +++ b/artiq/frontend/thorlabs_tcube_controller.py @@ -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 @@ -16,7 +17,8 @@ def get_argparser(): 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.") + help="Put the driver in simulation mode, even if " + "--device is used.") simple_network_args(parser, 3255) verbosity_args(parser) return parser @@ -27,9 +29,9 @@ def main(): init_logger(args) if not args.simulation and args.device is None: - raise ValueError("You need to specify either --simulation or " - "-d/--device argument. Use --help for more " - "information.") + 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": From 9d6287a6a362b63dd880d8d7705a94c9f323bde1 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Wed, 1 Jul 2015 22:22:53 +0200 Subject: [PATCH 134/227] expose machine units to user --- artiq/coredevice/core.py | 7 +- artiq/coredevice/dds.py | 30 +++++-- artiq/coredevice/ttl.py | 132 +++++++++++++++--------------- artiq/devices/pdq2/mediator.py | 36 ++++---- artiq/language/core.py | 84 +++++++++---------- artiq/sim/devices.py | 10 ++- artiq/sim/time.py | 18 ++-- artiq/test/full_stack.py | 15 ++-- artiq/test/transforms.py | 4 +- artiq/transforms/interleave.py | 7 +- artiq/transforms/lower_time.py | 18 ++-- artiq/transforms/quantize_time.py | 47 ++++------- artiq/transforms/tools.py | 7 +- examples/master/ddb.pyon | 13 ++- 14 files changed, 219 insertions(+), 209 deletions(-) diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index 7f7549115..6923b8ce9 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -121,10 +121,9 @@ class Core(AutoDB): 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) diff --git a/artiq/coredevice/dds.py b/artiq/coredevice/dds.py index f016b4cc0..09c4cb78b 100644 --- a/artiq/coredevice/dds.py +++ b/artiq/coredevice/dds.py @@ -37,7 +37,7 @@ class DDSBus(AutoDB): 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): @@ -76,12 +76,24 @@ class DDS(AutoDB): """ 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**14) + + @portable + def pow_to_turns(self, pow): + """Returns the phase in turns corresponding to the given phase offset + word.""" + return pow/2**14 + @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): @@ -104,9 +116,11 @@ class DDS(AutoDB): self.phase_mode = phase_mode @kernel - def set(self, frequency, phase=0, phase_mode=_PHASE_MODE_DEFAULT): + 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). + :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 @@ -114,7 +128,11 @@ class DDS(AutoDB): """ if phase_mode == _PHASE_MODE_DEFAULT: phase_mode = self.phase_mode + syscall("dds_set", now_mu(), self.channel, + frequency, round(phase*2**14), phase_mode) - syscall("dds_set", time_to_cycles(now()), self.channel, - self.frequency_to_ftw(frequency), round(phase*2**14), - 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) diff --git a/artiq/coredevice/ttl.py b/artiq/coredevice/ttl.py index 359b9b590..9a363b83d 100644 --- a/artiq/coredevice/ttl.py +++ b/artiq/coredevice/ttl.py @@ -2,47 +2,6 @@ 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): """RTIO TTL output driver. @@ -61,9 +20,9 @@ class TTLOut(AutoDB): 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): @@ -74,16 +33,25 @@ class TTLOut(AutoDB): @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) + 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() @@ -117,21 +85,21 @@ class TTLInOut(AutoDB): 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): @@ -142,43 +110,78 @@ class TTLInOut(AutoDB): @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) + 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) @@ -200,5 +203,4 @@ class TTLInOut(AutoDB): 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) diff --git a/artiq/devices/pdq2/mediator.py b/artiq/devices/pdq2/mediator.py index 63d3d7b72..f3c536219 100644 --- a/artiq/devices/pdq2/mediator.py +++ b/artiq/devices/pdq2/mediator.py @@ -99,7 +99,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 +125,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 +137,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 @@ -157,19 +159,15 @@ class CompoundPDQ2(AutoDB): class DBKeys: core = Device() pdq2_devices = Argument() - rtio_trigger = Argument() - rtio_frame = Argument() + trigger_device = Argument() + frame_devices = 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]) + self.trigger = self.dbh.get_device(self.trigger_device) + self.frame0 = self.dbh.get_device(self.frame_devices[0]) + self.frame1 = self.dbh.get_device(self.frame_devices[1]) + self.frame2 = self.dbh.get_device(self.frame_devices[2]) self.frames = [] self.current_frame = -1 diff --git a/artiq/language/core.py b/artiq/language/core.py index 424fd07cc..340ea2374 100644 --- a/artiq/language/core.py +++ b/artiq/language/core.py @@ -28,7 +28,6 @@ class int64(int): True >>> a + b 6 - """ pass @@ -62,7 +61,6 @@ 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)) @@ -88,7 +86,6 @@ 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): @@ -117,7 +114,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 +127,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 +140,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,7 +156,6 @@ _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 @@ -168,7 +163,8 @@ def set_syscall_manager(syscall_manager): # global namespace for kernels kernel_globals = ("sequential", "parallel", - "delay", "now", "at", "time_to_cycles", "cycles_to_time", + "delay_mu", "now_mu", "at_mu", "delay", + "seconds_to_mu", "mu_to_seconds", "syscall", "watchdog") @@ -190,7 +186,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 +195,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//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): diff --git a/artiq/sim/devices.py b/artiq/sim/devices.py index c5900a405..25fc934f5 100644 --- a/artiq/sim/devices.py +++ b/artiq/sim/devices.py @@ -7,13 +7,15 @@ from artiq.sim import time class Core(AutoDB): - _level = 0 + def build(self): + 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 diff --git a/artiq/sim/time.py b/artiq/sim/time.py index 3ae01a073..72cd3313f 100644 --- a/artiq/sim/time.py +++ b/artiq/sim/time.py @@ -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" diff --git a/artiq/test/full_stack.py b/artiq/test/full_stack.py index a75a73fcd..ad819827c 100644 --- a/artiq/test/full_stack.py +++ b/artiq/test/full_stack.py @@ -74,17 +74,20 @@ class _PulseLogger(AutoDB): 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(t, True, f) + self._append(self.int_usec(t), True, f) def off(self, t): - self._append(t, False, 0) + self._append(self.int_usec(t), False, 0) @kernel def pulse(self, f, duration): - self.on(round(now()*1000000000), f) + self.on(now_mu(), f) delay(duration) - self.off(round(now()*1000000000)) + self.off(now_mu()) class _Pulses(AutoDB): @@ -288,9 +291,9 @@ class _RTIOSequenceError(AutoDB): @kernel def run(self): - t = now() + t = now_mu() self.o.pulse(25*us) - at(t) + at_mu(t) self.o.pulse(25*us) diff --git a/artiq/test/transforms.py b/artiq/test/transforms.py index 31d77bd63..75af1b2dc 100644 --- a/artiq/test/transforms.py +++ b/artiq/test/transforms.py @@ -10,7 +10,7 @@ optimize_in = """ def run(): dds_sysclk = Fraction(1000000000, 1) - n = time_to_cycles((1.2345 * Fraction(1, 1000000000))) + n = seconds_to_mu((1.2345 * Fraction(1, 1000000000))) with sequential: frequency = 345 * Fraction(1000000, 1) frequency_to_ftw_return = int((((2 ** 32) * frequency) / dds_sysclk)) @@ -19,7 +19,7 @@ def run(): 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)) """ diff --git a/artiq/transforms/interleave.py b/artiq/transforms/interleave.py index 0a1b794a9..8aa9df838 100644 --- a/artiq/transforms/interleave.py +++ b/artiq/transforms/interleave.py @@ -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) diff --git a/artiq/transforms/lower_time.py b/artiq/transforms/lower_time.py index c2c779b84..5b3df0245 100644 --- a/artiq/transforms/lower_time.py +++ b/artiq/transforms/lower_time.py @@ -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]), diff --git a/artiq/transforms/quantize_time.py b/artiq/transforms/quantize_time.py index 835dcd780..aad75069a 100644 --- a/artiq/transforms/quantize_time.py +++ b/artiq/transforms/quantize_time.py @@ -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, - self.visit(node.args[0])) + 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) diff --git a/artiq/transforms/tools.py b/artiq/transforms/tools.py index 1b68fd0f3..97d596d2b 100644 --- a/artiq/transforms/tools.py +++ b/artiq/transforms/tools.py @@ -6,8 +6,9 @@ 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, @@ -92,7 +93,7 @@ def eval_constant(node): _replaceable_funcs = { "bool", "int", "float", "round", "int64", "round64", "Fraction", - "time_to_cycles", "cycles_to_time" + "seconds_to_mu", "mu_to_seconds" } diff --git a/examples/master/ddb.pyon b/examples/master/ddb.pyon index ff6772fcb..81903fca5 100644 --- a/examples/master/ddb.pyon +++ b/examples/master/ddb.pyon @@ -43,6 +43,12 @@ "class": "TTLOut", "arguments": {"channel": 4} }, + "ttl3": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 5} + }, "led": { "type": "local", "module": "artiq.coredevice.ttl", @@ -113,10 +119,9 @@ "class": "CompoundPDQ2", "arguments": { "pdq2_devices": ["qc_q1_0", "qc_q1_1", "qc_q1_2", "qc_q1_3"], - "rtio_trigger": 7, - "rtio_frame": [2, 3, 4] - }, - "comment": "Uses ttl 0, 1, 2 and 5" + "trigger_device": "ttl3", + "frame_devices": ["ttl0", "ttl1", "ttl2"] + } }, "lda": { From 771ad6cb26a977f9cfeb56c92576093589534691 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Wed, 1 Jul 2015 22:34:49 +0200 Subject: [PATCH 135/227] test/coredevice: adapt to MU API --- artiq/test/coredevice.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/artiq/test/coredevice.py b/artiq/test/coredevice.py index 552fc26e1..8661ae0a5 100644 --- a/artiq/test/coredevice.py +++ b/artiq/test/coredevice.py @@ -19,9 +19,9 @@ class RTT(Experiment, AutoDB): self.ttl_inout.gate_rising(2*us) with sequential: delay(1*us) - t0 = now() + t0 = now_mu() self.ttl_inout.pulse(1*us) - self.rtt = self.ttl_inout.timestamp() - t0 + self.rtt = mu_to_seconds(self.ttl_inout.timestamp() - t0) class Loopback(Experiment, AutoDB): @@ -37,9 +37,9 @@ class Loopback(Experiment, AutoDB): self.loop_in.gate_rising(2*us) with sequential: delay(1*us) - t0 = now() + t0 = now_mu() self.loop_out.pulse(1*us) - self.rtt = self.loop_in.timestamp() - t0 + self.rtt = mu_to_seconds(self.loop_in.timestamp() - t0) class PulseRate(Experiment, AutoDB): @@ -50,17 +50,17 @@ class PulseRate(Experiment, AutoDB): @kernel def run(self): - dt = time_to_cycles(1000*ns) + dt = seconds_to_mu(1000*ns) while True: try: for i in range(1000): - self.loop_out.pulse(cycles_to_time(dt)) - delay(cycles_to_time(dt)) + self.loop_out.pulse_mu(dt) + delay_mu(dt) except RTIOUnderflow: dt += 1 self.core.break_realtime() else: - self.pulse_rate = cycles_to_time(2*dt) + self.pulse_rate = mu_to_seconds(2*dt) break @@ -98,10 +98,10 @@ class RPCTiming(Experiment, AutoDB): def bench(self): self.ts = [0. for _ in range(self.repeats)] for i in range(self.repeats): - t1 = self.core.get_rtio_time() + t1 = self.core.get_rtio_counter_mu() self.nop(1) - t2 = self.core.get_rtio_time() - self.ts[i] = t2 - t1 + t2 = self.core.get_rtio_counter_mu() + self.ts[i] = mu_to_seconds(t2 - t1) def run(self): self.bench() @@ -116,5 +116,5 @@ class RPCTest(ExperimentCase): res = self.execute(RPCTiming) print(res) self.assertGreater(res["rpc_time_mean"], 100*ns) - self.assertLess(res["rpc_time_mean"], 10*ms) + self.assertLess(res["rpc_time_mean"], 15*ms) self.assertLess(res["rpc_time_stddev"], 1*ms) From e5e035d0311dde51d99ea4a75005438cb1ac4699 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Wed, 1 Jul 2015 22:37:12 +0200 Subject: [PATCH 136/227] doc/manual: add fire_and_forget --- doc/manual/protocols_reference.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/manual/protocols_reference.rst b/doc/manual/protocols_reference.rst index 070176acd..7ddfbc2ee 100644 --- a/doc/manual/protocols_reference.rst +++ b/doc/manual/protocols_reference.rst @@ -21,6 +21,14 @@ Protocols reference .. automodule:: artiq.protocols.pc_rpc :members: + +:mod:`artiq.protocols.fire_and_forget` module +--------------------------------------------- + +.. automodule:: artiq.protocols.fire_and_forget + :members: + + :mod:`artiq.protocols.sync_struct` module ----------------------------------------- From 984e82b82addf78c6566ac487bdf749d94a4dab9 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Wed, 1 Jul 2015 23:50:11 +0200 Subject: [PATCH 137/227] doc: update slides with new API --- doc/slides/artiq_overview.tex | 14 ++++++++------ doc/slides/taaccs.tex | 14 ++++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/doc/slides/artiq_overview.tex b/doc/slides/artiq_overview.tex index 97605fa89..8f7ed66c2 100644 --- a/doc/slides/artiq_overview.tex +++ b/doc/slides/artiq_overview.tex @@ -91,18 +91,19 @@ inner sep=.3mm] at (current page.south east) {% \begin{minted}[frame=leftline]{python} trigger.sync() # wait for trigger input -start = now() # capture trigger time +start = now_mu() # capture trigger time for i in range(3): delay(5*us) dds.pulse(900*MHz, 7*us) # first pulse 5 µs after trigger -at(start + 1*ms) # re-reference time-line +# re-reference time-line +at(start + seconds_to_mu(1*ms)) dds.pulse(200*MHz, 11*us) # exactly 1 ms after trigger \end{minted} \begin{itemize} \item Written in a subset of Python \item Executed on a CPU embedded on a FPGA (the \emph{core device}) - \item \verb!now(), at(), delay()! describe time-line of an experiment + \item \verb!now_mu(), at_mu(), delay_mu(), delay()! describe time-line of an experiment \item Exact time is kept in an internal variable \item That variable only loosely tracks the execution time of CPU instructions \item The value of that variable is exchanged with the RTIO fabric that @@ -148,11 +149,12 @@ dds.on(f, phase=0) # must round to integer tuning word for i in range(n): delay(dt) # must round to native cycles -dt_raw = time_to_cycles(dt) # integer number of cycles +dt_raw = seconds_to_mu(dt) # integer number of cycles f_raw = dds.frequency_to_ftw(f) # integer frequency tuning word -# determine correct phase despite accumulation of rounding errors -phi = n*cycles_to_time(dt_raw)*dds.ftw_to_frequency(f_raw) +# determine correct (to FP precision) phase +# despite accumulation of rounding errors +phi = mu_to_seconds(n*dt_raw)*dds.ftw_to_frequency(f_raw) \end{minted} \begin{itemize} diff --git a/doc/slides/taaccs.tex b/doc/slides/taaccs.tex index 09c263307..71c608f26 100644 --- a/doc/slides/taaccs.tex +++ b/doc/slides/taaccs.tex @@ -105,18 +105,19 @@ inner sep=.3mm] at (current page.south east) {% \begin{minted}[frame=leftline]{python} trigger.sync() # wait for trigger input -start = now() # capture trigger time +start = now_mu() # capture trigger time for i in range(3): delay(5*us) dds.pulse(900*MHz, 7*us) # first pulse 5 µs after trigger -at(start + 1*ms) # re-reference time-line +# re-reference time-line +at_mu(start + seconds_to_mu(1*ms)) dds.pulse(200*MHz, 11*us) # exactly 1 ms after trigger \end{minted} \begin{itemize} \item Written in a subset of Python \item Executed on a CPU embedded on a FPGA (the \emph{core device}) - \item \verb!now(), at(), delay()! describe time-line of an experiment + \item \verb!now_mu(), at_mu(), delay_mu(), delay()! describe time-line of an experiment \item Exact time is kept in an internal variable \item That variable only loosely tracks the execution time of CPU instructions \item The value of that variable is exchanged with the RTIO fabric that @@ -162,11 +163,12 @@ dds.on(f, phase=0) # must round to integer tuning word for i in range(n): delay(dt) # must round to native cycles -dt_raw = time_to_cycles(dt) # integer number of cycles +dt_raw = seconds_to_mu(dt) # integer number of cycles f_raw = dds.frequency_to_ftw(f) # integer frequency tuning word -# determine correct phase despite accumulation of rounding errors -phi = n*cycles_to_time(dt_raw)*dds.ftw_to_frequency(f_raw) +# determine correct (to FP precision) phase +# despite accumulation of rounding errors +phi = n*mu_to_seconds(dt_raw)*dds.ftw_to_frequency(f_raw) \end{minted} \begin{itemize} From 74f07092c731e84f8ca776ec770bbafc991d8836 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 2 Jul 2015 10:26:00 +0200 Subject: [PATCH 138/227] test/coredevice: fix timestamp conflict --- artiq/test/coredevice.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/artiq/test/coredevice.py b/artiq/test/coredevice.py index 8661ae0a5..8fc5df942 100644 --- a/artiq/test/coredevice.py +++ b/artiq/test/coredevice.py @@ -16,7 +16,9 @@ class RTT(Experiment, AutoDB): self.ttl_inout.output() delay(1*us) with parallel: - self.ttl_inout.gate_rising(2*us) + # 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() From 2881d5f00a1c5ec90c895227fb6767e5cb013163 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 2 Jul 2015 18:20:26 +0200 Subject: [PATCH 139/227] gateware: add RTIO clock generator --- artiq/gateware/rtio/phy/ttl_simple.py | 18 ++++++++++++++++++ doc/manual/fpga_board_ports.rst | 4 +++- soc/targets/artiq_kc705.py | 13 ++++++++++++- soc/targets/artiq_pipistrello.py | 6 +++++- 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/artiq/gateware/rtio/phy/ttl_simple.py b/artiq/gateware/rtio/phy/ttl_simple.py index 3250a2659..8a5e6e432 100644 --- a/artiq/gateware/rtio/phy/ttl_simple.py +++ b/artiq/gateware/rtio/phy/ttl_simple.py @@ -75,3 +75,21 @@ class Inout(Module): ] self.probes += [i, ts.oe] + + +class ClockGen(Module): + def __init__(self, pad, ftw_width=16): + 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), + # known phase on write: at rising edge + If(self.rtlink.o.stb, acc.eq(2**(ftw_width - 1))), + pad.eq(acc[-1]) + ] diff --git a/doc/manual/fpga_board_ports.rst b/doc/manual/fpga_board_ports.rst index 442c3e691..52bd8dffe 100644 --- a/doc/manual/fpga_board_ports.rst +++ b/doc/manual/fpga_board_ports.rst @@ -20,7 +20,9 @@ When plugged to an adapter, the NIST QC1 hardware can be used. The TTL lines are +--------------+----------+------------+ | 1 | PMT1 | Input | +--------------+----------+------------+ -| 2-17 | TTL0-15 | Output | +| 2-16 | TTL0-14 | Output | ++--------------+----------+------------+ +| 17 | TTL15 | Clock | +--------------+----------+------------+ | 18 | EXT_LED | Output | +--------------+----------+------------+ diff --git a/soc/targets/artiq_kc705.py b/soc/targets/artiq_kc705.py index d6631ef87..5ed077b5c 100644 --- a/soc/targets/artiq_kc705.py +++ b/soc/targets/artiq_kc705.py @@ -95,7 +95,7 @@ class NIST_QC1(_NIST_QCx): phy = ttl_simple.Inout(platform.request("pmt", i)) self.submodules += phy rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=512)) - for i in range(16): + for i in range(15): phy = ttl_simple.Output(platform.request("ttl", i)) self.submodules += phy rtio_channels.append(rtio.Channel.from_phy(phy)) @@ -105,6 +105,10 @@ class NIST_QC1(_NIST_QCx): rtio_channels.append(rtio.Channel.from_phy(phy)) self.add_constant("RTIO_TTL_COUNT", len(rtio_channels)) + phy = ttl_simple.ClockGen(platform.request("ttl", 15)) + self.submodules += phy + rtio_channels.append(rtio.Channel.from_phy(phy)) + self.add_constant("RTIO_DDS_CHANNEL", len(rtio_channels)) self.add_constant("DDS_CHANNEL_COUNT", 8) self.add_constant("DDS_AD9858") @@ -123,6 +127,9 @@ class NIST_QC2(_NIST_QCx): rtio_channels = [] for i in range(16): + if i == 14: + # TTL14 is for the clock generator + break if i % 4 == 3: phy = ttl_simple.Inout(platform.request("ttl", i)) self.submodules += phy @@ -137,6 +144,10 @@ class NIST_QC2(_NIST_QCx): rtio_channels.append(rtio.Channel.from_phy(phy)) self.add_constant("RTIO_TTL_COUNT", len(rtio_channels)) + phy = ttl_simple.ClockGen(platform.request("ttl", 14)) + self.submodules += phy + rtio_channels.append(rtio.Channel.from_phy(phy)) + self.add_constant("RTIO_DDS_CHANNEL", len(rtio_channels)) self.add_constant("DDS_CHANNEL_COUNT", 11) self.add_constant("DDS_AD9914") diff --git a/soc/targets/artiq_pipistrello.py b/soc/targets/artiq_pipistrello.py index b3bc058c3..3ad05a591 100644 --- a/soc/targets/artiq_pipistrello.py +++ b/soc/targets/artiq_pipistrello.py @@ -100,7 +100,7 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=512, ofifo_depth=4)) - for i in range(16): + for i in range(15): phy = ttl_simple.Output(platform.request("ttl", i)) self.submodules += phy rtio_channels.append(rtio.Channel.from_phy(phy, ofifo_depth=256)) @@ -115,6 +115,10 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd self.add_constant("RTIO_TTL_COUNT", len(rtio_channels)) + phy = ttl_simple.ClockGen(platform.request("ttl", 15)) + self.submodules += phy + rtio_channels.append(rtio.Channel.from_phy(phy)) + self.add_constant("RTIO_DDS_CHANNEL", len(rtio_channels)) self.add_constant("DDS_CHANNEL_COUNT", 8) phy = dds.AD9858(platform.request("dds"), 8) From 0a9f9093f74d99333de46558707e60d49307c322 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 2 Jul 2015 20:02:05 +0200 Subject: [PATCH 140/227] kc705: fix ttl15 --- soc/targets/artiq_kc705.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/soc/targets/artiq_kc705.py b/soc/targets/artiq_kc705.py index 5ed077b5c..f2ec5f0f3 100644 --- a/soc/targets/artiq_kc705.py +++ b/soc/targets/artiq_kc705.py @@ -129,7 +129,7 @@ class NIST_QC2(_NIST_QCx): for i in range(16): if i == 14: # TTL14 is for the clock generator - break + continue if i % 4 == 3: phy = ttl_simple.Inout(platform.request("ttl", i)) self.submodules += phy From 2674ed1d2df66923d1c8c1a62fc0cc40c4f75cd1 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Mon, 29 Jun 2015 12:36:20 -0600 Subject: [PATCH 141/227] use __all__ to structure the namespace --- artiq/__init__.py | 3 +++ artiq/language/__init__.py | 9 ++++++++- artiq/language/core.py | 31 ++++++++++++++++++------------- artiq/language/db.py | 3 +++ artiq/language/experiment.py | 2 ++ artiq/language/units.py | 3 +++ 6 files changed, 37 insertions(+), 14 deletions(-) diff --git a/artiq/__init__.py b/artiq/__init__.py index 7e6404c1e..ae6a8f96b 100644 --- a/artiq/__init__.py +++ b/artiq/__init__.py @@ -1,2 +1,5 @@ from artiq import language from artiq.language import * + +__all__ = [] +__all__.extend(language.__all__) diff --git a/artiq/language/__init__.py b/artiq/language/__init__.py index ebdb62601..ad51894b5 100644 --- a/artiq/language/__init__.py +++ b/artiq/language/__init__.py @@ -1,4 +1,11 @@ +from artiq.language import core, experiment, db, units from artiq.language.core import * -from artiq.language.experiment import Experiment +from artiq.language.experiment import * from artiq.language.db import * from artiq.language.units import * + +__all__ = [] +__all__.extend(core.__all__) +__all__.extend(experiment.__all__) +__all__.extend(db.__all__) +__all__.extend(units.__all__) diff --git a/artiq/language/core.py b/artiq/language/core.py index 340ea2374..051ef64c1 100644 --- a/artiq/language/core.py +++ b/artiq/language/core.py @@ -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"] + +# 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): @@ -65,7 +77,7 @@ def round64(x): return int64(round(x)) -_KernelFunctionInfo = _namedtuple("_KernelFunctionInfo", "core_name k_function") +_KernelFunctionInfo = namedtuple("_KernelFunctionInfo", "core_name k_function") def kernel(arg): @@ -89,16 +101,16 @@ def kernel(arg): """ 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, + return getattr(exp, arg).run(k_function, ((exp,) + k_args), k_kwargs) run_on_core.k_function_info = _KernelFunctionInfo( core_name=arg, k_function=k_function) 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( @@ -160,13 +172,6 @@ def set_syscall_manager(syscall_manager): global _syscall_manager _syscall_manager = syscall_manager -# global namespace for kernels - -kernel_globals = ("sequential", "parallel", - "delay_mu", "now_mu", "at_mu", "delay", - "seconds_to_mu", "mu_to_seconds", - "syscall", "watchdog") - class _Sequential: """In a sequential block, statements are executed one after another, with diff --git a/artiq/language/db.py b/artiq/language/db.py index 025dc22d2..d5f055603 100644 --- a/artiq/language/db.py +++ b/artiq/language/db.py @@ -2,6 +2,9 @@ Connection to device, parameter and result database. """ +__all__ = ["Device", "NoDefault", "Parameter", "Argument", "Result", "AutoDB"] + + class _AttributeKind: pass diff --git a/artiq/language/experiment.py b/artiq/language/experiment.py index 3473dcba3..c7fce7a55 100644 --- a/artiq/language/experiment.py +++ b/artiq/language/experiment.py @@ -1,5 +1,7 @@ from inspect import isclass +__all__ = ["Experiment", "has_analyze", "is_experiment"] + class Experiment: """Base class for experiments. diff --git a/artiq/language/units.py b/artiq/language/units.py index b3af0ebc3..83f1133a8 100644 --- a/artiq/language/units.py +++ b/artiq/language/units.py @@ -1,3 +1,5 @@ +__all__ = [] + _prefixes_str = "pnum_kMG" _smallest_prefix_exp = -12 @@ -8,6 +10,7 @@ def _register_unit(unit, prefixes): if prefix in prefixes: full_name = prefix + unit if prefix != "_" else unit globals()[full_name] = 10.**exponent + __all__.append(full_name) exponent += 3 From 813a2a2edf52f182be923704675e13b67758efba Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Sat, 4 Jul 2015 12:49:42 +0200 Subject: [PATCH 142/227] conda: add missing udev rules files in the package --- conda/artiq/build.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/conda/artiq/build.sh b/conda/artiq/build.sh index 03920a9dd..c3b7694f0 100755 --- a/conda/artiq/build.sh +++ b/conda/artiq/build.sh @@ -45,3 +45,7 @@ wget http://www.phys.ethz.ch/~robertjo/bscan_spi_lx45_csg324.bit mv bscan_spi_lx45_csg324.bit $BIN_PREFIX/pipistrello/ cp artiq/frontend/artiq_flash.sh $PREFIX/bin + +# misc +cp misc/99-papilio.rules $ARTIQ_PREFIX/misc/ +cp misc/99-kc705.rules $ARTIQ_PREFIX/misc/ From 3471ef80fdbc40cb85b1c1498d3d2336be558690 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Sat, 4 Jul 2015 14:48:55 +0200 Subject: [PATCH 143/227] manual: closes #63, tell to install xc3sprog before flashing --- doc/manual/installing.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index 4cf186931..5784d994a 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -51,6 +51,8 @@ You now need to flash 3 things on the FPGA board: 2. The BIOS 3. The ARTIQ runtime +First you need to :ref:`install xc3sprog `. Then, you can flash the board: + * For the Pipistrello board:: $ artiq_flash.sh -t pipistrello @@ -122,6 +124,8 @@ These steps are required to generate bitstream (``.bit``) files, build the MiSoC $ make -j4 $ sudo make install +.. _install-xc3sprog: + * Install JTAG tools needed to program the Pipistrello and KC705: :: From a615a3830a2c718aa31b8d5d0e15c40e6b67c86e Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 4 Jul 2015 18:35:11 +0200 Subject: [PATCH 144/227] test/coredevice: minor fixes --- artiq/test/coredevice.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/artiq/test/coredevice.py b/artiq/test/coredevice.py index 8fc5df942..3e3ce9150 100644 --- a/artiq/test/coredevice.py +++ b/artiq/test/coredevice.py @@ -35,6 +35,8 @@ class Loopback(Experiment, AutoDB): @kernel def run(self): + self.loop_in.input() + delay(1*us) with parallel: self.loop_in.gate_rising(2*us) with sequential: @@ -77,7 +79,7 @@ class CoredeviceTest(ExperimentCase): rtt = self.execute(Loopback)["rtt"] print(rtt) self.assertGreater(rtt, 0*ns) - self.assertLess(rtt, 40*ns) + self.assertLess(rtt, 50*ns) def test_pulse_rate(self): rate = self.execute(PulseRate)["pulse_rate"] From 753d61b38ffdf97e1c8f3fae28cf1806fa798d63 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 4 Jul 2015 18:36:01 +0200 Subject: [PATCH 145/227] complete support for TTL clock generator --- artiq/coredevice/runtime.py | 1 + artiq/coredevice/ttl.py | 82 ++++++++++++++++++++++++--- artiq/gateware/rtio/phy/ttl_simple.py | 13 ++++- artiq/test/coredevice.py | 24 ++++++++ soc/runtime/gen_service_table.py | 1 + soc/runtime/moninj.c | 2 +- soc/runtime/ttl.c | 8 +++ soc/runtime/ttl.h | 1 + soc/targets/artiq_kc705.py | 4 +- soc/targets/artiq_pipistrello.py | 2 +- 10 files changed, 124 insertions(+), 14 deletions(-) diff --git a/artiq/coredevice/runtime.py b/artiq/coredevice/runtime.py index 1fff8f4fc..5841f9d65 100644 --- a/artiq/coredevice/runtime.py +++ b/artiq/coredevice/runtime.py @@ -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", diff --git a/artiq/coredevice/ttl.py b/artiq/coredevice/ttl.py index 9a363b83d..768fc161e 100644 --- a/artiq/coredevice/ttl.py +++ b/artiq/coredevice/ttl.py @@ -26,7 +26,8 @@ class TTLOut(AutoDB): @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 @@ -37,12 +38,12 @@ class TTLOut(AutoDB): @kernel def off(self): - """Sets the output to a logic low state.""" + """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 + """Pulse the output high for the specified duration (in machine units).""" self.on() delay_mu(duration) @@ -50,7 +51,7 @@ class TTLOut(AutoDB): @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) @@ -103,18 +104,19 @@ class TTLInOut(AutoDB): @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.""" + """Set the output to a logic high state.""" self.set_o(True) @kernel def off(self): - """Sets the output to a logic low state.""" + """Set the output to a logic low state.""" self.set_o(False) @kernel @@ -204,3 +206,69 @@ class TTLInOut(AutoDB): If the gate is permanently closed, returns a negative value. """ return syscall("ttl_get", self.channel, self.i_previous_timestamp) + + +class TTLClockGen(AutoDB): + """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 + """ + class DBKeys: + core = Device() + channel = Argument() + + def build(self): + # in RTIO cycles + self.previous_timestamp = int64(0) + + @portable + def frequency_to_ftw(self, frequency): + """Returns the frequency tuning word corresponding to the given + frequency. + """ + return round(2**24*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**24 + + @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. + """ + 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 diff --git a/artiq/gateware/rtio/phy/ttl_simple.py b/artiq/gateware/rtio/phy/ttl_simple.py index 8a5e6e432..0127d19a0 100644 --- a/artiq/gateware/rtio/phy/ttl_simple.py +++ b/artiq/gateware/rtio/phy/ttl_simple.py @@ -78,7 +78,7 @@ class Inout(Module): class ClockGen(Module): - def __init__(self, pad, ftw_width=16): + def __init__(self, pad, ftw_width=24): self.rtlink = rtlink.Interface( rtlink.OInterface(ftw_width, suppress_nop=False)) @@ -89,7 +89,14 @@ class ClockGen(Module): self.sync.rio += If(self.rtlink.o.stb, ftw.eq(self.rtlink.o.data)) self.sync.rio_phy += [ acc.eq(acc + ftw), - # known phase on write: at rising edge - If(self.rtlink.o.stb, acc.eq(2**(ftw_width - 1))), + 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]) ] diff --git a/artiq/test/coredevice.py b/artiq/test/coredevice.py index 3e3ce9150..9d8673dfb 100644 --- a/artiq/test/coredevice.py +++ b/artiq/test/coredevice.py @@ -46,6 +46,26 @@ class Loopback(Experiment, AutoDB): self.rtt = mu_to_seconds(self.loop_in.timestamp() - t0) +class ClockGeneratorLoopback(Experiment, AutoDB): + class DBKeys: + core = Device() + loop_clock_in = Device() + loop_clock_out = Device() + count = Result() + + @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.count = self.loop_clock_in.count() + + class PulseRate(Experiment, AutoDB): class DBKeys: core = Device() @@ -81,6 +101,10 @@ class CoredeviceTest(ExperimentCase): self.assertGreater(rtt, 0*ns) self.assertLess(rtt, 50*ns) + def test_clock_generator_loopback(self): + count = self.execute(ClockGeneratorLoopback)["count"] + self.assertEqual(count, 10) + def test_pulse_rate(self): rate = self.execute(PulseRate)["pulse_rate"] print(rate) diff --git a/soc/runtime/gen_service_table.py b/soc/runtime/gen_service_table.py index b194662cd..27ae562b0 100755 --- a/soc/runtime/gen_service_table.py +++ b/soc/runtime/gen_service_table.py @@ -21,6 +21,7 @@ services = [ ("ttl_set_oe", "ttl_set_oe"), ("ttl_set_sensitivity", "ttl_set_sensitivity"), ("ttl_get", "ttl_get"), + ("ttl_clock_set", "ttl_clock_set"), ("dds_init", "dds_init"), ("dds_batch_enter", "dds_batch_enter"), diff --git a/soc/runtime/moninj.c b/soc/runtime/moninj.c index 34507d9a6..d7edff77d 100644 --- a/soc/runtime/moninj.c +++ b/soc/runtime/moninj.c @@ -51,7 +51,7 @@ static void moninj_monitor(const ip_addr_t *addr, u16_t port) reply.ttl_levels = 0; reply.ttl_oes = 0; reply.ttl_overrides = 0; - for(i=0;i Date: Sat, 4 Jul 2015 18:43:07 +0200 Subject: [PATCH 146/227] remove unneeded import --- artiq/devices/pdq2/mediator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/artiq/devices/pdq2/mediator.py b/artiq/devices/pdq2/mediator.py index f3c536219..64cb2703c 100644 --- a/artiq/devices/pdq2/mediator.py +++ b/artiq/devices/pdq2/mediator.py @@ -1,7 +1,6 @@ from artiq.language.core import * from artiq.language.db import * from artiq.language.units import * -from artiq.coredevice import ttl frame_setup = 20*ns From abd58667b944627484bd079aacbb5c78c2ee47f6 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 4 Jul 2015 18:49:09 +0200 Subject: [PATCH 147/227] pxi6733: small cleanup --- artiq/devices/pxi6733/driver.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/artiq/devices/pxi6733/driver.py b/artiq/devices/pxi6733/driver.py index 804535c6b..a57d2f0ff 100644 --- a/artiq/devices/pxi6733/driver.py +++ b/artiq/devices/pxi6733/driver.py @@ -1,9 +1,11 @@ # Yann Sionneau , 2015 from ctypes import byref, c_ulong -import numpy as np import logging +import numpy as np + + logger = logging.getLogger(__name__) @@ -40,7 +42,7 @@ class DAQmx: self.task = None self.daq = daq - def done_callback_py(self, taskhandle, status, callback_data): + def _done_callback(self, taskhandle, status, callback_data): if taskhandle != self.task: logger.warning("done callback called with unexpected task") else: @@ -113,7 +115,7 @@ class DAQmx: if ret: raise IOError("Error while writing samples to the channel buffer") - done_cb = self.daq.DAQmxDoneEventCallbackPtr(self.done_callback_py) + done_cb = self.daq.DAQmxDoneEventCallbackPtr(self._done_callback) self.task = t.taskHandle self.daq.DAQmxRegisterDoneEvent(t.taskHandle, 0, done_cb, None) t.StartTask() From 65ec6c28f4ef5c923930b2230d2639d02eaa922f Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 4 Jul 2015 19:21:25 +0200 Subject: [PATCH 148/227] ttl/clockgen: expose acc_width --- artiq/coredevice/ttl.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/artiq/coredevice/ttl.py b/artiq/coredevice/ttl.py index 768fc161e..431958fb7 100644 --- a/artiq/coredevice/ttl.py +++ b/artiq/coredevice/ttl.py @@ -224,20 +224,21 @@ class TTLClockGen(AutoDB): 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**24*frequency*self.core.ref_period) + 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**24 + return ftw/self.core.ref_period/2**self.acc_width @kernel def set_mu(self, frequency): From 00b9368a0c326c060554c63c076faf211c1b5ae9 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Sat, 4 Jul 2015 19:51:30 -0600 Subject: [PATCH 149/227] language/core: add EncodedException to __all_ --- artiq/language/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/language/core.py b/artiq/language/core.py index 051ef64c1..a4ca0b883 100644 --- a/artiq/language/core.py +++ b/artiq/language/core.py @@ -8,7 +8,7 @@ from functools import wraps __all__ = ["int64", "round64", "kernel", "portable", "set_time_manager", "set_syscall_manager", "set_watchdog_factory", - "RuntimeException"] + "RuntimeException", "EncodedException"] # global namespace for kernels kernel_globals = ("sequential", "parallel", From 4cbf280f1a59e379a2e23c6f1dc2ac0ae5cebbfa Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Sat, 4 Jul 2015 20:05:11 -0600 Subject: [PATCH 150/227] test: return experiment not, rdb --- artiq/test/coredevice.py | 21 ++++++++++++--------- artiq/test/hardware_testbench.py | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/artiq/test/coredevice.py b/artiq/test/coredevice.py index 9d8673dfb..5c784328b 100644 --- a/artiq/test/coredevice.py +++ b/artiq/test/coredevice.py @@ -90,23 +90,27 @@ class PulseRate(Experiment, AutoDB): class CoredeviceTest(ExperimentCase): def test_rtt(self): - rtt = self.execute(RTT)["rtt"] + self.execute(RTT) + rtt = self.dbh.get_result("rtt").read print(rtt) self.assertGreater(rtt, 0*ns) self.assertLess(rtt, 100*ns) def test_loopback(self): - rtt = self.execute(Loopback)["rtt"] + self.execute(Loopback) + rtt = self.dbh.get_result("rtt").read print(rtt) self.assertGreater(rtt, 0*ns) self.assertLess(rtt, 50*ns) def test_clock_generator_loopback(self): - count = self.execute(ClockGeneratorLoopback)["count"] + self.execute(ClockGeneratorLoopback) + count = self.dbh.get_result("count").read self.assertEqual(count, 10) def test_pulse_rate(self): - rate = self.execute(PulseRate)["pulse_rate"] + self.execute(PulseRate) + rate = self.dbh.get_result("pulse_rate").read print(rate) self.assertGreater(rate, 100*ns) self.assertLess(rate, 2500*ns) @@ -141,8 +145,7 @@ class RPCTiming(Experiment, AutoDB): class RPCTest(ExperimentCase): def test_rpc_timing(self): - res = self.execute(RPCTiming) - print(res) - self.assertGreater(res["rpc_time_mean"], 100*ns) - self.assertLess(res["rpc_time_mean"], 15*ms) - self.assertLess(res["rpc_time_stddev"], 1*ms) + self.execute(RPCTiming) + self.assertGreater(self.dbh.get_result("rpc_time_mean").read, 100*ns) + self.assertLess(self.dbh.get_result("rpc_time_mean").read, 15*ms) + self.assertLess(self.dbh.get_result("rpc_time_stddev").read, 1*ms) diff --git a/artiq/test/hardware_testbench.py b/artiq/test/hardware_testbench.py index bdb1e2e82..47d230ad2 100644 --- a/artiq/test/hardware_testbench.py +++ b/artiq/test/hardware_testbench.py @@ -38,6 +38,6 @@ class ExperimentCase(unittest.TestCase): self.rdb.build() exp.run() exp.analyze() - return self.rdb.data.read + return exp finally: self.dbh.close_devices() From 6faa8ecd51c522663a559c6c728a02aa12df8dd4 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Sat, 4 Jul 2015 20:35:02 -0600 Subject: [PATCH 151/227] test: split full_stack into coredevice and coredevice_vs_host also adapt it to hardware_testbench closes: #62 --- artiq/test/coredevice.py | 77 ++++++++ .../{full_stack.py => coredevice_vs_host.py} | 179 +++--------------- 2 files changed, 106 insertions(+), 150 deletions(-) rename artiq/test/{full_stack.py => coredevice_vs_host.py} (57%) diff --git a/artiq/test/coredevice.py b/artiq/test/coredevice.py index 5c784328b..1be60a398 100644 --- a/artiq/test/coredevice.py +++ b/artiq/test/coredevice.py @@ -3,6 +3,7 @@ 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(Experiment, AutoDB): @@ -88,6 +89,64 @@ class PulseRate(Experiment, AutoDB): break +class Watchdog(Experiment, AutoDB): + class DBKeys: + core = Device() + + @kernel + def run(self): + with watchdog(50*ms): + while True: + pass + + +class LoopbackCount(Experiment, AutoDB): + class DBKeys: + core = Device() + ttl_inout = Device() + npulses = Argument() + + def report(self, n): + self.result = n + + @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.report(self.ttl_inout.count()) + + +class Underflow(Experiment, AutoDB): + class DBKeys: + core = Device() + ttl_out = Device() + + @kernel + def run(self): + while True: + delay(25*ns) + self.ttl_out.pulse(25*ns) + + +class SequenceError(Experiment, AutoDB): + class DBKeys: + core = Device() + ttl_out = Device() + + @kernel + def run(self): + t = now_mu() + self.ttl_out.pulse(25*us) + at_mu(t) + self.ttl_out.pulse(25*us) + + class CoredeviceTest(ExperimentCase): def test_rtt(self): self.execute(RTT) @@ -115,6 +174,24 @@ class CoredeviceTest(ExperimentCase): self.assertGreater(rate, 100*ns) self.assertLess(rate, 2500*ns) + def test_loopback_count(self): + npulses = 2 + r = self.execute(LoopbackCount, npulses=npulses) + self.assertEqual(r.result, 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) + class RPCTiming(Experiment, AutoDB): class DBKeys: diff --git a/artiq/test/full_stack.py b/artiq/test/coredevice_vs_host.py similarity index 57% rename from artiq/test/full_stack.py rename to artiq/test/coredevice_vs_host.py index ad819827c..623aff4e8 100644 --- a/artiq/test/full_stack.py +++ b/artiq/test/coredevice_vs_host.py @@ -1,33 +1,19 @@ -import unittest from operator import itemgetter -import os from fractions import Fraction from artiq import * -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() +from artiq.test.hardware_testbench import ExperimentCase def _run_on_host(k_class, **parameters): coredev = sim_devices.Core() k_inst = k_class(core=coredev, **parameters) k_inst.run() + return k_inst -class _Primes(AutoDB): +class _Primes(Experiment, AutoDB): class DBKeys: core = Device() output_list = Argument() @@ -47,7 +33,10 @@ class _Primes(AutoDB): self.output_list.append(x) -class _Misc(AutoDB): +class _Misc(Experiment, AutoDB): + class DBKeys: + core = Device() + def build(self): self.input = 84 self.al = [1, 2, 3, 4, 5] @@ -63,7 +52,7 @@ class _Misc(AutoDB): self.list_copy_out = self.list_copy_in -class _PulseLogger(AutoDB): +class _PulseLogger(Experiment, AutoDB): class DBKeys: core = Device() output_list = Argument() @@ -90,7 +79,7 @@ class _PulseLogger(AutoDB): self.off(now_mu()) -class _Pulses(AutoDB): +class _Pulses(Experiment, AutoDB): class DBKeys: core = Device() output_list = Argument() @@ -118,7 +107,7 @@ class _MyException(Exception): pass -class _Exceptions(AutoDB): +class _Exceptions(Experiment, AutoDB): class DBKeys: core = Device() trace = Argument() @@ -162,9 +151,10 @@ class _Exceptions(AutoDB): self.trace.append(104) -class _RPCExceptions(AutoDB): +class _RPCExceptions(Experiment, AutoDB): class DBKeys: core = Device() + catch = Argument(False) def build(self): self.success = False @@ -172,53 +162,43 @@ class _RPCExceptions(AutoDB): 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 catch(self): + def do_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): +class HostVsDeviceCase(ExperimentCase): def test_primes(self): l_device, l_host = [], [] - _run_on_device(_Primes, maximum=100, output_list=l_device) + 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): - comm = comm_tcp.Comm(host=core_device) - try: - coredev = core.Core(comm=comm) - uut = _Misc(core=coredev) - uut.run() + 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) - finally: - comm.close() def test_pulses(self): l_device, l_host = [], [] - _run_on_device(_Pulses, output_list=l_device) + 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": @@ -229,115 +209,14 @@ class ExecutionCase(unittest.TestCase): def test_exceptions(self): t_device, t_host = [], [] with self.assertRaises(IndexError): - _run_on_device(_Exceptions, trace=t_device) + 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): - comm = comm_tcp.Comm(host=core_device) - try: - uut = _RPCExceptions(core=core.Core(comm=comm)) + for f in self.execute, _run_on_host: with self.assertRaises(_MyException): - uut.do_not_catch() - uut.catch() + f(_RPCExceptions, catch=False) + uut = self.execute(_RPCExceptions, catch=True) 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_mu() - self.o.pulse(25*us) - at_mu(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() From 959ba99f1c9844649fe8dc5144f7ba7a340de300 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Sat, 4 Jul 2015 21:08:28 -0600 Subject: [PATCH 152/227] pipistrello: try simpler constraints --- soc/targets/artiq_pipistrello.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/soc/targets/artiq_pipistrello.py b/soc/targets/artiq_pipistrello.py index de25fe7e3..77a9e4657 100644 --- a/soc/targets/artiq_pipistrello.py +++ b/soc/targets/artiq_pipistrello.py @@ -35,7 +35,10 @@ class _RTIOCRG(Module, AutoCSR): i_RST=ResetSignal()) rtio_external_clk = platform.request("pmt", 2) - platform.add_period_constraint(rtio_external_clk, 8.0) + # ISE infers constraints for the internal clock + # and propagates them through the BUFGMUX. Adding this: + # platform.add_period_constraint(rtio_external_clk, 8.0) + # seems to confuse it self.specials += Instance("BUFGMUX", i_I0=rtio_internal_clk, i_I1=rtio_external_clk, @@ -44,14 +47,9 @@ class _RTIOCRG(Module, AutoCSR): platform.add_platform_command(""" NET "{int_clk}" TNM_NET = "GRPint_clk"; -NET "{ext_clk}" TNM_NET = "GRPext_clk"; NET "sys_clk" TNM_NET = "GRPsys_clk"; TIMESPEC "TSfix_ise1" = FROM "GRPint_clk" TO "GRPsys_clk" TIG; TIMESPEC "TSfix_ise2" = FROM "GRPsys_clk" TO "GRPint_clk" TIG; -TIMESPEC "TSfix_ise3" = FROM "GRPext_clk" TO "GRPsys_clk" TIG; -TIMESPEC "TSfix_ise4" = FROM "GRPsys_clk" TO "GRPext_clk" TIG; -TIMESPEC "TSfix_ise5" = FROM "GRPext_clk" TO "GRPint_clk" TIG; -TIMESPEC "TSfix_ise6" = FROM "GRPint_clk" TO "GRPext_clk" TIG; """, int_clk=rtio_internal_clk, ext_clk=rtio_external_clk) From 409c66e96661ccee9a9e312157517e0b56659bfa Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Sat, 4 Jul 2015 21:44:28 -0600 Subject: [PATCH 153/227] test: convert lda/tcube/409b to hardware_testbench --- artiq/test/hardware_testbench.py | 15 +++++++++++++++ artiq/test/lda.py | 14 ++++---------- artiq/test/novatech409b.py | 7 ++----- artiq/test/thorlabs_tcube.py | 12 +++--------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/artiq/test/hardware_testbench.py b/artiq/test/hardware_testbench.py index 47d230ad2..e1b206885 100644 --- a/artiq/test/hardware_testbench.py +++ b/artiq/test/hardware_testbench.py @@ -14,6 +14,21 @@ 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.read[v] + 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): diff --git a/artiq/test/lda.py b/artiq/test/lda.py index 96a1ab0b1..5231b7964 100644 --- a/artiq/test/lda.py +++ b/artiq/test/lda.py @@ -1,11 +1,8 @@ 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: @@ -19,16 +16,13 @@ class GenericLdaTest: 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() diff --git a/artiq/test/novatech409b.py b/artiq/test/novatech409b.py index 165b81fad..314b1cb19 100644 --- a/artiq/test/novatech409b.py +++ b/artiq/test/novatech409b.py @@ -1,10 +1,7 @@ import unittest -import os from artiq.devices.novatech409b.driver import Novatech409B - - -novatech409b_device = os.getenv("NOVATECH409B_DEVICE") +from artiq.test.hardware_testbench import get_from_ddb class GenericNovatech409BTest: @@ -23,9 +20,9 @@ class GenericNovatech409BTest: self.assertEqual(r[0:23], "00989680 2000 01F5 0000") -@unittest.skipUnless(novatech409b_device, "no hardware") class TestNovatech409B(GenericNovatech409BTest, unittest.TestCase): def setUp(self): + novatech409b_device = get_from_ddb("novatech409b", "device") self.driver = Novatech409B(novatech409b_device) diff --git a/artiq/test/thorlabs_tcube.py b/artiq/test/thorlabs_tcube.py index eb2a18f97..0ecb362e8 100644 --- a/artiq/test/thorlabs_tcube.py +++ b/artiq/test/thorlabs_tcube.py @@ -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: @@ -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) From f6e8537db9e9f659f79595272607ba2a6071bd1f Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Sat, 4 Jul 2015 22:09:04 -0600 Subject: [PATCH 154/227] travis: add email-notification for hardware-ci --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5e9c750b1..25b8e51f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,11 @@ after_success: - binstar upload --user $binstar_login --channel dev --force $HOME/miniconda/conda-bld/linux-64/artiq-*.tar.bz2 - coveralls notifications: - email: false + email: + recipients: + - rjordens@nist.gov + on_success: always + on_failure: never irc: channels: - chat.freenode.net#m-labs From e056438cefeed6e6e4adba2fbe18fc603c59e31b Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Sat, 4 Jul 2015 22:09:20 -0600 Subject: [PATCH 155/227] travis: remove gitter webhook --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 25b8e51f2..9e9bd296a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,6 +37,3 @@ notifications: template: - "%{repository}#%{build_number} (%{branch} - %{commit} : %{author}): %{message}" - "Build details : %{build_url}" - webhooks: - urls: - - https://webhooks.gitter.im/e/d26782523952bfa53814 From a3fe5380678a021c078031f289317b350ac1fe6f Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Sat, 4 Jul 2015 22:36:23 -0600 Subject: [PATCH 156/227] test: fix get_from_ddb --- artiq/test/hardware_testbench.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/test/hardware_testbench.py b/artiq/test/hardware_testbench.py index e1b206885..386e255cb 100644 --- a/artiq/test/hardware_testbench.py +++ b/artiq/test/hardware_testbench.py @@ -20,7 +20,7 @@ def get_from_ddb(*path, default="skip"): v = FlatFileDB(os.path.join(artiq_root, "ddb.pyon")).data try: for p in path: - v = v.read[v] + v = v[p] return v.read except KeyError: if default == "skip": From 096c72242ea3617f524d48fa0712122f4e7f8842 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Sat, 4 Jul 2015 22:48:34 -0600 Subject: [PATCH 157/227] travis: shut up conda and binstar progress bars --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9e9bd296a..bba1eab6c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,17 +13,17 @@ before_install: - . ./.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 artiq --use-local + - 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 login -q --hostname $(hostname) --username $binstar_login --password $binstar_password + - binstar upload -q --user $binstar_login --channel dev --force $HOME/miniconda/conda-bld/linux-64/artiq-*.tar.bz2 - coveralls notifications: email: From fbdc0504d8c56d1b4aa44c819cd098cc2fb76f12 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Sat, 4 Jul 2015 22:52:09 -0600 Subject: [PATCH 158/227] travis: logout of binstar after upload --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index bba1eab6c..049a47401 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,7 @@ script: after_success: - binstar login -q --hostname $(hostname) --username $binstar_login --password $binstar_password - binstar upload -q --user $binstar_login --channel dev --force $HOME/miniconda/conda-bld/linux-64/artiq-*.tar.bz2 + - binstar logout -q - coveralls notifications: email: From 19442efdaeb77e61d468811ecf7df0297d98c65d Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Sat, 4 Jul 2015 23:19:08 -0600 Subject: [PATCH 159/227] travis: binstar takes -q before command... --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 049a47401..2469dedc2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,9 +22,9 @@ script: - coverage run --source=artiq setup.py test - make -C doc/manual html after_success: - - binstar login -q --hostname $(hostname) --username $binstar_login --password $binstar_password - - binstar upload -q --user $binstar_login --channel dev --force $HOME/miniconda/conda-bld/linux-64/artiq-*.tar.bz2 - - binstar logout -q + - 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: From 2eeaa3b9beabc4ef2b74d7d98db4a9a09ca5a370 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sun, 5 Jul 2015 18:43:32 +0200 Subject: [PATCH 160/227] pxi6733: clean up docstring --- artiq/devices/pxi6733/driver.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/artiq/devices/pxi6733/driver.py b/artiq/devices/pxi6733/driver.py index a57d2f0ff..d4a53423b 100644 --- a/artiq/devices/pxi6733/driver.py +++ b/artiq/devices/pxi6733/driver.py @@ -25,11 +25,11 @@ class DAQmx: def __init__(self, channels, clock): """ - :param channels: List of channels as a string or bytes(), following + :param channels: List of channels as a string, following the physical channels lists and ranges NI-DAQmx syntax. Example: Dev1/ao0, Dev1/ao1:ao3 - :param clock: Clock source terminal as a string or bytes(), following + :param clock: Clock source terminal as a string, following NI-DAQmx terminal names syntax. Example: PFI5 @@ -80,7 +80,8 @@ class DAQmx: not yet completed. :param sampling_freq: The sampling frequency in samples per second. - :param values: A numpy array of sample values to load in the device. + :param values: A numpy array of sample values (in volts) to load in + the device. """ self.clear_pending_task() From 58c01508226e721b7f016747b18ff289fb928514 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sun, 5 Jul 2015 19:07:13 +0200 Subject: [PATCH 161/227] ttl: improve clockgen doc --- artiq/coredevice/ttl.py | 4 ++++ artiq/gateware/rtio/phy/ttl_simple.py | 1 + 2 files changed, 5 insertions(+) diff --git a/artiq/coredevice/ttl.py b/artiq/coredevice/ttl.py index 431958fb7..d65aa62ff 100644 --- a/artiq/coredevice/ttl.py +++ b/artiq/coredevice/ttl.py @@ -253,6 +253,10 @@ class TTLClockGen(AutoDB): 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() diff --git a/artiq/gateware/rtio/phy/ttl_simple.py b/artiq/gateware/rtio/phy/ttl_simple.py index 0127d19a0..d424babe7 100644 --- a/artiq/gateware/rtio/phy/ttl_simple.py +++ b/artiq/gateware/rtio/phy/ttl_simple.py @@ -89,6 +89,7 @@ class ClockGen(Module): 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 From 2bc8286f3fb844fd51cfa8ef9dbcdc1da9895a7a Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sun, 5 Jul 2015 19:07:31 +0200 Subject: [PATCH 162/227] pdq2/mediator: fix arm --- artiq/devices/pdq2/mediator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/artiq/devices/pdq2/mediator.py b/artiq/devices/pdq2/mediator.py index 64cb2703c..39b6a89e6 100644 --- a/artiq/devices/pdq2/mediator.py +++ b/artiq/devices/pdq2/mediator.py @@ -75,7 +75,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 @@ -184,6 +184,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): From f9d878119a2ffddd9852b7f0c965b68398bb4d17 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sun, 5 Jul 2015 19:07:57 +0200 Subject: [PATCH 163/227] pxi6733: add mediator --- artiq/devices/pxi6733/mediator.py | 182 ++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 artiq/devices/pxi6733/mediator.py diff --git a/artiq/devices/pxi6733/mediator.py b/artiq/devices/pxi6733/mediator.py new file mode 100644 index 000000000..675a08847 --- /dev/null +++ b/artiq/devices/pxi6733/mediator.py @@ -0,0 +1,182 @@ +import numpy as np + +from artiq.language.core import * +from artiq.language.db 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(AutoDB): + class DBKeys: + core = Device() + daqmx_device = Argument() + clock_device = Argument() + channel_count = Argument() + sample_rate = Argument() + sample_rate_in_mu = Argument(False) + + def build(self): + self.daqmx = self.dbh.get_device(self.daqmx_device) + self.clock = self.dbh.get_device(self.clock_device) + + if not self.sample_rate_in_mu: + 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 From d20fb5abb28f238a9f4c7ae396ad1f1d01228025 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Tue, 7 Jul 2015 13:46:14 +0200 Subject: [PATCH 164/227] remove workaround --- soc/runtime/session.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/soc/runtime/session.c b/soc/runtime/session.c index 2cb19a495..352bca791 100644 --- a/soc/runtime/session.c +++ b/soc/runtime/session.c @@ -217,11 +217,6 @@ static int process_input(void) memcpy(&reply.eid, &buffer_in[9], 4); memcpy(&reply.retval, &buffer_in[13], 4); mailbox_send_and_wait(&reply); - /* HACK/FIXME: workaround for intermittent crashes that happen when running rpc_timing with comm_tcp */ - int i; - for(i=0;i<100000;i++) - __asm__ volatile("l.nop"); - /* */ user_kernel_state = USER_KERNEL_RUNNING; break; } From df232f54057338ab237e64085060d4d65efdb645 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Tue, 7 Jul 2015 15:18:28 +0200 Subject: [PATCH 165/227] lda driver: add error message when no device found --- artiq/devices/lda/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/devices/lda/driver.py b/artiq/devices/lda/driver.py index c198f04c7..d84605201 100644 --- a/artiq/devices/lda/driver.py +++ b/artiq/devices/lda/driver.py @@ -110,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.""" From 8a33d8c868dc93d81070a03e066b3900e85a77d3 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Tue, 7 Jul 2015 15:29:38 +0200 Subject: [PATCH 166/227] never stop RTIO counter --- artiq/coredevice/comm_generic.py | 12 ++++++--- artiq/coredevice/comm_serial.py | 1 + artiq/coredevice/core.py | 2 +- artiq/gateware/rtio/core.py | 3 ++- artiq/test/coredevice.py | 42 ++++++++++++++++++++++++++++++++ soc/runtime/ksupport.c | 2 +- soc/runtime/main.c | 19 +++++++++------ soc/runtime/rtio.c | 1 - soc/runtime/session.c | 20 +++++++++------ 9 files changed, 78 insertions(+), 24 deletions(-) diff --git a/artiq/coredevice/comm_generic.py b/artiq/coredevice/comm_generic.py index a737dfb84..88fee184b 100644 --- a/artiq/coredevice/comm_generic.py +++ b/artiq/coredevice/comm_generic.py @@ -95,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) @@ -125,9 +130,8 @@ 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) diff --git a/artiq/coredevice/comm_serial.py b/artiq/coredevice/comm_serial.py index 691b43928..25c926a25 100644 --- a/artiq/coredevice/comm_serial.py +++ b/artiq/coredevice/comm_serial.py @@ -19,6 +19,7 @@ class Comm(CommGeneric, AutoDB): 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"): diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index 6923b8ce9..61e7ed353 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -116,7 +116,7 @@ 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 diff --git a/artiq/gateware/rtio/core.py b/artiq/gateware/rtio/core.py index 9aad47127..ffe3a978f 100644 --- a/artiq/gateware/rtio/core.py +++ b/artiq/gateware/rtio/core.py @@ -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) diff --git a/artiq/test/coredevice.py b/artiq/test/coredevice.py index 1be60a398..0c5c32990 100644 --- a/artiq/test/coredevice.py +++ b/artiq/test/coredevice.py @@ -147,6 +147,33 @@ class SequenceError(Experiment, AutoDB): self.ttl_out.pulse(25*us) +class TimeKeepsRunning(Experiment, AutoDB): + class DBKeys: + core = Device() + time_at_start = Result() + + @kernel + def run(self): + self.time_at_start = now_mu() + + +class Handover(Experiment, AutoDB): + class DBKeys: + core = Device() + t1 = Result() + t2 = Result() + + @kernel + def get_now(self): + self.time_at_start = now_mu() + + def run(self): + self.get_now() + self.t1 = self.time_at_start + self.get_now() + self.t2 = self.time_at_start + + class CoredeviceTest(ExperimentCase): def test_rtt(self): self.execute(RTT) @@ -192,6 +219,21 @@ class CoredeviceTest(ExperimentCase): with self.assertRaises(IOError): self.execute(Watchdog) + def test_time_keeps_running(self): + self.execute(TimeKeepsRunning) + t1 = self.dbh.get_result("time_at_start").read + self.dbh.close_devices() # a fortiori the core device connection + self.execute(TimeKeepsRunning) + t2 = self.dbh.get_result("time_at_start").read + self.assertGreater(mu_to_seconds(t2 - t1, + self.dbh.get_device("core")), + 1*ms) + + def test_handover(self): + self.execute(Handover) + self.assertEqual(self.dbh.get_result("t1").read, + self.dbh.get_result("t2").read) + class RPCTiming(Experiment, AutoDB): class DBKeys: diff --git a/soc/runtime/ksupport.c b/soc/runtime/ksupport.c index a2eb090c6..032fe88ec 100644 --- a/soc/runtime/ksupport.c +++ b/soc/runtime/ksupport.c @@ -79,7 +79,7 @@ long long int now_init(void) if(now < 0) { rtio_init(); - now = 125000; + now = rtio_get_counter() + 125000; } return now; diff --git a/soc/runtime/main.c b/soc/runtime/main.c index fec357a0f..41433d264 100644 --- a/soc/runtime/main.c +++ b/soc/runtime/main.c @@ -151,16 +151,18 @@ static void regular_main(void) #else /* CSR_ETHMAC_BASE */ -static void reset_serial_session(void) +static void reset_serial_session(int signal) { int i; session_end(); - /* Signal end-of-session inband with zero length packet. */ - for(i=0;i<4;i++) - uart_write(0x5a); - for(i=0;i<4;i++) - uart_write(0x00); + if(signal) { + /* Signal end-of-session inband with zero length packet. */ + for(i=0;i<4;i++) + uart_write(0x5a); + for(i=0;i<4;i++) + uart_write(0x00); + } session_start(); } @@ -181,7 +183,8 @@ static void serial_service(void) if(r > 0) rxpending = 0; if(r < 0) - reset_serial_session(); + /* do not signal if reset was requested by host */ + reset_serial_session(r != -2); } session_poll((void **)&txdata, &txlen); @@ -191,7 +194,7 @@ static void serial_service(void) session_ack_data(txlen); session_ack_mem(txlen); } else if(txlen < 0) - reset_serial_session(); + reset_serial_session(1); } static void regular_main(void) diff --git a/soc/runtime/rtio.c b/soc/runtime/rtio.c index 0e5cdad10..3329019ba 100644 --- a/soc/runtime/rtio.c +++ b/soc/runtime/rtio.c @@ -14,4 +14,3 @@ long long int rtio_get_counter(void) rtio_counter_update_write(1); return rtio_counter_read(); } - diff --git a/soc/runtime/session.c b/soc/runtime/session.c index 352bca791..1a7f5d879 100644 --- a/soc/runtime/session.c +++ b/soc/runtime/session.c @@ -70,11 +70,13 @@ void session_start(void) memset(&buffer_out[4], 0, 4); kloader_stop(); user_kernel_state = USER_KERNEL_NONE; + now = -1; } void session_end(void) { kloader_stop(); + now = -1; kloader_start_idle_kernel(); } @@ -189,10 +191,7 @@ static int process_input(void) } buffer_in[buffer_in_index] = 0; - if(buffer_in[9]) - now = -1; - - k = kloader_find((char *)&buffer_in[10]); + k = kloader_find((char *)&buffer_in[9]); if(k == NULL) { log("Failed to find kernel entry point '%s' in object", &buffer_in[9]); buffer_out[8] = REMOTEMSG_TYPE_KERNEL_STARTUP_FAILED; @@ -311,6 +310,9 @@ int session_input(void *data, int len) /* receiving length */ buffer_in[buffer_in_index++] = _data[consumed]; consumed++; len--; + if((buffer_in_index == 8) && (get_in_packet_len() == 0)) + /* zero-length packet = session reset */ + return -2; } else { /* receiving payload */ int packet_len; @@ -465,12 +467,14 @@ static int send_rpc_request(int rpc_num, va_list args) /* assumes output buffer is empty when called */ static int process_kmsg(struct msg_base *umsg) { - if(user_kernel_state != USER_KERNEL_RUNNING) { - log("Received message from kernel CPU while not in running state"); - return 0; - } if(!validate_kpointer(umsg)) return 0; + if((user_kernel_state != USER_KERNEL_RUNNING) + && (umsg->type != MESSAGE_TYPE_NOW_INIT_REQUEST) + && (umsg->type != MESSAGE_TYPE_NOW_SAVE)) { + log("Received unexpected message from kernel CPU while not in running state"); + return 0; + } switch(umsg->type) { case MESSAGE_TYPE_NOW_INIT_REQUEST: { From 706bf2b4b474671f148665cc225cb998ce343636 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Tue, 7 Jul 2015 15:39:51 +0200 Subject: [PATCH 167/227] pxi6733: allow usage of 2-dimensional arrays. close #66 --- artiq/devices/pxi6733/driver.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/artiq/devices/pxi6733/driver.py b/artiq/devices/pxi6733/driver.py index d4a53423b..22839ee04 100644 --- a/artiq/devices/pxi6733/driver.py +++ b/artiq/devices/pxi6733/driver.py @@ -63,9 +63,9 @@ class DAQmx: 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 must concatenate the - values for the different channels in the ``values`` array. - The sample values for the same channel must be grouped together. + When using several channels simultaneously, you can either concatenate + the values for the different channels in a 1-dimensional ``values`` + numpy ndarray. Example: @@ -76,16 +76,21 @@ class DAQmx: 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 array of sample values (in volts) to load in + :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.channels, b"", min(values), max(values), From f3e5197c14a6c3349c73b7e1928ee1e809d1cc01 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Tue, 7 Jul 2015 15:48:47 +0200 Subject: [PATCH 168/227] test/coredevice/test_time_keeps_running: remove unnecessary close_devices, upper bound --- artiq/test/coredevice.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/artiq/test/coredevice.py b/artiq/test/coredevice.py index 0c5c32990..0d6aa8ee0 100644 --- a/artiq/test/coredevice.py +++ b/artiq/test/coredevice.py @@ -222,12 +222,12 @@ class CoredeviceTest(ExperimentCase): def test_time_keeps_running(self): self.execute(TimeKeepsRunning) t1 = self.dbh.get_result("time_at_start").read - self.dbh.close_devices() # a fortiori the core device connection self.execute(TimeKeepsRunning) t2 = self.dbh.get_result("time_at_start").read - self.assertGreater(mu_to_seconds(t2 - t1, - self.dbh.get_device("core")), - 1*ms) + dead_time = mu_to_seconds(t2 - t1, self.dbh.get_device("core")) + print(dead_time) + self.assertGreater(dead_time, 1*ms) + self.assertLess(dead_time, 200*ms) def test_handover(self): self.execute(Handover) From 2d343dd95d89b927d642312e17863ad90aa35e7e Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Tue, 7 Jul 2015 19:26:33 +0200 Subject: [PATCH 169/227] pc_rpc: AsyncioClient __do_rpc coroutine was never yielded from --- artiq/protocols/pc_rpc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/artiq/protocols/pc_rpc.py b/artiq/protocols/pc_rpc.py index d50c13591..539e9f7f3 100644 --- a/artiq/protocols/pc_rpc.py +++ b/artiq/protocols/pc_rpc.py @@ -246,7 +246,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 From 01098210781cc239be2d72c3fb7d9c9c5a5c4cf2 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Tue, 7 Jul 2015 19:30:13 +0200 Subject: [PATCH 170/227] tools: change asyncio_process_wait_timeout to handle cases where process.stdout is None. close #56 --- artiq/tools.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/artiq/tools.py b/artiq/tools.py index c7ddcab48..1d1f9c553 100644 --- a/artiq/tools.py +++ b/artiq/tools.py @@ -91,13 +91,14 @@ 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 - r = True - while r: - r = yield from asyncio.wait_for( - process.stdout.read(1024), - timeout=end_time - time.monotonic()) - + @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): From 34aacd3c5f8eb0a2ad7402d803e5306352f438ea Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Wed, 8 Jul 2015 17:22:43 +0200 Subject: [PATCH 171/227] complete AD9914 support (no programmable modulus, untested) --- artiq/coredevice/dds.py | 27 ++++++-- artiq/gateware/ad9xxx.py | 8 +-- doc/manual/core_drivers_reference.rst | 3 + examples/master/ddb.pyon | 6 +- soc/runtime/bridge.c | 4 +- soc/runtime/dds.c | 93 +++++++++++++++++++++++---- soc/runtime/dds.h | 32 ++++++++- soc/runtime/main.c | 24 +++---- soc/runtime/test_mode.c | 75 +++++++++++++++++---- soc/targets/artiq_pipistrello.py | 1 + 10 files changed, 221 insertions(+), 52 deletions(-) diff --git a/artiq/coredevice/dds.py b/artiq/coredevice/dds.py index 09c4cb78b..323209479 100644 --- a/artiq/coredevice/dds.py +++ b/artiq/coredevice/dds.py @@ -46,11 +46,14 @@ class DDSBus(AutoDB): syscall("dds_batch_exit") -class DDS(AutoDB): +class _DDSGeneric(AutoDB): """Core device Direct Digital Synthesis (DDS) driver. Controls one DDS channel managed directly by the core device's runtime. + 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. """ @@ -80,13 +83,13 @@ class DDS(AutoDB): def turns_to_pow(self, turns): """Returns the phase offset word corresponding to the given phase in turns.""" - return round(turns*2**14) + 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**14 + return pow/2**self.pow_width @kernel def init(self): @@ -119,7 +122,9 @@ class DDS(AutoDB): 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). + 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. @@ -129,10 +134,22 @@ class DDS(AutoDB): if phase_mode == _PHASE_MODE_DEFAULT: phase_mode = self.phase_mode syscall("dds_set", now_mu(), self.channel, - frequency, round(phase*2**14), phase_mode) + frequency, round(phase*2**self.pow_width), 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 diff --git a/artiq/gateware/ad9xxx.py b/artiq/gateware/ad9xxx.py index f774f63ed..aa087053f 100644 --- a/artiq/gateware/ad9xxx.py +++ b/artiq/gateware/ad9xxx.py @@ -13,7 +13,7 @@ class AD9xxx(Module): 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. sel is mapped to the lower bits, followed by reset. + 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. @@ -58,11 +58,11 @@ class AD9xxx(Module): gpio = Signal(flen(pads.sel) + 1) gpio_load = Signal() self.sync += If(gpio_load, gpio.eq(bus.dat_w)) - self.comb += pads.sel.eq(gpio), if hasattr(pads, "rst"): - self.comb += pads.rst.eq(gpio[-1]) + self.comb += pads.rst.eq(gpio[0]) else: - self.comb += pads.rst_n.eq(~gpio[-1]) + 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, diff --git a/doc/manual/core_drivers_reference.rst b/doc/manual/core_drivers_reference.rst index 0c9c38301..56fca095b 100644 --- a/doc/manual/core_drivers_reference.rst +++ b/doc/manual/core_drivers_reference.rst @@ -12,6 +12,9 @@ These drivers are for peripherals closely integrated into the core device, which :mod:`artiq.coredevice.dds` module ---------------------------------- +.. autoclass:: artiq.coredevice.dds._DDSGeneric + :members: + .. automodule:: artiq.coredevice.dds :members: diff --git a/examples/master/ddb.pyon b/examples/master/ddb.pyon index 81903fca5..ff945141a 100644 --- a/examples/master/ddb.pyon +++ b/examples/master/ddb.pyon @@ -65,19 +65,19 @@ "dds0": { "type": "local", "module": "artiq.coredevice.dds", - "class": "DDS", + "class": "AD9858", "arguments": {"sysclk": 1e9, "channel": 0} }, "dds1": { "type": "local", "module": "artiq.coredevice.dds", - "class": "DDS", + "class": "AD9858", "arguments": {"sysclk": 1e9, "channel": 1} }, "dds2": { "type": "local", "module": "artiq.coredevice.dds", - "class": "DDS", + "class": "AD9858", "arguments": {"sysclk": 1e9, "channel": 2} }, diff --git a/soc/runtime/bridge.c b/soc/runtime/bridge.c index c61875236..eb16587de 100644 --- a/soc/runtime/bridge.c +++ b/soc/runtime/bridge.c @@ -66,7 +66,7 @@ void bridge_main(void) struct msg_brg_dds_sel *msg; msg = (struct msg_brg_dds_sel *)umsg; - dds_write(DDS_GPIO, msg->channel); + dds_write(DDS_GPIO, msg->channel << 1); mailbox_acknowledge(); break; } @@ -74,7 +74,7 @@ void bridge_main(void) unsigned int g; g = dds_read(DDS_GPIO); - dds_write(DDS_GPIO, g | (1 << 7)); + dds_write(DDS_GPIO, g | 1); dds_write(DDS_GPIO, g); mailbox_acknowledge(); diff --git a/soc/runtime/dds.c b/soc/runtime/dds.c index 326c48845..5f9f8650e 100644 --- a/soc/runtime/dds.c +++ b/soc/runtime/dds.c @@ -7,9 +7,24 @@ #include "dds.h" #define DURATION_WRITE (5 << RTIO_FINE_TS_WIDTH) + +#if defined DDS_AD9858 +/* Assume 8-bit bus */ #define DURATION_INIT (7*DURATION_WRITE) /* not counting FUD */ #define DURATION_PROGRAM (8*DURATION_WRITE) /* not counting FUD */ +#elif defined DDS_AD9914 +/* Assume 16-bit bus */ +/* DAC calibration takes max. 135us as per datasheet. Take a good margin. */ +#define DURATION_DAC_CAL (30000 << RTIO_FINE_TS_WIDTH) +/* not counting final FUD */ +#define DURATION_INIT (8*DURATION_WRITE + DURATION_DAC_CAL) +#define DURATION_PROGRAM (5*DURATION_WRITE) /* not counting FUD */ + +#else +#error Unknown DDS configuration +#endif + #define DDS_WRITE(addr, data) do { \ rtio_o_address_write(addr); \ rtio_o_data_write(data); \ @@ -39,16 +54,43 @@ void dds_init(long long int timestamp, int channel) now = timestamp - DURATION_INIT; +#ifdef DDS_ONEHOT_SEL + channel = 1 << channel; +#endif + channel <<= 1; DDS_WRITE(DDS_GPIO, channel); - DDS_WRITE(DDS_GPIO, channel | (1 << 5)); + DDS_WRITE(DDS_GPIO, channel | 1); /* reset */ DDS_WRITE(DDS_GPIO, channel); - DDS_WRITE(0x00, 0x78); - DDS_WRITE(0x01, 0x00); - DDS_WRITE(0x02, 0x00); - DDS_WRITE(0x03, 0x00); - +#ifdef DDS_AD9858 + /* + * 2GHz divider disable + * SYNCLK disable + * Mixer power-down + * Phase detect power down + */ + DDS_WRITE(DDS_CFR0, 0x78); + DDS_WRITE(DDS_CFR1, 0x00); + DDS_WRITE(DDS_CFR2, 0x00); + DDS_WRITE(DDS_CFR3, 0x00); DDS_WRITE(DDS_FUD, 0); +#endif + +#ifdef DDS_AD9914 + /* + * Enable cosine output (to match AD9858 behavior) + * Enable DAC calibration + * Leave SYNCLK enabled and PLL/divider disabled + */ + DDS_WRITE(DDS_CFR1L, 0x0008); + DDS_WRITE(DDS_CFR1H, 0x0000); + DDS_WRITE(DDS_CFR4H, 0x0105); + DDS_WRITE(DDS_FUD, 0); + /* Disable DAC calibration */ + now += DURATION_DAC_CAL; + DDS_WRITE(DDS_CFR4H, 0x0005); + DDS_WRITE(DDS_FUD, 0); +#endif } /* Compensation to keep phase continuity when switching from absolute or tracking @@ -58,38 +100,67 @@ static unsigned int continuous_phase_comp[DDS_CHANNEL_COUNT]; static void dds_set_one(long long int now, long long int ref_time, unsigned int channel, unsigned int ftw, unsigned int pow, int phase_mode) { + unsigned int channel_enc; + if(channel >= DDS_CHANNEL_COUNT) { log("Attempted to set invalid DDS channel"); return; } - DDS_WRITE(DDS_GPIO, channel); +#ifdef DDS_ONEHOT_SEL + channel_enc = 1 << channel; +#else + channel_enc = channel; +#endif + DDS_WRITE(DDS_GPIO, channel_enc << 1); +#ifdef DDS_AD9858 DDS_WRITE(DDS_FTW0, ftw & 0xff); DDS_WRITE(DDS_FTW1, (ftw >> 8) & 0xff); DDS_WRITE(DDS_FTW2, (ftw >> 16) & 0xff); DDS_WRITE(DDS_FTW3, (ftw >> 24) & 0xff); +#endif + +#ifdef DDS_AD9914 + DDS_WRITE(DDS_FTWL, ftw & 0xffff); + DDS_WRITE(DDS_FTWH, (ftw >> 16) & 0xffff); +#endif /* We need the RTIO fine timestamp clock to be phase-locked * to DDS SYSCLK, and divided by an integer DDS_RTIO_CLK_RATIO. */ if(phase_mode == PHASE_MODE_CONTINUOUS) { /* Do not clear phase accumulator on FUD */ - DDS_WRITE(0x02, 0x00); +#ifdef DDS_AD9858 + DDS_WRITE(DDS_CFR2, 0x00); +#endif +#ifdef DDS_AD9914 + DDS_WRITE(DDS_CFR1L, 0x0008); +#endif pow += continuous_phase_comp[channel]; } else { long long int fud_time; /* Clear phase accumulator on FUD */ - DDS_WRITE(0x02, 0x40); +#ifdef DDS_AD9858 + DDS_WRITE(DDS_CFR2, 0x40); +#endif +#ifdef DDS_AD9914 + DDS_WRITE(DDS_CFR1L, 0x2008); +#endif fud_time = now + 2*DURATION_WRITE; - pow -= (ref_time - fud_time)*DDS_RTIO_CLK_RATIO*ftw >> 18; + pow -= (ref_time - fud_time)*DDS_RTIO_CLK_RATIO*ftw >> (32-DDS_POW_WIDTH); if(phase_mode == PHASE_MODE_TRACKING) - pow += ref_time*DDS_RTIO_CLK_RATIO*ftw >> 18; + pow += ref_time*DDS_RTIO_CLK_RATIO*ftw >> (32-DDS_POW_WIDTH); continuous_phase_comp[channel] = pow; } +#ifdef DDS_AD9858 DDS_WRITE(DDS_POW0, pow & 0xff); DDS_WRITE(DDS_POW1, (pow >> 8) & 0x3f); +#endif +#ifdef DDS_AD9914 + DDS_WRITE(DDS_POW, pow); +#endif DDS_WRITE(DDS_FUD, 0); } diff --git a/soc/runtime/dds.h b/soc/runtime/dds.h index e145b5b01..6ca9d1c1e 100644 --- a/soc/runtime/dds.h +++ b/soc/runtime/dds.h @@ -2,12 +2,17 @@ #define __DDS_H #include +#include #include /* Maximum number of commands in a batch */ #define DDS_MAX_BATCH 16 -/* DDS core registers */ +#ifdef DDS_AD9858 +#define DDS_CFR0 0x00 +#define DDS_CFR1 0x01 +#define DDS_CFR2 0x02 +#define DDS_CFR3 0x03 #define DDS_FTW0 0x0a #define DDS_FTW1 0x0b #define DDS_FTW2 0x0c @@ -16,6 +21,31 @@ #define DDS_POW1 0x0f #define DDS_FUD 0x40 #define DDS_GPIO 0x41 +#endif + +#ifdef DDS_AD9914 +#define DDS_CFR1L 0x01 +#define DDS_CFR1H 0x03 +#define DDS_CFR2L 0x05 +#define DDS_CFR2H 0x07 +#define DDS_CFR3L 0x09 +#define DDS_CFR3H 0x0b +#define DDS_CFR4L 0x0d +#define DDS_CFR4H 0x0f +#define DDS_FTWL 0x2d +#define DDS_FTWH 0x2f +#define DDS_POW 0x31 +#define DDS_FUD 0x80 +#define DDS_GPIO 0x81 +#endif + +#ifdef DDS_AD9858 +#define DDS_POW_WIDTH 14 +#endif + +#ifdef DDS_AD9914 +#define DDS_POW_WIDTH 16 +#endif enum { PHASE_MODE_CONTINUOUS = 0, diff --git a/soc/runtime/main.c b/soc/runtime/main.c index 41433d264..4feb8c9a0 100644 --- a/soc/runtime/main.c +++ b/soc/runtime/main.c @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -32,7 +31,6 @@ static void common_init(void) { - clock_init(); brg_start(); brg_ddsinitall(); kloader_stop(); @@ -211,34 +209,31 @@ static void regular_main(void) static void blink_led(void) { - int i, ev, p; + int i; + long long int t; - p = identifier_frequency_read()/10; - time_init(); for(i=0;i<3;i++) { leds_out_write(1); - while(!elapsed(&ev, p)); + t = clock_get_ms(); + while(clock_get_ms() < t + 250); leds_out_write(0); - while(!elapsed(&ev, p)); + t = clock_get_ms(); + while(clock_get_ms() < t + 250); } } static int check_test_mode(void) { char c; + long long int t; - timer0_en_write(0); - timer0_reload_write(0); - timer0_load_write(identifier_frequency_read() >> 2); - timer0_en_write(1); - timer0_update_value_write(1); - while(timer0_value_read()) { + t = clock_get_ms(); + while(clock_get_ms() < t + 1000) { if(readchar_nonblock()) { c = readchar(); if((c == 't')||(c == 'T')) return 1; } - timer0_update_value_write(1); } return 0; } @@ -251,6 +246,7 @@ int main(void) puts("ARTIQ runtime built "__DATE__" "__TIME__"\n"); + clock_init(); puts("Press 't' to enter test mode..."); blink_led(); diff --git a/soc/runtime/test_mode.c b/soc/runtime/test_mode.c index fbdc0ce52..3c0ce02f0 100644 --- a/soc/runtime/test_mode.c +++ b/soc/runtime/test_mode.c @@ -10,6 +10,7 @@ #include "dds.h" #include "flash_storage.h" #include "bridge_ctl.h" +#include "clock.h" #include "test_mode.h" static void leds(char *value) @@ -114,6 +115,9 @@ static void ddssel(char *n) return; } +#ifdef DDS_ONEHOT_SEL + n2 = 1 << n2; +#endif brg_ddssel(n2); } @@ -157,7 +161,12 @@ static void ddsr(char *addr) return; } +#ifdef DDS_AD9858 printf("0x%02x\n", brg_ddsread(addr2)); +#endif +#ifdef DDS_AD9914 + printf("0x%04x\n", brg_ddsread(addr2)); +#endif } static void ddsfud(void) @@ -186,11 +195,22 @@ static void ddsftw(char *n, char *ftw) return; } +#ifdef DDS_ONEHOT_SEL + n2 = 1 << n2; +#endif brg_ddssel(n2); + +#ifdef DDS_AD9858 brg_ddswrite(DDS_FTW0, ftw2 & 0xff); brg_ddswrite(DDS_FTW1, (ftw2 >> 8) & 0xff); brg_ddswrite(DDS_FTW2, (ftw2 >> 16) & 0xff); brg_ddswrite(DDS_FTW3, (ftw2 >> 24) & 0xff); +#endif +#ifdef DDS_AD9914 + brg_ddswrite(DDS_FTWL, ftw2 & 0xffff); + brg_ddswrite(DDS_FTWH, (ftw2 >> 16) & 0xffff); +#endif + brg_ddsfud(); } @@ -199,15 +219,34 @@ static void ddsreset(void) brg_ddsreset(); } +#ifdef DDS_AD9858 static void ddsinit(void) { brg_ddsreset(); - brg_ddswrite(0x00, 0x78); - brg_ddswrite(0x01, 0x00); - brg_ddswrite(0x02, 0x00); - brg_ddswrite(0x03, 0x00); + brg_ddswrite(DDS_CFR0, 0x78); + brg_ddswrite(DDS_CFR1, 0x00); + brg_ddswrite(DDS_CFR2, 0x00); + brg_ddswrite(DDS_CFR3, 0x00); brg_ddsfud(); } +#endif + +#ifdef DDS_AD9914 +static void ddsinit(void) +{ + long long int t; + + brg_ddsreset(); + brg_ddswrite(DDS_CFR1L, 0x0008); + brg_ddswrite(DDS_CFR1H, 0x0000); + brg_ddswrite(DDS_CFR4H, 0x0105); + brg_ddswrite(DDS_FUD, 0); + t = clock_get_ms(); + while(clock_get_ms() < t + 2); + brg_ddswrite(DDS_CFR4H, 0x0005); + brg_ddsfud(); +} +#endif static void ddstest_one(unsigned int i) { @@ -223,15 +262,27 @@ static void ddstest_one(unsigned int i) for(j=0; j<12; j++) { f = v[j]; - brg_ddswrite(0x0a, f & 0xff); - brg_ddswrite(0x0b, (f >> 8) & 0xff); - brg_ddswrite(0x0c, (f >> 16) & 0xff); - brg_ddswrite(0x0d, (f >> 24) & 0xff); +#ifdef DDS_AD9858 + brg_ddswrite(DDS_FTW0, f & 0xff); + brg_ddswrite(DDS_FTW1, (f >> 8) & 0xff); + brg_ddswrite(DDS_FTW2, (f >> 16) & 0xff); + brg_ddswrite(DDS_FTW3, (f >> 24) & 0xff); +#endif +#ifdef DDS_AD9914 + brg_ddswrite(DDS_FTWL, f & 0xffff); + brg_ddswrite(DDS_FTWH, (f >> 16) & 0xffff); +#endif brg_ddsfud(); - g = brg_ddsread(0x0a); - g |= brg_ddsread(0x0b) << 8; - g |= brg_ddsread(0x0c) << 16; - g |= brg_ddsread(0x0d) << 24; +#ifdef DDS_AD9858 + g = brg_ddsread(DDS_FTW0); + g |= brg_ddsread(DDS_FTW1) << 8; + g |= brg_ddsread(DDS_FTW2) << 16; + g |= brg_ddsread(DDS_FTW3) << 24; +#endif +#ifdef DDS_AD9914 + g = brg_ddsread(DDS_FTWL); + g |= brg_ddsread(DDS_FTWH) << 16; +#endif if(g != f) printf("readback fail on DDS %d, 0x%08x != 0x%08x\n", i, g, f); } diff --git a/soc/targets/artiq_pipistrello.py b/soc/targets/artiq_pipistrello.py index 77a9e4657..fd09ed373 100644 --- a/soc/targets/artiq_pipistrello.py +++ b/soc/targets/artiq_pipistrello.py @@ -119,6 +119,7 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd self.add_constant("RTIO_DDS_CHANNEL", len(rtio_channels)) self.add_constant("DDS_CHANNEL_COUNT", 8) + self.add_constant("DDS_AD9858") phy = dds.AD9858(platform.request("dds"), 8) self.submodules += phy rtio_channels.append(rtio.Channel.from_phy(phy, From 96a5d73c81f01891e606509bac055aebdd2e2622 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 9 Jul 2015 13:18:12 +0200 Subject: [PATCH 172/227] worker: split build stage from prepare --- artiq/language/experiment.py | 24 ++++++++++++++++++------ artiq/master/scheduler.py | 10 ++++++---- artiq/master/worker.py | 16 ++++++++++------ artiq/master/worker_impl.py | 5 ++++- artiq/test/worker.py | 3 ++- 5 files changed, 40 insertions(+), 18 deletions(-) diff --git a/artiq/language/experiment.py b/artiq/language/experiment.py index c7fce7a55..b3394918c 100644 --- a/artiq/language/experiment.py +++ b/artiq/language/experiment.py @@ -1,6 +1,6 @@ from inspect import isclass -__all__ = ["Experiment", "has_analyze", "is_experiment"] +__all__ = ["Experiment", "is_experiment"] class Experiment: @@ -9,11 +9,28 @@ class Experiment: 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 @@ -32,11 +49,6 @@ class Experiment: 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 diff --git a/artiq/master/scheduler.py b/artiq/master/scheduler.py index 9d989af73..93afb0508 100644 --- a/artiq/master/scheduler.py +++ b/artiq/master/scheduler.py @@ -99,13 +99,14 @@ class Run: 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, - self.priority) + 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") @@ -188,6 +189,7 @@ class PrepareStage(TaskObject): 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, " diff --git a/artiq/master/worker.py b/artiq/master/worker.py index 59955d09a..e3b1827ed 100644 --- a/artiq/master/worker.py +++ b/artiq/master/worker.py @@ -28,11 +28,11 @@ 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): + build_timeout=15.0, results_timeout=15.0): self.handlers = handlers self.send_timeout = send_timeout self.term_timeout = term_timeout - self.prepare_timeout = prepare_timeout + self.build_timeout = build_timeout self.results_timeout = results_timeout self.rid = None @@ -142,7 +142,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() @@ -209,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): 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) + self.build_timeout) + + @asyncio.coroutine + def prepare(self): + yield from self._worker_action({"action": "prepare"}) @asyncio.coroutine def run(self): diff --git a/artiq/master/worker_impl.py b/artiq/master/worker_impl.py index e82a79b1b..c5307d51a 100644 --- a/artiq/master/worker_impl.py +++ b/artiq/master/worker_impl.py @@ -117,7 +117,7 @@ def main(): 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"] @@ -131,6 +131,9 @@ def main(): **expid["arguments"]) rdb.build() put_object({"action": "completed"}) + elif action == "prepare": + exp_inst.prepare() + put_object({"action": "completed"}) elif action == "run": exp_inst.run() put_object({"action": "completed"}) diff --git a/artiq/test/worker.py b/artiq/test/worker.py index 7e0e1d3ab..ccd5339aa 100644 --- a/artiq/test/worker.py +++ b/artiq/test/worker.py @@ -32,7 +32,8 @@ class WatchdogTimeoutInBuild(Experiment, AutoDB): @asyncio.coroutine def _call_worker(worker, expid): try: - yield from worker.prepare(0, "main", expid, 0) + yield from worker.build(0, "main", expid, 0) + yield from worker.prepare() yield from worker.run() yield from worker.analyze() finally: From 9d4073ef3689d625d9b34957d6b229a773ce5714 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 9 Jul 2015 13:54:38 +0200 Subject: [PATCH 173/227] master: remove simple parameter history --- artiq/frontend/artiq_master.py | 5 +---- artiq/protocols/file_db.py | 16 ---------------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/artiq/frontend/artiq_master.py b/artiq/frontend/artiq_master.py index 1c9b19e6a..1f95d26cc 100755 --- a/artiq/frontend/artiq_master.py +++ b/artiq/frontend/artiq_master.py @@ -7,7 +7,7 @@ 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.file_db import FlatFileDB from artiq.master.scheduler import Scheduler from artiq.master.results import RTResults, get_last_rid from artiq.master.repository import Repository @@ -36,8 +36,6 @@ def main(): init_logger(args) ddb = FlatFileDB("ddb.pyon") pdb = FlatFileDB("pdb.pyon") - simplephist = SimpleHistory(30) - pdb.hooks.append(simplephist) rtr = RTResults() repository = Repository() @@ -74,7 +72,6 @@ def main(): "schedule": scheduler.notifier, "devices": ddb.data, "parameters": pdb.data, - "parameters_simplehist": simplephist.history, "rt_results": rtr.groups, "explist": repository.explist }) diff --git a/artiq/protocols/file_db.py b/artiq/protocols/file_db.py index 71228985c..6675dfcff 100644 --- a/artiq/protocols/file_db.py +++ b/artiq/protocols/file_db.py @@ -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)) From 80eea4ce6c09528b52b6e1a763db0e7e1cf228ee Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Thu, 9 Jul 2015 16:30:37 -0600 Subject: [PATCH 174/227] test: relax test_time_keeps_running on pipistrello this takes about 200-250ms --- artiq/test/coredevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/test/coredevice.py b/artiq/test/coredevice.py index 0d6aa8ee0..c780d19c2 100644 --- a/artiq/test/coredevice.py +++ b/artiq/test/coredevice.py @@ -227,7 +227,7 @@ class CoredeviceTest(ExperimentCase): dead_time = mu_to_seconds(t2 - t1, self.dbh.get_device("core")) print(dead_time) self.assertGreater(dead_time, 1*ms) - self.assertLess(dead_time, 200*ms) + self.assertLess(dead_time, 300*ms) def test_handover(self): self.execute(Handover) From 01ca6ebb1c677f57f5177b38b9d65eb6ec4323b8 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Fri, 10 Jul 2015 16:29:43 +0200 Subject: [PATCH 175/227] binstar.org is becoming anaconda.org --- .travis/get-anaconda.sh | 2 +- doc/manual/installing.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis/get-anaconda.sh b/.travis/get-anaconda.sh index 9f661b2d7..a4c2524b9 100755 --- a/.travis/get-anaconda.sh +++ b/.travis/get-anaconda.sh @@ -10,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 diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index 5784d994a..10bdd61c3 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -36,7 +36,7 @@ Installing the host side software For this, you need to add our binstar repository to your conda configuration:: - $ conda config --add channels http://conda.binstar.org/fallen/channel/dev + $ conda config --add channels http://conda.anaconda.org/fallen/channel/dev Then you can install the ARTIQ package, it will pull all the necessary dependencies:: From 0e92cfe053e478baddd2d58c7159f78191a0fca3 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 11 Jul 2015 22:26:37 +0200 Subject: [PATCH 176/227] artiq_run: remove support for stale watchdog API --- artiq/frontend/artiq_run.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/artiq/frontend/artiq_run.py b/artiq/frontend/artiq_run.py index 4aa66a91a..38fc7fe96 100755 --- a/artiq/frontend/artiq_run.py +++ b/artiq/frontend/artiq_run.py @@ -36,17 +36,6 @@ 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, expid): self.next_rid = 0 @@ -64,8 +53,6 @@ class DummyScheduler: def delete(self, rid): logger.info("Deleting RID %s", rid) - watchdog = DummyWatchdog - def get_argparser(with_file=True): parser = argparse.ArgumentParser( From 8b02b58a772bd11495698c0868b495db9029b543 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sun, 12 Jul 2015 14:50:46 +0200 Subject: [PATCH 177/227] sync_struct/Notifier: do not pass root param to publish --- artiq/protocols/sync_struct.py | 39 +++++++++++++++++----------------- artiq/test/scheduler.py | 6 +++--- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/artiq/protocols/sync_struct.py b/artiq/protocols/sync_struct.py index 95b390faa..00979ac91 100644 --- a/artiq/protocols/sync_struct.py +++ b/artiq/protocols/sync_struct.py @@ -13,6 +13,7 @@ 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 @@ -154,9 +155,9 @@ class Notifier: """ self._backing_struct.append(x) if self.root.publish is not None: - self.root.publish(self.root, {"action": "append", - "path": self._path, - "x": x}) + self.root.publish({"action": "append", + "path": self._path, + "x": x}) def insert(self, i, x): """Insert an element into a list. @@ -164,9 +165,9 @@ class Notifier: """ self._backing_struct.insert(i, x) if self.root.publish is not None: - self.root.publish(self.root, {"action": "insert", - "path": self._path, - "i": i, "x": x}) + 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 @@ -176,25 +177,25 @@ class Notifier: """ r = self._backing_struct.pop(i) if self.root.publish is not None: - self.root.publish(self.root, {"action": "pop", - "path": self._path, - "i": i}) + self.root.publish({"action": "pop", + "path": self._path, + "i": i}) return r 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", - "path": self._path, - "key": key, - "value": value}) + self.root.publish({"action": "setitem", + "path": self._path, + "key": key, + "value": value}) def __delitem__(self, key): self._backing_struct.__delitem__(key) if self.root.publish is not None: - self.root.publish(self.root, {"action": "delitem", - "path": self._path, - "key": key}) + self.root.publish({"action": "delitem", + "path": self._path, + "key": key}) def __getitem__(self, key): item = getitem(self._backing_struct, key) @@ -217,7 +218,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 +257,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]: diff --git a/artiq/test/scheduler.py b/artiq/test/scheduler.py index a70342486..53dd7c6f3 100644 --- a/artiq/test/scheduler.py +++ b/artiq/test/scheduler.py @@ -67,7 +67,7 @@ class SchedulerCase(unittest.TestCase): expect = _get_basic_steps(1, expid) done = asyncio.Event() expect_idx = 0 - def notify(notifier, mod): + def notify(mod): nonlocal expect_idx self.assertEqual(mod, expect[expect_idx]) expect_idx += 1 @@ -104,7 +104,7 @@ class SchedulerCase(unittest.TestCase): background_running = asyncio.Event() done = asyncio.Event() expect_idx = 0 - def notify(notifier, mod): + def notify(mod): nonlocal expect_idx if mod == {"path": [0], "value": "running", @@ -138,7 +138,7 @@ class SchedulerCase(unittest.TestCase): first_preparing = asyncio.Event() done = asyncio.Event() expect_idx = 0 - def notify(notifier, mod): + def notify(mod): nonlocal expect_idx if mod == {"path": [0], "value": "preparing", From 32d141f5ac8e76ae58062b9a2f06b8093b70728f Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Mon, 13 Jul 2015 22:08:20 +0200 Subject: [PATCH 178/227] refactor ddb/pdb/rdb --- artiq/coredevice/comm_dummy.py | 6 +- artiq/coredevice/comm_serial.py | 9 +- artiq/coredevice/comm_tcp.py | 9 +- artiq/coredevice/core.py | 12 +- artiq/coredevice/dds.py | 21 +- artiq/coredevice/ttl.py | 28 ++- artiq/devices/pdq2/mediator.py | 22 +-- artiq/devices/pxi6733/mediator.py | 25 +-- artiq/frontend/artiq_compile.py | 9 +- artiq/frontend/artiq_gui.py | 2 + artiq/frontend/artiq_master.py | 15 +- artiq/frontend/artiq_run.py | 43 ++--- artiq/language/__init__.py | 9 +- artiq/language/db.py | 133 ------------- artiq/language/environment.py | 182 ++++++++++++++++++ artiq/language/experiment.py | 54 ------ artiq/master/repository.py | 5 +- artiq/master/results.py | 103 ---------- artiq/master/worker_db.py | 164 +++++++++------- artiq/master/worker_impl.py | 32 ++- artiq/protocols/file_db.py | 2 +- artiq/sim/devices.py | 30 ++- artiq/test/coredevice.py | 153 +++++++-------- artiq/test/coredevice_vs_host.py | 62 +++--- artiq/test/hardware_testbench.py | 18 +- artiq/test/scheduler.py | 10 +- artiq/test/transforms.py | 4 +- artiq/test/worker.py | 12 +- artiq/tools.py | 2 +- doc/manual/conf.py | 2 +- doc/manual/core_language_reference.rst | 6 +- doc/manual/developing_a_ndsp.rst | 2 +- doc/manual/faq.rst | 19 +- doc/manual/getting_started.rst | 42 ++-- examples/master/repository/dds_test.py | 22 +-- .../repository/flopping_f_simulation.py | 28 ++- examples/master/repository/handover.py | 8 +- examples/master/repository/mandelbrot.py | 6 +- .../master/repository/photon_histogram.py | 38 ++-- examples/master/repository/transport.py | 29 ++- examples/sim/al_spectroscopy.py | 24 +-- examples/sim/simple_simulation.py | 27 ++- 42 files changed, 650 insertions(+), 779 deletions(-) delete mode 100644 artiq/language/db.py create mode 100644 artiq/language/environment.py delete mode 100644 artiq/language/experiment.py delete mode 100644 artiq/master/results.py diff --git a/artiq/coredevice/comm_dummy.py b/artiq/coredevice/comm_dummy.py index 807f83ca5..d2803ce7a 100644 --- a/artiq/coredevice/comm_dummy.py +++ b/artiq/coredevice/comm_dummy.py @@ -1,6 +1,5 @@ from operator import itemgetter -from artiq.language.db import AutoDB from artiq.language.units import ms from artiq.coredevice.runtime import LinkInterface @@ -13,7 +12,10 @@ class _RuntimeEnvironment(LinkInterface): return str(self.llvm_module) -class Comm(AutoDB): +class Comm: + def __init__(self, dmgr): + pass + def get_runtime_env(self): return _RuntimeEnvironment() diff --git a/artiq/coredevice/comm_serial.py b/artiq/coredevice/comm_serial.py index 25c926a25..70218da14 100644 --- a/artiq/coredevice/comm_serial.py +++ b/artiq/coredevice/comm_serial.py @@ -3,16 +3,15 @@ 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"): diff --git a/artiq/coredevice/comm_tcp.py b/artiq/coredevice/comm_tcp.py index 4cc254a10..eda672750 100644 --- a/artiq/coredevice/comm_tcp.py +++ b/artiq/coredevice/comm_tcp.py @@ -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"): diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index 61e7ed353..b41561161 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -1,7 +1,6 @@ import os from artiq.language.core import * -from artiq.language.db import * from artiq.language.units import ns from artiq.transforms.inline import inline @@ -46,13 +45,12 @@ 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 diff --git a/artiq/coredevice/dds.py b/artiq/coredevice/dds.py index 323209479..9ba4584d8 100644 --- a/artiq/coredevice/dds.py +++ b/artiq/coredevice/dds.py @@ -1,5 +1,4 @@ from artiq.language.core import * -from artiq.language.db import * from artiq.language.units import * @@ -23,14 +22,12 @@ 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 @@ -46,7 +43,7 @@ class DDSBus(AutoDB): syscall("dds_batch_exit") -class _DDSGeneric(AutoDB): +class _DDSGeneric: """Core device Direct Digital Synthesis (DDS) driver. Controls one DDS channel managed directly by the core device's runtime. @@ -57,12 +54,10 @@ class _DDSGeneric(AutoDB): :param sysclk: DDS system frequency. :param channel: channel number of the DDS device to control. """ - class DBKeys: - core = Device() - sysclk = Argument() - 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 diff --git a/artiq/coredevice/ttl.py b/artiq/coredevice/ttl.py index d65aa62ff..b9d680f8f 100644 --- a/artiq/coredevice/ttl.py +++ b/artiq/coredevice/ttl.py @@ -1,8 +1,7 @@ from artiq.language.core import * -from artiq.language.db import * -class TTLOut(AutoDB): +class TTLOut: """RTIO TTL output driver. This should be used with output-only channels. @@ -10,12 +9,10 @@ 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) @@ -58,7 +55,7 @@ class TTLOut(AutoDB): 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. @@ -76,11 +73,10 @@ 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) @@ -208,7 +204,7 @@ class TTLInOut(AutoDB): return syscall("ttl_get", self.channel, self.i_previous_timestamp) -class TTLClockGen(AutoDB): +class TTLClockGen: """RTIO TTL clock generator driver. This should be used with TTL channels that have a clock generator @@ -217,9 +213,9 @@ class TTLClockGen(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 diff --git a/artiq/devices/pdq2/mediator.py b/artiq/devices/pdq2/mediator.py index 39b6a89e6..27265bf9e 100644 --- a/artiq/devices/pdq2/mediator.py +++ b/artiq/devices/pdq2/mediator.py @@ -1,5 +1,4 @@ from artiq.language.core import * -from artiq.language.db import * from artiq.language.units import * @@ -154,19 +153,14 @@ class _Frame: self.pdq.next_segment = -1 -class CompoundPDQ2(AutoDB): - class DBKeys: - core = Device() - pdq2_devices = Argument() - trigger_device = Argument() - frame_devices = Argument() - - def build(self): - self.pdq2s = [self.dbh.get_device(d) for d in self.pdq2_devices] - self.trigger = self.dbh.get_device(self.trigger_device) - self.frame0 = self.dbh.get_device(self.frame_devices[0]) - self.frame1 = self.dbh.get_device(self.frame_devices[1]) - self.frame2 = self.dbh.get_device(self.frame_devices[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 diff --git a/artiq/devices/pxi6733/mediator.py b/artiq/devices/pxi6733/mediator.py index 675a08847..047653b61 100644 --- a/artiq/devices/pxi6733/mediator.py +++ b/artiq/devices/pxi6733/mediator.py @@ -1,7 +1,6 @@ import numpy as np from artiq.language.core import * -from artiq.language.db import * from artiq.language.units import * from artiq.wavesynth.compute_samples import Synthesizer @@ -139,20 +138,16 @@ class _Frame: self.daqmx.next_segment = -1 -class CompoundDAQmx(AutoDB): - class DBKeys: - core = Device() - daqmx_device = Argument() - clock_device = Argument() - channel_count = Argument() - sample_rate = Argument() - sample_rate_in_mu = Argument(False) - - def build(self): - self.daqmx = self.dbh.get_device(self.daqmx_device) - self.clock = self.dbh.get_device(self.clock_device) - - if not self.sample_rate_in_mu: +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 diff --git a/artiq/frontend/artiq_compile.py b/artiq/frontend/artiq_compile.py index e4e369211..c47360c51 100755 --- a/artiq/frontend/artiq_compile.py +++ b/artiq/frontend/artiq_compile.py @@ -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") diff --git a/artiq/frontend/artiq_gui.py b/artiq/frontend/artiq_gui.py index a6278a28c..839121458 100755 --- a/artiq/frontend/artiq_gui.py +++ b/artiq/frontend/artiq_gui.py @@ -18,9 +18,11 @@ from artiq.gui.parameters import ParametersDock 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(): parser = argparse.ArgumentParser(description="ARTIQ GUI client") parser.add_argument( diff --git a/artiq/frontend/artiq_master.py b/artiq/frontend/artiq_master.py index 1f95d26cc..37e3a6fde 100755 --- a/artiq/frontend/artiq_master.py +++ b/artiq/frontend/artiq_master.py @@ -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.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 @@ -36,7 +36,7 @@ def main(): init_logger(args) ddb = FlatFileDB("ddb.pyon") pdb = FlatFileDB("pdb.pyon") - rtr = RTResults() + rtr = Notifier(dict()) repository = Repository() if os.name == "nt": @@ -47,11 +47,10 @@ def main(): atexit.register(lambda: loop.close()) 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), } scheduler = Scheduler(get_last_rid() + 1, worker_handlers) worker_handlers["scheduler_submit"] = scheduler.submit @@ -72,7 +71,7 @@ def main(): "schedule": scheduler.notifier, "devices": ddb.data, "parameters": pdb.data, - "rt_results": rtr.groups, + "rt_results": rtr, "explist": repository.explist }) loop.run_until_complete(server_notify.start( diff --git a/artiq/frontend/artiq_run.py b/artiq/frontend/artiq_run.py index 38fc7fe96..baace1d0e 100755 --- a/artiq/frontend/artiq_run.py +++ b/artiq/frontend/artiq_run.py @@ -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: @@ -37,12 +36,11 @@ class SimpleParamLogger: class DummyScheduler: - def __init__(self, expid): + def __init__(self): self.next_rid = 0 - self.next_trid = 0 self.pipeline_name = "main" self.priority = 0 - self.expid = expid + self.expid = None def submit(self, pipeline_name, expid, priority, due_date, flush): rid = self.next_rid @@ -78,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: @@ -86,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 @@ -100,34 +98,33 @@ def _build_experiment(dbh, args): "experiment": args.experiment, "arguments": arguments } - return exp(dbh, - scheduler=DummyScheduler(expid), - **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)) diff --git a/artiq/language/__init__.py b/artiq/language/__init__.py index ad51894b5..7dfe5dba0 100644 --- a/artiq/language/__init__.py +++ b/artiq/language/__init__.py @@ -1,11 +1,10 @@ -from artiq.language import core, experiment, db, units +from artiq.language import core, environment, units from artiq.language.core import * -from artiq.language.experiment import * -from artiq.language.db import * +from artiq.language.environment import * from artiq.language.units import * + __all__ = [] __all__.extend(core.__all__) -__all__.extend(experiment.__all__) -__all__.extend(db.__all__) +__all__.extend(environment.__all__) __all__.extend(units.__all__) diff --git a/artiq/language/db.py b/artiq/language/db.py deleted file mode 100644 index d5f055603..000000000 --- a/artiq/language/db.py +++ /dev/null @@ -1,133 +0,0 @@ -""" -Connection to device, parameter and result database. -""" - -__all__ = ["Device", "NoDefault", "Parameter", "Argument", "Result", "AutoDB"] - - -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 diff --git a/artiq/language/environment.py b/artiq/language/environment.py new file mode 100644 index 000000000..4065759b2 --- /dev/null +++ b/artiq/language/environment.py @@ -0,0 +1,182 @@ +from inspect import isclass + + +__all__ = ["NoDefault", "FreeValue", "HasEnvironment", + "Experiment", "EnvExperiment", "is_experiment"] + + +class NoDefault: + """Represents the absence of a default value.""" + pass + + +class FreeValue: + def __init__(self, default=NoDefault): + if default is not NoDefault: + self.default_value = default + + def default(self): + return self.default_value + + def process(self, x): + return x + + def describe(self): + d = {"ty": "FreeValue"} + if hasattr(self, "default_value"): + d["default"] = self.default_value + return d + + +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(), **kwargs): + self.requested_args = dict() + + self.__dmgr = dmgr + self.__pdb = pdb + self.__rdb = rdb + self.__param_override = param_override + + 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): + raise NotImplementedError + + def dbs(self): + return self.__dmgr, self.__pdb, self.__rdb + + def get_argument(self, key, processor=None): + 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: + return processor.default() + return processor.process(argval) + + def attr_argument(self, key, processor=None): + setattr(self, key, self.get_argument(key, processor)) + + def get_device(self, key): + if self.__dmgr is None: + raise ValueError("Device manager not present") + return self.__dmgr.get(key) + + def attr_device(self, key): + setattr(self, key, self.get_device(key)) + + def get_parameter(self, key, default=NoDefault): + 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): + setattr(self, key, self.get_parameter(key, default)) + + def set_parameter(self, key, value): + if self.__pdb is None: + raise ValueError("Parameter database not present") + self.__pdb.set(key, value) + + def set_result(self, key, value, realtime=False): + 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): + setattr(self, key, set_result(key, init_value, True)) + + def get_result(self, key): + 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) diff --git a/artiq/language/experiment.py b/artiq/language/experiment.py deleted file mode 100644 index b3394918c..000000000 --- a/artiq/language/experiment.py +++ /dev/null @@ -1,54 +0,0 @@ -from inspect import isclass - -__all__ = ["Experiment", "is_experiment"] - - -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 - - -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 diff --git a/artiq/master/repository.py b/artiq/master/repository.py index 90ef186e9..3ddea5110 100644 --- a/artiq/master/repository.py +++ b/artiq/master/repository.py @@ -2,7 +2,7 @@ import os from artiq.protocols.sync_struct import Notifier from artiq.tools import file_import -from artiq.language.experiment import is_experiment +from artiq.language.environment import is_experiment def scan_experiments(): @@ -23,8 +23,7 @@ def scan_experiments(): name = name[:-1] entry = { "file": os.path.join("repository", f), - "experiment": k, - "gui_file": getattr(v, "__artiq_gui_file__", None) + "experiment": k } r[name] = entry return r diff --git a/artiq/master/results.py b/artiq/master/results.py deleted file mode 100644 index 8dc8eaff6..000000000 --- a/artiq/master/results.py +++ /dev/null @@ -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) diff --git a/artiq/master/worker_db.py b/artiq/master/worker_db.py index 2ba8b84f2..bd6d008b3 100644 --- a/artiq/master/worker_db.py +++ b/artiq/master/worker_db.py @@ -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): - try: - return self.realtime_data[name] - except KeyError: - 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 +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 - 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 diff --git a/artiq/master/worker_impl.py b/artiq/master/worker_impl.py index c5307d51a..eb0fec592 100644 --- a/artiq/master/worker_impl.py +++ b/artiq/master/worker_impl.py @@ -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 @@ -46,15 +45,14 @@ def make_parent_action(action, argnames, exception=ParentActionError): 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") @@ -82,7 +80,7 @@ class Scheduler: "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 @@ -110,8 +108,10 @@ 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: @@ -120,16 +120,12 @@ def main(): 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), - **expid["arguments"]) - rdb.build() + dmgr.virtual_devices["scheduler"].set_run_info( + obj["pipeline_name"], expid, obj["priority"]) + exp_inst = exp(dmgr, ParentPDB, rdb, + **expid["arguments"]) put_object({"action": "completed"}) elif action == "prepare": exp_inst.prepare() @@ -150,7 +146,7 @@ def main(): elif action == "terminate": break finally: - dbh.close_devices() + dmgr.close_devices() if __name__ == "__main__": main() diff --git a/artiq/protocols/file_db.py b/artiq/protocols/file_db.py index 6675dfcff..b7499587e 100644 --- a/artiq/protocols/file_db.py +++ b/artiq/protocols/file_db.py @@ -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): diff --git a/artiq/sim/devices.py b/artiq/sim/devices.py index 25fc934f5..3f11c1c1e 100644 --- a/artiq/sim/devices.py +++ b/artiq/sim/devices.py @@ -1,13 +1,12 @@ 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): - def build(self): +class Core: + def __init__(self, dmgr): self.ref_period = 1 self._level = 0 @@ -20,12 +19,11 @@ class Core(AutoDB): 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 @@ -42,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): @@ -53,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): diff --git a/artiq/test/coredevice.py b/artiq/test/coredevice.py index c780d19c2..863d4b383 100644 --- a/artiq/test/coredevice.py +++ b/artiq/test/coredevice.py @@ -6,11 +6,10 @@ from artiq.coredevice.runtime_exceptions import RTIOUnderflow from artiq.coredevice import runtime_exceptions -class RTT(Experiment, AutoDB): - class DBKeys: - core = Device() - ttl_inout = Device() - rtt = Result() +class RTT(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("ttl_inout") @kernel def run(self): @@ -24,15 +23,15 @@ class RTT(Experiment, AutoDB): delay(1*us) t0 = now_mu() self.ttl_inout.pulse(1*us) - self.rtt = mu_to_seconds(self.ttl_inout.timestamp() - t0) + self.set_result("rtt", + mu_to_seconds(self.ttl_inout.timestamp() - t0)) -class Loopback(Experiment, AutoDB): - class DBKeys: - core = Device() - loop_in = Device() - loop_out = Device() - rtt = Result() +class Loopback(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("loop_in") + self.attr_device("loop_out") @kernel def run(self): @@ -44,15 +43,15 @@ class Loopback(Experiment, AutoDB): delay(1*us) t0 = now_mu() self.loop_out.pulse(1*us) - self.rtt = mu_to_seconds(self.loop_in.timestamp() - t0) + self.set_result("rtt", + mu_to_seconds(self.loop_in.timestamp() - t0)) -class ClockGeneratorLoopback(Experiment, AutoDB): - class DBKeys: - core = Device() - loop_clock_in = Device() - loop_clock_out = Device() - count = Result() +class ClockGeneratorLoopback(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("loop_clock_in") + self.attr_device("loop_clock_out") @kernel def run(self): @@ -64,14 +63,14 @@ class ClockGeneratorLoopback(Experiment, AutoDB): with sequential: delay(200*ns) self.loop_clock_out.set(1*MHz) - self.count = self.loop_clock_in.count() + self.set_result("count", + self.loop_clock_in.count()) -class PulseRate(Experiment, AutoDB): - class DBKeys: - core = Device() - loop_out = Device() - pulse_rate = Result() +class PulseRate(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("loop_out") @kernel def run(self): @@ -85,13 +84,14 @@ class PulseRate(Experiment, AutoDB): dt += 1 self.core.break_realtime() else: - self.pulse_rate = mu_to_seconds(2*dt) + self.set_result("pulse_rate", + mu_to_seconds(2*dt)) break -class Watchdog(Experiment, AutoDB): - class DBKeys: - core = Device() +class Watchdog(EnvExperiment): + def build(self): + self.attr_device("core") @kernel def run(self): @@ -100,14 +100,11 @@ class Watchdog(Experiment, AutoDB): pass -class LoopbackCount(Experiment, AutoDB): - class DBKeys: - core = Device() - ttl_inout = Device() - npulses = Argument() - - def report(self, n): - self.result = n +class LoopbackCount(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("ttl_inout") + self.attr_argument("npulses") @kernel def run(self): @@ -119,13 +116,14 @@ class LoopbackCount(Experiment, AutoDB): for i in range(self.npulses): delay(25*ns) self.ttl_inout.pulse(25*ns) - self.report(self.ttl_inout.count()) + self.set_result("count", + self.ttl_inout.count()) -class Underflow(Experiment, AutoDB): - class DBKeys: - core = Device() - ttl_out = Device() +class Underflow(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("ttl_out") @kernel def run(self): @@ -134,10 +132,10 @@ class Underflow(Experiment, AutoDB): self.ttl_out.pulse(25*ns) -class SequenceError(Experiment, AutoDB): - class DBKeys: - core = Device() - ttl_out = Device() +class SequenceError(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("ttl_out") @kernel def run(self): @@ -147,21 +145,18 @@ class SequenceError(Experiment, AutoDB): self.ttl_out.pulse(25*us) -class TimeKeepsRunning(Experiment, AutoDB): - class DBKeys: - core = Device() - time_at_start = Result() +class TimeKeepsRunning(EnvExperiment): + def build(self): + self.attr_device("core") @kernel def run(self): - self.time_at_start = now_mu() + self.set_result("time_at_start", now_mu()) -class Handover(Experiment, AutoDB): - class DBKeys: - core = Device() - t1 = Result() - t2 = Result() +class Handover(EnvExperiment): + def build(self): + self.attr_device("core") @kernel def get_now(self): @@ -169,34 +164,34 @@ class Handover(Experiment, AutoDB): def run(self): self.get_now() - self.t1 = self.time_at_start + self.set_result("t1", self.time_at_start) self.get_now() - self.t2 = self.time_at_start + self.set_result("t2", self.time_at_start) class CoredeviceTest(ExperimentCase): def test_rtt(self): self.execute(RTT) - rtt = self.dbh.get_result("rtt").read + 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.dbh.get_result("rtt").read + 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.dbh.get_result("count").read + count = self.rdb.get("count") self.assertEqual(count, 10) def test_pulse_rate(self): self.execute(PulseRate) - rate = self.dbh.get_result("pulse_rate").read + rate = self.rdb.get("pulse_rate") print(rate) self.assertGreater(rate, 100*ns) self.assertLess(rate, 2500*ns) @@ -204,7 +199,8 @@ class CoredeviceTest(ExperimentCase): def test_loopback_count(self): npulses = 2 r = self.execute(LoopbackCount, npulses=npulses) - self.assertEqual(r.result, npulses) + count = self.rdb.get("count") + self.assertEqual(count, npulses) def test_underflow(self): with self.assertRaises(runtime_exceptions.RTIOUnderflow): @@ -221,26 +217,23 @@ class CoredeviceTest(ExperimentCase): def test_time_keeps_running(self): self.execute(TimeKeepsRunning) - t1 = self.dbh.get_result("time_at_start").read + t1 = self.rdb.get("time_at_start") self.execute(TimeKeepsRunning) - t2 = self.dbh.get_result("time_at_start").read - dead_time = mu_to_seconds(t2 - t1, self.dbh.get_device("core")) + 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.dbh.get_result("t1").read, - self.dbh.get_result("t2").read) + self.assertEqual(self.rdb.get("t1"), self.rdb.get("t2")) -class RPCTiming(Experiment, AutoDB): - class DBKeys: - core = Device() - repeats = Argument(100) - rpc_time_mean = Result() - rpc_time_stddev = Result() +class RPCTiming(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_argument("repeats", FreeValue(100)) def nop(self, x): pass @@ -257,14 +250,14 @@ class RPCTiming(Experiment, AutoDB): def run(self): self.bench() mean = sum(self.ts)/self.repeats - self.rpc_time_stddev = sqrt( - sum([(t - mean)**2 for t in self.ts])/self.repeats)*s - self.rpc_time_mean = mean*s + 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.dbh.get_result("rpc_time_mean").read, 100*ns) - self.assertLess(self.dbh.get_result("rpc_time_mean").read, 15*ms) - self.assertLess(self.dbh.get_result("rpc_time_stddev").read, 1*ms) + 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) diff --git a/artiq/test/coredevice_vs_host.py b/artiq/test/coredevice_vs_host.py index 623aff4e8..31ef27da4 100644 --- a/artiq/test/coredevice_vs_host.py +++ b/artiq/test/coredevice_vs_host.py @@ -6,18 +6,19 @@ from artiq.sim import devices as sim_devices from artiq.test.hardware_testbench import ExperimentCase -def _run_on_host(k_class, **parameters): - coredev = sim_devices.Core() - k_inst = k_class(core=coredev, **parameters) +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(Experiment, AutoDB): - class DBKeys: - core = Device() - output_list = Argument() - maximum = Argument() +class _Primes(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_argument("output_list") + self.attr_argument("maximum") @kernel def run(self): @@ -33,11 +34,10 @@ class _Primes(Experiment, AutoDB): self.output_list.append(x) -class _Misc(Experiment, AutoDB): - class DBKeys: - core = Device() - +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] @@ -52,11 +52,11 @@ class _Misc(Experiment, AutoDB): self.list_copy_out = self.list_copy_in -class _PulseLogger(Experiment, AutoDB): - class DBKeys: - core = Device() - output_list = Argument() - name = Argument() +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"): @@ -79,14 +79,13 @@ class _PulseLogger(Experiment, AutoDB): self.off(now_mu()) -class _Pulses(Experiment, AutoDB): - class DBKeys: - core = Device() - output_list = Argument() - +class _Pulses(EnvExperiment): def build(self): + self.attr_device("core") + self.attr_argument("output_list") + for name in "a", "b", "c", "d": - pl = _PulseLogger(core=self.core, + pl = _PulseLogger(*self.dbs(), output_list=self.output_list, name=name) setattr(self, name, pl) @@ -107,10 +106,10 @@ class _MyException(Exception): pass -class _Exceptions(Experiment, AutoDB): - class DBKeys: - core = Device() - trace = Argument() +class _Exceptions(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_argument("trace") @kernel def run(self): @@ -151,12 +150,11 @@ class _Exceptions(Experiment, AutoDB): self.trace.append(104) -class _RPCExceptions(Experiment, AutoDB): - class DBKeys: - core = Device() - catch = Argument(False) - +class _RPCExceptions(EnvExperiment): def build(self): + self.attr_device("core") + self.attr_argument("catch", FreeValue(False)) + self.success = False def exception_raiser(self): diff --git a/artiq/test/hardware_testbench.py b/artiq/test/hardware_testbench.py index 386e255cb..a082a676c 100644 --- a/artiq/test/hardware_testbench.py +++ b/artiq/test/hardware_testbench.py @@ -5,9 +5,8 @@ import logging from artiq.language import * from artiq.protocols.file_db import FlatFileDB -from artiq.master.worker_db import DBHub, ResultDB -from artiq.frontend.artiq_run import ( - DummyScheduler, DummyWatchdog, SimpleParamLogger) +from artiq.master.worker_db import DeviceManager, ResultDB +from artiq.frontend.artiq_run import DummyScheduler artiq_root = os.getenv("ARTIQ_ROOT") @@ -33,9 +32,10 @@ def get_from_ddb(*path, default="skip"): 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(lambda description: None, lambda mod: None) - self.dbh = DBHub(self.ddb, self.pdb, self.rdb) + self.rdb = ResultDB() def execute(self, cls, **kwargs): expid = { @@ -43,16 +43,16 @@ class ExperimentCase(unittest.TestCase): "experiment": cls.__name__, "arguments": kwargs } - sched = DummyScheduler(expid) + self.dmgr.virtual_devices["scheduler"].expid = expid try: try: - exp = cls(self.dbh, scheduler=sched, **kwargs) + 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) - self.rdb.build() + exp.prepare() exp.run() exp.analyze() return exp finally: - self.dbh.close_devices() + self.dmgr.close_devices() diff --git a/artiq/test/scheduler.py b/artiq/test/scheduler.py index 53dd7c6f3..7493e53d4 100644 --- a/artiq/test/scheduler.py +++ b/artiq/test/scheduler.py @@ -7,12 +7,18 @@ from artiq import * from artiq.master.scheduler import Scheduler -class EmptyExperiment(Experiment, AutoDB): +class EmptyExperiment(EnvExperiment): + def build(self): + pass + def run(self): pass -class BackgroundExperiment(Experiment, AutoDB): +class BackgroundExperiment(EnvExperiment): + def build(self): + self.attr_device("scheduler") + def run(self): while True: self.scheduler.pause() diff --git a/artiq/test/transforms.py b/artiq/test/transforms.py index 75af1b2dc..dffee41a2 100644 --- a/artiq/test/transforms.py +++ b/artiq/test/transforms.py @@ -36,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) diff --git a/artiq/test/worker.py b/artiq/test/worker.py index ccd5339aa..8682ea041 100644 --- a/artiq/test/worker.py +++ b/artiq/test/worker.py @@ -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) diff --git a/artiq/tools.py b/artiq/tools.py index 1d1f9c553..2f7870eab 100644 --- a/artiq/tools.py +++ b/artiq/tools.py @@ -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 diff --git a/doc/manual/conf.py b/doc/manual/conf.py index d550ee3ff..2df182943 100644 --- a/doc/manual/conf.py +++ b/doc/manual/conf.py @@ -118,7 +118,7 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'classic' +html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/doc/manual/core_language_reference.rst b/doc/manual/core_language_reference.rst index 35c285e01..9f0a21d06 100644 --- a/doc/manual/core_language_reference.rst +++ b/doc/manual/core_language_reference.rst @@ -9,10 +9,10 @@ The most commonly used features from those modules can be imported with ``from a .. automodule:: artiq.language.core :members: -:mod:`artiq.language.db` module -------------------------------- +:mod:`artiq.language.environment` module +---------------------------------------- -.. automodule:: artiq.language.db +.. automodule:: artiq.language.environment :members: :mod:`artiq.language.units` module diff --git a/doc/manual/developing_a_ndsp.rst b/doc/manual/developing_a_ndsp.rst index 0bc867892..d6320c242 100644 --- a/doc/manual/developing_a_ndsp.rst +++ b/doc/manual/developing_a_ndsp.rst @@ -103,7 +103,7 @@ Run it as before, while the controller is running. You should see the message ap $ ./hello_controller.py message: Hello World! -When using the driver in an experiment, for simple cases the ``Client`` instance can be returned by the :class:`artiq.language.db.AutoDB` mechanism and used normally as a device. +When using the driver in an experiment, the ``Client`` instance can be returned by the environment mechanism (via the ``get_device`` and ``attr_device`` methods of :class:`artiq.language.environment.HasEnvironment`) and used normally as a device. :warning: RPC servers operate on copies of objects provided by the client, and modifications to mutable types are not written back. For example, if the client passes a list as a parameter of an RPC method, and that method ``append()s`` an element to the list, the element is not appended to the client's list. diff --git a/doc/manual/faq.rst b/doc/manual/faq.rst index 2e95d2864..c30912291 100644 --- a/doc/manual/faq.rst +++ b/doc/manual/faq.rst @@ -7,30 +7,19 @@ How do I ... prevent my first RTIO command from causing an underflow? -------------------------------------------------------- -The RTIO timestamp counter starts counting at zero at the beginning of the first kernel run on the core device. The first RTIO event is programmed with a small timestamp above zero. If the kernel needs more time than this timestamp to produce the event, an underflow will occur. You can prevent it by calling ``break_realtime`` just before programming the first event. - -override the `sysclk` frequency of just one DDS? ------------------------------------------------- - -Override the parameter using an argument in the DDB. +The first RTIO event is programmed with a small timestamp above the value of the timecounter at the start of the experiment. If the kernel needs more time than this timestamp to produce the event, an underflow will occur. You can prevent it by calling ``break_realtime`` just before programming the first event, or by adding a sufficient delay. organize parameters in folders? ------------------------------- -Use GUI auto-completion and filtering. -Names need to be unique. +Folders are not supported yet, use GUI filtering for now. Names need to be unique. enforce functional dependencies between parameters? --------------------------------------------------- If you want to override a parameter ``b`` in the PDB to be ``b = 2*a``, use wrapper experiments, overriding parameters by passing them to the -experiment's constructor. - -get rid of ``DBKeys``? ----------------------- - -``DBKeys`` references keys in PDB, DDB and RDB. +experiment's constructor (``param_override`` argument). write a generator feeding a kernel feeding an analyze function? --------------------------------------------------------------- @@ -94,4 +83,4 @@ The preferred way to specify a serial device is to make use of the ``hwgrep://`` URL: it allows to select the serial device by its USB vendor ID, product ID and/or serial number. Those never change, unlike the device file name. -See the :ref:`TDC001 documentation ` for an example of ``hwgrep://`` usage. \ No newline at end of file +See the :ref:`TDC001 documentation ` for an example of ``hwgrep://`` usage. diff --git a/doc/manual/getting_started.rst b/doc/manual/getting_started.rst index da2952fd3..5a5fd1329 100644 --- a/doc/manual/getting_started.rst +++ b/doc/manual/getting_started.rst @@ -11,23 +11,23 @@ As a very first step, we will turn on a LED on the core device. Create a file `` from artiq import * - class LED(Experiment, AutoDB): - class DBKeys: - core = Device() - led = Device() + class LED(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("led") @kernel def run(self): self.led.on() -The central part of our code is our ``LED`` class, that derives from :class:`artiq.language.db.AutoDB`. ``AutoDB`` is part of the mechanism that attaches device drivers and retrieves parameters according to a database. Our ``DBKeys`` class lists the devices (and parameters) that ``LED`` needs in order to operate, and the names of the attributes (e.g. ``led``) are used to search the database. ``AutoDB`` replaces them with the actual device drivers (and parameter values). Finally, the ``@kernel`` decorator tells the system that the ``run`` method must be executed on the core device (instead of the host). +The central part of our code is our ``LED`` class, that derives from :class:`artiq.language.environment.EnvExperiment`. Among other features, ``EnvExperiment`` calls our ``build`` method and provides the ``attr_device`` method that interfaces to the device database to create the appropriate device drivers and make those drivers accessible as ``self.core`` and ``self.led``. The ``@kernel`` decorator tells the system that the ``run`` method must be executed on the core device (instead of the host). The decorator uses ``self.core`` internally, which is why we request the core device using ``attr_device`` like any other. Copy the files ``ddb.pyon`` and ``pdb.pyon`` (containing the device and parameter databases) from the ``examples`` folder of ARTIQ into the same directory as ``led.py`` (alternatively, you can use the ``-d`` and ``-p`` options of ``artiq_run.py``). You can open the database files using a text editor - their contents are in a human-readable format. -Run your code using ``artiq_run.py``, which is part of the ARTIQ front-end tools: :: +Run your code using ``artiq_run``, which is part of the ARTIQ front-end tools: :: - $ artiq_run.py led.py + $ artiq_run led.py The LED of the device should turn on. Congratulations! You have a basic ARTIQ system up and running. @@ -41,10 +41,10 @@ Modify the code as follows: :: def input_led_state(): return int(input("Enter desired LED state: ")) - class LED(Experiment, AutoDB): - class DBKeys: - core = Device() - led = Device() + class LED(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("led") @kernel def run(self): @@ -58,9 +58,9 @@ Modify the code as follows: :: You can then turn the LED off and on by entering 0 or 1 at the prompt that appears: :: - $ artiq_run.py led.py + $ artiq_run led.py Enter desired LED state: 1 - $ artiq_run.py led.py + $ artiq_run led.py Enter desired LED state: 0 What happens is the ARTIQ compiler notices that the ``input_led_state`` function does not have a ``@kernel`` decorator and thus must be executed on the host. When the core device calls it, it sends a request to the host to execute it. The host displays the prompt, collects user input, and sends the result back to the core device, which sets the LED state accordingly. @@ -90,10 +90,10 @@ Create a new file ``rtio.py`` containing the following: :: from artiq import * - class Tutorial(Experiment, AutoDB): - class DBKeys: - core = Device() - ttl0 = Device() + class Tutorial(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("ttl0") @kernel def run(self): @@ -113,10 +113,10 @@ Try reducing the period of the generated waveform until the CPU cannot keep up w def print_underflow(): print("RTIO underflow occured") - class Tutorial(Experiment, AutoDB): - class DBKeys: - core = Device() - ttl0 = Device() + class Tutorial(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("ttl0") @kernel def run(self): diff --git a/examples/master/repository/dds_test.py b/examples/master/repository/dds_test.py index a36af8447..ff4458abc 100644 --- a/examples/master/repository/dds_test.py +++ b/examples/master/repository/dds_test.py @@ -1,19 +1,19 @@ from artiq import * -class DDSTest(Experiment, AutoDB): +class DDSTest(EnvExperiment): """DDS test""" - class DBKeys: - core = Device() - dds_bus = Device() - dds0 = Device() - dds1 = Device() - dds2 = Device() - ttl0 = Device() - ttl1 = Device() - ttl2 = Device() - led = Device() + def build(self): + self.attr_device("core") + self.attr_device("dds_bus") + self.attr_device("dds0") + self.attr_device("dds1") + self.attr_device("dds2") + self.attr_device("ttl0") + self.attr_device("ttl1") + self.attr_device("ttl2") + self.attr_device("led") @kernel def run(self): diff --git a/examples/master/repository/flopping_f_simulation.py b/examples/master/repository/flopping_f_simulation.py index 23cf42239..19a5127d4 100644 --- a/examples/master/repository/flopping_f_simulation.py +++ b/examples/master/repository/flopping_f_simulation.py @@ -23,25 +23,21 @@ def model_numpy(xdata, F0): return r -class FloppingF(Experiment, AutoDB): +class FloppingF(EnvExperiment): """Flopping F simulation""" - class DBKeys: - npoints = Argument(100) - min_freq = Argument(1000) - max_freq = Argument(2000) + def build(self): + self.attr_argument("npoints", FreeValue(100)) + self.attr_argument("min_freq", FreeValue(1000)) + self.attr_argument("max_freq", FreeValue(2000)) - F0 = Argument(1500) - noise_amplitude = Argument(0.1) + self.attr_argument("F0", FreeValue(1500)) + self.attr_argument("noise_amplitude", FreeValue(0.1)) - frequency = Result() - brightness = Result() + self.frequency = self.set_result("flopping_f_frequency", [], True) + self.brightness = self.set_result("flopping_f_brightness", [], True) - flopping_freq = Parameter() - - realtime_results = { - ("frequency", "brightness"): "xy" - } + self.attr_device("scheduler") def run(self): for i in range(self.npoints): @@ -56,7 +52,7 @@ class FloppingF(Experiment, AutoDB): def analyze(self): popt, pcov = curve_fit(model_numpy, self.frequency.read, self.brightness.read, - p0=[self.flopping_freq]) + p0=[self.get_parameter("flopping_freq")]) perr = np.sqrt(np.diag(pcov)) if perr < 0.1: - self.flopping_freq = float(popt) + self.set_parameter("flopping_freq", float(popt)) diff --git a/examples/master/repository/handover.py b/examples/master/repository/handover.py index 8974678a9..5870d27ba 100644 --- a/examples/master/repository/handover.py +++ b/examples/master/repository/handover.py @@ -1,10 +1,10 @@ from artiq import * -class Handover(Experiment, AutoDB): - class DBKeys: - core = Device() - led = Device() +class Handover(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("led") @kernel def blink_once(self): diff --git a/examples/master/repository/mandelbrot.py b/examples/master/repository/mandelbrot.py index 0a0c030a6..a14e58900 100644 --- a/examples/master/repository/mandelbrot.py +++ b/examples/master/repository/mandelbrot.py @@ -3,11 +3,11 @@ import sys from artiq import * -class Mandelbrot(Experiment, AutoDB): +class Mandelbrot(EnvExperiment): """Mandelbrot set demo""" - class DBKeys: - core = Device() + def build(self): + self.attr_device("core") def col(self, i): sys.stdout.write(" .,-:;i+hHM$*#@ "[i]) diff --git a/examples/master/repository/photon_histogram.py b/examples/master/repository/photon_histogram.py index aebe1beb4..bad44e1b7 100644 --- a/examples/master/repository/photon_histogram.py +++ b/examples/master/repository/photon_histogram.py @@ -1,29 +1,24 @@ from artiq import * -class PhotonHistogram(Experiment, AutoDB): +class PhotonHistogram(EnvExperiment): """Photon histogram""" - class DBKeys: - core = Device() - dds_bus = Device() - bd_dds = Device() - bd_sw = Device() - bdd_dds = Device() - bdd_sw = Device() - pmt = Device() + def build(self): + self.attr_device("core") + self.attr_device("dds_bus") + self.attr_device("bd_dds") + self.attr_device("bd_sw") + self.attr_device("bdd_dds") + self.attr_device("bdd_sw") + self.attr_device("pmt") - nbins = Argument(100) - repeats = Argument(100) + self.attr_argument("nbins", FreeValue(100)) + self.attr_argument("repeats", FreeValue(100)) - cool_f = Parameter(230*MHz) - detect_f = Parameter(220*MHz) - detect_t = Parameter(100*us) - - ion_present = Parameter(True) - - hist = Result() - total = Result() + self.attr_parameter("cool_f", 230*MHz) + self.attr_parameter("detect_f", 220*MHz) + self.attr_parameter("detect_t", 100*us) @kernel def program_cooling(self): @@ -65,9 +60,8 @@ class PhotonHistogram(Experiment, AutoDB): hist[n] += 1 total += n - self.hist = hist - self.total = total - self.ion_present = total > 5*self.repeats + self.set_result("cooling_photon_histogram", hist) + self.set_parameter("ion_present", total > 5*self.repeats) if __name__ == "__main__": diff --git a/examples/master/repository/transport.py b/examples/master/repository/transport.py index ce1b2ece4..ed44e0a26 100644 --- a/examples/master/repository/transport.py +++ b/examples/master/repository/transport.py @@ -10,23 +10,22 @@ transport_data = dict( # 4 devices, 3 board each, 3 dacs each ) -class Transport(Experiment, AutoDB): +class Transport(EnvExperiment): """Transport""" - class DBKeys: - core = Device() - bd = Device() - bdd = Device() - pmt = Device() - electrodes = Device() + def build(self): + self.attr_device("core") + self.attr_device("bd") + self.attr_device("bdd") + self.attr_device("pmt") + self.attr_device("electrodes") - wait_at_stop = Parameter(100*us) - speed = Parameter(1.5) + self.attr_argument("wait_at_stop", FreeValue(100*us)) + self.attr_argument("speed", FreeValue(1.5)) + self.attr_argument("repeats", FreeValue(100)) + self.attr_argument("nbins", FreeValue(100)) - repeats = Argument(100) - nbins = Argument(100) - - def prepare(self, stop): + def calc_waveforms(self, stop): t = transport_data["t"][:stop]*self.speed u = transport_data["u"][:stop] @@ -89,9 +88,9 @@ class Transport(Experiment, AutoDB): def scan(self, stops): for s in stops: self.histogram = [] - # non-kernel, calculate waveforms, build frames + # non-kernel, build frames # could also be rpc'ed from repeat() - self.prepare(s) + self.calc_waveforms(s) # kernel part self.repeat() # live update 2d plot with current self.histogram diff --git a/examples/sim/al_spectroscopy.py b/examples/sim/al_spectroscopy.py index f41f09886..7f52fc32c 100644 --- a/examples/sim/al_spectroscopy.py +++ b/examples/sim/al_spectroscopy.py @@ -1,20 +1,20 @@ from artiq import * -class AluminumSpectroscopy(Experiment, AutoDB): +class AluminumSpectroscopy(EnvExperiment): """Aluminum spectroscopy (simulation)""" - class DBKeys: - core = Device() - mains_sync = Device() - laser_cooling = Device() - spectroscopy = Device() - spectroscopy_b = Device() - state_detection = Device() - pmt = Device() - spectroscopy_freq = Parameter(432*MHz) - photon_limit_low = Argument(10) - photon_limit_high = Argument(15) + def build(self): + self.attr_device("core") + self.attr_device("mains_sync") + self.attr_device("laser_cooling") + self.attr_device("spectroscopy") + self.attr_device("spectroscopy_b") + self.attr_device("state_detection") + self.attr_device("pmt") + self.attr_parameter("spectroscopy_freq", 432*MHz) + self.attr_argument("photon_limit_low", FreeValue(10)) + self.attr_argument("photon_limit_high", FreeValue(15)) @kernel def run(self): diff --git a/examples/sim/simple_simulation.py b/examples/sim/simple_simulation.py index e0e9d2b0c..602fcbd54 100644 --- a/examples/sim/simple_simulation.py +++ b/examples/sim/simple_simulation.py @@ -1,15 +1,13 @@ from artiq import * -class SimpleSimulation(Experiment, AutoDB): +class SimpleSimulation(EnvExperiment): """Simple simulation""" - class DBKeys: - core = Device() - a = Device() - b = Device() - c = Device() - d = Device() + def build(self): + self.attr_device("core") + for wo in "abcd": + self.attr_device(wo) @kernel def run(self): @@ -23,16 +21,13 @@ class SimpleSimulation(Experiment, AutoDB): def main(): - from artiq.sim import devices as sd + from artiq.sim import devices - core = sd.Core() - exp = SimpleSimulation( - core=core, - a=sd.WaveOutput(core=core, name="a"), - b=sd.WaveOutput(core=core, name="b"), - c=sd.WaveOutput(core=core, name="c"), - d=sd.WaveOutput(core=core, name="d"), - ) + dmgr = dict() + dmgr["core"] = devices.Core(dmgr) + for wo in "abcd": + dmgr[wo] = devices.WaveOutput(dmgr, wo) + exp = SimpleSimulation(dmgr) exp.run() if __name__ == "__main__": From 820ff2da2c69a280f9bb3a031fa10cefc5c28da8 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Mon, 13 Jul 2015 22:22:26 +0200 Subject: [PATCH 179/227] test/coredevice: WA for lack of constant string support in compiler (see issue #68) --- artiq/test/coredevice.py | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/artiq/test/coredevice.py b/artiq/test/coredevice.py index 863d4b383..8ab3b27b7 100644 --- a/artiq/test/coredevice.py +++ b/artiq/test/coredevice.py @@ -11,6 +11,9 @@ class RTT(EnvExperiment): 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() @@ -23,8 +26,7 @@ class RTT(EnvExperiment): delay(1*us) t0 = now_mu() self.ttl_inout.pulse(1*us) - self.set_result("rtt", - mu_to_seconds(self.ttl_inout.timestamp() - t0)) + self.set_rtt(mu_to_seconds(self.ttl_inout.timestamp() - t0)) class Loopback(EnvExperiment): @@ -33,6 +35,9 @@ class Loopback(EnvExperiment): 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() @@ -43,8 +48,7 @@ class Loopback(EnvExperiment): delay(1*us) t0 = now_mu() self.loop_out.pulse(1*us) - self.set_result("rtt", - mu_to_seconds(self.loop_in.timestamp() - t0)) + self.set_rtt(mu_to_seconds(self.loop_in.timestamp() - t0)) class ClockGeneratorLoopback(EnvExperiment): @@ -53,6 +57,9 @@ class ClockGeneratorLoopback(EnvExperiment): 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() @@ -63,8 +70,7 @@ class ClockGeneratorLoopback(EnvExperiment): with sequential: delay(200*ns) self.loop_clock_out.set(1*MHz) - self.set_result("count", - self.loop_clock_in.count()) + self.set_count(self.loop_clock_in.count()) class PulseRate(EnvExperiment): @@ -72,6 +78,9 @@ class PulseRate(EnvExperiment): 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) @@ -84,8 +93,7 @@ class PulseRate(EnvExperiment): dt += 1 self.core.break_realtime() else: - self.set_result("pulse_rate", - mu_to_seconds(2*dt)) + self.set_pulse_rate(mu_to_seconds(2*dt)) break @@ -106,6 +114,9 @@ class LoopbackCount(EnvExperiment): 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() @@ -116,8 +127,7 @@ class LoopbackCount(EnvExperiment): for i in range(self.npulses): delay(25*ns) self.ttl_inout.pulse(25*ns) - self.set_result("count", - self.ttl_inout.count()) + self.set_count(self.ttl_inout.count()) class Underflow(EnvExperiment): @@ -149,9 +159,12 @@ 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_result("time_at_start", now_mu()) + self.set_time_at_start(now_mu()) class Handover(EnvExperiment): From 56fc7a484ceb106abe011c530d2bb1a06cd209f1 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Mon, 13 Jul 2015 23:21:29 +0200 Subject: [PATCH 180/227] TTLInOut: timestamp -> timestamp_mu --- artiq/coredevice/ttl.py | 2 +- artiq/test/coredevice.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/artiq/coredevice/ttl.py b/artiq/coredevice/ttl.py index b9d680f8f..644e55ff6 100644 --- a/artiq/coredevice/ttl.py +++ b/artiq/coredevice/ttl.py @@ -195,7 +195,7 @@ class TTLInOut: return count @kernel - def timestamp(self): + def timestamp_mu(self): """Poll the RTIO input and returns an event timestamp, according to the gating. diff --git a/artiq/test/coredevice.py b/artiq/test/coredevice.py index 8ab3b27b7..95e8573b9 100644 --- a/artiq/test/coredevice.py +++ b/artiq/test/coredevice.py @@ -26,7 +26,7 @@ class RTT(EnvExperiment): delay(1*us) t0 = now_mu() self.ttl_inout.pulse(1*us) - self.set_rtt(mu_to_seconds(self.ttl_inout.timestamp() - t0)) + self.set_rtt(mu_to_seconds(self.ttl_inout.timestamp_mu() - t0)) class Loopback(EnvExperiment): @@ -48,7 +48,7 @@ class Loopback(EnvExperiment): delay(1*us) t0 = now_mu() self.loop_out.pulse(1*us) - self.set_rtt(mu_to_seconds(self.loop_in.timestamp() - t0)) + self.set_rtt(mu_to_seconds(self.loop_in.timestamp_mu() - t0)) class ClockGeneratorLoopback(EnvExperiment): From 90ba9f7bbf02537103b23214d23fc12577049628 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Tue, 14 Jul 2015 01:01:19 +0200 Subject: [PATCH 181/227] llvmlite: rename our package to be llvmlite_or1k to avoid collision with llvmlite package needed for numba --- artiq/coredevice/runtime.py | 4 +- artiq/py2llvm/ast_body.py | 2 +- artiq/py2llvm/base_types.py | 2 +- artiq/py2llvm/fractions.py | 2 +- artiq/py2llvm/lists.py | 2 +- artiq/py2llvm/module.py | 4 +- artiq/py2llvm/tools.py | 2 +- artiq/py2llvm/values.py | 2 +- artiq/test/py2llvm.py | 2 +- conda/llvmdev-or1k/meta.yaml | 1 + conda/llvmlite-or1k/meta.yaml | 5 +- misc/llvmlite-add-all-targets.patch | 78 +++++++++++++++++++++++++++++ 12 files changed, 92 insertions(+), 14 deletions(-) diff --git a/artiq/coredevice/runtime.py b/artiq/coredevice/runtime.py index 5841f9d65..8c45b6ed1 100644 --- a/artiq/coredevice/runtime.py +++ b/artiq/coredevice/runtime.py @@ -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 diff --git a/artiq/py2llvm/ast_body.py b/artiq/py2llvm/ast_body.py index 3cba3a976..580d7f189 100644 --- a/artiq/py2llvm/ast_body.py +++ b/artiq/py2llvm/ast_body.py @@ -1,6 +1,6 @@ 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 diff --git a/artiq/py2llvm/base_types.py b/artiq/py2llvm/base_types.py index 9e6f809ef..3ef472984 100644 --- a/artiq/py2llvm/base_types.py +++ b/artiq/py2llvm/base_types.py @@ -1,4 +1,4 @@ -import llvmlite.ir as ll +import llvmlite_or1k.ir as ll from artiq.py2llvm.values import VGeneric diff --git a/artiq/py2llvm/fractions.py b/artiq/py2llvm/fractions.py index c2a9730fa..aae7575cc 100644 --- a/artiq/py2llvm/fractions.py +++ b/artiq/py2llvm/fractions.py @@ -1,7 +1,7 @@ import inspect import 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 diff --git a/artiq/py2llvm/lists.py b/artiq/py2llvm/lists.py index ded97773e..e17ab5348 100644 --- a/artiq/py2llvm/lists.py +++ b/artiq/py2llvm/lists.py @@ -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 diff --git a/artiq/py2llvm/module.py b/artiq/py2llvm/module.py index 535b1ab51..75830971b 100644 --- a/artiq/py2llvm/module.py +++ b/artiq/py2llvm/module.py @@ -1,5 +1,5 @@ -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 diff --git a/artiq/py2llvm/tools.py b/artiq/py2llvm/tools.py index 27ffd8566..ba9e76949 100644 --- a/artiq/py2llvm/tools.py +++ b/artiq/py2llvm/tools.py @@ -1,4 +1,4 @@ -import llvmlite.ir as ll +import llvmlite_or1k.ir as ll def is_terminated(basic_block): return (basic_block.instructions diff --git a/artiq/py2llvm/values.py b/artiq/py2llvm/values.py index 554aad7c6..6f0b90e2c 100644 --- a/artiq/py2llvm/values.py +++ b/artiq/py2llvm/values.py @@ -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: diff --git a/artiq/test/py2llvm.py b/artiq/test/py2llvm.py index 9f2948db8..6eb607c7d 100644 --- a/artiq/test/py2llvm.py +++ b/artiq/test/py2llvm.py @@ -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 diff --git a/conda/llvmdev-or1k/meta.yaml b/conda/llvmdev-or1k/meta.yaml index 87586bc24..9406b8943 100644 --- a/conda/llvmdev-or1k/meta.yaml +++ b/conda/llvmdev-or1k/meta.yaml @@ -12,6 +12,7 @@ build: requirements: build: - system [linux and not armv6] + - cmake [linux] run: - system [linux and not armv6] diff --git a/conda/llvmlite-or1k/meta.yaml b/conda/llvmlite-or1k/meta.yaml index 9dbd8ab83..2a0191ce5 100644 --- a/conda/llvmlite-or1k/meta.yaml +++ b/conda/llvmlite-or1k/meta.yaml @@ -9,7 +9,6 @@ source: requirements: build: - python - - llvmdev-or1k - setuptools run: - python @@ -19,8 +18,8 @@ build: test: imports: - - llvmlite - - llvmlite.llvmpy + - llvmlite_or1k + - llvmlite_or1k.llvmpy about: home: https://pypi.python.org/pypi/llvmlite/ diff --git a/misc/llvmlite-add-all-targets.patch b/misc/llvmlite-add-all-targets.patch index 6b52fad9f..8ce43beef 100644 --- a/misc/llvmlite-add-all-targets.patch +++ b/misc/llvmlite-add-all-targets.patch @@ -36,3 +36,81 @@ index bfaa5b2..7d0df11 100644 def initialize_native_target(): """ Initialize the native (host) target. Necessary before doing any +diff --git a/ffi/build.py b/ffi/build.py +index 3889ba5..58f93ec 100755 +--- a/ffi/build.py ++++ b/ffi/build.py +@@ -58,7 +58,7 @@ def find_win32_generator(): + + def main_win32(): + generator = find_win32_generator() +- config = 'Release' ++ config = 'Debug' + if not os.path.exists(build_dir): + os.mkdir(build_dir) + try_cmake(here_dir, build_dir, generator) +diff --git a/setup.py b/setup.py +index 6d28265..f4edd29 100644 +--- a/setup.py ++++ b/setup.py +@@ -15,10 +15,10 @@ from llvmlite.utils import get_library_files + import versioneer + + versioneer.VCS = 'git' +-versioneer.versionfile_source = 'llvmlite/_version.py' +-versioneer.versionfile_build = 'llvmlite/_version.py' ++versioneer.versionfile_source = 'llvmlite_or1k/_version.py' ++versioneer.versionfile_build = 'llvmlite_or1k/_version.py' + versioneer.tag_prefix = 'v' # tags are like v1.2.0 +-versioneer.parentdir_prefix = 'llvmlite-' # dirname like 'myproject-1.2.0' ++versioneer.parentdir_prefix = 'llvmlite_or1k-' # dirname like 'myproject-1.2.0' + + + here_dir = os.path.dirname(__file__) +@@ -54,7 +54,7 @@ class LlvmliteBuildExt(build_ext): + # HACK: this makes sure the library file (which is large) is only + # included in binary builds, not source builds. + self.distribution.package_data = { +- "llvmlite.binding": get_library_files(), ++ "llvmlite_or1k.binding": get_library_files(), + } + + +@@ -63,7 +63,7 @@ class LlvmliteInstall(install): + # This seems to only be necessary on OSX. + def run(self): + self.distribution.package_data = { +- "llvmlite.binding": get_library_files(), ++ "llvmlite_or1k.binding": get_library_files(), + } + install.run(self) + +@@ -74,14 +74,14 @@ cmdclass.update({'build': LlvmliteBuild, + }) + + +-packages = ['llvmlite', +- 'llvmlite.binding', +- 'llvmlite.ir', +- 'llvmlite.llvmpy', +- 'llvmlite.tests', ++packages = ['llvmlite_or1k', ++ 'llvmlite_or1k.binding', ++ 'llvmlite_or1k.ir', ++ 'llvmlite_or1k.llvmpy', ++ 'llvmlite_or1k.tests', + ] + +-setup(name='llvmlite', ++setup(name='llvmlite_or1k', + description="lightweight wrapper around basic LLVM functionality", + version=versioneer.get_version(), + classifiers=[ +@@ -96,6 +96,7 @@ setup(name='llvmlite', + "Topic :: Software Development :: Code Generators", + "Topic :: Software Development :: Compilers", + ], ++ package_dir={"llvmlite_or1k" : "llvmlite"}, + # Include the separately-compiled shared library + author="Continuum Analytics, Inc.", + author_email="numba-users@continuum.io", From 6e3fd591f754ad3e0d537af47691724b49a7c4fe Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Tue, 14 Jul 2015 16:59:04 +0200 Subject: [PATCH 182/227] gui: remove unnecessary QSplitter --- artiq/gui/parameters.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/artiq/gui/parameters.py b/artiq/gui/parameters.py index 8cf8d2f61..86495b6bf 100644 --- a/artiq/gui/parameters.py +++ b/artiq/gui/parameters.py @@ -29,10 +29,8 @@ class ParametersDock(dockarea.Dock): def __init__(self): dockarea.Dock.__init__(self, "Parameters", size=(400, 300)) - splitter = QtGui.QSplitter(QtCore.Qt.Horizontal) - self.addWidget(splitter) grid = LayoutWidget() - splitter.addWidget(grid) + self.addWidget(grid) self.search = QtGui.QLineEdit() self.search.setPlaceholderText("search...") From 84e7f55df350c585582b20c09524a38fc73e9f61 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Tue, 14 Jul 2015 17:28:26 +0200 Subject: [PATCH 183/227] gui: fix DDS class recognition --- artiq/gui/moninj.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/gui/moninj.py b/artiq/gui/moninj.py index f302cb800..282413cc7 100644 --- a/artiq/gui/moninj.py +++ b/artiq/gui/moninj.py @@ -167,7 +167,7 @@ class _DeviceManager: self.send_to_device, channel, force_out, k) self.ttl_cb() if (v["module"] == "artiq.coredevice.dds" - and v["class"] == "DDS"): + and v["class"] in {"AD9858", "AD9914"}): channel = v["arguments"]["channel"] sysclk = v["arguments"]["sysclk"] self.dds_widgets[channel] = _DDSWidget( From 1edeb5a13f45815d9cba19e6615a00ffd84c2704 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Tue, 14 Jul 2015 17:30:21 +0200 Subject: [PATCH 184/227] sync_struct: docstring cleanup --- artiq/protocols/sync_struct.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/artiq/protocols/sync_struct.py b/artiq/protocols/sync_struct.py index 00979ac91..665edfc91 100644 --- a/artiq/protocols/sync_struct.py +++ b/artiq/protocols/sync_struct.py @@ -150,9 +150,7 @@ 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({"action": "append", @@ -160,9 +158,7 @@ class Notifier: "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({"action": "insert", @@ -172,9 +168,7 @@ class Notifier: 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({"action": "pop", From 55cd41444e34343aec0352a5327f9ba05cf9012d Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Tue, 14 Jul 2015 17:30:55 +0200 Subject: [PATCH 185/227] gui/DictSync: better support of nested structs --- artiq/gui/tools.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/artiq/gui/tools.py b/artiq/gui/tools.py index f38ebcc09..c8308718f 100644 --- a/artiq/gui/tools.py +++ b/artiq/gui/tools.py @@ -6,13 +6,29 @@ class _DictSyncSubstruct: 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 _DictSyncSubstruct(self.update_cb, self.ref[key]) + class DictSyncModel(QtCore.QAbstractTableModel): def __init__(self, headers, parent, init): From 21e8596d8c373ea3b7c8fafc6caab47399d5d2be Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Tue, 14 Jul 2015 17:31:18 +0200 Subject: [PATCH 186/227] gui: RT results overview --- artiq/frontend/artiq_gui.py | 9 ++++- artiq/gui/results.py | 68 +++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 artiq/gui/results.py diff --git a/artiq/frontend/artiq_gui.py b/artiq/frontend/artiq_gui.py index 839121458..9169fb531 100755 --- a/artiq/frontend/artiq_gui.py +++ b/artiq/frontend/artiq_gui.py @@ -14,6 +14,7 @@ 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.schedule import ScheduleDock from artiq.gui.log import LogDock @@ -70,12 +71,18 @@ def main(): args.server, args.port_notify)) atexit.register(lambda: loop.run_until_complete(d_explorer.sub_close())) + d_results = ResultsDock() + 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())) area.addDock(d_ttl_dds.dds_dock, "top") area.addDock(d_ttl_dds.ttl_dock, "above", d_ttl_dds.dds_dock) - area.addDock(d_explorer, "above", d_ttl_dds.ttl_dock) + area.addDock(d_results, "above", d_ttl_dds.ttl_dock) + area.addDock(d_explorer, "above", d_results) d_params = ParametersDock() area.addDock(d_params, "right", d_explorer) diff --git a/artiq/gui/results.py b/artiq/gui/results.py new file mode 100644 index 000000000..505daaf6e --- /dev/null +++ b/artiq/gui/results.py @@ -0,0 +1,68 @@ +import asyncio + +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 + + +def _fmt_type(v): + t = type(v) + r = t.__name__ + if t is list or t is dict or t is set: + r += " ({})".format(len(v)) + return r + + +class ResultsModel(DictSyncModel): + def __init__(self, parent, init): + DictSyncModel.__init__(self, ["Result", "Type"], + 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 _fmt_type(v) + else: + raise ValueError + + +class ResultsDock(dockarea.Dock): + def __init__(self): + dockarea.Dock.__init__(self, "Results", size=(1500, 500)) + + grid = LayoutWidget() + self.addWidget(grid) + + self.table = QtGui.QTableView() + self.table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) + 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(["Number", "XY", "Histogram"]): + btn = QtGui.QPushButton(name) + display_grid.addWidget(btn, n, 0) + + @asyncio.coroutine + def sub_connect(self, host, port): + self.subscriber = Subscriber("rt_results", self.init_results_model) + yield from self.subscriber.connect(host, port) + + @asyncio.coroutine + def sub_close(self): + yield from self.subscriber.close() + + def init_results_model(self, init): + table_model = ResultsModel(self.table, init) + self.table.setModel(table_model) + return table_model From e20b26011743882f0b39cc7b508b56766361f6cf Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Tue, 14 Jul 2015 19:08:08 +0200 Subject: [PATCH 187/227] gui: fix selections --- artiq/gui/results.py | 2 +- artiq/gui/schedule.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/artiq/gui/results.py b/artiq/gui/results.py index 505daaf6e..316fd6cac 100644 --- a/artiq/gui/results.py +++ b/artiq/gui/results.py @@ -41,7 +41,7 @@ class ResultsDock(dockarea.Dock): self.addWidget(grid) self.table = QtGui.QTableView() - self.table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) + self.table.setSelectionMode(QtGui.QAbstractItemView.NoSelection) grid.addWidget(self.table, 0, 0) add_display_box = QtGui.QGroupBox("Add display") diff --git a/artiq/gui/schedule.py b/artiq/gui/schedule.py index 459547f86..c9e6eb2b3 100644 --- a/artiq/gui/schedule.py +++ b/artiq/gui/schedule.py @@ -56,6 +56,7 @@ class ScheduleDock(dockarea.Dock): self.table = QtGui.QTableView() self.table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) + self.table.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) self.addWidget(self.table) self.table.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) From a07f2473b059045bc21137bd1270457a04848701 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Tue, 14 Jul 2015 20:06:29 +0200 Subject: [PATCH 188/227] manual: add core device moninj port --- doc/manual/default_network_ports.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/manual/default_network_ports.rst b/doc/manual/default_network_ports.rst index 10b54cf23..c5728e428 100644 --- a/doc/manual/default_network_ports.rst +++ b/doc/manual/default_network_ports.rst @@ -4,7 +4,9 @@ Default network ports +--------------------------+--------------+ | Component | Default port | +==========================+==============+ -| Core device | 1381 | +| Core device (main) | 1381 | ++--------------------------+--------------+ +| Core device (mon/inj) | 3250 (UDP) | +--------------------------+--------------+ | Master (notifications) | 3250 | +--------------------------+--------------+ From 7770ab64f288bb42733841ca028ca5d3bda98cc8 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Tue, 14 Jul 2015 23:43:08 +0200 Subject: [PATCH 189/227] worker: factor timeouts --- artiq/master/worker.py | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/artiq/master/worker.py b/artiq/master/worker.py index e3b1827ed..923559d30 100644 --- a/artiq/master/worker.py +++ b/artiq/master/worker.py @@ -26,14 +26,9 @@ class WorkerError(Exception): class Worker: - def __init__(self, handlers, - send_timeout=0.5, term_timeout=1.0, - build_timeout=15.0, results_timeout=15.0): + def __init__(self, handlers, send_timeout=0.5): self.handlers = handlers self.send_timeout = send_timeout - self.term_timeout = term_timeout - self.build_timeout = build_timeout - self.results_timeout = results_timeout self.rid = None self.process = None @@ -74,7 +69,7 @@ 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. @@ -96,7 +91,7 @@ class Worker: 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) @@ -105,7 +100,7 @@ class Worker: 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) self.process.kill() @@ -116,7 +111,7 @@ class Worker: 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()) @@ -125,7 +120,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") @@ -185,7 +180,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() @@ -196,7 +191,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: @@ -209,7 +204,7 @@ class Worker: return completed @asyncio.coroutine - def build(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( @@ -218,7 +213,7 @@ class Worker: "pipeline_name": pipeline_name, "expid": expid, "priority": priority}, - self.build_timeout) + timeout) @asyncio.coroutine def prepare(self): @@ -247,6 +242,6 @@ 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) From 9ed4dcd7d1dea38d1bdd1058939efd680c9f3877 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Wed, 15 Jul 2015 10:54:44 +0200 Subject: [PATCH 190/227] repository: load experiments in worker, list arguments --- artiq/frontend/artiq_master.py | 5 +-- artiq/master/repository.py | 63 +++++++++++++++++++++++----------- artiq/master/worker.py | 28 +++++++++++---- artiq/master/worker_impl.py | 36 +++++++++++++++++++ 4 files changed, 103 insertions(+), 29 deletions(-) diff --git a/artiq/frontend/artiq_master.py b/artiq/frontend/artiq_master.py index 37e3a6fde..bf3b6d0c5 100755 --- a/artiq/frontend/artiq_master.py +++ b/artiq/frontend/artiq_master.py @@ -37,7 +37,6 @@ def main(): ddb = FlatFileDB("ddb.pyon") pdb = FlatFileDB("pdb.pyon") rtr = Notifier(dict()) - repository = Repository() if os.name == "nt": loop = asyncio.ProactorEventLoop() @@ -61,12 +60,14 @@ def main(): "master_ddb": ddb, "master_pdb": pdb, "master_schedule": scheduler, - "master_repository": repository, }) loop.run_until_complete(server_control.start( args.bind, args.port_control)) atexit.register(lambda: loop.run_until_complete(server_control.stop())) + repository = Repository() + loop.run_until_complete(repository.scan()) + server_notify = Publisher({ "schedule": scheduler.notifier, "devices": ddb.data, diff --git a/artiq/master/repository.py b/artiq/master/repository.py index 3ddea5110..c4c098f2b 100644 --- a/artiq/master/repository.py +++ b/artiq/master/repository.py @@ -1,38 +1,61 @@ import os +import logging +import asyncio from artiq.protocols.sync_struct import Notifier -from artiq.tools import file_import -from artiq.language.environment import is_experiment +from artiq.master.worker import Worker -def scan_experiments(): +logger = logging.getLogger(__name__) + + +@asyncio.coroutine +def _scan_experiments(): 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() + 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 + "file": full_name, + "class_name": class_name, + "arguments": arguments } r[name] = entry + except: + logger.warning("Skipping file '%s'", f, exc_info=True) return r +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] + + class Repository: def __init__(self): - self.explist = Notifier(scan_experiments()) + self.explist = Notifier(dict()) - def get_data(self, filename): - with open(os.path.join("repository", filename)) as f: - return f.read() + @asyncio.coroutine + def scan(self): + new_explist = yield from _scan_experiments() + _sync_explist(self.explist, new_explist) diff --git a/artiq/master/worker.py b/artiq/master/worker.py index 923559d30..198c97afc 100644 --- a/artiq/master/worker.py +++ b/artiq/master/worker.py @@ -26,7 +26,7 @@ class WorkerError(Exception): class Worker: - def __init__(self, handlers, send_timeout=0.5): + def __init__(self, handlers=dict(), send_timeout=0.5): self.handlers = handlers self.send_timeout = send_timeout @@ -74,7 +74,7 @@ class Worker: worker process. This method should always be called by the user to clean up, even if - prepare() raises an exception.""" + build() or examine() raises an exception.""" self.closed.set() yield from self.io_lock.acquire() try: @@ -83,10 +83,10 @@ 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"} @@ -94,7 +94,7 @@ class Worker: 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 @@ -102,11 +102,11 @@ class Worker: yield from asyncio_process_wait_timeout(self.process, 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() @@ -170,6 +170,8 @@ 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] try: @@ -245,3 +247,15 @@ class Worker: def write_results(self, timeout=15.0): yield from self._worker_action({"action": "write_results"}, 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 diff --git a/artiq/master/worker_impl.py b/artiq/master/worker_impl.py index eb0fec592..59df48a02 100644 --- a/artiq/master/worker_impl.py +++ b/artiq/master/worker_impl.py @@ -99,6 +99,39 @@ def get_exp(file, exp): return getattr(module, exp) +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) + 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 @@ -143,6 +176,9 @@ 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: From 7de56666e39dd21164c3a241973b748dc12e5073 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Wed, 15 Jul 2015 10:59:48 +0200 Subject: [PATCH 191/227] worker,environment: support scanning of arguments with no default --- artiq/language/environment.py | 19 +++++++++++++++++-- artiq/master/worker_impl.py | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/artiq/language/environment.py b/artiq/language/environment.py index 4065759b2..9d43d8da6 100644 --- a/artiq/language/environment.py +++ b/artiq/language/environment.py @@ -10,12 +10,20 @@ class NoDefault: pass +class DefaultMissing(Exception): + """Raised by the ``default`` method of argument processors when no default + value is available.""" + pass + + class FreeValue: 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): @@ -32,13 +40,14 @@ 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(), **kwargs): + param_override=dict(), default_arg_none=False, **kwargs): self.requested_args = dict() 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 @@ -65,7 +74,13 @@ class HasEnvironment: try: argval = self.__kwargs[key] except KeyError: - return processor.default() + 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): diff --git a/artiq/master/worker_impl.py b/artiq/master/worker_impl.py index 59df48a02..2262f31fb 100644 --- a/artiq/master/worker_impl.py +++ b/artiq/master/worker_impl.py @@ -126,7 +126,7 @@ def examine(dmgr, pdb, rdb, file): name = exp_class.__doc__.splitlines()[0].strip() if name[-1] == ".": name = name[:-1] - exp_inst = exp_class(dmgr, pdb, rdb) + 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) From 255aba92475e67cb5b1213d83eceb01205a851ab Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Wed, 15 Jul 2015 11:07:48 +0200 Subject: [PATCH 192/227] test/worker: remove stale handler --- artiq/test/worker.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/artiq/test/worker.py b/artiq/test/worker.py index 8682ea041..2616a3656 100644 --- a/artiq/test/worker.py +++ b/artiq/test/worker.py @@ -52,12 +52,8 @@ def _run_experiment(experiment): "experiment": experiment, "arguments": dict() } - handlers = { - "init_rt_results": lambda description: None - } - loop = asyncio.get_event_loop() - worker = Worker(handlers) + worker = Worker() loop.run_until_complete(_call_worker(worker, expid)) From 84de2fb28bf47dc4e781f0a0181c6a29711f0d09 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Wed, 15 Jul 2015 11:08:12 +0200 Subject: [PATCH 193/227] expid: experiment -> class_name --- artiq/frontend/artiq_client.py | 12 ++++++------ artiq/gui/explorer.py | 6 +++--- artiq/gui/schedule.py | 6 +++--- artiq/master/worker_impl.py | 8 ++++---- artiq/test/hardware_testbench.py | 2 +- artiq/test/scheduler.py | 2 +- artiq/test/worker.py | 4 ++-- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/artiq/frontend/artiq_client.py b/artiq/frontend/artiq_client.py index 78e4a49ac..8305bc787 100755 --- a/artiq/frontend/artiq_client.py +++ b/artiq/frontend/artiq_client.py @@ -43,8 +43,8 @@ def get_argparser(): parser_add.add_argument("-f", "--flush", default=False, action="store_true", help="flush the pipeline before preparing " "the experiment") - parser_add.add_argument("-e", "--experiment", default=None, - help="experiment to run") + 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="*", @@ -102,7 +102,7 @@ def _action_submit(remote, args): expid = { "file": args.file, - "experiment": args.experiment, + "class_name": args.class_name, "arguments": arguments, } if args.timed is None: @@ -142,7 +142,7 @@ def _show_schedule(schedule): x[1]["due_date"] or 0, x[0])) table = PrettyTable(["RID", "Pipeline", " Status ", "Prio", - "Due date", "File", "Experiment", "Arguments"]) + "Due date", "File", "Class name", "Arguments"]) for rid, v in l: row = [rid, v["pipeline"], v["status"], v["priority"]] if v["due_date"] is None: @@ -151,10 +151,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(v["expid"]["class_name"]) row.append(format_arguments(v["expid"]["arguments"])) table.add_row(row) print(table) diff --git a/artiq/gui/explorer.py b/artiq/gui/explorer.py index df2057b05..4b4529cfd 100644 --- a/artiq/gui/explorer.py +++ b/artiq/gui/explorer.py @@ -85,11 +85,11 @@ class ExplorerDock(dockarea.Dock): return self.explist_model @asyncio.coroutine - def submit(self, pipeline_name, file, experiment, arguments, + 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, @@ -107,6 +107,6 @@ class ExplorerDock(dockarea.Dock): else: due_date = None asyncio.async(self.submit(self.pipeline.text(), - expinfo["file"], expinfo["experiment"], + expinfo["file"], expinfo["class_name"], dict(), self.priority.value(), due_date, self.flush.isChecked())) diff --git a/artiq/gui/schedule.py b/artiq/gui/schedule.py index c9e6eb2b3..cd9c8d402 100644 --- a/artiq/gui/schedule.py +++ b/artiq/gui/schedule.py @@ -13,7 +13,7 @@ class _ScheduleModel(DictSyncModel): def __init__(self, parent, init): DictSyncModel.__init__(self, ["RID", "Pipeline", "Status", "Prio", "Due date", - "File", "Experiment", "Arguments"], + "File", "Class name", "Arguments"], parent, init) def sort_key(self, k, v): @@ -38,10 +38,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"] + return v["expid"]["class_name"] elif column == 7: return format_arguments(v["expid"]["arguments"]) else: diff --git a/artiq/master/worker_impl.py b/artiq/master/worker_impl.py index 2262f31fb..23de27b22 100644 --- a/artiq/master/worker_impl.py +++ b/artiq/master/worker_impl.py @@ -86,9 +86,9 @@ class Scheduler: 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: @@ -96,7 +96,7 @@ 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", @@ -154,7 +154,7 @@ def main(): start_time = time.localtime() rid = obj["rid"] expid = obj["expid"] - exp = get_exp(expid["file"], expid["experiment"]) + 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, diff --git a/artiq/test/hardware_testbench.py b/artiq/test/hardware_testbench.py index a082a676c..1734852cc 100644 --- a/artiq/test/hardware_testbench.py +++ b/artiq/test/hardware_testbench.py @@ -40,7 +40,7 @@ class ExperimentCase(unittest.TestCase): def execute(self, cls, **kwargs): expid = { "file": sys.modules[cls.__module__].__file__, - "experiment": cls.__name__, + "class_name": cls.__name__, "arguments": kwargs } self.dmgr.virtual_devices["scheduler"].expid = expid diff --git a/artiq/test/scheduler.py b/artiq/test/scheduler.py index 7493e53d4..9c1b2717e 100644 --- a/artiq/test/scheduler.py +++ b/artiq/test/scheduler.py @@ -28,7 +28,7 @@ class BackgroundExperiment(EnvExperiment): def _get_expid(name): return { "file": sys.modules[__name__].__file__, - "experiment": name, + "class_name": name, "arguments": dict() } diff --git a/artiq/test/worker.py b/artiq/test/worker.py index 2616a3656..b40e7b6c8 100644 --- a/artiq/test/worker.py +++ b/artiq/test/worker.py @@ -46,10 +46,10 @@ def _call_worker(worker, expid): 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() } loop = asyncio.get_event_loop() From f836465585be6c2d2983fe79424b78a2965a12c3 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Wed, 15 Jul 2015 11:20:41 +0200 Subject: [PATCH 194/227] coredevice: environment -> runtime --- artiq/coredevice/comm_dummy.py | 14 -------------- artiq/coredevice/core.py | 6 +++--- artiq/coredevice/runtime.py | 4 ++-- artiq/py2llvm/__init__.py | 4 ++-- artiq/py2llvm/ast_body.py | 20 ++++++++++---------- artiq/py2llvm/module.py | 14 +++++++------- 6 files changed, 24 insertions(+), 38 deletions(-) diff --git a/artiq/coredevice/comm_dummy.py b/artiq/coredevice/comm_dummy.py index d2803ce7a..5b0c35c46 100644 --- a/artiq/coredevice/comm_dummy.py +++ b/artiq/coredevice/comm_dummy.py @@ -1,24 +1,10 @@ from operator import itemgetter -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: def __init__(self, dmgr): pass - def get_runtime_env(self): - return _RuntimeEnvironment() - def switch_clock(self, external): pass diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index b41561161..536a10054 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -13,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 @@ -54,7 +54,7 @@ class Core: 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): @@ -102,7 +102,7 @@ class Core: 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 diff --git a/artiq/coredevice/runtime.py b/artiq/coredevice/runtime.py index 8c45b6ed1..84a6bb386 100644 --- a/artiq/coredevice/runtime.py +++ b/artiq/coredevice/runtime.py @@ -196,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 @@ -209,4 +209,4 @@ class Environment(LinkInterface): return obj def __repr__(self): - return "".format(self.cpu_type) + return "".format(self.cpu_type) diff --git a/artiq/py2llvm/__init__.py b/artiq/py2llvm/__init__.py index fefb2b9ff..ebb8a93af 100644 --- a/artiq/py2llvm/__init__.py +++ b/artiq/py2llvm/__init__.py @@ -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() diff --git a/artiq/py2llvm/ast_body.py b/artiq/py2llvm/ast_body.py index 580d7f189..42de0fc9c 100644 --- a/artiq/py2llvm/ast_body.py +++ b/artiq/py2llvm/ast_body.py @@ -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) diff --git a/artiq/py2llvm/module.py b/artiq/py2llvm/module.py index 75830971b..f4df806e6 100644 --- a/artiq/py2llvm/module.py +++ b/artiq/py2llvm/module.py @@ -5,12 +5,12 @@ 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): From af20efafa5ef1390f870d95ee20caeb0c6061ebc Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Wed, 15 Jul 2015 17:20:26 +0200 Subject: [PATCH 195/227] conda: update llvmlite-or1k package and up the build number --- conda/llvmlite-or1k/meta.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/conda/llvmlite-or1k/meta.yaml b/conda/llvmlite-or1k/meta.yaml index 2a0191ce5..db3c24bcd 100644 --- a/conda/llvmlite-or1k/meta.yaml +++ b/conda/llvmlite-or1k/meta.yaml @@ -9,12 +9,13 @@ source: requirements: build: - python + - llvmdev-or1k - setuptools run: - python build: - number: 0 + number: 1 test: imports: From 511d51977ebdd35674dea094e91057b32239e9bc Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Wed, 15 Jul 2015 17:27:50 +0200 Subject: [PATCH 196/227] llvmlite: split patch to be cleaner. close #72 --- conda/llvmlite-or1k/bld.bat | 2 + conda/llvmlite-or1k/build.sh | 2 + misc/llvmlite-add-all-targets.patch | 78 ------------------- misc/llvmlite-build-as-debug-on-windows.patch | 13 ++++ misc/llvmlite-rename.patch | 65 ++++++++++++++++ 5 files changed, 82 insertions(+), 78 deletions(-) create mode 100644 misc/llvmlite-build-as-debug-on-windows.patch create mode 100644 misc/llvmlite-rename.patch diff --git a/conda/llvmlite-or1k/bld.bat b/conda/llvmlite-or1k/bld.bat index 78ab6ac38..17e63ad30 100644 --- a/conda/llvmlite-or1k/bld.bat +++ b/conda/llvmlite-or1k/bld.bat @@ -6,6 +6,8 @@ if exist ffi\build rmdir /S /Q ffi\build @rem Apply patches patch -p1 < %RECIPE_DIR%/../../misc/llvmlite-add-all-targets.patch +patch -p1 < %RECIPE_DIR%/../../misc/llvmlite-rename.patch +patch -p1 < %RECIPE_DIR%/../../misc/llvmlite-build-as-debug-on-windows.patch %PYTHON% -S setup.py install if errorlevel 1 exit 1 diff --git a/conda/llvmlite-or1k/build.sh b/conda/llvmlite-or1k/build.sh index f2734bba4..327c15518 100755 --- a/conda/llvmlite-or1k/build.sh +++ b/conda/llvmlite-or1k/build.sh @@ -1,4 +1,6 @@ #!/bin/bash patch -p1 < ${RECIPE_DIR}/../../misc/llvmlite-add-all-targets.patch +patch -p1 < ${RECIPE_DIR}/../../misc/llvmlite-rename.patch +patch -p1 < ${RECIPE_DIR}/../../misc/llvmlite-build-as-debug-on-windows.patch PATH=/usr/local/llvm-or1k/bin:$PATH $PYTHON setup.py install diff --git a/misc/llvmlite-add-all-targets.patch b/misc/llvmlite-add-all-targets.patch index 8ce43beef..6b52fad9f 100644 --- a/misc/llvmlite-add-all-targets.patch +++ b/misc/llvmlite-add-all-targets.patch @@ -36,81 +36,3 @@ index bfaa5b2..7d0df11 100644 def initialize_native_target(): """ Initialize the native (host) target. Necessary before doing any -diff --git a/ffi/build.py b/ffi/build.py -index 3889ba5..58f93ec 100755 ---- a/ffi/build.py -+++ b/ffi/build.py -@@ -58,7 +58,7 @@ def find_win32_generator(): - - def main_win32(): - generator = find_win32_generator() -- config = 'Release' -+ config = 'Debug' - if not os.path.exists(build_dir): - os.mkdir(build_dir) - try_cmake(here_dir, build_dir, generator) -diff --git a/setup.py b/setup.py -index 6d28265..f4edd29 100644 ---- a/setup.py -+++ b/setup.py -@@ -15,10 +15,10 @@ from llvmlite.utils import get_library_files - import versioneer - - versioneer.VCS = 'git' --versioneer.versionfile_source = 'llvmlite/_version.py' --versioneer.versionfile_build = 'llvmlite/_version.py' -+versioneer.versionfile_source = 'llvmlite_or1k/_version.py' -+versioneer.versionfile_build = 'llvmlite_or1k/_version.py' - versioneer.tag_prefix = 'v' # tags are like v1.2.0 --versioneer.parentdir_prefix = 'llvmlite-' # dirname like 'myproject-1.2.0' -+versioneer.parentdir_prefix = 'llvmlite_or1k-' # dirname like 'myproject-1.2.0' - - - here_dir = os.path.dirname(__file__) -@@ -54,7 +54,7 @@ class LlvmliteBuildExt(build_ext): - # HACK: this makes sure the library file (which is large) is only - # included in binary builds, not source builds. - self.distribution.package_data = { -- "llvmlite.binding": get_library_files(), -+ "llvmlite_or1k.binding": get_library_files(), - } - - -@@ -63,7 +63,7 @@ class LlvmliteInstall(install): - # This seems to only be necessary on OSX. - def run(self): - self.distribution.package_data = { -- "llvmlite.binding": get_library_files(), -+ "llvmlite_or1k.binding": get_library_files(), - } - install.run(self) - -@@ -74,14 +74,14 @@ cmdclass.update({'build': LlvmliteBuild, - }) - - --packages = ['llvmlite', -- 'llvmlite.binding', -- 'llvmlite.ir', -- 'llvmlite.llvmpy', -- 'llvmlite.tests', -+packages = ['llvmlite_or1k', -+ 'llvmlite_or1k.binding', -+ 'llvmlite_or1k.ir', -+ 'llvmlite_or1k.llvmpy', -+ 'llvmlite_or1k.tests', - ] - --setup(name='llvmlite', -+setup(name='llvmlite_or1k', - description="lightweight wrapper around basic LLVM functionality", - version=versioneer.get_version(), - classifiers=[ -@@ -96,6 +96,7 @@ setup(name='llvmlite', - "Topic :: Software Development :: Code Generators", - "Topic :: Software Development :: Compilers", - ], -+ package_dir={"llvmlite_or1k" : "llvmlite"}, - # Include the separately-compiled shared library - author="Continuum Analytics, Inc.", - author_email="numba-users@continuum.io", diff --git a/misc/llvmlite-build-as-debug-on-windows.patch b/misc/llvmlite-build-as-debug-on-windows.patch new file mode 100644 index 000000000..e385fb4a2 --- /dev/null +++ b/misc/llvmlite-build-as-debug-on-windows.patch @@ -0,0 +1,13 @@ +diff --git a/ffi/build.py b/ffi/build.py +index 3889ba5..58f93ec 100755 +--- a/ffi/build.py ++++ b/ffi/build.py +@@ -58,7 +58,7 @@ def find_win32_generator(): + + def main_win32(): + generator = find_win32_generator() +- config = 'Release' ++ config = 'Debug' + if not os.path.exists(build_dir): + os.mkdir(build_dir) + try_cmake(here_dir, build_dir, generator) diff --git a/misc/llvmlite-rename.patch b/misc/llvmlite-rename.patch new file mode 100644 index 000000000..faea85104 --- /dev/null +++ b/misc/llvmlite-rename.patch @@ -0,0 +1,65 @@ +diff --git a/setup.py b/setup.py +index 6d28265..f4edd29 100644 +--- a/setup.py ++++ b/setup.py +@@ -15,10 +15,10 @@ from llvmlite.utils import get_library_files + import versioneer + + versioneer.VCS = 'git' +-versioneer.versionfile_source = 'llvmlite/_version.py' +-versioneer.versionfile_build = 'llvmlite/_version.py' ++versioneer.versionfile_source = 'llvmlite_or1k/_version.py' ++versioneer.versionfile_build = 'llvmlite_or1k/_version.py' + versioneer.tag_prefix = 'v' # tags are like v1.2.0 +-versioneer.parentdir_prefix = 'llvmlite-' # dirname like 'myproject-1.2.0' ++versioneer.parentdir_prefix = 'llvmlite_or1k-' # dirname like 'myproject-1.2.0' + + + here_dir = os.path.dirname(__file__) +@@ -54,7 +54,7 @@ class LlvmliteBuildExt(build_ext): + # HACK: this makes sure the library file (which is large) is only + # included in binary builds, not source builds. + self.distribution.package_data = { +- "llvmlite.binding": get_library_files(), ++ "llvmlite_or1k.binding": get_library_files(), + } + + +@@ -63,7 +63,7 @@ class LlvmliteInstall(install): + # This seems to only be necessary on OSX. + def run(self): + self.distribution.package_data = { +- "llvmlite.binding": get_library_files(), ++ "llvmlite_or1k.binding": get_library_files(), + } + install.run(self) + +@@ -74,14 +74,14 @@ cmdclass.update({'build': LlvmliteBuild, + }) + + +-packages = ['llvmlite', +- 'llvmlite.binding', +- 'llvmlite.ir', +- 'llvmlite.llvmpy', +- 'llvmlite.tests', ++packages = ['llvmlite_or1k', ++ 'llvmlite_or1k.binding', ++ 'llvmlite_or1k.ir', ++ 'llvmlite_or1k.llvmpy', ++ 'llvmlite_or1k.tests', + ] + +-setup(name='llvmlite', ++setup(name='llvmlite_or1k', + description="lightweight wrapper around basic LLVM functionality", + version=versioneer.get_version(), + classifiers=[ +@@ -96,6 +96,7 @@ setup(name='llvmlite', + "Topic :: Software Development :: Code Generators", + "Topic :: Software Development :: Compilers", + ], ++ package_dir={"llvmlite_or1k" : "llvmlite"}, + # Include the separately-compiled shared library + author="Continuum Analytics, Inc.", + author_email="numba-users@continuum.io", From fa4f38b871009f75b8c6fc23bd82bddbd13caa69 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Wed, 15 Jul 2015 17:31:57 +0200 Subject: [PATCH 197/227] manual: add missing llvmlite patches --- doc/manual/installing.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index 10bdd61c3..ec7f923c4 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -297,6 +297,8 @@ Installing the host-side software $ git clone https://github.com/numba/llvmlite $ cd llvmlite $ patch -p1 < ~/artiq-dev/artiq/misc/llvmlite-add-all-targets.patch + $ patch -p1 < ~/artiq-dev/artiq/misc/llvmlite-rename.patch + $ patch -p1 < ~/artiq-dev/artiq/misc/llvmlite-build-as-debug-on-windows.patch $ PATH=/usr/local/llvm-or1k/bin:$PATH sudo -E python3 setup.py install .. note:: From 774c66a2093420f5b3db263a00e4d8081f4859e4 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Wed, 15 Jul 2015 17:32:32 +0200 Subject: [PATCH 198/227] manual: also build LLVM native target (needed for py2llvm test) --- doc/manual/installing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index ec7f923c4..58b6e1a04 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -289,7 +289,7 @@ Installing the host-side software $ cd .. $ mkdir build $ cd build - $ cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local/llvm-or1k -DLLVM_TARGETS_TO_BUILD=OR1K -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=ON + $ cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local/llvm-or1k -DLLVM_TARGETS_TO_BUILD="OR1K;X86" -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=ON $ make -j4 $ sudo make install From 08eec40861b8e3cdd40cb4627842ee95cd8b8f97 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Wed, 15 Jul 2015 17:34:06 +0200 Subject: [PATCH 199/227] manual: building LLVM as shared libraries is not recommended on Linux and not supported on Windows --- doc/manual/installing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index 58b6e1a04..dec7eab80 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -289,7 +289,7 @@ Installing the host-side software $ cd .. $ mkdir build $ cd build - $ cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local/llvm-or1k -DLLVM_TARGETS_TO_BUILD="OR1K;X86" -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=ON + $ cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local/llvm-or1k -DLLVM_TARGETS_TO_BUILD="OR1K;X86" -DCMAKE_BUILD_TYPE=Debug $ make -j4 $ sudo make install From 66940ea8154ab180c3792e1a4723b126b092b4f5 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Wed, 15 Jul 2015 20:54:55 +0200 Subject: [PATCH 200/227] rtio: disable NOP suppression after reset and underflow --- artiq/gateware/rtio/core.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/artiq/gateware/rtio/core.py b/artiq/gateware/rtio/core.py index ffe3a978f..b4fedcf88 100644 --- a/artiq/gateware/rtio/core.py +++ b/artiq/gateware/rtio/core.py @@ -122,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( - optree("&", - [getattr(self.ev, a) == getattr(buf, a) - for a in ("data", "address") - if hasattr(self.ev, a)], - default=0)) + # 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)), + # 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 From 78ee4bdb9960809929373bfcddb59eddee16e4fd Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Thu, 16 Jul 2015 19:56:46 +0200 Subject: [PATCH 201/227] pyqtgraph: use more up to date revision a6d5e28 on develop branch --- conda/pyqtgraph/meta.yaml | 7 +++---- setup.py | 3 +++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/conda/pyqtgraph/meta.yaml b/conda/pyqtgraph/meta.yaml index 4f9939dfe..9be6a6dda 100644 --- a/conda/pyqtgraph/meta.yaml +++ b/conda/pyqtgraph/meta.yaml @@ -1,11 +1,10 @@ package: name: pyqtgraph - version: "0.9.10" + version: 0.9.10~a6d5e28 source: - fn: pyqtgraph-0.9.10.tar.gz - url: https://pypi.python.org/packages/source/p/pyqtgraph/pyqtgraph-0.9.10.tar.gz - md5: bd84bf7537c43cf38db81cc1ad4f743a + git_url: https://github.com/pyqtgraph/pyqtgraph.git + git_rev: a6d5e28 requirements: build: diff --git a/setup.py b/setup.py index 8192d6de9..8a8e3f476 100755 --- a/setup.py +++ b/setup.py @@ -42,6 +42,9 @@ setup( license="BSD", install_requires=requirements, extras_require={}, + dependency_links=[ + "git+https://github.com/pyqtgraph/pyqtgraph.git@a6d5e28#egg=pyqtgraph" + ], packages=find_packages(), namespace_packages=[], test_suite="artiq.test", From a83473a19a963eb993d36a72ec88d25a39ec2fdc Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 16 Jul 2015 20:52:27 +0200 Subject: [PATCH 202/227] sync_struct: clarify notify_cb doc --- artiq/protocols/sync_struct.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/protocols/sync_struct.py b/artiq/protocols/sync_struct.py index 665edfc91..dfd3a10b5 100644 --- a/artiq/protocols/sync_struct.py +++ b/artiq/protocols/sync_struct.py @@ -53,8 +53,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 From 9649e1837a85030ea5189b04a1433f4f451a9233 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 16 Jul 2015 20:52:53 +0200 Subject: [PATCH 203/227] gui: basic plotting --- artiq/frontend/artiq_gui.py | 4 +- artiq/gui/displays.py | 130 ++++++++++++++++++++++++++++++++++++ artiq/gui/results.py | 59 ++++++++++++++-- 3 files changed, 185 insertions(+), 8 deletions(-) create mode 100644 artiq/gui/displays.py diff --git a/artiq/frontend/artiq_gui.py b/artiq/frontend/artiq_gui.py index 9169fb531..2db26042b 100755 --- a/artiq/frontend/artiq_gui.py +++ b/artiq/frontend/artiq_gui.py @@ -3,12 +3,12 @@ import argparse import asyncio import atexit +import os # Quamash must be imported first so that pyqtgraph picks up the Qt binding # it has chosen. from quamash import QEventLoop, QtGui from pyqtgraph import dockarea -import os from artiq.protocols.file_db import FlatFileDB from artiq.protocols.pc_rpc import AsyncioClient @@ -71,7 +71,7 @@ def main(): args.server, args.port_notify)) atexit.register(lambda: loop.run_until_complete(d_explorer.sub_close())) - d_results = ResultsDock() + 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())) diff --git a/artiq/gui/displays.py b/artiq/gui/displays.py new file mode 100644 index 000000000..a29e42526 --- /dev/null +++ b/artiq/gui/displays.py @@ -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 = 0.0 + 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)) +]) diff --git a/artiq/gui/results.py b/artiq/gui/results.py index 316fd6cac..40522d719 100644 --- a/artiq/gui/results.py +++ b/artiq/gui/results.py @@ -1,4 +1,6 @@ import asyncio +from collections import OrderedDict +from functools import partial from quamash import QtGui, QtCore from pyqtgraph import dockarea @@ -6,6 +8,7 @@ from pyqtgraph import LayoutWidget from artiq.protocols.sync_struct import Subscriber from artiq.gui.tools import DictSyncModel +from artiq.gui.displays import * def _fmt_type(v): @@ -34,8 +37,10 @@ class ResultsModel(DictSyncModel): class ResultsDock(dockarea.Dock): - def __init__(self): + 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) @@ -49,13 +54,17 @@ class ResultsDock(dockarea.Dock): display_grid = QtGui.QGridLayout() add_display_box.setLayout(display_grid) - for n, name in enumerate(["Number", "XY", "Histogram"]): + 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.subscriber = Subscriber("rt_results", self.init_results_model, + self.on_mod) yield from self.subscriber.connect(host, port) @asyncio.coroutine @@ -63,6 +72,44 @@ class ResultsDock(dockarea.Dock): yield from self.subscriber.close() def init_results_model(self, init): - table_model = ResultsModel(self.table, init) - self.table.setModel(table_model) - return table_model + 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) From 39c6bc940cc5dc388d25031c8b0653983db1765d Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 17 Jul 2015 10:49:55 +0200 Subject: [PATCH 204/227] coreconfig: use new database API (closes #75) --- artiq/frontend/artiq_coreconfig.py | 43 ++++++++++++++++-------------- artiq/master/worker_db.py | 4 +-- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/artiq/frontend/artiq_coreconfig.py b/artiq/frontend/artiq_coreconfig.py index 9eb39d7fc..301657117 100755 --- a/artiq/frontend/artiq_coreconfig.py +++ b/artiq/frontend/artiq_coreconfig.py @@ -2,7 +2,7 @@ import argparse -from artiq.master.worker_db import create_device +from artiq.master.worker_db import DeviceManager from artiq.protocols.file_db import FlatFileDB @@ -44,26 +44,29 @@ def get_argparser(): def main(): args = get_argparser().parse_args() - ddb = FlatFileDB(args.ddb) - comm = create_device(ddb.request("comm"), None) + 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()) + 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() diff --git a/artiq/master/worker_db.py b/artiq/master/worker_db.py index bd6d008b3..0da07dcf7 100644 --- a/artiq/master/worker_db.py +++ b/artiq/master/worker_db.py @@ -103,7 +103,7 @@ class ResultDB: result_dict_to_hdf5(f, self.nrt) -def create_device(desc, dmgr): +def _create_device(desc, dmgr): ty = desc["type"] if ty == "local": module = importlib.import_module(desc["module"]) @@ -139,7 +139,7 @@ class DeviceManager: while isinstance(desc, str): # alias desc = self.ddb.get(desc) - dev = create_device(desc, self) + dev = _create_device(desc, self) self.active_devices[name] = dev return dev From 4907991ea39ccb79a76129bc1571879a64dce1b8 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 17 Jul 2015 18:53:43 +0200 Subject: [PATCH 205/227] sync_struct: docstring cleanup --- artiq/protocols/sync_struct.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/artiq/protocols/sync_struct.py b/artiq/protocols/sync_struct.py index dfd3a10b5..e8fd2c69d 100644 --- a/artiq/protocols/sync_struct.py +++ b/artiq/protocols/sync_struct.py @@ -8,7 +8,6 @@ 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 @@ -23,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"] @@ -134,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 @@ -203,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) From 6b36d93cc2c1c687548e5605b66835c61ac45191 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 17 Jul 2015 18:55:48 +0200 Subject: [PATCH 206/227] master,client: support repository rescan --- artiq/frontend/artiq_client.py | 8 ++++++++ artiq/frontend/artiq_master.py | 7 ++++--- artiq/master/repository.py | 8 ++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/artiq/frontend/artiq_client.py b/artiq/frontend/artiq_client.py index 8305bc787..e7035381a 100755 --- a/artiq/frontend/artiq_client.py +++ b/artiq/frontend/artiq_client.py @@ -82,6 +82,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 @@ -134,6 +137,10 @@ 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: @@ -228,6 +235,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: diff --git a/artiq/frontend/artiq_master.py b/artiq/frontend/artiq_master.py index bf3b6d0c5..196d4643c 100755 --- a/artiq/frontend/artiq_master.py +++ b/artiq/frontend/artiq_master.py @@ -56,18 +56,19 @@ def main(): scheduler.start() atexit.register(lambda: loop.run_until_complete(scheduler.stop())) + repository = Repository() + repository.scan_async() + server_control = Server({ "master_ddb": ddb, "master_pdb": pdb, "master_schedule": scheduler, + "master_repository": repository }) loop.run_until_complete(server_control.start( args.bind, args.port_control)) atexit.register(lambda: loop.run_until_complete(server_control.stop())) - repository = Repository() - loop.run_until_complete(repository.scan()) - server_notify = Publisher({ "schedule": scheduler.notifier, "devices": ddb.data, diff --git a/artiq/master/repository.py b/artiq/master/repository.py index c4c098f2b..3e5e2a783 100644 --- a/artiq/master/repository.py +++ b/artiq/master/repository.py @@ -54,8 +54,16 @@ def _sync_explist(target, source): class Repository: def __init__(self): self.explist = Notifier(dict()) + self._scanning = False @asyncio.coroutine def scan(self): + if self._scanning: + return + self._scanning = True new_explist = yield from _scan_experiments() _sync_explist(self.explist, new_explist) + self._scanning = False + + def scan_async(self): + asyncio.async(self.scan()) From 2576036ba10310dd43714333eba59a4eb6b67e97 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 17 Jul 2015 19:32:38 +0200 Subject: [PATCH 207/227] language,worker: preserve order of arguments --- artiq/language/environment.py | 3 ++- artiq/master/worker_impl.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/artiq/language/environment.py b/artiq/language/environment.py index 9d43d8da6..2f1f10690 100644 --- a/artiq/language/environment.py +++ b/artiq/language/environment.py @@ -1,3 +1,4 @@ +from collections import OrderedDict from inspect import isclass @@ -41,7 +42,7 @@ class HasEnvironment: parameters, results, arguments).""" def __init__(self, dmgr=None, pdb=None, rdb=None, *, param_override=dict(), default_arg_none=False, **kwargs): - self.requested_args = dict() + self.requested_args = OrderedDict() self.__dmgr = dmgr self.__pdb = pdb diff --git a/artiq/master/worker_impl.py b/artiq/master/worker_impl.py index 23de27b22..b733a054d 100644 --- a/artiq/master/worker_impl.py +++ b/artiq/master/worker_impl.py @@ -127,8 +127,8 @@ def examine(dmgr, pdb, rdb, file): 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()} + arguments = [(k, v.describe()) + for k, v in exp_inst.requested_args.items()] register_experiment(class_name, name, arguments) From dd7920f0c3ea6b60552b5bd333e9e5365a17d251 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 17 Jul 2015 21:28:46 +0200 Subject: [PATCH 208/227] gui: basic argument support --- artiq/frontend/artiq_gui.py | 2 +- artiq/gui/explorer.py | 83 +++++++++++++++++++++++++++++++++---- 2 files changed, 76 insertions(+), 9 deletions(-) diff --git a/artiq/frontend/artiq_gui.py b/artiq/frontend/artiq_gui.py index 2db26042b..645643fb5 100755 --- a/artiq/frontend/artiq_gui.py +++ b/artiq/frontend/artiq_gui.py @@ -66,7 +66,7 @@ def main(): win.resize(1400, 800) win.setWindowTitle("ARTIQ") - d_explorer = ExplorerDock(status_bar, schedule_ctl) + 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())) diff --git a/artiq/gui/explorer.py b/artiq/gui/explorer.py index 4b4529cfd..e4d594969 100644 --- a/artiq/gui/explorer.py +++ b/artiq/gui/explorer.py @@ -1,10 +1,12 @@ 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 @@ -21,20 +23,68 @@ class _ExplistModel(DictSyncModel): return k +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()) + + +_procty_to_entry = { + "FreeValue": _FreeValueEntry +} + + +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, status_bar, schedule_ctl): + 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() @@ -63,8 +113,22 @@ class ExplorerDock(dockarea.Dock): 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) @@ -106,7 +170,10 @@ class ExplorerDock(dockarea.Dock): 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["class_name"], - dict(), self.priority.value(), due_date, - self.flush.isChecked())) + arguments, self.priority.value(), + due_date, self.flush.isChecked())) From 657f198cdc93dd8121aff5e5dfb1448a64795bd6 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 18 Jul 2015 10:26:47 +0200 Subject: [PATCH 209/227] gui: display '---' on failed number result --- artiq/gui/displays.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/gui/displays.py b/artiq/gui/displays.py index a29e42526..ca2407871 100644 --- a/artiq/gui/displays.py +++ b/artiq/gui/displays.py @@ -64,7 +64,7 @@ class NumberDisplay(dockarea.Dock): try: n = float(data[result]) except: - n = 0.0 + n = "---" self.number.display(n) From 5f95a302e6adb868a504fd5b01cd26de05cce2e7 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 18 Jul 2015 16:24:37 +0200 Subject: [PATCH 210/227] environment: improve docstrings --- artiq/language/environment.py | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/artiq/language/environment.py b/artiq/language/environment.py index 2f1f10690..613a42f3c 100644 --- a/artiq/language/environment.py +++ b/artiq/language/environment.py @@ -60,12 +60,25 @@ class HasEnvironment: 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()") @@ -85,17 +98,23 @@ class HasEnvironment: 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: @@ -109,14 +128,24 @@ class HasEnvironment: 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: @@ -132,9 +161,17 @@ class HasEnvironment: 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) From 9e29a4650ac073c4214465a9f8f5b3ca70cfa094 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 18 Jul 2015 16:25:08 +0200 Subject: [PATCH 211/227] environment,gui: more argument types --- artiq/gui/explorer.py | 61 ++++++++++++++++- artiq/language/environment.py | 69 +++++++++++++++++++- examples/master/repository/arguments_demo.py | 18 +++++ 3 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 examples/master/repository/arguments_demo.py diff --git a/artiq/gui/explorer.py b/artiq/gui/explorer.py index e4d594969..ccf1bea46 100644 --- a/artiq/gui/explorer.py +++ b/artiq/gui/explorer.py @@ -33,8 +33,67 @@ class _FreeValueEntry(QtGui.QLineEdit): 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 + "FreeValue": _FreeValueEntry, + "BooleanValue": _BooleanEntry, + "EnumerationValue": _EnumerationEntry, + "NumberValue": _NumberEntry, + "StringValue": _StringEntry } diff --git a/artiq/language/environment.py b/artiq/language/environment.py index 613a42f3c..f6da67803 100644 --- a/artiq/language/environment.py +++ b/artiq/language/environment.py @@ -2,7 +2,10 @@ from collections import OrderedDict from inspect import isclass -__all__ = ["NoDefault", "FreeValue", "HasEnvironment", +__all__ = ["NoDefault", + "FreeValue", "BooleanValue", "EnumerationValue", + "NumberValue", "StringValue", + "HasEnvironment", "Experiment", "EnvExperiment", "is_experiment"] @@ -17,7 +20,7 @@ class DefaultMissing(Exception): pass -class FreeValue: +class _SimpleArgProcessor: def __init__(self, default=NoDefault): if default is not NoDefault: self.default_value = default @@ -31,12 +34,72 @@ class FreeValue: return x def describe(self): - d = {"ty": "FreeValue"} + 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).""" diff --git a/examples/master/repository/arguments_demo.py b/examples/master/repository/arguments_demo.py new file mode 100644 index 000000000..78b8521b3 --- /dev/null +++ b/examples/master/repository/arguments_demo.py @@ -0,0 +1,18 @@ +from artiq import * + + +class ArgumentsDemo(EnvExperiment): + def build(self): + self.attr_argument("free_value", FreeValue(None)) + self.attr_argument("boolean", BooleanValue(True)) + self.attr_argument("enum", EnumerationValue( + ["foo", "bar", "quux"], "foo")) + self.attr_argument("number", NumberValue(42, unit="s", step=0.1)) + self.attr_argument("string", StringValue("Hello World")) + + def run(self): + print(self.free_value) + print(self.boolean) + print(self.enum) + print(self.number) + print(self.string) From deaa492566d09470ceb2b69c9a46b7e02a6a53a5 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 18 Jul 2015 19:26:41 +0200 Subject: [PATCH 212/227] language: add scan iterators --- artiq/language/scan.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 artiq/language/scan.py diff --git a/artiq/language/scan.py b/artiq/language/scan.py new file mode 100644 index 000000000..fc20d98fc --- /dev/null +++ b/artiq/language/scan.py @@ -0,0 +1,34 @@ +from random import Random + + +class LinearScan: + def __init__(self, min, max, npoints): + self.min = min + self.max = max + self.npoints = npoints + + def _gen(self): + r = self.max - self.min + d = self.npoints - 1 + for i in range(self.npoints): + yield r*i/d + self.min + + def __iter__(self): + return self._gen() + + +class RandomScan: + def __init__(self, min, max, npoints, seed=0): + self.min = min + self.max = max + self.npoints = npoints + self.seed = 0 + + def _gen(self): + prng = Random(self.seed) + r = self.max - self.min + for i in range(self.npoints): + yield prng.random()*r + self.min + + def __iter__(self): + return self._gen() From 937ca853aabe956d748662be24979ffdfadfe740 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sun, 19 Jul 2015 11:36:52 +0200 Subject: [PATCH 213/227] language/scan: fix random scan, add explicit scan, specify what runs on host/device --- artiq/language/scan.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/artiq/language/scan.py b/artiq/language/scan.py index fc20d98fc..522321a16 100644 --- a/artiq/language/scan.py +++ b/artiq/language/scan.py @@ -1,4 +1,6 @@ -from random import Random +from random import Random, shuffle + +from artiq.language.core import * class LinearScan: @@ -7,28 +9,32 @@ class LinearScan: 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() class RandomScan: def __init__(self, min, max, npoints, seed=0): - self.min = min - self.max = max - self.npoints = npoints - self.seed = 0 - - def _gen(self): - prng = Random(self.seed) - r = self.max - self.min - for i in range(self.npoints): - yield prng.random()*r + self.min + self.sequence = list(LinearScan(min, max, npoints)) + shuffle(self.sequence, Random(seed).random) + @portable def __iter__(self): - return self._gen() + return iter(self.sequence) + + +class ExplicitScan: + def __init__(self, sequence): + self.sequence = sequence + + @portable + def __iter__(self): + return iter(self.sequence) From bb05ed268e546e24d021dab689145182b3677f8d Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sun, 19 Jul 2015 16:38:33 +0200 Subject: [PATCH 214/227] language/scan: add argument processor --- artiq/language/__init__.py | 4 +- artiq/language/scan.py | 75 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/artiq/language/__init__.py b/artiq/language/__init__.py index 7dfe5dba0..6497cda47 100644 --- a/artiq/language/__init__.py +++ b/artiq/language/__init__.py @@ -1,10 +1,12 @@ -from artiq.language import core, environment, units +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__) diff --git a/artiq/language/scan.py b/artiq/language/scan.py index 522321a16..c1a0182f8 100644 --- a/artiq/language/scan.py +++ b/artiq/language/scan.py @@ -1,6 +1,27 @@ 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: @@ -20,6 +41,10 @@ class LinearScan: 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): @@ -30,6 +55,11 @@ class RandomScan: 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): @@ -38,3 +68,48 @@ class ExplicitScan: @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 From 97ebdee2c6072289e4ed269ac3681978721dad41 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sun, 19 Jul 2015 18:08:24 +0200 Subject: [PATCH 215/227] gui: add pyqtgraph patch to prevent closing of detached nonclosable docks (move them to main window instead) --- ...graph-do-not-close-nonclosable-docks.patch | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 misc/pyqtgraph-do-not-close-nonclosable-docks.patch diff --git a/misc/pyqtgraph-do-not-close-nonclosable-docks.patch b/misc/pyqtgraph-do-not-close-nonclosable-docks.patch new file mode 100644 index 000000000..307b664bc --- /dev/null +++ b/misc/pyqtgraph-do-not-close-nonclosable-docks.patch @@ -0,0 +1,28 @@ +diff --git a/pyqtgraph/dockarea/Dock.py b/pyqtgraph/dockarea/Dock.py +index 4493d07..a05e685 100644 +--- a/pyqtgraph/dockarea/Dock.py ++++ b/pyqtgraph/dockarea/Dock.py +@@ -18,6 +18,7 @@ class Dock(QtGui.QWidget, DockDrop): + self.label = DockLabel(name, self, closable) + if closable: + self.label.sigCloseClicked.connect(self.close) ++ self.closable = closable + self.labelHidden = False + self.moveLabel = True ## If false, the dock is no longer allowed to move the label. + self.autoOrient = autoOrientation +diff --git a/pyqtgraph/dockarea/DockArea.py b/pyqtgraph/dockarea/DockArea.py +index ffe75b6..b054b24 100644 +--- a/pyqtgraph/dockarea/DockArea.py ++++ b/pyqtgraph/dockarea/DockArea.py +@@ -306,7 +306,10 @@ class DockArea(Container, QtGui.QWidget, DockDrop): + def clear(self): + docks = self.findAll()[1] + for dock in docks.values(): +- dock.close() ++ if dock.closable: ++ dock.close() ++ else: ++ self.home.moveDock(dock, "top", None) + + ## PySide bug: We need to explicitly redefine these methods + ## or else drag/drop events will not be delivered. From 1a0dc499ddc859b6eeb62cde8baf3e3ed708e154 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sun, 19 Jul 2015 18:27:41 +0200 Subject: [PATCH 216/227] gui: exit when main window is closed --- artiq/frontend/artiq_gui.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/artiq/frontend/artiq_gui.py b/artiq/frontend/artiq_gui.py index 645643fb5..05b2e7384 100755 --- a/artiq/frontend/artiq_gui.py +++ b/artiq/frontend/artiq_gui.py @@ -41,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() @@ -56,15 +67,12 @@ def main(): args.server, args.port_control, "master_schedule")) atexit.register(lambda: schedule_ctl.close_rpc()) - win = QtGui.QMainWindow() - win.setWindowIcon(QtGui.QIcon(os.path.join(data_dir, "icon.png"))) + 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(win, status_bar, schedule_ctl) loop.run_until_complete(d_explorer.sub_connect( @@ -101,7 +109,7 @@ def main(): area.addDock(d_schedule, "above", d_log) win.show() - loop.run_forever() + loop.run_until_complete(win.exit_request.wait()) if __name__ == "__main__": main() From 47191eda91a98cd15de6a43855f63992f2e99e2c Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Sun, 19 Jul 2015 21:36:51 -0600 Subject: [PATCH 217/227] dds monitor: relax timing (for pipistrello) --- artiq/gateware/rtio/phy/dds.py | 43 +++++++++++++++++----------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/artiq/gateware/rtio/phy/dds.py b/artiq/gateware/rtio/phy/dds.py index e9685223d..8568c57e1 100644 --- a/artiq/gateware/rtio/phy/dds.py +++ b/artiq/gateware/rtio/phy/dds.py @@ -14,39 +14,40 @@ class _AD9xxx(Module): # # # + # 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(self.rtlink.o.stb & - (self.rtlink.o.address == 2**flen(pads.a)+1), - current_channel.eq(self.rtlink.o.data)) + 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: - for i in range(4): - self.sync.rio += \ - If(self.rtlink.o.stb & \ - (self.rtlink.o.address == ftw_base+i) & \ - (current_channel == c), - ftw[i*8:(i+1)*8].eq(self.rtlink.o.data) - ) + 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: - for i in range(2): - self.sync.rio += \ - If(self.rtlink.o.stb & \ - (self.rtlink.o.address == ftw_base+2*i) & \ - (current_channel == c), - ftw[i*16:(i+1)*16].eq(self.rtlink.o.data) - ) + 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 - for c, (probe, ftw) in enumerate(zip(self.probes, ftws)): - fud = self.rtlink.o.stb & \ - (self.rtlink.o.address == 2**flen(pads.a)) - self.sync.rio += If(fud & (current_channel == c), probe.eq(ftw)) + 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): From 179ca36d09cd4724c59b4dab9cff63cd454c5d17 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Tue, 21 Jul 2015 17:23:32 +0200 Subject: [PATCH 218/227] gui: basic scan support --- artiq/frontend/artiq_gui.py | 11 +- artiq/gui/explorer.py | 4 +- artiq/gui/scan.py | 137 +++++++++++++++++++ artiq/language/scan.py | 3 +- examples/master/repository/arguments_demo.py | 3 + 5 files changed, 150 insertions(+), 8 deletions(-) create mode 100644 artiq/gui/scan.py diff --git a/artiq/frontend/artiq_gui.py b/artiq/frontend/artiq_gui.py index 05b2e7384..3a2cd17b8 100755 --- a/artiq/frontend/artiq_gui.py +++ b/artiq/frontend/artiq_gui.py @@ -87,17 +87,18 @@ def main(): 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())) - 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_explorer, "above", d_results) 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())) + 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) loop.run_until_complete(d_schedule.sub_connect( args.server, args.port_notify)) diff --git a/artiq/gui/explorer.py b/artiq/gui/explorer.py index ccf1bea46..1d82b2a99 100644 --- a/artiq/gui/explorer.py +++ b/artiq/gui/explorer.py @@ -8,6 +8,7 @@ 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): @@ -93,7 +94,8 @@ _procty_to_entry = { "BooleanValue": _BooleanEntry, "EnumerationValue": _EnumerationEntry, "NumberValue": _NumberEntry, - "StringValue": _StringEntry + "StringValue": _StringEntry, + "Scannable": ScanController } diff --git a/artiq/gui/scan.py b/artiq/gui/scan.py new file mode 100644 index 000000000..1ec7c2dcf --- /dev/null +++ b/artiq/gui/scan.py @@ -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} diff --git a/artiq/language/scan.py b/artiq/language/scan.py index c1a0182f8..90867737d 100644 --- a/artiq/language/scan.py +++ b/artiq/language/scan.py @@ -60,7 +60,6 @@ class RandomScan: "min": self.min, "max": self.max, "npoints": self.npoints} - class ExplicitScan: def __init__(self, sequence): self.sequence = sequence @@ -109,7 +108,7 @@ class Scannable: d["global_min"] = self.global_min d["global_max"] = self.global_max d["global_step"] = self.global_step - d["unit"]= self.unit + d["unit"] = self.unit if hasattr(self, "default_value"): d["default"] = self.default_value.describe() return d diff --git a/examples/master/repository/arguments_demo.py b/examples/master/repository/arguments_demo.py index 78b8521b3..97393df19 100644 --- a/examples/master/repository/arguments_demo.py +++ b/examples/master/repository/arguments_demo.py @@ -9,6 +9,7 @@ class ArgumentsDemo(EnvExperiment): ["foo", "bar", "quux"], "foo")) self.attr_argument("number", NumberValue(42, unit="s", step=0.1)) self.attr_argument("string", StringValue("Hello World")) + self.attr_argument("scan", Scannable(global_max=400, default=NoScan(325))) def run(self): print(self.free_value) @@ -16,3 +17,5 @@ class ArgumentsDemo(EnvExperiment): print(self.enum) print(self.number) print(self.string) + for i in self.scan: + print(i) From e247fb541594e52b4f2eb0dec000a72bafd63254 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Wed, 22 Jul 2015 05:13:46 +0800 Subject: [PATCH 219/227] gui/tools: add ListSyncModel --- artiq/gui/tools.py | 60 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/artiq/gui/tools.py b/artiq/gui/tools.py index c8308718f..ece8125b8 100644 --- a/artiq/gui/tools.py +++ b/artiq/gui/tools.py @@ -1,7 +1,7 @@ from quamash import QtCore -class _DictSyncSubstruct: +class _SyncSubstruct: def __init__(self, update_cb, ref): self.update_cb = update_cb self.ref = ref @@ -27,7 +27,7 @@ class _DictSyncSubstruct: self.update_cb() def __getitem__(self, key): - return _DictSyncSubstruct(self.update_cb, self.ref[key]) + return _SyncSubstruct(self.update_cb, self.ref[key]) class DictSyncModel(QtCore.QAbstractTableModel): @@ -100,13 +100,63 @@ class DictSyncModel(QtCore.QAbstractTableModel): del self.backing_store[k] self.endRemoveRows() - def __getitem__(self, key): + def __getitem__(self, k): def update(): - self[key] = self.backing_store[key] - return _DictSyncSubstruct(update, self.backing_store[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 From 8402f1cdcd4cbabce31f224635052a0ac901d50d Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Wed, 22 Jul 2015 05:13:50 +0800 Subject: [PATCH 220/227] master,gui: basic log support --- artiq/frontend/artiq_gui.py | 3 +++ artiq/frontend/artiq_master.py | 26 ++++++++++++++++++++------ artiq/gui/log.py | 33 +++++++++++++++++++++++++++++++++ artiq/master/worker.py | 3 +++ artiq/master/worker_impl.py | 21 ++++++++++++++++++++- 5 files changed, 79 insertions(+), 7 deletions(-) diff --git a/artiq/frontend/artiq_gui.py b/artiq/frontend/artiq_gui.py index 3a2cd17b8..f1f63bab4 100755 --- a/artiq/frontend/artiq_gui.py +++ b/artiq/frontend/artiq_gui.py @@ -105,6 +105,9 @@ def main(): 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) diff --git a/artiq/frontend/artiq_master.py b/artiq/frontend/artiq_master.py index 196d4643c..6f7840cda 100755 --- a/artiq/frontend/artiq_master.py +++ b/artiq/frontend/artiq_master.py @@ -30,14 +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") - rtr = Notifier(dict()) - if os.name == "nt": loop = asyncio.ProactorEventLoop() asyncio.set_event_loop(loop) @@ -45,11 +52,17 @@ 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 = { "get_device": ddb.get, "get_parameter": pdb.get, "set_parameter": pdb.set, "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 @@ -74,7 +87,8 @@ def main(): "devices": ddb.data, "parameters": pdb.data, "rt_results": rtr, - "explist": repository.explist + "explist": repository.explist, + "log": log.data }) loop.run_until_complete(server_notify.start( args.bind, args.port_notify)) diff --git a/artiq/gui/log.py b/artiq/gui/log.py index ed5c9abb6..77981a37f 100644 --- a/artiq/gui/log.py +++ b/artiq/gui/log.py @@ -1,7 +1,40 @@ +import asyncio + from quamash import QtGui 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.addWidget(self.log) + + @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 init_log_model(self, init): + table_model = _LogModel(self.log, init) + self.log.setModel(table_model) + return table_model diff --git a/artiq/master/worker.py b/artiq/master/worker.py index 198c97afc..919906ca2 100644 --- a/artiq/master/worker.py +++ b/artiq/master/worker.py @@ -4,6 +4,7 @@ import logging import subprocess import traceback import time +from functools import partial from artiq.protocols import pyon from artiq.tools import (asyncio_process_wait_timeout, asyncio_process_wait, @@ -174,6 +175,8 @@ class Worker: 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} diff --git a/artiq/master/worker_impl.py b/artiq/master/worker_impl.py index b733a054d..77ff1349c 100644 --- a/artiq/master/worker_impl.py +++ b/artiq/master/worker_impl.py @@ -44,6 +44,25 @@ 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: get = make_parent_action("get_device", "name", KeyError) @@ -133,7 +152,7 @@ def examine(dmgr, pdb, rdb, file): def main(): - sys.stdout = sys.stderr + sys.stdout = sys.stderr = LogForwarder() start_time = None rid = None From bd2bd68a542d28e86e9a00c74c81e6f5d7a050ba Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Wed, 22 Jul 2015 05:47:14 +0800 Subject: [PATCH 221/227] gui,client: do now show arguments --- artiq/frontend/artiq_client.py | 4 +--- artiq/gui/schedule.py | 5 +---- artiq/protocols/pc_rpc.py | 13 +++++++++++-- artiq/tools.py | 10 ---------- 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/artiq/frontend/artiq_client.py b/artiq/frontend/artiq_client.py index e7035381a..8277ba318 100755 --- a/artiq/frontend/artiq_client.py +++ b/artiq/frontend/artiq_client.py @@ -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(): @@ -149,7 +148,7 @@ def _show_schedule(schedule): x[1]["due_date"] or 0, x[0])) table = PrettyTable(["RID", "Pipeline", " Status ", "Prio", - "Due date", "File", "Class name", "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: @@ -162,7 +161,6 @@ def _show_schedule(schedule): row.append("") else: row.append(v["expid"]["class_name"]) - row.append(format_arguments(v["expid"]["arguments"])) table.add_row(row) print(table) else: diff --git a/artiq/gui/schedule.py b/artiq/gui/schedule.py index cd9c8d402..363cc6570 100644 --- a/artiq/gui/schedule.py +++ b/artiq/gui/schedule.py @@ -6,14 +6,13 @@ 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", "Class name", "Arguments"], + "File", "Class name"], parent, init) def sort_key(self, k, v): @@ -42,8 +41,6 @@ class _ScheduleModel(DictSyncModel): return "" else: return v["expid"]["class_name"] - elif column == 7: - return format_arguments(v["expid"]["arguments"]) else: raise ValueError diff --git a/artiq/protocols/pc_rpc.py b/artiq/protocols/pc_rpc.py index 539e9f7f3..a16c3dfd9 100644 --- a/artiq/protocols/pc_rpc.py +++ b/artiq/protocols/pc_rpc.py @@ -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__) @@ -375,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 @@ -383,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 diff --git a/artiq/tools.py b/artiq/tools.py index 2f7870eab..767e858dd 100644 --- a/artiq/tools.py +++ b/artiq/tools.py @@ -19,16 +19,6 @@ def parse_arguments(arguments): 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) From 073e09ed364c6e790125fff6a92bc217f4ce79be Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Wed, 22 Jul 2015 06:01:09 +0800 Subject: [PATCH 222/227] gui: common format for results/params --- artiq/gui/parameters.py | 4 ++-- artiq/gui/results.py | 14 +++----------- artiq/gui/tools.py | 11 +++++++++++ 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/artiq/gui/parameters.py b/artiq/gui/parameters.py index 86495b6bf..e608beae2 100644 --- a/artiq/gui/parameters.py +++ b/artiq/gui/parameters.py @@ -5,7 +5,7 @@ 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): @@ -20,7 +20,7 @@ class ParametersModel(DictSyncModel): if column == 0: return k elif column == 1: - return str(v) + return short_format(v) else: raise ValueError diff --git a/artiq/gui/results.py b/artiq/gui/results.py index 40522d719..900e16663 100644 --- a/artiq/gui/results.py +++ b/artiq/gui/results.py @@ -7,21 +7,13 @@ 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 from artiq.gui.displays import * -def _fmt_type(v): - t = type(v) - r = t.__name__ - if t is list or t is dict or t is set: - r += " ({})".format(len(v)) - return r - - class ResultsModel(DictSyncModel): def __init__(self, parent, init): - DictSyncModel.__init__(self, ["Result", "Type"], + DictSyncModel.__init__(self, ["Result", "Value"], parent, init) def sort_key(self, k, v): @@ -31,7 +23,7 @@ class ResultsModel(DictSyncModel): if column == 0: return k elif column == 1: - return _fmt_type(v) + return short_format(v) else: raise ValueError diff --git a/artiq/gui/tools.py b/artiq/gui/tools.py index ece8125b8..cab4e256d 100644 --- a/artiq/gui/tools.py +++ b/artiq/gui/tools.py @@ -1,6 +1,17 @@ from quamash import QtCore +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 From aa2acb9137ab192b1ce10ce897987d727158a912 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 23 Jul 2015 22:36:52 +0800 Subject: [PATCH 223/227] gui: auto resize of table columns --- artiq/gui/log.py | 2 ++ artiq/gui/parameters.py | 2 ++ artiq/gui/results.py | 2 ++ artiq/gui/schedule.py | 2 ++ 4 files changed, 8 insertions(+) diff --git a/artiq/gui/log.py b/artiq/gui/log.py index 77981a37f..0c6c7cb22 100644 --- a/artiq/gui/log.py +++ b/artiq/gui/log.py @@ -23,6 +23,8 @@ class LogDock(dockarea.Dock): self.log = QtGui.QTableView() self.log.setSelectionMode(QtGui.QAbstractItemView.NoSelection) + self.log.horizontalHeader().setResizeMode( + QtGui.QHeaderView.ResizeToContents) self.addWidget(self.log) @asyncio.coroutine diff --git a/artiq/gui/parameters.py b/artiq/gui/parameters.py index e608beae2..5fbac5866 100644 --- a/artiq/gui/parameters.py +++ b/artiq/gui/parameters.py @@ -39,6 +39,8 @@ class ParametersDock(dockarea.Dock): self.table = QtGui.QTableView() self.table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) + self.table.horizontalHeader().setResizeMode( + QtGui.QHeaderView.ResizeToContents) grid.addWidget(self.table, 1, 0) def search_parameters(self): diff --git a/artiq/gui/results.py b/artiq/gui/results.py index 900e16663..964856744 100644 --- a/artiq/gui/results.py +++ b/artiq/gui/results.py @@ -39,6 +39,8 @@ class ResultsDock(dockarea.Dock): 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") diff --git a/artiq/gui/schedule.py b/artiq/gui/schedule.py index 363cc6570..d023c880b 100644 --- a/artiq/gui/schedule.py +++ b/artiq/gui/schedule.py @@ -54,6 +54,8 @@ 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) From 3a06e22b67fa81e5ecbbbd86db1b31c0c40d8c2c Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 23 Jul 2015 23:06:15 +0800 Subject: [PATCH 224/227] master: handle logging while scanning repository --- artiq/frontend/artiq_master.py | 2 +- artiq/master/repository.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/artiq/frontend/artiq_master.py b/artiq/frontend/artiq_master.py index 6f7840cda..01c3fb081 100755 --- a/artiq/frontend/artiq_master.py +++ b/artiq/frontend/artiq_master.py @@ -69,7 +69,7 @@ def main(): scheduler.start() atexit.register(lambda: loop.run_until_complete(scheduler.stop())) - repository = Repository() + repository = Repository(log.log) repository.scan_async() server_control = Server({ diff --git a/artiq/master/repository.py b/artiq/master/repository.py index 3e5e2a783..465ce6f85 100644 --- a/artiq/master/repository.py +++ b/artiq/master/repository.py @@ -10,13 +10,13 @@ logger = logging.getLogger(__name__) @asyncio.coroutine -def _scan_experiments(): +def _scan_experiments(log): r = dict() for f in os.listdir("repository"): if f.endswith(".py"): try: full_name = os.path.join("repository", f) - worker = Worker() + worker = Worker({"log": lambda message: log("scan", message)}) try: description = yield from worker.examine(full_name) finally: @@ -52,16 +52,17 @@ def _sync_explist(target, source): class Repository: - def __init__(self): + 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() + new_explist = yield from _scan_experiments(self.log_fn) _sync_explist(self.explist, new_explist) self._scanning = False From 1a4028ca925e2130b1f276e10562e25e5d02fdaf Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 23 Jul 2015 23:06:37 +0800 Subject: [PATCH 225/227] gui: better log lookandfeel --- artiq/gui/log.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/artiq/gui/log.py b/artiq/gui/log.py index 0c6c7cb22..7c396ddad 100644 --- a/artiq/gui/log.py +++ b/artiq/gui/log.py @@ -1,6 +1,6 @@ import asyncio -from quamash import QtGui +from quamash import QtGui, QtCore from pyqtgraph import dockarea from artiq.protocols.sync_struct import Subscriber @@ -25,6 +25,10 @@ class LogDock(dockarea.Dock): 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) @asyncio.coroutine From 5b1165f413cbb6697ec243bba21a61925f724974 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 24 Jul 2015 00:11:05 +0800 Subject: [PATCH 226/227] gui: log autoscroll --- artiq/gui/log.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/artiq/gui/log.py b/artiq/gui/log.py index 7c396ddad..7a0fb4894 100644 --- a/artiq/gui/log.py +++ b/artiq/gui/log.py @@ -30,6 +30,7 @@ class LogDock(dockarea.Dock): 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): @@ -40,7 +41,17 @@ class LogDock(dockarea.Dock): 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 From 6b0e120d75f25561e001b50107e3548d478f76da Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Thu, 23 Jul 2015 12:34:54 -0600 Subject: [PATCH 227/227] wavesynth/Synthesizer: allow empty data --- artiq/wavesynth/compute_samples.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/artiq/wavesynth/compute_samples.py b/artiq/wavesynth/compute_samples.py index 0658f8bde..476fb385a 100644 --- a/artiq/wavesynth/compute_samples.py +++ b/artiq/wavesynth/compute_samples.py @@ -9,6 +9,8 @@ class Spline: self.c = [0.0] def set_coefficients(self, c): + if not c: + c = [0.] self.c = copy(c) discrete_compensate(self.c) @@ -25,6 +27,8 @@ class SplinePhase: self.c0 = 0.0 def set_coefficients(self, c): + if not c: + c = [0.] self.c0 = c[0] c1p = c[1:] discrete_compensate(c1p)