From ab8247b3d7e9b815b210b579b2194b621bf860e7 Mon Sep 17 00:00:00 2001 From: linuswck Date: Mon, 11 Sep 2023 17:28:33 +0800 Subject: [PATCH] Shuttler: Add coredevice example code for Shuttler This example code: - Demonstrates the init flow for Shuttler - Blinks LED L0, L1 - Demonstrates the real-time control of relay - Includes example fns for configuring the PDQ Output Channel in mu --- artiq/examples/kasli_shuttler/device_db.py | 330 ++++++++++++++++++ .../kasli_shuttler/repository/shuttler.py | 330 ++++++++++++++++++ 2 files changed, 660 insertions(+) create mode 100644 artiq/examples/kasli_shuttler/device_db.py create mode 100644 artiq/examples/kasli_shuttler/repository/shuttler.py diff --git a/artiq/examples/kasli_shuttler/device_db.py b/artiq/examples/kasli_shuttler/device_db.py new file mode 100644 index 000000000..b87f8dc63 --- /dev/null +++ b/artiq/examples/kasli_shuttler/device_db.py @@ -0,0 +1,330 @@ +core_addr = "192.168.1.73" + +device_db = { + "core": { + "type": "local", + "module": "artiq.coredevice.core", + "class": "Core", + "arguments": {"host": core_addr, "ref_period": 1e-09, "target": "rv32g"}, + }, + "core_log": { + "type": "controller", + "host": "::1", + "port": 1068, + "command": "aqctl_corelog -p {port} --bind {bind} " + core_addr + }, + "core_moninj": { + "type": "controller", + "host": "::1", + "port_proxy": 1383, + "port": 1384, + "command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr + }, + "core_cache": { + "type": "local", + "module": "artiq.coredevice.cache", + "class": "CoreCache" + }, + "core_dma": { + "type": "local", + "module": "artiq.coredevice.dma", + "class": "CoreDMA" + }, + + "i2c_switch0": { + "type": "local", + "module": "artiq.coredevice.i2c", + "class": "I2CSwitch", + "arguments": {"address": 0xe0} + }, + "i2c_switch1": { + "type": "local", + "module": "artiq.coredevice.i2c", + "class": "I2CSwitch", + "arguments": {"address": 0xe2} + }, +} + +device_db["efc_led0"] = { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 0x040000}, +} + +device_db["efc_led1"] = { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 0x040001}, +} + +device_db["pdq_config"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Config", + "arguments": {"channel": 0x040002}, +} + +device_db["pdq_trigger"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Trigger", + "arguments": {"channel": 0x040003}, +} + +device_db["pdq0_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x040004}, +} + +device_db["pdq0_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x040005}, +} + +device_db["pdq1_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x040006}, +} + +device_db["pdq1_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x040007}, +} + +device_db["pdq2_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x040008}, +} + +device_db["pdq2_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x040009}, +} + +device_db["pdq3_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x04000A}, +} + +device_db["pdq3_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x04000B}, +} + +device_db["pdq4_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x04000C}, +} + +device_db["pdq4_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x04000D}, +} + +device_db["pdq5_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x04000E}, +} + +device_db["pdq5_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x04000F}, +} + +device_db["pdq6_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x040010}, +} + +device_db["pdq6_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x040011}, +} + +device_db["pdq7_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x040012}, +} + +device_db["pdq7_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x040013}, +} + +device_db["pdq8_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x040014}, +} + +device_db["pdq8_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x040015}, +} + +device_db["pdq9_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x040016}, +} + +device_db["pdq9_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x040017}, +} + +device_db["pdq10_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x040018}, +} + +device_db["pdq10_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x040019}, +} + +device_db["pdq11_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x04001A}, +} + +device_db["pdq11_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x04001B}, +} + +device_db["pdq12_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x04001C}, +} + +device_db["pdq12_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x04001D}, +} + +device_db["pdq13_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x04001E}, +} + +device_db["pdq13_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x04001F}, +} + +device_db["pdq14_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x040020}, +} + +device_db["pdq14_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x040021}, +} + +device_db["pdq15_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x040022}, +} + +device_db["pdq15_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x040023}, +} + +device_db["spi_afe_relay"] = { + "type": "local", + "module": "artiq.coredevice.spi2", + "class": "SPIMaster", + "arguments": {"channel": 0x040024} +} + +device_db["afe_relay"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Relay", + "arguments": { + "spi_device": "spi_afe_relay", + } +} + +device_db["spi_afe_adc"] = { + "type": "local", + "module": "artiq.coredevice.spi2", + "class": "SPIMaster", + "arguments": {"channel": 0x040025} +} + +device_db["afe_adc"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "ADC", + "arguments": { + "spi_device": "spi_afe_adc", + } +} diff --git a/artiq/examples/kasli_shuttler/repository/shuttler.py b/artiq/examples/kasli_shuttler/repository/shuttler.py new file mode 100644 index 000000000..70e01d4f9 --- /dev/null +++ b/artiq/examples/kasli_shuttler/repository/shuttler.py @@ -0,0 +1,330 @@ +from artiq.experiment import * +from artiq.coredevice.shuttler import shuttler_volt_to_mu + +DAC_Fs_MHZ = 125 +CORDIC_GAIN = 1.64676 + +@portable +def pdq_phase_offset(offset_degree): + return round(offset_degree / 360 * (2 ** 16)) + +@portable +def pdq_freq_mu(freq_mhz): + return round(float(2) ** 32 / DAC_Fs_MHZ * freq_mhz) + +@portable +def pdq_chirp_rate_mu(freq_mhz_per_us): + return round(float(2) ** 32 * freq_mhz_per_us / (DAC_Fs_MHZ ** 2)) + +@portable +def pdq_freq_sweep(start_f_MHz, end_f_MHz, time_us): + return pdq_chirp_rate_mu((end_f_MHz - start_f_MHz)/(time_us)) + +@portable +def pdq_volt_amp_mu(volt): + return shuttler_volt_to_mu(volt) + +@portable +def pdq_volt_damp_mu(volt_per_us): + return round(float(2) ** 30 * (volt_per_us / 20) / DAC_Fs_MHZ) + +@portable +def pdq_volt_ddamp_mu(volt_per_us_square): + return round(float(2) ** 46 * (volt_per_us_square / 20) * 2 / (DAC_Fs_MHZ ** 2)) + +@portable +def pdq_volt_dddamp_mu(volt_per_us_cube): + return round(float(2) ** 46 * (volt_per_us_cube / 20) * 6 / (DAC_Fs_MHZ ** 3)) + +@portable +def pdq_dds_amp_mu(volt): + return pdq_volt_amp_mu(volt / CORDIC_GAIN) + +@portable +def pdq_dds_damp_mu(volt_per_us): + return pdq_volt_damp_mu(volt_per_us / CORDIC_GAIN) + +@portable +def pdq_dds_ddamp_mu(volt_per_us_square): + return pdq_volt_ddamp_mu(volt_per_us_square / CORDIC_GAIN) + +@portable +def pdq_dds_dddamp_mu(volt_per_us_cube): + return pdq_volt_dddamp_mu(volt_per_us_cube / CORDIC_GAIN) + +class Shuttler(EnvExperiment): + def build(self): + self.setattr_device("core") + self.setattr_device("core_dma") + self.setattr_device("scheduler") + self.leds = [ self.get_device("efc_led{}".format(i)) for i in range(2) ] + self.setattr_device("pdq_config") + self.setattr_device("pdq_trigger") + self.pdq_volt = [ self.get_device("pdq{}_volt".format(i)) for i in range(16) ] + self.pdq_dds = [ self.get_device("pdq{}_dds".format(i)) for i in range(16) ] + self.setattr_device("afe_relay") + self.setattr_device("afe_adc") + + + @kernel + def record(self): + with self.core_dma.record("example_waveform"): + self.example_waveform() + + @kernel + def init(self): + self.led() + self.relay_init() + self.adc_init() + self.pdq_reset() + + @kernel + def run(self): + self.core.reset() + self.core.break_realtime() + self.init() + + self.record() + example_waveform_handle = self.core_dma.get_handle("example_waveform") + + print("Example Waveforms are on OUT0 and OUT1") + self.core.break_realtime() + while not(self.scheduler.check_termination()): + delay(1*s) + self.core_dma.playback_handle(example_waveform_handle) + + @kernel + def pdq_reset(self): + for i in range(16): + self.pdq_channel_reset(i) + # To avoid RTIO Underflow + delay(50*us) + + @kernel + def pdq_channel_reset(self, ch): + self.pdq_volt[ch].set_waveform( + a0=0, + a1=0, + a2=0, + a3=0, + ) + self.pdq_dds[ch].set_waveform( + b0=0, + b1=0, + b2=0, + b3=0, + c0=0, + c1=0, + c2=0, + ) + self.pdq_trigger.trigger(1 << ch) + + @kernel + def example_waveform(self): + # Equation of Output Waveform + # w(t_us) = a(t_us) + b(t_us) * cos(c(t_us)) + # Step 1: + # Enable the Output Relay of OUT0 and OUT1 + # Step 2: Cosine Wave Frequency Sweep from 10kHz to 50kHz in 500us + # OUT0: b(t_us) = 1 + # c(t_us) = 2 * pi * (0.08 * t_us ^ 2 + 0.01 * t_us) + # OUT1: b(t_us) = 1 + # c(t_us) = 2 * pi * (0.05 * t_us) + # Step 3(after 500us): Cosine Wave with 180 Degree Phase Offset + # OUT0: b(t_us) = 1 + # c(t_us) = 2 * pi * (0.05 * t_us) + pi + # OUT1: b(t_us) = 1 + # c(t_us) = 2 * pi * (0.05 * t_us) + # Step 4(after 500us): Cosine Wave with Amplitude Envelop + # OUT0: b(t_us) = -0.0001367187 * t_us ^ 2 + 0.06835937 * t_us + # c(t_us) = 2 * pi * (0.05 * t_us) + # OUT1: b(t_us) = -0.0001367187 * t_us ^ 2 + 0.06835937 * t_us + # c(t_us) = 0 + # Step 5(after 500us): Sawtooth Wave Modulated with 50kHz Cosine Wave + # OUT0: a(t_us) = 0.01 * t_us - 5 + # b(t_us) = 1 + # c(t_us) = 2 * pi * (0.05 * t_us) + # OUT1: a(t_us) = 0.01 * t_us - 5 + # Step 6(after 1000us): A Combination of Previous Waveforms + # OUT0: a(t_us) = 0.01 * t_us - 5 + # b(t_us) = -0.0001367187 * t_us ^ 2 + 0.06835937 * t_us + # c(t_us) = 2 * pi * (0.08 * t_us ^ 2 + 0.01 * t_us) + # Step 7(after 500us): Mirrored Waveform in Step 6 + # OUT0: a(t_us) = 2.5 + -0.01 * (1000 ^ 2) * t_us + # b(t_us) = 0.0001367187 * t_us ^ 2 - 0.06835937 * t_us + # c(t_us) = 2 * pi * (-0.08 * t_us ^ 2 + 0.05 * t_us) + pi + # Step 8(after 500us): + # Disable Output Relay of OUT0 and OUT1 + # Reset OUT0 and OUT1 + + ## Step 1 ## + self.afe_relay.enable(0b11) + + ## Step 2 ## + start_f_MHz = 0.01 + end_f_MHz = 0.05 + duration_us = 500 + # OUT0 and OUT1 have their frequency and phase aligned at 500us + self.pdq_dds[0].set_waveform( + b0=pdq_dds_amp_mu(1.0), + b1=0, + b2=0, + b3=0, + c0=0, + c1=pdq_freq_mu(start_f_MHz), + c2=pdq_freq_sweep(start_f_MHz, end_f_MHz, duration_us), + ) + self.pdq_dds[1].set_waveform( + b0=pdq_dds_amp_mu(1.0), + b1=0, + b2=0, + b3=0, + c0=0, + c1=pdq_freq_mu(end_f_MHz), + c2=0, + ) + self.pdq_trigger.trigger(0b11) + delay(500*us) + + ## Step 3 ## + # OUT0 and OUT1 has 180 degree phase difference + self.pdq_dds[0].set_waveform( + b0=pdq_dds_amp_mu(1.0), + b1=0, + b2=0, + b3=0, + c0=pdq_phase_offset(180.0), + c1=pdq_freq_mu(end_f_MHz), + c2=0, + ) + # Phase and Output Setting of OUT1 is retained + # if the channel is not triggered or config is not cleared + self.pdq_trigger.trigger(0b1) + delay(500*us) + + ## Step 4 ## + # b(0) = 0, b(250) = 8.545, b(500) = 0 + self.pdq_dds[0].set_waveform( + b0=0, + b1=pdq_dds_damp_mu(0.06835937), + b2=pdq_dds_ddamp_mu(-0.0001367187), + b3=0, + c0=0, + c1=pdq_freq_mu(end_f_MHz), + c2=0, + ) + self.pdq_dds[1].set_waveform( + b0=0, + b1=pdq_dds_damp_mu(0.06835937), + b2=pdq_dds_ddamp_mu(-0.0001367187), + b3=0, + c0=0, + c1=0, + c2=0, + ) + self.pdq_trigger.trigger(0b11) + delay(500*us) + + ## Step 5 ## + self.pdq_volt[0].set_waveform( + a0=pdq_volt_amp_mu(-5.0), + a1=int32(pdq_volt_damp_mu(0.01)), + a2=0, + a3=0, + ) + self.pdq_dds[0].set_waveform( + b0=pdq_dds_amp_mu(1.0), + b1=0, + b2=0, + b3=0, + c0=0, + c1=pdq_freq_mu(end_f_MHz), + c2=0, + ) + self.pdq_volt[1].set_waveform( + a0=pdq_volt_amp_mu(-5.0), + a1=int32(pdq_volt_damp_mu(0.01)), + a2=0, + a3=0, + ) + self.pdq_dds[1].set_waveform( + b0=0, + b1=0, + b2=0, + b3=0, + c0=0, + c1=0, + c2=0, + ) + self.pdq_trigger.trigger(0b11) + delay(1000*us) + + ## Step 6 ## + self.pdq_volt[0].set_waveform( + a0=pdq_volt_amp_mu(-2.5), + a1=int32(pdq_volt_damp_mu(0.01)), + a2=0, + a3=0, + ) + self.pdq_dds[0].set_waveform( + b0=0, + b1=pdq_dds_damp_mu(0.06835937), + b2=pdq_dds_ddamp_mu(-0.0001367187), + b3=0, + c0=0, + c1=pdq_freq_mu(start_f_MHz), + c2=pdq_freq_sweep(start_f_MHz, end_f_MHz, duration_us), + ) + self.pdq_trigger.trigger(0b1) + self.pdq_channel_reset(1) + delay(500*us) + + ## Step 7 ## + self.pdq_volt[0].set_waveform( + a0=pdq_volt_amp_mu(2.5), + a1=int32(pdq_volt_damp_mu(-0.01)), + a2=0, + a3=0, + ) + self.pdq_dds[0].set_waveform( + b0=0, + b1=pdq_dds_damp_mu(-0.06835937), + b2=pdq_dds_ddamp_mu(0.0001367187), + b3=0, + c0=pdq_phase_offset(180.0), + c1=pdq_freq_mu(end_f_MHz), + c2=pdq_freq_sweep(end_f_MHz, start_f_MHz, duration_us), + ) + self.pdq_trigger.trigger(0b1) + delay(500*us) + + ## Step 8 ## + self.afe_relay.enable(0) + self.pdq_channel_reset(0) + self.pdq_channel_reset(1) + + @kernel + def led(self): + for i in range(2): + for j in range(3): + self.leds[i].pulse(.1*s) + delay(.1*s) + + @kernel + def relay_init(self): + self.afe_relay.init() + self.afe_relay.enable(0x0000) + + @kernel + def adc_init(self): + delay_mu(int64(self.core.ref_multiplier)) + self.afe_adc.power_up() + + delay_mu(int64(self.core.ref_multiplier)) + assert self.afe_adc.read_id() >> 4 == 0x038d + + delay_mu(int64(self.core.ref_multiplier)) + # The actual output voltage is limited by the hardware, the calculated calibration gain and offset. + # For example, if the system has a calibration gain of 1.06, then the max output voltage = 10 / 1.06 = 9.43V. + # Setting a value larger than 9.43V will result in overflow. + self.afe_adc.calibrate(self.pdq_volt, self.pdq_trigger, self.pdq_config)