Urukul monitoring (#1142, #1921)

This commit is contained in:
Spaqin 2022-07-07 10:52:53 +08:00 committed by GitHub
parent d17675e9b5
commit 8be945d5c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 198 additions and 31 deletions

View File

@ -45,6 +45,7 @@ Highlights:
switch is supported. switch is supported.
* The "ip" config option can now be set to "use_dhcp" in order to use DHCP to obtain an IP address. * 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. DHCP will also be used if no "ip" config option is set.
* Urukul monitoring and frequency setting (through dashboard) is now supported.
Breaking changes: Breaking changes:

View File

@ -8,6 +8,8 @@ from PyQt5 import QtCore, QtWidgets, QtGui
from sipyco.sync_struct import Subscriber from sipyco.sync_struct import Subscriber
from artiq.coredevice.comm_moninj import * 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.tools import LayoutWidget
from artiq.gui.flowlayout import FlowLayout from artiq.gui.flowlayout import FlowLayout
@ -179,14 +181,45 @@ class _SimpleDisplayWidget(QtWidgets.QFrame):
raise NotImplementedError 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): 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.dm = dm
self.bus_channel = bus_channel self.bus_channel = bus_channel
self.channel = channel self.channel = channel
self.dds_name = title self.dds_name = title
self.cpld = cpld
self.cur_frequency = 0 self.cur_frequency = 0
self.dds_model = dds_model
QtWidgets.QFrame.__init__(self) QtWidgets.QFrame.__init__(self)
@ -249,7 +282,7 @@ class _DDSWidget(QtWidgets.QFrame):
set_grid.addWidget(set_btn, 0, 1, 1, 1) set_grid.addWidget(set_btn, 0, 1, 1, 1)
# for urukuls also allow switching off RF # for urukuls also allow switching off RF
if self.cpld: if self.dds_model.is_urukul:
off_btn = QtWidgets.QToolButton() off_btn = QtWidgets.QToolButton()
off_btn.setText("Off") off_btn.setText("Off")
off_btn.setToolTip("Switch off the output") off_btn.setToolTip("Switch off the output")
@ -276,7 +309,7 @@ class _DDSWidget(QtWidgets.QFrame):
set_btn.clicked.connect(self.set_clicked) set_btn.clicked.connect(self.set_clicked)
apply.clicked.connect(self.apply_changes) apply.clicked.connect(self.apply_changes)
if self.cpld: if self.dds_model.is_urukul:
off_btn.clicked.connect(self.off_clicked) off_btn.clicked.connect(self.off_clicked)
self.value_edit.returnPressed.connect(lambda: self.apply_changes(None)) self.value_edit.returnPressed.connect(lambda: self.apply_changes(None))
self.value_edit.escapePressedConnect(self.cancel_changes) self.value_edit.escapePressedConnect(self.cancel_changes)
@ -293,19 +326,20 @@ class _DDSWidget(QtWidgets.QFrame):
self.value_edit.selectAll() self.value_edit.selectAll()
def off_clicked(self, set): 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): def apply_changes(self, apply):
self.data_stack.setCurrentIndex(0) self.data_stack.setCurrentIndex(0)
self.button_stack.setCurrentIndex(0) self.button_stack.setCurrentIndex(0)
frequency = float(self.value_edit.text())*1e6 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): def cancel_changes(self, cancel):
self.data_stack.setCurrentIndex(0) self.data_stack.setCurrentIndex(0)
self.button_stack.setCurrentIndex(0) self.button_stack.setCurrentIndex(0)
def refresh_display(self): def refresh_display(self):
self.cur_frequency = self.dds_model.cur_frequency
self.value_label.setText("<font size=\"4\">{:.7f}</font>" self.value_label.setText("<font size=\"4\">{:.7f}</font>"
.format(self.cur_frequency/1e6)) .format(self.cur_frequency/1e6))
self.value_edit.setText("{:.7f}" self.value_edit.setText("{:.7f}"
@ -356,7 +390,8 @@ def setup_from_ddb(ddb):
bus_channel = v["arguments"]["bus_channel"] bus_channel = v["arguments"]["bus_channel"]
channel = v["arguments"]["channel"] channel = v["arguments"]["channel"]
dds_sysclk = v["arguments"]["sysclk"] 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) description.add(widget)
elif (v["module"] == "artiq.coredevice.ad9910" elif (v["module"] == "artiq.coredevice.ad9910"
and v["class"] == "AD9910") or \ and v["class"] == "AD9910") or \
@ -368,7 +403,11 @@ def setup_from_ddb(ddb):
dds_cpld = v["arguments"]["cpld_device"] dds_cpld = v["arguments"]["cpld_device"]
spi_dev = ddb[dds_cpld]["arguments"]["spi_device"] spi_dev = ddb[dds_cpld]["arguments"]["spi_device"]
bus_channel = ddb[spi_dev]["arguments"]["channel"] 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) description.add(widget)
elif ( (v["module"] == "artiq.coredevice.ad53xx" and v["class"] == "AD53xx") elif ( (v["module"] == "artiq.coredevice.ad53xx" and v["class"] == "AD53xx")
or (v["module"] == "artiq.coredevice.zotino" and v["class"] == "Zotino")): 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) mi_port = v.get("port_proxy", 1383)
except KeyError: except KeyError:
pass pass
return mi_addr, mi_port, dds_sysclk, description return mi_addr, mi_port, description
class _DeviceManager: class _DeviceManager:
@ -415,15 +454,13 @@ class _DeviceManager:
return ddb return ddb
def notify(self, mod): 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): 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.mi_port = mi_port
self.reconnect_mi.set() self.reconnect_mi.set()
self.dds_sysclk = dds_sysclk
for to_remove in self.description - description: for to_remove in self.description - description:
widget = self.widgets_by_uid[to_remove.uid] widget = self.widgets_by_uid[to_remove.uid]
del self.widgets_by_uid[to_remove.uid] del self.widgets_by_uid[to_remove.uid]
@ -512,13 +549,13 @@ class _DeviceManager:
scheduling["flush"]) scheduling["flush"])
logger.info("Submitted '%s', RID is %d", title, rid) 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 # 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 # urukuls need CPLD init and switch to on
# keep previous config if it was set already # keep previous config if it was set already
cpld_dev = """self.setattr_device("core_cache") 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") cpld_init = """cfg = self.core_cache.get("_{cpld}_cfg")
if len(cfg) > 0: if len(cfg) > 0:
self.{cpld}.cfg_reg = cfg[0] self.{cpld}.cfg_reg = cfg[0]
@ -526,10 +563,10 @@ class _DeviceManager:
self.{cpld}.init() self.{cpld}.init()
self.core_cache.put("_{cpld}_cfg", [self.{cpld}.cfg_reg]) self.core_cache.put("_{cpld}_cfg", [self.{cpld}.cfg_reg])
cfg = self.core_cache.get("_{cpld}_cfg") cfg = self.core_cache.get("_{cpld}_cfg")
""".format(cpld=dds_cpld) """.format(cpld=dds_model.cpld)
cfg_sw = """self.{}.cfg_sw(True) cfg_sw = """self.{}.cfg_sw(True)
cfg[0] = self.{}.cfg_reg cfg[0] = self.{}.cfg_reg
""".format(dds_channel, dds_cpld) """.format(dds_channel, dds_model.cpld)
else: else:
cpld_dev = "" cpld_dev = ""
cpld_init = "" cpld_init = ""
@ -560,7 +597,7 @@ class _DeviceManager:
"SetDDS", "SetDDS",
"Set DDS {} {}MHz".format(dds_channel, freq/1e6))) "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 # urukul only
toggle_exp = textwrap.dedent(""" toggle_exp = textwrap.dedent("""
from artiq.experiment import * from artiq.experiment import *
@ -586,7 +623,7 @@ class _DeviceManager:
self.{ch}.init() self.{ch}.init()
self.{ch}.cfg_sw({sw}) self.{ch}.cfg_sw({sw})
cfg[0] = self.{cpld}.cfg_reg 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( asyncio.ensure_future(
self._submit_by_content( self._submit_by_content(
toggle_exp, toggle_exp,
@ -619,11 +656,11 @@ class _DeviceManager:
elif probe == TTLProbe.oe.value: elif probe == TTLProbe.oe.value:
widget.cur_oe = bool(value) widget.cur_oe = bool(value)
widget.refresh_display() widget.refresh_display()
if (channel, probe) in self.dds_widgets: elif (channel, probe) in self.dds_widgets:
widget = self.dds_widgets[(channel, probe)] 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() widget.refresh_display()
if (channel, probe) in self.dac_widgets: elif (channel, probe) in self.dac_widgets:
widget = self.dac_widgets[(channel, probe)] widget = self.dac_widgets[(channel, probe)]
widget.cur_value = value widget.cur_value = value
widget.refresh_display() widget.refresh_display()

View File

@ -3,7 +3,7 @@ from migen.build.generic_platform import *
from migen.genlib.io import DifferentialOutput from migen.genlib.io import DifferentialOutput
from artiq.gateware import rtio 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.suservo import servo, pads as servo_pads
from artiq.gateware.rtio.phy import servo as rtservo, fastino, phaser from artiq.gateware.rtio.phy import servo as rtservo, fastino, phaser
@ -222,13 +222,13 @@ class Urukul(_EEM):
return ios return ios
@classmethod @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) 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.platform.request("urukul{}_spi_n".format(eem)))
target.submodules += phy target.submodules += spi_phy
target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4)) target.rtio_channels.append(rtio.Channel.from_phy(spi_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))
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
@ -237,9 +237,14 @@ class Urukul(_EEM):
target.rtio_channels.append(rtio.Channel.from_phy(phy)) target.rtio_channels.append(rtio.Channel.from_phy(phy))
pads = target.platform.request("urukul{}_io_update".format(eem)) pads = target.platform.request("urukul{}_io_update".format(eem))
phy = ttl_out_cls(pads.p, pads.n) io_upd_phy = ttl_out_cls(pads.p, pads.n)
target.submodules += phy target.submodules += io_upd_phy
target.rtio_channels.append(rtio.Channel.from_phy(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: if eem_aux is not None:
for signal in "sw0 sw1 sw2 sw3".split(): for signal in "sw0 sw1 sw2 sw3".split():
pads = target.platform.request("urukul{}_{}".format(eem, signal)) pads = target.platform.request("urukul{}_{}".format(eem, signal))
@ -247,6 +252,7 @@ class Urukul(_EEM):
target.submodules += phy target.submodules += phy
target.rtio_channels.append(rtio.Channel.from_phy(phy)) target.rtio_channels.append(rtio.Channel.from_phy(phy))
class Sampler(_EEM): class Sampler(_EEM):
@staticmethod @staticmethod
def io(eem, eem_aux, iostandard): def io(eem, eem_aux, iostandard):

View File

@ -47,7 +47,7 @@ def peripheral_urukul(module, peripheral, **kwargs):
else: else:
sync_gen_cls = None sync_gen_cls = None
eem.Urukul.add_std(module, port, port_aux, ttl_serdes_7series.Output_8X, 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): def peripheral_novogorny(module, peripheral, **kwargs):

View File

@ -3,6 +3,11 @@ from migen import *
from artiq.gateware import ad9_dds from artiq.gateware import ad9_dds
from artiq.gateware.rtio.phy.wishbone import RT2WB 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): class AD9914(Module):
def __init__(self, pads, nchannels, onehot=False, **kwargs): 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), [ self.sync.rio_phy += If(current_address == 2**len(pads.a), [
If(selected(c), probe.eq(ftw)) If(selected(c), probe.eq(ftw))
for c, (probe, ftw) in enumerate(zip(self.probes, ftws))]) 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")
)
)
)