Example #1
0
	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)
Example #2
0
    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)
Example #3
0
    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
Example #5
0
 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)
Example #6
0
 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
Example #7
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'
Example #8
0
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
Example #9
0
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
Example #10
0
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
Example #12
0
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)
Example #13
0
    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
Example #14
0
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!")
Example #15
0
    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)
Example #17
0
    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
Example #18
0
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))