def __init__(self,hostname,dsoc_desc=None,boffile=None): """ Initialize an ArtooDaq object. Parameters ---------- hostname : string Address of the roach2 on the 1GbE (control) network. dsoc_desc : tuple A tuple with the first element the IP address / hostname and the second element the port where data is to be received. This argument, if not None, is passed directly to socket.bind(); see the documentation of that class for details. In this case a socket is opened and bound to the given address. If None, then the data socket is not opened. Default is None. boffile : string Program the device with this bitcode if not None. The special filename 'latest-build' uses the current build of the bit-code. Default is None. """ # connect to roach and store local copy of FpgaClient r2 = FpgaClient(hostname) if not r2.wait_connected(self._TIMEOUT): raise RuntimeError("Unable to connect to ROACH2 named '{0}'".format(hostname)) self._roach2 = r2 # program bitcode if not boffile is None: self._start(boffile) # initialize some data structures self._ddc_1st = dict() for did in self.DIGITAL_CHANNELS: self._ddc_1st[did] = None # if requested, open data socket if not dsoc_desc is None: self.open_dsoc(dsoc_desc)
def __init__(self, hostname, dsoc_desc=None, boffile=None): """ Initialize an ArtooDaq object. Parameters ---------- hostname : string Address of the roach2 on the 1GbE (control) network. dsoc_desc : tuple A tuple with the first element the IP address / hostname and the second element the port where data is to be received. This argument, if not None, is passed directly to socket.bind(); see the documentation of that class for details. In this case a socket is opened and bound to the given address. If None, then the data socket is not opened. Default is None. boffile : string Program the device with this bitcode if not None. The special filename 'latest-build' uses the current build of the bit-code. Default is None. """ # connect to roach and store local copy of FpgaClient r2 = FpgaClient(hostname) if not r2.wait_connected(self._TIMEOUT): raise RuntimeError( "Unable to connect to ROACH2 named '{0}'".format(hostname)) self._roach2 = r2 # program bitcode if not boffile is None: self._start(boffile) # if requested, open data socket if not dsoc_desc is None: self.open_dsoc(dsoc_desc)
def _connect(self, roach2_host): # Connect and wait until ready self.roach2 = FpgaClient(roach2_host) if roach2_host: if not self.roach2.wait_connected(timeout=5): raise RuntimeError('Timeout trying to connect to {0}.' 'Is it up and running the swarm sever?'.format(self.roach2.host))
def __init__(self,roach=None,wafer=0,roachip='roach',adc_valon=None): """ Class to represent the heterodyne readout system (high-frequency (1.5 GHz), IQ mixers) roach: an FpgaClient instance for communicating with the ROACH. If not specified, will try to instantiate one connected to *roachip* wafer: 0 Not used for heterodyne system roachip: (optional). Network address of the ROACH if you don't want to provide an FpgaClient adc_valon: a Valon class, a string, or None Provide access to the Valon class which controls the Valon synthesizer which provides the ADC and DAC sampling clock. The default None value will use the valon.find_valon function to locate a synthesizer and create a Valon class for you. You can alternatively pass a string such as '/dev/ttyUSB0' to specify the port for the synthesizer, which will then be used for creating a Valon class. Finally, for test suites, you can directly pass a Valon class or a class with the same interface. """ if roach: self.r = roach else: from corr.katcp_wrapper import FpgaClient self.r = FpgaClient(roachip) t1 = time.time() timeout = 10 while not self.r.is_connected(): if (time.time()-t1) > timeout: raise Exception("Connection timeout to roach") time.sleep(0.1) if adc_valon is None: import valon ports = valon.find_valons() if len(ports) == 0: raise Exception("No Valon found!") self.adc_valon_port = ports[0] self.adc_valon = valon.Synthesizer(ports[0]) #use latest port elif type(adc_valon) is str: import valon self.adc_valon_port = adc_valon self.adc_valon = valon.Synthesizer(self.adc_valon_port) else: self.adc_valon = adc_valon self.adc_atten = -1 self.dac_atten = -1 self.bof_pid = None self.roachip = roachip self.fs = self.adc_valon.get_frequency_a() self.wafer = wafer self.dac_ns = 2**16 # number of samples in the dac buffer self.raw_adc_ns = 2**12 # number of samples in the raw ADC buffer self.nfft = 2**14 self.boffile = 'iq2xpfb14mcr4_2013_Aug_02_1446.bof' self.bufname = 'ppout%d' % wafer
def __init__(self, server, include_baselines, bee2_host, bee2_port, lags=32, bof='bee2_calib_corr.bof'): """ Overloaded method which adds some arguments necessary for connecting to 'tcpborphserver' running on a BEE2.""" BasicCorrelationProvider.__init__(self, server, include_baselines, lags) self.bee2_host = bee2_host self.bee2_port = bee2_port self.bee2 = FpgaClient(bee2_host, port=bee2_port) self.bee2._connected.wait() self._program(bof) self.bee2.write_int('start', 1)
def __init__(self, proc_func, bufname, roachip='roach'): self.bufname = bufname self.data_thread = None self.proc_func = proc_func from corr.katcp_wrapper import FpgaClient self.data_thread_r = FpgaClient(roachip, timeout=0.1) t1 = time.time() timeout = 10 while not self.data_thread_r.is_connected(): if (time.time() - t1) > timeout: raise Exception("Connection timeout to roach") time.sleep(0.1) self.last_addr = 0
def __init__(self,roach=None,roachip='roach',adc_valon = None): """ Class to represent the heterodyne readout system (high frequency, 1.5 GHz, with IQ mixers) roach: an FpgaClient instance for communicating with the ROACH. If not specified, will try to instantiate one connected to *roachip* roachip: (optional). Network address of the ROACH if you don't want to provide an FpgaClient """ if roach: self.r = roach else: from corr.katcp_wrapper import FpgaClient self.r = FpgaClient(roachip) t1 = time.time() timeout = 10 while not self.r.is_connected(): if (time.time()-t1) > timeout: raise Exception("Connection timeout to roach") time.sleep(0.1) if adc_valon is None: import valon ports = valon.find_valons() if len(ports) == 0: raise Exception("No Valon found!") self.adc_valon_port = ports[0] self.adc_valon = valon.Synthesizer(ports[0]) #use latest port elif type(adc_valon) is str: import valon self.adc_valon_port = adc_valon self.adc_valon = valon.Synthesizer(self.adc_valon_port) else: self.adc_valon = adc_valon self.fs = self.adc_valon.get_frequency_a() self.dac_ns = 2**16 # number of samples in the dac buffer self.raw_adc_ns = 2**11 # number of samples in the raw ADC buffer self.nfft = 2**14 self.boffile = 'iqx2fft14dac14r1_2013_Jun_24_1921.bof'
class SinglePixelHeterodyne(SinglePixelReadout): def __init__(self,roach=None,roachip='roach',adc_valon = None): """ Class to represent the heterodyne readout system (high frequency, 1.5 GHz, with IQ mixers) roach: an FpgaClient instance for communicating with the ROACH. If not specified, will try to instantiate one connected to *roachip* roachip: (optional). Network address of the ROACH if you don't want to provide an FpgaClient """ if roach: self.r = roach else: from corr.katcp_wrapper import FpgaClient self.r = FpgaClient(roachip) t1 = time.time() timeout = 10 while not self.r.is_connected(): if (time.time()-t1) > timeout: raise Exception("Connection timeout to roach") time.sleep(0.1) if adc_valon is None: import valon ports = valon.find_valons() if len(ports) == 0: raise Exception("No Valon found!") self.adc_valon_port = ports[0] self.adc_valon = valon.Synthesizer(ports[0]) #use latest port elif type(adc_valon) is str: import valon self.adc_valon_port = adc_valon self.adc_valon = valon.Synthesizer(self.adc_valon_port) else: self.adc_valon = adc_valon self.fs = self.adc_valon.get_frequency_a() self.dac_ns = 2**16 # number of samples in the dac buffer self.raw_adc_ns = 2**11 # number of samples in the raw ADC buffer self.nfft = 2**14 self.boffile = 'iqx2fft14dac14r1_2013_Jun_24_1921.bof' def set_channel(self,ch,dphi=-0.25,amp=-3): """ ch: channel number (-dac_ns/2 to dac_ns/2-1) dphi: phase offset between I and Q components in turns (nominally -1/4 = pi/2 radians) amp: amplitude relative to full scale in dB nfft: size of the fft """ self.set_tone(ch/(1.0*self.dac_ns), dphi=dphi, amp=amp) absch = np.abs(ch) chan_per_bin = self.dac_ns/self.nfft ibin = absch // chan_per_bin if ch < 0: ibin = self.nfft-ibin self.select_bin(int(ibin)) def get_data(self,nread=10): """ Get a stream of data from a single FFT bin nread: number of 4096 sample frames to read returns dout,addrs dout: complex data stream. Real and imaginary parts are each 16 bit signed integers (but cast to numpy complex) addrs: counter values when each frame was read. Can be used to check that frames are contiguous """ bufname = 'ppout' return self._read_data(nread, bufname) def load_waveform(self,iwave,qwave): if len(iwave) != self.dac_ns or len(qwave) != self.dac_ns: raise Exception("Waveforms should be %d samples long" % self.dac_ns) iw2 = iwave.astype('>i2').tostring() qw2 = qwave.astype('>i2').tostring() self.r.blindwrite('iout',iw2) self.r.blindwrite('qout',qw2) self.r.write_int('dacctrl',0) self.r.write_int('dacctrl',1) def set_tone(self,f0,dphi=0.25,amp=-3): a = 10**(amp/20.0) if a > 0.9999: print "warning: clipping amplitude to 0.9999" a = 0.9999 swr = (2**15)*a*np.cos(2*np.pi*(f0*np.arange(self.dac_ns))) swi = (2**15)*a*np.cos(2*np.pi*(dphi+f0*np.arange(self.dac_ns))) self.load_waveform(swr,swi) def select_bin(self,ibin): """ Set the register which selects the FFT bin we get data from ibin: 0 to nfft -1 """ self.r.write_int('chansel',ibin) def _set_fs(self,fs,chan_spacing=2.0): """ Set sampling frequency in MHz Note, this should generally not be called without also reprogramming the ROACH Use initialize() instead """ self.adc_valon.set_frequency_a(fs,chan_spacing=chan_spacing) self.fs = fs
class SinglePixelBaseband(SinglePixelReadout): def __init__(self,roach=None,wafer=0,roachip='roach',adc_valon=None): """ Class to represent the baseband readout system (low-frequency (150 MHz), no mixers) roach: an FpgaClient instance for communicating with the ROACH. If not specified, will try to instantiate one connected to *roachip* wafer: 0 or 1. In baseband mode, each of the two DAC and ADC connections can be used independantly to readout a single wafer each. This parameter indicates which connection you want to use. roachip: (optional). Network address of the ROACH if you don't want to provide an FpgaClient adc_valon: a Valon class, a string, or None Provide access to the Valon class which controls the Valon synthesizer which provides the ADC and DAC sampling clock. The default None value will use the valon.find_valon function to locate a synthesizer and create a Valon class for you. You can alternatively pass a string such as '/dev/ttyUSB0' to specify the port for the synthesizer, which will then be used for creating a Valon class. Finally, for test suites, you can directly pass a Valon class or a class with the same interface. """ if roach: self.r = roach else: from corr.katcp_wrapper import FpgaClient self.r = FpgaClient(roachip) t1 = time.time() timeout = 10 while not self.r.is_connected(): if (time.time()-t1) > timeout: raise Exception("Connection timeout to roach") time.sleep(0.1) if adc_valon is None: import valon ports = valon.find_valons() if len(ports) == 0: raise Exception("No Valon found!") self.adc_valon_port = ports[0] self.adc_valon = valon.Synthesizer(ports[0]) #use latest port elif type(adc_valon) is str: import valon self.adc_valon_port = adc_valon self.adc_valon = valon.Synthesizer(self.adc_valon_port) else: self.adc_valon = adc_valon self.fs = self.adc_valon.get_frequency_a() self.wafer = wafer self.dac_ns = 2**16 # number of samples in the dac buffer self.raw_adc_ns = 2**12 # number of samples in the raw ADC buffer self.nfft = 2**14 # self.boffile = 'adcdac2xfft14r4_2013_Jun_13_1717.bof' self.boffile = 'adcdac2xfft14r5_2013_Jun_18_1542.bof' self.bufname = 'ppout%d' % wafer def set_channel(self,ch,dphi=None,amp=-3): """ ch: channel number (0 to dac_ns-1) dphi: phase offset between I and Q components in turns (nominally 1/4 = pi/2 radians) not used for Baseband readout amp: amplitude relative to full scale in dB nfft: size of the fft """ self.set_tone(ch/(1.0*self.dac_ns), dphi=dphi, amp=amp) absch = np.abs(ch) chan_per_bin = (self.dac_ns/self.nfft)/2 # divide by 2 because it's a real signal ibin = absch // chan_per_bin # if ch < 0: # ibin = nfft-ibin self.select_bin(int(ibin)) def get_data(self,nread=10): """ Get a stream of data from a single FFT bin nread: number of 4096 sample frames to read returns dout,addrs dout: complex data stream. Real and imaginary parts are each 16 bit signed integers (but cast to numpy complex) addrs: counter values when each frame was read. Can be used to check that frames are contiguous """ bufname = 'ppout%d' % self.wafer return self._read_data(nread, bufname) def load_waveform(self,wave): if len(wave) != self.dac_ns: raise Exception("Waveform should be %d samples long" % self.dac_ns) w2 = wave.astype('>i2').tostring() if self.wafer == 0: self.r.blindwrite('iout',w2) else: self.r.blindwrite('qout',w2) self.r.write_int('dacctrl',0) self.r.write_int('dacctrl',1) def set_tone(self,f0,dphi=None,amp=-3): if dphi: print "warning: got dphi parameter in set_tone; ignoring for baseband readout" a = 10**(amp/20.0) if a > 0.9999: print "warning: clipping amplitude to 0.9999" a = 0.9999 swr = (2**15)*a*np.cos(2*np.pi*(f0*np.arange(self.dac_ns))) self.load_waveform(swr) def select_bin(self,ibin): """ Set the register which selects the FFT bin we get data from ibin: 0 to nfft -1 """ offset = 2 # bins are shifted by 2 ibin = np.mod(ibin-offset,self.nfft) self.r.write_int('chansel',ibin) def _set_fs(self,fs,chan_spacing=2.0): """ Set sampling frequency in MHz Note, this should generally not be called without also reprogramming the ROACH Use initialize() instead """ self.adc_valon.set_frequency_a(fs,chan_spacing=chan_spacing) self.fs = fs
class KatcpCatcher(): def __init__(self, proc_func, bufname, roachip='roach'): self.bufname = bufname self.data_thread = None self.proc_func = proc_func from corr.katcp_wrapper import FpgaClient self.data_thread_r = FpgaClient(roachip, timeout=0.1) t1 = time.time() timeout = 10 while not self.data_thread_r.is_connected(): if (time.time() - t1) > timeout: raise Exception("Connection timeout to roach") time.sleep(0.1) self.last_addr = 0 def start_data_thread(self): if self.data_thread: self.quit_data_thread = True self.data_thread.join(1.0) self.data_thread = None self.quit_data_thread = False self.data_thread = threading.Thread(target=self._cont_read_data, args=()) # IMPORTANT - where cont_read_data comes in self.data_thread.daemon = True self.data_thread.start() def _proc_raw_data(self, data, addr): if addr - self.last_addr > 8192: print "skipped:", addr, self.last_addr, (addr - self.last_addr) self.last_addr = addr data = np.fromstring(data, dtype='>i2').astype('float32').view('complex64') self.pxx = (np.abs(np.fft.fft(data.reshape((-1, 1024)), axis=1)) ** 2).mean(0) def _cont_read_data(self): """ Low level data reading loop. Reads data continuously and passes it to self.proc_func """ regname = '%s_addr' % self.bufname brama = '%s_a' % self.bufname bramb = '%s_b' % self.bufname r = self.data_thread_r a = r.read_uint(regname) & 0x1000 addr = r.read_uint(regname) b = addr & 0x1000 while a == b: addr = r.read_uint(regname) b = addr & 0x1000 data = [] addrs = [] tic = time.time() idle = 0 while not self.quit_data_thread: a = b if a: bram = brama else: bram = bramb data = r.read(bram, 4 * 2 ** 12) self.proc_func(data, addr) # Where proc_func comes in # coord passes self.aggregator.proc_raw_data as the proc_func here. addr = r.read_uint(regname) b = addr & 0x1000 while (a == b) and not self.quit_data_thread: try: addr = r.read_uint(regname) b = addr & 0x1000 idle += 1 except Exception, e: print e time.sleep(0.1) else:
class RoachBaseband(RoachInterface): def __init__(self,roach=None,wafer=0,roachip='roach',adc_valon=None): """ Class to represent the baseband readout system (low-frequency (150 MHz), no mixers) roach: an FpgaClient instance for communicating with the ROACH. If not specified, will try to instantiate one connected to *roachip* wafer: 0 or 1. In baseband mode, each of the two DAC and ADC connections can be used independantly to readout a single wafer each. This parameter indicates which connection you want to use. roachip: (optional). Network address of the ROACH if you don't want to provide an FpgaClient adc_valon: a Valon class, a string, or None Provide access to the Valon class which controls the Valon synthesizer which provides the ADC and DAC sampling clock. The default None value will use the valon.find_valon function to locate a synthesizer and create a Valon class for you. You can alternatively pass a string such as '/dev/ttyUSB0' to specify the port for the synthesizer, which will then be used for creating a Valon class. Finally, for test suites, you can directly pass a Valon class or a class with the same interface. """ if roach: self.r = roach else: from corr.katcp_wrapper import FpgaClient self.r = FpgaClient(roachip) t1 = time.time() timeout = 10 while not self.r.is_connected(): if (time.time()-t1) > timeout: raise Exception("Connection timeout to roach") time.sleep(0.1) if adc_valon is None: import valon ports = valon.find_valons() if len(ports) == 0: raise Exception("No Valon found!") for port in ports: try: self.adc_valon_port = port self.adc_valon = valon.Synthesizer(port) f = self.adc_valon.get_frequency_a() break except: pass elif type(adc_valon) is str: import valon self.adc_valon_port = adc_valon self.adc_valon = valon.Synthesizer(self.adc_valon_port) else: self.adc_valon = adc_valon self.adc_atten = -1 self.dac_atten = -1 self.bof_pid = None self.roachip = roachip self.fs = self.adc_valon.get_frequency_a() self.wafer = wafer self.dac_ns = 2**16 # number of samples in the dac buffer self.raw_adc_ns = 2**12 # number of samples in the raw ADC buffer self.nfft = 2**14 self.boffile = 'bb2xpfb14mcr5_2013_Jul_31_1301.bof' self.bufname = 'ppout%d' % wafer def load_waveform(self,wave,fast=True): """ Load waveform wave : array of 16-bit (dtype='i2') integers with waveform fast : boolean decide what method for loading the dram """ data = np.zeros((2*wave.shape[0],),dtype='>i2') offset = self.wafer*2 data[offset::4] = wave[::2] data[offset+1::4] = wave[1::2] self.r.write_int('dram_mask', data.shape[0]/4 - 1) self._load_dram(data,fast=fast) def set_tone_freqs(self,freqs,nsamp,amps=None): """ Set the stimulus tones to generate freqs : array of frequencies in MHz For baseband system, these must be positive nsamp : int, must be power of 2 number of samples in the playback buffer. Frequency resolution will be fs/nsamp amps : optional array of floats, same length as freqs array specify the relative amplitude of each tone. Can set to zero to read out a portion of the spectrum with no stimulus tone. returns: actual_freqs : array of the actual frequencies after quantization based on nsamp """ bins = np.round((freqs/self.fs)*nsamp).astype('int') actual_freqs = self.fs*bins/float(nsamp) self.set_tone_bins(bins, nsamp,amps=amps) self.fft_bins = self.calc_fft_bins(bins, nsamp) if self.fft_bins.shape[0] > 8: readout_selection = range(8) else: readout_selection = range(self.fft_bins.shape[0]) self.select_fft_bins(readout_selection) return actual_freqs def set_tone_bins(self,bins,nsamp,amps=None): """ Set the stimulus tones by specific integer bins bins : array of bins at which tones should be placed For Heterodyne system, negative frequencies should be placed in cannonical FFT order nsamp : int, must be power of 2 number of samples in the playback buffer. Frequency resolution will be fs/nsamp amps : optional array of floats, same length as bins array specify the relative amplitude of each tone. Can set to zero to read out a portion of the spectrum with no stimulus tone. """ spec = np.zeros((nsamp/2+1,),dtype='complex') self.tone_bins = bins.copy() self.tone_nsamp = nsamp phases = np.random.random(len(bins))*2*np.pi self.phases = phases.copy() if amps is None: amps = 1.0 self.amps = amps spec[bins] = amps*np.exp(1j*phases) wave = np.fft.irfft(spec) self.wavenorm = np.abs(wave).max() qwave = np.round((wave/self.wavenorm)*(2**15-1024)).astype('>i2') self.qwave = qwave self.load_waveform(qwave) def calc_fft_bins(self,tone_bins,nsamp): """ Calculate the FFT bins in which the tones will fall tone_bins : array of integers the tone bins (0 to nsamp - 1) which contain tones nsamp : length of the playback bufffer returns : fft_bins, array of integers. """ tone_bins_per_fft_bin = nsamp/(2*self.nfft) # factor of 2 because real signal fft_bins = np.round(tone_bins/float(tone_bins_per_fft_bin)).astype('int') return fft_bins def fft_bin_to_index(self,bins): """ Convert FFT bins to FPGA indexes """ top_half = bins > self.nfft/2 idx = bins.copy() idx[top_half] = self.nfft - bins[top_half] + self.nfft/2 return idx def select_fft_bins(self,readout_selection): """ Select which subset of the available FFT bins to read out Initially we can only read out from a subset of the FFT bins, so this function selects which bins to read out right now This also takes care of writing the selection to the FPGA with the appropriate tweaks The readout selection is stored to self.readout_selection The FPGA readout indexes is stored in self.fpga_fft_readout_indexes The bins that we are reading out is stored in self.readout_fft_bins readout_selection : array of ints indexes into the self.fft_bins array to specify the bins to read out """ offset = 2 idxs = self.fft_bin_to_index(self.fft_bins[readout_selection]) order = idxs.argsort() idxs = idxs[order] self.readout_selection = np.array(readout_selection)[order] self.fpga_fft_readout_indexes = idxs self.readout_fft_bins = self.fft_bins[self.readout_selection] binsel = np.zeros((self.fpga_fft_readout_indexes.shape[0]+1,),dtype='>i4') binsel[:-1] = np.mod(self.fpga_fft_readout_indexes-offset,self.nfft) binsel[-1] = -1 self.r.write('chans',binsel.tostring()) def demodulate_data(self,data): """ Demodulate the data from the FFT bin This function assumes that self.select_fft_bins was called to set up the necessary class attributes data : array of complex data returns : demodulated data in an array of the same shape and dtype as *data* """ demod = np.zeros_like(data) t = np.arange(data.shape[0]) for n,ich in enumerate(self.readout_selection): phi0 = self.phases[ich] k = self.tone_bins[ich] m = self.fft_bins[ich] if m >= self.nfft/2: sign = 1.0 else: sign = -1.0 nfft = self.nfft ns = self.tone_nsamp foffs = (2*k*nfft - m*ns)/float(ns) demod[:,n] = np.exp(sign*1j*(2*np.pi*foffs*t + phi0)) * data[:,n] if m >= self.nfft/2: demod[:,n] = np.conjugate(demod[:,n]) return demod def get_data(self,nread=10,demod=True): """ Get a chunk of data nread: number of 4096 sample frames to read demod: should the data be demodulated before returning? Default, yes returns dout,addrs dout: complex data stream. Real and imaginary parts are each 16 bit signed integers (but cast to numpy complex) addrs: counter values when each frame was read. Can be used to check that frames are contiguous """ bufname = 'ppout%d' % self.wafer chan_offset = 1 draw,addr,ch = self._read_data(nread, bufname) if not np.all(ch == ch[0]): print "all channel registers not the same; this case not yet supported" return draw,addr,ch if not np.all(np.diff(addr)<8192): print "address skip!" nch = self.readout_selection.shape[0] dout = draw.reshape((-1,nch)) shift = np.flatnonzero(self.fpga_fft_readout_indexes==(ch[0]-chan_offset))[0] - (nch-1) print shift dout = np.roll(dout,shift,axis=1) if demod: dout = self.demodulate_data(dout) return dout,addr def _set_fs(self,fs,chan_spacing=2.0): """ Set sampling frequency in MHz Note, this should generally not be called without also reprogramming the ROACH Use initialize() instead """ self.adc_valon.set_frequency_a(fs,chan_spacing=chan_spacing) # for now the baseband readout uses both valon outputs, self.adc_valon.set_frequency_b(fs,chan_spacing=chan_spacing) # one for ADC, one for DAC self.fs = fs
class BEE2CorrelationProvider(BasicCorrelationProvider): """ Connects to an a running instance of 'tcpborphserver' attached to a single BEE2 corner chip, reads off correlation functions for the requested set of baselines, and sends them over UDP packets to registered subscribers. See 'backends.basic. BasicCorrelationProvider' for more detail.""" def __init__(self, server, include_baselines, bee2_host, bee2_port, lags=32, bof='bee2_calib_corr.bof'): """ Overloaded method which adds some arguments necessary for connecting to 'tcpborphserver' running on a BEE2.""" BasicCorrelationProvider.__init__(self, server, include_baselines, lags) self.bee2_host = bee2_host self.bee2_port = bee2_port self.bee2 = FpgaClient(bee2_host, port=bee2_port) self.bee2._connected.wait() self._program(bof) self.bee2.write_int('start', 1) def _program(self, bof): """ Update the list of available bitstreams and program the BEE2 corner chip with the requested image.""" self.logger.debug("_program('%s')" %bof) self.bofs = self.bee2.listbof() if bof in self.bofs: self.bee2.progdev(bof) self.logger.info("successfully programmed '%s'" %bof) else: err_msg = "'%s' not available! Check the BOF path." %bof self.logger.error(err_msg) raise BEE2BorphError(err_msg) def correlate(self): """ This overloads 'BasicCorrelationProvider.correlate' (which does nothing) and enables/resets correlations on the BEE2 corner chip as well as setting integration times, etc. It then reads the correlations and stores them to be broadcast to its list of subscribers.""" self.logger.debug('correlate()') integration_time = self.server._integration_time self.logger.info("correlating for %0.2f seconds" %integration_time) self.bee2.write_int('hb_cntto', integration_time+1) for baseline in self._include_baselines: raw = self.bee2.read('corr_out%d' %(int(baseline[1])-1), 128) self._correlations[baseline] = array(CORR_OUT.unpack(raw)) self.logger.info('baseline %s, mean %d' %(baseline, self._correlations[baseline].mean())) self.bee2.write_int('corr_record', 0) self.bee2.write_int('corr_en', 0) self.bee2.write_int('corr_rst', 1) self.bee2.write_int('corr_rst', 0) self.bee2.write_int('corr_en', 1) sleep(integration_time+1) self.bee2.write_int('corr_record', 1)
def __init__(self, roach=None, roachip='roach', adc_valon=None, host_ip=None, nfs_root='/srv/roach_boot/etch', lo_valon=None): """ Abstract class to represent readout system roach: an FpgaClient instance for communicating with the ROACH. If not specified, will try to instantiate one connected to *roachip* roachip: (optional). Network address of the ROACH if you don't want to provide an FpgaClient adc_valon: a Valon class, a string, or None Provide access to the Valon class which controls the Valon synthesizer which provides the ADC and DAC sampling clock. The default None value will use the valon.find_valon function to locate a synthesizer and create a Valon class for you. You can alternatively pass a string such as '/dev/ttyUSB0' to specify the port for the synthesizer, which will then be used for creating a Valon class. Finally, for test suites, you can directly pass a Valon class or a class with the same interface. host_ip: Override IP address to which the ROACH should send it's data. If left as None, the host_ip will be set appropriately based on the HOSTNAME. """ self.is_roach2 = False self._using_mock_roach = False if roach: self.r = roach # Check if we're using a fake ROACH for testing. If so, disable additional externalities # This logic could be made more general if desired (i.e. has attribute mock # or type name matches regex including 'mock' if type(roach) is MockRoach: self._using_mock_roach = True else: # pragma: no cover from corr.katcp_wrapper import FpgaClient logger.debug("Creating FpgaClient") self.r = FpgaClient(roachip) t1 = time.time() timeout = 10 logger.debug("Waiting for connection to ROACH") while not self.r.is_connected(): if (time.time() - t1) > timeout: raise Exception("Connection timeout to roach") time.sleep(0.1) logger.debug("ROACH is connected") if adc_valon is None: # pragma: no cover from kid_readout.roach import valon ports = valon.find_valons() if len(ports) == 0: self.adc_valon_port = None self.adc_valon = None logger.warn("Warning: No valon found! You will not be able to change or verify the sampling frequency") else: for port in ports: try: self.adc_valon_port = port self.adc_valon = valon.Synthesizer(port) f = self.adc_valon.get_frequency_a() break except: pass elif type(adc_valon) is str: # pragma: no cover from kid_readout.roach import valon self.adc_valon_port = adc_valon self.adc_valon = valon.Synthesizer(self.adc_valon_port) else: self.adc_valon = adc_valon if type(lo_valon) is str: # pragma: no cover from kid_readout.roach import valon self.lo_valon_port = lo_valon self.lo_valon = valon.Synthesizer(self.lo_valon_port) else: self.lo_valon = lo_valon if host_ip is None: # pragma: no cover hostname = socket.gethostname() if hostname == 'detectors': host_ip = '192.168.1.1' else: host_ip = '192.168.1.1' self.host_ip = host_ip self.roachip = roachip self.nfs_root = nfs_root self._config_file_name = CONFIG_FILE_NAME_TEMPLATE % self.roachip self.adc_atten = 31.5 self.dac_atten = -1 self.fft_gain = 0 self.fft_bins = None self.tone_nsamp = None self.tone_bins = None self.phases = None self.amps = None self.readout_selection = None self.modulation_output = 0 self.modulation_rate = 0 self.wavenorm = None self.phase0 = None self.loopback = None self.debug_register = None # Things to be configured by subclasses self.lo_frequency = 0.0 self.iq_delay = 0 self.heterodyne = False self.bof_pid = None self.boffile = None self.wafer = None self.raw_adc_ns = 2 ** 12 # number of samples in the raw ADC buffer self.nfft = None # Boffile specific register names self._fpga_output_buffer = None
class SwarmMember: def __init__(self, roach2_host): # Set all initial members self.logger = logging.getLogger('SwarmMember') self._inputs = [SwarmInput(),] * len(SWARM_MAPPING_INPUTS) self.roach2_host = roach2_host # Connect to our ROACH2 if self.roach2_host: self._connect(roach2_host) def __eq__(self, other): if other is not None: return self.roach2_host == other.roach2_host else: return not self.is_valid() def __ne__(self, other): return not self.__eq__(other) def is_valid(self): return self.roach2_host is not None def __repr__(self): repr_str = 'SwarmMember(roach2_host={host})[{inputs[0]!r}][{inputs[1]!r}]' return repr_str.format(host=self.roach2_host, inputs=self._inputs) def __str__(self): repr_str = '{host} [{inputs[0]!s}] [{inputs[1]!s}]' return repr_str.format(host=self.roach2_host, inputs=self._inputs) def __getitem__(self, input_n): return self._inputs[input_n] def get_input(self, input_n): return self._inputs[input_n] def set_input(self, input_n, input_inst): self._inputs[input_n] = input_inst def setup(self, fid, fids_expected, bitcode, itime_sec, listener, noise=randint(0, 15)): # Reset logger for current setup self.logger = logging.getLogger('SwarmMember[%d]' % fid) # Program the board self._program(bitcode) # Set noise to perfect correlation self.set_noise(0xffffffff, 0xffffffff) self.reset_digital_noise() # ...but actually use the ADCs self.set_source(2, 2) # Setup our scopes to capture raw data self.set_scope(3, 0, 6) # Calibrate the ADC MMCM phases self.calibrate_adc() # Setup the F-engine self._setup_fengine() # Setup flat complex gains self.set_flat_cgains(0, 2**12) self.set_flat_cgains(1, 2**12) # Setup the X-engine self._setup_xeng_tvg() self.set_itime(itime_sec) self.reset_xeng() # Initial setup of the switched corner-turn self._setup_corner_turn(fid, fids_expected) # Setup the 10 GbE visibility self._setup_visibs(listener) # Verify QDRs self.verify_qdr() def _connect(self, roach2_host): # Connect and wait until ready self.roach2 = FpgaClient(roach2_host) if roach2_host: self.roach2.wait_connected() def _program(self, bitcode): # Program with the bitcode self._bitcode = bitcode self.roach2.progdev(self._bitcode) def set_digital_seed(self, source_n, seed): # Set the seed for internal noise seed_bin = pack(SWARM_REG_FMT, seed) self.roach2.write(SWARM_SOURCE_SEED % source_n, seed_bin) def set_noise(self, seed_0, seed_1): # Setup our digital noise self.set_digital_seed(0, seed_0) self.set_digital_seed(1, seed_1) def reset_digital_noise(self, source_0=True, source_1=True): # Reset the given sources by twiddling the right bits mask = (source_1 << 31) + (source_0 << 30) val = self.roach2.read_uint(SWARM_SOURCE_CTRL) self.roach2.write(SWARM_SOURCE_CTRL, pack(SWARM_REG_FMT, val & ~mask)) self.roach2.write(SWARM_SOURCE_CTRL, pack(SWARM_REG_FMT, val | mask)) self.roach2.write(SWARM_SOURCE_CTRL, pack(SWARM_REG_FMT, val & ~mask)) def set_source(self, source_0, source_1): # Set our sources to the given values ctrl_bin = pack(SWARM_REG_FMT, (source_1<<3) + source_0) self.roach2.write(SWARM_SOURCE_CTRL, ctrl_bin) def set_scope(self, sync_out, scope_0, scope_1): # Set our scopes to the given values ctrl_bin = pack(SWARM_REG_FMT, (sync_out<<16) + (scope_1<<8) + scope_0) self.roach2.write(SWARM_SCOPE_CTRL, ctrl_bin) def calibrate_adc(self): # Set ADCs to test mode for inp in SWARM_MAPPING_INPUTS: set_test_mode(self.roach2, inp) # Send a sync sync_adc(self.roach2) # Do the calibration for inp in SWARM_MAPPING_INPUTS: opt, glitches = calibrate_mmcm_phase(self.roach2, inp, [SWARM_SCOPE_SNAP % inp,]) if opt: self.logger.info('ADC%d calibration found optimal phase: %d' % (inp, opt)) else: self.logger.error('ADC%d calibration failed!' % inp) # Unset test modes for inp in SWARM_MAPPING_INPUTS: unset_test_mode(self.roach2, inp) def _setup_fengine(self): # Set the shift schedule of the F-engine sched_bin = pack(SWARM_REG_FMT, SWARM_SHIFT_SCHEDULE) self.roach2.write(SWARM_FENGINE_CTRL, sched_bin) def set_flat_cgains(self, input_n, flat_value): # Set gains for input to a flat value gains = [flat_value,] * SWARM_CHANNELS gains_bin = pack('>%dH' % SWARM_CHANNELS, *gains) self.roach2.write(SWARM_CGAIN_GAIN % input_n, gains_bin) def reset_xeng(self): # Twiddle bit 29 mask = 1 << 29 # reset bit location val = self.roach2.read_uint(SWARM_XENG_CTRL) self.roach2.write(SWARM_XENG_CTRL, pack(SWARM_REG_FMT, val & ~mask)) self.roach2.write(SWARM_XENG_CTRL, pack(SWARM_REG_FMT, val | mask)) self.roach2.write(SWARM_XENG_CTRL, pack(SWARM_REG_FMT, val & ~mask)) def get_itime(self): # Get the integration time in spectra xeng_time = self.roach2.read_uint(SWARM_XENG_CTRL) & 0x1ffff cycles = xeng_time / (11 * (SWARM_EXT_HB_PER_WCYCLE/SWARM_WALSH_SKIP)) return cycles * SWARM_WALSH_PERIOD def set_itime(self, itime_sec): # Set the integration (11 spectra per step * steps per cycle) self._xeng_itime = 11 * (SWARM_EXT_HB_PER_WCYCLE/SWARM_WALSH_SKIP) * int(itime_sec/SWARM_WALSH_PERIOD) self.roach2.write(SWARM_XENG_CTRL, pack(SWARM_REG_FMT, self._xeng_itime)) def _reset_corner_turn(self): # Twiddle bits 31 and 30 mask = (1 << 31) + (1 << 30) val = self.roach2.read_uint(SWARM_NETWORK_CTRL) self.roach2.write(SWARM_NETWORK_CTRL, pack(SWARM_REG_FMT, val & ~mask)) self.roach2.write(SWARM_NETWORK_CTRL, pack(SWARM_REG_FMT, val | mask)) self.roach2.write(SWARM_NETWORK_CTRL, pack(SWARM_REG_FMT, val & ~mask)) def _setup_corner_turn(self, this_fid, fids_expected, ipbase=0xc0a88000, macbase=0x000f530cd500, bh_mac=0x000f530cd899): # Reset the cores self._reset_corner_turn() # Store our FID self.fid = this_fid self.fids_expected = fids_expected # Set static parameters self.roach2.write_int(SWARM_NETWORK_FIDS_EXPECTED, self.fids_expected) self.roach2.write_int(SWARM_NETWORK_IPBASE, ipbase) self.roach2.write_int(SWARM_NETWORK_FID, self.fid) # Initialize the ARP table arp = [bh_mac] * 256 # Fill the ARP table for fid in SWARM_ALL_FID: for core in SWARM_ALL_CORE: last_byte = (fid << 4) + 0b1100 + core arp[last_byte] = macbase + last_byte # Configure 10 GbE devices for core in SWARM_ALL_CORE: name = SWARM_NETWORK_CORE % core last_byte = (self.fid << 4) + 0b1100 + core self.roach2.config_10gbe_core(name, macbase + last_byte, ipbase + last_byte, 18008, arp) # Lastly enable the TX only (for now) self.roach2.write(SWARM_NETWORK_CTRL, pack(SWARM_REG_FMT, 0x20)) def reset_ddr3(self): # Twiddle bit 30 mask = 1 << 30 # reset bit location val = self.roach2.read_uint(SWARM_VISIBS_DELAY_CTRL) self.roach2.write(SWARM_VISIBS_DELAY_CTRL, pack(SWARM_REG_FMT, val & ~mask)) self.roach2.write(SWARM_VISIBS_DELAY_CTRL, pack(SWARM_REG_FMT, val | mask)) self.roach2.write(SWARM_VISIBS_DELAY_CTRL, pack(SWARM_REG_FMT, val & ~mask)) def xengine_tvg(self, enable=False): # Disable/enable using bit 31 mask = 1 << 31 # enable bit location val = self.roach2.read_uint(SWARM_XENG_CTRL) if enable: self.roach2.write(SWARM_XENG_CTRL, pack(SWARM_REG_FMT, val | mask)) else: self.roach2.write(SWARM_XENG_CTRL, pack(SWARM_REG_FMT, val & ~mask)) def _setup_xeng_tvg(self): # Give each input a different constant value const_inputs = [0x0102, 0x0304, 0x0506, 0x0708, 0x090a, 0x0b0c, 0x0d0e, 0x0f10] * (SWARM_VISIBS_CHANNELS/8) for i in SWARM_ALL_FID: self.roach2.write(SWARM_XENG_TVG % i, pack('>%dH' % SWARM_VISIBS_CHANNELS, *const_inputs)) def visibs_delay(self, enable=True, delay_test=False, chunk_delay=2**23): # Disable/enable Laura's DDR3 delay and test self.roach2.write_int(SWARM_VISIBS_DELAY_CTRL, (enable<<31) + (delay_test<<29) + chunk_delay) def qdr_ready(self, qdr_num=0): # get the QDR status status = self.roach2.read_uint(SWARM_QDR_CTRL % qdr_num, offset=1) phy_rdy = bool(status & 1) cal_fail = bool((status >> 8) & 1) #print 'fid %s qdr%d status %s' %(self.fid, qdr_num, stat) return phy_rdy and not cal_fail def reset_qdr(self, qdr_num=0): # set the QDR status self.roach2.blindwrite(SWARM_QDR_CTRL % qdr_num, pack(SWARM_REG_FMT, 0xffffffff)) self.roach2.blindwrite(SWARM_QDR_CTRL % qdr_num, pack(SWARM_REG_FMT, 0x0)) def verify_qdr(self): # check qdr ready, reset if not ready for qnum in SWARM_ALL_QDR: self.logger.debug('checking QDR%d' % qnum) rdy = self.qdr_ready(qnum) if not rdy: self.logger.warning('QDR%d not ready, resetting' % qnum) self.reset_qdr(qnum) def _setup_visibs(self, listener, delay_test=False): # Store (or override) our listener self._listener = listener # Reset the DDR3 self.reset_ddr3() # Enable DDR3 interleaver self.visibs_delay(enable=True) # Fill the visibs ARP table arp = [0xffffffffffff] * 256 arp[self._listener.ip & 0xff] = self._listener.mac # Configure the transmit interface final_hex = (self.fid + 4) * 2 src_ip = (192<<24) + (168<<16) + (10<<8) + final_hex + 50 src_mac = (2<<40) + (2<<32) + final_hex + src_ip self.roach2.config_10gbe_core(SWARM_VISIBS_CORE, src_mac, src_ip, 4000, arp) # Configure the visibility packet buffer self.roach2.write(SWARM_VISIBS_SENDTO_IP, pack(SWARM_REG_FMT, self._listener.ip)) self.roach2.write(SWARM_VISIBS_SENDTO_PORT, pack(SWARM_REG_FMT, self._listener.port)) # Reset (and disable) visibility transmission self.roach2.write(SWARM_VISIBS_TENGBE_CTRL, pack(SWARM_REG_FMT, 1<<30)) self.roach2.write(SWARM_VISIBS_TENGBE_CTRL, pack(SWARM_REG_FMT, 0)) # Finally enable transmission self.roach2.write(SWARM_VISIBS_TENGBE_CTRL, pack(SWARM_REG_FMT, 1<<31)) def get_visibs_ip(self): # Update/store the visibs core net info self.visibs_netinfo = self.roach2.get_10gbe_core_details(SWARM_VISIBS_CORE) # Return the visibs core IP return inet_ntoa(pack(SWARM_REG_FMT, self.visibs_netinfo['my_ip'])) def sync_sowf(self): # Twiddle bit 31 mask = 1 << 31 # reset bit location val = self.roach2.read_uint(SWARM_SYNC_CTRL) self.roach2.write(SWARM_SYNC_CTRL, pack(SWARM_REG_FMT, val & ~mask)) self.roach2.write(SWARM_SYNC_CTRL, pack(SWARM_REG_FMT, val | mask)) self.roach2.write(SWARM_SYNC_CTRL, pack(SWARM_REG_FMT, val & ~mask)) def sync_1pps(self): # Twiddle bit 30 mask = 1 << 30 # reset bit location val = self.roach2.read_uint(SWARM_SYNC_CTRL) self.roach2.write(SWARM_SYNC_CTRL, pack(SWARM_REG_FMT, val & ~mask)) self.roach2.write(SWARM_SYNC_CTRL, pack(SWARM_REG_FMT, val | mask)) self.roach2.write(SWARM_SYNC_CTRL, pack(SWARM_REG_FMT, val & ~mask)) def sync_mcnt(self): # Twiddle bit 29 mask = 1 << 29 # reset bit location val = self.roach2.read_uint(SWARM_SYNC_CTRL) self.roach2.write(SWARM_SYNC_CTRL, pack(SWARM_REG_FMT, val & ~mask)) self.roach2.write(SWARM_SYNC_CTRL, pack(SWARM_REG_FMT, val | mask)) self.roach2.write(SWARM_SYNC_CTRL, pack(SWARM_REG_FMT, val & ~mask)) def enable_network(self): # Enable the RX and TX self.roach2.write(SWARM_NETWORK_CTRL, pack(SWARM_REG_FMT, 0x30)) def fringe_stop(self, enable): # Stop fringe stopping message = Message.request(SWARM_FSTOP_STOP_CMD) reply, informs = self.roach2.blocking_request(message, timeout=60) if not reply.reply_ok(): self.logger.error("Stopping fringe stopping failed!") # Start it again (if requested) if enable: message = Message.request(SWARM_FSTOP_START_CMD) reply, informs = self.roach2.blocking_request(message, timeout=60) if not reply.reply_ok(): self.logger.error("Starting fringe stopping failed!") def dewalsh(self, enable_0, enable_1): # Set the Walsh control register self.roach2.write(SWARM_WALSH_CTRL, pack(SWARM_REG_FMT, (enable_1<<30) + (enable_0<<28) + 0xfffff)) def set_walsh_pattern(self, input_n, pattern, offset=0, swap90=True): # Get the current Walsh table walsh_table_bin = self.roach2.read(SWARM_WALSH_TABLE_BRAM, SWARM_WALSH_TABLE_LEN*4) walsh_table = list(unpack('>%dI' % SWARM_WALSH_TABLE_LEN, walsh_table_bin)) # Find out many repeats we need pattern_size = len(pattern) / SWARM_WALSH_SKIP repeats = SWARM_WALSH_TABLE_LEN / pattern_size # Repeat the pattern as needed for rep in range(repeats): # Go through each step (with skips) for step in range(pattern_size): # Get the requested Walsh phase index = ((step + offset) * SWARM_WALSH_SKIP) % len(pattern) phase = int(pattern[index]) # Swap 90 if requested if swap90: if phase == 1: phase = 3 elif phase == 3: phase = 1 # Get the current value in table current = walsh_table[rep*pattern_size + step] # Mask in our phase shift_by = input_n * 4 mask = 0xf << shift_by new = (current & ~mask) | (phase << shift_by) walsh_table[rep*pattern_size + step] = new # Finally write the updated table back walsh_table_bin = pack('>%dI' % SWARM_WALSH_TABLE_LEN, *walsh_table) self.roach2.write(SWARM_WALSH_TABLE_BRAM, walsh_table_bin) def set_sideband_states(self, sb_states): # Write the states to the right BRAM sb_states_bin = pack('>%dB' % (len(sb_states)), *sb_states) self.roach2.write(SWARM_SB_STATE_BRAM, sb_states_bin) def get_delay(self, input_n): # Get the delay value in ns message = Message.request(SWARM_DELAY_GET_CMD, str(input_n)) reply, informs = self.roach2.blocking_request(message, timeout=60) if not reply.reply_ok(): self.logger.error("Getting the delay failed!") else: return float(reply.arguments[1]) def set_delay(self, input_n, value): # Set the delay value in ns message = Message.request(SWARM_DELAY_SET_CMD, str(input_n), str(value)) reply, informs = self.roach2.blocking_request(message, timeout=60) if not reply.reply_ok(): self.logger.error("Setting the delay failed!")
def _connect(self, roach2_host): # Connect and wait until ready self.roach2 = FpgaClient(roach2_host) if roach2_host: self.roach2.wait_connected()
import argparse from corr.katcp_wrapper import FpgaClient as ROACH if __name__ == '__main__': # Grab options from the command line parser = argparse.ArgumentParser() adcget = parser.add_mutually_exclusive_group() parser.add_argument('-i', '--ip-roach', dest='roach', required=True, help='Hostname/ip address of the ROACH.') adcget.add_argument('-z', '--zdok1', action='store_true', help='Use the zdok 1 model instead of the zdok 0.') adcget.add_argument('-k', '--kill', action='store_true', help='Kill the BOF process on the FPGA.') args = parser.parse_args() # Connect to the ROACH board roach = ROACH(args.roach) if not roach.wait_connected(10): print 'ERROR: Cannot connect to ROACH.' sys.exit(1) # Set up the FPGA if args.kill: print 'Killing BOF process.' roach.progdev('') else: print 'Initializing the ADC.' boffile = 'iadc_demux4_zdok%d.bof' % int(args.zdok1) roach.progdev(boffile)
def __init__(self, roach=None, roachip='roach', adc_valon=None, host_ip=None, nfs_root='/srv/roach_boot/etch', lo_valon=None): """ Abstract class to represent readout system roach: an FpgaClient instance for communicating with the ROACH. If not specified, will try to instantiate one connected to *roachip* roachip: (optional). Network address of the ROACH if you don't want to provide an FpgaClient adc_valon: a Valon class, a string, or None Provide access to the Valon class which controls the Valon synthesizer which provides the ADC and DAC sampling clock. The default None value will use the valon.find_valon function to locate a synthesizer and create a Valon class for you. You can alternatively pass a string such as '/dev/ttyUSB0' to specify the port for the synthesizer, which will then be used for creating a Valon class. Finally, for test suites, you can directly pass a Valon class or a class with the same interface. host_ip: Override IP address to which the ROACH should send it's data. If left as None, the host_ip will be set appropriately based on the HOSTNAME. """ self.is_roach2 = False self._using_mock_roach = False if roach: self.r = roach # Check if we're using a fake ROACH for testing. If so, disable additional externalities # This logic could be made more general if desired (i.e. has attribute mock # or type name matches regex including 'mock' if type(roach) is MockRoach: self._using_mock_roach = True else: # pragma: no cover from corr.katcp_wrapper import FpgaClient logger.debug("Creating FpgaClient") self.r = FpgaClient(roachip) t1 = time.time() timeout = 10 logger.debug("Waiting for connection to ROACH") while not self.r.is_connected(): if (time.time() - t1) > timeout: raise Exception("Connection timeout to roach") time.sleep(0.1) logger.debug("ROACH is connected") if adc_valon is None: # pragma: no cover from kid_readout.roach import valon ports = valon.find_valons() if len(ports) == 0: self.adc_valon_port = None self.adc_valon = None logger.warn( "Warning: No valon found! You will not be able to change or verify the sampling frequency" ) else: for port in ports: try: self.adc_valon_port = port self.adc_valon = valon.Synthesizer(port) f = self.adc_valon.get_frequency_a() break except: pass elif type(adc_valon) is str: # pragma: no cover from kid_readout.roach import valon self.adc_valon_port = adc_valon self.adc_valon = valon.Synthesizer(self.adc_valon_port) else: self.adc_valon = adc_valon if type(lo_valon) is str: # pragma: no cover from kid_readout.roach import valon self.lo_valon_port = lo_valon self.lo_valon = valon.Synthesizer(self.lo_valon_port) else: self.lo_valon = lo_valon if host_ip is None: # pragma: no cover hostname = socket.gethostname() if hostname == 'detectors': host_ip = '192.168.1.1' else: host_ip = '192.168.1.1' self.host_ip = host_ip self.roachip = roachip self.nfs_root = nfs_root self._config_file_name = CONFIG_FILE_NAME_TEMPLATE % self.roachip self.adc_atten = 31.5 self.dac_atten = -1 self.fft_gain = 0 self.fft_bins = None self.tone_nsamp = None self.tone_bins = None self.phases = None self.amps = None self.readout_selection = None self.modulation_output = 0 self.modulation_rate = 0 self.wavenorm = None self.phase0 = None self.loopback = None self.debug_register = None # Things to be configured by subclasses self.lo_frequency = 0.0 self.iq_delay = 0 self.heterodyne = False self.bof_pid = None self.boffile = None self.wafer = None self.raw_adc_ns = 2**12 # number of samples in the raw ADC buffer self.nfft = None # Boffile specific register names self._fpga_output_buffer = None
class SwarmROACH(object): def __init__(self, roach2_host, parent_logger=module_logger): # Set all initial members self.roach2_host = roach2_host self.logger = parent_logger.getChild( '{name}[host={host!r}]'.format( name=self.__class__.__name__, host=self.roach2_host, ) ) # Connect to our ROACH2 if self.roach2_host: self._connect(roach2_host) def __eq__(self, other): if other is not None: return self.roach2_host == other.roach2_host else: return not self.is_valid() def __ne__(self, other): return not self.__eq__(other) def is_valid(self): return self.roach2_host is not None def _connect(self, roach2_host): # Connect and wait until ready self.roach2 = FpgaClient(roach2_host) if roach2_host: if not self.roach2.wait_connected(timeout=5): raise RuntimeError('Timeout trying to connect to {0}.' 'Is it up and running the swarm sever?'.format(self.roach2.host)) def _program(self, bitcode): # Program with the bitcode self._bitcode = bitcode self.roach2.progdev(self._bitcode) def idle(self, bitcode=SWARM_IDLE_BITCODE): # Unload plugins and program with idle code self.unload_plugins() self.roach2.progdev(bitcode) self.logger.info('Idled with {0}'.format(bitcode)) def send_katcp_cmd(self, cmd, *args): # Create the message object message = Message.request(cmd, *args) # Send the request, and block for 60 seconds reply, informs = self.roach2.blocking_request(message, timeout=60) # Check for error, and raise one if present if not reply.reply_ok(): raise RuntimeError(reply) # Otherwise return what we got return reply, informs def plugin_list(self): # Send plugin-list command reply, informs = self.send_katcp_cmd('plugin-list') # Return the list of loaded plugin names return list(inform.arguments[0] for inform in informs) def unload_plugins(self): # Unload all currently loaded plugins for plugin in reversed(self.plugin_list()): self.send_katcp_cmd('plugin-unload', plugin) def reload_plugins(self, plugins_config=SWARM_PLUGINS_CONFIG): # Unload all currently loaded plugins self.unload_plugins() # Read the default plugins file cfg = ConfigParser({'init': ''}) cfg.read(plugins_config) # Get the names of all default plugins default_plugins = cfg.sections() # Cycle through defaults for plugin in default_plugins: # First, load the plugin path = cfg.get(plugin, 'file') self.send_katcp_cmd('plugin-load', path) # Then, run user init commands (if requested) for cmdstr in cfg.get(plugin, 'init').splitlines(): cmd, sep, args = cmdstr.partition(' ') # If failure: catch, log, and proceed try: self.send_katcp_cmd(cmd, *args.split()) except RuntimeError as err: self.logger.error("Plugin init failure: {0}".format(err))