From a940550e4755f92c96408acfd560099e2e2ee3ed Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Tue, 2 Jan 2018 14:52:13 +0100 Subject: [PATCH] urukul: add CPLD and AD9912 driver [wip] --- artiq/coredevice/ad9912.py | 131 ++++++++++---- artiq/coredevice/urukul.py | 162 ++++++++++++++++++ artiq/examples/master/device_db.py | 55 ++++++ .../coredevice_examples/simple/urukul.py | 72 ++++++++ 4 files changed, 386 insertions(+), 34 deletions(-) create mode 100644 artiq/coredevice/urukul.py create mode 100644 artiq/examples/master/repository/coredevice_examples/simple/urukul.py diff --git a/artiq/coredevice/ad9912.py b/artiq/coredevice/ad9912.py index 10b360806..4b522c54e 100644 --- a/artiq/coredevice/ad9912.py +++ b/artiq/coredevice/ad9912.py @@ -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)) diff --git a/artiq/coredevice/urukul.py b/artiq/coredevice/urukul.py new file mode 100644 index 000000000..750993eed --- /dev/null +++ b/artiq/coredevice/urukul.py @@ -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))) diff --git a/artiq/examples/master/device_db.py b/artiq/examples/master/device_db.py index 8d0b8c693..da3f55707 100644 --- a/artiq/examples/master/device_db.py +++ b/artiq/examples/master/device_db.py @@ -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": { diff --git a/artiq/examples/master/repository/coredevice_examples/simple/urukul.py b/artiq/examples/master/repository/coredevice_examples/simple/urukul.py new file mode 100644 index 000000000..8b8958a6c --- /dev/null +++ b/artiq/examples/master/repository/coredevice_examples/simple/urukul.py @@ -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)