From 8be945d5c7b22280ac2b052cd549d946b091fa3d Mon Sep 17 00:00:00 2001 From: Spaqin Date: Thu, 7 Jul 2022 10:52:53 +0800 Subject: [PATCH] Urukul monitoring (#1142, #1921) --- RELEASE_NOTES.rst | 1 + artiq/dashboard/moninj.py | 81 ++++++++++++++++------ artiq/gateware/eem.py | 22 +++--- artiq/gateware/eem_7series.py | 2 +- artiq/gateware/rtio/phy/dds.py | 123 +++++++++++++++++++++++++++++++++ 5 files changed, 198 insertions(+), 31 deletions(-) diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 19a97a206..213f77598 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -45,6 +45,7 @@ Highlights: switch is supported. * The "ip" config option can now be set to "use_dhcp" in order to use DHCP to obtain an IP address. DHCP will also be used if no "ip" config option is set. +* Urukul monitoring and frequency setting (through dashboard) is now supported. Breaking changes: diff --git a/artiq/dashboard/moninj.py b/artiq/dashboard/moninj.py index df5854259..ef514b4b1 100644 --- a/artiq/dashboard/moninj.py +++ b/artiq/dashboard/moninj.py @@ -8,6 +8,8 @@ from PyQt5 import QtCore, QtWidgets, QtGui from sipyco.sync_struct import Subscriber from artiq.coredevice.comm_moninj import * +from artiq.coredevice.ad9910 import _AD9910_REG_PROFILE0, _AD9910_REG_PROFILE7, _AD9910_REG_FTW +from artiq.coredevice.ad9912_reg import AD9912_POW1 from artiq.gui.tools import LayoutWidget from artiq.gui.flowlayout import FlowLayout @@ -179,14 +181,45 @@ class _SimpleDisplayWidget(QtWidgets.QFrame): raise NotImplementedError +class _DDSModel: + def __init__(self, dds_type, ref_clk, cpld=None, pll=1, clk_div=0): + self.cpld = cpld + self.cur_frequency = 0 + self.cur_reg = 0 + self.dds_type = dds_type + self.is_urukul = dds_type in ["AD9910", "AD9912"] + + if dds_type == "AD9914": + self.ftw_per_hz = 2**32 / ref_clk + else: + if dds_type == "AD9910": + max_freq = 1 << 32 + clk_mult = [4, 1, 2, 4] + elif dds_type == "AD9912": # AD9912 + max_freq = 1 << 48 + clk_mult = [1, 1, 2, 4] + else: + raise NotImplementedError + sysclk = ref_clk / clk_mult[clk_div] * pll + self.ftw_per_hz = 1 / sysclk * max_freq + + def monitor_update(self, probe, value): + if self.dds_type == "AD9912": + value = value << 16 + self.cur_frequency = self._ftw_to_freq(value) + + def _ftw_to_freq(self, ftw): + return ftw / self.ftw_per_hz + + class _DDSWidget(QtWidgets.QFrame): - def __init__(self, dm, title, bus_channel=0, channel=0, cpld=None): + def __init__(self, dm, title, bus_channel=0, channel=0, dds_model=None): self.dm = dm self.bus_channel = bus_channel self.channel = channel self.dds_name = title - self.cpld = cpld self.cur_frequency = 0 + self.dds_model = dds_model QtWidgets.QFrame.__init__(self) @@ -249,7 +282,7 @@ class _DDSWidget(QtWidgets.QFrame): set_grid.addWidget(set_btn, 0, 1, 1, 1) # for urukuls also allow switching off RF - if self.cpld: + if self.dds_model.is_urukul: off_btn = QtWidgets.QToolButton() off_btn.setText("Off") off_btn.setToolTip("Switch off the output") @@ -276,7 +309,7 @@ class _DDSWidget(QtWidgets.QFrame): set_btn.clicked.connect(self.set_clicked) apply.clicked.connect(self.apply_changes) - if self.cpld: + if self.dds_model.is_urukul: off_btn.clicked.connect(self.off_clicked) self.value_edit.returnPressed.connect(lambda: self.apply_changes(None)) self.value_edit.escapePressedConnect(self.cancel_changes) @@ -293,19 +326,20 @@ class _DDSWidget(QtWidgets.QFrame): self.value_edit.selectAll() def off_clicked(self, set): - self.dm.dds_channel_toggle(self.dds_name, self.cpld, sw=False) + self.dm.dds_channel_toggle(self.dds_name, self.dds_model, sw=False) def apply_changes(self, apply): self.data_stack.setCurrentIndex(0) self.button_stack.setCurrentIndex(0) frequency = float(self.value_edit.text())*1e6 - self.dm.dds_set_frequency(self.dds_name, self.cpld, frequency) + self.dm.dds_set_frequency(self.dds_name, self.dds_model, frequency) def cancel_changes(self, cancel): self.data_stack.setCurrentIndex(0) self.button_stack.setCurrentIndex(0) def refresh_display(self): + self.cur_frequency = self.dds_model.cur_frequency self.value_label.setText("{:.7f}" .format(self.cur_frequency/1e6)) self.value_edit.setText("{:.7f}" @@ -356,7 +390,8 @@ def setup_from_ddb(ddb): bus_channel = v["arguments"]["bus_channel"] channel = v["arguments"]["channel"] dds_sysclk = v["arguments"]["sysclk"] - widget = _WidgetDesc(k, comment, _DDSWidget, (k, bus_channel, channel)) + model = _DDSModel(v["class"], dds_sysclk) + widget = _WidgetDesc(k, comment, _DDSWidget, (k, bus_channel, channel, model)) description.add(widget) elif (v["module"] == "artiq.coredevice.ad9910" and v["class"] == "AD9910") or \ @@ -368,7 +403,11 @@ def setup_from_ddb(ddb): dds_cpld = v["arguments"]["cpld_device"] spi_dev = ddb[dds_cpld]["arguments"]["spi_device"] bus_channel = ddb[spi_dev]["arguments"]["channel"] - widget = _WidgetDesc(k, comment, _DDSWidget, (k, bus_channel, channel, dds_cpld)) + pll = v["arguments"]["pll_n"] + refclk = ddb[dds_cpld]["arguments"]["refclk"] + clk_div = v["arguments"].get("clk_div", 0) + model = _DDSModel( v["class"], refclk, dds_cpld, pll, clk_div) + widget = _WidgetDesc(k, comment, _DDSWidget, (k, bus_channel, channel, model)) description.add(widget) elif ( (v["module"] == "artiq.coredevice.ad53xx" and v["class"] == "AD53xx") or (v["module"] == "artiq.coredevice.zotino" and v["class"] == "Zotino")): @@ -385,7 +424,7 @@ def setup_from_ddb(ddb): mi_port = v.get("port_proxy", 1383) except KeyError: pass - return mi_addr, mi_port, dds_sysclk, description + return mi_addr, mi_port, description class _DeviceManager: @@ -415,15 +454,13 @@ class _DeviceManager: return ddb def notify(self, mod): - mi_addr, mi_port, dds_sysclk, description = setup_from_ddb(self.ddb) + mi_addr, mi_port, description = setup_from_ddb(self.ddb) 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 - for to_remove in self.description - description: widget = self.widgets_by_uid[to_remove.uid] del self.widgets_by_uid[to_remove.uid] @@ -512,13 +549,13 @@ class _DeviceManager: scheduling["flush"]) logger.info("Submitted '%s', RID is %d", title, rid) - def dds_set_frequency(self, dds_channel, dds_cpld, freq): + def dds_set_frequency(self, dds_channel, dds_model, freq): # create kernel and fill it in and send-by-content - if dds_cpld: + if dds_model.is_urukul: # urukuls need CPLD init and switch to on # keep previous config if it was set already cpld_dev = """self.setattr_device("core_cache") - self.setattr_device("{}")""".format(dds_cpld) + self.setattr_device("{}")""".format(dds_model.cpld) cpld_init = """cfg = self.core_cache.get("_{cpld}_cfg") if len(cfg) > 0: self.{cpld}.cfg_reg = cfg[0] @@ -526,10 +563,10 @@ class _DeviceManager: self.{cpld}.init() self.core_cache.put("_{cpld}_cfg", [self.{cpld}.cfg_reg]) cfg = self.core_cache.get("_{cpld}_cfg") - """.format(cpld=dds_cpld) + """.format(cpld=dds_model.cpld) cfg_sw = """self.{}.cfg_sw(True) cfg[0] = self.{}.cfg_reg - """.format(dds_channel, dds_cpld) + """.format(dds_channel, dds_model.cpld) else: cpld_dev = "" cpld_init = "" @@ -560,7 +597,7 @@ class _DeviceManager: "SetDDS", "Set DDS {} {}MHz".format(dds_channel, freq/1e6))) - def dds_channel_toggle(self, dds_channel, dds_cpld, sw=True): + def dds_channel_toggle(self, dds_model, sw=True): # urukul only toggle_exp = textwrap.dedent(""" from artiq.experiment import * @@ -586,7 +623,7 @@ class _DeviceManager: self.{ch}.init() self.{ch}.cfg_sw({sw}) cfg[0] = self.{cpld}.cfg_reg - """.format(ch=dds_channel, cpld=dds_cpld, sw=sw)) + """.format(ch=dds_channel, cpld=dds_model.cpld, sw=sw)) asyncio.ensure_future( self._submit_by_content( toggle_exp, @@ -619,11 +656,11 @@ class _DeviceManager: elif probe == TTLProbe.oe.value: widget.cur_oe = bool(value) widget.refresh_display() - if (channel, probe) in self.dds_widgets: + elif (channel, probe) in self.dds_widgets: widget = self.dds_widgets[(channel, probe)] - widget.cur_frequency = value*self.dds_sysclk/2**32 + widget.dds_model.monitor_update(probe, value) widget.refresh_display() - if (channel, probe) in self.dac_widgets: + elif (channel, probe) in self.dac_widgets: widget = self.dac_widgets[(channel, probe)] widget.cur_value = value widget.refresh_display() diff --git a/artiq/gateware/eem.py b/artiq/gateware/eem.py index 23a85eca8..467f3cae2 100644 --- a/artiq/gateware/eem.py +++ b/artiq/gateware/eem.py @@ -3,7 +3,7 @@ from migen.build.generic_platform import * from migen.genlib.io import DifferentialOutput from artiq.gateware import rtio -from artiq.gateware.rtio.phy import spi2, ad53xx_monitor, grabber +from artiq.gateware.rtio.phy import spi2, ad53xx_monitor, dds, grabber from artiq.gateware.suservo import servo, pads as servo_pads from artiq.gateware.rtio.phy import servo as rtservo, fastino, phaser @@ -222,13 +222,13 @@ class Urukul(_EEM): return ios @classmethod - def add_std(cls, target, eem, eem_aux, ttl_out_cls, sync_gen_cls=None, iostandard=default_iostandard): + def add_std(cls, target, eem, eem_aux, ttl_out_cls, dds_type, sync_gen_cls=None, iostandard=default_iostandard): cls.add_extension(target, eem, eem_aux, iostandard=iostandard) - phy = spi2.SPIMaster(target.platform.request("urukul{}_spi_p".format(eem)), + spi_phy = spi2.SPIMaster(target.platform.request("urukul{}_spi_p".format(eem)), target.platform.request("urukul{}_spi_n".format(eem))) - target.submodules += phy - target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4)) + target.submodules += spi_phy + target.rtio_channels.append(rtio.Channel.from_phy(spi_phy, ififo_depth=4)) pads = target.platform.request("urukul{}_dds_reset_sync_in".format(eem)) if sync_gen_cls is not None: # AD9910 variant and SYNC_IN from EEM @@ -237,9 +237,14 @@ class Urukul(_EEM): target.rtio_channels.append(rtio.Channel.from_phy(phy)) pads = target.platform.request("urukul{}_io_update".format(eem)) - phy = ttl_out_cls(pads.p, pads.n) - target.submodules += phy - target.rtio_channels.append(rtio.Channel.from_phy(phy)) + io_upd_phy = ttl_out_cls(pads.p, pads.n) + target.submodules += io_upd_phy + target.rtio_channels.append(rtio.Channel.from_phy(io_upd_phy)) + + dds_monitor = dds.UrukulMonitor(spi_phy, io_upd_phy, dds_type) + target.submodules += dds_monitor + spi_phy.probes.extend(dds_monitor.probes) + if eem_aux is not None: for signal in "sw0 sw1 sw2 sw3".split(): pads = target.platform.request("urukul{}_{}".format(eem, signal)) @@ -247,6 +252,7 @@ class Urukul(_EEM): target.submodules += phy target.rtio_channels.append(rtio.Channel.from_phy(phy)) + class Sampler(_EEM): @staticmethod def io(eem, eem_aux, iostandard): diff --git a/artiq/gateware/eem_7series.py b/artiq/gateware/eem_7series.py index 1f5f770c2..1983b1009 100644 --- a/artiq/gateware/eem_7series.py +++ b/artiq/gateware/eem_7series.py @@ -47,7 +47,7 @@ def peripheral_urukul(module, peripheral, **kwargs): else: sync_gen_cls = None eem.Urukul.add_std(module, port, port_aux, ttl_serdes_7series.Output_8X, - sync_gen_cls, **kwargs) + peripheral["dds"], sync_gen_cls, **kwargs) def peripheral_novogorny(module, peripheral, **kwargs): diff --git a/artiq/gateware/rtio/phy/dds.py b/artiq/gateware/rtio/phy/dds.py index c542937fa..ca7494c20 100644 --- a/artiq/gateware/rtio/phy/dds.py +++ b/artiq/gateware/rtio/phy/dds.py @@ -3,6 +3,11 @@ from migen import * from artiq.gateware import ad9_dds from artiq.gateware.rtio.phy.wishbone import RT2WB +from artiq.coredevice.spi2 import SPI_CONFIG_ADDR, SPI_DATA_ADDR, SPI_END +from artiq.coredevice.urukul import CS_DDS_CH0, CS_DDS_MULTI, CFG_IO_UPDATE, CS_CFG + +from artiq.coredevice.ad9912_reg import AD9912_POW1 +from artiq.coredevice.ad9910 import _AD9910_REG_PROFILE0, _AD9910_REG_PROFILE7, _AD9910_REG_FTW class AD9914(Module): def __init__(self, pads, nchannels, onehot=False, **kwargs): @@ -54,3 +59,121 @@ class AD9914(Module): self.sync.rio_phy += If(current_address == 2**len(pads.a), [ If(selected(c), probe.eq(ftw)) for c, (probe, ftw) in enumerate(zip(self.probes, ftws))]) + + +class UrukulMonitor(Module): + def __init__(self, spi_phy, io_update_phy, dds, nchannels=4): + self.spi_phy = spi_phy + self.io_update_phy = io_update_phy + + self.probes = [Signal(32) for i in range(nchannels)] + + self.cs = Signal(8) + self.current_data = Signal.like(self.spi_phy.rtlink.o.data) + current_address = Signal.like(self.spi_phy.rtlink.o.address) + data_length = Signal(8) + flags = Signal(8) + + self.sync.rio += If(self.spi_phy.rtlink.o.stb, [ + current_address.eq(self.spi_phy.rtlink.o.address), + self.current_data.eq(self.spi_phy.rtlink.o.data), + If(self.spi_phy.rtlink.o.address == SPI_CONFIG_ADDR, [ + self.cs.eq(self.spi_phy.rtlink.o.data[24:]), + data_length.eq(self.spi_phy.rtlink.o.data[8:16] + 1), + flags.eq(self.spi_phy.rtlink.o.data[0:8]) + ]) + ]) + + for i in range(nchannels): + ch_sel = Signal() + self.comb += ch_sel.eq( + ((self.cs == CS_DDS_MULTI) | (self.cs == i + CS_DDS_CH0)) + & (current_address == SPI_DATA_ADDR) + ) + + if dds == "ad9912": + mon_cls = _AD9912Monitor + elif dds == "ad9910": + mon_cls = _AD9910Monitor + else: + raise NotImplementedError + + monitor = mon_cls(self.current_data, data_length, flags, ch_sel) + self.submodules += monitor + + self.sync.rio_phy += [ + If(ch_sel & self.is_io_update(), self.probes[i].eq(monitor.ftw)) + ] + + def is_io_update(self): + # shifted 8 bits left for 32-bit bus + reg_io_upd = (self.cs == CS_CFG) & self.current_data[8 + CFG_IO_UPDATE] + phy_io_upd = False + if self.io_update_phy: + phy_io_upd = self.io_update_phy.rtlink.o.stb & self.io_update_phy.rtlink.o.data + return phy_io_upd | reg_io_upd + + +class _AD9912Monitor(Module): + def __init__(self, current_data, data_length, flags, ch_sel): + self.ftw = Signal(32, reset_less=True) + + fsm = ClockDomainsRenamer("rio_phy")(FSM(reset_state="IDLE")) + self.submodules += fsm + + reg_addr = current_data[16:29] + reg_write = ~current_data[31] + + fsm.act("IDLE", + If(ch_sel & reg_write, + If((data_length == 16) & (reg_addr == AD9912_POW1), + NextState("READ") + ) + ) + ) + + fsm.act("READ", + If(ch_sel, + If(flags & SPI_END, + # lower 16 bits (16-32 from 48-bit transfer) + NextValue(self.ftw[:16], current_data[16:]), + NextState("IDLE") + ).Else( + NextValue(self.ftw[16:], current_data[:16]) + ) + ) + ) + + +class _AD9910Monitor(Module): + def __init__(self, current_data, data_length, flags, ch_sel): + self.ftw = Signal(32, reset_less=True) + + fsm = ClockDomainsRenamer("rio_phy")(FSM(reset_state="IDLE")) + self.submodules += fsm + + reg_addr = current_data[24:29] + reg_write = ~current_data[31] + + fsm.act("IDLE", + If(ch_sel & reg_write, + If((data_length == 8) & (_AD9910_REG_PROFILE7 >= reg_addr) & (reg_addr >= _AD9910_REG_PROFILE0), + NextState("READ") + ).Elif(reg_addr == _AD9910_REG_FTW, + If((data_length == 24) & (flags & SPI_END), + NextValue(self.ftw[:16], current_data[8:24]) + ).Elif(data_length == 8, + NextState("READ") + ) + ) + ) + ) + + fsm.act("READ", + If(ch_sel, + If(flags & SPI_END, + NextValue(self.ftw, current_data), + NextState("IDLE") + ) + ) + )