Source code for qick.rfboard

import os
from .qick import SocIp, QickSoc
from .qick_asm import QickConfig
from pynq.overlay import Overlay, DefaultIP
from pynq.buffer import allocate
import xrfclk
import numpy as np
import time
from qick.ipq_pynq_utils import clock_models


[docs]class AxisSignalGenV3(SocIp): # AXIS Table Registers. # START_ADDR_REG # # WE_REG # * 0 : disable writes. # * 1 : enable writes. # bindto = ['user.org:user:axis_signal_gen_v3:1.0'] REGISTERS = {'start_addr_reg': 0, 'we_reg': 1} # Generics N = 12 NDDS = 16 # Maximum number of samples MAX_LENGTH = 2**N*NDDS def __init__(self, description, **kwargs): super().__init__(description) def config(self, axi_dma, dds_mr_switch, axis_switch, channel, name, **kwargs): # Default registers. self.start_addr_reg = 0 self.we_reg = 0 # dma self.dma = axi_dma # Real/imaginary selection switch. #self.iq_switch = AxisDdsMrSwitch(dds_mr_switch) self.iq_switch = dds_mr_switch # switch self.switch = axis_switch # Channel. self.ch = channel # Name. self.name = name # Load waveforms. def load(self, buff_in, addr=0): # Route switch to channel. self.switch.sel(slv=self.ch) time.sleep(0.1) # Define buffer. self.buff = allocate(shape=(len(buff_in)), dtype=np.int16) ################### ### Load I data ### ################### np.copyto(self.buff, buff_in) # Enable writes. self.wr_enable(addr) # DMA data. self.dma.sendchannel.transfer(self.buff) self.dma.sendchannel.wait() # Disable writes. self.wr_disable() def wr_enable(self, addr=0): self.start_addr_reg = addr self.we_reg = 1 def wr_disable(self): self.we_reg = 0
[docs]class AxisSignalGenV3Ctrl(SocIp): # Signal Generator V3 Control registers. # ADDR_REG bindto = ['user.org:user:axis_signal_gen_v3_ctrl:1.0'] REGISTERS = { 'freq': 0, 'phase': 1, 'addr': 2, 'gain': 3, 'nsamp': 4, 'outsel': 5, 'mode': 6, 'stdysel': 7, 'we': 8} # Generics of Signal Generator. N = 10 NDDS = 16 B = 16 MAX_v = 2**B - 1 # Sampling frequency. fs = 4096 def __init__(self, description, **kwargs): super().__init__(description) # Default registers. self.freq = 0 self.phase = 0 self.addr = 0 self.gain = 30000 self.nsamp = 16*100 self.outsel = 1 # dds self.mode = 1 # periodic self.stdysel = 1 # zero self.we = 0 def add(self, freq=0, phase=0, addr=0, gain=30000, nsamp=16*100, outsel="dds", mode="periodic", stdysel="zero"): # Input frequency is in MHz. w0 = 2*np.pi*freq/self.fs freq_tmp = w0/(2*np.pi)*self.MAX_v self.freq = int(np.round(freq_tmp)) self.phase = phase self.addr = addr self.gain = gain self.nsamp = int(np.round(nsamp/self.NDDS)) self.outsel = {"product": 0, "dds":1, "envelope":2}[outsel] self.mode = {"nsamp": 0, "periodic":1}[mode] self.stdysel = {"last": 0, "zero":1}[stdysel] # Write fifo.. self.we = 1 self.we = 0 def set_fs(self, fs): self.fs = fs
[docs]class AxisSignalGenV6Ctrl(SocIp): # Signal Generator V6 Control registers. # FREQ_REG : 32-bit. # PHASE_REG : 32-bit. # ADDR_REG : 16-bit. # GAIN_REG : 16-bit. # NSAMP_REG : 16-bit. # OUTSEL_REG : 2-bit. # * 0 : product. # * 1 : dds. # * 2 : envelope. # MODE_REG : 1-bit. # * 0 : nsamp. # * 1 : periodic. # STDYSEL_REG : 1-bit. # * 0 : last. # * 1 : zero. # PHRST_REG : 1-bit. # * 0 : don't reset. # * 1 : reset. # WE_REG : 1-bit. # * 0 : disable. # * 1 : enable. bindto = ['user.org:user:axis_signal_gen_v6_ctrl:1.0'] REGISTERS = { 'freq_reg' : 0, 'phase_reg' : 1, 'addr_reg' : 2, 'gain_reg' : 3, 'nsamp_reg' : 4, 'outsel_reg' : 5, 'mode_reg' : 6, 'stdysel_reg' : 7, 'phrst_reg' : 8, 'we_reg' : 9} def __init__(self, description, **kwargs): super().__init__(description) # Default registers. self.we_reg = 0 def configure(self, fs, gen): # Sampling frequency. self.fs = fs # Frequency resolution. self.df = fs/2**gen.B_DDS # Generator controlled by this block. self.gen = gen def add(self, freq = 0 , phase = 0 , addr = 0 , gain = 0.99 , nsamp = 16*100 , outsel = "dds" , mode = "periodic", stdysel = "zero" , phrst = "no" , debug = False ): # Set registers. self.freq_reg = int(np.round(freq/self.df)) self.phase_reg = phase self.addr_reg = addr self.gain_reg = int(gain*self.gen.MAXV) self.nsamp_reg = int(np.round(nsamp/self.gen.NDDS)) self.outsel_reg = {"product": 0, "dds":1, "envelope":2}[outsel] self.mode_reg = {"nsamp": 0, "periodic":1}[mode] self.stdysel_reg = {"last": 0, "zero":1}[stdysel] self.phase_reg = {"no": 0, "yes":1}[phrst] if debug: print("{}".format(self.__class__.__name__)) print(" * freq_reg : {}".format(self.freq_reg)) print(" * phase_reg : {}".format(self.phase_reg)) print(" * addr_reg : {}".format(self.addr_reg)) print(" * gain_reg : {}".format(self.gain_reg)) print(" * nsamp_reg : {}".format(self.nsamp_reg)) print(" * outsel_reg : {}".format(self.outsel_reg)) print(" * mode_reg : {}".format(self.mode_reg)) print(" * stdysel_reg : {}".format(self.stdysel_reg)) print(" * phase_reg : {}".format(self.phase_reg)) # Write fifo.. self.we_reg = 1 self.we_reg = 0
[docs]class AxisDdsMrSwitch(SocIp): # AXIS DDS MR SWITCH registers. # DDS_REAL_IMAG_REG # * 0 : real part. # * 1 : imaginary part. # bindto = ['user.org:user:axis_dds_mr_switch:1.0'] REGISTERS = {'dds_real_imag': 0} def __init__(self, description, **kwargs): """ Constructor method """ super().__init__(description) # Default registers. # dds_real_imag = 0 : take real part. self.dds_real_imag = 0 def config(self, reg_): self.dds_real_imag = reg_ def real(self): self.config(0) def imag(self): self.config(1)
[docs]class spi(DefaultIP): bindto = ['xilinx.com:ip:axi_quad_spi:3.2'] SPI_REGLIST = ['DGIER', 'IPISR', 'IPIER', 'SRR', 'SPICR', 'SPISR', 'SPI_DTR', 'SPI_DRR', 'SPI_SSR', 'SPI_TXFIFO_OR', 'SPI_RXFIFO_OR'] # # SPI registers - See Xilinx PG153 AXI Quad SPI for discriptions # #DGIER = 0x1C # 0x1C - RW - SPI Device Global Interrupt Enable Register #IPISR = 0x20 # 0x20 - RW - SPI IP Interrupt Status Register #IPIER = 0x28 # 0x28 - RW - SPI IP Interrupt Enable Register #SRR = 0x40 # 0x40 - WO - SPI Software Reset Reg #SPICR = 0x60 # 0x60 - RW - SPI Control Register #SPISR = 0x64 # 0x64 - RO - SPI Status Register #SPI_DTR = 0x68 # 0x68 - WO - SPI Data Transmit Register #SPI_DRR = 0x6C # 0x6C - RO - SPI Data Receive Register #SPI_SSR = 0x70 # 0x70 - RW - SPI Slave Select Register #SPI_TXFIFO_OR = 0x74 # 0x74 - RW - SPI Transmit FIFO Occupancy Register #SPI_RXFIFO_OR = 0x78 # 0x78 - RO - SPI Receive FIFO Occupancy Register def __init__(self, description, **kwargs): super().__init__(description) # Data width. self.data_width = int(description['parameters']['C_NUM_TRANSFER_BITS']) # Soft reset SPI. self.rst() # De-assert slave select self.SPI_SSR = 0 def __setattr__(self, a, v): if a in self.SPI_REGLIST: setattr(self.register_map, a, v) else: super().__setattr__(a, v) def __getattr__(self, a): #print(self.SPI_REGLIST) if a in self.SPI_REGLIST: return getattr(self.register_map, a) else: return super().__getattribute__(a) def rst(self): self.SRR = 0xA # SPI Control Register: # Bit 9 : LSB/MSB selection. # -> 0 : MSB first # -> 1 : LSB first # # Bit 8 : Master Transaction Inhibit. # -> 0 : Master Transaction Enabled. # -> 1 : Master Transaction Disabled. # # Bit 7 : Manual Slave Select Assertion. # -> 0 : Slave select asserted by master core logic. # -> 1 : Slave select follows data in SSR. # # Bit 6 : RX FIFO Reset. # -> 0 : Normal operation. # -> 1 : Reset RX FIFO. # # Bit 5 : TX FIFO Reset. # -> 0 : Normal operation. # -> 1 : Reset RX FIFO. # # Bit 4 : Clock Phase. # -> 0 : # -> 1 : # # Bit 3 : Clock Polarity. # -> 0 : Active-High clock. SCK idles low. # -> 1 : Active-Low clock. SCK idles high. # # Bit 2 : Master mode. # -> 0 : Slave configuration. # -> 1 : Master configuration. # # Bit 1 : SPI system enable. # -> 0 : SPI disabled. Outputs 3-state. # -> 1 : SPI enabled. # # Bit 0 : Local loopback mode. # -> 0 : Normal operation. # -> 1 : Loopback mode. def config(self, lsb="lsb", msttran="enable", ssmode="ssr", rxfifo="rst", txfifo="rst", cpha="", cpol="high", mst="master", en="enable", loopback="no"): # LSB/MSB. self.register_map.SPICR.LSB_First = {"lsb":1, "msb":0}[lsb] # Master transaction inhibit. self.register_map.SPICR.Master_Transaction_Inhibit = {"disable":1, "enable":0}[msttran] # Manual slave select. self.register_map.SPICR.Manual_Slave_Select_Assertion_Enable = {"ssr":1, "auto":0}[ssmode] # RX FIFO. self.register_map.SPICR.RX_FIFO_Reset = {"rst":1, "":0}[rxfifo] # TX FIFO. self.register_map.SPICR.TX_FIFO_Reset = {"rst":1, "":0}[txfifo] # CPHA. self.register_map.SPICR.CPHA = {"invert":1, "":0}[cpha] # CPOL self.register_map.SPICR.CPOL = {"low":1, "high":0}[cpol] # Master mode. self.register_map.SPICR.Master = {"master":1, "slave":0}[mst] # SPI enable. self.register_map.SPICR.SPE = {"enable":1, "disable":0}[en] # Loopback self.register_map.SPICR.LOOP = {"yes":1, "no":0}[loopback] # Enable function.
[docs] def en_level(self, nch=4, chlist=[0], en_l="high"): """ chlist: list of bits to enable en_l: enable level "high": ignore nch, enabled bits are set high "low": nch is total length, enabled bits are set low """ ch_en = 0 if en_l == "high": for i in range(len(chlist)): ch_en |= (1 << chlist[i]) elif en_l == "low": ch_en = 2**nch - 1 for i in range(len(chlist)): ch_en &= ~(1 << chlist[i]) return ch_en
# Send function.
[docs] def send_m(self, data, ch_en, cs_t="pulse"): """ The data must be formatted in bytes, regardless of the data width of the SPI IP. For data width 16 or 32, the bytes will be packed in little-endian order. """ if not isinstance(data, bytes): raise RuntimeError("data is not a bytes object: ", data) if self.data_width == 16: data = np.frombuffer(data, dtype=np.dtype('H')) # uint16 elif self.data_width == 32: data = np.frombuffer(data, dtype=np.dtype('I')) # uint32 # Manually assert channels. ch_en_temp = self.SPI_SSR.Selected_Slave # Standard CS at the beginning of transaction. if cs_t != "pulse": self.SPI_SSR = ch_en # Send data. for word in data: # Send data. self.SPI_DTR = word # LE pulse at the end. if cs_t == "pulse": # Write SSR to enable channels. self.SPI_SSR = ch_en # Write SSR to previous value. self.SPI_SSR = ch_en_temp # Bring CS to default value. if cs_t != "pulse": self.SPI_SSR = ch_en_temp
# Receive function.
[docs] def receive(self): """ The returned data will be formatted in bytes, regardless of the data width of the SPI IP. For data width 16 or 32, the bytes will be unpacked in little-endian order. """ # Fifo is empty if self.SPISR.RX_Empty==1: return bytes() else: # Get number of samples on fifo. nr = self.SPI_RXFIFO_OR.Occupancy_Value + 1 data_r = [self.SPI_DRR.RX_Data for i in range(nr)] if self.data_width == 8: return bytes(data_r) elif self.data_width == 16: return np.array(data_r).astype(np.dtype('H')).tobytes() # uint16 elif self.data_width == 32: return np.array(data_r).astype(np.dtype('I')).tobytes() # uint32
# Send/Receive.
[docs] def send_receive_m(self, data, ch_en, cs_t="pulse"): """ data: list of bytes to send ch_en: destination address """ self.send_m(data, ch_en, cs_t) data_r = self.receive() return data_r
# Step Attenuator PE43705. # Range 0-31.75 dB. # Parts are used in serial mode. # See schematics for Address/LE correspondance. class PE43705: address = 0 nSteps = 2**7 dbStep = 0.25 dbMinAtt = 0 dbMaxAtt = (nSteps-1)*dbStep def __init__(self, address=0): self.address = address def db2step(self, db): ret = -1 # Sanity check. if db < self.dbMinAtt: raise RuntimeError("attenuation value %f out of range" % (db)) elif db > self.dbMaxAtt: raise RuntimeError("attenuation value %f out of range" % (db)) else: ret = int(np.round(db/self.dbStep)) return ret def db2reg(self, db): # will get packed as (address << 8) | step return bytes([self.db2step(db), self.address]) # GPIO chip MCP23S08. class MCP23S08: # Commands. cmd_wr = 0x40 cmd_rd = 0x41 # Registers. REGS = {'IODIR_REG': 0x00, 'IPOL_REG': 0x01, 'GPINTEN_REG': 0x02, 'DEFVAL_REG': 0x03, 'INTCON_REG': 0x04, 'IOCON_REG': 0x05, 'GPPU_REG': 0x06, 'INTF_REG': 0x07, 'INTCAP_REG': 0x08, 'GPIO_REG': 0x09, 'OLAT_REG': 0x0A} def __init__(self, dev_addr): self.dev_addr = dev_addr # Register/address mapping. def reg2addr(self, reg="GPIO_REG"): if reg in self.REGS: return self.REGS[reg] else: print("%s: register %s not recognized." % (self.__class__.__name__, reg)) return -1 # Data array: 3 bytes. # byte[0] = opcode. # byte[1] = register address. # byte[2] = register value (dummy for read). def reg_rd(self, reg="GPIO_REG"): # Read command. cmd = self.cmd_rd + 2*self.dev_addr # Address. addr = self.reg2addr(reg) # Dummy byte for clocking data out. return bytes([cmd, addr, 0]) def reg_wr(self, reg="GPIO_REG", val=0): # Write command. cmd = self.cmd_wr + 2*self.dev_addr # Address. addr = self.reg2addr(reg) return bytes([cmd, addr, val]) # LO Chip ADF4372. class ADF4372: # Reference input. f_REF_in = 122.88 # Fixed 25-bit modulus. MOD1 = 2**25 # Commands. cmd_wr = 0x00 cmd_rd = 0x80 # Registers. REGS = {'CONFIG0_REG': 0x00, 'CONFIG1_REG': 0x01, 'CHIP_REG': 0x03, 'PROD_ID0_REG': 0x04, 'PROD_ID1_REG': 0x05, 'PROD_REV_REG': 0x06, 'INT_LOW_REG': 0x10, 'INT_HIGH_REG': 0x11, 'CAL_PRE_REG': 0x12, 'FRAC1_LOW_REG': 0x14, 'FRAC1_MID_REG': 0x15, 'FRAC1_HIGH_REG': 0x16, 'FRAC2_LOW_REG': 0x17, # NOTE: bit zero is the MSB of FRAC1. 'FRAC2_HIGH_REG': 0x18, 'MOD2_LOW_REG': 0x19, 'MOD2_HIGH_REG': 0x1A, # NOTE: bi 6 is PHASE_ADJ. 'PHASE_LOW_REG': 0x1B, 'PHASE_MID_REG': 0x1C, 'PHASE_HIGH_REG': 0x1D, 'CONFIG2_REG': 0x1E, 'RCNT_REG': 0x1F, 'MUXOUT_REG': 0x20, 'REF_REG': 0x22, 'CONFIG3_REG': 0x23, 'RFDIV_REG': 0x24, 'RFOUT_REG': 0x25, 'BLEED0_REG': 0x26, 'BLEED1_REG': 0x27, 'LOCK_REG': 0x28, 'CONFIG4_REG': 0x2A, 'SD_REG': 0x2B, 'VCO_BIAS0_REG': 0x2C, 'VCO_BIAS1_REG': 0x2D, 'VCO_BIAS2_REG': 0x2E, 'VCO_BIAS3_REG': 0x2F, 'VCO_BAND_REG': 0x30, 'TIMEOUT_REG': 0x31, 'ADC_REG': 0x32, 'SYNTH_TIMEOUT_REG': 0x33, 'VCO_TIMEOUT_REG': 0x34, 'ADC_CLK_REG': 0x35, 'ICP_OFFSET_REG': 0x36, 'SI_BAND_REG': 0x37, 'SI_VCO_REG': 0x38, 'SI_VTUNE_REG': 0x39, 'ADC_OFFSET_REG': 0x3A, 'SD_RESET_REG': 0x3D, 'CP_TMODE_REG': 0x3E, 'CLK1_DIV_LOW_REG': 0x3F, 'CLK1_DIV_HIGH_REG': 0x40, 'CLK2_DIV_REG': 0x41, 'TRM_RESD0_REG': 0x47, 'TRM_RESD1_REG': 0x52, 'VCO_DATA_LOW_REG': 0x6E, 'VCO_DATA_HIGH_REG': 0x6F, 'BIAS_SEL_X2_REG': 0x70, 'BIAS_SEL_X4_REG': 0x71, 'AUXOUT_REG': 0x72, 'LD_PD_ADC_REG': 0x73, 'LOCK_DETECT_REG': 0x7C} def reg2addr(self, reg="CONFIG0_REG"): if reg in self.REGS: return self.REGS[reg] else: print("%s: register %s not recognized." % (self.__class__.__name__, reg)) return -1 # Data array: 3 bytes. # byte[0] = opcode/addr high. # byte[1] = addr low. # byte[2] = register value (dummy for read). def reg_rd(self, reg="CONFIG0_REG"): # Address. addr = self.reg2addr(reg) # Dummy byte for clocking data out. return bytes([self.cmd_rd, addr, 0]) def reg_wr(self, reg="CONFIG0_REG", val=0): # Address. addr = self.reg2addr(reg) return bytes([self.cmd_wr, addr, val]) # Simple frequency setting function. # FRAC2 = 0 not used. # INT,FRAC1 sections are used. # All frequencies are in MHz. # Frequency must be in the range 4-8 GHz. def set_freq(self, fin=6000): # Structures for output. regs = {} regs['INT'] = {'FULL': 0, 'LOW': 0, 'HIGH': 0} regs['FRAC1'] = {'FULL': 0, 'LOW': 0, 'MID': 0, 'HIGH': 0, 'MSB': 0} # Sanity check. if fin < 4000: print("%s: input frequency %d below the limit" % (self.__class__.__name__, fin)) return -1 elif fin > 8000: print("%s: input frequency %d above the limit" % (self.__class__.__name__, fin)) return -1 Ndiv = fin/self.f_REF_in # Integer part. int_ = int(np.floor(Ndiv)) int_low = int_ & 0xff int_high = int_ >> 8 # Fractional part. frac_ = Ndiv - int_ frac_ = int(np.floor(frac_*self.MOD1)) frac_low = frac_ & 0xff frac_mid = (frac_ >> 8) & 0xff frac_high = (frac_ >> 16) & 0xff frac_msb = frac_ >> 24 # Write values into structure. regs['INT']['FULL'] = int_ regs['INT']['LOW'] = int_low regs['INT']['HIGH'] = int_high regs['FRAC1']['FULL'] = frac_ regs['FRAC1']['LOW'] = frac_low regs['FRAC1']['MID'] = frac_mid regs['FRAC1']['HIGH'] = frac_high regs['FRAC1']['MSB'] = frac_msb return regs # BIAS DAC chip AD5781. class AD5781: # Commands. cmd_wr = 0x0 cmd_rd = 0x1 # Negative/Positive voltage references. VREFN = -10 VREFP = 10 # Bits. B = 18 # Registers. REGS = {'DAC_REG': 0x01, 'CTRL_REG': 0x02, 'CLEAR_REG': 0x03, 'SOFT_REG': 0x04} # Register/address mapping. def reg2addr(self, reg="DAC_REG"): if reg in self.REGS: return self.REGS[reg] else: print("%s: register %s not recognized." % (self.__class__.__name__, reg)) return -1 def reg_rd(self, reg="DAC_REG"): # Address. addr = self.reg2addr(reg) # R/W bit + address (upper 4 bits). cmd = (self.cmd_rd << 3) | addr cmd = (cmd << 4) # Dummy bytes for completing the command. # NOTE: another full, 24-bit transaction is needed to clock the register out (may be all 0s). return bytes([cmd, 0, 0]) def reg_wr(self, reg="DAC_REG", val=0): byte = [] # Address. addr = self.reg2addr(reg) # R/W bit + address (upper 4 bits). cmd = (self.cmd_wr << 3) | addr cmd = (cmd << 20) | val return cmd.to_bytes(length=3, byteorder='big') # Compute register value for voltage setting. def volt2reg(self, volt=0): if volt < self.VREFN: print("%s: %d V out of range." % (self.__class__.__name__, volt)) return -1 elif volt > self.VREFP: print("%s: %d V out of range." % (self.__class__.__name__, volt)) return -1 else: Df = (2**self.B - 1)*(volt - self.VREFN)/(self.VREFP - self.VREFN) # Shift by two as 2 lower bits are not used. Df = int(Df) << 2 return int(Df) # BIAS DAC chip DAC11001. class DAC11001: # Commands. cmd_wr = 0x0 cmd_rd = 0x1 # Negative/Positive voltage references. VREFN = -10 VREFP = 10 # Bits. B = 20 # Registers. REGS = {'DAC_DATA_REG' : 0x01 , 'CONFIG1_REG' : 0x02 , 'DAC_CLEAR_DATA_REG': 0x03 , 'TRIGGER_REG' : 0x04 , 'STATUS_REG' : 0x05 , 'CONFIG2_REG' : 0x06 } # Register/address mapping. def reg2addr(self, reg="DAC_DATA_REG"): if reg in self.REGS: return self.REGS[reg] else: print("%s: register %s not recognized." % (self.__class__.__name__, reg)) return -1 def reg_rd(self, reg="DAC_DATA_REG"): data = 0 # Address. addr = self.reg2addr(reg) # R/W bit (MSB) + address (lower 7 bits). cmd = (self.cmd_rd << 7) | addr #data |= (cmd << 24) data = bytes(3) + bytes([cmd]) return data def reg_wr(self, reg="DAC_DATA_REG", val=0): data = 0 # Address. addr = self.reg2addr(reg) # R/W bit (MSB) + address (lower 7 bits). cmd = (self.cmd_wr << 7) | addr #data |= (cmd << 24) # Value is 24 bits (lower 4 not used). #data |= val data = val.to_bytes(length=3, byteorder='little') + bytes([cmd]) return data # Compute register value for voltage setting. def volt2reg(self, volt=0): Df = np.round(2**self.B*(volt - self.VREFN)/(self.VREFP - self.VREFN)) if (Df<0 or Df>2**self.B): raise RuntimeError("%f V out of range." % (volt)) elif Df==2**self.B: # special case: V=VREFP is actually not reachable, but that's annoying and nobody will mind if we round down by an LSB Df -= 1 # Shift by two as 4 lower bits are not used. return int(Df) << 4 # ADMV8818 Filter Chip. class ADMV8818: # Commands. cmd_wr = 0x00 cmd_rd = 0x01 # Registers. REGS = {'ADI_SPI_CONFIG_A' : 0x000, 'ADI_SPI_CONFIG_B' : 0x001, 'CHIPTYPE' : 0x003, 'PRODUCT_ID_L' : 0x004, 'PRODUCT_ID_H' : 0x005, 'WR0_SW' : 0x020, 'WR0_FILTER' : 0x021} # Filter Bands. BANDS = {} BANDS['LPF'] = {} BANDS['LPF']['bypass'] = {'min': 2.00 , 'max': 18.00, 'switch': 0} BANDS['LPF']['LPF1'] = {'min': 2.05 , 'max': 3.85 , 'switch': 1} BANDS['LPF']['LPF2'] = {'min': 3.35 , 'max': 7.25 , 'switch': 2} BANDS['LPF']['LPF3'] = {'min': 7.00 , 'max': 13.00, 'switch': 3} BANDS['LPF']['LPF4'] = {'min': 12.55, 'max': 18.85, 'switch': 4} BANDS['HPF'] = {} BANDS['HPF']['bypass'] = {'min': 2.00 , 'max': 18.00, 'switch': 0} BANDS['HPF']['HPF1'] = {'min': 1.75 , 'max': 3.55 , 'switch': 1} BANDS['HPF']['HPF2'] = {'min': 3.40 , 'max': 7.25 , 'switch': 2} BANDS['HPF']['HPF3'] = {'min': 6.60 , 'max': 12.60, 'switch': 3} # Actual max is 12.00. Modified to overlap bands. BANDS['HPF']['HPF4'] = {'min': 12.50, 'max': 19.90, 'switch': 4} # Number of bits for band setting. B = 4 def __init__(self): # Initialize df for each band. for b in self.BANDS['LPF'].keys(): span = self.BANDS['LPF'][b]['max'] - self.BANDS['LPF'][b]['min'] df = span/2**self.B self.BANDS['LPF'][b]['span'] = span self.BANDS['LPF'][b]['df'] = df for b in self.BANDS['HPF'].keys(): span = self.BANDS['HPF'][b]['max'] - self.BANDS['HPF'][b]['min'] df = span/2**self.B self.BANDS['HPF'][b]['span'] = span self.BANDS['HPF'][b]['df'] = df def cmd_wr(self, reg="CHIPTYPE", value=0, debug=False): if reg in self.REGS.keys(): # Register addresss. addr = self.REGS[reg] # Data. byte = (addr & 0x7fff).to_bytes(length=2, byteorder='big') + value.to_bytes(length=1, byteorder='big') if debug: for b in byte: print("{}: 0x{:02X}".format(self.__class__.__name__, b)) else: raise RuntimeError("%s: register %s not found." %(self.__class__.__name__, reg)) return byte def cmd_rd(self, reg="CHIPTYPE", debug=False): if reg in self.REGS.keys(): # Register addresss. addr = self.REGS[reg] # Dummy. byte = (0x8000 | (addr & 0x7fff)).to_bytes(length=2, byteorder='big') + bytes(1) if debug: for b in byte: print("{}: 0x{:02X}".format(self.__class__.__name__, b)) else: raise RuntimeError("%s: register %s not found." %(self.__class__.__name__, reg)) return byte def freq2band(self, f=0, section="LPF", debug=False): ret = None if section == "LPF": for b in self.BANDS[section].keys(): if b != 'bypass': fmin = self.BANDS[section][b]['min'] fmax = self.BANDS[section][b]['max'] if ((f > fmin) and (f < fmax)): ret = b break elif section == "HPF": for b in self.BANDS[section].keys(): if b != 'bypass': fmin = self.BANDS[section][b]['min'] fmax = self.BANDS[section][b]['max'] if ((f > fmin) and (f < fmax)): ret = b break if debug: if ret is not None: print("{}: frequency {:.2f} GHz for section {} found in band {}".format(self.__class__.__name__, f, section, b)) else: print("{}: frequency {:.2f} GHz for section {} not found.".format(self.__class__.__name__, f, section)) return ret def freq2bits(self, f=0, section="LPF", band="LPF1", debug=False): ret = None if section == "LPF": if band in self.BANDS[section].keys(): fmin = self.BANDS[section][band]['min'] fmax = self.BANDS[section][band]['max'] span = self.BANDS[section][band]['span'] df = self.BANDS[section][band]['df'] if ((f > fmin) and (f < fmax)): ret = int((f - fmin)/df) elif section == "HPF": if band in self.BANDS[section].keys(): fmin = self.BANDS[section][band]['min'] fmax = self.BANDS[section][band]['max'] span = self.BANDS[section][band]['span'] df = self.BANDS[section][band]['df'] if ((f > fmin) and (f < fmax)): ret = int((f - fmin)/df) if ret is not None: if debug: print("{}: fmin = {:.2f} GHz, fmax = {:.2f} GHz, span = {:.2f} GHz, df = {:.3f} GHz, f = {:.2f} GHz, bits = {}".format(self.__class__.__name__, fmin, fmax, span, df, f, ret)) else: print("{}: frequency {:.2f} GHz not found in section {} band {}.".format(self.__class__.__name__, f, section, band)) return ret def band2switch(self, section="LPF", band="LPF1", debug=False): if section in self.BANDS.keys(): if band in self.BANDS[section].keys(): return self.BANDS[section][band]['switch'] else: print("{}: band {} not found in section {}. Using bypass by default.".format(self.__class__.__name__, band, section)) return 0 else: print("{}: section {} not found. Using bypass by default.".format(self.__class__.__name__, section)) return 0 # Attenuator class: This class instantiates spi and PE43705 to simplify access to attenuator. class attenuator: # Constructor. def __init__(self, spi_ip, ch=0, nch=3, le=[0], en_l="high", cs_t="pulse"): # PE43705. self.pe = PE43705(address=ch) # SPI. self.spi = spi_ip # Lath-enable. self.ch_en = self.spi.en_level(nch, le, en_l) self.cs_t = cs_t # Initialize with max attenuation. self.set_att(31.75) # Set attenuation function. def set_att(self, db): # Register value. reg = self.pe.db2reg(db) # Write value using spi. self.spi.send_receive_m(reg, self.ch_en, self.cs_t) # Filter class: This class instantiates spi and ADMV8818 to simplify access to the filter chip. class prog_filter: # Constructor. def __init__(self, spi_ip, ch=0, cs_t=""): # ADMV8818. self.ic = ADMV8818() # SPI. self.spi = spi_ip # Lath-enable. self.ch_en = ch self.cs_t = cs_t # All CS to high value. self.spi.SPI_SSR = 0xff def reg_wr(self, reg="CHIPTYPE", value=0, debug=False): if debug: print("{}: writing register {}".format(self.__class__.__name__, reg)) # Byte array. byte = self.ic.cmd_wr(reg=reg, value=value, debug=debug) # Execute write. self.spi.send_receive_m(byte, self.ch_en, self.cs_t) def reg_rd(self, reg="CHIPTYPE", debug=False): if debug: print("{}: reading register {}".format(self.__class__.__name__, reg)) # Byte array. byte = self.ic.cmd_rd(reg=reg, debug=debug) # Send/receive. ret = int.from_bytes(self.spi.send_receive_m(byte, self.ch_en, self.cs_t), byteorder="big") # Execute write. return ret & 0xff def set_filter(self, fc=0, bw=None, ftype="lowpass", debug=False): # Low-pass. if ftype == 'lowpass': if debug: print("{}: setting {} filter type, fc = {:.2f} GHz.".format(self.__class__.__name__, ftype, fc)) band_lpf = self.ic.freq2band(f=fc, section="LPF", debug=debug) bits_lpf = self.ic.freq2bits(f=fc, section="LPF", band=band_lpf, debug=debug) band_hpf = 'bypass' bits_hpf = 0 elif ftype == 'highpass': if debug: print("{}: setting {} filter type, fc = {:.2f} GHz.".format(self.__class__.__name__, ftype, fc)) band_lpf = 'bypass' bits_lpf = 0 band_hpf = self.ic.freq2band(f=fc, section="HPF", debug=debug) bits_hpf = self.ic.freq2bits(f=fc, section="HPF", band=band_hpf, debug=debug) elif ftype == 'bandpass': # Default bw is 2 GHz. if bw is None: bw = 2 f1 = fc-bw/2 f2 = fc+bw/2 if debug: print("{}: setting {} filter type, fc = {:.2f} GHz, bw = {:.2f} GHz.".format(self.__class__.__name__, ftype, fc, bw)) band_lpf = self.ic.freq2band(f=f2, section="LPF", debug=debug) bits_lpf = self.ic.freq2bits(f=f2, section="LPF", band=band_lpf, debug=debug) band_hpf = self.ic.freq2band(f=f1, section="HPF", debug=debug) bits_hpf = self.ic.freq2bits(f=f1, section="HPF", band=band_hpf, debug=debug) elif ftype == 'bypass': if debug: print("{}: setting filter to bypass mode.".format(self.__class__.__name__)) band_lpf = 'bypass' bits_lpf = 0 band_hpf = 'bypass' bits_hpf = 0 else: raise Warning("%s: filter type %s not supported." % (self.__class__.__name__, ftype)) # WR0_SW register. value = 0xc0 + (self.ic.band2switch(section="HPF", band=band_hpf) << 3) + self.ic.band2switch(section="LPF", band=band_lpf) self.reg_wr(reg="WR0_SW", value=value, debug=debug) # WR0_FILTER register. value = (bits_hpf << 4) + bits_lpf self.reg_wr(reg="WR0_FILTER", value=value, debug=debug) # Power, Switch and Fan. class SwitchControl: # Constructor. def __init__(self, spi_ip): self.spi = spi_ip self.devs = [] self.net2port = {} def add_MCP(self, ch_en, outputs, dev_addr=0): if len(outputs) != 8: raise RuntimeError("must define all 8 outputs from the MCP23S08 (use None for NC pins)") defaults = 0 for iOutput, output in enumerate(outputs): defaults <<= 1 if output is not None: netname, defaultval = output self.net2port[netname] = (len(self.devs), iOutput) defaults += defaultval self.devs.append(power_sw_fan(self.spi, ch_en=ch_en, dev_addr=dev_addr, defaults=defaults)) def __setitem__(self, netname, val): iDev, iBit = self.net2port[netname] if val == 1: self.devs[iDev].bits_set(bits=[iBit]) elif val == 0: self.devs[iDev].bits_reset(bits=[iBit]) else: raise RuntimeError("invalid value:", val) class power_sw_fan: # Constructor. def __init__(self, spi_ip, ch_en, defaults=0xFF, dev_addr=0, cs_t=""): # MCP23S08. self.mcp = MCP23S08(dev_addr=dev_addr) # SPI. self.spi = spi_ip # CS. self.ch_en = ch_en self.cs_t = cs_t # All CS to high value. self.spi.SPI_SSR = 0xff # Set all bits as outputs. byte = self.mcp.reg_wr("IODIR_REG", 0x00) self.spi.send_receive_m(byte, self.ch_en, self.cs_t) # Set default output values. byte = self.mcp.reg_wr("GPIO_REG", defaults) self.spi.send_receive_m(byte, self.ch_en, self.cs_t) # Write bits. def bits_set(self, bits=[0]): val = 0 # Read actual value. byte = self.mcp.reg_rd("GPIO_REG") vals = self.spi.send_receive_m(byte, self.ch_en, self.cs_t) val = int(vals[2]) # Set bits. for i in range(len(bits)): val |= (1 << bits[i]) # Set value to hardware. byte = self.mcp.reg_wr("GPIO_REG", val) self.spi.send_receive_m(byte, self.ch_en, self.cs_t) def bits_reset(self, bits=[0]): val = 0xff # Read actual value. byte = self.mcp.reg_rd("GPIO_REG") vals = self.spi.send_receive_m(byte, self.ch_en, self.cs_t) val = int(vals[2]) # Reset bits. for i in range(len(bits)): val &= ~(1 << bits[i]) # Set value to hardware. byte = self.mcp.reg_wr("GPIO_REG", val) self.spi.send_receive_m(byte, self.ch_en, self.cs_t) # LO Synthesis. class lo_synth: # Constructor. def __init__(self, spi_ip, nch=2, le=[0], en_l="low", cs_t=""): # ADF4372. self.adf = ADF4372() # SPI. self.spi = spi_ip # CS. self.ch_en = self.spi.en_level(nch, le, en_l) self.cs_t = cs_t # All CS to high value. self.spi.SPI_SSR = 0xff # Write 0x00 to reg 0x73 self.reg_wr("LD_PD_ADC_REG", 0x00) # Write 0x3a to reg 0x72 self.reg_wr("AUXOUT_REG", 0x3A) # Write 0x60 to reg 0x71 self.reg_wr("BIAS_SEL_X4_REG", 0x60) # Write 0xe3 to reg 0x70 self.reg_wr("BIAS_SEL_X2_REG", 0xE3) # Write 0xf4 to reg 0x52 self.reg_wr("TRM_RESD1_REG", 0xF4) # Write 0xc0 to reg 0x47 self.reg_wr("TRM_RESD0_REG", 0xC0) # Write 0x28 to reg 0x41 self.reg_wr("CLK2_DIV_REG", 0x28) # Write 0x50 to reg 0x40 self.reg_wr("CLK1_DIV_HIGH_REG", 0x50) # Write 0x80 to reg 0x3f self.reg_wr("CLK1_DIV_LOW_REG", 0x80) # Write 0x0c to reg 0x3e self.reg_wr("CP_TMODE_REG", 0x0C) # Write 0x00 to reg 0x3d self.reg_wr("SD_RESET_REG", 0x00) # Write 0x55 to reg 0x3a self.reg_wr("ADC_OFFSET_REG", 0x55) # Write 0x07 to reg 0x39 self.reg_wr("SI_VTUNE_REG", 0x07) # Write 0x00 to reg 0x38 self.reg_wr("SI_VCO_REG", 0x00) # Write 0x00 to reg 0x37 self.reg_wr("SI_BAND_REG", 0x00) # Write 0x30 to reg 0x36 self.reg_wr("ICP_OFFSET_REG", 0x30) # Write 0xff to reg 0x35 self.reg_wr("ADC_CLK_REG", 0xFF) # Write 0x86 to reg 0x34 self.reg_wr("VCO_TIMEOUT_REG", 0x86) # Write 0x23 to reg 0x33 self.reg_wr("SYNTH_TIMEOUT_REG", 0x23) # Write 0x04 to reg 0x32 self.reg_wr("ADC_REG", 0x04) # Write 0x02 to reg 0x31 self.reg_wr("TIMEOUT_REG", 0x02) # Write 0x34 to reg 0x30 self.reg_wr("VCO_BAND_REG", 0x34) # Write 0x94 to reg 0x2f self.reg_wr("VCO_BIAS3_REG", 0x94) # Write 0x12 to reg 0x2e self.reg_wr("VCO_BIAS2_REG", 0x12) # Write 0x11 to reg 0x2d self.reg_wr("VCO_BIAS1_REG", 0x11) # Write 0x44 to reg 0x2c self.reg_wr("VCO_BIAS0_REG", 0x44) # Write 0x10 to reg 0x2b self.reg_wr("SD_REG", 0x10) # Write 0x00 to reg 0x2a self.reg_wr("CONFIG4_REG", 0x00) # Write 0x83 to reg 0x28 self.reg_wr("LOCK_REG", 0x83) # Write 0xcd to reg 0x27 self.reg_wr("BLEED1_REG", 0xcd) # Write 0x2f to reg 0x26 self.reg_wr("BLEED0_REG", 0x2F) # Write 0x07 to reg 0x25 self.reg_wr("RFOUT_REG", 0x07) # Write 0x80 to reg 0x24 self.reg_wr("RFDIV_REG", 0x80) # Write 0x00 to reg 0x23 self.reg_wr("CONFIG3_REG", 0x00) # Write 0x00 to reg 0x22 self.reg_wr("REF_REG", 0x00) # Write 0x14 to reg 0x20 self.reg_wr("MUXOUT_REG", 0x14) # Write 0x01 to reg 0x1f self.reg_wr("RCNT_REG", 0x01) # Write 0x58 to reg 0x1e self.reg_wr("CONFIG2_REG", 0x58) # Write 0x00 to reg 0x1d self.reg_wr("PHASE_HIGH_REG", 0x00) # Write 0x00 to reg 0x1c self.reg_wr("PHASE_MID_REG", 0x00) # Write 0x00 to reg 0x1b self.reg_wr("PHASE_LOW_REG", 0x00) # Write 0x00 to reg 0x1a self.reg_wr("MOD2_HIGH_REG", 0x00) # Write 0x03 to reg 0x19 self.reg_wr("MOD2_LOW_REG", 0x03) # Write 0x00 to reg 0x18 self.reg_wr("FRAC2_HIGH_REG", 0x00) # Write 0x01 to reg 0x17 (holds MSB of FRAC1 on bit[0]). self.reg_wr("FRAC2_LOW_REG", 0x01) # Write 0x61 to reg 0x16 self.reg_wr("FRAC1_HIGH_REG", 0x61) # Write 0x055 to reg 0x15 self.reg_wr("FRAC1_MID_REG", 0x55) # Write 0x55 to reg 0x14 self.reg_wr("FRAC1_LOW_REG", 0x55) # Write 0x40 to reg 0x12 self.reg_wr("CAL_PRE_REG", 0x40) # Write 0x00 to reg 0x11 self.reg_wr("INT_HIGH_REG", 0x00) # Write 0x28 to reg 0x10 self.reg_wr("INT_LOW_REG", 0x28) def reg_rd(self, reg="CONFIG0_REG"): # Byte array. byte = self.adf.reg_rd(reg) # Execute read. reg = self.spi.send_receive_m(byte, self.ch_en, self.cs_t) return reg def reg_wr(self, reg="CONFIG0_REG", val=0): # Byte array. byte = self.adf.reg_wr(reg, val) # Execute write. self.spi.send_receive_m(byte, self.ch_en, self.cs_t) def set_freq(self, fin=6000): # Get INT/FRAC register values. regs = self.adf.set_freq(fin) # Check if it was successful. if regs == -1: a = 1 else: # Write FRAC1 register. # MSB self.reg_wr('FRAC2_LOW_REG', regs['FRAC1']['MSB']) # HIGH. self.reg_wr('FRAC1_HIGH_REG', regs['FRAC1']['HIGH']) # MID. self.reg_wr('FRAC1_MID_REG', regs['FRAC1']['MID']) # LOW. self.reg_wr('FRAC1_LOW_REG', regs['FRAC1']['LOW']) # Write INT register. # HIGH. self.reg_wr('INT_HIGH_REG', regs['INT']['HIGH']) # LOW self.reg_wr('INT_LOW_REG', regs['INT']['LOW']) # Bias dac. class dac_bias: # Constructor. def __init__(self, spi_ip, ch_en, cs_t="", gpio_ip=None, version=1, fpga_board="ZCU216", debug=False): # SPI. self.ch_en = ch_en self.cs_t = cs_t self.spi = spi_ip self.spi.SPI_SSR = 0xff # Version. self.version = version # Board. self.fpga_board = fpga_board if debug: print("{}: DAC Channel = {}.".format(self.__class__.__name__, self.ch_en)) if self.fpga_board == 'ZCU111': # AD5791. self.ad = AD5781() # Initialize control register. self.write(reg="CTRL_REG", val=0x312) # Initialize to 0 volts. self.set_volt(0) elif self.fpga_board == 'ZCU216': if version == 1: # GPIO. self.gpio = gpio_ip.channel1 # DAC11001. self.ad = DAC11001() # Initialize control register. self.write(reg="CONFIG1_REG", val=0x4e00) # Initialize to 0 volts. self.set_volt(0) # Enable output switch. self.gpio.write(1,0x1) else: raise RuntimeError("%s: version %d not supported." % (self.__class__.__name, version)) else: raise RuntimeError("%s: board %s not recognized." % (self.__class__.__name__, fpga_board)) def read(self, reg="DAC_REG"): if self.fpga_board == 'ZCU216' and self.version == 1: # Read command. data = self.ad.reg_rd(reg) reg = self.spi.send_receive_m(data, self.ch_en, self.cs_t) # Another read with dummy data to allow clocking register out. data = bytes(4) reg = self.spi.send_receive_m(data, self.ch_en, self.cs_t) return int.from_bytes(reg[:3], byteorder='little') else: # Read command. byte = self.ad.reg_rd(reg) reg = self.spi.send_receive_m(byte, self.ch_en, self.cs_t) # Another read with dummy data to allow clocking register out. byte = bytes(3) reg = self.spi.send_receive_m(byte, self.ch_en, self.cs_t) return reg def write(self, reg="DAC_REG", val=0, debug=False): if self.fpga_board == 'ZCU216' and self.version == 1: # Write command. data = self.ad.reg_wr(reg, val) if debug: print("{}: writing register {} with values {}.".format(self.__class__.__name__, reg, data)) self.spi.send_receive_m(data, self.ch_en, self.cs_t) else: # Write command. data = self.ad.reg_wr(reg, val) if debug: print("{}: writing register {} with values {}.".format(self.__class__.__name__, reg, data)) self.spi.send_receive_m(data, self.ch_en, self.cs_t) def set_volt(self, volt=0, debug=False): # Convert volts to register value. val = self.ad.volt2reg(volt) if self.fpga_board == 'ZCU216' and self.version == 1: self.write(reg="DAC_DATA_REG", val=val, debug=debug) else: self.write(reg="DAC_REG", val=val, debug=debug) # Variable Gain Amp chip LMH6401. class LMH6401: # Commands. cmd_wr = 0x00 cmd_rd = 0x80 # Registers. REGS = {'REVID_REG': 0x00, 'PRODID_REG': 0x01, 'GAIN_REG': 0x02, 'TGAIN_REG': 0x04, 'TFREQ_REG': 0x05} # Register/address mapping. def reg2addr(self, reg="GAIN_REG"): if reg in self.REGS: return self.REGS[reg] else: print("%s: register %s not recognized." % (self.__class__.__name__, reg)) return -1 # Data array: 2 bytes. # byte[0] = rw/address. # byte[1] = data. def reg_rd(self, reg="GAIN_REG"): # Address. addr = self.reg2addr(reg) # Read command. cmd = self.cmd_rd | addr # Dummy byte for clocking data out. return bytes([cmd, 0]) def reg_wr(self, reg="GAIN_REG", val=0): # Address. addr = self.reg2addr(reg) # Read command. cmd = self.cmd_wr | addr return bytes([cmd, val]) # Variable step amp class: This class instantiates spi and LMH6401 to simplify access to amplifier. class gain: # Number of bits of gain setting. B = 6 # Minimum/maximum gain. Gmin = -6 Gmax = 26 # Constructor. def __init__(self, spi_ip, ch_en, cs_t=""): # LMH6401. self.lmh = LMH6401() # SPI. self.spi = spi_ip # Lath-enable. self.ch_en = ch_en self.cs_t = cs_t # Initalize to min gain. self.set_gain(-6) # Set gain. def set_gain(self, db): # Sanity check. if db < self.Gmin: print("%s: gain %f out of limits." % (self.__class__.__name__, db)) elif db > self.Gmax: print("%s: gain %f out of limits." % (self.__class__.__name__, db)) else: # Convert gain to attenuation (register value). db_a = int(np.round(self.Gmax - db)) # Write command. byte = self.lmh.reg_wr(reg="GAIN_REG", val=db_a) # Write value using spi. self.spi.send_receive_m(byte, self.ch_en, self.cs_t) # Class to describe the ADC-RF channel chain. class adc_rf_ch(): # Constructor. def __init__(self, ch=0, switches=None, attn_spi=None, filter_spi=None, version=2, fpga_board="ZCU216", rfboard_ch=0, rfboard_sel=None, debug=False): # Channel number. self.ch = ch # RF board version. self.version = version # FPGA Board. self.fpga_board = fpga_board # ZCU111 board. if self.fpga_board == 'ZCU111': # Power switches. self.switches = switches # Attenuator. self.attn = attenuator(attn_spi, ch, le=[0]) # Default to 30 dB attenuation. self.set_attn_db(30) # ZCU216 board. elif self.fpga_board == 'ZCU216': if version == 1: # Board selection. self.rfboard_ch = rfboard_ch self.brd_sel = rfboard_sel # Channels are numbered from 0-7. Daughter cards have 2 channels each, with nubers going from 0-1. self.local_ch = ch % 2 if debug: print("{}: ADC Channel = {}, Daughter Card = {}, Daughter Card DAC channel {}.".format(self.__class__.__name__, self.ch, self.rfboard_ch, self.local_ch)) # Attenuators. There is 1 per ADC Channel. self.attn = [] self.attn.append(attenuator(attn_spi, ch=self.local_ch, nch=1, le=[0])) if debug: print("{}: adding attenuator with address {}.".format(self.__class__.__name__, self.local_ch)) # Filters. There is 1 per ADC Channel. self.filter = prog_filter(filter_spi, ch=self.local_ch) if debug: print("{}: adding filter with address {}.".format(self.__class__.__name__, self.local_ch)) # Initialize filter. self.init_filter() else: raise RuntimeError("%s: version %d not supported." % (self.__class__.__name, version)) else: raise RuntimeError("%s: board %s not recognized." % (self.__class__.__name__, fpga_board)) # Set attenuator. def set_attn_db(self, db=0, debug=False): if self.fpga_board == 'ZCU216' and self.version == 1: # Enable this daughter card. self.brd_sel.enable(board_id = self.rfboard_ch, debug=debug) # Set attenuator. self.attn[0].set_att(db) # Disable all daughter cards. self.brd_sel.disable() else: self.attn.set_att(db) self.enable() def init_filter(self,debug=False): # Enable this daughter card. self.brd_sel.enable(board_id = self.rfboard_ch, debug=debug) # Program ADI_SPI_CONFIG_A register to 0x3C. self.filter.reg_wr(reg="ADI_SPI_CONFIG_A", value=0x3C, debug=debug) # Disable all daughter cards. self.brd_sel.disable() def set_filter(self, fc=0, bw=None, ftype="lowpass", debug=False): # Enable this daughter card. self.brd_sel.enable(board_id = self.rfboard_ch, debug=debug) # Set filter. self.filter.set_filter(fc=fc, bw=bw, ftype=ftype, debug=debug) # Disable all daughter cards. self.brd_sel.disable() def read_filter(self, reg="", debug=False): if debug: print("{}: reading register {}".format(self.__class__.__name__, reg)) # Enable this daughter card. self.brd_sel.enable(board_id = self.rfboard_ch, debug=debug) # Set filter. ret = self.filter.reg_rd(reg=reg, debug=debug) # Disable all daughter cards. self.brd_sel.disable() return ret def enable(self): # Turn on 5V power. self.switches["RF2IF5V_EN%d"%(self.ch)] = 1 def disable(self): # Turn off 5V power. self.switches["RF2IF5V_EN%d"%(self.ch)] = 0 # Class to describe the ADC-DC channel chain. class adc_dc_ch(): # Constructor. def __init__(self, ch, switches, gain_spi, version=2): # Channel number. self.ch = ch # RF board version. self.version = version # Power switches. self.switches = switches # Variable Gain Amplifier. if ch < 4 or ch > 7: print("%s: channel %d not valid for ADC-DC type" % (self.__class__.__name__, ch)) self.gain = gain(gain_spi, ch_en=ch) # Default to 0 dB gain. self.set_gain_db(0) # Set gain. def set_gain_db(self, db=0): self.gain.set_gain(db) if self.version==2: self.enable() def enable(self): if self.version!=2: raise RuntimeError("enable/disable not supported for version", self.version) # Power up. self.switches["RF2IF_PD%d"%(self.ch)] = 0 def disable(self): if self.version!=2: raise RuntimeError("enable/disable not supported for version", self.version) # Power down. self.switches["RF2IF_PD%d"%(self.ch)] = 1 # Class to describe the DAC channel chain. class dac_ch(): # Constructor. def __init__(self, ch=0, switches=None, attn_spi=None, filter_spi=None, version=2, fpga_board="ZCU216", rfboard_ch=0, rfboard_sel=None, debug=False): # Channel number. self.ch = ch # RF board version. self.version = version # FPGA Board. self.fpga_board = fpga_board # ZCU111 board. if self.fpga_board == 'ZCU111': # RF input and power switches. self.switches = switches # Attenuators. self.attn = [] self.attn.append(attenuator(attn_spi, ch, le=[1])) self.attn.append(attenuator(attn_spi, ch, le=[2])) # Initialize in off state. self.disable() # ZCU216 board. elif self.fpga_board == 'ZCU216': if version == 1: # Board selection. self.rfboard_ch = rfboard_ch self.brd_sel = rfboard_sel # Channels are numbered from 0-15. Daughter cards have 4 channels each, with nubers going from 0-3. self.local_ch = ch % 4 if debug: print("{}: DAC Channel = {}, Daughter Card = {}, Daughter Card DAC channel {}.".format(self.__class__.__name__, self.ch, self.rfboard_ch, self.local_ch)) # Attenuators. There are 2 per DAC Channel. self.attn = [] for i in range(2): addr = 2*self.local_ch+i self.attn.append(attenuator(attn_spi, ch=addr, nch=1, le=[0])) if debug: print("{}: adding attenuator with address {}.".format(self.__class__.__name__, addr)) # Filters. There is 1 per ADC Channel. self.filter = prog_filter(filter_spi, ch=self.local_ch) if debug: print("{}: adding filter with address {}.".format(self.__class__.__name__, self.local_ch)) # Initialize filter. self.init_filter() else: raise RuntimeError("%s: version %d not supported." % (self.__class__.__name, version)) else: raise RuntimeError("%s: board %s not recognized." % (self.__class__.__name__, fpga_board)) # Switch selection. def rfsw_sel(self, sel="RF"): if sel == "RF": # Set logic one. # Select RF output from switch. self.switches["CH%d_PE42020_CTL"%(self.ch)] = 1 # Turn on 5V power to RF chain. self.switches["IF2RF5V_EN%d"%(self.ch)] = 1 if self.version==2: # Power down DC amplifier. self.switches["IF2RF_PD%d"%(self.ch)] = 1 elif sel == "DC": # Select DC output from switch. self.switches["CH%d_PE42020_CTL"%(self.ch)] = 0 # Turn off 5V power to RF chain. self.switches["IF2RF5V_EN%d"%(self.ch)] = 0 if self.version==2: # Power up DC amplifier. self.switches["IF2RF_PD%d"%(self.ch)] = 0 elif sel == "OFF": # Select RF output from switch. self.switches["CH%d_PE42020_CTL"%(self.ch)] = 1 # Turn off 5V power to RF chain. self.switches["IF2RF5V_EN%d"%(self.ch)] = 0 if self.version==2: # Power down DC amplifier. self.switches["IF2RF_PD%d"%(self.ch)] = 1 else: print("%s: selection %s not recoginzed." % (self.__class__.__name__, sel)) # Set attenuator. def set_attn_db(self, attn=0, db=0, debug=False): # Enable daughter card. if self.fpga_board == 'ZCU216' and self.version == 1: # Board selection logic. self.brd_sel.enable(board_id = self.rfboard_ch, debug=debug) # Set attenuator. if attn < len(self.attn): self.attn[attn].set_att(db) else: print("%s: attenuator %d not in chain." % (self.__class__.__name__, attn)) # Disable daughter card. if self.fpga_board == 'ZCU216' and self.version == 1: # Board selection logic. self.brd_sel.disable() def init_filter(self,debug=False): # Enable this daughter card. self.brd_sel.enable(board_id = self.rfboard_ch, debug=debug) # Program ADI_SPI_CONFIG_A register to 0x3C. self.filter.reg_wr(reg="ADI_SPI_CONFIG_A", value=0x3C, debug=debug) # Disable all daughter cards. self.brd_sel.disable() def set_filter(self, fc=0, bw=None, ftype="lowpass", debug=False): # Enable this daughter card. self.brd_sel.enable(board_id = self.rfboard_ch, debug=debug) # Set filter. self.filter.set_filter(fc=fc, bw=bw, ftype=ftype, debug=debug) # Disable all daughter cards. self.brd_sel.disable() def read_filter(self, reg="", debug=False): if debug: print("{}: reading register {}".format(self.__class__.__name__, reg)) # Enable this daughter card. self.brd_sel.enable(board_id = self.rfboard_ch, debug=debug) # Set filter. ret = self.filter.reg_rd(reg=reg, debug=debug) # Disable all daughter cards. self.brd_sel.disable() return ret def set_rf(self, att1, att2): if self.fpga_board == 'ZCU216' and self.version == 1: # TODO: Check that this is a RF daughter card. self.set_attn_db(attn=0, db=att1) self.set_attn_db(attn=1, db=att2) else: self.rfsw_sel("RF") self.set_attn_db(attn=0, db=att1) self.set_attn_db(attn=1, db=att2) def set_dc(self): self.rfsw_sel("DC") def disable(self): self.rfsw_sel("OFF") self.set_attn_db(attn=0, db=31.75) self.set_attn_db(attn=1, db=31.75)
[docs]class BoardSelection: """ This class is used to enable one daughter card on the RF Board for the ZCU216, V1. """ def __init__(self, gpio_ip): self.gpio = gpio_ip.channel1 def enable(self, board_id = 0, debug=False): # There are 8 boards: 3 bits for selection, 1 bit for active/inactive. # Bits: # |-----|-------|-------|-------| # | B3 | B2 | B1 | B0 | # |-----|-------|-------|-------| # | E/D | SEL 2 | SEL 1 | SEL 0 | # |-----|-------|-------|-------| # # E/D: # * 0 : disable. # * 1 : enable (selected board). val_ = (1 << 3) + board_id self.gpio.write(val_, 0xf) if debug: print("{}: setting vaue = 0x{:01X}".format(self.__class__.__name__,val_)) def disable(self): # E/D bit to 0. self.gpio.write(0, 0xf)
[docs]class RFQickSoc(QickSoc): """ Overrides the __init__ method of QickSoc in order to add the drivers for the preproduction (V1) version of the RF board. Otherwise supports all the QickSoc functionality. """ def __init__(self, bitfile, clk_output=True, no_tproc=False, **kwargs): """ A bitfile must always be provided, since the default bitstream will not work with the RF board. By default, re-initialize the clocks every time. This ensures that the LO output to the RF board is enabled. """ super().__init__(bitfile=bitfile, clk_output=clk_output, no_tproc=no_tproc, **kwargs) # Add configuration dictionary. self['rfboard'] = {} self.rfb_config(no_tproc)
[docs] def rfb_config(self, no_tproc): """ Configure the SPI interfaces to the RF board. """ # SPI used for Attenuators. self.attn_spi.config(lsb="lsb") # SPI used for Power, Switch and Fan. self.psf_spi.config(lsb="msb") # SPI used for the LO. self.lo_spi.config(lsb="msb") # SPI used for DAC BIAS. self.dac_bias_spi.config(lsb="msb", cpha="invert") # GPIO outputs: # ADC/DAC power enable, DAC RF input switch. # Initialize everything with power off. self.switches = SwitchControl(self.psf_spi) # ADC power self.switches.add_MCP(ch_en=0, outputs=[("RF2IF5V_EN"+str(i), 0) for i in range(4)] + [None]*4) # DAC power self.switches.add_MCP(ch_en=1, outputs=[("IF2RF5V_EN"+str(i), 0) for i in range(8)]) # DAC RF/DC switch self.switches.add_MCP(ch_en=2, outputs=[("CH%d_PE42020_CTL"%(i), 1) for i in range(8)]) # LO Synthesizers. self.lo = [lo_synth(self.lo_spi, le=[i]) for i in range(2)] # DAC BIAS. self.dac_bias = [dac_bias(self.dac_bias_spi, ch_en=ii) for ii in range(8)] # ADC channels. self.adc_chains = [adc_rf_ch(ii, self.switches, self.attn_spi) for ii in range(4)] + [adc_dc_ch(ii, self.switches, self.psf_spi, version=1) for ii in range(4,8)] # DAC channels. self.dac_chains = [dac_ch(ii, self.switches, self.attn_spi, version=1) for ii in range(8)] if not no_tproc: # Link gens/readouts to the corresponding RF board channels. for gen in self.gens: tile, block = [int(a) for a in gen.dac] gen.rfb = self.dacs[4*tile + block] for avg_buf in self.avg_bufs: tile, block = [int(a) for a in avg_buf.readout.adc] avg_buf.rfb = self.adc_chains[2*tile + block]
[docs] def rfb_set_lo(self, f): """Set both of the RF-board local oscillators to the same frequency. Tile 0 DACs and all RF ADCs are connected to LO[0], tile 1 DACs are connected to LO[1]. Parameters ---------- f : float Frequency (4000-8000 MHz) """ for lo in self.lo: lo.set_freq(f)
[docs] def rfb_set_gen_rf(self, gen_ch, att1, att2): """Enable and configure an RF-board output channel for RF output. Parameters ---------- gen_ch : int DAC channel (index in 'gens' list) att1 : float Attenuation for first stage (0-31.75 dB) att2 : float Attenuation for second stage (0-31.75 dB) """ self.gens[gen_ch].rfb.set_rf(att1, att2)
[docs] def rfb_set_gen_dc(self, gen_ch): """Enable and configure an RF-board output channel for DC output. Parameters ---------- gen_ch : int DAC channel (index in 'gens' list) """ self.gens[gen_ch].rfb.set_dc()
[docs] def rfb_set_ro_rf(self, ro_ch, att): """Enable and configure an RF-board RF input channel. Will fail if this is not an RF input. Parameters ---------- ro_ch : int ADC channel (index in 'avg_bufs' list) att : float Attenuation (0 to 31.75 dB) """ self.avg_bufs[ro_ch].rfb.set_attn_db(att)
[docs] def rfb_set_ro_dc(self, ro_ch, gain): """Enable and configure an RF-board DC input channel. Will fail if this is not a DC input. Parameters ---------- ro_ch : int ADC channel (index in 'readouts' list) gain : float Gain (-6 to 26 dB) """ self.avg_bufs[ro_ch].rfb.set_gain_db(gain)
[docs] def rfb_set_bias(self, bias_ch, v): """Set a voltage on an RF-board bias DAC. Parameters ---------- bias_ch : int Channel number (0-7) v : float Voltage (-10 to 10 V) """ self.dac_bias[bias_ch].set_volt(v)
[docs] def rfb_set_gen_filter(self, gen_ch, fc, bw=1, ftype='bandpass'): """Set the programmable Analog Filter of the chain. Parameters ---------- gen_ch : int DAC channel (index in 'gens' list) fc : float Center frequency for bandpass, cut-off frequency of lowpass and highpass. bw : float Bandwidth. ftype : string. Filter type: bypass, lowpass, highpass or bandpass. """ self.gens[gen_ch].rfb.set_filter(fc = fc, bw = bw, ftype = ftype)
[docs] def rfb_set_ro_filter(self, ro_ch, fc, bw=1, ftype='bandpass'): """Enable and configure an RF-board RF input channel. Will fail if this is not an RF input. Parameters ---------- ro_ch : int ADC channel (index in 'avg_bufs' list) fc : float Center frequency for bandpass, cut-off frequency of lowpass and highpass. bw : float Bandwidth. ftype : string. Filter type: bypass, lowpass, highpass or bandpass. """ self.avg_bufs[ro_ch].rfb.set_filter(fc = fc, bw = bw, ftype = ftype)
class lo_synth_v2: def __init__(self, spi_ip, ch): # SPI. self.spi = spi_ip # CS. self.ch_en = self.spi.en_level(3, [ch], "low") self.cs_t = "" # All CS to high value. self.spi.SPI_SSR = 0xff self.lmx = clock_models.LMX2594(122.88) self.reset() @property def freq(self): return self.lmx.f_outa def reset(self): self.reg_wr(0x000002) self.reg_wr(0x000000) def reg_wr(self, regval): data = regval.to_bytes(length=3, byteorder='big') rec = self.spi.send_receive_m(data, self.ch_en, self.cs_t) def reg_rd(self, addr): data = bytes([addr + (1<<7), 0, 0]) return self.spi.send_receive_m(data, self.ch_en, self.cs_t) def read_and_parse(self, addr): regval = int.from_bytes(self.reg_rd(addr), byteorder="big") reg = clock_models.Register(self.lmx.registers_by_addr[addr].regdef) reg.parse(regval) return reg def is_locked(self): status = self.get_param("rb_LD_VTUNE") #print(status.value_description) return status.value == self.lmx.rb_LD_VTUNE.LOCKED.value def set_freq(self, f, pwr=50, osc_2x=False, reset=True, verbose=False): self.lmx.set_output_frequency(f, pwr=pwr, en_b=True, osc_2x=osc_2x, verbose=verbose) if reset: self.reset() self.program() time.sleep(0.01) self.calibrate(verbose=verbose) def calibrate(self, timeout=1.0, n_attempts=5, verbose=False): for i in range(n_attempts): # you'd think FCAL_EN needs to be toggled, not just set to 1? # but datasheet doesn't say so, and this seems to work self.set_param("FCAL_EN", 1) starttime = time.time() while time.time()-starttime < timeout: lock = self.is_locked() if lock: if verbose: print("LO locked on attempt %d after %.2f sec"%(i+1, time.time()-starttime)) return time.sleep(0.01) if verbose: print("lock attempt %d failed"%(i+1)) raise RuntimeError("LO failed to lock after %d attempts"%(n_attempts)) def set_param(self, name, val): param = getattr(self.lmx, name) param.value = val if isinstance(param, clock_models.Field): self.reg_wr(self.lmx.registers_by_addr[param.addr].get_raw()) else: # MultiRegister for field in param.fields: self.reg_wr(self.lmx.registers_by_addr[field.addr].get_raw()) def get_param(self, name): param = getattr(self.lmx, name) return self.read_and_parse(param.addr).fields[param.index] def program(self): for regval in self.lmx.get_register_dump(): self.reg_wr(regval)
[docs]class RFQickSocV2(RFQickSoc):
[docs] def rfb_config(self, no_tproc): """ Configure the SPI interfaces to the RF board. """ # SPI used for Attenuators. self.attn_spi.config(lsb="lsb") # SPI used for Power, Switch and Fan. self.psf_spi.config(lsb="msb") # SPI used for the LO. self.lo_spi.config(lsb="msb") # SPI used for DAC BIAS. self.dac_bias_spi.config(lsb="msb", cpha="invert") # GPIO outputs: # ADC/DAC power enable, DAC RF input switch. # Initialize everything with power off. self.switches = SwitchControl(self.psf_spi) # ADC power/power-down self.switches.add_MCP(ch_en=0, dev_addr=0, outputs=[("RF2IF5V_EN"+str(i), 0) for i in range(4)] + [("RF2IF_PD"+str(i), 1) for i in range(4, 8)]) # DAC power-down self.switches.add_MCP(ch_en=1, dev_addr=1, outputs=[("IF2RF_PD"+str(i), 1) for i in range(8)]) # DAC power self.switches.add_MCP(ch_en=1, dev_addr=0, outputs=[("IF2RF5V_EN"+str(i), 0) for i in range(8)]) # DAC RF/DC switch self.switches.add_MCP(ch_en=2, dev_addr=0, outputs=[("CH%d_PE42020_CTL"%(i), 1) for i in range(8)]) # LO Synthesizers. self.lo = [lo_synth_v2(self.lo_spi, i) for i in range(3)] # DAC BIAS. self.dac_bias = [dac_bias(self.dac_bias_spi, ch_en=ii, fpga_board=self['board']) for ii in range(8)] # ADC channels. self.adc_chains = [adc_rf_ch(ii, self.switches, self.attn_spi, fpga_board=self['board']) for ii in range(4)] + [adc_dc_ch(ii, self.switches, self.psf_spi) for ii in range(4,8)] # DAC channels. self.dac_chains = [dac_ch(ii, self.switches, self.attn_spi, fpga_board=self['board']) for ii in range(8)] # Link RF channels to LOs. for adc in self.adc_chains[:4]: adc.lo = self.lo[0] for dac in self.dac_chains[:4]: dac.lo = self.lo[1] for dac in self.dac_chains[4:]: dac.lo = self.lo[2] if not no_tproc: # Link gens/readouts to the corresponding RF board channels. for gen in self.gens: tile, block = [int(a) for a in gen.dac] gen.rfb = self.dac_chains[4*tile + block] for avg_buf in self.avg_bufs: tile, block = [int(a) for a in avg_buf.readout.adc] avg_buf.rfb = self.adc_chains[2*tile + block]
[docs] def rfb_set_lo(self, f, ch=None, verbose=False): """Set RF-board local oscillators. LO[0]: all RF ADCs LO[1]: RF DACs 0-3 LO[2]: RF DACs 4-7 Parameters ---------- f : float Frequency (4000-8000 MHz) ch : int LO to configure (None=all) verbose : bool Print freq and lock info. """ if ch is not None: self.lo[ch].set_freq(f, verbose=verbose) else: for lo in self.lo: lo.set_freq(f, verbose=verbose)
[docs] def rfb_get_lo(self, gen_ch=None, ro_ch=None): """Get local oscillator frequency for a DAC or ADC channel. Parameters ---------- gen_ch : int DAC channel (index in 'gens' list) ro_ch : int ADC channel (index in 'readouts' list) """ if gen_ch is not None and ro_ch is not None: raise RuntimeError("can't specify both gen_ch and ro_ch") if gen_ch is not None: return self.gens[gen_ch].rfb.lo.freq if ro_ch is not None: return self.avg_bufs[ro_ch].rfb.lo.freq raise RuntimeError("must specify gen_ch or ro_ch")
[docs]class RFQickSoc216V1(RFQickSoc):
[docs] def rfb_config(self, no_tproc): """ Configure the GPIO/SPI interfaces to the RF board. """ # GPIO for Board Selection. if 'brd_sel_gpio' in self.ip_dict.keys(): self.board_sel = BoardSelection(self.brd_sel_gpio) else: raise RuntimeError("%s: brd_sel_gpio for board selection logic not found." % self.__class__.__name__) # SPI used for Attenuators. if 'attn_spi' in self.ip_dict.keys(): self.attn_spi.config(lsb="lsb") else: raise RuntimeError("%s: attn_spi for attenuator control not found." % self.__class__.__name__) # SPI used for Filter. if 'filter_spi' in self.ip_dict.keys(): self.filter_spi.config(lsb="msb") else: raise RuntimeError("%s: filter_spi for filter control not found." % self.__class__.__name__) # SPI used for BIAS. if 'bias_spi' in self.ip_dict.keys(): self.bias_spi.config(lsb="msb", cpha="invert") else: raise RuntimeError("%s: bias_spi for bias DACs control not found." % self.__class__.__name__) if 'bias_gpio' in self.ip_dict.keys(): pass else: raise RuntimeError("%s: bias_gpio for bias DACs control not found." % self.__class__.__name__) # DAC BIAS. self.dac_bias = [dac_bias(self.bias_spi, ch_en=ii, gpio_ip=self.bias_gpio, version=1, fpga_board=self['board']) for ii in range(8)] # ADC channels. ADC's daughter cards are the upper 4. self.adc_chains = [] NRF = 4 # Number of ADC daughter cards. NCH = 2 # ADC channels per daughter card. for rf_board in range(NRF): for ch in range(NCH): self.adc_chains.append(adc_rf_ch(ch=NCH*rf_board+ch, attn_spi=self.attn_spi, filter_spi=self.filter_spi, version=1, fpga_board=self['board'], rfboard_ch=NRF+rf_board, rfboard_sel=self.board_sel)) # DAC channels. DAC's daughter cards are the lower 4. self.dac_chains = [] NRF = 4 # Number of DAC daughter cards. NCH = 4 # DAC channels per daughter card. for rf_board in range(NRF): for ch in range(NCH): self.dac_chains.append(dac_ch(ch=NCH*rf_board+ch, attn_spi=self.attn_spi, filter_spi=self.filter_spi, version=1, fpga_board=self['board'], rfboard_ch=rf_board, rfboard_sel=self.board_sel)) # Link gens/readouts to the corresponding RF board channels. if not no_tproc: for gen in self.gens: tile, block = [int(a) for a in gen.dac] gen.rfb = self.dac_chains[4*tile + block] for avg_buf in self.avg_bufs: tile, block = [int(a) for a in avg_buf.readout.adc] #TODO: is tile-1 correct? not tile-2? avg_buf.rfb = self.adc_chains[4*(tile-1) + block]