from artiq.language.core import kernel, delay, portable from artiq.language.units import us, ms from numpy import int32, int64 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_CFG_WR = 2 SPIT_CFG_RD = 16 SPIT_ATT_WR = 2 SPIT_ATT_RD = 16 SPIT_DDS_WR = 2 SPIT_DDS_RD = 16 # CFG configuration register bit offsets CFG_RF_SW = 0 CFG_LED = 4 CFG_PROFILE = 8 CFG_IO_UPDATE = 12 CFG_MASK_NU = 13 CFG_CLK_SEL = 17 CFG_SYNC_SEL = 18 CFG_RST = 19 CFG_IO_RST = 20 # 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 # supported hardware and CPLD code version STA_PROTO_REV_MATCH = 0x08 # 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 @portable def urukul_cfg(rf_sw, led, profile, io_update, mask_nu, clk_sel, sync_sel, rst, io_rst): """Build Urukul CPLD configuration register""" return ((rf_sw << CFG_RF_SW) | (led << CFG_LED) | (profile << CFG_PROFILE) | (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)) @portable def urukul_sta_rf_sw(sta): """Return the RF switch status from Urukul status register value.""" return (sta >> STA_RF_SW) & 0xf @portable def urukul_sta_smp_err(sta): """Return the SMP_ERR status from Urukul status register value.""" return (sta >> STA_SMP_ERR) & 0xf @portable def urukul_sta_pll_lock(sta): """Return the PLL_LOCK status from Urukul status register value.""" return (sta >> STA_PLL_LOCK) & 0xf @portable def urukul_sta_ifc_mode(sta): """Return the IFC_MODE status from Urukul status register value.""" return (sta >> STA_IFC_MODE) & 0xf @portable def urukul_sta_proto_rev(sta): """Return the PROTO_REV value from Urukul status register value.""" return (sta >> STA_PROTO_REV) & 0x7f class _RegIOUpdate: def __init__(self, cpld): self.cpld = cpld @kernel def pulse(self, t): cfg = self.cpld.cfg_reg self.cpld.cfg_write(cfg | (1 << CFG_IO_UPDATE)) delay(t) self.cpld.cfg_write(cfg) class CPLD: """Urukul CPLD SPI router and configuration interface. :param spi_device: SPI bus device name :param io_update_device: IO update RTIO TTLOut channel name :param dds_reset_device: DDS reset RTIO TTLOut channel name :param refclk: Reference clock (SMA, MMCX or on-board 100 MHz oscillator) frequency in Hz :param clk_sel: Reference clock selection. 0 corresponds to the internal MMCX or ob-board XO clock. 1 corresponds to the front panel SMA. :param sync_sel: SYNC clock selection. 0 corresponds to SYNC clock over EEM from FPGA. 1 corresponds to SYNC clock from DDS0. :param rf_sw: Initial CPLD RF switch register setting (default: 0x0). Knowledge of this state is not transferred between experiments. :param att: Initial attenuator setting shift register (default: 0x00000000). See also: :meth:`set_all_att_mu`. Knowledge of this state is not transferred between experiments. :param core_device: Core device name """ kernel_invariants = {"refclk", "bus", "core", "io_update"} def __init__(self, dmgr, spi_device, io_update_device=None, dds_reset_device=None, sync_sel=0, clk_sel=0, rf_sw=0, refclk=125e6, att=0x00000000, core_device="core"): self.core = dmgr.get(core_device) self.refclk = refclk self.bus = dmgr.get(spi_device) if io_update_device is not None: self.io_update = dmgr.get(io_update_device) else: self.io_update = _RegIOUpdate(self) if dds_reset_device is not None: self.dds_reset = dmgr.get(dds_reset_device) self.cfg_reg = urukul_cfg(rf_sw=rf_sw, led=0, profile=0, io_update=0, mask_nu=0, clk_sel=clk_sel, sync_sel=sync_sel, rst=0, io_rst=0) self.att_reg = att @kernel def cfg_write(self, cfg): """Write to the configuration register. See :func:`urukul_cfg` for possible flags. :param data: 24 bit data to be written. Will be stored at :attr:`cfg_reg`. """ self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 24, SPIT_CFG_WR, CS_CFG) self.bus.write(cfg << 8) self.cfg_reg = cfg @kernel def sta_read(self): """Read the status register. Use any of the following functions to extract values: * :func:`urukul_sta_rf_sw` * :func:`urukul_sta_smp_err` * :func:`urukul_sta_pll_lock` * :func:`urukul_sta_ifc_mode` * :func:`urukul_sta_proto_rev` :return: The status register value. """ self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, 24, SPIT_CFG_RD, CS_CFG) self.bus.write(self.cfg_reg << 8) return self.bus.read() @kernel def init(self, blind=False): """Initialize and detect Urukul. Resets the DDS I/O interface and verifies correct CPLD gateware version. Does not pulse the DDS MASTER_RESET as that confuses the AD9910. :param blind: Do not attempt to verify presence and compatibility. """ cfg = self.cfg_reg # Don't pulse MASTER_RESET (m-labs/artiq#940) self.cfg_reg = cfg | (0 << CFG_RST) | (1 << CFG_IO_RST) if blind: self.cfg_write(self.cfg_reg) else: proto_rev = urukul_sta_proto_rev(self.sta_read()) if proto_rev != STA_PROTO_REV_MATCH: raise ValueError("Urukul proto_rev mismatch") delay(100*us) # reset, slack self.cfg_write(cfg) delay(1*ms) # DDS wake up @kernel def io_rst(self): """Pulse IO_RST""" self.cfg_write(self.cfg_reg | (1 << CFG_IO_RST)) self.cfg_write(self.cfg_reg & ~(1 << CFG_IO_RST)) @kernel def cfg_sw(self, channel, on): """Configure the RF switches through the configuration register. These values are logically OR-ed with the LVDS lines on EEM1. :param channel: Channel index (0-3) :param on: Switch value """ c = self.cfg_reg if on: c |= 1 << channel else: c &= ~(1 << channel) self.cfg_write(c) @kernel def set_att_mu(self, channel, att): """Set digital step attenuator in machine units. This method will write the attenuator settings of all four channels. :param channel: Attenuator channel (0-3). :param att: Digital attenuation setting: 255 minimum attenuation, 0 maximum attenuation (31.5 dB) """ a = self.att_reg & ~(0xff << (channel * 8)) a |= att << (channel * 8) self.set_all_att_mu(a) @kernel def set_all_att_mu(self, att_reg): """Set all four digital step attenuators (in machine units). .. seealso:: :meth:`set_att_mu` :param att_reg: Attenuator setting string (32 bit) """ self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 32, SPIT_ATT_WR, CS_ATT) self.bus.write(att_reg) self.att_reg = att_reg @kernel def set_att(self, channel, att): """Set digital step attenuator in SI units. :param channel: Attenuator channel (0-3). :param att: Attenuation setting in dB. Higher value is more attenuation. """ self.set_att_mu(channel, 255 - int32(round(att*8))) @kernel def get_att_mu(self): """Return the digital step attenuator settings in machine units. This method will also (as a side effect) write the attenuator settings of all four channels. :return: 32 bit attenuator settings """ self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, 32, SPIT_ATT_RD, CS_ATT) self.bus.write(self.att_reg) return self.bus.read()