diff --git a/artiq/coredevice/almazny.py b/artiq/coredevice/almazny.py new file mode 100644 index 0000000000..9e02069862 --- /dev/null +++ b/artiq/coredevice/almazny.py @@ -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)) diff --git a/artiq/coredevice/coredevice_generic.schema.json b/artiq/coredevice/coredevice_generic.schema.json index 79527cf311..0148837d8e 100644 --- a/artiq/coredevice/coredevice_generic.schema.json +++ b/artiq/coredevice/coredevice_generic.schema.json @@ -496,6 +496,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 b3643bbf14..117c4aa1da 100644 --- a/artiq/coredevice/mirny.py +++ b/artiq/coredevice/mirny.py @@ -31,16 +31,6 @@ # 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 - class Mirny: """ @@ -177,106 +167,3 @@ def write_ext(self, addr, length, data, ext_div=SPIT_WR): if length < 32: data <<= 32 - length 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) \ No newline at end of file diff --git a/artiq/frontend/artiq_ddb_template.py b/artiq/frontend/artiq_ddb_template.py index b6d9294a37..ccc9c3eaf2 100755 --- a/artiq/frontend/artiq_ddb_template.py +++ b/artiq/frontend/artiq_ddb_template.py @@ -284,6 +284,7 @@ def process_urukul(self, rtio_offset, peripheral): 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(""" @@ -323,6 +324,20 @@ def process_mirny(self, rtio_offset, peripheral): 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"] + '"' @@ -340,13 +355,12 @@ def process_mirny(self, rtio_offset, peripheral): 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 a4f999029d..cbf5c24ea2 100755 --- a/artiq/frontend/artiq_sinara_tester.py +++ b/artiq/frontend/artiq_sinara_tester.py @@ -97,7 +97,7 @@ def build(self): 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