coredevice: mirny/adf5355: add basic high-level interface

Signed-off-by: Etienne Wodey <wodey@iqo.uni-hannover.de>
This commit is contained in:
Etienne Wodey 2020-10-08 23:36:50 +02:00 committed by Sébastien Bourdeauducq
parent 4f311e7448
commit 211500089f
3 changed files with 1256 additions and 40 deletions

View File

@ -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)

View File

@ -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

View File

@ -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)