artiq/artiq/coredevice/mirny.py

132 lines
3.8 KiB
Python
Raw Normal View History

2020-01-20 20:07:20 +08:00
"""RTIO driver for Mirny (4 channel GHz PLLs)
"""
from artiq.language.core import kernel, delay
from artiq.language.units import us
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 clock write and read dividers
SPIT_WR = 4
SPIT_RD = 16
SPI_CS = 1
2020-01-20 20:07:20 +08:00
WE = 1 << 24
# supported CPLD code version
PROTO_REV_MATCH = 0x0
2020-01-20 20:07:20 +08:00
class Mirny:
"""
Mirny PLL-based RF generator.
2020-01-20 20:07:20 +08:00
: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: "XO" - onboard crystal oscillator
"SMA" - front-panel SMA connector
"MMCX" - internal MMCX connector
Passing an integer writes its two least significant bits as ``clk_sel``
in the CPLD's register 1. The effect depends on the hardware revision.
2020-01-20 20:07:20 +08:00
:param core_device: Core device name (default: "core")
"""
kernel_invariants = {"bus", "core", "refclk", "clk_sel_hw_rev"}
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)
# reference clock frequency
self.refclk = refclk
assert 10 <= self.refclk / 1e6 <= 600, "Invalid refclk"
# reference clock selection
if isinstance(clk_sel, str):
self.clk_sel_hw_rev = {
# clk source: [v1.1, v1.0]
"xo": [0, 0],
"mmcx": [3, 2],
"sma": [2, 3],
}[clk_sel.lower()]
else:
clk_sel = int(clk_sel) & 0x3
self.clk_sel_hw_rev = [clk_sel] * 2
self.clk_sel = -1
# board hardware revision
self.hw_rev = 0 # v1.0: 0b11, v1.1: 0b10
# TODO: support clk_div on v1.0 boards
@kernel
def read_reg(self, addr):
2020-01-20 20:07:20 +08:00
"""Read a register"""
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)
@kernel
def write_reg(self, addr, data):
2020-01-20 20:07:20 +08:00
"""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))
@kernel
def init(self, blind=False):
"""
Initialize and detect Mirny.
:param blind: Do not attempt to verify presence and compatibility.
"""
reg0 = self.read_reg(0)
self.hw_rev = reg0 & 0x3
if not blind:
if (reg0 >> 2) & 0x3 != PROTO_REV_MATCH:
raise ValueError("Mirny PROTO_REV mismatch")
delay(100 * us) # slack
# select clock source
self.clk_sel = self.clk_sel_hw_rev[self.hw_rev - 2]
self.write_reg(1, (self.clk_sel << 4))
delay(1000 * us)
@kernel
def set_att_mu(self, channel, att):
"""Set digital step attenuator in machine units.
:param att: Attenuation setting, 8 bit digital.
"""
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 16, SPIT_WR, SPI_CS)
self.bus.write(((channel | 8) << 25) | (att << 16))
@kernel
def write_ext(self, addr, length, data):
2020-01-20 20:07:20 +08:00
"""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)
if length < 32:
data <<= 32 - length
self.bus.write(data)