Merge branch 'master' into nac3

This commit is contained in:
Sebastien Bourdeauducq 2022-05-24 11:12:20 +08:00
commit 45edb9e0f7
12 changed files with 108 additions and 26 deletions

View File

@ -12,6 +12,7 @@ Highlights:
- Kasli-SoC, a new EEM carrier based on a Zynq SoC, enabling much faster kernel execution. - 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 - HVAMP_8CH 8 channel HV amplifier for Fastino / Zotinos
- Almazny mezzanine board for Mirny - 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). * Softcore targets now use the RISC-V architecture (VexRiscv) instead of OR1K (mor1kx).
* Gateware FPU is supported on KC705 and Kasli 2.0. * Gateware FPU is supported on KC705 and Kasli 2.0.
* Faster compilation for large arrays/lists. * Faster compilation for large arrays/lists.

View File

@ -110,9 +110,10 @@ class CommMgmt:
return ty return ty
def _read_expect(self, 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 {})". raise IOError("Incorrect reply from device: {} (expected {})".
format(self._read_type, ty)) format(header, ty))
def _read_int32(self): def _read_int32(self):
(value, ) = struct.unpack(self.endian + "l", self._read(4)) (value, ) = struct.unpack(self.endian + "l", self._read(4))
@ -159,7 +160,12 @@ class CommMgmt:
def config_read(self, key): def config_read(self, key):
self._write_header(Request.ConfigRead) self._write_header(Request.ConfigRead)
self._write_string(key) 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() return self._read_string()
def config_write(self, key, value): def config_write(self, key, value):

View File

@ -170,11 +170,11 @@
}, },
"bank_direction_low": { "bank_direction_low": {
"type": "string", "type": "string",
"enum": ["input", "output"] "enum": ["input", "output", "clkgen"]
}, },
"bank_direction_high": { "bank_direction_high": {
"type": "string", "type": "string",
"enum": ["input", "output"] "enum": ["input", "output", "clkgen"]
} }
}, },
"required": ["ports", "bank_direction_low", "bank_direction_high"] "required": ["ports", "bank_direction_low", "bank_direction_high"]

View File

@ -14,6 +14,12 @@ from artiq.gui.scientific_spinbox import ScientificSpinBox
logger = logging.getLogger(__name__) 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): class Editor(QtWidgets.QDialog):
def __init__(self, parent, dataset_ctl, key, value): def __init__(self, parent, dataset_ctl, key, value):
QtWidgets.QDialog.__init__(self, parent=parent) QtWidgets.QDialog.__init__(self, parent=parent)
@ -26,7 +32,11 @@ class Editor(QtWidgets.QDialog):
self.setLayout(grid) self.setLayout(grid)
grid.addWidget(QtWidgets.QLabel("Name:"), 0, 0) 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(QtWidgets.QLabel("Value:"), 1, 0)
grid.addWidget(self.get_edit_widget(value), 1, 1) grid.addWidget(self.get_edit_widget(value), 1, 1)
@ -39,8 +49,9 @@ class Editor(QtWidgets.QDialog):
buttons.rejected.connect(self.reject) buttons.rejected.connect(self.reject)
def accept(self): def accept(self):
newkey = self.name_widget.text()
value = self.initial_type(self.get_edit_widget_value()) 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) QtWidgets.QDialog.accept(self)
def get_edit_widget(self, initial_value): def get_edit_widget(self, initial_value):

View File

@ -203,6 +203,7 @@ _WidgetDesc = namedtuple("_WidgetDesc", "uid comment cls arguments")
def setup_from_ddb(ddb): def setup_from_ddb(ddb):
mi_addr = None mi_addr = None
mi_port = None
dds_sysclk = None dds_sysclk = None
description = set() description = set()
@ -235,14 +236,16 @@ def setup_from_ddb(ddb):
description.add(widget) description.add(widget)
elif v["type"] == "controller" and k == "core_moninj": elif v["type"] == "controller" and k == "core_moninj":
mi_addr = v["host"] mi_addr = v["host"]
mi_port = v.get("port_proxy", 1383)
except KeyError: except KeyError:
pass pass
return mi_addr, dds_sysclk, description return mi_addr, mi_port, dds_sysclk, description
class _DeviceManager: class _DeviceManager:
def __init__(self): def __init__(self):
self.mi_addr = None self.mi_addr = None
self.mi_port = None
self.reconnect_mi = asyncio.Event() self.reconnect_mi = asyncio.Event()
self.mi_connection = None self.mi_connection = None
self.mi_connector_task = asyncio.ensure_future(self.mi_connector()) self.mi_connector_task = asyncio.ensure_future(self.mi_connector())
@ -264,10 +267,11 @@ class _DeviceManager:
return ddb return ddb
def notify(self, mod): 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_addr = mi_addr
self.mi_port = mi_port
self.reconnect_mi.set() self.reconnect_mi.set()
self.dds_sysclk = dds_sysclk self.dds_sysclk = dds_sysclk
@ -397,7 +401,7 @@ class _DeviceManager:
new_mi_connection = CommMonInj(self.monitor_cb, self.injection_status_cb, new_mi_connection = CommMonInj(self.monitor_cb, self.injection_status_cb,
self.disconnect_cb) self.disconnect_cb)
try: try:
await new_mi_connection.connect(self.mi_addr, 1383) await new_mi_connection.connect(self.mi_addr, self.mi_port)
except asyncio.CancelledError: except asyncio.CancelledError:
logger.info("cancelled connection to moninj") logger.info("cancelled connection to moninj")
break break

View File

@ -9,6 +9,7 @@ pub enum Error {
MissingSeparator { offset: usize }, MissingSeparator { offset: usize },
Utf8Error(str::Utf8Error), Utf8Error(str::Utf8Error),
NoFlash, NoFlash,
KeyNotFound
} }
impl fmt::Display for Error { impl fmt::Display for Error {
@ -28,6 +29,8 @@ impl fmt::Display for Error {
write!(f, "{}", err), write!(f, "{}", err),
&Error::NoFlash => &Error::NoFlash =>
write!(f, "flash memory is not present"), 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| { f(Lock::take().and_then(|lock| {
let mut iter = Iter::new(lock.data()); let mut iter = Iter::new(lock.data());
let mut value = &[][..]; let mut value = &[][..];
let mut found = false;
while let Some(result) = iter.next() { while let Some(result) = iter.next() {
let (record_key, record_value) = result?; let (record_key, record_value) = result?;
if key.as_bytes() == record_key { if key.as_bytes() == record_key {
found = true;
// last write wins // last write wins
value = record_value value = record_value
} }
} }
Ok(value) if found { Ok(value) } else { Err(Error::KeyNotFound) }
})) }))
} }

View File

@ -529,7 +529,7 @@ fn flash_kernel_worker(io: &Io, aux_mutex: &Mutex,
config::read(config_key, |result| { config::read(config_key, |result| {
match result { match result {
Ok(kernel) if kernel.len() > 0 => unsafe { Ok(kernel) => unsafe {
// kernel CPU cannot access the SPI flash address space directly, // kernel CPU cannot access the SPI flash address space directly,
// so make a copy. // so make a copy.
kern_load(io, &mut session, Vec::from(kernel).as_ref()) kern_load(io, &mut session, Vec::from(kernel).as_ref())

View File

@ -7,7 +7,10 @@ import socket
import ssl import ssl
import io import io
import zipfile import zipfile
import json
from prettytable import PrettyTable
from getpass import getpass from getpass import getpass
from tqdm import tqdm
def get_artiq_cert(): def get_artiq_cert():
@ -54,19 +57,41 @@ class Client:
self.fsocket.write((" ".join(command) + "\n").encode()) self.fsocket.write((" ".join(command) + "\n").encode())
self.fsocket.flush() self.fsocket.flush()
def read_line(self):
return self.fsocket.readline().decode("ascii")
def read_reply(self): def read_reply(self):
return self.fsocket.readline().decode("ascii").split() return self.fsocket.readline().decode("ascii").split()
def read_json(self):
return json.loads(self.fsocket.readline().decode("ascii"))
def login(self, username, password): def login(self, username, password):
self.send_command("LOGIN", username, password) self.send_command("LOGIN", username, password)
return self.read_reply() == ["HELLO"] return self.read_reply() == ["HELLO"]
def build(self, 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) self.send_command("BUILD", rev, variant)
reply = self.read_reply()[0] reply = self.read_reply()[0]
if reply != "BUILDING": if reply != "BUILDING":
return reply, None return reply, None
print("Build in progress. This may take 10-15 minutes.") print("Build in progress. This may take 10-15 minutes.")
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() reply, status = self.read_reply()
if reply != "DONE": if reply != "DONE":
raise ValueError("Unexpected server reply: expected 'DONE', got '{}'".format(reply)) raise ValueError("Unexpected server reply: expected 'DONE', got '{}'".format(reply))
@ -76,7 +101,15 @@ class Client:
reply, length = self.read_reply() reply, length = self.read_reply()
if reply != "PRODUCT": if reply != "PRODUCT":
raise ValueError("Unexpected server reply: expected 'PRODUCT', got '{}'".format(reply)) 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.") print("Download completed.")
return "OK", contents return "OK", contents
@ -84,6 +117,13 @@ class Client:
self.send_command("PASSWD", password) self.send_command("PASSWD", password)
return self.read_reply() == ["OK"] 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(): def main():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
@ -95,9 +135,11 @@ def main():
action.required = True action.required = True
act_build = action.add_parser("build", help="build and download firmware") 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("--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("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_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() args = parser.parse_args()
cert = args.cert cert = args.cert
@ -146,14 +188,22 @@ def main():
if rev is None: if rev is None:
print("Unable to determine currently installed ARTIQ revision. Specify manually using --rev.") print("Unable to determine currently installed ARTIQ revision. Specify manually using --rev.")
sys.exit(1) sys.exit(1)
result, contents = client.build(rev, args.variant) result, contents = client.build(rev, args.variant, args.log)
if result != "OK": if result != "OK":
if result == "UNAUTHORIZED": if result == "UNAUTHORIZED":
print("You are not authorized to build this variant. Your firmware subscription may have expired. Contact helpdesk\x40m-labs.hk.") 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: else:
print("Build failed: {}".format(result)) print("Build failed: {}".format(result))
sys.exit(1) sys.exit(1)
zip_unarchive(contents, args.directory) 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: else:
raise ValueError raise ValueError
finally: finally:

View File

@ -94,7 +94,8 @@ class PeripheralManager:
def process_dio(self, rtio_offset, peripheral, num_channels=8): def process_dio(self, rtio_offset, peripheral, num_channels=8):
class_names = { class_names = {
"input": "TTLInOut", "input": "TTLInOut",
"output": "TTLOut" "output": "TTLOut",
"clkgen": "TTLClockGen"
} }
classes = [ classes = [
class_names[peripheral["bank_direction_low"]], class_names[peripheral["bank_direction_low"]],

View File

@ -231,10 +231,8 @@ class Urukul(_EEM):
target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4)) target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4))
pads = target.platform.request("urukul{}_dds_reset_sync_in".format(eem)) 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 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.submodules += phy
target.rtio_channels.append(rtio.Channel.from_phy(phy)) target.rtio_channels.append(rtio.Channel.from_phy(phy))

View File

@ -5,7 +5,8 @@ from artiq.gateware.rtio.phy import ttl_simple, ttl_serdes_7series, edge_counter
def peripheral_dio(module, peripheral, **kwargs): def peripheral_dio(module, peripheral, **kwargs):
ttl_classes = { ttl_classes = {
"input": ttl_serdes_7series.InOut_8X, "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: if len(peripheral["ports"]) != 1:
raise ValueError("wrong number of ports") raise ValueError("wrong number of ports")

View File

@ -145,11 +145,16 @@ class InOut(Module):
class ClockGen(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)) 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) ftw = Signal(ftw_width)
acc = Signal(ftw_width) acc = Signal(ftw_width)
self.sync.rio += If(self.rtlink.o.stb, ftw.eq(self.rtlink.o.data)) self.sync.rio += If(self.rtlink.o.stb, ftw.eq(self.rtlink.o.data))
@ -165,5 +170,5 @@ class ClockGen(Module):
acc.eq(0) acc.eq(0)
) )
), ),
pad.eq(acc[-1]) pad_o.eq(acc[-1])
] ]