forked from M-Labs/artiq
urukul: add CPLD and AD9912 driver [wip]
This commit is contained in:
parent
c2be820e9a
commit
a940550e47
|
@ -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))
|
||||
|
|
|
@ -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)))
|
|
@ -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": {
|
||||
|
|
|
@ -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)
|
Loading…
Reference in New Issue