urukul: add CPLD and AD9912 driver [wip]

This commit is contained in:
Robert Jördens 2018-01-02 14:52:13 +01:00
parent c2be820e9a
commit a940550e47
4 changed files with 386 additions and 34 deletions

View File

@ -3,53 +3,116 @@ Driver for the AD9912 DDS.
"""
from artiq.language.core import kernel, delay_mu
from artiq.language.units import ns, us
from artiq.coredevice import spi
from artiq.language.core import kernel, delay_mu, delay
from artiq.language.units import us, ns
from artiq.coredevice import spi, urukul
from artiq.coredevice.ad9912_reg import *
_AD9912_SPI_CONFIG = (0*spi.SPI_OFFLINE | 0*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 numpy import int32, int64
class AD9912:
"""
Support for the Analog devices AD9912 DDS
:param spi_device: Name of the SPI bus this device is on.
:param chip_select: Value to drive on the chip select lines
during transactions.
:param chip_select: Chip select configuration.
:param cpld_device: Name of the Urukul CPLD this device is on.
:param sw_device: Name of the RF switch device.
"""
kernel_invariants = {"chip_select", "cpld", "core", "bus", "sw",
"ftw_per_hz", "sysclk", "pll_n"}
def __init__(self, dmgr, spi_device, chip_select):
self.core = dmgr.get("core")
self.bus = dmgr.get(spi_device)
def __init__(self, dmgr, chip_select, cpld_device, sw_device=None,
pll_n=10):
self.cpld = dmgr.get(cpld_device)
self.core = self.cpld.core
self.bus = self.cpld.bus
assert chip_select >= 4
self.chip_select = chip_select
if sw_device:
self.sw = dmgr.get(sw_device)
self.pll_n = pll_n
self.sysclk = self.cpld.refclk * pll_n
self.ftw_per_hz = 1/self.sysclk*(int64(1) << 48)
@kernel
def setup_bus(self, write_div=5, read_div=20):
"""Configure the SPI bus and the SPI transaction parameters
for this device. This method has to be called before any other method
if the bus has been used to access a different device in the meantime.
This method advances the timeline by the duration of two
RTIO-to-Wishbone bus transactions.
:param write_div: Write clock divider.
:param read_div: Read clock divider.
"""
# write: 5*8ns >= 40ns = t_clk (typ clk rate)
# read: 2*8*ns >= 25ns = t_dv (clk falling to miso valid) + RTT
self.bus.set_config_mu(_AD9912_SPI_CONFIG, write_div, read_div)
self.bus.set_xfer(self.chip_select, 24, 0)
def write(self, addr, data, length=1):
assert length > 0
assert length <= 4
self.bus.set_xfer(self.chip_select, 16, 0)
self.bus.write((addr | ((length - 1) << 13)) << 16)
delay_mu(-self.bus.xfer_period_mu)
self.bus.set_xfer(self.chip_select, length*8, 0)
if length < 4:
data <<= 32 - length*8
self.bus.write(data)
delay_mu(self.bus.xfer_period_mu - self.bus.write_period_mu)
@kernel
def write(self, data):
"""Write 24 bits of data.
def read(self, addr, length=1):
assert length > 0
assert length <= 4
self.bus.set_xfer(self.chip_select, 16, 0)
self.bus.write((addr | ((length - 1) << 13) | 0x8000) << 16)
delay_mu(-self.bus.xfer_period_mu)
self.bus.set_xfer(self.chip_select, 0, length*8)
self.bus.write(0)
delay_mu(2*self.bus.xfer_period_mu)
data = self.bus.read_sync()
if length < 4:
data &= (1 << (length*8)) - 1
return data
This method advances the timeline by the duration of the SPI transfer
and the required CS high time.
@kernel
def init(self):
t = now_mu()
self.write(AD9912_SER_CONF, 0x99)
prodid = self.read(AD9912_PRODIDH, length=2)
assert (prodid == 0x1982) or (prodid == 0x1902)
delay(10*us)
self.write(AD9912_PWRCNTRL1, 0x80) # HSTL, CMOS power down
delay(10*us)
self.write(AD9912_N_DIV, self.pll_n//2 - 2)
delay(10*us)
self.write(AD9912_PLLCFG, 0b00000101) # 375 µA, high range
at_mu(t)
delay(100*us)
@kernel
def set_att_mu(self, att):
self.cpld.set_att_mu(self.chip_select - 4, att)
@kernel
def set_att(self, att):
self.cpld.set_att(self.chip_select - 4, att)
@kernel
def set_mu(self, ftw=int64(0), pow=int32(0)):
# do a streaming transfer of FTW and POW
self.bus.set_xfer(self.chip_select, 16, 0)
self.bus.write((AD9912_POW1 << 16) | (3 << 29))
delay_mu(-self.bus.xfer_period_mu)
self.bus.set_xfer(self.chip_select, 32, 0)
self.bus.write((pow << 16) | int32(ftw >> 32))
self.bus.write(int32(ftw))
delay_mu(self.bus.xfer_period_mu - self.bus.write_period_mu)
self.cpld.io_update.pulse(10*ns)
@portable(flags={"fast-math"})
def frequency_to_ftw(self, frequency):
"""Returns the frequency tuning word corresponding to the given
frequency.
"""
self.bus.write(data << 8)
delay_mu(self.bus.ref_period_mu) # get to 20ns min cs high
return int64(round(self.ftw_per_hz*frequency))
@portable(flags={"fast-math"})
def turns_to_pow(self, phase):
"""Returns the phase offset word corresponding to the given
phase.
"""
return int32(round((1 << 16)*phase))
@kernel
def set(self, frequency, phase=0.0):
self.set_mu(self.frequency_to_ftw(frequency),
self.turns_to_pow(phase))

162
artiq/coredevice/urukul.py Normal file
View File

@ -0,0 +1,162 @@
from artiq.language.core import kernel, delay_mu, delay, now_mu, at_mu
from artiq.language.units import us
from numpy import int32, int64
from artiq.coredevice import spi
_SPI_CONFIG = (0*spi.SPI_OFFLINE | 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_CFG_WR = 2
_SPIT_CFG_RD = 16
_SPIT_ATT_WR = 2
_SPIT_ATT_RD = 16
_SPIT_DDS_WR = 16
_SPIT_DDS_RD = 16
# CFG configuration register bit offsets
CFG_RF_SW = 0
CFG_LED = 4
CFG_PROFILE = 8
CFG_ATT_LE = 11
CFG_IO_UPDATE = 12
CFG_MASK_NU = 16
CFG_CLK_SEL = 17
CFG_SYNC_SEL = 18
CFG_RST = 19
CFG_IO_RST = 20
@kernel
def urukul_cfg(rf_sw, led, profile, att_le, io_update, mask_nu,
clk_sel, sync_sel, rst, io_rst):
return ((rf_sw << CFG_RF_SW) | (led << CFG_LED) |
(profile << CFG_PROFILE) | (att_le << CFG_ATT_LE) |
(io_update << CFG_IO_UPDATE) | (mask_nu << CFG_MASK_NU) |
(clk_sel << CFG_CLK_SEL) | (sync_sel << CFG_SYNC_SEL) |
(rst << CFG_RST) | (io_rst << CFG_IO_RST))
# STA status register bit offsets
STA_RF_SW = 0
STA_SMP_ERR = 4
STA_PLL_LOCK = 8
STA_IFC_MODE = 12
STA_PROTO_REV = 16
@kernel
def urukul_sta_rf_sw(sta):
return (sta >> STA_RF_SW) & 0xf
@kernel
def urukul_sta_smp_err(sta):
return (sta >> STA_SMP_ERR) & 0xf
@kernel
def urukul_sta_pll_lock(sta):
return (sta >> STA_PLL_LOCK) & 0xf
@kernel
def urukul_sta_ifc_mode(sta):
return (sta >> STA_IFC_MODE) & 0xf
@kernel
def urukul_sta_proto_rev(sta):
return (sta >> STA_PROTO_REV) & 0xff
# supported hardware and CPLD code version
STA_PROTO_REV_MATCH = 0x06
# chip select (decoded)
CS_CFG = 1
CS_ATT = 2
CS_DDS_MULTI = 3
CS_DDS_CH0 = 4
CS_DDS_CH1 = 5
CS_DDS_CH2 = 6
CS_DDS_CH3 = 7
class CPLD:
def __init__(self, dmgr, spi_device, io_update_device, dds_reset_device,
refclk=100e6, core_device="core"):
self.core = dmgr.get(core_device)
self.refclk = refclk
self.bus = dmgr.get(spi_device)
self.io_update = dmgr.get(io_update_device)
if dds_reset_device is not None:
self.dds_reset = dmgr.get(dds_reset_device)
self.cfg_reg = int32(0)
self.att_reg = int32(0)
@kernel
def cfg_write(self, cfg_reg):
self.bus.set_config_mu(_SPI_CONFIG, _SPIT_CFG_WR, _SPIT_CFG_RD)
self.bus.set_xfer(CS_CFG, 24, 0)
self.bus.write(cfg_reg << 8)
self.bus.set_config_mu(_SPI_CONFIG, _SPIT_DDS_WR, _SPIT_DDS_RD)
self.cfg_reg = cfg_reg
@kernel
def sta_read(self):
self.bus.set_config_mu(_SPI_CONFIG, _SPIT_CFG_WR, _SPIT_CFG_RD)
self.bus.set_xfer(CS_CFG, 0, 24)
self.bus.write(self.cfg_reg << 8)
self.bus.set_config_mu(_SPI_CONFIG, _SPIT_DDS_WR, _SPIT_DDS_RD)
return self.bus.read_sync()
@kernel
def init(self, clk_sel=0, sync_sel=0):
t = now_mu()
cfg = urukul_cfg(rf_sw=0, led=0, profile=0, att_le=0,
io_update=0, mask_nu=0, clk_sel=clk_sel,
sync_sel=sync_sel, rst=0, io_rst=0)
self.cfg_write(cfg | (1 << CFG_RST))
self.cfg_write(cfg)
proto_rev = urukul_sta_proto_rev(self.sta_read())
if proto_rev != STA_PROTO_REV_MATCH:
raise ValueError("Urukul proto_rev mismatch")
at_mu(t)
delay(100*us)
@kernel
def cfg_sw(self, sw, on):
c = self.cfg_reg
if on:
c |= 1 << sw
else:
c &= ~(1 << sw)
self.write_cfg(c)
@kernel
def set_att_mu(self, channel, att):
"""
Parameters:
att (int): 0-255, 255 minimum attenuation,
0 maximum attenuation (31.5 dB)
"""
a = self.att_reg & ~(0xff << (channel * 8))
a |= att << (channel * 8)
self.att_reg = a
self.bus.set_config_mu(_SPI_CONFIG, _SPIT_ATT_WR, _SPIT_ATT_RD)
self.bus.set_xfer(CS_ATT, 32, 0)
self.bus.write(a)
self.cfg_write(self.cfg_reg | (1 << CFG_ATT_LE))
self.cfg_write(self.cfg_reg & ~(1 << CFG_ATT_LE))
@kernel
def set_att(self, channel, att):
self.set_att_mu(channel, 255 - int32(round(att*8)))

View File

@ -233,6 +233,61 @@ device_db = {
"class": "TTLOut",
"arguments": {"channel": 38}
},
"urukul_cpld": {
"type": "local",
"module": "artiq.coredevice.urukul",
"class": "CPLD",
"arguments": {
"spi_device": "spi_urukul",
"io_update_device": "ttl_urukul_io_update",
"dds_reset_device": "ttl_urukul_dds_reset",
"refclk": 100e6
}
},
"urukul_ch0": {
"type": "local",
"module": "artiq.coredevice.ad9912",
"class": "AD9912",
"arguments": {
"pll_n": 10,
"chip_select": 4,
"cpld_device": "urukul_cpld",
"sw_device": "ttl_urukul_sw0"
}
},
"urukul_ch1": {
"type": "local",
"module": "artiq.coredevice.ad9912",
"class": "AD9912",
"arguments": {
"pll_n": 10,
"chip_select": 5,
"cpld_device": "urukul_cpld",
"sw_device": "ttl_urukul_sw1"
}
},
"urukul_ch2": {
"type": "local",
"module": "artiq.coredevice.ad9912",
"class": "AD9912",
"arguments": {
"pll_n": 10,
"chip_select": 6,
"cpld_device": "urukul_cpld",
"sw_device": "ttl_urukul_sw2"
}
},
"urukul_ch3": {
"type": "local",
"module": "artiq.coredevice.ad9912",
"class": "AD9912",
"arguments": {
"pll_n": 10,
"chip_select": 7,
"cpld_device": "urukul_cpld",
"sw_device": "ttl_urukul_sw3"
}
},
# AD9914 DDS
"dds0": {

View File

@ -0,0 +1,72 @@
from artiq.experiment import *
class UrukulTest(EnvExperiment):
def build(self):
self.setattr_device("core")
self.setattr_device("fmcdio_dirctl")
self.setattr_device("urukul_cpld")
self.setattr_device("urukul_ch0")
self.setattr_device("urukul_ch1")
self.setattr_device("urukul_ch2")
self.setattr_device("urukul_ch3")
self.setattr_device("led")
def p(self, f, *a):
print(f % a)
@kernel
def run(self):
self.core.reset()
self.led.on()
delay(5*ms)
# Zotino plus Urukul (MISO, IO_UPDATE_RET)
self.fmcdio_dirctl.set(0x0A008800)
self.led.off()
self.urukul_cpld.init(clk_sel=1)
self.urukul_ch0.init()
self.urukul_ch1.init()
self.urukul_ch2.init()
self.urukul_ch3.init()
delay(100*us)
self.urukul_ch0.set(10*MHz)
self.urukul_ch0.sw.on()
self.urukul_ch0.set_att(10.)
delay(100*us)
self.urukul_ch1.set(10*MHz, 0.5)
self.urukul_ch1.sw.on()
self.urukul_ch1.set_att(10.)
delay(100*us)
self.urukul_ch2.set(400*MHz)
self.urukul_ch2.sw.on()
self.urukul_ch2.set_att(0.)
delay(100*us)
self.urukul_ch3.set(1*MHz)
self.urukul_ch3.sw.on()
self.urukul_ch3.set_att(0.)
while True:
self.urukul_ch0.set_mu(0x123456789abc, 0)
while True:
self.urukul_ch0.sw.pulse(5*ms)
delay(5*ms)
while False:
self.led.pulse(.5*s)
delay(.5*s)
@kernel
def test_att_noise(self, n=1024):
bus = self.urukul_cpld.bus
bus.set_config_mu(_SPI_CONFIG, _SPIT_ATT_WR, _SPIT_ATT_RD)
bus.set_xfer(CS_ATT, 32, 0)
for i in range(n):
delay(5*us)
bus.write(self.att_reg)
bus.set_config_mu(_SPI_CONFIG, _SPIT_DDS_WR, _SPIT_DDS_RD)