forked from M-Labs/artiq
1
0
Fork 0

Almazny v1.2 support

Based on PR #2060 by Robert Jördens.
This commit is contained in:
Sebastien Bourdeauducq 2023-05-09 12:54:48 +08:00
parent 0e7e30d46e
commit 45cd438fb8
7 changed files with 210 additions and 118 deletions

View File

@ -15,6 +15,8 @@ Highlights:
- Sampler: fixed ADC MU to Volt conversion factor for Sampler v2.2+. - Sampler: fixed ADC MU to Volt conversion factor for Sampler v2.2+.
For earlier hardware versions, specify the hardware version in the device For earlier hardware versions, specify the hardware version in the device
database file (e.g. ``"hw_rev": "v2.1"``) to use the correct conversion factor. database file (e.g. ``"hw_rev": "v2.1"``) to use the correct conversion factor.
- Almazny v1.2. It is incompatible with the legacy versions and is the default. To use legacy
versions, specify ``almazny_hw_rev`` in the JSON description.
- Metlino and Sayma support has been dropped due to complications with synchronous RTIO clocking. - Metlino and Sayma support has been dropped due to complications with synchronous RTIO clocking.
* CPU (on softcore platforms) and AXI bus (on Zynq) are now clocked synchronously with the RTIO * CPU (on softcore platforms) and AXI bus (on Zynq) are now clocked synchronously with the RTIO
clock, to facilitate implementation of local processing on DRTIO satellites, and to slightly clock, to facilitate implementation of local processing on DRTIO satellites, and to slightly

178
artiq/coredevice/almazny.py Normal file
View File

@ -0,0 +1,178 @@
from artiq.language.core import kernel, portable
from numpy import int32
# almazny-specific data
ALMAZNY_LEGACY_REG_BASE = 0x0C
ALMAZNY_LEGACY_OE_SHIFT = 12
# higher SPI write divider to match almazny shift register timing
# min SER time before SRCLK rise = 125ns
# -> div=32 gives 125ns for data before clock rise
# works at faster dividers too but could be less reliable
ALMAZNY_LEGACY_SPIT_WR = 32
class AlmaznyLegacy:
"""
Almazny (High frequency mezzanine board for Mirny)
This applies to Almazny hardware v1.1 and earlier.
Use :class:`artiq.coredevice.almazny.AlmaznyChannel` for Almazny v1.2 and later.
:param host_mirny - Mirny device Almazny is connected to
"""
def __init__(self, dmgr, host_mirny):
self.mirny_cpld = dmgr.get(host_mirny)
self.att_mu = [0x3f] * 4
self.channel_sw = [0] * 4
self.output_enable = False
@kernel
def init(self):
self.output_toggle(self.output_enable)
@kernel
def att_to_mu(self, att):
"""
Convert an attenuator setting in dB to machine units.
:param att: attenuator setting in dB [0-31.5]
:return: attenuator setting in machine units
"""
mu = round(att * 2.0)
if mu > 63 or mu < 0:
raise ValueError("Invalid Almazny attenuator settings!")
return mu
@kernel
def mu_to_att(self, att_mu):
"""
Convert a digital attenuator setting to dB.
:param att_mu: attenuator setting in machine units
:return: attenuator setting in dB
"""
return att_mu / 2
@kernel
def set_att(self, channel, att, rf_switch=True):
"""
Sets attenuators on chosen shift register (channel).
:param channel - index of the register [0-3]
:param att_mu - attenuation setting in dBm [0-31.5]
:param rf_switch - rf switch (bool)
"""
self.set_att_mu(channel, self.att_to_mu(att), rf_switch)
@kernel
def set_att_mu(self, channel, att_mu, rf_switch=True):
"""
Sets attenuators on chosen shift register (channel).
:param channel - index of the register [0-3]
:param att_mu - attenuation setting in machine units [0-63]
:param rf_switch - rf switch (bool)
"""
self.channel_sw[channel] = 1 if rf_switch else 0
self.att_mu[channel] = att_mu
self._update_register(channel)
@kernel
def output_toggle(self, oe):
"""
Toggles output on all shift registers on or off.
:param oe - toggle output enable (bool)
"""
self.output_enable = oe
cfg_reg = self.mirny_cpld.read_reg(1)
en = 1 if self.output_enable else 0
delay(100 * us)
new_reg = (en << ALMAZNY_LEGACY_OE_SHIFT) | (cfg_reg & 0x3FF)
self.mirny_cpld.write_reg(1, new_reg)
delay(100 * us)
@kernel
def _flip_mu_bits(self, mu):
# in this form MSB is actually 0.5dB attenuator
# unnatural for users, so we flip the six bits
return (((mu & 0x01) << 5)
| ((mu & 0x02) << 3)
| ((mu & 0x04) << 1)
| ((mu & 0x08) >> 1)
| ((mu & 0x10) >> 3)
| ((mu & 0x20) >> 5))
@kernel
def _update_register(self, ch):
self.mirny_cpld.write_ext(
ALMAZNY_LEGACY_REG_BASE + ch,
8,
self._flip_mu_bits(self.att_mu[ch]) | (self.channel_sw[ch] << 6),
ALMAZNY_LEGACY_SPIT_WR
)
delay(100 * us)
@kernel
def _update_all_registers(self):
for i in range(4):
self._update_register(i)
class AlmaznyChannel:
"""
One Almazny channel
Almazny is a mezzanine for the Quad PLL RF source Mirny that exposes and
controls the frequency-doubled outputs.
This driver requires Almazny hardware revision v1.2 or later
and Mirny CPLD gateware v0.3 or later.
Use :class:`artiq.coredevice.almazny.AlmaznyLegacy` for Almazny hardware v1.1 and earlier.
:param host_mirny: Mirny CPLD device name
:param channel: channel index (0-3)
"""
def __init__(self, dmgr, host_mirny, channel):
self.channel = channel
self.mirny_cpld = dmgr.get(cpld_device)
@portable
def to_mu(self, att, enable, led):
"""
Convert an attenuation in dB, RF switch state and LED state to machine
units.
:param att: attenuator setting in dB (0-31.5)
:param enable: RF switch state (bool)
:param led: LED state (bool)
:return: channel setting in machine units
"""
mu = int32(round(att * 2.))
if mu >= 64 or mu < 0:
raise ValueError("Attenuation out of range")
# unfortunate hardware design: bit reverse
mu = ((mu & 0x15) << 1) | ((mu >> 1) & 0x15)
mu = ((mu & 0x03) << 4) | (mu & 0x0c) | ((mu >> 4) & 0x03)
if enable:
mu |= 1 << 6
if led:
mu |= 1 << 7
return mu
@kernel
def set_mu(self, mu):
"""
Set channel state (machine units).
:param mu: channel state in machine units.
"""
self.mirny_cpld.write_ext(
addr=0xc + self.channel, length=8, data=mu, ext_div=32)
@kernel
def set(self, att, enable, led=False):
"""
Set attenuation, RF switch, and LED state (SI units).
:param att: attenuator setting in dB (0-31.5)
:param enable: RF switch state (bool)
:param led: LED state (bool)
"""
self.set_mu(self.to_mu(att, enable, led))

View File

@ -512,6 +512,11 @@
"almazny": { "almazny": {
"type": "boolean", "type": "boolean",
"default": false "default": false
},
"almazny_hw_rev": {
"type": "string",
"pattern": "^v[0-9]+\\.[0-9]+",
"default": "v1.2"
} }
}, },
"required": ["ports"] "required": ["ports"]

View File

@ -31,16 +31,6 @@ WE = 1 << 24
# supported CPLD code version # supported CPLD code version
PROTO_REV_MATCH = 0x0 PROTO_REV_MATCH = 0x0
# almazny-specific data
ALMAZNY_REG_BASE = 0x0C
ALMAZNY_OE_SHIFT = 12
# higher SPI write divider to match almazny shift register timing
# min SER time before SRCLK rise = 125ns
# -> div=32 gives 125ns for data before clock rise
# works at faster dividers too but could be less reliable
ALMAZNY_SPIT_WR = 32
class Mirny: class Mirny:
""" """
@ -177,106 +167,3 @@ class Mirny:
if length < 32: if length < 32:
data <<= 32 - length data <<= 32 - length
self.bus.write(data) self.bus.write(data)
class Almazny:
"""
Almazny (High frequency mezzanine board for Mirny)
:param host_mirny - Mirny device Almazny is connected to
"""
def __init__(self, dmgr, host_mirny):
self.mirny_cpld = dmgr.get(host_mirny)
self.att_mu = [0x3f] * 4
self.channel_sw = [0] * 4
self.output_enable = False
@kernel
def init(self):
self.output_toggle(self.output_enable)
@kernel
def att_to_mu(self, att):
"""
Convert an attenuator setting in dB to machine units.
:param att: attenuator setting in dB [0-31.5]
:return: attenuator setting in machine units
"""
mu = round(att * 2.0)
if mu > 63 or mu < 0:
raise ValueError("Invalid Almazny attenuator settings!")
return mu
@kernel
def mu_to_att(self, att_mu):
"""
Convert a digital attenuator setting to dB.
:param att_mu: attenuator setting in machine units
:return: attenuator setting in dB
"""
return att_mu / 2
@kernel
def set_att(self, channel, att, rf_switch=True):
"""
Sets attenuators on chosen shift register (channel).
:param channel - index of the register [0-3]
:param att_mu - attenuation setting in dBm [0-31.5]
:param rf_switch - rf switch (bool)
"""
self.set_att_mu(channel, self.att_to_mu(att), rf_switch)
@kernel
def set_att_mu(self, channel, att_mu, rf_switch=True):
"""
Sets attenuators on chosen shift register (channel).
:param channel - index of the register [0-3]
:param att_mu - attenuation setting in machine units [0-63]
:param rf_switch - rf switch (bool)
"""
self.channel_sw[channel] = 1 if rf_switch else 0
self.att_mu[channel] = att_mu
self._update_register(channel)
@kernel
def output_toggle(self, oe):
"""
Toggles output on all shift registers on or off.
:param oe - toggle output enable (bool)
"""
self.output_enable = oe
cfg_reg = self.mirny_cpld.read_reg(1)
en = 1 if self.output_enable else 0
delay(100 * us)
new_reg = (en << ALMAZNY_OE_SHIFT) | (cfg_reg & 0x3FF)
self.mirny_cpld.write_reg(1, new_reg)
delay(100 * us)
@kernel
def _flip_mu_bits(self, mu):
# in this form MSB is actually 0.5dB attenuator
# unnatural for users, so we flip the six bits
return (((mu & 0x01) << 5)
| ((mu & 0x02) << 3)
| ((mu & 0x04) << 1)
| ((mu & 0x08) >> 1)
| ((mu & 0x10) >> 3)
| ((mu & 0x20) >> 5))
@kernel
def _update_register(self, ch):
self.mirny_cpld.write_ext(
ALMAZNY_REG_BASE + ch,
8,
self._flip_mu_bits(self.att_mu[ch]) | (self.channel_sw[ch] << 6),
ALMAZNY_SPIT_WR
)
delay(100 * us)
@kernel
def _update_all_registers(self):
for i in range(4):
self._update_register(i)

View File

@ -287,6 +287,7 @@ class PeripheralManager:
return next(channel) return next(channel)
def process_mirny(self, rtio_offset, peripheral): def process_mirny(self, rtio_offset, peripheral):
legacy_almazny = ("v1.0", "v1.1")
mirny_name = self.get_name("mirny") mirny_name = self.get_name("mirny")
channel = count(0) channel = count(0)
self.gen(""" self.gen("""
@ -326,6 +327,20 @@ class PeripheralManager:
name=mirny_name, name=mirny_name,
mchn=i) mchn=i)
if peripheral["almazny"] and peripheral["almazny_hw_rev"] not in legacy_almazny:
self.gen("""
device_db["{name}_almazny{i}"] = {{
"type": "local",
"module": "artiq.coredevice.almazny",
"class": "AlmaznyChannel",
"arguments": {{
"cpld_device": "{name}_cpld",
"channel": {i},
}},
}}""",
name=mirny_name,
i=i)
clk_sel = peripheral["clk_sel"] clk_sel = peripheral["clk_sel"]
if isinstance(peripheral["clk_sel"], str): if isinstance(peripheral["clk_sel"], str):
clk_sel = '"' + peripheral["clk_sel"] + '"' clk_sel = '"' + peripheral["clk_sel"] + '"'
@ -343,13 +358,12 @@ class PeripheralManager:
name=mirny_name, name=mirny_name,
refclk=peripheral["refclk"], refclk=peripheral["refclk"],
clk_sel=clk_sel) clk_sel=clk_sel)
almazny = peripheral.get("almazny", False) if peripheral["almazny"] and peripheral["almazny_hw_rev"] in legacy_almazny:
if almazny:
self.gen(""" self.gen("""
device_db["{name}_almazny"] = {{ device_db["{name}_almazny"] = {{
"type": "local", "type": "local",
"module": "artiq.coredevice.mirny", "module": "artiq.coredevice.almazny",
"class": "Almazny", "class": "AlmaznyLegacy",
"arguments": {{ "arguments": {{
"host_mirny": "{name}_cpld", "host_mirny": "{name}_cpld",
}}, }},

View File

@ -98,7 +98,7 @@ class SinaraTester(EnvExperiment):
self.suservos[name] = self.get_device(name) self.suservos[name] = self.get_device(name)
elif (module, cls) == ("artiq.coredevice.suservo", "Channel"): elif (module, cls) == ("artiq.coredevice.suservo", "Channel"):
self.suschannels[name] = self.get_device(name) self.suschannels[name] = self.get_device(name)
elif (module, cls) == ("artiq.coredevice.mirny", "Almazny"): elif (module, cls) == ("artiq.coredevice.almazny", "AlmaznyLegacy"):
self.almaznys[name] = self.get_device(name) self.almaznys[name] = self.get_device(name)
# Remove Urukul, Sampler, Zotino and Mirny control signals # Remove Urukul, Sampler, Zotino and Mirny control signals

View File

@ -92,6 +92,12 @@ RF generation drivers
.. automodule:: artiq.coredevice.mirny .. automodule:: artiq.coredevice.mirny
:members: :members:
:mod:`artiq.coredevice.almazny` module
++++++++++++++++++++++++++++++++++++++
.. automodule:: artiq.coredevice.almazny
:members:
:mod:`artiq.coredevice.adf5356` module :mod:`artiq.coredevice.adf5356` module
+++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++