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.core import kernel, delay_mu, delay
|
||||||
from artiq.language.units import ns, us
|
from artiq.language.units import us, ns
|
||||||
from artiq.coredevice import spi
|
from artiq.coredevice import spi, urukul
|
||||||
|
from artiq.coredevice.ad9912_reg import *
|
||||||
|
|
||||||
|
from numpy import int32, int64
|
||||||
_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)
|
|
||||||
|
|
||||||
|
|
||||||
class AD9912:
|
class AD9912:
|
||||||
"""
|
"""
|
||||||
Support for the Analog devices AD9912 DDS
|
Support for the Analog devices AD9912 DDS
|
||||||
|
|
||||||
:param spi_device: Name of the SPI bus this device is on.
|
:param chip_select: Chip select configuration.
|
||||||
:param chip_select: Value to drive on the chip select lines
|
:param cpld_device: Name of the Urukul CPLD this device is on.
|
||||||
during transactions.
|
: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):
|
def __init__(self, dmgr, chip_select, cpld_device, sw_device=None,
|
||||||
self.core = dmgr.get("core")
|
pll_n=10):
|
||||||
self.bus = dmgr.get(spi_device)
|
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
|
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
|
@kernel
|
||||||
def setup_bus(self, write_div=5, read_div=20):
|
def write(self, addr, data, length=1):
|
||||||
"""Configure the SPI bus and the SPI transaction parameters
|
assert length > 0
|
||||||
for this device. This method has to be called before any other method
|
assert length <= 4
|
||||||
if the bus has been used to access a different device in the meantime.
|
self.bus.set_xfer(self.chip_select, 16, 0)
|
||||||
|
self.bus.write((addr | ((length - 1) << 13)) << 16)
|
||||||
This method advances the timeline by the duration of two
|
delay_mu(-self.bus.xfer_period_mu)
|
||||||
RTIO-to-Wishbone bus transactions.
|
self.bus.set_xfer(self.chip_select, length*8, 0)
|
||||||
|
if length < 4:
|
||||||
:param write_div: Write clock divider.
|
data <<= 32 - length*8
|
||||||
:param read_div: Read clock divider.
|
self.bus.write(data)
|
||||||
"""
|
delay_mu(self.bus.xfer_period_mu - self.bus.write_period_mu)
|
||||||
# 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)
|
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def write(self, data):
|
def read(self, addr, length=1):
|
||||||
"""Write 24 bits of data.
|
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
|
@kernel
|
||||||
and the required CS high time.
|
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)
|
return int64(round(self.ftw_per_hz*frequency))
|
||||||
delay_mu(self.bus.ref_period_mu) # get to 20ns min cs high
|
|
||||||
|
@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",
|
"class": "TTLOut",
|
||||||
"arguments": {"channel": 38}
|
"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
|
# AD9914 DDS
|
||||||
"dds0": {
|
"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