From 095fb9e3332ded9013d9872c3f855cf725141653 Mon Sep 17 00:00:00 2001 From: Spaqin Date: Tue, 11 Jan 2022 09:55:39 +0800 Subject: [PATCH] add Almazny support (#1780) --- RELEASE_NOTES.rst | 1 + .../coredevice/coredevice_generic.schema.json | 4 + artiq/coredevice/mirny.py | 117 +++++++++++++++++- artiq/frontend/artiq_ddb_template.py | 12 ++ artiq/frontend/artiq_sinara_tester.py | 67 +++++++++- 5 files changed, 198 insertions(+), 3 deletions(-) diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index ceb7600d9..a97f43d5e 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -11,6 +11,7 @@ Highlights: * New hardware support: - Kasli-SoC, a new EEM carrier based on a Zynq SoC, enabling much faster kernel execution. - HVAMP_8CH 8 channel HV amplifier for Fastino / Zotinos + - Almazny mezzanine board for Mirny * Softcore targets now use the RISC-V architecture (VexRiscv) instead of OR1K (mor1kx). * Faster compilation for large arrays/lists. * Phaser: diff --git a/artiq/coredevice/coredevice_generic.schema.json b/artiq/coredevice/coredevice_generic.schema.json index 274d7e2aa..f5d096788 100644 --- a/artiq/coredevice/coredevice_generic.schema.json +++ b/artiq/coredevice/coredevice_generic.schema.json @@ -409,6 +409,10 @@ } ], "default": 0 + }, + "almazny": { + "type": "boolean", + "default": false } }, "required": ["ports"] diff --git a/artiq/coredevice/mirny.py b/artiq/coredevice/mirny.py index ddcdd1931..70daf1f61 100644 --- a/artiq/coredevice/mirny.py +++ b/artiq/coredevice/mirny.py @@ -31,6 +31,16 @@ 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 + class Mirny: """ @@ -132,11 +142,114 @@ class Mirny: self.bus.write(((channel | 8) << 25) | (att << 16)) @kernel - def write_ext(self, addr, length, data): + def write_ext(self, addr, length, data, ext_div=SPIT_WR): """Perform SPI write to a prefixed address""" self.bus.set_config_mu(SPI_CONFIG, 8, SPIT_WR, SPI_CS) self.bus.write(addr << 25) - self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, length, SPIT_WR, SPI_CS) + self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, length, ext_div, SPI_CS) 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 0a14a06be..b24f63315 100755 --- a/artiq/frontend/artiq_ddb_template.py +++ b/artiq/frontend/artiq_ddb_template.py @@ -294,6 +294,18 @@ class PeripheralManager: name=mirny_name, refclk=peripheral["refclk"], clk_sel=clk_sel) + almazny = peripheral.get("almazny", False) + if almazny: + self.gen(""" + device_db["{name}_almazny"] = {{ + "type": "local", + "module": "artiq.coredevice.mirny", + "class": "Almazny", + "arguments": {{ + "host_mirny": "{name}_cpld", + }}, + }}""", + name=mirny_name) return next(channel) diff --git a/artiq/frontend/artiq_sinara_tester.py b/artiq/frontend/artiq_sinara_tester.py index 702f22efb..31f947631 100755 --- a/artiq/frontend/artiq_sinara_tester.py +++ b/artiq/frontend/artiq_sinara_tester.py @@ -59,6 +59,7 @@ class SinaraTester(EnvExperiment): self.mirnies = dict() self.suservos = dict() self.suschannels = dict() + self.almaznys = dict() ddb = self.get_device_db() for name, desc in ddb.items(): @@ -96,6 +97,8 @@ 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"): + self.almaznys[name] = self.get_device(name) # Remove Urukul, Sampler, Zotino and Mirny control signals # from TTL outs (tested separately) and remove Urukuls covered by @@ -351,6 +354,68 @@ class SinaraTester(EnvExperiment): for channel in channels: channel.pulse(100*ms) delay(100*ms) + @kernel + def init_almazny(self, almazny): + self.core.break_realtime() + almazny.init() + almazny.output_toggle(True) + + @kernel + def almazny_set_attenuators_mu(self, almazny, ch, atts): + self.core.break_realtime() + almazny.set_att_mu(ch, atts) + + @kernel + def almazny_set_attenuators(self, almazny, ch, atts): + self.core.break_realtime() + almazny.set_att(ch, atts) + + @kernel + def almazny_toggle_output(self, almazny, rf_on): + self.core.break_realtime() + almazny.output_toggle(rf_on) + + def test_almaznys(self): + print("*** Testing Almaznys.") + for name, almazny in sorted(self.almaznys.items(), key=lambda x: x[0]): + print(name + "...") + print("Initializing Mirny CPLDs...") + for name, cpld in sorted(self.mirny_cplds.items(), key=lambda x: x[0]): + print(name + "...") + self.init_mirny(cpld) + print("...done") + + print("Testing attenuators. Frequencies:") + for card_n, channels in enumerate(chunker(self.mirnies, 4)): + for channel_n, (channel_name, channel_dev) in enumerate(channels): + frequency = 2000 + card_n * 250 + channel_n * 50 + print("{}\t{}MHz".format(channel_name, frequency*2)) + self.setup_mirny(channel_dev, frequency) + print("{} info: {}".format(channel_name, channel_dev.info())) + self.init_almazny(almazny) + print("RF ON, all attenuators ON. Press ENTER when done.") + for i in range(4): + self.almazny_set_attenuators_mu(almazny, i, 63) + input() + print("RF ON, half power attenuators ON. Press ENTER when done.") + for i in range(4): + self.almazny_set_attenuators(almazny, i, 15.5) + input() + print("RF ON, all attenuators OFF. Press ENTER when done.") + for i in range(4): + self.almazny_set_attenuators(almazny, i, 0) + input() + print("SR outputs are OFF. Press ENTER when done.") + self.almazny_toggle_output(almazny, False) + input() + print("RF ON, all attenuators are ON. Press ENTER when done.") + for i in range(4): + self.almazny_set_attenuators(almazny, i, 31.5) + self.almazny_toggle_output(almazny, True) + input() + print("RF OFF. Press ENTER when done.") + self.almazny_toggle_output(almazny, False) + input() def test_mirnies(self): print("*** Testing Mirny PLLs.") @@ -365,7 +430,7 @@ class SinaraTester(EnvExperiment): print("Frequencies:") for card_n, channels in enumerate(chunker(self.mirnies, 4)): for channel_n, (channel_name, channel_dev) in enumerate(channels): - frequency = 1000*(card_n + 1) + channel_n * 100 + 8 # Extra 8 Hz for easier observation + frequency = 1000*(card_n + 1) + channel_n * 100 print("{}\t{}MHz".format(channel_name, frequency)) self.setup_mirny(channel_dev, frequency) print("{} info: {}".format(channel_name, channel_dev.info()))