diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 0bd400186..19a97a206 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -12,6 +12,7 @@ Highlights: - Kasli-SoC, a new EEM carrier based on a Zynq SoC, enabling much faster kernel execution. - HVAMP_8CH 8 channel HV amplifier for Fastino / Zotinos - Almazny mezzanine board for Mirny +* TTL device output can be now configured to work as a clock generator. * Softcore targets now use the RISC-V architecture (VexRiscv) instead of OR1K (mor1kx). * Gateware FPU is supported on KC705 and Kasli 2.0. * Faster compilation for large arrays/lists. diff --git a/artiq/coredevice/comm_mgmt.py b/artiq/coredevice/comm_mgmt.py index 539643751..870e2759d 100644 --- a/artiq/coredevice/comm_mgmt.py +++ b/artiq/coredevice/comm_mgmt.py @@ -110,9 +110,10 @@ class CommMgmt: return ty def _read_expect(self, ty): - if self._read_header() != ty: + header = self._read_header() + if header != ty: raise IOError("Incorrect reply from device: {} (expected {})". - format(self._read_type, ty)) + format(header, ty)) def _read_int32(self): (value, ) = struct.unpack(self.endian + "l", self._read(4)) @@ -159,7 +160,12 @@ class CommMgmt: def config_read(self, key): self._write_header(Request.ConfigRead) self._write_string(key) - self._read_expect(Reply.ConfigData) + ty = self._read_header() + if ty == Reply.Error: + raise IOError("Device failed to read config. The key may not exist.") + elif ty != Reply.ConfigData: + raise IOError("Incorrect reply from device: {} (expected {})". + format(ty, Reply.ConfigData)) return self._read_string() def config_write(self, key, value): diff --git a/artiq/coredevice/coredevice_generic.schema.json b/artiq/coredevice/coredevice_generic.schema.json index 462570501..fa687104f 100644 --- a/artiq/coredevice/coredevice_generic.schema.json +++ b/artiq/coredevice/coredevice_generic.schema.json @@ -170,11 +170,11 @@ }, "bank_direction_low": { "type": "string", - "enum": ["input", "output"] + "enum": ["input", "output", "clkgen"] }, "bank_direction_high": { "type": "string", - "enum": ["input", "output"] + "enum": ["input", "output", "clkgen"] } }, "required": ["ports", "bank_direction_low", "bank_direction_high"] diff --git a/artiq/dashboard/datasets.py b/artiq/dashboard/datasets.py index 10e51e906..674e653e9 100644 --- a/artiq/dashboard/datasets.py +++ b/artiq/dashboard/datasets.py @@ -14,6 +14,12 @@ from artiq.gui.scientific_spinbox import ScientificSpinBox logger = logging.getLogger(__name__) +async def rename(key, newkey, value, dataset_ctl): + if key != newkey: + await dataset_ctl.delete(key) + await dataset_ctl.set(newkey, value) + + class Editor(QtWidgets.QDialog): def __init__(self, parent, dataset_ctl, key, value): QtWidgets.QDialog.__init__(self, parent=parent) @@ -26,7 +32,11 @@ class Editor(QtWidgets.QDialog): self.setLayout(grid) grid.addWidget(QtWidgets.QLabel("Name:"), 0, 0) - grid.addWidget(QtWidgets.QLabel(key), 0, 1) + + self.name_widget = QtWidgets.QLineEdit() + self.name_widget.setText(key) + + grid.addWidget(self.name_widget, 0, 1) grid.addWidget(QtWidgets.QLabel("Value:"), 1, 0) grid.addWidget(self.get_edit_widget(value), 1, 1) @@ -39,8 +49,9 @@ class Editor(QtWidgets.QDialog): buttons.rejected.connect(self.reject) def accept(self): + newkey = self.name_widget.text() value = self.initial_type(self.get_edit_widget_value()) - asyncio.ensure_future(self.dataset_ctl.set(self.key, value)) + asyncio.ensure_future(rename(self.key, newkey, value, self.dataset_ctl)) QtWidgets.QDialog.accept(self) def get_edit_widget(self, initial_value): diff --git a/artiq/dashboard/moninj.py b/artiq/dashboard/moninj.py index a9adc5678..c624529af 100644 --- a/artiq/dashboard/moninj.py +++ b/artiq/dashboard/moninj.py @@ -203,6 +203,7 @@ _WidgetDesc = namedtuple("_WidgetDesc", "uid comment cls arguments") def setup_from_ddb(ddb): mi_addr = None + mi_port = None dds_sysclk = None description = set() @@ -235,14 +236,16 @@ def setup_from_ddb(ddb): description.add(widget) elif v["type"] == "controller" and k == "core_moninj": mi_addr = v["host"] + mi_port = v.get("port_proxy", 1383) except KeyError: pass - return mi_addr, dds_sysclk, description + return mi_addr, mi_port, dds_sysclk, description class _DeviceManager: def __init__(self): self.mi_addr = None + self.mi_port = None self.reconnect_mi = asyncio.Event() self.mi_connection = None self.mi_connector_task = asyncio.ensure_future(self.mi_connector()) @@ -264,10 +267,11 @@ class _DeviceManager: return ddb def notify(self, mod): - mi_addr, dds_sysclk, description = setup_from_ddb(self.ddb) + mi_addr, mi_port, dds_sysclk, description = setup_from_ddb(self.ddb) - if mi_addr != self.mi_addr: + if (mi_addr, mi_port) != (self.mi_addr, self.mi_port): self.mi_addr = mi_addr + self.mi_port = mi_port self.reconnect_mi.set() self.dds_sysclk = dds_sysclk @@ -397,7 +401,7 @@ class _DeviceManager: new_mi_connection = CommMonInj(self.monitor_cb, self.injection_status_cb, self.disconnect_cb) try: - await new_mi_connection.connect(self.mi_addr, 1383) + await new_mi_connection.connect(self.mi_addr, self.mi_port) except asyncio.CancelledError: logger.info("cancelled connection to moninj") break diff --git a/artiq/firmware/libboard_misoc/config.rs b/artiq/firmware/libboard_misoc/config.rs index 9a737e303..51f71ac33 100644 --- a/artiq/firmware/libboard_misoc/config.rs +++ b/artiq/firmware/libboard_misoc/config.rs @@ -9,6 +9,7 @@ pub enum Error { MissingSeparator { offset: usize }, Utf8Error(str::Utf8Error), NoFlash, + KeyNotFound } impl fmt::Display for Error { @@ -28,6 +29,8 @@ impl fmt::Display for Error { write!(f, "{}", err), &Error::NoFlash => write!(f, "flash memory is not present"), + &Error::KeyNotFound => + write!(f, "key not found") } } } @@ -156,14 +159,16 @@ mod imp { f(Lock::take().and_then(|lock| { let mut iter = Iter::new(lock.data()); let mut value = &[][..]; + let mut found = false; while let Some(result) = iter.next() { let (record_key, record_value) = result?; if key.as_bytes() == record_key { + found = true; // last write wins value = record_value } } - Ok(value) + if found { Ok(value) } else { Err(Error::KeyNotFound) } })) } diff --git a/artiq/firmware/runtime/session.rs b/artiq/firmware/runtime/session.rs index bd1562269..c6d745545 100644 --- a/artiq/firmware/runtime/session.rs +++ b/artiq/firmware/runtime/session.rs @@ -529,7 +529,7 @@ fn flash_kernel_worker(io: &Io, aux_mutex: &Mutex, config::read(config_key, |result| { match result { - Ok(kernel) if kernel.len() > 0 => unsafe { + Ok(kernel) => unsafe { // kernel CPU cannot access the SPI flash address space directly, // so make a copy. kern_load(io, &mut session, Vec::from(kernel).as_ref()) diff --git a/artiq/frontend/afws_client.py b/artiq/frontend/afws_client.py index 7d9a78265..25ddfc1f9 100755 --- a/artiq/frontend/afws_client.py +++ b/artiq/frontend/afws_client.py @@ -7,7 +7,10 @@ import socket import ssl import io import zipfile +import json +from prettytable import PrettyTable from getpass import getpass +from tqdm import tqdm def get_artiq_cert(): @@ -54,20 +57,42 @@ class Client: self.fsocket.write((" ".join(command) + "\n").encode()) self.fsocket.flush() + def read_line(self): + return self.fsocket.readline().decode("ascii") + def read_reply(self): return self.fsocket.readline().decode("ascii").split() + def read_json(self): + return json.loads(self.fsocket.readline().decode("ascii")) + 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) + def build(self, rev, variant, log): + if not variant: + variants = self.get_variants() + if len(variants) != 1: + raise ValueError("User can build more than 1 variant - need to specify") + variant = variants[0][0] + print("Building variant: {}".format(variant)) + if log: + self.send_command("BUILD", rev, variant, "LOG_ENABLE") + else: + 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 log: + line = self.read_line() + while line != "" and line.startswith("LOG"): + print(line[4:], end="") + line = self.read_line() + reply, status = line.split() + else: + reply, status = self.read_reply() if reply != "DONE": raise ValueError("Unexpected server reply: expected 'DONE', got '{}'".format(reply)) if status != "done": @@ -76,13 +101,28 @@ class Client: reply, length = self.read_reply() if reply != "PRODUCT": raise ValueError("Unexpected server reply: expected 'PRODUCT', got '{}'".format(reply)) - contents = self.fsocket.read(int(length)) + length = int(length) + contents = bytearray() + with tqdm(total=length, unit="iB", unit_scale=True, unit_divisor=1024) as progress_bar: + total = 0 + while total != length: + chunk_len = min(4096, length-total) + contents += self.fsocket.read(chunk_len) + total += chunk_len + progress_bar.update(chunk_len) print("Download completed.") return "OK", contents def passwd(self, password): self.send_command("PASSWD", password) return self.read_reply() == ["OK"] + + def get_variants(self): + self.send_command("GET_VARIANTS") + reply = self.read_reply()[0] + if reply != "OK": + raise ValueError("Unexpected server reply: expected 'OK', got '{}'".format(reply)) + return self.read_json() def main(): @@ -95,9 +135,11 @@ def main(): 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("--log", action="store_true", help="Display the build log") act_build.add_argument("directory", help="output directory") + act_build.add_argument("variant", nargs="?", default=None, help="variant to build (can be omitted if user is authorised to build only one)") act_passwd = action.add_parser("passwd", help="change password") + act_get_variants = action.add_parser("get_variants", help="get available variants and expiry dates") args = parser.parse_args() cert = args.cert @@ -146,14 +188,22 @@ def main(): 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) + result, contents = client.build(rev, args.variant, args.log) 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.") + elif result == "TOOMANY": + print("Too many builds in a queue. Please wait for others to finish.") else: print("Build failed: {}".format(result)) sys.exit(1) zip_unarchive(contents, args.directory) + elif args.action == "get_variants": + data = client.get_variants() + table = PrettyTable() + table.field_names = ["Variant", "Expiry date"] + table.add_rows(data) + print(table) else: raise ValueError finally: diff --git a/artiq/frontend/artiq_ddb_template.py b/artiq/frontend/artiq_ddb_template.py index 592834e7f..b76031df8 100755 --- a/artiq/frontend/artiq_ddb_template.py +++ b/artiq/frontend/artiq_ddb_template.py @@ -94,7 +94,8 @@ class PeripheralManager: def process_dio(self, rtio_offset, peripheral, num_channels=8): class_names = { "input": "TTLInOut", - "output": "TTLOut" + "output": "TTLOut", + "clkgen": "TTLClockGen" } classes = [ class_names[peripheral["bank_direction_low"]], diff --git a/artiq/gateware/eem.py b/artiq/gateware/eem.py index 9c706a2f0..ae47154c4 100644 --- a/artiq/gateware/eem.py +++ b/artiq/gateware/eem.py @@ -231,10 +231,8 @@ class Urukul(_EEM): target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4)) pads = target.platform.request("urukul{}_dds_reset_sync_in".format(eem)) - pad = Signal(reset=0) - target.specials += DifferentialOutput(pad, pads.p, pads.n) if sync_gen_cls is not None: # AD9910 variant and SYNC_IN from EEM - phy = sync_gen_cls(pad, ftw_width=4) + phy = sync_gen_cls(pad=pads.p, pad_n=pads.n, ftw_width=4) target.submodules += phy target.rtio_channels.append(rtio.Channel.from_phy(phy)) diff --git a/artiq/gateware/eem_7series.py b/artiq/gateware/eem_7series.py index 0cf518922..141923c8d 100644 --- a/artiq/gateware/eem_7series.py +++ b/artiq/gateware/eem_7series.py @@ -5,7 +5,8 @@ from artiq.gateware.rtio.phy import ttl_simple, ttl_serdes_7series, edge_counter def peripheral_dio(module, peripheral, **kwargs): ttl_classes = { "input": ttl_serdes_7series.InOut_8X, - "output": ttl_serdes_7series.Output_8X + "output": ttl_serdes_7series.Output_8X, + "clkgen": ttl_simple.ClockGen } if len(peripheral["ports"]) != 1: raise ValueError("wrong number of ports") diff --git a/artiq/gateware/rtio/phy/ttl_simple.py b/artiq/gateware/rtio/phy/ttl_simple.py index 4484ce3af..192165dc9 100644 --- a/artiq/gateware/rtio/phy/ttl_simple.py +++ b/artiq/gateware/rtio/phy/ttl_simple.py @@ -145,11 +145,16 @@ class InOut(Module): class ClockGen(Module): - def __init__(self, pad, ftw_width=24): + def __init__(self, pad, pad_n=None, ftw_width=24, dci=False): self.rtlink = rtlink.Interface(rtlink.OInterface(ftw_width)) # # # + pad_o = Signal() + if pad_n is None: + self.comb += pad.eq(pad_o) + else: + self.specials += DifferentialOutput(pad_o, pad, pad_n) ftw = Signal(ftw_width) acc = Signal(ftw_width) self.sync.rio += If(self.rtlink.o.stb, ftw.eq(self.rtlink.o.data)) @@ -165,5 +170,5 @@ class ClockGen(Module): acc.eq(0) ) ), - pad.eq(acc[-1]) + pad_o.eq(acc[-1]) ]