예제 #1
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
예제 #2
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
예제 #3
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!")