diff --git a/MANIFEST.in b/MANIFEST.in index 27aa85a93..0c094f0eb 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,3 +5,4 @@ include versioneer.py include artiq/_version.py include artiq/coredevice/coredevice_generic.schema.json include artiq/compiler/kernel.ld +include artiq/afws.pem diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index fdb45ed7a..3cba6ae3e 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -23,7 +23,7 @@ Highlights: * On Kasli, the number of FIFO lanes in the scalable events dispatcher (SED) can now be configured in the JSON hardware description file. * ``artiq_ddb_template`` generates edge-counter keys that start with the key of the corresponding - TTL device (e.g. ``"ttl_0_counter"`` for the edge counter on TTL device``"ttl_0"``) + TTL device (e.g. ``ttl_0_counter`` for the edge counter on TTL device ``ttl_0``). * ``artiq_master`` now has an ``--experiment-subdir`` option to scan only a subdirectory of the repository when building the list of experiments. * The configuration entry ``rtio_clock`` supports multiple clocking settings, deprecating the usage diff --git a/artiq/afws.pem b/artiq/afws.pem new file mode 100644 index 000000000..cebce3e97 --- /dev/null +++ b/artiq/afws.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID0zCCArugAwIBAgIUPkNfEUx/uau3z8SD4mgMbCK/DEgwDQYJKoZIhvcNAQEL +BQAweTELMAkGA1UEBhMCSEsxEzARBgNVBAgMClNvbWUtU3RhdGUxFzAVBgNVBAoM +Dk0tTGFicyBMaW1pdGVkMRkwFwYDVQQDDBBuaXhibGQubS1sYWJzLmhrMSEwHwYJ +KoZIhvcNAQkBFhJoZWxwZGVza0BtLWxhYnMuaGswHhcNMjIwMjA2MTA1ODQ0WhcN +MjUwMjA1MTA1ODQ0WjB5MQswCQYDVQQGEwJISzETMBEGA1UECAwKU29tZS1TdGF0 +ZTEXMBUGA1UECgwOTS1MYWJzIExpbWl0ZWQxGTAXBgNVBAMMEG5peGJsZC5tLWxh +YnMuaGsxITAfBgkqhkiG9w0BCQEWEmhlbHBkZXNrQG0tbGFicy5oazCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAPWetZhoggPR2ae7waGzv1AQ8NQO3noW +8DofVjusNpX5i/YB0waAr1bm1tALLJoHV2r/gTxujlXCe/L/WG1DLseCf6NO9sHg +t0FLhDpF9kPMWBgauVVLepd2Y2yU1G8eFuEVGnsiQSu0IzsZP5FQBJSyxvxJ+V/L +EW9ox91VGOP9VZR9jqdlYjGhcwClHA/nHe0q1fZq42+9rG466I5yIlNSoa7ilhTU +2C2doxy6Sr6VJYnLEMQqoIF65t3MkKi9iaqN7az0OCrj6XR0P5iKBzUhIgMUd2qs +7Id0XUdbQvaoaRI67vhGkNr+f4rdAUNCDGcbbokuBnmE7/gva6BAABUCAwEAAaNT +MFEwHQYDVR0OBBYEFM2e2FmcytXhKyfC1KEjVJ2mPSy3MB8GA1UdIwQYMBaAFM2e +2FmcytXhKyfC1KEjVJ2mPSy3MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAKH0z5vlbfTghjYWwd2yEEFBbZx5XxaLHboFQpFpxu9sZoidVs047tco +MOr1py9juiNGGM8G35sw9306f+thDFwqlQfSExUwp5pRQNq+mxglMSF05HWDqBwb +wnItKi/WXpkMQXgpQJFVeflz4B4ZFNlH1UQl5bwacXOM9NM9zO7duCjVXmGE0yxi +VQyApfPQYu9whCSowDYYaA0toJeikMzGfWxhlAH79/2Qmit8KcSCbX1fK/QoRZLa +5NeUi/OlJbBpkgTrfzfMLphmsPWPAVMeUKzqd/vXfG6ZBOZZm6e6sl8RBycBezII +15WekikTE5+T54/E0xiu+zIW/Xhhk14= +-----END CERTIFICATE----- diff --git a/artiq/coredevice/adf5356.py b/artiq/coredevice/adf5356.py index f7020cd7b..2167ef9ad 100644 --- a/artiq/coredevice/adf5356.py +++ b/artiq/coredevice/adf5356.py @@ -119,6 +119,18 @@ class ADF5356: else: self.sync() + @kernel + def set_att(self, att: float): + """Set digital step attenuator in SI units. + + This method will write the attenuator settings of the channel. + + .. seealso:: :meth:`artiq.coredevice.mirny.Mirny.set_att` + + :param att: Attenuation in dB. + """ + self.cpld.set_att(self.channel, att) + @kernel def set_att_mu(self, att: int32): """Set digital step attenuator in machine units. diff --git a/artiq/coredevice/comm_moninj.py b/artiq/coredevice/comm_moninj.py index 392ad701a..f03e91bbc 100644 --- a/artiq/coredevice/comm_moninj.py +++ b/artiq/coredevice/comm_moninj.py @@ -2,7 +2,7 @@ import asyncio import logging import struct from enum import Enum - +from .comm import set_keepalive __all__ = ["TTLProbe", "TTLOverride", "CommMonInj"] @@ -29,6 +29,8 @@ class CommMonInj: async def connect(self, host, port=1383): self._reader, self._writer = await asyncio.open_connection(host, port) + set_keepalive(self._writer.transport.get_extra_info('socket'), 1, 1, 3) + try: self._writer.write(b"ARTIQ moninj\n") # get device endian diff --git a/artiq/coredevice/mirny.py b/artiq/coredevice/mirny.py index 2a8210c30..55d03f855 100644 --- a/artiq/coredevice/mirny.py +++ b/artiq/coredevice/mirny.py @@ -1,7 +1,7 @@ """RTIO driver for Mirny (4 channel GHz PLLs) """ -from artiq.language.core import nac3, Kernel, KernelInvariant, kernel +from artiq.language.core import nac3, Kernel, KernelInvariant, kernel, portable from artiq.language.units import us from numpy import int32 @@ -144,6 +144,18 @@ class Mirny: self.write_reg(1, (self.clk_sel << 4)) self.core.delay(1000. * us) + @portable + def att_to_mu(self, att: float) -> int32: + """Convert an attenuation setting in dB to machine units. + + :param att: Attenuation setting in dB. + :return: Digital attenuation setting. + """ + code = int32(255) - int32(round(att * 8.)) + if code < 0 or code > 255: + raise ValueError("Invalid Mirny attenuation!") + return code + @kernel def set_att_mu(self, channel: int32, att: int32): """Set digital step attenuator in machine units. @@ -153,6 +165,21 @@ class Mirny: self.bus.set_config_mu(SPI_CONFIG | SPI_END, 16, SPIT_WR, SPI_CS) self.bus.write(((channel | 8) << 25) | (att << 16)) + @kernel + def set_att(self, channel: int32, att: float): + """Set digital step attenuator in SI units. + + This method will write the attenuator settings of the selected channel. + + .. seealso:: :meth:`set_att_mu` + + :param channel: Attenuator channel (0-3). + :param att: Attenuation setting in dB. Higher value is more + attenuation. Minimum attenuation is 0*dB, maximum attenuation is + 31.5*dB. + """ + self.set_att_mu(channel, self.att_to_mu(att)) + @kernel def write_ext(self, addr: int32, length: int32, data: int32, ext_div: int32 = SPIT_WR): """Perform SPI write to a prefixed address""" diff --git a/artiq/firmware/bootloader/main.rs b/artiq/firmware/bootloader/main.rs index 9a41bdb4b..8c428bedb 100644 --- a/artiq/firmware/bootloader/main.rs +++ b/artiq/firmware/bootloader/main.rs @@ -65,8 +65,8 @@ fn memory_test(total: &mut usize, wrong: &mut usize) -> bool { }) } - fn prng32(seed: &mut u32) -> u32 { *seed = 1664525 * *seed + 1013904223; *seed } - fn prng16(seed: &mut u16) -> u16 { *seed = 25173 * *seed + 13849; *seed } + fn prng32(seed: &mut u32) -> u32 { *seed = u32::wrapping_add(u32::wrapping_mul(1664525, *seed), 1013904223); *seed } + fn prng16(seed: &mut u16) -> u16 { *seed = u16::wrapping_add(u16::wrapping_mul(25173, *seed), 13849); *seed } for _ in 0..4 { // Test data bus diff --git a/artiq/firmware/libboard_misoc/sdram.rs b/artiq/firmware/libboard_misoc/sdram.rs index b3e112eb0..1227b411d 100644 --- a/artiq/firmware/libboard_misoc/sdram.rs +++ b/artiq/firmware/libboard_misoc/sdram.rs @@ -211,9 +211,10 @@ mod ddr { // Generate pseudo-random sequence let mut prs = [0; DFII_NPHASES * DFII_PIX_DATA_SIZE]; - let mut prv = 42; + let mut prv: u32 = 42; for b in prs.iter_mut() { - prv = 1664525 * prv + 1013904223; + + prv = u32::wrapping_add(u32::wrapping_mul(1664525, prv), 1013904223); *b = prv as u8; } @@ -296,7 +297,7 @@ mod ddr { let mut prs = [0; DFII_NPHASES * DFII_PIX_DATA_SIZE]; let mut prv = 42; for b in prs.iter_mut() { - prv = 1664525 * prv + 1013904223; + prv = u32::wrapping_add(u32::wrapping_mul(1664525, prv), 1013904223); *b = prv as u8; } diff --git a/artiq/firmware/libproto_artiq/rpc_proto.rs b/artiq/firmware/libproto_artiq/rpc_proto.rs index 0295b5297..a93a0e82c 100644 --- a/artiq/firmware/libproto_artiq/rpc_proto.rs +++ b/artiq/firmware/libproto_artiq/rpc_proto.rs @@ -5,15 +5,20 @@ use byteorder::{NativeEndian, ByteOrder}; use io::{ProtoRead, Read, Write, ProtoWrite, Error}; use self::tag::{Tag, TagIterator, split_tag}; +#[inline] +fn alignment_offset(alignment: isize, ptr: isize) -> isize { + (-ptr).rem_euclid(alignment) +} + unsafe fn align_ptr(ptr: *const ()) -> *const T { let alignment = core::mem::align_of::() as isize; - let fix = (alignment - (ptr as isize) % alignment) % alignment; + let fix = alignment_offset(alignment as isize, ptr as isize); ((ptr as isize) + fix) as *const T } unsafe fn align_ptr_mut(ptr: *mut ()) -> *mut T { let alignment = core::mem::align_of::() as isize; - let fix = (alignment - (ptr as isize) % alignment) % alignment; + let fix = alignment_offset(alignment as isize, ptr as isize); ((ptr as isize) + fix) as *mut T } @@ -54,6 +59,7 @@ unsafe fn recv_value(reader: &mut R, tag: Tag, data: &mut *mut (), }) } Tag::Tuple(it, arity) => { + *data = data.offset(alignment_offset(tag.alignment() as isize, *data as isize)); let mut it = it.clone(); for _ in 0..arity { let tag = it.next().expect("truncated tag"); @@ -69,9 +75,12 @@ unsafe fn recv_value(reader: &mut R, tag: Tag, data: &mut *mut (), let length = (*ptr).length as usize; let tag = it.clone().next().expect("truncated tag"); - (*ptr).elements = alloc(tag.size() * (*ptr).length as usize)?; + let padding = if let Tag::Int64 | Tag::Float64 = tag { 4 } else { 0 }; + let mut data = alloc(tag.size() * length + padding)?; - let mut data = (*ptr).elements; + data = data.offset(alignment_offset(tag.alignment() as isize, data as isize)); + + (*ptr).elements = data; match tag { Tag::Bool => { let dest = slice::from_raw_parts_mut(data as *mut u8, length); @@ -109,9 +118,11 @@ unsafe fn recv_value(reader: &mut R, tag: Tag, data: &mut *mut (), let length = total_len as usize; let elt_tag = it.clone().next().expect("truncated tag"); - *buffer = alloc(elt_tag.size() * total_len as usize)?; + let padding = if let Tag::Int64 | Tag::Float64 = tag { 4 } else { 0 }; + let mut data = alloc(elt_tag.size() * length + padding)?; + data = data.offset(alignment_offset(tag.alignment() as isize, data as isize)); - let mut data = *buffer; + *buffer = data; match elt_tag { Tag::Bool => { let dest = slice::from_raw_parts_mut(data as *mut u8, length); @@ -139,6 +150,7 @@ unsafe fn recv_value(reader: &mut R, tag: Tag, data: &mut *mut (), }) } Tag::Range(it) => { + *data = data.offset(alignment_offset(tag.alignment() as isize, *data as isize)); let tag = it.clone().next().expect("truncated tag"); recv_value(reader, tag, data, alloc)?; recv_value(reader, tag, data, alloc)?; @@ -336,6 +348,7 @@ pub fn send_args(writer: &mut W, service: u32, tag_bytes: &[u8], data: *const mod tag { use core::fmt; + use super::alignment_offset; pub fn split_tag(tag_bytes: &[u8]) -> (&[u8], &[u8]) { let tag_separator = @@ -385,6 +398,33 @@ mod tag { } } + pub fn alignment(self) -> usize { + use cslice::CSlice; + match self { + Tag::None => 1, + Tag::Bool => core::mem::align_of::(), + Tag::Int32 => core::mem::align_of::(), + Tag::Int64 => core::mem::align_of::(), + Tag::Float64 => core::mem::align_of::(), + // struct type: align to largest element + Tag::Tuple(it, arity) => { + let it = it.clone(); + it.take(arity.into()).map(|t| t.alignment()).max().unwrap() + }, + Tag::Range(it) => { + let it = it.clone(); + it.take(3).map(|t| t.alignment()).max().unwrap() + } + // CSlice basically + Tag::Bytes | Tag::String | Tag::ByteArray | Tag::List(_) => + core::mem::align_of::>(), + // array buffer is allocated, so no need for alignment first + Tag::Array(_, _) => 1, + // will not be sent from the host + _ => unreachable!("unexpected tag from host") + } + } + pub fn size(self) -> usize { match self { Tag::None => 0, @@ -401,6 +441,7 @@ mod tag { for _ in 0..arity { let tag = it.next().expect("truncated tag"); size += tag.size(); + size += alignment_offset(tag.alignment() as isize, size as isize) as usize; } size } @@ -469,6 +510,13 @@ mod tag { } } + impl<'a> Iterator for TagIterator<'a> { + type Item = Tag<'a>; + fn next(&mut self) -> Option { + (self as &mut TagIterator<'a>).next() + } + } + impl<'a> fmt::Display for TagIterator<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut it = self.clone(); diff --git a/artiq/frontend/afws_client.py b/artiq/frontend/afws_client.py new file mode 100755 index 000000000..7d9a78265 --- /dev/null +++ b/artiq/frontend/afws_client.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 + +import sys +import argparse +import os +import socket +import ssl +import io +import zipfile +from getpass import getpass + + +def get_artiq_cert(): + try: + import artiq + except ImportError: + return None + filename = os.path.join(os.path.dirname(artiq.__file__), "afws.pem") + if not os.path.isfile(filename): + return None + return filename + + +def get_artiq_rev(): + try: + import artiq + except ImportError: + return None + return artiq._version.get_rev() + + +def zip_unarchive(data, directory): + buf = io.BytesIO(data) + with zipfile.ZipFile(buf) as archive: + archive.extractall(directory) + + +class Client: + def __init__(self, server, port, cafile): + self.ssl_context = ssl.create_default_context(cafile=cafile) + self.raw_socket = socket.create_connection((server, port)) + try: + self.socket = self.ssl_context.wrap_socket(self.raw_socket, server_hostname=server) + except: + self.raw_socket.close() + raise + self.fsocket = self.socket.makefile("rwb") + + def close(self): + self.socket.close() + self.raw_socket.close() + + def send_command(self, *command): + self.fsocket.write((" ".join(command) + "\n").encode()) + self.fsocket.flush() + + def read_reply(self): + return self.fsocket.readline().decode("ascii").split() + + def login(self, username, password): + self.send_command("LOGIN", username, password) + return self.read_reply() == ["HELLO"] + + def build(self, rev, variant): + self.send_command("BUILD", rev, variant) + reply = self.read_reply()[0] + if reply != "BUILDING": + return reply, None + print("Build in progress. This may take 10-15 minutes.") + reply, status = self.read_reply() + if reply != "DONE": + raise ValueError("Unexpected server reply: expected 'DONE', got '{}'".format(reply)) + if status != "done": + return status, None + print("Build completed. Downloading...") + reply, length = self.read_reply() + if reply != "PRODUCT": + raise ValueError("Unexpected server reply: expected 'PRODUCT', got '{}'".format(reply)) + contents = self.fsocket.read(int(length)) + print("Download completed.") + return "OK", contents + + def passwd(self, password): + self.send_command("PASSWD", password) + return self.read_reply() == ["OK"] + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--server", default="nixbld.m-labs.hk", help="server to connect to (default: %(default)s)") + parser.add_argument("--port", default=7402, type=int, help="port to connect to (default: %(default)d)") + parser.add_argument("--cert", default=None, help="SSL certificate file used to authenticate server (default: afws.pem in ARTIQ)") + parser.add_argument("username", help="user name for logging into AFWS") + action = parser.add_subparsers(dest="action") + action.required = True + act_build = action.add_parser("build", help="build and download firmware") + act_build.add_argument("--rev", default=None, help="revision to build (default: currently installed ARTIQ revision)") + act_build.add_argument("variant", help="variant to build") + act_build.add_argument("directory", help="output directory") + act_passwd = action.add_parser("passwd", help="change password") + args = parser.parse_args() + + cert = args.cert + if cert is None: + cert = get_artiq_cert() + if cert is None: + print("SSL certificate not found in ARTIQ. Specify manually using --cert.") + sys.exit(1) + + if args.action == "passwd": + password = getpass("Current password: ") + else: + password = getpass() + + client = Client(args.server, args.port, cert) + try: + if not client.login(args.username, password): + print("Login failed") + sys.exit(1) + print("Logged in successfully.") + if args.action == "passwd": + print("Password must made of alphanumeric characters (a-z, A-Z, 0-9) and be at least 8 characters long.") + password = getpass("New password: ") + password_confirm = getpass("New password (again): ") + while password != password_confirm: + print("Passwords do not match") + password = getpass("New password: ") + password_confirm = getpass("New password (again): ") + if not client.passwd(password): + print("Failed to change password") + sys.exit(1) + elif args.action == "build": + try: + os.mkdir(args.directory) + except FileExistsError: + try: + if any(os.scandir(args.directory)): + print("Output directory already exists and is not empty. Please remove it and try again.") + sys.exit(1) + except NotADirectoryError: + print("A file with the same name as the output directory already exists. Please remove it and try again.") + sys.exit(1) + rev = args.rev + if rev is None: + rev = get_artiq_rev() + if rev is None: + print("Unable to determine currently installed ARTIQ revision. Specify manually using --rev.") + sys.exit(1) + result, contents = client.build(rev, args.variant) + if result != "OK": + if result == "UNAUTHORIZED": + print("You are not authorized to build this variant. Your firmware subscription may have expired. Contact helpdesk\x40m-labs.hk.") + else: + print("Build failed: {}".format(result)) + sys.exit(1) + zip_unarchive(contents, args.directory) + else: + raise ValueError + finally: + client.close() + + +if __name__ == "__main__": + main() diff --git a/artiq/frontend/artiq_flash.py b/artiq/frontend/artiq_flash.py index 62b8dc39c..12475cf0b 100755 --- a/artiq/frontend/artiq_flash.py +++ b/artiq/frontend/artiq_flash.py @@ -13,7 +13,6 @@ from collections import defaultdict from sipyco import common_args from artiq import __version__ as artiq_version -from artiq import __artiq_dir__ as artiq_dir from artiq.remoting import SSHClient, LocalClient from artiq.frontend.bit2bin import bit2bin @@ -63,13 +62,11 @@ Prerequisites: parser.add_argument("-t", "--target", default="kasli", help="target board, default: %(default)s, one of: " "kasli sayma metlino kc705") - parser.add_argument("-V", "--variant", default=None, - help="board variant. Autodetected if only one is installed.") parser.add_argument("-I", "--preinit-command", default=[], action="append", help="add a pre-initialization OpenOCD command. " "Useful for selecting a board when several are connected.") parser.add_argument("-f", "--storage", help="write file to storage area") - parser.add_argument("-d", "--dir", help="look for board binaries in this directory") + parser.add_argument("-d", "--dir", default=None, help="look for board binaries in this directory") parser.add_argument("--srcbuild", help="board binaries directory is laid out as a source build tree", default=False, action="store_true") parser.add_argument("--no-rtm-jtag", help="do not attempt JTAG to the RTM", @@ -338,56 +335,19 @@ def main(): }, }[args.target] - bin_dir = args.dir - if bin_dir is None: - bin_dir = os.path.join(artiq_dir, "board-support") - - needs_artifacts = not args.action or any( - action in args.action - for action in ["gateware", "rtm_gateware", "bootloader", "firmware", "load", "rtm_load"]) - variant = args.variant - if needs_artifacts and variant is None: - variants = [] - if args.srcbuild: - for entry in os.scandir(bin_dir): - if entry.is_dir(): - variants.append(entry.name) - else: - prefix = args.target + "-" - for entry in os.scandir(bin_dir): - if entry.is_dir() and entry.name.startswith(prefix): - variants.append(entry.name[len(prefix):]) - if args.target == "sayma": - try: - variants.remove("rtm") - except ValueError: - pass - if all(action in ["rtm_gateware", "storage", "rtm_load", "erase", "start"] - for action in args.action) and args.action: - pass - elif len(variants) == 0: - raise FileNotFoundError("no variants found, did you install a board binary package?") - elif len(variants) == 1: - variant = variants[0] - else: - raise ValueError("more than one variant found for selected board, specify -V. " - "Found variants: {}".format(" ".join(sorted(variants)))) - if needs_artifacts: - if args.srcbuild: - variant_dir = variant - else: - variant_dir = args.target + "-" + variant - if args.target == "sayma": - if args.srcbuild: - rtm_variant_dir = "rtm" - else: - rtm_variant_dir = "sayma-rtm" - if not args.action: - if args.target == "sayma" and variant != "simplesatellite" and variant != "master": + if args.target == "sayma": args.action = "gateware rtm_gateware bootloader firmware start".split() else: args.action = "gateware bootloader firmware start".split() + needs_artifacts = any( + action in args.action + for action in ["gateware", "rtm_gateware", "bootloader", "firmware", "load", "rtm_load"]) + if needs_artifacts and args.dir is None: + raise ValueError("the directory containing the binaries need to be specified using -d.") + + binary_dir = args.dir + rtm_binary_dir = os.path.join(binary_dir, "rtm") if args.host is None: client = LocalClient() @@ -400,14 +360,14 @@ def main(): programmer_cls = config["programmer"] programmer = programmer_cls(client, preinit_script=args.preinit_command) - def artifact_path(this_variant_dir, *path_filename): + def artifact_path(this_binary_dir, *path_filename): if args.srcbuild: # source tree - use path elements to locate file - return os.path.join(bin_dir, this_variant_dir, *path_filename) + return os.path.join(this_binary_dir, *path_filename) else: # flat tree - all files in the same directory, discard path elements *_, filename = path_filename - return os.path.join(bin_dir, this_variant_dir, filename) + return os.path.join(this_binary_dir, filename) def convert_gateware(bit_filename, header=False): bin_handle, bin_filename = tempfile.mkstemp( @@ -429,15 +389,15 @@ def main(): for action in args.action: if action == "gateware": gateware_bin = convert_gateware( - artifact_path(variant_dir, "gateware", "top.bit")) + artifact_path(binary_dir, "gateware", "top.bit")) programmer.write_binary(*config["gateware"], gateware_bin) elif action == "rtm_gateware": rtm_gateware_bin = convert_gateware( - artifact_path(rtm_variant_dir, "gateware", "top.bit"), header=True) + artifact_path(rtm_binary_dir, "gateware", "top.bit"), header=True) programmer.write_binary(*config["rtm_gateware"], rtm_gateware_bin) elif action == "bootloader": - bootloader_bin = artifact_path(variant_dir, "software", "bootloader", "bootloader.bin") + bootloader_bin = artifact_path(binary_dir, "software", "bootloader", "bootloader.bin") programmer.write_binary(*config["bootloader"], bootloader_bin) elif action == "storage": storage_img = args.storage @@ -445,7 +405,7 @@ def main(): elif action == "firmware": firmware_fbis = [] for firmware in "satman", "runtime": - filename = artifact_path(variant_dir, "software", firmware, firmware + ".fbi") + filename = artifact_path(binary_dir, "software", firmware, firmware + ".fbi") if os.path.exists(filename): firmware_fbis.append(filename) if not firmware_fbis: @@ -456,13 +416,13 @@ def main(): programmer.write_binary(*config["firmware"], firmware_fbis[0]) elif action == "load": if args.target == "sayma": - gateware_bit = artifact_path(variant_dir, "gateware", "top.bit") + gateware_bit = artifact_path(binary_dir, "gateware", "top.bit") programmer.load(gateware_bit, 1) else: - gateware_bit = artifact_path(variant_dir, "gateware", "top.bit") + gateware_bit = artifact_path(binary_dir, "gateware", "top.bit") programmer.load(gateware_bit, 0) elif action == "rtm_load": - rtm_gateware_bit = artifact_path(rtm_variant_dir, "gateware", "top.bit") + rtm_gateware_bit = artifact_path(rtm_binary_dir, "gateware", "top.bit") programmer.load(rtm_gateware_bit, 0) elif action == "start": programmer.start() diff --git a/artiq/frontend/artiq_sinara_tester.py b/artiq/frontend/artiq_sinara_tester.py index 31f947631..a4f999029 100755 --- a/artiq/frontend/artiq_sinara_tester.py +++ b/artiq/frontend/artiq_sinara_tester.py @@ -331,7 +331,7 @@ class SinaraTester(EnvExperiment): self.core.break_realtime() channel.init() - channel.set_att_mu(160) + channel.set_att(11.5*dB) channel.sw.on() self.core.break_realtime() diff --git a/artiq/gui/entries.py b/artiq/gui/entries.py index 7e5e80916..b63be3902 100644 --- a/artiq/gui/entries.py +++ b/artiq/gui/entries.py @@ -100,6 +100,17 @@ class NumberEntryInt(QtWidgets.QSpinBox): if "default" in procdesc: return procdesc["default"] else: + have_max = "max" in procdesc and procdesc["max"] is not None + have_min = "min" in procdesc and procdesc["min"] is not None + if have_max and have_min: + if procdesc["min"] <= 0 < procdesc["max"]: + return 0 + elif have_min and not have_max: + if procdesc["min"] >= 0: + return procdesc["min"] + elif not have_min and have_max: + if procdesc["max"] < 0: + return procdesc["max"] return 0 diff --git a/artiq/test/coredevice/test_embedding.py b/artiq/test/coredevice/test_embedding.py index 8f5e4e3bc..4e86e9aa5 100644 --- a/artiq/test/coredevice/test_embedding.py +++ b/artiq/test/coredevice/test_embedding.py @@ -515,3 +515,26 @@ class NumpyBoolTest(ExperimentCase): def test_numpy_bool(self): """Test NumPy bools decay to ARTIQ compiler builtin bools as expected""" self.create(_NumpyBool).run() + + +class _Alignment(EnvExperiment): + def build(self): + self.setattr_device("core") + + @rpc + def a_tuple(self) -> TList(TTuple([TBool, TFloat, TBool])): + return [(True, 1234.5678, True)] + + @kernel + def run(self): + a, b, c = self.a_tuple()[0] + d, e, f = self.a_tuple()[0] + assert a == d + assert b == e + assert c == f + return 0 + + +class AlignmentTest(ExperimentCase): + def test_tuple(self): + self.create(_Alignment).run() diff --git a/doc/manual/index.rst b/doc/manual/index.rst index c7ecde30f..76b0b8f61 100644 --- a/doc/manual/index.rst +++ b/doc/manual/index.rst @@ -1,9 +1,8 @@ ARTIQ documentation =================== -Contents: - .. toctree:: + :caption: Contents :maxdepth: 2 introduction diff --git a/flake.lock b/flake.lock index 2a3aad7d0..1ae195f8e 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,29 @@ { "nodes": { + "artiq-comtools": { + "inputs": { + "nixpkgs": [ + "nac3", + "nixpkgs" + ], + "sipyco": [ + "sipyco" + ] + }, + "locked": { + "lastModified": 1644743100, + "narHash": "sha256-XqxMq2l2DXSovV7r2k/FXjYRM3bvVl3Mjy+C1koVAx4=", + "owner": "m-labs", + "repo": "artiq-comtools", + "rev": "8a126dd7d0a3f2d50ae151ec633cd52587d98796", + "type": "github" + }, + "original": { + "owner": "m-labs", + "repo": "artiq-comtools", + "type": "github" + } + }, "mozilla-overlay": { "flake": false, "locked": { @@ -16,6 +40,24 @@ "type": "github" } }, + "nac3": { + "inputs": { + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1644759922, + "narHash": "sha256-3hNFahCeHp0seppVONSlAMXIzn0vjCJGrJj6CySLLxw=", + "ref": "master", + "rev": "21d9182ba2924cf9cc555a201e661d2ea474eed9", + "revCount": 594, + "type": "git", + "url": "https://git.m-labs.hk/M-Labs/nac3.git" + }, + "original": { + "type": "git", + "url": "https://git.m-labs.hk/M-Labs/nac3.git" + } + }, "nixpkgs": { "locked": { "lastModified": 1644472683, @@ -34,11 +76,33 @@ }, "root": { "inputs": { + "artiq-comtools": "artiq-comtools", "mozilla-overlay": "mozilla-overlay", + "nac3": "nac3", + "sipyco": "sipyco", "src-migen": "src-migen", - "src-misoc": "src-misoc", - "src-nac3": "src-nac3", - "src-sipyco": "src-sipyco" + "src-misoc": "src-misoc" + } + }, + "sipyco": { + "inputs": { + "nixpkgs": [ + "nac3", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1644649772, + "narHash": "sha256-LE9L5bDSunCPEnuf5Ed8enTAXA2vkTSmjvqPX9ILO0Y=", + "owner": "m-labs", + "repo": "sipyco", + "rev": "8e4382352bc64bd01c9db35d9c9b0ef42b8b9d3b", + "type": "github" + }, + "original": { + "owner": "m-labs", + "repo": "sipyco", + "type": "github" } }, "src-migen": { @@ -74,40 +138,6 @@ "type": "git", "url": "https://github.com/m-labs/misoc.git" } - }, - "src-nac3": { - "inputs": { - "nixpkgs": "nixpkgs" - }, - "locked": { - "lastModified": 1644723702, - "narHash": "sha256-2pLJHYvQmoXhdy3WDZpfU7kDMpgH/G7uREZOyql7R10=", - "ref": "master", - "rev": "4b8e70f7462b139e388f098d42f9f47e4915f431", - "revCount": 589, - "type": "git", - "url": "https://git.m-labs.hk/M-Labs/nac3.git" - }, - "original": { - "type": "git", - "url": "https://git.m-labs.hk/M-Labs/nac3.git" - } - }, - "src-sipyco": { - "flake": false, - "locked": { - "lastModified": 1644649772, - "narHash": "sha256-LE9L5bDSunCPEnuf5Ed8enTAXA2vkTSmjvqPX9ILO0Y=", - "owner": "m-labs", - "repo": "sipyco", - "rev": "8e4382352bc64bd01c9db35d9c9b0ef42b8b9d3b", - "type": "github" - }, - "original": { - "owner": "m-labs", - "repo": "sipyco", - "type": "github" - } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 8a3e9cc77..c78a065b1 100644 --- a/flake.nix +++ b/flake.nix @@ -2,20 +2,25 @@ description = "A leading-edge control system for quantum information experiments"; inputs.mozilla-overlay = { url = github:mozilla/nixpkgs-mozilla; flake = false; }; - inputs.src-sipyco = { url = github:m-labs/sipyco; flake = false; }; - inputs.src-nac3 = { type = "git"; url = "https://git.m-labs.hk/M-Labs/nac3.git"; }; + inputs.sipyco.url = github:m-labs/sipyco; + inputs.sipyco.inputs.nixpkgs.follows = "nac3/nixpkgs"; + inputs.nac3 = { type = "git"; url = "https://git.m-labs.hk/M-Labs/nac3.git"; }; + inputs.artiq-comtools.url = github:m-labs/artiq-comtools; + inputs.artiq-comtools.inputs.nixpkgs.follows = "nac3/nixpkgs"; + inputs.artiq-comtools.inputs.sipyco.follows = "sipyco"; inputs.src-migen = { url = github:m-labs/migen; flake = false; }; inputs.src-misoc = { type = "git"; url = "https://github.com/m-labs/misoc.git"; submodules = true; flake = false; }; - outputs = { self, mozilla-overlay, src-sipyco, src-nac3, src-migen, src-misoc }: + outputs = { self, mozilla-overlay, sipyco, nac3, artiq-comtools, src-migen, src-misoc }: let - pkgs = import src-nac3.inputs.nixpkgs { system = "x86_64-linux"; overlays = [ (import mozilla-overlay) ]; }; + pkgs = import nac3.inputs.nixpkgs { system = "x86_64-linux"; overlays = [ (import mozilla-overlay) ]; }; artiqVersionMajor = 8; artiqVersionMinor = self.sourceInfo.revCount or 0; artiqVersionId = self.sourceInfo.shortRev or "unknown"; - artiqVersion = (builtins.toString artiqVersionMajor) + "." + (builtins.toString artiqVersionMinor) + "-" + artiqVersionId + "-beta"; + artiqVersion = (builtins.toString artiqVersionMajor) + "." + (builtins.toString artiqVersionMinor) + "." + artiqVersionId + ".beta"; + artiqRev = self.sourceInfo.rev or "unknown"; rustManifest = pkgs.fetchurl { url = "https://static.rust-lang.org/dist/2021-01-29/channel-rust-nightly.toml"; @@ -51,12 +56,6 @@ fontconfig ]; - sipyco = pkgs.python3Packages.buildPythonPackage { - name = "sipyco"; - src = src-sipyco; - propagatedBuildInputs = with pkgs.python3Packages; [ pybase64 numpy ]; - }; - qasync = pkgs.python3Packages.buildPythonPackage rec { pname = "qasync"; version = "0.19.0"; @@ -78,11 +77,15 @@ version = artiqVersion; src = self; - preBuild = "export VERSIONEER_OVERRIDE=${version}"; + preBuild = + '' + export VERSIONEER_OVERRIDE=${version} + export VERSIONEER_REV=${artiqRev} + ''; nativeBuildInputs = [ pkgs.qt5.wrapQtAppsHook ]; # keep llvm_x and lld_x in sync with nac3 - propagatedBuildInputs = [ pkgs.llvm_13 pkgs.lld_13 src-nac3.packages.x86_64-linux.nac3artiq sipyco ] + propagatedBuildInputs = [ pkgs.llvm_13 pkgs.lld_13 nac3.packages.x86_64-linux.nac3artiq sipyco.packages.x86_64-linux.sipyco artiq-comtools.packages.x86_64-linux.artiq-comtools ] ++ (with pkgs.python3Packages; [ pyqtgraph pygit2 numpy dateutil scipy prettytable pyserial h5py pyqt5 qasync ]); dontWrapQtApps = true; @@ -183,7 +186,7 @@ }; makeArtiqBoardPackage = { target, variant, buildCommand ? "python -m artiq.gateware.targets.${target} -V ${variant}" }: - pkgs.python3Packages.toPythonModule (pkgs.stdenv.mkDerivation { + pkgs.stdenv.mkDerivation { name = "artiq-board-${target}-${variant}"; phases = [ "buildPhase" "checkPhase" "installPhase" ]; cargoDeps = rustPlatform.fetchCargoTarball { @@ -192,7 +195,7 @@ sha256 = "sha256-YyycMsDzR+JRcMZJd6A/CRi2J9nKmaWY/KXUnAQaZ00="; }; nativeBuildInputs = [ - (pkgs.python3.withPackages(ps: [ migen misoc artiq ])) + (pkgs.python3.withPackages(ps: [ ps.jsonschema migen misoc artiq])) rustPlatform.rust.rustc rustPlatform.rust.cargo pkgs.llvmPackages_13.clang-unwrapped @@ -222,7 +225,7 @@ ''; installPhase = '' - TARGET_DIR=$out/${pkgs.python3Packages.python.sitePackages}/artiq/board-support/${target}-${variant} + TARGET_DIR=$out mkdir -p $TARGET_DIR cp artiq_${target}/${variant}/gateware/top.bit $TARGET_DIR if [ -e artiq_${target}/${variant}/software/bootloader/bootloader.bin ] @@ -235,7 +238,7 @@ ''; # don't mangle ELF files as they are not for NixOS dontFixup = true; - }); + }; openocd-bscanspi = let bscan_spi_bitstreams-pkg = pkgs.stdenv.mkDerivation { @@ -275,36 +278,79 @@ name = "openocd-bscanspi"; paths = [ openocd-fixed bscan_spi_bitstreams-pkg ]; }; + + sphinxcontrib-wavedrom = pkgs.python3Packages.buildPythonPackage rec { + pname = "sphinxcontrib-wavedrom"; + version = "3.0.2"; + src = pkgs.python3Packages.fetchPypi { + inherit pname version; + sha256 = "sha256-ukZd3ajt0Sx3LByof4R80S31F5t1yo+L8QUADrMMm2A="; + }; + buildInputs = [ pkgs.python3Packages.setuptools_scm ]; + propagatedBuildInputs = [ pkgs.nodejs pkgs.nodePackages.wavedrom-cli ] ++ (with pkgs.python3Packages; [ wavedrom sphinx xcffib cairosvg ]); + }; + latex-artiq-manual = pkgs.texlive.combine { + inherit (pkgs.texlive) + scheme-basic latexmk cmap collection-fontsrecommended fncychap + titlesec tabulary varwidth framed fancyvrb float wrapfig parskip + upquote capt-of needspace etoolbox; + }; in rec { packages.x86_64-linux = rec { - inherit migen misoc vivadoEnv vivado openocd-bscanspi artiq; - inherit (src-nac3.packages.x86_64-linux) python3-mimalloc; + inherit (nac3.packages.x86_64-linux) python3-mimalloc; + inherit qasync openocd-bscanspi artiq; + inherit migen misoc asyncserial microscope vivadoEnv vivado; artiq-board-kc705-nist_clock = makeArtiqBoardPackage { target = "kc705"; variant = "nist_clock"; }; - artiq-board-kc705-nist_qc2 = makeArtiqBoardPackage { - target = "kc705"; - variant = "nist_qc2"; + inherit sphinxcontrib-wavedrom latex-artiq-manual; + artiq-manual-html = pkgs.stdenvNoCC.mkDerivation rec { + name = "artiq-manual-html-${version}"; + version = artiqVersion; + src = self; + buildInputs = [ + pkgs.python3Packages.sphinx pkgs.python3Packages.sphinx_rtd_theme + pkgs.python3Packages.sphinx-argparse sphinxcontrib-wavedrom + ]; + buildPhase = '' + export VERSIONEER_OVERRIDE=${artiqVersion} + export SOURCE_DATE_EPOCH=${builtins.toString self.sourceInfo.lastModified} + cd doc/manual + make html + ''; + installPhase = '' + cp -r _build/html $out + mkdir $out/nix-support + echo doc manual $out index.html >> $out/nix-support/hydra-build-products + ''; }; - artiq-board-kc705-nist_clock_master = makeArtiqBoardPackage { - target = "kc705"; - variant = "nist_clock_master"; - }; - artiq-board-kc705-nist_qc2_master = makeArtiqBoardPackage { - target = "kc705"; - variant = "nist_qc2_master"; - }; - artiq-board-kc705-nist_clock_satellite = makeArtiqBoardPackage { - target = "kc705"; - variant = "nist_clock"; - }; - artiq-board-kc705-nist_qc2_satellite = makeArtiqBoardPackage { - target = "kc705"; - variant = "nist_qc2"; + artiq-manual-pdf = pkgs.stdenvNoCC.mkDerivation rec { + name = "artiq-manual-pdf-${version}"; + version = artiqVersion; + src = self; + buildInputs = [ + pkgs.python3Packages.sphinx pkgs.python3Packages.sphinx_rtd_theme + pkgs.python3Packages.sphinx-argparse sphinxcontrib-wavedrom + latex-artiq-manual + ]; + buildPhase = '' + export VERSIONEER_OVERRIDE=${artiq.version} + export SOURCE_DATE_EPOCH=${builtins.toString self.sourceInfo.lastModified} + cd doc/manual + make latexpdf + ''; + installPhase = '' + mkdir $out + cp _build/latex/ARTIQ.pdf $out + mkdir $out/nix-support + echo doc-pdf manual $out ARTIQ.pdf >> $out/nix-support/hydra-build-products + ''; }; }; + inherit makeArtiqBoardPackage; + defaultPackage.x86_64-linux = packages.x86_64-linux.python3-mimalloc.withPackages(ps: [ packages.x86_64-linux.artiq ]); devShell.x86_64-linux = pkgs.mkShell { @@ -321,6 +367,8 @@ packages.x86_64-linux.vivadoEnv packages.x86_64-linux.vivado packages.x86_64-linux.openocd-bscanspi + pkgs.python3Packages.sphinx pkgs.python3Packages.sphinx_rtd_theme + pkgs.python3Packages.sphinx-argparse sphinxcontrib-wavedrom latex-artiq-manual ]; }; diff --git a/setup.py b/setup.py index e5752f47c..18fa12710 100755 --- a/setup.py +++ b/setup.py @@ -35,6 +35,7 @@ console_scripts = [ "artiq_run = artiq.frontend.artiq_run:main", "artiq_flash = artiq.frontend.artiq_flash:main", "aqctl_corelog = artiq.frontend.aqctl_corelog:main", + "afws_client = artiq.frontend.afws_client:main", ] gui_scripts = [ diff --git a/versioneer.py b/versioneer.py index 68574501b..20794abc9 100644 --- a/versioneer.py +++ b/versioneer.py @@ -1,9 +1,13 @@ import os import sys + VERSION_FILE = """ def get_version(): return "{version}" + +def get_rev(): + return "{rev}" """ def get_version(): @@ -18,10 +22,13 @@ def get_version(): version += ".beta" return version -def write_to_version_file(filename, version): +def get_rev(): + return os.getenv("VERSIONEER_REV", default="unknown") + +def write_to_version_file(filename, version, rev): os.unlink(filename) with open(filename, "w") as f: - f.write(VERSION_FILE.format(version=version)) + f.write(VERSION_FILE.format(version=version, rev=rev)) def get_cmdclass(): @@ -36,11 +43,12 @@ def get_cmdclass(): class cmd_build_py(_build_py): def run(self): version = get_version() + rev = get_rev() _build_py.run(self) target_versionfile = os.path.join(self.build_lib, "artiq", "_version.py") print("UPDATING %s" % target_versionfile) - write_to_version_file(target_versionfile, version) + write_to_version_file(target_versionfile, version, rev) cmds["build_py"] = cmd_build_py @@ -54,6 +62,7 @@ def get_cmdclass(): def run(self): version = get_version() self._versioneer_generated_version = version + self._versioneer_rev = get_rev() # unless we update this, the command will keep using the old # version self.distribution.metadata.version = version @@ -64,7 +73,8 @@ def get_cmdclass(): target_versionfile = os.path.join(base_dir, "artiq", "_version.py") print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, - self._versioneer_generated_version) + self._versioneer_generated_version, + self._versioneer_rev) cmds["sdist"] = cmd_sdist return cmds