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