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