def find_mmcms(roach_name): """ For the given roach, determine all the MMCM optimal phase values for all the different combinations of bof file and frequency. """ # connect to roach tmsg = 'Connecting to %s'%roach_name logger.info(tmsg) roach = corr.katcp_wrapper.FpgaClient(roach_name) time.sleep(1) if not roach.is_connected(): raise Exception("Cannot connect to %s" % roach_name) # we'll need this to change the frequency valonSerial = "/dev/ttyS1" # this should never change v = ValonKATCP(roach, valonSerial) # this is the object that will find the MMCM value cal = ADCCalibrate(dir = '.' #opts.dir , roach_name = roach_name #, gpib_addr = gpibaddr , roach = roach) #, test = True) # read the config file and find the mmcm through each mode fn = get_config_filename(roach_name) cp = ADCConfFile(fn) for key, value in cp.mmcm_info.items(): bof, frq = key i, adc0, adc1, _ = value # determine the MMCM optimal phase values for this combo # of bof file and frequency adc0s = [] adc1s = [] trails = 1 for trial in range(trails): adc0, adc1 = find_this_mmcm(v, cal, roach, bof, frq) tmsg = "Found ADC mmcm's for trial %d: %s, %s" % (trial, adc0, adc1) print tmsg logger.info(tmsg) if has_adc_difference(adc0, adc1, adc0s, adc1s): adc0s.append(adc0) adc1s.append(adc1) cp.write_mmcms(bof, frq, 0, adc0s) cp.write_mmcms(bof, frq, 1, adc1s) cp.write_to_file()
class ADCCalibrations: def __init__(self, test=False, conf_dir=None, data_dir=None, roaches=None, mmcm_trials=None, ogp_trials=None, test_tone=None, ampl=None, now=None, manual=False, do_ogps=True, do_mmcms=True, gpib_addr=None): self.test = test self.now = now self.conf_dir = conf_dir if conf_dir is not None else '.' self.data_dir = data_dir if data_dir is not None else '.' self.roaches = roaches if roaches is not None else self.get_roach_names_from_config( ) self.banks = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'] self.manual = manual self.do_mmcms = do_mmcms self.do_ogps = do_ogps if not self.do_ogps and not self.do_mmcms: raise Exception("One of do_ogps and do_mmcms must be True") # mmcms self.mmcm_trials = mmcm_trials if mmcm_trials is not None else 5 self.mmcm_tolerance = 4 # ogps/inls self.ogp_bof = 'h1k_ver106_2014_Apr_11_1612.bof' self.testfreq = test_tone if test_tone is not None else 18.3105 # MHz self.ampl = ampl if ampl is not None else -3 self.ogp_trials = ogp_trials if ogp_trials is not None else 10 self.gpibaddr = gpib_addr if gpib_addr is not None else '10.16.96.174' # tape room # helper classes self.cp = ConfigParser.ConfigParser() self.roach = None self.valon = None self.cal = None self.adcConf = None def find_all_calibrations(self): if self.do_mmcms: self.find_all_mmcms() if self.do_ogps: self.find_all_ogps() def find_all_ogps(self): for r in self.roaches: self.find_ogps(r) def find_all_mmcms(self): for r in self.roaches: self.find_mmcms(r) def get_roach_names_from_config(self): "Determines what roaches to connect to from the vegas.conf file" fn = "%s/%s" % (self.conf_dir, "vegas.conf") r = cp.read(fn) if len(r) == 0: print "Could not find roach names from: ", fn return [] # what banks to read? sec = "DEFAULTS" subsys = self.cp.get(sec, "subsystems") subsys = [int(s) for s in subsys.split(',')] # what roaches corresopond to those banks? roaches = [] for s in subsys: bank = self.banks[s - 1] sec = "BANK%s" % bank # roach_host = vegasr2-1.gb.nrao.edu roaches.append(cp.get(sec, "roach_host").split('.')[0]) return roaches def get_adc_config_filename(self, roach_name): fn = "%s/%s-adc.conf" % (self.conf_dir, roach_name) logger.info("MMCM config file: %s" % fn) return fn def init_for_roach(self, roach_name): "Inits the helper classes we use to interact w/ the given roach name" # connect to roach tmsg = 'Connecting to %s' % roach_name logger.info(tmsg) if not self.test: self.roach = corr.katcp_wrapper.FpgaClient(roach_name) time.sleep(1) if not self.roach.is_connected(): raise Exception("Cannot connect to %s" % roach_name) # we'll need this to change the frequency valonSerial = "/dev/ttyS1" # this should never change if not self.test: self.valon = ValonKATCP(self.roach, valonSerial) # this is the object that will find the MMCM value self.cal = ADCCalibrate(dir=self.data_dir, roach_name=roach_name, gpib_addr=self.gpibaddr, roach=self.roach, now=self.now, test=self.test) # read the config file and find the mmcm through each mode fn = self.get_adc_config_filename(roach_name) self.adcConf = ADCConfFile(fn) def find_ogps(self, roach_name): self.init_for_roach(roach_name) for frq, value in self.adcConf.ogp_info.items(): #i, ogp0, ogp1 = value # determine the OGP values for this clockrate tmsg = "Finding OGPs for clockrate: %s" % frq logger.info(tmsg) ogp0, ogp1 = self.find_this_ogp(frq) if ogp0 is not None and ogp1 is not None: self.adcConf.write_ogps(frq, 0, ogp0) self.adcConf.write_ogps(frq, 1, ogp1) self.adcConf.write_to_file() # the INLs don't change w/ either bof, or clockrate, # so now that this roach is in a suitable state (it's # MMCM and OGP calibrations are done), let's do one INL for z in range(2): self.cal.do_inl(z) self.adcConf.write_inls(z, self.cal.inl.inls) self.adcConf.write_to_file() def change_bof(self, bof): if self.test: return # switch to the given bof file self.roach.progdev(bof) tmsg = "Roach BOF file set to: %s" % bof logger.info(tmsg) time.sleep(2) def change_frequency(self, freq): "If necessary, use the Valon Synth to change the Roach board clockrate." if self.test: return # we also need to switch to the given frequency valonSynth = 0 # neither should this (0: A, 8: B) current_clkrate = self.valon.get_frequency(valonSynth) tmsg = "Valon Synth set to frequency: %f MHz" % current_clkrate clkrate = freq / 1e6 # Hz -> MHz logger.info(tmsg) if abs(current_clkrate - clkrate) > 0.001: self.valon.set_frequency(valonSynth, clkrate) time.sleep(1) current_clkrate = self.valon.get_frequency(valonSynth) tmsg = "Valon Synth changed to frequency: %f MHz" % current_clkrate logger.info(tmsg) # in addition, this class uses clockrate for ogps self.cal.set_clockrate(clkrate) def find_this_ogp(self, freq): # we may not need to do this, but its probably safer. # OGPs don't change w/ bof file, so this is arbitrary self.change_bof(self.ogp_bof) # but OGPs *do* change with clock rate self.change_frequency(freq) # since we reprogrammed the roach, mmcm calibrate self.cal.do_mmcm(2) # TBF: do this once manually at the beginning? if not self.cal.gpib_test( 2, self.testfreq, self.ampl, manual=self.manual): logger.info("canceling find_this_ogp for frequency %s" % freq) return None, None # now find the ogps self.cal.do_ogp(0, self.testfreq, self.ogp_trials) ogp0 = self.cal.ogp.ogps self.cal.do_ogp(1, self.testfreq, self.ogp_trials) ogp1 = self.cal.ogp.ogps return ogp0, ogp1 def find_mmcms(self, roach_name): """ For the given roach, determine all the MMCM optimal phase values for all the different combinations of bof file and frequency. """ self.init_for_roach(roach_name) for key, value in self.adcConf.mmcm_info.items(): bof, frq = key i, adc0, adc1, _ = value # determine the MMCM optimal phase values for this combo # of bof file and frequency adc0s = [] adc1s = [] for trial in range(self.mmcm_trials): adc0, adc1 = self.find_this_mmcm( bof, frq) #v, cal, roach, bof, frq) tmsg = "Found ADC mmcm's for trial %d: %s, %s" % (trial, adc0, adc1) print tmsg logger.info(tmsg) if self.has_adc_difference(adc0, adc1, adc0s, adc1s): adc0s.append(adc0) adc1s.append(adc1) self.adcConf.write_mmcms(bof, frq, 0, adc0s) self.adcConf.write_mmcms(bof, frq, 1, adc1s) self.adcConf.write_to_file() def has_adc_difference(self, adc0, adc1, adc0s, adc1s): assert len(adc0s) == len(adc1s) if len(adc0s) == 0: return True has_diff = True adcs = zip(adc0s, adc1s) t = self.mmcm_tolerance for i in range(len(adc0s)): #if ((abs(adc0s[i] - adc0) < tolerance) and (abs(adc1s[i] - adc1) < tolerance)): if self.is_within_tolerance(adc0, adc0s[i], t) \ and self.is_within_tolerance(adc1, adc1s[i], t): has_diff = False return has_diff def is_within_tolerance(self, x, y, tolerance): # they are both None if x is None and y is None: return True # one of them is None and the other isn't if x is None or y is None: return True # if this were False we'd get None's written to .conf! # none of them are none return abs(x - y) < tolerance def find_this_mmcm(self, bof, freq): #valon, adcCal, roach, bof, freq): """ Sets the given bof file and frequency (Hz) for a roach board using the given valon and ADCCalibration objects, returns the ADCs' MMCM optimal phase results. """ # switch to the given bof file self.change_bof(bof) # we also need to switch to the given frequency self.change_frequency(freq) # Now actually find the MMCM optimal phase for each ADC set_phase = False # TBF? self.cal.set_zdok(0) adc0, g = self.cal.mmcm.calibrate_mmcm_phase(set_phase=set_phase) tmsg = "MMCM (Opt. Phase, Glitches) for zdok %d: %s, %s" % (0, adc0, g) logger.info(tmsg) self.cal.set_zdok(1) adc1, g = self.cal.mmcm.calibrate_mmcm_phase(set_phase=set_phase) tmsg = "MMCM (Opt. Phase, Glitches) for zdok %d: %s, %s" % (1, adc1, g) logger.info(tmsg) return adc0, adc1
class ADCCalibrate: """ This is a high-level class responsible for the calibration of the two ADC cards (zdoks) in each of Vegas's Roach 2 boards. It may interact with the user, and leverages a suite of lower level helper classes. """ def __init__(self , roach = None , roach_name = None , zdok = 0 , gpib_addr = None , test = False , dir = '.' , now = None , config = False , bof = False , clockrate = None): self.zdok = zdok self.test = test self.dir = dir self.clockrate = clockrate if clockrate is not None else 1500.0 self.config = 0 #config self.bof = bof # Removing this check because you may be using ADCCalibrate in a read-only # mode where gpib is not needed (since it is write-only, TBF) #if not test and gpib_addr is None: # raise Exception, "Must specify gpib_addr if ADCCalibrate is not in test mode." if not test and roach_name is None: raise Exception, "Must specify Roach if ADCCalibrate is not in test mode." self.roach_name = roach_name if not test else "noroach" if not test and roach is None: self.roach = corr.katcp_wrapper.FpgaClient(self.roach_name) time.sleep(3) if not self.roach.is_connected(): raise Exception, "%s did not work" % self.roach_name else: self.roach = roach self.now = datetime.now() if now is None else now self.time_frmt = '%Y-%m-%d-%H%M%S' self.current_time = self.now.strftime(self.time_frmt) #self.set_file_label() # helper classes self.gpib = GPIB(gpib_addr, test = test) self.spi = SPI(zdok = zdok, test = test, roach = self.roach) self.adc = AdcSnapshot(zdok = zdok, test = test, roach = self.roach, clockrate = self.clockrate) # higher-level classes self.ogp = OGP(zdok = zdok , spi = self.spi , adc = self.adc , roach_name = roach_name , clockrate = self.clockrate , now = now , dir = dir) self.inl = INL(zdok = zdok , spi = self.spi , roach_name = roach_name , now = now , dir = dir) self.mmcm = MMCM(zdok = zdok, spi = self.spi, adc = self.adc) self.configFile = "%s-adc.conf" % roach_name self.configPath = "%s/%s" % (dir, self.configFile) self.cf = ADCConfFile(self.configPath) self.n_cores = 4 self.cores = range(1,self.n_cores+1) #self.clockrate = 1500.0 self.samp_freq = 2*self.clockrate # file prefixes self.post_mmcm_ramp_check_name = "post_mmcm_ramp_check" self.post_ramp_check_raw_name = "post_ramp_check_raw" self.raw_startup_name = "raw_startup" self.loaded_files = [] def set_zdok(self, zdok): # Note: this is zdok - or ADC ('0' or '1') that # the lower level classes interact with. In this # upper level class we sometimes see zdok == '2', # which means "work on both '0' and '1'" # Pass it down self.spi.set_zdok(zdok) self.adc.set_zdok(zdok) self.ogp.set_zdok(zdok) self.inl.set_zdok(zdok) self.mmcm.set_zdok(zdok) # self.set_file_label() def set_clockrate(self, clockrate): self.adc.set_clockrate(clockrate) self.ogp.set_clockrate(clockrate) def set_freq(self, freq): self.gpib.freq = freq def set_ampl(self, ampl): self.gpib.ampl = ampl def get_check_filename(self, title, zdoks): return "%s/%s_%s_zs%d_%s" % (self.dir , title , self.roach_name , zdoks , self.current_time) #def set_file_label(self): # self.file_label = "_%s_z%d_%s" % (self.roach_name # , self.zdok # , self.current_time) #def get_post_ramp_check_raw_filename(self): # return "post_ramp_check_raw%s" % self.file_label def user_input(self, prompt): prompt = "%s (Y/N)" % prompt logger.debug(prompt) response = raw_input(prompt) logger.debug("user response: \t%s" % response) return response in ["Y", "y"] def load_calibrations(self , indir = None , zdoks = None , types = None , use_conf = False , freq = None): "Loads the most recent ogp, inl calibration files and loads them into the ADC Cards." # where to find the calibration files? if indir is None: var = "YGOR_TELESCOPE" if not os.environ.has_key(var): msg = "If directory for calibration files is not given, YGOR_TELESCOPE must be set." logger.debug(msg) raise Exception, msg else: indir = os.path.join(os.environ[var], "etc/config") # which zdok (ADC card) to load calibrations into? if zdoks is None or zdoks == 2: zdoks = range(2) elif zdoks == 0 or zdoks == 1: zdoks = [zdoks] else: msg = "Zdoks value must be 0,1,2; unsupported: %d" % zdoks logger.debug(msg) raise Exception, msg # which type of calibration? allTypes = ['ogp', 'inl'] if types is None: types = allTypes else: if not all([t in allTypes for t in types]): msg = "Types %s not in %s" % (types, allTypes) logger.debug(msg) raise Exception, msg if use_conf and freq is None: msg = "Must specify freq (MHz) if .conf file is used for loading calibrations." logger.debug(msg) raise Exception, msg if use_conf: self.load_calibrations_from_conf(indir, zdoks, types, freq) else: # go through and find the most recent of each type # of file needed #example file: ogp_noroach_z0_2014-04-24-090838 self.loaded_files = [] for type in types: for zdok in zdoks: ext = "" if type == 'ogp' else ".meas" base_name = "%s_%s_z%s_*%s" % (type, self.roach_name, zdok, ext) # Rely on the timestamp at the end of the file name to make sure we # get the most recent file files = sorted([f for f in os.listdir(indir) if fnmatch.fnmatch(f, base_name)] , reverse = True) if len(files) > 0: f = "%s/%s" % (indir, files[0]) if type == 'ogp': self.ogp.load_from_file(f, zdok = zdok) else: self.inl.load_from_file(f, zdok = zdok) logger.info("Loading file for calibration: %s" % f) self.loaded_files.append(f) else: msg = "load_calibrations: Could not find files in %s matching pattern %s" % (indir, base_name) logger.debug(msg) raise Exception, msg def load_calibrations_from_conf(self, indir, zdoks, types, freq): "Load calibrations from the <roach>-adc.conf file found in given directory." filename = "%s/%s" % (indir, self.configFile) self.cf.read_file(filename) for type in types: for zdok in zdoks: if type == 'ogp': self.ogp.set_zdok(zdok) self.ogp.spi.set_control() self.ogp.set_offsets(self.cf.get_ogp_offsets(freq, zdok)) self.ogp.set_gains( self.cf.get_ogp_gains(freq, zdok)) self.ogp.set_phases( self.cf.get_ogp_phases(freq, zdok)) elif type == 'inl': self.inl.set_inls(self.cf.get_inls(zdok)) def do_ogp(self, zdoks, freq, n_trails): "Handles single zdok, or both" if zdoks==2: self.gpib.set_freq(freq) self.do_ogp(0, freq, n_trails) self.do_ogp(1, freq, n_trails) elif zdoks!=1 and zdoks!=0: logger.error("ZDOK " + str(zdoks) + " is not a valid input, aborting...") else: self.gpib.set_freq(freq) self.ogp.do_ogp(zdoks, freq, n_trails) if self.config: self.cf.write_ogps(self.clockrate*1e6, zdoks, self.ogp.ogps) self.cf.write_to_file() def do_inl(self, zdoks): "Handles single zdok, or both" if zdoks==2: self.do_inl(0) self.do_inl(1) elif zdoks!=1 and zdoks!=0: logger.error("ZDOK " + str(zdoks) + " is not a valid input, aborting...") else: self.inl.do_inl(zdoks) if self.config: self.cf.write_inls(zdoks, self.inl.inls) self.cf.write_to_file() def do_mmcm(self, zdok): "Handles single zdok, or both" if zdok==2: self.do_mmcm(0) self.do_mmcm(1) elif zdok==1 or zdok==0: logger.info("doing MMCM calibration for zdok " + str(zdok)) self.mmcm.set_zdok(zdok) opt, g = self.mmcm.calibrate_mmcm_phase() logger.debug("MMCM (Optimal Phase, [Glitches]) for zdok " + str(zdok) + " : " + str((opt, g))) if self.config: self.cf.write_mmcms(self.bof, self.clockrate*1e6, zdok, opt) self.cf.write_to_file() else: logger.error("ZDOK " + str(zdok) + " is not a valid input") def gpib_test(self, zdok, freq, ampl, manual=True): logger.info("Checking if the synthesizer is connected correctly...") #if self.gpib is None: # logger.info('Initializing the synthesizer...') # try: # with time_limit(15): # self.gpib = GPIB(self.freq, self.ampl) # except TimeoutException, msg: # to_continue = 'N' # logger.error("Time out trying to connect to the synthesizer at " \ # + addr+ "...aborting...") # time.sleep(2) logger.debug("ampl " + str(ampl)) logger.debug("test_freq " + str(freq)) self.gpib.set_freq(freq) self.gpib.set_ampl(ampl) if manual: self.check_raw(zdok, save=False) self.check_spec(zdok, save=False) tprompt = "Does the system look OK so far, and you wish to continue?" to_continue = self.user_input(tprompt) else: to_continue = True #'Y' return to_continue def get_ramp(self, zdok, set_mode = True): self.set_zdok(zdok) if set_mode: self.spi.set_test_mode() else: print "Getting Ramp WITHOUT setting to test mode!" snap_name = "adcsnap%s" % zdok #self.get_snap_name(zdok) a, b, c, d = self.adc.get_test_vector([snap_name], man_trig=True, wait_period=2) if set_mode: self.spi.unset_test_mode() return a, b, c, d def check_ramp(self, zdok, save=True, view=True, filename=None, set_mode = True): #"ramp"): filename = filename if filename is not None else self.get_check_filename(self.post_mmcm_ramp_check_name, zdok) # get test vectors logmsg = "Checking ramp test... zdok: "+str(zdok)+" save:" + str(save) logmsg += " filename: " + str(filename) + "\n" logger.info(logmsg) if zdok==2: a0, b0, c0, d0 = self.get_ramp(0, set_mode = set_mode) a1, b1, c1, d1 = self.get_ramp(1, set_mode = set_mode) elif zdok==0: a0, b0, c0, d0 = self.get_ramp(0, set_mode = set_mode) a1 = np.zeros(len(a0)) b1 = np.zeros(len(b0)) c1 = np.zeros(len(c0)) d1 = np.zeros(len(d0)) elif zdok==1: a1, b1, c1, d1 = self.get_ramp(1, set_mode = set_mode) a0 = np.zeros(len(a1)) b0 = np.zeros(len(b1)) c0 = np.zeros(len(c1)) d0 = np.zeros(len(d1)) else: logmsg = "Invalid input for zdok: "+ str(zdok) + " aborting..." logger.error(logmsg) # plot stuff f = figure() ax0 = f.add_subplot(211) ax1 = f.add_subplot(212) ax0.plot(a0, '-o', b0, '-d', c0, '-^', d0, '-s') ax1.plot(a1, '-o', b1, '-d', c1, '-^', d1, '-s') ax0.set_title('ADC0') ax1.set_title('ADC1') f.suptitle(filename) if save: logger.debug("Saving file :%s"%(filename+'.png')) savefig(filename+'.png', dpi=300) if view: show() else: close() logger.debug("Now check raw data to make sure ADCs are back in data capturing mode...") # make sure the ADCs are succesfully set back to regular data capturing mode #self.check_raw(save=save, filename="post_ramp_check_raw" + timestamp) fn = self.get_check_filename(self.post_ramp_check_raw_name, zdok) self.check_raw(zdok, save=save, view=view, filename=fn) def check_raw(self, zdok, save=True, view=True, filename=None): filename = filename if filename is not None else self.get_check_filename(self.raw_startup_name, zdok) # get the data logmsg = "Checking raw data... zdok: "+str(zdok)+" save:" + str(save) logmsg += " filename: " + str(filename) logger.info(logmsg) if zdok == 2: raw0 = self.adc.get_raw(0) raw1 = self.adc.get_raw(1) elif zdok == 0: raw0 = self.adc.get_raw(0) raw1 = np.zeros(len(raw0)) elif zdok == 1: raw1 = self.adc.get_raw(1) raw0 = np.zeros(len(raw1)) else: logmsg = "Invalid input for zdok: "+ str(zdok) + " aborting..." logger.error(logmsg) m0 = max(np.abs(raw0)) m1 = max(np.abs(raw1)) if m0>=128 or m1>=128: logger.warning("Power too high, clipping might be occurring...please check") # plot stuff f = figure() ax0 = f.add_subplot(231) ax1 = f.add_subplot(234) ax0.plot(raw0, '-o') ax0.set_title('ADC0') ax1.plot(raw1, '-d') ax1.set_title('ADC1') ax00 = f.add_subplot(232) ax10 = f.add_subplot(235) ax00.plot(raw0[0:int(len(raw0)/100)], '-o') ax00.set_title('ADC0 - Zoom 100x') ax10.plot(raw1[0:int(len(raw0)/100)], '-d') ax10.set_title('ADC1 - Zoom 100x') ax01 = f.add_subplot(233) ax11 = f.add_subplot(236) ax01.plot(raw0[0:int(len(raw0)/20)], '-o') ax01.set_title('ADC0 - Zoom 20x') ax11.plot(raw1[0:int(len(raw0)/20)], '-d') ax11.set_title('ADC1 - Zoom 20x') f.suptitle(filename) f.text(0.5, 0.04, 'time', ha='center', va='center') f.text(0.06, 0.5, 'amplitude', ha='center', va='center', rotation='vertical') if save: logger.debug("Saving file :%s"%(filename+'.png')) f.set_size_inches(18, 12) savefig(filename+'.png', dpi=150) if view: show() else: close() return def check_spec(self, zdok, save=True, view=True, filename = None): #filename="spec"): filename = filename if filename is not None else self.get_check_filename("spec", zdok) logmsg = "Checking spectrum... zdok: "+str(zdok)+" save:" + str(save) logmsg += " filename: " + str(filename) + "\n" logger.info(logmsg) if zdok == 2: nfr0 = self.adc.get_spec(0) spikes0 = self.adc.find_spike(nfr0) nfr1 =self.adc.get_spec(1) spikes1 = self.adc.find_spike(nfr1) elif zdok == 0: nfr0 = self.adc.get_spec(0) spikes0 = self.adc.find_spike(nfr0) nfr1 = np.zeros(len(nfr0)) - 1.0 spikes1 = np.array([-1.0]) elif zdok == 1: nfr1 = self.adc.get_spec(1) spikes1 = self.adc.find_spike(nfr1) nfr0 = np.zeros(len(nfr1)) - 1.0 spikes0 = np.array([-1.0]) else: logmsg = "Invalid input for zdok: "+ str(zdok) + " aborting..." logger.error(logmsg) nchan = len(nfr0) # this is NOT related to the FPGA design freqs = np.arange(0, self.clockrate, self.clockrate*1./nchan) logger.debug("Doing " + str(nchan) + " points FFT. ") logger.debug("Nyquist : " + str(self.clockrate) ) logger.debug("Found spikes at %.4fMHz for ADC0"%spikes0) logger.debug("Found spikes at %.4fMHz for ADC1"%spikes1) # plot stuff f = figure() ax0 = f.add_subplot(211) ax1 = f.add_subplot(212) ax0.plot(freqs, 10*np.log(nfr0)) ax0.annotate('spike ~%.4fMHz'%spikes0[0], xy=(spikes0[0], 0), xycoords='data', xytext=(spikes0[0]+500,-30), textcoords='data', arrowprops=dict(arrowstyle="->",connectionstyle="arc3"), ha='right', va='top') ax0.set_title('ADC0') ax1y = 10*np.log(nfr1) ax1.plot(freqs, ax1y) ax1.annotate('spike ~%.4fMHz'%spikes1[0], xy=(spikes1[0], 0), xycoords='data', xytext=(spikes1[0]+500,-30), textcoords='data', arrowprops=dict(arrowstyle="->",connectionstyle="arc3"), ha='right', va='top') info_str = "" if self.gpib.freq is not None: info_str += "Current input test tone frequency: %.4f"%self.gpib.freq if self.gpib.ampl is not None: info_str += "\nCurrent input power level: %.4f"%self.gpib.ampl ax1.text(450, min(ax1y)+20, info_str, bbox={'facecolor':'yellow', 'alpha':0.9}) ax1.set_title('ADC1') f.suptitle(filename) f.text(0.5, 0.04, 'frequency (MHz)', ha='center', va='center') f.text(0.06, 0.5, 'power (dB)', ha='center', va='center', rotation='vertical') if save: logger.debug("Saving file :%s"%(filename+'.png')) savefig(filename+'.png', dpi=300) if view: show() else: close() return def ampl_setup(self, zdok, manual=True, new_ampl=None, check_ampl=False): ampl = self.gpib.ampl # User interactions logmsg = "Changing input power level...current: " + str(ampl) logger.info(logmsg) if manual: tprompt = "Please enter the new Power level (dbM): (press enter to skip)" new_ampl_raw = raw_input(tprompt) logger.debug(tprompt) logger.debug("user input: " + new_ampl_raw) if new_ampl_raw=="": logger.debug("Keeping current power level...") return else: new_ampl = float(new_ampl_raw) # TBF: these limits seem arbitrary too_low = -15 too_high = 10 if check_ampl and (new_ampl<too_low or new_ampl>too_high): logmsg = "ampl " + str(new_ampl) + " too big or too small (range: " + str(too_low) + "-" + str(too_high) + ")" logger.error(logmsg) raise Exception, logmsg exit() # Finally, set the new amplitude logger.debug(" New ampl is: " + str(new_ampl)) ampl = new_ampl self.gpib.set_ampl(ampl) time.sleep(2) if manual: # Double check? tprompt = " Check raw ADC data now?" # (Y/N)" if self.user_input(tprompt): #logger.debug(tprompt) #to_check = raw_input(tprompt) #logger.debug("user input: " + to_check) #if to_check=='Y' or to_check=='y': self.check_raw(zdok, save=False, view=True) # Add notes to logger tprompt = "Does the raw data look okay? If not, "\ + "please briefly describe the problem here "\ + "(or press enter to proceed): " notes = raw_input(tprompt) logger.debug(notes) #if not isempty(notes): if len(notes) > 0: logger.warning(notes) def freq_setup(self, zdok, manual=True, freq=None): #global test_freq test_freq = freq if freq is not None else self.gpib.freq logmsg = "Changing input frequency...current: " + str(test_freq) logger.info(logmsg) if manual: tprompt = "Please enter the new test frequency (MHz): (press enter to skip)" logger.debug(tprompt) freq_raw = raw_input(tprompt) logger.debug("user input: " + freq_raw) if freq_raw == "": logger.debug("Keeping current frequency...") return else: freq = float(freq_raw) if freq < 0 or freq > 1500: logmsg = "freq " + str(freq) + " too big or too small" logger.error(logmsg) raise Exception, logmsg exit() logger.debug(" New frequency is: " + str(freq)) test_freq = freq self.gpib.set_freq(test_freq) time.sleep(2) if manual: tprompt = " Check raw ADC data now? (Y/N)" to_check = raw_input(tprompt) logger.debug(tprompt) logger.debug("user input: " + to_check) if to_check=='Y' or to_check=='y': self.check_raw(zdok, save=False, view=True) tprompt = "Does the raw data look okay? If not, "\ + "please briefly describe the problem here "\ + "(or press enter to proceed): " notes = raw_input(tprompt) logger.debug(tprompt) logger.debug(notes) logger.warning(notes) def freq_scan(self, save=True, view=True, filename=None): #"freq_scan"): filename = filename if filename is not None else self.get_check_filename("freq_scan", 2) test_freq = self.gpib.freq logger.info("Starting frequency scan (both ADCs)... save: " +str(save)) # Gather the data freqs = [] f0 = [] f1 = [] spikes0_arr = [] spikes1_arr = [] rng = 50 for i in range(0, rng): test_freq = i*30+random.random()*30 logger.debug("freq : " + str(test_freq)) self.gpib.set_freq(test_freq) time.sleep(2) nfr0 = self.adc.get_spec(0) spikes0 = self.adc.find_spike(nfr0) nfr1 = self.adc.get_spec(1) spikes1 = self.adc.find_spike(nfr1) logger.debug("Found spikes at %.4fMHz for ADC0"%spikes0) logger.debug("Found spikes at %.4fMHz for ADC1"%spikes1) freqs.append(test_freq) f0.append(10*log(nfr0)) f1.append(10*log(nfr1)) spikes0_arr.append(spikes0) spikes1_arr.append(spikes1) # plot it! f = figure() ax0 = f.add_subplot(211) ax1 = f.add_subplot(212) logger.debug(" Plotting now...") nchan = len(nfr0) # this is NOT related to the FPGA design freqs_ind = np.arange(0, self.clockrate, self.clockrate*1./nchan) for i in range(1, rng, 10): logger.debug(" \ttest_freq: " + str(freqs[i])) ax0.plot(freqs_ind, f0[i]) ax0.annotate('spike ~%.4fMHz'%spikes0_arr[i][0], xy=(spikes0_arr[i][0], 0), xycoords='data', xytext=(spikes0_arr[i][0]+500,-1*i), textcoords='data', arrowprops=dict(arrowstyle="->",connectionstyle="arc3"), ha='right', va='top') logger.debug(" \tannotated spike (ADC0): %.4fMHz"%spikes0_arr[i][0]) ax1.plot(freqs_ind, f1[i]) ax1.annotate('spike ~%.4fMHz'%spikes1_arr[i][0], xy=(spikes1_arr[i][0], 0), xycoords='data', xytext=(spikes1_arr[i][0]+500,-1*i), textcoords='data', arrowprops=dict(arrowstyle="->",connectionstyle="arc3"), ha='right', va='top') logger.debug(" \tannotated spike (ADC1): %.4fMHz"%spikes1_arr[i][0]) ax0.set_title('ADC0') ax1.set_title('ADC1') f.suptitle(filename) info_str = "Current input power level: %.4f"%self.gpib.ampl ax1.text(450, min(f1[0])+20, info_str, bbox={'facecolor':'yellow', 'alpha':0.9}) f.text(0.5, 0.04, 'frequency (MHz)', ha='center', va='center') f.text(0.06, 0.5, 'power (dB)', ha='center', va='center', rotation='vertical') if save: logger.debug("Saving file :%s"%(filename+'.png')) savefig(filename+'.png', dpi=300) if view: show() else: close()
class ADCCalibrations: def __init__(self , test = False , conf_dir = None , data_dir = None , roaches = None , mmcm_trials = None , ogp_trials = None , test_tone = None , ampl = None , now = None , manual = False , do_ogps = True , do_mmcms = True , gpib_addr = None): self.test = test self.now = now self.conf_dir = conf_dir if conf_dir is not None else '.' self.data_dir = data_dir if data_dir is not None else '.' self.roaches = roaches if roaches is not None else self.get_roach_names_from_config() self.banks = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'] self.manual = manual self.do_mmcms = do_mmcms self.do_ogps = do_ogps if not self.do_ogps and not self.do_mmcms: raise Exception("One of do_ogps and do_mmcms must be True") # mmcms self.mmcm_trials = mmcm_trials if mmcm_trials is not None else 5 self.mmcm_tolerance = 4 # ogps/inls self.ogp_bof = 'h1k_ver106_2014_Apr_11_1612.bof' self.testfreq = test_tone if test_tone is not None else 18.3105 # MHz self.ampl = ampl if ampl is not None else -3 self.ogp_trials = ogp_trials if ogp_trials is not None else 10 self.gpibaddr = gpib_addr if gpib_addr is not None else '10.16.96.174' # tape room # helper classes self.cp = ConfigParser.ConfigParser() self.roach = None self.valon = None self.cal = None self.adcConf = None def find_all_calibrations(self): if self.do_mmcms: self.find_all_mmcms() if self.do_ogps: self.find_all_ogps() def find_all_ogps(self): for r in self.roaches: self.find_ogps(r) def find_all_mmcms(self): for r in self.roaches: self.find_mmcms(r) def get_roach_names_from_config(self): "Determines what roaches to connect to from the vegas.conf file" fn = "%s/%s" % (self.conf_dir, "vegas.conf") r = cp.read(fn) if len(r)==0: print "Could not find roach names from: ", fn return [] # what banks to read? sec = "DEFAULTS" subsys = self.cp.get(sec, "subsystems") subsys = [int(s) for s in subsys.split(',')] # what roaches corresopond to those banks? roaches = [] for s in subsys: bank = self.banks[s-1] sec = "BANK%s" % bank # roach_host = vegasr2-1.gb.nrao.edu roaches.append(cp.get(sec, "roach_host").split('.')[0]) return roaches def get_adc_config_filename(self, roach_name): fn = "%s/%s-adc.conf" % (self.conf_dir, roach_name) logger.info("MMCM config file: %s" % fn) return fn def init_for_roach(self, roach_name): "Inits the helper classes we use to interact w/ the given roach name" # connect to roach tmsg = 'Connecting to %s'%roach_name logger.info(tmsg) if not self.test: self.roach = corr.katcp_wrapper.FpgaClient(roach_name) time.sleep(1) if not self.roach.is_connected(): raise Exception("Cannot connect to %s" % roach_name) # we'll need this to change the frequency valonSerial = "/dev/ttyS1" # this should never change if not self.test: self.valon = ValonKATCP(self.roach, valonSerial) # this is the object that will find the MMCM value self.cal = ADCCalibrate(dir = self.data_dir , roach_name = roach_name , gpib_addr = self.gpibaddr , roach = self.roach , now = self.now , test = self.test) # read the config file and find the mmcm through each mode fn = self.get_adc_config_filename(roach_name) self.adcConf = ADCConfFile(fn) def find_ogps(self, roach_name): self.init_for_roach(roach_name) for frq, value in self.adcConf.ogp_info.items(): #i, ogp0, ogp1 = value # determine the OGP values for this clockrate tmsg = "Finding OGPs for clockrate: %s" % frq logger.info(tmsg) ogp0, ogp1 = self.find_this_ogp(frq) if ogp0 is not None and ogp1 is not None: self.adcConf.write_ogps(frq, 0, ogp0) self.adcConf.write_ogps(frq, 1, ogp1) self.adcConf.write_to_file() # the INLs don't change w/ either bof, or clockrate, # so now that this roach is in a suitable state (it's # MMCM and OGP calibrations are done), let's do one INL for z in range(2): self.cal.do_inl(z) self.adcConf.write_inls(z, self.cal.inl.inls) self.adcConf.write_to_file() def change_bof(self, bof): if self.test: return # switch to the given bof file self.roach.progdev(bof) tmsg = "Roach BOF file set to: %s" % bof logger.info(tmsg) time.sleep(2) def change_frequency(self, freq): "If necessary, use the Valon Synth to change the Roach board clockrate." if self.test: return # we also need to switch to the given frequency valonSynth = 0 # neither should this (0: A, 8: B) current_clkrate = self.valon.get_frequency(valonSynth) tmsg = "Valon Synth set to frequency: %f MHz" % current_clkrate clkrate = freq / 1e6 # Hz -> MHz logger.info(tmsg) if abs(current_clkrate - clkrate) > 0.001: self.valon.set_frequency(valonSynth, clkrate) time.sleep(1) current_clkrate = self.valon.get_frequency(valonSynth) tmsg = "Valon Synth changed to frequency: %f MHz" % current_clkrate logger.info(tmsg) # in addition, this class uses clockrate for ogps self.cal.set_clockrate(clkrate) def find_this_ogp(self, freq): # we may not need to do this, but its probably safer. # OGPs don't change w/ bof file, so this is arbitrary self.change_bof(self.ogp_bof) # but OGPs *do* change with clock rate self.change_frequency(freq) # since we reprogrammed the roach, mmcm calibrate self.cal.do_mmcm(2) # TBF: do this once manually at the beginning? if not self.cal.gpib_test(2, self.testfreq, self.ampl, manual=self.manual): logger.info("canceling find_this_ogp for frequency %s" % freq) return None, None # now find the ogps self.cal.do_ogp(0, self.testfreq, self.ogp_trials) ogp0 = self.cal.ogp.ogps self.cal.do_ogp(1, self.testfreq, self.ogp_trials) ogp1 = self.cal.ogp.ogps return ogp0, ogp1 def find_mmcms(self, roach_name): """ For the given roach, determine all the MMCM optimal phase values for all the different combinations of bof file and frequency. """ self.init_for_roach(roach_name) for key, value in self.adcConf.mmcm_info.items(): bof, frq = key i, adc0, adc1, _ = value # determine the MMCM optimal phase values for this combo # of bof file and frequency adc0s = [] adc1s = [] for trial in range(self.mmcm_trials): adc0, adc1 = self.find_this_mmcm(bof, frq) #v, cal, roach, bof, frq) tmsg = "Found ADC mmcm's for trial %d: %s, %s" % (trial, adc0, adc1) print tmsg logger.info(tmsg) if self.has_adc_difference(adc0, adc1, adc0s, adc1s): adc0s.append(adc0) adc1s.append(adc1) self.adcConf.write_mmcms(bof, frq, 0, adc0s) self.adcConf.write_mmcms(bof, frq, 1, adc1s) self.adcConf.write_to_file() def has_adc_difference(self, adc0, adc1, adc0s, adc1s): assert len(adc0s) == len(adc1s) if len(adc0s) == 0: return True has_diff = True adcs = zip(adc0s, adc1s) t = self.mmcm_tolerance for i in range(len(adc0s)): #if ((abs(adc0s[i] - adc0) < tolerance) and (abs(adc1s[i] - adc1) < tolerance)): if self.is_within_tolerance(adc0, adc0s[i], t) \ and self.is_within_tolerance(adc1, adc1s[i], t): has_diff = False return has_diff def is_within_tolerance(self, x, y, tolerance): # they are both None if x is None and y is None: return True # one of them is None and the other isn't if x is None or y is None: return True # if this were False we'd get None's written to .conf! # none of them are none return abs(x - y) < tolerance def find_this_mmcm(self, bof, freq): #valon, adcCal, roach, bof, freq): """ Sets the given bof file and frequency (Hz) for a roach board using the given valon and ADCCalibration objects, returns the ADCs' MMCM optimal phase results. """ # switch to the given bof file self.change_bof(bof) # we also need to switch to the given frequency self.change_frequency(freq) # Now actually find the MMCM optimal phase for each ADC set_phase = False # TBF? self.cal.set_zdok(0) adc0, g = self.cal.mmcm.calibrate_mmcm_phase(set_phase = set_phase) tmsg = "MMCM (Opt. Phase, Glitches) for zdok %d: %s, %s" % (0, adc0, g) logger.info(tmsg) self.cal.set_zdok(1) adc1, g = self.cal.mmcm.calibrate_mmcm_phase(set_phase = set_phase) tmsg = "MMCM (Opt. Phase, Glitches) for zdok %d: %s, %s" % (1, adc1, g) logger.info(tmsg) return adc0, adc1