diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index cd41ac526..71179152d 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -15,6 +15,8 @@ Highlights: - Sampler: fixed ADC MU to Volt conversion factor for Sampler v2.2+. 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. + - 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. * 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 diff --git a/artiq/coredevice/almazny.py b/artiq/coredevice/almazny.py new file mode 100644 index 000000000..ca6fc8a1c --- /dev/null +++ b/artiq/coredevice/almazny.py @@ -0,0 +1,197 @@ +from numpy import int32 + +from artiq.language.core import nac3, Kernel, KernelInvariant, kernel, portable +from artiq.language.units import us + +from artiq.coredevice.core import Core +from artiq.coredevice.mirny import Mirny +from artiq.coredevice.spi2 import * + + +# 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 + + +@nac3 +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 + """ + + core: KernelInvariant[Core] + mirny_cpld: KernelInvariant[Mirny] + att_mu: Kernel[list[int32]] + channel_sw: Kernel[list[int32]] + output_enable: Kernel[bool] + + def __init__(self, dmgr, host_mirny): + self.mirny_cpld = dmgr.get(host_mirny) + self.core = self.mirny_cpld.core + 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: float) -> int32: + """ + 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: int32) -> float: + """ + Convert a digital attenuator setting to dB. + + :param att_mu: attenuator setting in machine units + :return: attenuator setting in dB + """ + return float(att_mu) / 2. + + @kernel + def set_att(self, channel: int32, att: float, rf_switch: bool = 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: int32, att_mu: int32, rf_switch: bool = 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: bool): + """ + 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 + self.core.delay(100. * us) + new_reg = (en << ALMAZNY_LEGACY_OE_SHIFT) | (cfg_reg & 0x3FF) + self.mirny_cpld.write_reg(1, new_reg) + self.core.delay(100. * us) + + @kernel + def _flip_mu_bits(self, mu: int32) -> int32: + # 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: int32): + 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 + ) + self.core.delay(100. * us) + + @kernel + def _update_all_registers(self): + for i in range(4): + self._update_register(i) + + +@nac3 +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) + """ + + core: KernelInvariant[Core] + mirny_cpld: KernelInvariant[Mirny] + channel: KernelInvariant[int32] + + def __init__(self, dmgr, host_mirny, channel): + self.mirny_cpld = dmgr.get(host_mirny) + self.core = self.mirny_cpld.core + self.channel = channel + + @portable + def to_mu(self, att: float, enable: bool, led: bool) -> int32: + """ + 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 = 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: int32): + """ + 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: float, enable: bool, led: bool = 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)) diff --git a/artiq/coredevice/coredevice_generic.schema.json b/artiq/coredevice/coredevice_generic.schema.json index c7e74c68e..5fc8661e7 100644 --- a/artiq/coredevice/coredevice_generic.schema.json +++ b/artiq/coredevice/coredevice_generic.schema.json @@ -464,6 +464,11 @@ "almazny": { "type": "boolean", "default": false + }, + "almazny_hw_rev": { + "type": "string", + "pattern": "^v[0-9]+\\.[0-9]+", + "default": "v1.2" } }, "required": ["ports"] diff --git a/artiq/coredevice/mirny.py b/artiq/coredevice/mirny.py index 9aa7f88d8..3aeeefbf0 100644 --- a/artiq/coredevice/mirny.py +++ b/artiq/coredevice/mirny.py @@ -32,16 +32,6 @@ WE = 1 << 24 # supported CPLD code version 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 - @nac3 class Mirny: @@ -183,114 +173,3 @@ class Mirny: if length < 32: data <<= 32 - length self.bus.write(data) - - -@nac3 -class Almazny: - """ - Almazny (High frequency mezzanine board for Mirny) - - :param host_mirny - Mirny device Almazny is connected to - """ - - core: KernelInvariant[Core] - mirny_cpld: KernelInvariant[Mirny] - att_mu: Kernel[list[int32]] - channel_sw: Kernel[list[int32]] - output_enable: Kernel[bool] - - def __init__(self, dmgr, host_mirny): - self.mirny_cpld = dmgr.get(host_mirny) - self.core = self.mirny_cpld.core - 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: float) -> int32: - """ - 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: int32) -> float: - """ - Convert a digital attenuator setting to dB. - - :param att_mu: attenuator setting in machine units - :return: attenuator setting in dB - """ - return float(att_mu) / 2. - - @kernel - def set_att(self, channel: int32, att: float, rf_switch: bool = 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: int32, att_mu: int32, rf_switch: bool = 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: bool): - """ - 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 - self.core.delay(100. * us) - new_reg = (en << ALMAZNY_OE_SHIFT) | (cfg_reg & 0x3FF) - self.mirny_cpld.write_reg(1, new_reg) - self.core.delay(100. * us) - - @kernel - def _flip_mu_bits(self, mu: int32) -> int32: - # 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: int32): - 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 - ) - self.core.delay(100. * us) - - @kernel - def _update_all_registers(self): - for i in range(4): - self._update_register(i) diff --git a/artiq/frontend/artiq_ddb_template.py b/artiq/frontend/artiq_ddb_template.py index 9322e7f50..8cb24088e 100755 --- a/artiq/frontend/artiq_ddb_template.py +++ b/artiq/frontend/artiq_ddb_template.py @@ -287,6 +287,7 @@ class PeripheralManager: return next(channel) def process_mirny(self, rtio_offset, peripheral): + legacy_almazny = ("v1.0", "v1.1") mirny_name = self.get_name("mirny") channel = count(0) self.gen(""" @@ -326,6 +327,20 @@ class PeripheralManager: name=mirny_name, 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"] if isinstance(peripheral["clk_sel"], str): clk_sel = '"' + peripheral["clk_sel"] + '"' @@ -343,13 +358,12 @@ class PeripheralManager: name=mirny_name, refclk=peripheral["refclk"], clk_sel=clk_sel) - almazny = peripheral.get("almazny", False) - if almazny: + if peripheral["almazny"] and peripheral["almazny_hw_rev"] in legacy_almazny: self.gen(""" device_db["{name}_almazny"] = {{ "type": "local", - "module": "artiq.coredevice.mirny", - "class": "Almazny", + "module": "artiq.coredevice.almazny", + "class": "AlmaznyLegacy", "arguments": {{ "host_mirny": "{name}_cpld", }}, diff --git a/artiq/frontend/artiq_sinara_tester.py b/artiq/frontend/artiq_sinara_tester.py index d1888d177..2a2d91cc4 100755 --- a/artiq/frontend/artiq_sinara_tester.py +++ b/artiq/frontend/artiq_sinara_tester.py @@ -115,7 +115,7 @@ class SinaraTester(EnvExperiment): self.suservos[name] = self.get_device(name) elif (module, cls) == ("artiq.coredevice.suservo", "Channel"): 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) # Remove Urukul, Sampler, Zotino and Mirny control signals diff --git a/doc/manual/core_drivers_reference.rst b/doc/manual/core_drivers_reference.rst index 5d1562267..9782ce274 100644 --- a/doc/manual/core_drivers_reference.rst +++ b/doc/manual/core_drivers_reference.rst @@ -92,6 +92,12 @@ RF generation drivers .. automodule:: artiq.coredevice.mirny :members: +:mod:`artiq.coredevice.almazny` module +++++++++++++++++++++++++++++++++++++++ + +.. automodule:: artiq.coredevice.almazny + :members: + :mod:`artiq.coredevice.adf5356` module +++++++++++++++++++++++++++++++++++++++