From 211500089f59799054d52f3ebdfee87b594508e6 Mon Sep 17 00:00:00 2001 From: Etienne Wodey Date: Thu, 8 Oct 2020 23:36:50 +0200 Subject: [PATCH] coredevice: mirny/adf5355: add basic high-level interface Signed-off-by: Etienne Wodey --- artiq/coredevice/adf5355.py | 483 ++++++++++++++++++++- artiq/coredevice/adf5355_reg.py | 742 ++++++++++++++++++++++++++++++++ artiq/coredevice/mirny.py | 71 ++- 3 files changed, 1256 insertions(+), 40 deletions(-) create mode 100644 artiq/coredevice/adf5355_reg.py diff --git a/artiq/coredevice/adf5355.py b/artiq/coredevice/adf5355.py index a23df6701..6bfd18da5 100644 --- a/artiq/coredevice/adf5355.py +++ b/artiq/coredevice/adf5355.py @@ -9,14 +9,39 @@ on Mirny-style prefixed SPI buses. # https://www.analog.com/media/en/technical-documentation/user-guides/EV-ADF5356SD1Z-UG-1087.pdf -from artiq.language.core import kernel, delay -from artiq.language.units import us +from artiq.language.core import kernel, portable, rpc, delay +from artiq.language.units import us, GHz, MHz +from artiq.language.types import TInt32, TInt64 from artiq.coredevice import spi2 as spi +from artiq.coredevice.adf5355_reg import * -SPI_CONFIG = (0*spi.SPI_OFFLINE | 0*spi.SPI_END | - 0*spi.SPI_INPUT | 1*spi.SPI_CS_POLARITY | - 0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE | - 0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX) +from fractions import Fraction +from numpy import int32, int64, floor, ceil + + +SPI_CONFIG = ( + 0 * spi.SPI_OFFLINE + | 0 * spi.SPI_END + | 0 * spi.SPI_INPUT + | 1 * spi.SPI_CS_POLARITY + | 0 * spi.SPI_CLK_POLARITY + | 0 * spi.SPI_CLK_PHASE + | 0 * spi.SPI_LSB_FIRST + | 0 * spi.SPI_HALF_DUPLEX +) + + +ADF5355_MIN_VCO_FREQ = int64(3.4 * GHz) +ADF5355_MAX_VCO_FREQ = int64(6.8 * GHz) +ADF5355_MAX_OUTA_FREQ = ADF5355_MAX_VCO_FREQ +ADF5355_MIN_OUTA_FREQ = ADF5355_MIN_VCO_FREQ / 64 +ADF5355_MAX_OUTB_FREQ = ADF5355_MAX_VCO_FREQ * 2 +ADF5355_MIN_OUTB_FREQ = ADF5355_MIN_VCO_FREQ * 2 + +ADF5355_MAX_FREQ_PFD = int32(125.0 * MHz) +ADF5355_MODULUS1 = int32(16777216) +ADF5355_MAX_MODULUS2 = int32(16383) # FIXME: ADF5356 has 28 bits MOD2 +ADF5355_MAX_R_CNT = int32(1023) class ADF5355: @@ -25,17 +50,57 @@ class ADF5355: :param cpld_device: Mirny CPLD device name :param sw_device: Mirny RF switch device name :param channel: Mirny RF channel index + :param ref_doubler: enable/disable reference clock doubler + :param ref_divider: enable/disable reference clock divide-by-2 :param core_device: Core device name (default: "core") """ - kernel_invariants = {"cpld", "sw", "channel", "core"} - def __init__(self, dmgr, cpld_device, sw_device, channel, - core="core"): + kernel_invariants = {"cpld", "sw", "channel", "core", "sysclk"} + + def __init__( + self, + dmgr, + cpld_device, + sw_device, + channel, + ref_doubler=False, + ref_divider=False, + core="core", + ): self.cpld = dmgr.get(cpld_device) self.sw = dmgr.get(sw_device) self.channel = channel self.core = dmgr.get(core) + self.ref_doubler = ref_doubler + self.ref_divider = ref_divider + self.sysclk = self.cpld.refclk + assert 10 <= self.sysclk / 1e6 <= 600 + + self._init_registers() + + @kernel + def init(self, blind=False): + """ + Initialize and configure the PLL. + + :param blind: Do not attempt to verify presence. + """ + if not blind: + # MUXOUT = VDD + self.write(ADF5355_REG4_MUXOUT(1) | 4) + delay(5000 * us) + if not self.read_muxout(): + raise ValueError("MUXOUT not high") + delay(1000 * us) + + # MUXOUT = DGND + self.write(ADF5355_REG4_MUXOUT(2) | 4) + delay(5000 * us) + if self.read_muxout(): + raise ValueError("MUXOUT not low") + delay(1000 * us) + @kernel def set_att_mu(self, att): """Set digital step attenuator in machine units. @@ -53,13 +118,393 @@ class ADF5355: return bool(self.cpld.read_reg(0) & (1 << (self.channel + 8))) @kernel - def init(self): - self.write((1 << 27) | 4) - if not self.read_muxout(): - raise ValueError("MUXOUT not high") - delay(100*us) - self.write((2 << 27) | 4) - if self.read_muxout(): - raise ValueError("MUXOUT not low") - delay(100*us) - self.write((6 << 27) | 4) + def set_frequency(self, f): + """ + Output given frequency on output A. + + :param f: 53.125 MHz <= f <= 6800 MHz + """ + freq = int64(round(f)) + + if freq > ADF5355_MAX_VCO_FREQ: + raise ValueError("Requested too high frequency") + + # select minimal output divider + rf_div_sel = 0 + while freq < ADF5355_MIN_VCO_FREQ: + freq <<= 1 + rf_div_sel += 1 + + if (1 << rf_div_sel) > 64: + raise ValueError("Requested too low frequency") + + # choose reference divider that maximizes PFD frequency + self.regs[4] = ADF5355_REG4_R_COUNTER_UPDATE( + self.regs[4], self._compute_reference_counter() + ) + f_pfd = self.f_pfd() + + # choose prescaler + if freq > int64(6e9): + self.regs[0] |= ADF5355_REG0_PRESCALER(1) # 8/9 + n_min, n_max = 75, 65535 + + # adjust reference divider to be able to match n_min constraint + while n_min * f_pfd > freq: + r = ADF5355_REG4_R_COUNTER_GET(self.regs[4]) + self.regs[4] = ADF5355_REG4_R_COUNTER_UPDATE(self.regs[4], r + 1) + f_pfd = self.f_pfd() + else: + self.regs[0] &= ~ADF5355_REG0_PRESCALER(1) # 4/5 + n_min, n_max = 23, 32767 + + # calculate PLL parameters + n, frac1, (frac2_msb, frac2_lsb), (mod2_msb, mod2_lsb) = calculate_pll( + freq, f_pfd + ) + + if not (n_min <= n <= n_max): + raise ValueError("Invalid INT value") + + # configure PLL + self.regs[0] = ADF5355_REG0_INT_VALUE_UPDATE(self.regs[0], n) + self.regs[1] = ADF5355_REG1_MAIN_FRAC_VALUE_UPDATE(self.regs[1], frac1) + self.regs[2] = ADF5355_REG2_AUX_FRAC_LSB_VALUE_UPDATE(self.regs[2], frac2_lsb) + self.regs[2] = ADF5355_REG2_AUX_MOD_LSB_VALUE_UPDATE(self.regs[2], mod2_lsb) + self.regs[13] = ADF5355_REG13_AUX_FRAC_MSB_VALUE_UPDATE( + self.regs[13], frac2_msb + ) + self.regs[13] = ADF5355_REG13_AUX_MOD_MSB_VALUE_UPDATE(self.regs[13], mod2_msb) + + self.regs[6] = ADF5355_REG6_RF_DIVIDER_SELECT_UPDATE(self.regs[6], rf_div_sel) + self.regs[6] = ADF5355_REG6_CP_BLEED_CURRENT_UPDATE( + self.regs[6], int32(floor(24 * f_pfd / (61.44 * MHz))) + ) + self.regs[9] = ADF5355_REG9_VCO_BAND_DIVISION_UPDATE( + self.regs[9], int32(ceil(f_pfd / 160e3)) + ) + + # commit + # TODO: frequency update sync + self.sync() + + @kernel + def sync(self): + """ + Write all registers to the device. Attempts to lock the PLL. + """ + f_pfd = self.f_pfd() + + if f_pfd <= 75.0 * MHz: + for i in range(13, 0, -1): + self.write(self.regs[i]) + delay(200 * us) + self.write(self.regs[0] | ADF5355_REG0_AUTOCAL(1)) + else: + # AUTOCAL AT HALF PFD FREQUENCY + + # calculate PLL at f_pfd/2 + n, frac1, (frac2_msb, frac2_lsb), (mod2_msb, mod2_lsb) = calculate_pll( + self.f_vco(), f_pfd >> 1 + ) + + self.write( + 13 + | ADF5355_REG13_AUX_FRAC_MSB_VALUE(frac2_msb) + | ADF5355_REG13_AUX_MOD_MSB_VALUE(mod2_msb) + ) + + for i in range(12, 4, -1): + self.write(self.regs[i]) + + self.write( + ADF5355_REG4_R_COUNTER_UPDATE(self.regs[4], 2 * self.ref_counter()) + ) + + self.write(self.regs[3]) + self.write( + 2 + | ADF5355_REG2_AUX_MOD_LSB_VALUE(mod2_lsb) + | ADF5355_REG2_AUX_FRAC_LSB_VALUE(frac2_lsb) + ) + self.write(1 | ADF5355_REG1_MAIN_FRAC_VALUE(frac1)) + + delay(200 * us) + self.write(ADF5355_REG0_INT_VALUE(n) | ADF5355_REG0_AUTOCAL(1)) + + # RELOCK AT WANTED PFD FREQUENCY + + for i in [4, 2, 1]: + self.write(self.regs[i]) + + # force-disable autocal + self.write(self.regs[0] & ~ADF5355_REG0_AUTOCAL(1)) + + @portable + def f_pfd(self) -> TInt64: + """ + Return the PFD frequency for the cached set of registers. + """ + r = ADF5355_REG4_R_COUNTER_GET(self.regs[4]) + d = ADF5355_REG4_R_DOUBLER_GET(self.regs[4]) + t = ADF5355_REG4_R_DIVIDER_GET(self.regs[4]) + return self._compute_pfd_frequency(r, d, t) + + @portable + def f_vco(self) -> TInt64: + """ + Return the VCO frequency for the cached set of registers. + """ + return int64( + self.f_pfd() + * ( + self.pll_n() + + (self.pll_frac1() + self.pll_frac2() / self.pll_mod2()) + / ADF5355_MODULUS1 + ) + ) + + @portable + def pll_n(self) -> TInt32: + """ + Return the PLL integer value (INT) for the cached set of registers. + """ + return ADF5355_REG0_INT_VALUE_GET(self.regs[0]) + + @portable + def pll_frac1(self) -> TInt32: + """ + Return the main fractional value (FRAC1) for the cached set of registers. + """ + return ADF5355_REG1_MAIN_FRAC_VALUE_GET(self.regs[1]) + + @portable + def pll_frac2(self) -> TInt32: + """ + Return the auxiliary fractional value (FRAC2) for the cached set of registers. + """ + return ( + ADF5355_REG13_AUX_FRAC_MSB_VALUE_GET(self.regs[13]) << 14 + ) | ADF5355_REG2_AUX_FRAC_LSB_VALUE_GET(self.regs[2]) + + @portable + def pll_mod2(self) -> TInt32: + """ + Return the auxiliary modulus value (MOD2) for the cached set of registers. + """ + return ( + ADF5355_REG13_AUX_MOD_MSB_VALUE_GET(self.regs[13]) << 14 + ) | ADF5355_REG2_AUX_MOD_LSB_VALUE_GET(self.regs[2]) + + @portable + def ref_counter(self) -> TInt32: + """ + Return the reference counter value (R) for the cached set of registers. + """ + return ADF5355_REG4_R_COUNTER_GET(self.regs[4]) + + @rpc + def info(self): + output_divider = 1 << ADF5355_REG6_RF_DIVIDER_SELECT_GET(self.regs[6]) + prescaler = ADF5355_REG0_PRESCALER_GET(self.regs[0]) + return { + # output + "f_outA": self.f_vco() / output_divider, + "f_outB": self.f_vco() * 2, + "output_divider": output_divider, + # PLL parameters + "f_vco": self.f_vco(), + "pll_n": self.pll_n(), + "pll_frac1": self.pll_frac1(), + "pll_frac2": self.pll_frac2(), + "pll_mod2": self.pll_mod2(), + "prescaler": "4/5" if prescaler == 0 else "8/9", + # reference / PFD + "sysclk": self.sysclk, + "ref_doubler": self.ref_doubler, + "ref_divider": self.ref_divider, + "ref_counter": self.ref_counter(), + "f_pfd": self.f_pfd(), + } + + @portable + def _init_registers(self): + """ + Initialize cached registers with sensible defaults. + """ + # fill with control bits + self.regs = [int32(i) for i in range(ADF5355_NUM_REGS)] + + # REG2 + # ==== + + # avoid divide-by-zero + self.regs[2] |= ADF5355_REG2_AUX_MOD_LSB_VALUE(1) + + # REG4 + # ==== + + # single-ended reference mode is recommended + # for references up to 250 MHz, even if the signal is differential + if self.sysclk <= 250 * MHz: + self.regs[4] |= ADF5355_REG4_REF_MODE(0) + else: + self.regs[4] |= ADF5355_REG4_REF_MODE(1) + + # phase detector polarity: positive + self.regs[4] |= ADF5355_REG4_PD_POLARITY(1) + + # charge pump current: 0.94 mA + self.regs[4] |= ADF5355_REG4_CURRENT_SETTING(2) + + # MUXOUT: digital lock detect + self.regs[4] |= ADF5355_REG4_MUX_LOGIC(1) # 3v3 logic + self.regs[4] |= ADF5355_REG4_MUXOUT(6) + + # setup reference path + if self.ref_doubler: + self.regs[4] |= ADF5355_REG4_R_DOUBLER(1) + + if self.ref_divider: + self.regs[4] |= ADF5355_REG4_R_DIVIDER(1) + + r = self._compute_reference_counter() + self.regs[4] |= ADF5355_REG4_R_COUNTER(r) + + # REG5 + # ==== + + # reserved values + self.regs[5] = int32(0x800025) + + # REG6 + # ==== + + # reserved values + self.regs[6] = int32(0x14000006) + + # enable negative bleed + self.regs[6] |= ADF5355_REG6_NEGATIVE_BLEED(1) + + # charge pump bleed current + # self.regs[6] |= ADF5355_REG6_CP_BLEED_CURRENT( + # int32(floor(24 * self.f_pfd / (61.44 * MHz))) + # ) + + # direct feedback from VCO to N counter + self.regs[6] |= ADF5355_REG6_FB_SELECT(1) + + # mute until the PLL is locked + self.regs[6] |= ADF5355_REG6_MUTE_TILL_LD(1) + + # enable output A + self.regs[6] |= ADF5355_REG6_RF_OUTPUT_A_ENABLE(1) + + # set output A power to max power, is adjusted by extra attenuator + self.regs[6] |= ADF5355_REG6_RF_OUTPUT_A_POWER(3) # +5 dBm + + # REG7 + # ==== + + # reserved values + self.regs[7] = int32(0x10000007) + + # sync load-enable to reference + self.regs[7] |= ADF5355_REG7_LE_SYNC(1) + + # frac-N lock-detect precision: 12 ns + self.regs[7] |= ADF5355_REG7_FRAC_N_LD_PRECISION(3) + + # REG8 + # ==== + + # reserved values + self.regs[8] = int32(0x102D0428) + + # REG9 + # ==== + + # default timeouts (from eval software) + self.regs[9] |= ( + ADF5355_REG9_SYNTH_LOCK_TIMEOUT(13) + | ADF5355_REG9_AUTOCAL_TIMEOUT(31) + | ADF5355_REG9_TIMEOUT(0x67) + ) + + # REG10 + # ===== + + # reserved values + self.regs[10] = int32(0xC0000A) + + # ADC defaults (from eval software) + self.regs[10] |= ( + ADF5355_REG10_ADC_ENABLE(1) + | ADF5355_REG10_ADC_CLK_DIV(256) + | ADF5355_REG10_ADC_CONV(1) + ) + + # REG11 + # ===== + + # reserved values + self.regs[11] = int32(0x61200B) + + # REG12 + # ===== + + # reserved values + self.regs[12] = int32(0x15FC) + + @portable + def _compute_pfd_frequency(self, r, d, t) -> TInt64: + """ + Calculate the PFD frequency from the given reference path parameters + """ + return int64(self.sysclk * ((1 + d) / (r * (1 + t)))) + + @portable + def _compute_reference_counter(self) -> TInt32: + """ + Determine the reference counter R that maximizes the PFD frequency + """ + d = ADF5355_REG4_R_DOUBLER_GET(self.regs[4]) + t = ADF5355_REG4_R_DIVIDER_GET(self.regs[4]) + r = 1 + while self._compute_pfd_frequency(r, d, t) > ADF5355_MAX_FREQ_PFD: + r += 1 + return int32(r) + + +@portable +def calculate_pll(f_vco: TInt64, f_pfd: TInt64): + """ + Calculate fractional-N PLL parameters such that + + f_vco = f_pfd * (n + (frac1 + frac2/mod2) / mod1) + + where + mod1 = 16777216 + mod2 = 16383 + """ + f_pfd = int64(f_pfd) + f_vco = int64(f_vco) + + # integral part + n = int32(f_vco / f_pfd) + r = f_vco / f_pfd - n + + # main fractional part + frac1 = int32(ADF5355_MODULUS1 * r) + r = r * ADF5355_MODULUS1 - frac1 + + # auxiliary fractional part + # FIXME: calculate optimal MOD2 + mod2 = ADF5355_MAX_MODULUS2 + frac2 = int32(r * mod2) + + # split frac2, mod2 + frac2_msb, frac2_lsb = (frac2 >> 14) & 0x3FFF, frac2 & 0x3FFF + mod2_msb, mod2_lsb = (mod2 >> 14) & 0x3FFF, mod2 & 0x3FFF + + return n, frac1, (frac2_msb, frac2_lsb), (mod2_msb, mod2_lsb) diff --git a/artiq/coredevice/adf5355_reg.py b/artiq/coredevice/adf5355_reg.py new file mode 100644 index 000000000..9eb0830df --- /dev/null +++ b/artiq/coredevice/adf5355_reg.py @@ -0,0 +1,742 @@ +# auto-generated, do not edit +from artiq.language.core import portable +from artiq.language.types import TInt32 +from numpy import int32 + + +@portable +def ADF5355_REG0_AUTOCAL_GET(reg: TInt32) -> TInt32: + return int32((reg >> 21) & 0x1) + + +@portable +def ADF5355_REG0_AUTOCAL(x: TInt32) -> TInt32: + return int32((x & 0x1) << 21) + + +@portable +def ADF5355_REG0_AUTOCAL_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 21)) | ((x & 0x1) << 21)) + + +@portable +def ADF5355_REG0_INT_VALUE_GET(reg: TInt32) -> TInt32: + return int32((reg >> 4) & 0xFFFF) + + +@portable +def ADF5355_REG0_INT_VALUE(x: TInt32) -> TInt32: + return int32((x & 0xFFFF) << 4) + + +@portable +def ADF5355_REG0_INT_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0xFFFF << 4)) | ((x & 0xFFFF) << 4)) + + +@portable +def ADF5355_REG0_PRESCALER_GET(reg: TInt32) -> TInt32: + return int32((reg >> 20) & 0x1) + + +@portable +def ADF5355_REG0_PRESCALER(x: TInt32) -> TInt32: + return int32((x & 0x1) << 20) + + +@portable +def ADF5355_REG0_PRESCALER_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 20)) | ((x & 0x1) << 20)) + + +@portable +def ADF5355_REG1_MAIN_FRAC_VALUE_GET(reg: TInt32) -> TInt32: + return int32((reg >> 4) & 0xFFFFFF) + + +@portable +def ADF5355_REG1_MAIN_FRAC_VALUE(x: TInt32) -> TInt32: + return int32((x & 0xFFFFFF) << 4) + + +@portable +def ADF5355_REG1_MAIN_FRAC_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0xFFFFFF << 4)) | ((x & 0xFFFFFF) << 4)) + + +@portable +def ADF5355_REG2_AUX_FRAC_LSB_VALUE_GET(reg: TInt32) -> TInt32: + return int32((reg >> 18) & 0x3FFF) + + +@portable +def ADF5355_REG2_AUX_FRAC_LSB_VALUE(x: TInt32) -> TInt32: + return int32((x & 0x3FFF) << 18) + + +@portable +def ADF5355_REG2_AUX_FRAC_LSB_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x3FFF << 18)) | ((x & 0x3FFF) << 18)) + + +@portable +def ADF5355_REG2_AUX_MOD_LSB_VALUE_GET(reg: TInt32) -> TInt32: + return int32((reg >> 4) & 0x3FFF) + + +@portable +def ADF5355_REG2_AUX_MOD_LSB_VALUE(x: TInt32) -> TInt32: + return int32((x & 0x3FFF) << 4) + + +@portable +def ADF5355_REG2_AUX_MOD_LSB_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x3FFF << 4)) | ((x & 0x3FFF) << 4)) + + +@portable +def ADF5355_REG3_PHASE_ADJUST_GET(reg: TInt32) -> TInt32: + return int32((reg >> 28) & 0x1) + + +@portable +def ADF5355_REG3_PHASE_ADJUST(x: TInt32) -> TInt32: + return int32((x & 0x1) << 28) + + +@portable +def ADF5355_REG3_PHASE_ADJUST_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 28)) | ((x & 0x1) << 28)) + + +@portable +def ADF5355_REG3_PHASE_RESYNC_GET(reg: TInt32) -> TInt32: + return int32((reg >> 29) & 0x1) + + +@portable +def ADF5355_REG3_PHASE_RESYNC(x: TInt32) -> TInt32: + return int32((x & 0x1) << 29) + + +@portable +def ADF5355_REG3_PHASE_RESYNC_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 29)) | ((x & 0x1) << 29)) + + +@portable +def ADF5355_REG3_PHASE_VALUE_GET(reg: TInt32) -> TInt32: + return int32((reg >> 4) & 0xFFFFFF) + + +@portable +def ADF5355_REG3_PHASE_VALUE(x: TInt32) -> TInt32: + return int32((x & 0xFFFFFF) << 4) + + +@portable +def ADF5355_REG3_PHASE_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0xFFFFFF << 4)) | ((x & 0xFFFFFF) << 4)) + + +@portable +def ADF5355_REG3_SD_LOAD_RESET_GET(reg: TInt32) -> TInt32: + return int32((reg >> 30) & 0x1) + + +@portable +def ADF5355_REG3_SD_LOAD_RESET(x: TInt32) -> TInt32: + return int32((x & 0x1) << 30) + + +@portable +def ADF5355_REG3_SD_LOAD_RESET_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 30)) | ((x & 0x1) << 30)) + + +@portable +def ADF5355_REG4_COUNTER_RESET_GET(reg: TInt32) -> TInt32: + return int32((reg >> 4) & 0x1) + + +@portable +def ADF5355_REG4_COUNTER_RESET(x: TInt32) -> TInt32: + return int32((x & 0x1) << 4) + + +@portable +def ADF5355_REG4_COUNTER_RESET_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 4)) | ((x & 0x1) << 4)) + + +@portable +def ADF5355_REG4_CP_THREE_STATE_GET(reg: TInt32) -> TInt32: + return int32((reg >> 5) & 0x1) + + +@portable +def ADF5355_REG4_CP_THREE_STATE(x: TInt32) -> TInt32: + return int32((x & 0x1) << 5) + + +@portable +def ADF5355_REG4_CP_THREE_STATE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 5)) | ((x & 0x1) << 5)) + + +@portable +def ADF5355_REG4_CURRENT_SETTING_GET(reg: TInt32) -> TInt32: + return int32((reg >> 10) & 0xF) + + +@portable +def ADF5355_REG4_CURRENT_SETTING(x: TInt32) -> TInt32: + return int32((x & 0xF) << 10) + + +@portable +def ADF5355_REG4_CURRENT_SETTING_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0xF << 10)) | ((x & 0xF) << 10)) + + +@portable +def ADF5355_REG4_DOUBLE_BUFF_GET(reg: TInt32) -> TInt32: + return int32((reg >> 14) & 0x1) + + +@portable +def ADF5355_REG4_DOUBLE_BUFF(x: TInt32) -> TInt32: + return int32((x & 0x1) << 14) + + +@portable +def ADF5355_REG4_DOUBLE_BUFF_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 14)) | ((x & 0x1) << 14)) + + +@portable +def ADF5355_REG4_MUX_LOGIC_GET(reg: TInt32) -> TInt32: + return int32((reg >> 8) & 0x1) + + +@portable +def ADF5355_REG4_MUX_LOGIC(x: TInt32) -> TInt32: + return int32((x & 0x1) << 8) + + +@portable +def ADF5355_REG4_MUX_LOGIC_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 8)) | ((x & 0x1) << 8)) + + +@portable +def ADF5355_REG4_MUXOUT_GET(reg: TInt32) -> TInt32: + return int32((reg >> 27) & 0x7) + + +@portable +def ADF5355_REG4_MUXOUT(x: TInt32) -> TInt32: + return int32((x & 0x7) << 27) + + +@portable +def ADF5355_REG4_MUXOUT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x7 << 27)) | ((x & 0x7) << 27)) + + +@portable +def ADF5355_REG4_PD_POLARITY_GET(reg: TInt32) -> TInt32: + return int32((reg >> 7) & 0x1) + + +@portable +def ADF5355_REG4_PD_POLARITY(x: TInt32) -> TInt32: + return int32((x & 0x1) << 7) + + +@portable +def ADF5355_REG4_PD_POLARITY_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 7)) | ((x & 0x1) << 7)) + + +@portable +def ADF5355_REG4_POWER_DOWN_GET(reg: TInt32) -> TInt32: + return int32((reg >> 6) & 0x1) + + +@portable +def ADF5355_REG4_POWER_DOWN(x: TInt32) -> TInt32: + return int32((x & 0x1) << 6) + + +@portable +def ADF5355_REG4_POWER_DOWN_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 6)) | ((x & 0x1) << 6)) + + +@portable +def ADF5355_REG4_R_COUNTER_GET(reg: TInt32) -> TInt32: + return int32((reg >> 15) & 0x3FF) + + +@portable +def ADF5355_REG4_R_COUNTER(x: TInt32) -> TInt32: + return int32((x & 0x3FF) << 15) + + +@portable +def ADF5355_REG4_R_COUNTER_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x3FF << 15)) | ((x & 0x3FF) << 15)) + + +@portable +def ADF5355_REG4_R_DIVIDER_GET(reg: TInt32) -> TInt32: + return int32((reg >> 25) & 0x1) + + +@portable +def ADF5355_REG4_R_DIVIDER(x: TInt32) -> TInt32: + return int32((x & 0x1) << 25) + + +@portable +def ADF5355_REG4_R_DIVIDER_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 25)) | ((x & 0x1) << 25)) + + +@portable +def ADF5355_REG4_R_DOUBLER_GET(reg: TInt32) -> TInt32: + return int32((reg >> 26) & 0x1) + + +@portable +def ADF5355_REG4_R_DOUBLER(x: TInt32) -> TInt32: + return int32((x & 0x1) << 26) + + +@portable +def ADF5355_REG4_R_DOUBLER_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 26)) | ((x & 0x1) << 26)) + + +@portable +def ADF5355_REG4_REF_MODE_GET(reg: TInt32) -> TInt32: + return int32((reg >> 9) & 0x1) + + +@portable +def ADF5355_REG4_REF_MODE(x: TInt32) -> TInt32: + return int32((x & 0x1) << 9) + + +@portable +def ADF5355_REG4_REF_MODE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 9)) | ((x & 0x1) << 9)) + + +@portable +def ADF5355_REG6_BLEED_POLARITY_GET(reg: TInt32) -> TInt32: + return int32((reg >> 31) & 0x1) + + +@portable +def ADF5355_REG6_BLEED_POLARITY(x: TInt32) -> TInt32: + return int32((x & 0x1) << 31) + + +@portable +def ADF5355_REG6_BLEED_POLARITY_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 31)) | ((x & 0x1) << 31)) + + +@portable +def ADF5355_REG6_CP_BLEED_CURRENT_GET(reg: TInt32) -> TInt32: + return int32((reg >> 13) & 0xFF) + + +@portable +def ADF5355_REG6_CP_BLEED_CURRENT(x: TInt32) -> TInt32: + return int32((x & 0xFF) << 13) + + +@portable +def ADF5355_REG6_CP_BLEED_CURRENT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0xFF << 13)) | ((x & 0xFF) << 13)) + + +@portable +def ADF5355_REG6_FB_SELECT_GET(reg: TInt32) -> TInt32: + return int32((reg >> 24) & 0x1) + + +@portable +def ADF5355_REG6_FB_SELECT(x: TInt32) -> TInt32: + return int32((x & 0x1) << 24) + + +@portable +def ADF5355_REG6_FB_SELECT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 24)) | ((x & 0x1) << 24)) + + +@portable +def ADF5355_REG6_GATE_BLEED_GET(reg: TInt32) -> TInt32: + return int32((reg >> 30) & 0x1) + + +@portable +def ADF5355_REG6_GATE_BLEED(x: TInt32) -> TInt32: + return int32((x & 0x1) << 30) + + +@portable +def ADF5355_REG6_GATE_BLEED_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 30)) | ((x & 0x1) << 30)) + + +@portable +def ADF5355_REG6_MUTE_TILL_LD_GET(reg: TInt32) -> TInt32: + return int32((reg >> 11) & 0x1) + + +@portable +def ADF5355_REG6_MUTE_TILL_LD(x: TInt32) -> TInt32: + return int32((x & 0x1) << 11) + + +@portable +def ADF5355_REG6_MUTE_TILL_LD_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 11)) | ((x & 0x1) << 11)) + + +@portable +def ADF5355_REG6_NEGATIVE_BLEED_GET(reg: TInt32) -> TInt32: + return int32((reg >> 29) & 0x1) + + +@portable +def ADF5355_REG6_NEGATIVE_BLEED(x: TInt32) -> TInt32: + return int32((x & 0x1) << 29) + + +@portable +def ADF5355_REG6_NEGATIVE_BLEED_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 29)) | ((x & 0x1) << 29)) + + +@portable +def ADF5355_REG6_RF_DIVIDER_SELECT_GET(reg: TInt32) -> TInt32: + return int32((reg >> 21) & 0x7) + + +@portable +def ADF5355_REG6_RF_DIVIDER_SELECT(x: TInt32) -> TInt32: + return int32((x & 0x7) << 21) + + +@portable +def ADF5355_REG6_RF_DIVIDER_SELECT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x7 << 21)) | ((x & 0x7) << 21)) + + +@portable +def ADF5355_REG6_RF_OUTPUT_A_ENABLE_GET(reg: TInt32) -> TInt32: + return int32((reg >> 6) & 0x1) + + +@portable +def ADF5355_REG6_RF_OUTPUT_A_ENABLE(x: TInt32) -> TInt32: + return int32((x & 0x1) << 6) + + +@portable +def ADF5355_REG6_RF_OUTPUT_A_ENABLE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 6)) | ((x & 0x1) << 6)) + + +@portable +def ADF5355_REG6_RF_OUTPUT_A_POWER_GET(reg: TInt32) -> TInt32: + return int32((reg >> 4) & 0x3) + + +@portable +def ADF5355_REG6_RF_OUTPUT_A_POWER(x: TInt32) -> TInt32: + return int32((x & 0x3) << 4) + + +@portable +def ADF5355_REG6_RF_OUTPUT_A_POWER_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x3 << 4)) | ((x & 0x3) << 4)) + + +@portable +def ADF5355_REG6_RF_OUTPUT_B_ENABLE_GET(reg: TInt32) -> TInt32: + return int32((reg >> 10) & 0x1) + + +@portable +def ADF5355_REG6_RF_OUTPUT_B_ENABLE(x: TInt32) -> TInt32: + return int32((x & 0x1) << 10) + + +@portable +def ADF5355_REG6_RF_OUTPUT_B_ENABLE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 10)) | ((x & 0x1) << 10)) + + +@portable +def ADF5355_REG7_FRAC_N_LD_PRECISION_GET(reg: TInt32) -> TInt32: + return int32((reg >> 5) & 0x3) + + +@portable +def ADF5355_REG7_FRAC_N_LD_PRECISION(x: TInt32) -> TInt32: + return int32((x & 0x3) << 5) + + +@portable +def ADF5355_REG7_FRAC_N_LD_PRECISION_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x3 << 5)) | ((x & 0x3) << 5)) + + +@portable +def ADF5355_REG7_LD_CYCLE_COUNT_GET(reg: TInt32) -> TInt32: + return int32((reg >> 8) & 0x3) + + +@portable +def ADF5355_REG7_LD_CYCLE_COUNT(x: TInt32) -> TInt32: + return int32((x & 0x3) << 8) + + +@portable +def ADF5355_REG7_LD_CYCLE_COUNT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x3 << 8)) | ((x & 0x3) << 8)) + + +@portable +def ADF5355_REG7_LD_MODE_GET(reg: TInt32) -> TInt32: + return int32((reg >> 4) & 0x1) + + +@portable +def ADF5355_REG7_LD_MODE(x: TInt32) -> TInt32: + return int32((x & 0x1) << 4) + + +@portable +def ADF5355_REG7_LD_MODE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 4)) | ((x & 0x1) << 4)) + + +@portable +def ADF5355_REG7_LE_SEL_SYNC_EDGE_GET(reg: TInt32) -> TInt32: + return int32((reg >> 27) & 0x1) + + +@portable +def ADF5355_REG7_LE_SEL_SYNC_EDGE(x: TInt32) -> TInt32: + return int32((x & 0x1) << 27) + + +@portable +def ADF5355_REG7_LE_SEL_SYNC_EDGE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 27)) | ((x & 0x1) << 27)) + + +@portable +def ADF5355_REG7_LE_SYNC_GET(reg: TInt32) -> TInt32: + return int32((reg >> 25) & 0x1) + + +@portable +def ADF5355_REG7_LE_SYNC(x: TInt32) -> TInt32: + return int32((x & 0x1) << 25) + + +@portable +def ADF5355_REG7_LE_SYNC_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 25)) | ((x & 0x1) << 25)) + + +@portable +def ADF5355_REG7_LOL_MODE_GET(reg: TInt32) -> TInt32: + return int32((reg >> 7) & 0x1) + + +@portable +def ADF5355_REG7_LOL_MODE(x: TInt32) -> TInt32: + return int32((x & 0x1) << 7) + + +@portable +def ADF5355_REG7_LOL_MODE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 7)) | ((x & 0x1) << 7)) + + +@portable +def ADF5355_REG9_AUTOCAL_TIMEOUT_GET(reg: TInt32) -> TInt32: + return int32((reg >> 9) & 0x1F) + + +@portable +def ADF5355_REG9_AUTOCAL_TIMEOUT(x: TInt32) -> TInt32: + return int32((x & 0x1F) << 9) + + +@portable +def ADF5355_REG9_AUTOCAL_TIMEOUT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1F << 9)) | ((x & 0x1F) << 9)) + + +@portable +def ADF5355_REG9_SYNTH_LOCK_TIMEOUT_GET(reg: TInt32) -> TInt32: + return int32((reg >> 4) & 0x1F) + + +@portable +def ADF5355_REG9_SYNTH_LOCK_TIMEOUT(x: TInt32) -> TInt32: + return int32((x & 0x1F) << 4) + + +@portable +def ADF5355_REG9_SYNTH_LOCK_TIMEOUT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1F << 4)) | ((x & 0x1F) << 4)) + + +@portable +def ADF5355_REG9_TIMEOUT_GET(reg: TInt32) -> TInt32: + return int32((reg >> 14) & 0x3FF) + + +@portable +def ADF5355_REG9_TIMEOUT(x: TInt32) -> TInt32: + return int32((x & 0x3FF) << 14) + + +@portable +def ADF5355_REG9_TIMEOUT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x3FF << 14)) | ((x & 0x3FF) << 14)) + + +@portable +def ADF5355_REG9_VCO_BAND_DIVISION_GET(reg: TInt32) -> TInt32: + return int32((reg >> 24) & 0xFF) + + +@portable +def ADF5355_REG9_VCO_BAND_DIVISION(x: TInt32) -> TInt32: + return int32((x & 0xFF) << 24) + + +@portable +def ADF5355_REG9_VCO_BAND_DIVISION_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0xFF << 24)) | ((x & 0xFF) << 24)) + + +@portable +def ADF5355_REG10_ADC_CLK_DIV_GET(reg: TInt32) -> TInt32: + return int32((reg >> 6) & 0xFF) + + +@portable +def ADF5355_REG10_ADC_CLK_DIV(x: TInt32) -> TInt32: + return int32((x & 0xFF) << 6) + + +@portable +def ADF5355_REG10_ADC_CLK_DIV_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0xFF << 6)) | ((x & 0xFF) << 6)) + + +@portable +def ADF5355_REG10_ADC_CONV_GET(reg: TInt32) -> TInt32: + return int32((reg >> 5) & 0x1) + + +@portable +def ADF5355_REG10_ADC_CONV(x: TInt32) -> TInt32: + return int32((x & 0x1) << 5) + + +@portable +def ADF5355_REG10_ADC_CONV_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 5)) | ((x & 0x1) << 5)) + + +@portable +def ADF5355_REG10_ADC_ENABLE_GET(reg: TInt32) -> TInt32: + return int32((reg >> 4) & 0x1) + + +@portable +def ADF5355_REG10_ADC_ENABLE(x: TInt32) -> TInt32: + return int32((x & 0x1) << 4) + + +@portable +def ADF5355_REG10_ADC_ENABLE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 4)) | ((x & 0x1) << 4)) + + +@portable +def ADF5355_REG11_VCO_BAND_HOLD_GET(reg: TInt32) -> TInt32: + return int32((reg >> 24) & 0x1) + + +@portable +def ADF5355_REG11_VCO_BAND_HOLD(x: TInt32) -> TInt32: + return int32((x & 0x1) << 24) + + +@portable +def ADF5355_REG11_VCO_BAND_HOLD_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x1 << 24)) | ((x & 0x1) << 24)) + + +@portable +def ADF5355_REG12_PHASE_RESYNC_CLK_VALUE_GET(reg: TInt32) -> TInt32: + return int32((reg >> 12) & 0xFFFFF) + + +@portable +def ADF5355_REG12_PHASE_RESYNC_CLK_VALUE(x: TInt32) -> TInt32: + return int32((x & 0xFFFFF) << 12) + + +@portable +def ADF5355_REG12_PHASE_RESYNC_CLK_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0xFFFFF << 12)) | ((x & 0xFFFFF) << 12)) + + +@portable +def ADF5355_REG13_AUX_FRAC_MSB_VALUE_GET(reg: TInt32) -> TInt32: + return int32((reg >> 18) & 0x3FFF) + + +@portable +def ADF5355_REG13_AUX_FRAC_MSB_VALUE(x: TInt32) -> TInt32: + return int32((x & 0x3FFF) << 18) + + +@portable +def ADF5355_REG13_AUX_FRAC_MSB_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x3FFF << 18)) | ((x & 0x3FFF) << 18)) + + +@portable +def ADF5355_REG13_AUX_MOD_MSB_VALUE_GET(reg: TInt32) -> TInt32: + return int32((reg >> 4) & 0x3FFF) + + +@portable +def ADF5355_REG13_AUX_MOD_MSB_VALUE(x: TInt32) -> TInt32: + return int32((x & 0x3FFF) << 4) + + +@portable +def ADF5355_REG13_AUX_MOD_MSB_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: + return int32((reg & ~(0x3FFF << 4)) | ((x & 0x3FFF) << 4)) + + +ADF5355_NUM_REGS = 14 diff --git a/artiq/coredevice/mirny.py b/artiq/coredevice/mirny.py index 813e72bcb..2c533d4f8 100644 --- a/artiq/coredevice/mirny.py +++ b/artiq/coredevice/mirny.py @@ -9,10 +9,16 @@ from numpy import int32 from artiq.coredevice import spi2 as spi -SPI_CONFIG = (0*spi.SPI_OFFLINE | 0*spi.SPI_END | - 0*spi.SPI_INPUT | 1*spi.SPI_CS_POLARITY | - 0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE | - 0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX) +SPI_CONFIG = ( + 0 * spi.SPI_OFFLINE + | 0 * spi.SPI_END + | 0 * spi.SPI_INPUT + | 1 * spi.SPI_CS_POLARITY + | 0 * spi.SPI_CLK_POLARITY + | 0 * spi.SPI_CLK_PHASE + | 0 * spi.SPI_LSB_FIRST + | 0 * spi.SPI_HALF_DUPLEX +) # SPI clock write and read dividers SPIT_WR = 4 @@ -24,41 +30,65 @@ WE = 1 << 24 class Mirny: - """Mirny PLL-based RF generator. + """ + Mirny PLL-based RF generator. :param spi_device: SPI bus device + :param refclk: Reference clock (SMA, MMCX or on-board 100 MHz oscillator) + frequency in Hz + :param clk_sel: Reference clock selection. + valid options are: 0 - internal 100MHz XO; 1 - front-panel SMA; 2 - + internal MMCX :param core_device: Core device name (default: "core") """ + kernel_invariants = {"bus", "core"} - def __init__(self, dmgr, spi_device, core_device="core"): + def __init__(self, dmgr, spi_device, refclk=100e6, clk_sel=0, core_device="core"): self.core = dmgr.get(core_device) self.bus = dmgr.get(spi_device) + self.refclk = refclk + assert 10 <= self.refclk / 1e6 <= 600, "Invalid refclk" + + self.clk_sel = clk_sel & 0b11 + assert 0 <= self.clk_sel <= 3, "Invalid clk_sel" + + # TODO: support clk_div on v1.0 boards + @kernel def read_reg(self, addr): """Read a register""" - self.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, 24, - SPIT_RD, SPI_CS) + self.bus.set_config_mu( + SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, 24, SPIT_RD, SPI_CS + ) self.bus.write((addr << 25)) - return self.bus.read() & int32(0xffff) + return self.bus.read() & int32(0xFFFF) @kernel def write_reg(self, addr, data): """Write a register""" self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 24, SPIT_WR, SPI_CS) - self.bus.write((addr << 25) | WE | ((data & 0xffff) << 8)) + self.bus.write((addr << 25) | WE | ((data & 0xFFFF) << 8)) @kernel - def init(self): - """Initialize Mirny by reading the status register and verifying - compatible hardware and protocol revisions""" - reg0 = self.read_reg(0) - if reg0 & 0b11 != 0b11: - raise ValueError("Mirny HW_REV mismatch") - if (reg0 >> 2) & 0b11 != 0b00: - raise ValueError("Mirny PROTO_REV mismatch") - delay(100*us) # slack + def init(self, blind=False): + """ + Initialize and detect Mirny. + + :param blind: Do not attempt to verify presence and compatibility. + """ + if not blind: + reg0 = self.read_reg(0) + if reg0 & 0b11 != 0b11: + raise ValueError("Mirny HW_REV mismatch") + if (reg0 >> 2) & 0b11 != 0b00: + raise ValueError("Mirny PROTO_REV mismatch") + delay(100 * us) # slack + + # select clock source + self.write_reg(1, (self.clk_sel << 4)) + delay(1000 * us) @kernel def set_att_mu(self, channel, att): @@ -74,8 +104,7 @@ class Mirny: """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, SPIT_WR, SPI_CS) if length < 32: data <<= 32 - length self.bus.write(data)