|
@@ -0,0 +1,435 @@
|
|
|
+import re
|
|
|
+import time
|
|
|
+import logging
|
|
|
+import subprocess
|
|
|
+import ConfigParser
|
|
|
+import numpy as np
|
|
|
+
|
|
|
+log = logging.getLogger(__name__)
|
|
|
+
|
|
|
+
|
|
|
+class BoardError(Exception):
|
|
|
+ pass
|
|
|
+
|
|
|
+
|
|
|
+class ObserverError(Exception):
|
|
|
+ pass
|
|
|
+
|
|
|
+
|
|
|
+class BoardConfiguration():
|
|
|
+
|
|
|
+ def __init__(self, config_file=None, logger=None):
|
|
|
+ self._config = {}
|
|
|
+ self._observers = {}
|
|
|
+ self.logger = logger
|
|
|
+ self._set_defaults()
|
|
|
+
|
|
|
+ if config_file:
|
|
|
+ self.load_config(config_file)
|
|
|
+
|
|
|
+ def _set_defaults(self):
|
|
|
+ c = self._config
|
|
|
+ c['fpga_delay_max'] = 15
|
|
|
+ c['fpga_delay'] = 0
|
|
|
+ c['fpga_delay_factor'] = 150
|
|
|
+
|
|
|
+ c['chip_delay_max'] = 31
|
|
|
+ c['chip_1_delay'] = 4
|
|
|
+ c['chip_2_delay'] = 4
|
|
|
+ c['chip_3_delay'] = 4
|
|
|
+ c['chip_4_delay'] = 4
|
|
|
+ c['chip_delay_factor'] = 3
|
|
|
+
|
|
|
+ c['th_delay_max'] = 15
|
|
|
+ c['th_delay'] = 3
|
|
|
+ c['th_delay_factor'] = 150
|
|
|
+
|
|
|
+ c['adc_delay_max'] = 15
|
|
|
+ c['adc_1_delay'] = 4
|
|
|
+ c['adc_2_delay'] = 4
|
|
|
+ c['adc_3_delay'] = 4
|
|
|
+ c['adc_4_delay'] = 4
|
|
|
+ c['adc_delay_factor'] = 150
|
|
|
+
|
|
|
+ c['th_to_adc_cycles'] = 7
|
|
|
+ c['adc_1_delay_individual'] = -1 # -1 = 'ADC 1 delay is the same as th_to_adc_cycles'
|
|
|
+ # 0 .. 16 = 'Use this offset instead'
|
|
|
+
|
|
|
+ c['orbits_observe'] = 100
|
|
|
+ c['orbits_skip'] = 2
|
|
|
+
|
|
|
+ def register_logger(self, logger):
|
|
|
+ self.logger = logger
|
|
|
+
|
|
|
+ def load_config(self, filename):
|
|
|
+ if filename:
|
|
|
+ config = ConfigParser.RawConfigParser()
|
|
|
+ if not config.read(str(filename)):
|
|
|
+ return
|
|
|
+
|
|
|
+ for key in self._config.keys():
|
|
|
+ try:
|
|
|
+ self._config[key] = config.getint('Config', key)
|
|
|
+ if self.logger:
|
|
|
+ self.logger.info("Read '%s' for '%s' from '%s'"%(str(self._config[key]), key, str(filename)))
|
|
|
+ except ConfigParser.NoOptionError as e:
|
|
|
+ pass
|
|
|
+ except ConfigParser.NoSectionError as e:
|
|
|
+ pass
|
|
|
+
|
|
|
+ def save_config(self, filename):
|
|
|
+ if filename:
|
|
|
+ with open(str(filename), 'w') as f:
|
|
|
+ cp = ConfigParser.RawConfigParser()
|
|
|
+ cp.add_section('Config')
|
|
|
+ for key in self._config.keys():
|
|
|
+ cp.set('Config', key, self._config[key])
|
|
|
+ f.write('#\n'
|
|
|
+ '# HEB (Hot Electron Bolometer) Configuration file\n'
|
|
|
+ '#\n'
|
|
|
+ '# (c) Karlsruhe Institute of Technology, 2014\n'
|
|
|
+ '# All rights reserved.\n'
|
|
|
+ '#\n'
|
|
|
+ '# Applicable Firmware Version(s):\n'
|
|
|
+ '#\n\n')
|
|
|
+ cp.write(f)
|
|
|
+
|
|
|
+ def get(self, key):
|
|
|
+ return self._config.get(key, None)
|
|
|
+
|
|
|
+ def update(self, key, value):
|
|
|
+ self._config[key] = value
|
|
|
+ self._notify_observers(key, value)
|
|
|
+
|
|
|
+ def get(self, key):
|
|
|
+ return self._config.get(key, None)
|
|
|
+
|
|
|
+ def update(self, key, value):
|
|
|
+ self._config[key] = value
|
|
|
+ self._notify_observers(key, value)
|
|
|
+
|
|
|
+ def observe(self, who, callback, key):
|
|
|
+ if key not in self._config.keys():
|
|
|
+ raise ObserverError(str("Key '%s' is unknown."%key))
|
|
|
+
|
|
|
+ if self._observers.get(key, None) is None:
|
|
|
+ self._observers[key] = []
|
|
|
+
|
|
|
+ self._observers[key].append([who, callback])
|
|
|
+
|
|
|
+ def unobserve(self, who, key=None):
|
|
|
+ if key is not None:
|
|
|
+ observers = np.array(self._observers.get(key, None))
|
|
|
+ if observers is None:
|
|
|
+ return
|
|
|
+ if who not in observers[:, 0]:
|
|
|
+ return
|
|
|
+ for i, _obs in enumerate(self._observers[key]):
|
|
|
+ if _obs[0] is who:
|
|
|
+ del self._observers[key][i]
|
|
|
+ if not self._observers[key]:
|
|
|
+ del self._observers[key]
|
|
|
+ return
|
|
|
+
|
|
|
+ for _key in self._observers.keys():
|
|
|
+ for i, _obs in enumerate(self._observers[_key]):
|
|
|
+ if _obs[0] is who:
|
|
|
+ del self._observers[_key][i]
|
|
|
+ if not self._observers[_key]:
|
|
|
+ del self._observers[_key]
|
|
|
+
|
|
|
+ def _notify_observers(self, key, value):
|
|
|
+ observers = self._observers.get(key, None)
|
|
|
+ if observers is None:
|
|
|
+ return
|
|
|
+ for (who, callback) in observers:
|
|
|
+ callback(value)
|
|
|
+
|
|
|
+ def make_uint(self, value, maximum, name=None):
|
|
|
+ if value is None:
|
|
|
+ raise ValueError(str("%s Value is invalid (None)" % name))
|
|
|
+
|
|
|
+ val = None
|
|
|
+ try:
|
|
|
+ val = int(value)
|
|
|
+ except:
|
|
|
+ raise ValueError(str("%s Value is not a valid number" % name))
|
|
|
+
|
|
|
+ if maximum is not None:
|
|
|
+ if val > maximum:
|
|
|
+ raise ValueError(str("%s Value is too large (>%i)" % (name, maximum)))
|
|
|
+
|
|
|
+ if val < 0:
|
|
|
+ raise ValueError(str("%s Values below 0 are not allowed" % name))
|
|
|
+
|
|
|
+ return val
|
|
|
+
|
|
|
+ def set_fpga_delay(self, value):
|
|
|
+ time_factor = self.make_uint(value, self.get('fpga_delay_max'), 'FPGA_Delay')
|
|
|
+ reg_value = "0x000501" + '{0:01x}'.format(time_factor) + "0"
|
|
|
+ write_pci(reg_value, '0x9060')
|
|
|
+ if self.logger:
|
|
|
+ self.logger.info("Set FPGA clock delay %i * %i --> %i picoseconds" % (time_factor, self.get('fpga_delay_factor'), time_factor*self.get('fpga_delay_factor')))
|
|
|
+ self.update('fpga_delay', value)
|
|
|
+
|
|
|
+ def set_chip_delay(self, adcs, values):
|
|
|
+ if not adcs or not values:
|
|
|
+ if self.logger:
|
|
|
+ self.logger.info("Nothing to do for chip delay.")
|
|
|
+ return
|
|
|
+
|
|
|
+ _adcs = []
|
|
|
+ for adc in adcs:
|
|
|
+ _adcs.append(self.make_uint(adc, 3, 'ADC_'))
|
|
|
+
|
|
|
+ _values = []
|
|
|
+ for value in values:
|
|
|
+ _values.append(self.make_uint(value, self.get('chip_delay_max'), 'ADC_Value'))
|
|
|
+
|
|
|
+ a_v = zip(_adcs, _values)
|
|
|
+
|
|
|
+ factors = [None, None, None, None]
|
|
|
+ for (adc, value) in a_v:
|
|
|
+ factors[adc] = value
|
|
|
+
|
|
|
+ reg_value = ''
|
|
|
+ mask = ''
|
|
|
+ # Chip Delays are stored as 'ADC_4 ADC_3 ADC_2 ADC_1' in the register.
|
|
|
+ # Therefore, we need to traverse the factors array in reverse order
|
|
|
+ for value in reversed(factors):
|
|
|
+ if value is not None:
|
|
|
+ reg_value += '{0:02x}'.format(value)
|
|
|
+ mask += 'ff'
|
|
|
+ else:
|
|
|
+ reg_value += '00'
|
|
|
+ mask += '00'
|
|
|
+
|
|
|
+ write_pci(reg_value, '0x9080', hex_mask=mask)
|
|
|
+ if self.logger:
|
|
|
+ s = "Setting ADC Delays:"
|
|
|
+ for (adc, value) in a_v:
|
|
|
+ s += ' ADC_%i Fine Delay: %i,' % (adc, value)
|
|
|
+ s = s[:-1] # cut away the last dangling ','
|
|
|
+ self.logger.info(s)
|
|
|
+ for (adc, value) in a_v:
|
|
|
+ s = 'chip_%i_delay'%(adc+1)
|
|
|
+ self.update(s, value)
|
|
|
+
|
|
|
+ def set_th_delay(self, value):
|
|
|
+ time_factor = self.make_uint(value, self.get('th_delay_max'), 'TH_Delay')
|
|
|
+ reg_value = "0x000501" + '{0:01x}'.format(time_factor) + "3"
|
|
|
+ write_pci(reg_value, '0x9060')
|
|
|
+ if self.logger:
|
|
|
+ self.logger.info("Set T/H Signal delay %i * %i --> %i picoseconds" % (time_factor, self.get('th_delay_factor'), time_factor*self.get('th_delay_factor')))
|
|
|
+ self.update('th_delay', value)
|
|
|
+
|
|
|
+ def set_adc_delay(self, adc, value):
|
|
|
+ if adc is None or value is None:
|
|
|
+ if self.logger:
|
|
|
+ self.logger.info("Nothing to do for ADC delay.")
|
|
|
+ return
|
|
|
+ _adc = self.make_uint(adc, 3, 'ADC Number')
|
|
|
+ _val = self.make_uint(value, self.get('adc_delay_max'), 'ADC Delay')
|
|
|
+ reg_value = "0x000501" + '{0:01x}'.format(_val) + "%i" % (_adc+4)
|
|
|
+ write_pci(reg_value, '0x9060')
|
|
|
+ if self.logger:
|
|
|
+ s = "Setting ADC_%i delay %i * %i --> %i picoseconds" % ((_adc+1), _val, self.get('adc_delay_factor'), _val*self.get('adc_delay_factor'))
|
|
|
+ self.logger.info(s)
|
|
|
+ adc_s = 'adc_%i_delay'%(_adc+1)
|
|
|
+ self.update(adc_s, _val)
|
|
|
+
|
|
|
+ def set_delay(self, n):
|
|
|
+ def write_delay(value, channel):
|
|
|
+ cmd = '00501' + '%01x' % value + str(channel)
|
|
|
+ write_pci(cmd, reg='0x9060')
|
|
|
+ time.sleep(0.005)
|
|
|
+
|
|
|
+ write_delay(n, 3)
|
|
|
+ self.update('th_delay', n)
|
|
|
+
|
|
|
+ delay = n + self.get('th_to_adc_cycles')
|
|
|
+
|
|
|
+ if delay > self.get('adc_delay_max'):
|
|
|
+ delay -= self.get('adc_delay_max') + 1
|
|
|
+
|
|
|
+ write_delay(delay, 5)
|
|
|
+ self.update('adc_2_delay', delay)
|
|
|
+ write_delay(delay, 6)
|
|
|
+ self.update('adc_3_delay', delay)
|
|
|
+ write_delay(delay, 7)
|
|
|
+ self.update('adc_4_delay', delay)
|
|
|
+
|
|
|
+ #ADC 1 might have an individual delay
|
|
|
+ try:
|
|
|
+ delay = n + self.make_uint(self.get('adc_1_delay_individual'), 16, 'ADC 1 individual delay')
|
|
|
+ except ValueError:
|
|
|
+ if self.logger:
|
|
|
+ self.logger.info(r"'adc_1_delay_individual' not set or inactive. Using default.")
|
|
|
+
|
|
|
+ if delay > self.get('adc_delay_max'):
|
|
|
+ delay -= self.get('adc_delay_max') + 1
|
|
|
+ write_delay(delay, 4)
|
|
|
+ self.update('adc_1_delay', delay)
|
|
|
+
|
|
|
+
|
|
|
+# def safe_call(cmd):
|
|
|
+# try:
|
|
|
+# return subprocess.check_output(cmd)
|
|
|
+# except subprocess.CalledProcessError as e:
|
|
|
+# raise BoardError(e.output)
|
|
|
+# except OSError as e:
|
|
|
+# raise BoardError('{}: {}'.format(' '.join(cmd), str(e)))
|
|
|
+def safe_call(cmd): # TODO: For Production restore save_call
|
|
|
+ log.info(cmd)
|
|
|
+ return True
|
|
|
+
|
|
|
+def write_pci(value, reg='0x9040', opts=[], hex_mask='FFFFFFFF'):
|
|
|
+
|
|
|
+ if hex_mask != 'FFFFFFFF':
|
|
|
+ if len(hex_mask) > 8:
|
|
|
+ hex_mask = hex_mask[-8:]
|
|
|
+ prev_val = read_pci(1, reg)[0]
|
|
|
+ prev_bits = '{0:032b}'.format(int(prev_val,16))
|
|
|
+ mask_bits = '{0:032b}'.format(int(hex_mask,16))
|
|
|
+ value_bits = '{0:032b}'.format(int(value,16))
|
|
|
+ new_bits = list(prev_bits)
|
|
|
+
|
|
|
+ for i, bit in enumerate(mask_bits):
|
|
|
+ if bit == '1':
|
|
|
+ new_bits[i] = value_bits[i]
|
|
|
+
|
|
|
+ value = hex(int("".join(new_bits),2))
|
|
|
+
|
|
|
+ cmd = ['pci', '-w', reg, value]
|
|
|
+ cmd.extend(opts)
|
|
|
+ safe_call(cmd)
|
|
|
+ log.debug('Written %str to register %s'%(value, reg))
|
|
|
+
|
|
|
+
|
|
|
+def read_pci(amount=1, reg='0x9000', opts=[], decimal=False):
|
|
|
+ cmd = ['pci', '-r', reg, "-s%i"%amount]
|
|
|
+ cmd.extend(opts)
|
|
|
+ output = safe_call(cmd).split("\n")
|
|
|
+ lines = []
|
|
|
+ for line in output:
|
|
|
+ if line and line != "\n":
|
|
|
+ lines.append(line.split(": ")[1])
|
|
|
+ formated = []
|
|
|
+ for line in lines:
|
|
|
+ if line and line != "\n":
|
|
|
+ formated += re.findall(r'[\da-fA-F]{8}', line)
|
|
|
+ if decimal:
|
|
|
+ return [int(x,16) for x in formated]
|
|
|
+ else:
|
|
|
+ return formated
|
|
|
+
|
|
|
+
|
|
|
+def get_dec_from_bits(bits, msb=-1, lsb=-1):
|
|
|
+ rtrnValue = 0
|
|
|
+
|
|
|
+ if (msb < 0 or lsb < 0):
|
|
|
+ rtrnValue = int(bits)
|
|
|
+ elif (msb < lsb) or (msb > 31) or (lsb > 31):
|
|
|
+ log.info("Bit range for msb and lsb of get_dec_from_bits was wrong. Not truncating")
|
|
|
+ rtrnValue = int(bits, 2)
|
|
|
+ else:
|
|
|
+ chunk = ('{0:032b}'.format(int(bits, 2)))[31-msb:32-lsb]
|
|
|
+ rtrnValue = int(chunk, 2)
|
|
|
+
|
|
|
+ return rtrnValue
|
|
|
+
|
|
|
+
|
|
|
+def start_dma(dma='dma0r'):
|
|
|
+ log.info('Start DMA')
|
|
|
+ cmd = ['pci', '--start-dma', dma]
|
|
|
+ safe_call(cmd)
|
|
|
+ time.sleep(0.05)
|
|
|
+
|
|
|
+
|
|
|
+def stop_dma(dma='dma0r'):
|
|
|
+ log.info('Stop DMA')
|
|
|
+ cmd = ['pci', '--stop-dma', dma]
|
|
|
+ safe_call(cmd)
|
|
|
+ time.sleep(0.05)
|
|
|
+
|
|
|
+
|
|
|
+def data_reset():
|
|
|
+ log.info('Data reset')
|
|
|
+ write_pci('000003f5', hex_mask='7')
|
|
|
+ time.sleep(0.05)
|
|
|
+ write_pci('000003f0', hex_mask='7')
|
|
|
+ time.sleep(0.05)
|
|
|
+
|
|
|
+
|
|
|
+def flush_dma(dma='dma0'):
|
|
|
+ log.info('Flushing DMA Pipeline')
|
|
|
+ write_pci('03f0', hex_mask='CF0')
|
|
|
+ cmd = ['pci', '-r', dma, '-o', '/dev/null', '--multipacket']
|
|
|
+ safe_call(cmd)
|
|
|
+
|
|
|
+
|
|
|
+def is_active():
|
|
|
+ control = read_pci(1, '0x9040')[0]
|
|
|
+ control_bits = '{0:032b}'.format(int(control, 16))
|
|
|
+ return control_bits[22:26] == "1111"
|
|
|
+
|
|
|
+
|
|
|
+def start_pilot_bunch_emulator():
|
|
|
+ log.info('Start pilot bunch emulator')
|
|
|
+ write_pci('400003f0', hex_mask='400003F0')
|
|
|
+ time.sleep(0.05)
|
|
|
+ write_pci('03f0', hex_mask='CF0')
|
|
|
+
|
|
|
+
|
|
|
+def start_acquisition():
|
|
|
+ log.info('Start acquisition')
|
|
|
+ write_pci('1', '4', hex_mask='1')
|
|
|
+ time.sleep(0.05)
|
|
|
+ write_pci('00bf0', hex_mask='CF0')
|
|
|
+
|
|
|
+
|
|
|
+def stop_acquisition():
|
|
|
+ log.info('Stop acquisition')
|
|
|
+ write_pci('003f0', hex_mask='CF0')
|
|
|
+ time.sleep(0.05)
|
|
|
+
|
|
|
+
|
|
|
+def enable_transfer():
|
|
|
+ log.info('Enable data transfer')
|
|
|
+ write_pci('007f0', hex_mask='CF0')
|
|
|
+ time.sleep(0.05)
|
|
|
+
|
|
|
+
|
|
|
+def write_data(filename, dma='dma0'):
|
|
|
+ log.info('Write data to {}'.format(filename))
|
|
|
+ cmd = ['pci', '-r', dma, '-o', filename, '--multipacket']
|
|
|
+ safe_call(cmd)
|
|
|
+ write_pci('003f0', hex_mask='CF0')
|
|
|
+
|
|
|
+
|
|
|
+def run_status(script_path):
|
|
|
+ cmd = ['bash', script_path]
|
|
|
+ output = safe_call(cmd)
|
|
|
+ log.info(output)
|
|
|
+
|
|
|
+
|
|
|
+def acquire_data(filename, duration=1.0, simulate=False):
|
|
|
+ #start_dma() #Dont start dma when it is already active! (Causes data corruption in the driver)
|
|
|
+
|
|
|
+ if simulate:
|
|
|
+ start_pilot_bunch_emulator()
|
|
|
+
|
|
|
+ start_acquisition()
|
|
|
+ #time.sleep(duraition) #calculate the time instead
|
|
|
+ wait_for_revolutions()
|
|
|
+ stop_acquisition()
|
|
|
+ enable_transfer()
|
|
|
+ write_data(filename)
|
|
|
+ flush_dma()
|
|
|
+
|
|
|
+
|
|
|
+def wait_for_revolutions():
|
|
|
+ n = read_pci(1, '0x9020', decimal=True)[0] # Get the ammount of orbits to observe
|
|
|
+ spin_time_ns = 368 * n
|
|
|
+ wait_time_s = spin_time_ns / 1000.0 / 1000.0 / 1000.0 # Milli, Micro, Nano
|
|
|
+ time.sleep(wait_time_s * 1.4) # 40% Safety margin
|