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 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 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!")