From 854a99d01a9fa73575204ee632d2e2c90a5d392f Mon Sep 17 00:00:00 2001 From: Autumn Bauman Date: Tue, 21 Jun 2022 13:50:45 -0400 Subject: [PATCH 01/78] Driver file --- astropix.py | 429 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 429 insertions(+) create mode 100644 astropix.py diff --git a/astropix.py b/astropix.py new file mode 100644 index 00000000..8bf0876d --- /dev/null +++ b/astropix.py @@ -0,0 +1,429 @@ +""" +Central module of astropix. This incorporates all of the various modules from the original +The class methods of all the other modules are inherited here. + +Author: Autumn Bauman +""" +# Needed modules. They all import their own suppourt libraries, +# and eventually there will be a list of which ones are needed to run +from typing import Dict +# from sqlalchemy import true +from modules.spi import Spi +from modules.nexysio import Nexysio +from modules.decode import Decode +from modules.injectionboard import Injectionboard +from modules.voltageboard import Voltageboard +from bitstring import BitArray +from tqdm import tqdm +import regex as re +import binascii +import time + +# Logging stuff +import logging +from modules.setup_logger import logger +logger = logging.getLogger(__name__) + + + +# Here are the default configuration values. +# This includes the DAC configurations, default registers, etc... + + + +class astropix2: + # First the global defaults which will be used later + DACS_CFG = { + 'blres': 0, + 'nu1': 0, + 'vn1': 20, + 'vnfb': 1, + 'vnfoll': 10, + 'nu5': 0, + 'nu6': 0, + 'nu7': 0, + 'nu8': 0, + 'vn2': 0, + 'vnfoll2': 1, + 'vnbias': 0, + 'vpload': 5, + 'nu13': 0, + 'vncomp': 2, + 'vpfoll': 60, + 'nu16': 0, + 'vprec': 30, + 'vnrec': 30 + } + + BIAS_CFG = { + 'DisHiDR': 0, + 'q01': 0, + 'qon0': 0, + 'qon1': 1, + 'qon2': 0, + 'qon3': 1, + } + + + # Init just opens the chip and gets the handle. After this runs + # asic_config also needs to be called to set it up. Seperating these + # allows for simpler specifying of values. + def __init__(self, clock_period_ns = 10, inject:bool = False): + # _asic_start tracks if the inital configuration has been run on the ASIC yet. + # By not handeling this in the init it simplifies the function, making it simpler + # to put in custom configurations and allows for less writing to the chip, + # only doing it once at init or when settings need to be changed as opposed to + # each time a parameter is changed. + self._asic_start = False + self.nexys = Nexysio() + self.handle = self.nexys.autoopen() + self._wait_progress(2) + # Ensure it is working + print("Opened FPGA, testing...") + self._test_io() + print("Test successful.") + # Start putting the variables in for use down the line + self.sampleclock_period_ns = clock_period_ns + # Creates objects used later on + self.decode = Decode(clock_period_ns) + + +##################### ASIC METHODS FOR USERS ######################### + + # Method to initalize the asic. This is taking the place of asic.py. + # All of the interfacing is handeled through asic_update + def asic_init(self, dac_setup: dict = None, bias_setup:dict = None, digital_mask:str = None): + # Now that the asic has been initalized we can go and make this true + self._asic_start = True + # The use of update methods on the dictionairy allows for only the keys that + # need changing to be passed to the function (hopefully) simplifying the interface + self.dac_setup = self.DACS_CFG + if dac_setup is not None: + self.dac_setup.update(dac_setup) + self.bias_setup = self.BIAS_CFG + if bias_setup is not None: + self.bias_setup.update(bias_setup) + + if digital_mask is not None: + self._make_digital_mask(digital_mask) + else: self._make_analog_mask() + + self._make_digitalconfig() + self._make_reconfig() + # Loads it to the chip + print("LOADING TO ASIC...") + self.asic_update() + print("ASIC SUCCESSFULLY CONFIGURED") + + # The method to write data to the asic. Called whenever somthing is changed + # or after a group of changes are done. Taken straight from asic.py. + # Might need updating down the line but it shoudl still work + + def asic_update(self): + """Update ASIC""" + + # Not needed for v2 + # dummybits = self.gen_asic_pattern(BitArray(uint=0, length=245), True) + # Write config + asicbits = self.nexys.gen_asic_pattern(self._construct_asic_vector(), True) + self.nexys.write(asicbits) + + + # Methods to update the internal variables. Please don't do it manually + # This updates the dac config + def update_dac(self, dac_config:dict, update_now: bool = True): + if self._asic_start: + self.dac_setup.update(dac_config) + # This will automatically load the new configuration if needed + if update_now: self.asic_update() + else: raise Exception("asic_init must first be called!") + + def update_bias(self, bias_cfg:dict, update_now: bool = True): + if self._asic_start: + self.bias_setup.update(bias_cfg) + else: raise Exception("asic_init must first be called!") + + # This functiion is how binary masks are applied to + + + def enable_spi(self): + self.nexys.spi_enable() + self.nexys.spi_reset() + # Set SPI clockdivider + # freq = 100 MHz/spi_clkdiv + self.nexys.spi_clkdiv = 255 + + # This section is here in case it is needed, but I doubt it so + # it will stay commented out unless needed + + #asic.dacs['vn1'] = 5 + """ + # Generate bitvector for SPI ASIC config + asic_bitvector = self._construct_asic_vector() + spi_data = self.nexys.asic_spi_vector(asic_bitvector, True, 10) + + # Write Config via spi + # nexys.write_spi(spi_data, False, 8191) + """ + + self.nexys.send_routing_cmd() + + print("SPI ENABLED") + + +################## Voltageboard Methods ############################ + +# Here we intitalize the 8 DAC voltageboard in slot 4. dacvals are carried over from past +# scripts. Default from beam_test.py: +# Use this: (8, [0, 0, 1.1, 1, 0, 0, 1, 1.035]) + def init_voltages(self, slot: int, vcal:float, vsupply: float, vthreshold:float, dacvals: tuple[int, list[float]] = (8, [0, 0, 1.1, 1, 0, 0, 1, 1.4])): + # used to ensure this has been called in the right order: + self._voltages_exist = True + + if vthreshold is not None: + if vthreshold > 1.7: raise Exception("Threshold voltage out of range!") + dacvals[1][-1] = vthreshold + # Create object + self.vboard = Voltageboard(self.handle, slot, dacvals) + # Set calibrated values + self.vboard.vcal = vcal + self.vboard.vsupply = vsupply + # Send config to the chip + self.vboard.update_vb() + + # Here we have the stuff to run injection + + # defaults for the arguments: + # position: 3 + # dac_settings: (2, [0.4, 0.0]) + # Settings from the orininal scripts + """ + inj.period = 100 + inj.clkdiv = 400 + inj.initdelay = 10000 + inj.cycle = 0 + inj.pulsesperset = 1 + """ + # Setup Injections + def init_injection(self, dac_settings:tuple[int, list[float]] = (2, [0.4, 0.0]), position: int = 3, inj_period:int = 100, clkdiv:int = 400, initdelay: int = 10000, cycle: float = 0, pulseperset: int = 1): + # Some fault tolerance + try: + self._voltages_exist + except: + raise Exception("init_voltages must be called first!") + + # Create the object! + self.inj_volts = Voltageboard(self.handle, position, dac_settings) + # set the parameters + self.inj_volts.vcal = self.vboard.vcal + self.inj_volts.vsupply = self.vboard.vsupply + self.inj_volts.update_vb() + # Now to configure the actual injection thing + self.injector = Injectionboard(self.handle) + # Now to configure it. above are the values from the original scripting. + self.injector.period = inj_period + self.injector.clkdiv = clkdiv + self.injector.initdelay = initdelay + self.injector.cycle = cycle + self.injector.pulsesperset = pulseperset + + # These start and stop injecting voltage. Fairly simple. + def start_injection(self): + self.injector.start() + print("BEGAN INJECTION") + + def stop_injection(self): + self.injector.stop() + print("STOPPED INJECTION") + + +########################### Input and Output ############################# + # This method checks the chip to see if a hit has been logged + + def hits_present(self): + if (int.from_bytes(self.nexys.read_register(70),"big") == 0): + return True + else: + return False + + +############################ Decoder Stuffs ############################## + # This function generates a list of the hits in the stream. Retuerns a bytearray + + def get_readout(self, return_hex: bool = False): + self.nexys.write_spi_bytes(20) + readout = self.nexys.read_spi_fifo() + if return_hex: + return binascii.hexlify(readout) + else: + return readout + + + def decode_readout(self, readout, printer: bool = False): + list_hits = self.decode.hits_from_readoutstream(readout) + hit_list = [] + for hit in list_hits: + # Generates the values from the bitstream + id = int(hit[0]) >> 3 + payload = int(hit[0]) & 0b111 + location = int(hit[1]) & 0b111111 + col = 1 if (int(hit[1]) >> 7 ) & 1 else 0 + timestamp = int(hit[2]) + tot_msb = int(hit[3]) & 0b1111 + tot_lsb = int(hit[4]) + tot_total = (tot_msb << 8) + tot_lsb + + wrong_id = 0 if (id) == 0 else '\x1b[0;31;40m{}\x1b[0m'.format(id) + wrong_payload = 4 if (payload) == 4 else'\x1b[0;31;40m{}\x1b[0m'.format(payload) + + # will give terminal output if desiered + if printer: + print( + f"Header: ChipId: {wrong_id}\tPayload: {wrong_payload}\t" + f"Location: {location}\tRow/Col: {'Col' if col else 'Row'}\t" + f"Timestamp: {timestamp}\t" + f"ToT: MSB: {tot_msb}\tLSB: {tot_lsb} Total: {tot_total} ({(tot_total * self.sampleclock_period_ns)/1000.0} us)" + ) + # hits are sored in dictionairy form + hits = { + 'Chip ID': wrong_id, + 'payload': wrong_payload, + 'location': location, + 'row/col': ('Col' if col else 'Row'), + 'timestamp': timestamp, + 'tot_msb': tot_msb, + 'tot_lsb': tot_lsb, + 'tot_total': tot_total, + 'tot_ns': ((tot_total * self.sampleclock_period_ns)/1000.0) + } + hit_list.append(hits) + return hit_list + + # To be called when initalizing the asic, clears the FPGAs memory + def dump_fpga(self, decode = False, printer = False): + readout = self.get_readout() + if decode: + return self.decode_readout(readout, printer) + + + + + + + +###################### INTERNAL METHODS ########################### + +# Below here are internal methods used for constructing things and testing + + # _test_io(): A function to read and write a register on the chip to see if + # everythign is working. + # It takes no arguments + + def _test_io(self): + try: # Attempts to write to and read from a register + self.nexys.write_register(0x09, 0x55, True) + self.nexys.read_register(0x09) + self.nexys.spi_reset() + self.nexys.sr_readback_reset() + except: + raise Exception("Could not read or write from astropix!") + + # _make_digitalconfig(): Constructs the digitalconfig dictionairy. + # Takes no arguments currently, and there is no way to update + # self.digitalconfig (yet). Those might be added down the line + + def _make_digitalconfig(self): + # This can probably be replaced with a dictionairy comprehension. + # I put this into for loops so we can use the range function which + # makes it a lot easier to see whats going on + self.digitalconfig = {'interupt_pushpull': 1} + for i in range(1,19): + self.digitalconfig[f"En_Inj{i}"] = 0 + self.digitalconfig["ResetB"] = 0 + for i in range(0,8): + self.digitalconfig[f'Extrabit{i}'] = 1 + for i in range(8,15): + self.digitalconfig[f'Extrabit{i}'] = 0 + + # Function to construct the reconfig dictionairy. This code is taken from + # asic.py. + # This simply sets it up for an analog run + def _make_analog_mask(self): + if self.inject: + bitconfig_col = 0b111_11111_11111_11111_11111_11111_11111_11101 #for injection + else: + bitconfig_col = 0b111_11111_11111_11111_11111_11111_11111_11100 #for noise + self.recconfig = {'ColConfig0': bitconfig_col} + i = 1 + while i < 35: + self.recconfig[f'ColConfig{i}'] = 0b001_11111_11111_11111_11111_11111_11111_11110 + i += 1 + + # used for digital working with the sensor. + + def _make_digital_mask(self, digitmask:str): + # Cleans up the string, ensures it is only 1, 0, or \n + bitmask = re.sub("[^01\n]", "", digitmask) + # turn it into a list + bitlist = bitmask.split("\n") + # Remove any extra rows that creeped in + if len(bitlist) > 35: + bitlist = bitlist[0:35] + # The dictionairy which is returned + self.recconfig = {} + # used in construction + i = 0 + # itterates through the list and does binairy magic on it + for bits in bitlist: + # This works by adding entries to a dictionairy and then: + # 1) creating a 35 bit space of zeros + # 2) converting the string to a binairy integer + # 3) shifting by one to make room for injection on/off bit + # 4) setting the injection bit if we want injection on this run + + self.recconfig[f"ColConfig{i}"] = (((0b00 << 35) + int(bits, 2)) << 1) + (0b1 if self.inject == True else 0) + i += 1 + + + + + # This is from asic.py, and it essentially takes all the parameters and puts + # them into a form ready to be loaded onto the board. + # Parameters: msbfirst: Send vector MSB first + + def _construct_asic_vector(self, msbfirst:bool = False): + bitvector = BitArray() + + for value in self.digitalconfig.values(): + bitvector.append(self.__int2nbit(value, 1)) + + for value in self.bias_setup.values(): + bitvector.append(self.__int2nbit(value, 1)) + + for value in self.dac_setup.values(): + bitvector.append(self.__int2nbit(value, 6)) + + for value in self.recconfig.values(): + bitvector.append(self.__int2nbit(value, 38)) + + if not msbfirst: + bitvector.reverse() + + # print(f'Bitvector: {bitvector} \n') + + return bitvector + def __int2nbit(self,value: int, nbits: int) -> BitArray: + """Convert int to 6bit bitarray + + :param value: DAC value 0-63 + """ + + try: + return BitArray(uint=value, length=nbits) + except ValueError: + print(f'Allowed Values 0 - {2**nbits-1}') + + # A progress bar! So facny I know + def _wait_progress(seconds:int): + for _ in tqdm(range(seconds), desc=f'Wait {seconds} s'): + time.sleep(1) From 6ee4e4fb994284dedb6ebc8a6334abe9d555dbe4 Mon Sep 17 00:00:00 2001 From: Autumn Bauman Date: Tue, 21 Jun 2022 13:52:41 -0400 Subject: [PATCH 02/78] Updated readme.md --- README.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/README.md b/README.md index 806cb750..1e6746e1 100644 --- a/README.md +++ b/README.md @@ -69,3 +69,57 @@ Create links to shared lib: ### Mac See [FTDI Mac OS X Installation Guide](https://www.ftdichip.com/Support/Documents/InstallGuides/Mac_OS_X_Installation_Guide.pdf) D2XX Driver section from page 10. + +# How to use the astropix2 module +Astropix-py is a module with the goal of simplifying and unifying all of the diffrent branches and modulles into a single module which can be easily worked with. +The goal is to provide a simple interface where astropix can be configured, initalized, monitored, and iterfaced with without having to modify source files or copy and paste code from various repositories. + +Although we aim to maintain compatibility with older branches, that will not be possible in all cases (for example the asic.py module). When this happens the original files will be preserved to maintain backwards compatibility and directions and information for moving over to the new interface. + +## Directions for use: +Must go in this order!! + +1. Creating the instance + - After import, call astropix2(). + - Usage: `astropix2([no required], clock_period_ns: int, inject: bool)` + - optional arguments: + - clock_period_ns, default 10 + - inject, default `False`. When true configures the pixels to accept an injection voltage + +2. Initalizing the ASIC + - call `astro.asic_init()` + - Usage: `astro.asic_init([no required], dac_setup: dict, bias_setup: dict, digital_mask: str)` + - Optional arguments: + - dac_setup: dictionairy of values which will be used to change the defalt dac settings. Does not need to have a complete dictionairy, only values that you want to change. Default None + - bias_setup: dictionairy of values which will be used to change the defalt bias settings. Does not need to have a complete dictionairy, only values that you want to change. Default None + - digital_mask: text data of 1s and 0s in a 35x35 grid (newline seperated rows) specifying what pixels are on and off. If not specified chip will be in analog mode +3. initializing voltages + - call `astro.init_voltages(slot, vcal, vsupply, vthreshold, [optional] dacvals)` + - slot: Usually 4, tells chip where the board is + - vcal: calibrated voltage. Usually 0.989 + - vsupply: voltage to gecco board, usually 2.7 + - vthreshold: ToT threshold voltage. Usually 1.075 ish + - optional, dacvals: if you want to configure the dac values, do that here +4. initalizing injector board (optional) + - call `astro.init_injection()` + - Has following options and defaults: + - dac_settings:tuple[int, list[float]] = (2, [0.4, 0.0]) + - position: int = 3, position in board, same as slot in init_voltages(). + - inj_period:int = 100 + - clkdiv:int = 400 + - initdelay: int = 10000 + - cycle: float = 0 + - pulseperset: int = 1 +5. enable SPI + - `astro.enable_spi()` + - takes no arguments + +Useful methods: + +astro.hits_present() --> bool. Are thre any hits on the board currently? + +astro.get_readout() --> bytearray. Gets bytestream from the chip + +astro.decode_readout(readout, [opt] printer) --> list of dictionairies. printer prints the decoded values to terminal + +astro.start_injection() and astro.stop_injection() are self explainatory From fe3d27338663400b552dce6bcbae3731961e6e6e Mon Sep 17 00:00:00 2001 From: Autumn Bauman Date: Fri, 24 Jun 2022 14:50:06 -0400 Subject: [PATCH 03/78] Added ability to reset sensor remotely --- modules/nexysio.py | 19 +++++++++++++++++++ sensor_reset.py | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 sensor_reset.py diff --git a/modules/nexysio.py b/modules/nexysio.py index 3aaa22cc..ad1a9601 100644 --- a/modules/nexysio.py +++ b/modules/nexysio.py @@ -11,6 +11,7 @@ import logging import binascii +import time from modules.spi import Spi from modules.setup_logger import logger @@ -333,3 +334,21 @@ def gen_asic_pattern(self, value: bytearray, wload: bool, clkdiv: int = 8, readb # concatenate header+data return b''.join([header, data]) + + + + def get_configregister(self): + return int.from_bytes(self.read_register(0), 'big') + def chip_reset(self) -> None: + """ + Reset SPI + Set res_n to 0 for 1s + res_n is connected to FTDI Reg0: Bit: 4 + """ + # Set Reset bits 1 + configregister = self.set_bit(self.get_configregister(), 4) + self.write_register(0, configregister, True) + time.sleep(1) + # Set Reset bits and readback bit 0 + configregister = self.clear_bit(self.get_configregister(), 4) + self.write_register(0, configregister, True) diff --git a/sensor_reset.py b/sensor_reset.py new file mode 100644 index 00000000..992810d9 --- /dev/null +++ b/sensor_reset.py @@ -0,0 +1,18 @@ +""" +Code to cleanly power cycle sensor remotely + +Driver code: Autumn +Nexys code: Nicolas +""" + +from modules.nexysio import Nexysio + +# This is the driver code to do this + +nexys = Nexysio() +handle = nexys.autoopen() + +nexys.chip_reset() + +# This may or may not work, or it could cause issues. I will comment it out if needed. +nexys.close() \ No newline at end of file From 5dfe04b34c9dfb68f43732a7aef097187ebb49d4 Mon Sep 17 00:00:00 2001 From: Autumn Bauman Date: Tue, 5 Jul 2022 10:45:54 -0400 Subject: [PATCH 04/78] Added digital mask method and a new beam test. --- beam_test_new.py | 78 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 beam_test_new.py diff --git a/beam_test_new.py b/beam_test_new.py new file mode 100644 index 00000000..173e5032 --- /dev/null +++ b/beam_test_new.py @@ -0,0 +1,78 @@ +""" +Updated version of beam_test.py using the astropix.py module +""" + +from astropix import astropix2 +import datetime +import time +import csv + +filedir = "./data/" + +filebase = "test_on_" + +inj_runup = 10 + +max_errors = 1 # limit of how many decde index errors are allowed in a row + +mask_file = "./testdat/mask.txt" + +#Init stuffs +def main(inj:bool = False, ToT:float = 100): + astro = astropix2(inj) + astro.asic_init() + astro.init_voltages(4, .908, 2.7, 1.1) + #if inj: astro.init_injection() + astro.enable_spi() + + print("CONFIGURATION SUCCESSFUL") + + + filepath = filedir + filebase + datetime.strftime('%Y.%m.%d-%h:%m:%s') + '.csv' + csv_header = [ + 'timestamp', + 'global_time' + 'Chip ID', + 'payload', + 'location', + 'row/col', + 'tot_total', + 'tot_ns', + 'hitbits' + ] + + with open(filepath, 'w') as file: + #file.write("globaltime, timestamp, chip_time, location, rowcol, tot_total, tot_time\n") + writer = csv.DictWriter(file, csv_header, extrasaction='ignore') + writer.writeheader() + astro.start_injection() + astro.dump_fpga() + + + + errors = 0 # Sets + i = 0 + try: + while (errors < max_errors): + if astro.hits_present(): + time.sleep(.5) + readout = astro.get_readout() + try: + hits = astro.decode_readout(printer = True) + except IndexError: + errors += 1 + continue + # This gives time since epoch to help standardize data across all systems + timenow = time.time() + # Here we itterate over the list of hits and add the timestamp to the dictionairies + for i in range(len(hits) - 1): + hits[i]['global_time'] = timenow + # Takes the list of hits and itterativey writes them all to the csv output + writer.writerows(hits) + i += 1 + else: time.sleep(.5) + except KeyboardInterrupt: + astro.close() + +if __name__ == "__main__": + main() \ No newline at end of file From b4e6d1f3e27b19ef056f0db479562e5f1f735620 Mon Sep 17 00:00:00 2001 From: Autumn Bauman Date: Tue, 5 Jul 2022 10:46:12 -0400 Subject: [PATCH 05/78] digital mask update method --- astropix.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/astropix.py b/astropix.py index 8bf0876d..c096c338 100644 --- a/astropix.py +++ b/astropix.py @@ -143,9 +143,13 @@ def update_bias(self, bias_cfg:dict, update_now: bool = True): self.bias_setup.update(bias_cfg) else: raise Exception("asic_init must first be called!") - # This functiion is how binary masks are applied to - - + # This makes a new digital mask and rewrites the configuration to the asic + def update_mask_digit(self, maskstr: str): + if self._asic_start: + self._make_digital_mask(maskstr) + self.asic_update() + else: raise Exception("asic_init must first be called!") + def enable_spi(self): self.nexys.spi_enable() self.nexys.spi_reset() @@ -170,6 +174,9 @@ def enable_spi(self): print("SPI ENABLED") + def close_connection(self): + self.nexys.close() + ################## Voltageboard Methods ############################ @@ -294,7 +301,8 @@ def decode_readout(self, readout, printer: bool = False): 'tot_msb': tot_msb, 'tot_lsb': tot_lsb, 'tot_total': tot_total, - 'tot_ns': ((tot_total * self.sampleclock_period_ns)/1000.0) + 'tot_ns': ((tot_total * self.sampleclock_period_ns)/1000.0), + 'hit_bits': hit } hit_list.append(hits) return hit_list From 32cf72fff775905cc51db900c9e7740da8cec715 Mon Sep 17 00:00:00 2001 From: Autumn Bauman Date: Wed, 6 Jul 2022 13:06:30 -0400 Subject: [PATCH 06/78] Completed beam_test.py --- astropix.py | 218 +++++++++++++++++++++++-------- beam_test.py | 296 ++++++++++++++++++++++-------------------- beam_test_new.py | 78 ----------- beam_test_old.py | 157 ++++++++++++++++++++++ modules/hitplotter.py | 82 ++++++++++++ 5 files changed, 558 insertions(+), 273 deletions(-) delete mode 100644 beam_test_new.py create mode 100644 beam_test_old.py create mode 100644 modules/hitplotter.py diff --git a/astropix.py b/astropix.py index c096c338..a970c527 100644 --- a/astropix.py +++ b/astropix.py @@ -7,7 +7,6 @@ # Needed modules. They all import their own suppourt libraries, # and eventually there will be a list of which ones are needed to run from typing import Dict -# from sqlalchemy import true from modules.spi import Spi from modules.nexysio import Nexysio from modules.decode import Decode @@ -15,6 +14,7 @@ from modules.voltageboard import Voltageboard from bitstring import BitArray from tqdm import tqdm +import pandas as pd import regex as re import binascii import time @@ -69,6 +69,14 @@ class astropix2: # asic_config also needs to be called to set it up. Seperating these # allows for simpler specifying of values. def __init__(self, clock_period_ns = 10, inject:bool = False): + """ + Initalizes astropix object. + No required arguments + Optional: + clock_period_ns:int - period of main clock in ns + inject:bool - if set to True will enable injection for the whole array. + """ + # _asic_start tracks if the inital configuration has been run on the ASIC yet. # By not handeling this in the init it simplifies the function, making it simpler # to put in custom configurations and allows for less writing to the chip, @@ -79,11 +87,12 @@ def __init__(self, clock_period_ns = 10, inject:bool = False): self.handle = self.nexys.autoopen() self._wait_progress(2) # Ensure it is working - print("Opened FPGA, testing...") + logger.info("Opened FPGA, testing...") self._test_io() - print("Test successful.") + logger.info("FPGA test successful.") # Start putting the variables in for use down the line self.sampleclock_period_ns = clock_period_ns + self.inject = inject # Creates objects used later on self.decode = Decode(clock_period_ns) @@ -93,6 +102,16 @@ def __init__(self, clock_period_ns = 10, inject:bool = False): # Method to initalize the asic. This is taking the place of asic.py. # All of the interfacing is handeled through asic_update def asic_init(self, dac_setup: dict = None, bias_setup:dict = None, digital_mask:str = None): + """ + self.asic_init() - initalize the asic configuration. Must be called first + Positional arguments: None + Optional: + dac_setup: dict - dictionairy of values passed to the configuration. Only needs values diffent from defaults + bias_setup: dict - dict of values for the bias configuration Only needs key/vals for changes from default + digital_mask: str - String of 1s and 0s in 35x35 arangement which masks the array. Needed to enable pixels not (0,0) + """ + + # Now that the asic has been initalized we can go and make this true self._asic_start = True # The use of update methods on the dictionairy allows for only the keys that @@ -111,46 +130,53 @@ def asic_init(self, dac_setup: dict = None, bias_setup:dict = None, digital_mask self._make_digitalconfig() self._make_reconfig() # Loads it to the chip - print("LOADING TO ASIC...") + logger.info("LOADING TO ASIC...") self.asic_update() - print("ASIC SUCCESSFULLY CONFIGURED") + logger.info("ASIC SUCCESSFULLY CONFIGURED") + # The method to write data to the asic. Called whenever somthing is changed # or after a group of changes are done. Taken straight from asic.py. - # Might need updating down the line but it shoudl still work - + # Might need updating down the line but it should still work def asic_update(self): - """Update ASIC""" + """ + Remakes configbits and writes to asic. + Takes no input and does not return + """ - # Not needed for v2 - # dummybits = self.gen_asic_pattern(BitArray(uint=0, length=245), True) - # Write config asicbits = self.nexys.gen_asic_pattern(self._construct_asic_vector(), True) self.nexys.write(asicbits) # Methods to update the internal variables. Please don't do it manually # This updates the dac config - def update_dac(self, dac_config:dict, update_now: bool = True): - if self._asic_start: - self.dac_setup.update(dac_config) - # This will automatically load the new configuration if needed - if update_now: self.asic_update() - else: raise Exception("asic_init must first be called!") - - def update_bias(self, bias_cfg:dict, update_now: bool = True): - if self._asic_start: - self.bias_setup.update(bias_cfg) - else: raise Exception("asic_init must first be called!") - - # This makes a new digital mask and rewrites the configuration to the asic - def update_mask_digit(self, maskstr: str): - if self._asic_start: - self._make_digital_mask(maskstr) - self.asic_update() - else: raise Exception("asic_init must first be called!") + def update_asic_config(self, bias_cfg:dict = None, dac_cfg:dict = None, maskstr:str = None): + """ + Updates and writes confgbits to asic + + bias_cfg:dict - Updates the bias settings. Only needs key/value pairs which need updated + dac_cfg:dict - Updates DAC settings. Only needs key/value pairs which need updated + """ + if self.asic_start: + if bias_cfg is not None: + self.bias_setup.update(bias_cfg) + if dac_cfg is not None: + self.dac_setup.update(dac_cfg) + if maskstr is not None: + self._make_digital_mask(maskstr) + else: logger.info("Nothing to do") + + self.asic_update() + else: raise RuntimeError("Asic has not been initalized") + def enable_spi(self): + """ + Starts spi bus. + + Takes no arguments, returns nothing + """ + self.nexys.spi_enable() self.nexys.spi_reset() # Set SPI clockdivider @@ -172,9 +198,12 @@ def enable_spi(self): self.nexys.send_routing_cmd() - print("SPI ENABLED") + logger.info("SPI ENABLED") def close_connection(self): + """ + Terminates the spi bus + """ self.nexys.close() @@ -183,12 +212,27 @@ def close_connection(self): # Here we intitalize the 8 DAC voltageboard in slot 4. dacvals are carried over from past # scripts. Default from beam_test.py: # Use this: (8, [0, 0, 1.1, 1, 0, 0, 1, 1.035]) - def init_voltages(self, slot: int, vcal:float, vsupply: float, vthreshold:float, dacvals: tuple[int, list[float]] = (8, [0, 0, 1.1, 1, 0, 0, 1, 1.4])): + def init_voltages(self, slot: int = 4, vcal:float = .908, vsupply: float = 2.7, vthreshold:float = None, dacvals: tuple[int, list[float]] = None): + """ + Configures the voltage board + No required parameters. No return. + + slot:int = 4 - Position of voltage board + vcal:float = 0.908 - Calibration of the voltage rails + vsupply = 2.7 - Supply Voltage + vthreshold:float = None - ToT threshold value. Takes precedence over dacvals if set + dacvals:tuple[int, list[float] - vboard dac settings. Must be fully specified if set. + """ + default_vdac = (8, [0, 0, 1.1, 1, 0, 0, 1, 1.075]) + # used to ensure this has been called in the right order: self._voltages_exist = True - - if vthreshold is not None: - if vthreshold > 1.7: raise Exception("Threshold voltage out of range!") + # Set dacvals + if dacvals is None: + dacvals = default_vdac + # dacvals takes precidence over vthreshold + elif vthreshold is not None: + if vthreshold > 1.5 or vthreshold < 0: logging.warning("Threshold voltage out of range of sensor!") dacvals[1][-1] = vthreshold # Create object self.vboard = Voltageboard(self.handle, slot, dacvals) @@ -212,15 +256,37 @@ def init_voltages(self, slot: int, vcal:float, vsupply: float, vthreshold:float, inj.pulsesperset = 1 """ # Setup Injections - def init_injection(self, dac_settings:tuple[int, list[float]] = (2, [0.4, 0.0]), position: int = 3, inj_period:int = 100, clkdiv:int = 400, initdelay: int = 10000, cycle: float = 0, pulseperset: int = 1): + def init_injection(self, slot: int = 3, inj_voltage:float = None, inj_period:int = 100, clkdiv:int = 400, initdelay: int = 10000, cycle: float = 0, pulseperset: int = 1, dac_config:tuple[int, list[float]] = (2, [0.4, 0.0])): + """ + Configure injections + No required arguments. No returns. + Optional Arguments: + slot: int - Location of the injection module + inj_voltage: float - Injection Voltage. Range from 0 to 1.8. If dac_config is set inj_voltage will be overwritten + inj_period: int + clkdiv: int + initdelay: int + cycle: float + pulseperset: int + dac_config:tuple[int, list[float]]: injdac settings. Must be fully specified if set. + """ + default_injdac = (2, [0.4, 0.0]) # Some fault tolerance try: self._voltages_exist except: - raise Exception("init_voltages must be called first!") + raise RuntimeError("init_voltages must be called first!") + # Sets the dac_setup if it isn't specified + if dac_config is None: + dac_settings = default_injdac + else: + dac_settings = dac_config + # The dac_config takes presedence + if inj_voltage is not None and dac_config is None: + dac_settings[1][1] = inj_voltage # Create the object! - self.inj_volts = Voltageboard(self.handle, position, dac_settings) + self.inj_volts = Voltageboard(self.handle, slot, dac_settings) # set the parameters self.inj_volts.vcal = self.vboard.vcal self.inj_volts.vsupply = self.vboard.vsupply @@ -236,37 +302,71 @@ def init_injection(self, dac_settings:tuple[int, list[float]] = (2, [0.4, 0.0]), # These start and stop injecting voltage. Fairly simple. def start_injection(self): + """ + Starts Injection. + Takes no arguments and no return + """ self.injector.start() - print("BEGAN INJECTION") + logging.info("BEGAN INJECTION") def stop_injection(self): + """ + Stops Injection. + Takes no arguments and no return + """ self.injector.stop() - print("STOPPED INJECTION") + logging.info("STOPPED INJECTION") ########################### Input and Output ############################# # This method checks the chip to see if a hit has been logged def hits_present(self): + """ + Looks at interupt + Returns bool, True if present + """ if (int.from_bytes(self.nexys.read_register(70),"big") == 0): return True else: return False + def get_log_header(self): + """ + Returns header for use in a log file with all settings. + """ + # This is not a nice line, but its the most efficent way to get all the values in the same place. + return f"Voltageboard settings: {self.vboard.dacvalues}\n" + f"Digital: {self.digitalconfig}\n" +f"Biasblock: {self.bias_setup}\n" + f"DAC: {self.dac_setup}\n" + f"Receiver: {self.recconfig}\n" + ############################ Decoder Stuffs ############################## # This function generates a list of the hits in the stream. Retuerns a bytearray - def get_readout(self, return_hex: bool = False): + def get_readout(self): + """ + Reads hit buffer. + No arguments. + Returns bytearray + """ self.nexys.write_spi_bytes(20) readout = self.nexys.read_spi_fifo() - if return_hex: - return binascii.hexlify(readout) - else: - return readout + return readout - def decode_readout(self, readout, printer: bool = False): + def decode_readout(self, readout:bytearray, i:int, printer: bool = False): + """ + Decodes readout + + Required argument: + readout: Bytearray - readout from sensor, not the printed Hex values + i: int - Readout number + + Optional: + printer: bool - Print decoded output to terminal + + Returns dataframe + """ + list_hits = self.decode.hits_from_readoutstream(readout) hit_list = [] for hit in list_hits: @@ -286,32 +386,42 @@ def decode_readout(self, readout, printer: bool = False): # will give terminal output if desiered if printer: print( - f"Header: ChipId: {wrong_id}\tPayload: {wrong_payload}\t" + f"{i} Header: ChipId: {wrong_id}\tPayload: {wrong_payload}\t" f"Location: {location}\tRow/Col: {'Col' if col else 'Row'}\t" f"Timestamp: {timestamp}\t" f"ToT: MSB: {tot_msb}\tLSB: {tot_lsb} Total: {tot_total} ({(tot_total * self.sampleclock_period_ns)/1000.0} us)" ) # hits are sored in dictionairy form + # Look into dataframe hits = { - 'Chip ID': wrong_id, - 'payload': wrong_payload, + 'readout': i, + 'Chip ID': id, + 'payload': payload, 'location': location, 'row/col': ('Col' if col else 'Row'), 'timestamp': timestamp, 'tot_msb': tot_msb, 'tot_lsb': tot_lsb, 'tot_total': tot_total, - 'tot_ns': ((tot_total * self.sampleclock_period_ns)/1000.0), - 'hit_bits': hit + 'tot_us': ((tot_total * self.sampleclock_period_ns)/1000.0), + 'hit_bits': hit, + 'hittime': time.time() } hit_list.append(hits) - return hit_list + + # Much simpler to convert to df in the return statement vs df.concat + return pd.DataFrame(hit_list) # To be called when initalizing the asic, clears the FPGAs memory - def dump_fpga(self, decode = False, printer = False): + def dump_fpga(self): + """ + Reads out hit buffer and disposes of the output. + + Does not return or take arguments. + """ readout = self.get_readout() - if decode: - return self.decode_readout(readout, printer) + del readout + diff --git a/beam_test.py b/beam_test.py index fabbd8fd..a54b570d 100644 --- a/beam_test.py +++ b/beam_test.py @@ -1,157 +1,171 @@ -# -*- coding: utf-8 -*- -"""""" """ -Created on Sat Jun 26 00:10:56 2021 +Updated version of beam_test.py using the astropix.py module -@author: Nicolas Striebig +Author: Autumn Bauman """ -from modules.asic import Asic -from modules.injectionboard import Injectionboard -from modules.nexysio import Nexysio -from modules.voltageboard import Voltageboard -from modules.decode import Decode - -from utils.utils import wait_progress - +#from msilib.schema import File +from astropix import astropix2 +import modules.hitplotter as hitplotter import binascii - -import os +import datetime +import pandas as pd +import numpy as np import time - -def main(): - - nexys = Nexysio() - - # Open FTDI Device with Index 0 - #handle = nexys.open(0) - handle = nexys.autoopen() - - # Write and read directly to register - # Example: Write 0x55 to register 0x09 and read it back - nexys.write_register(0x09, 0x55, True) - nexys.read_register(0x09) - - nexys.spi_reset() - nexys.sr_readback_reset() - - # - # Configure ASIC - # - - # Write to asicSR - asic = Asic(handle) - asic.update_asic() - - # Example: Update Config Bit - # asic.digitalconfig['En_Inj17'] = 1 - # asic.dacs['vn1'] = 63 - # asic.update_asic() - - # - # Configure Voltageboard - # - - # Configure 8 DAC Voltageboard in Slot 4 with list values - # 3 = Vcasc2, 4=BL, 7=Vminuspix, 8=Thpix - vboard1 = Voltageboard(handle, 4, (8, [0, 0, 1.1, 1, 0, 0, 1, 1.1])) - - # Set measured 1V for one-point calibration - vboard1.vcal = 0.989 - vboard1.vsupply = 3.3 - - # Update voltageboards - vboard1.update_vb() - - # Write only first 3 DACs, other DACs will be 0 - # vboard1.dacvalues = (8, [1.2, 1, 1]) - # vboard1.update_vb() - - # - # Configure Injectionboard - # - - # Set Injection level - injvoltage = Voltageboard(handle, 3, (2, [0.4, 0.0])) - injvoltage.vcal = vboard1.vcal - injvoltage.vsupply = vboard1.vsupply - injvoltage.update_vb() - - inj = Injectionboard(handle) - - # Set Injection Params for 330MHz patgen clock - inj.period = 100 - inj.clkdiv = 400 - inj.initdelay = 10000 - inj.cycle = 0 - inj.pulsesperset = 1 - - # - # SPI - # - - # Enable SPI - nexys.spi_enable() - nexys.spi_reset() - - # Set SPI clockdivider - # freq = 100 MHz/spi_clkdiv - nexys.spi_clkdiv = 255 - - #asic.dacs['vn1'] = 5 - - # Generate bitvector for SPI ASIC config - asic_bitvector = asic.gen_asic_vector() - spi_data = nexys.asic_spi_vector(asic_bitvector, True, 10) - - # Write Config via spi - # nexys.write_spi(spi_data, False, 8191) - - # Send Routing command - nexys.send_routing_cmd() - - # Reset SPI Read FIFO - - #inj.start() - inj.stop() - - wait_progress(3) - - #decode = Decode() - +import logging +import argparse + +from modules.setup_logger import logger +logger = logging.getLogger(__name__) + + +#Init stuffs +def main(args): + + # Used for creating the mask + masked = False + if args.mask is not None: + masked = True + with open(args.mask, 'r') as file: + bitmask = file.read() + + # Prepare everything, create the object + astro = astropix2(inject=args.inject) + + # Passes mask if specified, else it creates an analog mask of (0,0) + if masked: + astro.asic_init(digital_mask=bitmask) + else: + astro.asic_init() + + astro.init_voltages(vthreshold=args.threshold) + # If injection is on initalize the board + if args.inject: + astro.init_injection(inj_voltage=args.vinj) + astro.enable_spi() + logger.info("Chip configured") + astro.dump_fpga() + + if args.inject: + astro.start_injection() + + max_errors = args.errormax + i = 0 + errors = 0 # Sets the threshold + + # Prepares the file paths + if args.saveascsv: # Here for csv + csvpath = args.outdir + args.name + '_' + datetime.datetime.strftime("%Y%m%d-%H%M%S") + '.csv' + # And here for the text files/logs + logpath = args.outdir + args.name + '_' + datetime.datetime.strftime("%Y%m%d-%H%M%S") + '.log' + + # textfiles are always saved so we open it up a + logfile = open(logpath,'w') + # Writes all the config information to the file + logfile.write(astro.get_log_header()) + + # Enables the hitplotter and uses logic on whether or not to save the images + if args.showhits: plotter = hitplotter.HitPlotter(35, outdir=args.outdir if args.plotsave else None) + + try: # By enclosing the main loop in try/except we are able to capture keyboard interupts cleanly + + while True: #This loop doesn't need to use any conditional logic as all the exit cases are easier to handle with + # This might be possible to do in the loop declaration, but its a lot easier to simply add in this logic + if args.maxhits is not None: + if i >= args.maxhits: break + + if astro.hits_present(): # Checks if hits are present + time.sleep(.1) # this is probably not needed, will ask Nick + readout = astro.get_readout() # Gets the bytearray from the chip + # Writes the hex version to hits + logfile.write(f"{i}\t{str(binascii.hexlify(readout))}\n") + # Added fault tolerance for decoding, the limits of which are set through arguments + try: + hits = astro.decode_readout(readout, i, printer = True) + except IndexError: + errors += 1 + logger.error(f"Decoding failed. Failure {errors} of {max_errors}") + # If it has errored out, this will exit the loop and program + if errors > max_errors: + logger.critical(f"Decoding failed {errors} times on an index error. Terminating Progam...") + break + + continue + # If we are saving a csv this will write it out. + if args.saveascsv: + # Since we need the header only on the first hit readout this opens it in write mode first with header set true + # and for all times after set false and append mode + hits.write_csv(csvpath, header=False if i!=0 else True, mode='a' if i != 0 else 'w') + + # This handels the hitplotting. Code by Henrike and Amanda + if args.showhits: + rows,columns=[],[] + if len(decList)>0:#safeguard against bad readouts without recorded decodable hits + #Isolate row and column information from array returned from decoder + decList=np.array(decList) + location = np.array(decList[:,0]) + rowOrCol = np.array(decList[:,1]) + rows = location[rowOrCol==0] + columns = location[rowOrCol==1] + plotter.plot_event( rows, columns, i) + # Increments the run counter + i += 1 + # If no hits are present this waits for some to accumulate + else: time.sleep(.1) + + + + # Ends program cleanly when a keyboard interupt is sent. + except KeyboardInterrupt: + logger.critical("Keyboard interupt. Program halt!") - """ i = 0 - while os.path.exists("log/sample%s.log" % i): - i += 1 + logfile.close() # Close open file + if args.inject: astro.stop_injection() #stops injection + astro.close() # Closes SPI + logger.info("Program terminated") + # END OF PROGRAM - file = open("log/sample%s.log" % i, "w") """ - timestr = time.strftime("beam_%Y%m%d-%H%M%S") - file = open("log/%s.log" % timestr, "w") - file.write(f"Voltageboard settings: {vboard1.dacvalues}\n") - file.write(f"Digital: {asic.digitalconfig}\n") - file.write(f"Biasblock: {asic.biasconfig}\n") - file.write(f"DAC: {asic.dacs}\n") - file.write(f"Receiver: {asic.recconfig}\n") - readout = bytearray() + - while True: - print("Reg: {}", int.from_bytes(nexys.read_register(70),"big")) - if(int.from_bytes(nexys.read_register(70),"big") == 0): - time.sleep(0.1) - nexys.write_spi_bytes(20) - readout = nexys.read_spi_fifo() - file.write(str(binascii.hexlify(readout))) +if __name__ == "__main__": - print(binascii.hexlify(readout)) + parser = argparse.ArgumentParser(description='Astropix Driver Code') + parser.add_argument('-n', '--name', default='', required=False, + help='Option to give extra name to output files upon running') - #decode.decode_astropix2_hits(decode.hits_from_readoutstream(readout)) + parser.add_argument('-o', '--outdir', default='.', required=False, + help='Output Directory') - # inj.stop() + parser.add_argument('-s', '--showhits', action='store_true', + default=False, required=False, + help='Display hits in real time during data taking') + + parser.add_argument('-p', '--plotsave', action='store_true', default=False, required=False, + help='Save plots as image files. DEFAULT FALSE') + + parser.add_argument('-c', '--saveascsv', action='store_true', + default=False, required=False, + help='save output files as CSV. If False, save as txt') + + parser.add_argument('-i', '--inject', action='store_true',default=False, + help = 'Toggle injection on and off. DEFAULT: OFF') - # Close connection - nexys.close() + parser.add_argument('-v','--vinj', action='store', default = 0.4, type=float, + help = 'Specify injection voltage. DEFAULT 0.4V') + parser.add_argument('-m', '--mask', action='store', required=False, type=str, default = None, + help = 'filepath to digital mask. Required to enable pixels not (0,0)') -if __name__ == "__main__": - main() + parser.add_argument('-t', '--threshold', type = float, action='store', default=1.075, + help = 'Threshold voltage for digital ToT (in V). DEFAULT 1.075V') + + parser.add_argument('-E', '--errormax', action='store', type=int, default='0', + help='Maximum index errors allowed during decoding. DEFAULT 0') + parser.add_argument('-M', '--maxruns', type=int, action='store', default=None, + help = 'Maximum number of readouts') + + parser.add_argument + args = parser.parse_args() + + main(args) \ No newline at end of file diff --git a/beam_test_new.py b/beam_test_new.py deleted file mode 100644 index 173e5032..00000000 --- a/beam_test_new.py +++ /dev/null @@ -1,78 +0,0 @@ -""" -Updated version of beam_test.py using the astropix.py module -""" - -from astropix import astropix2 -import datetime -import time -import csv - -filedir = "./data/" - -filebase = "test_on_" - -inj_runup = 10 - -max_errors = 1 # limit of how many decde index errors are allowed in a row - -mask_file = "./testdat/mask.txt" - -#Init stuffs -def main(inj:bool = False, ToT:float = 100): - astro = astropix2(inj) - astro.asic_init() - astro.init_voltages(4, .908, 2.7, 1.1) - #if inj: astro.init_injection() - astro.enable_spi() - - print("CONFIGURATION SUCCESSFUL") - - - filepath = filedir + filebase + datetime.strftime('%Y.%m.%d-%h:%m:%s') + '.csv' - csv_header = [ - 'timestamp', - 'global_time' - 'Chip ID', - 'payload', - 'location', - 'row/col', - 'tot_total', - 'tot_ns', - 'hitbits' - ] - - with open(filepath, 'w') as file: - #file.write("globaltime, timestamp, chip_time, location, rowcol, tot_total, tot_time\n") - writer = csv.DictWriter(file, csv_header, extrasaction='ignore') - writer.writeheader() - astro.start_injection() - astro.dump_fpga() - - - - errors = 0 # Sets - i = 0 - try: - while (errors < max_errors): - if astro.hits_present(): - time.sleep(.5) - readout = astro.get_readout() - try: - hits = astro.decode_readout(printer = True) - except IndexError: - errors += 1 - continue - # This gives time since epoch to help standardize data across all systems - timenow = time.time() - # Here we itterate over the list of hits and add the timestamp to the dictionairies - for i in range(len(hits) - 1): - hits[i]['global_time'] = timenow - # Takes the list of hits and itterativey writes them all to the csv output - writer.writerows(hits) - i += 1 - else: time.sleep(.5) - except KeyboardInterrupt: - astro.close() - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/beam_test_old.py b/beam_test_old.py new file mode 100644 index 00000000..fabbd8fd --- /dev/null +++ b/beam_test_old.py @@ -0,0 +1,157 @@ +# -*- coding: utf-8 -*- +"""""" +""" +Created on Sat Jun 26 00:10:56 2021 + +@author: Nicolas Striebig +""" + +from modules.asic import Asic +from modules.injectionboard import Injectionboard +from modules.nexysio import Nexysio +from modules.voltageboard import Voltageboard +from modules.decode import Decode + +from utils.utils import wait_progress + +import binascii + +import os +import time + +def main(): + + nexys = Nexysio() + + # Open FTDI Device with Index 0 + #handle = nexys.open(0) + handle = nexys.autoopen() + + # Write and read directly to register + # Example: Write 0x55 to register 0x09 and read it back + nexys.write_register(0x09, 0x55, True) + nexys.read_register(0x09) + + nexys.spi_reset() + nexys.sr_readback_reset() + + # + # Configure ASIC + # + + # Write to asicSR + asic = Asic(handle) + asic.update_asic() + + # Example: Update Config Bit + # asic.digitalconfig['En_Inj17'] = 1 + # asic.dacs['vn1'] = 63 + # asic.update_asic() + + # + # Configure Voltageboard + # + + # Configure 8 DAC Voltageboard in Slot 4 with list values + # 3 = Vcasc2, 4=BL, 7=Vminuspix, 8=Thpix + vboard1 = Voltageboard(handle, 4, (8, [0, 0, 1.1, 1, 0, 0, 1, 1.1])) + + # Set measured 1V for one-point calibration + vboard1.vcal = 0.989 + vboard1.vsupply = 3.3 + + # Update voltageboards + vboard1.update_vb() + + # Write only first 3 DACs, other DACs will be 0 + # vboard1.dacvalues = (8, [1.2, 1, 1]) + # vboard1.update_vb() + + # + # Configure Injectionboard + # + + # Set Injection level + injvoltage = Voltageboard(handle, 3, (2, [0.4, 0.0])) + injvoltage.vcal = vboard1.vcal + injvoltage.vsupply = vboard1.vsupply + injvoltage.update_vb() + + inj = Injectionboard(handle) + + # Set Injection Params for 330MHz patgen clock + inj.period = 100 + inj.clkdiv = 400 + inj.initdelay = 10000 + inj.cycle = 0 + inj.pulsesperset = 1 + + # + # SPI + # + + # Enable SPI + nexys.spi_enable() + nexys.spi_reset() + + # Set SPI clockdivider + # freq = 100 MHz/spi_clkdiv + nexys.spi_clkdiv = 255 + + #asic.dacs['vn1'] = 5 + + # Generate bitvector for SPI ASIC config + asic_bitvector = asic.gen_asic_vector() + spi_data = nexys.asic_spi_vector(asic_bitvector, True, 10) + + # Write Config via spi + # nexys.write_spi(spi_data, False, 8191) + + # Send Routing command + nexys.send_routing_cmd() + + # Reset SPI Read FIFO + + #inj.start() + inj.stop() + + wait_progress(3) + + #decode = Decode() + + + """ i = 0 + while os.path.exists("log/sample%s.log" % i): + i += 1 + + file = open("log/sample%s.log" % i, "w") """ + timestr = time.strftime("beam_%Y%m%d-%H%M%S") + file = open("log/%s.log" % timestr, "w") + file.write(f"Voltageboard settings: {vboard1.dacvalues}\n") + file.write(f"Digital: {asic.digitalconfig}\n") + file.write(f"Biasblock: {asic.biasconfig}\n") + file.write(f"DAC: {asic.dacs}\n") + file.write(f"Receiver: {asic.recconfig}\n") + + readout = bytearray() + + while True: + print("Reg: {}", int.from_bytes(nexys.read_register(70),"big")) + if(int.from_bytes(nexys.read_register(70),"big") == 0): + time.sleep(0.1) + nexys.write_spi_bytes(20) + readout = nexys.read_spi_fifo() + file.write(str(binascii.hexlify(readout))) + + print(binascii.hexlify(readout)) + + #decode.decode_astropix2_hits(decode.hits_from_readoutstream(readout)) + + # inj.stop() + + # Close connection + nexys.close() + + +if __name__ == "__main__": + main() diff --git a/modules/hitplotter.py b/modules/hitplotter.py new file mode 100644 index 00000000..29a01f53 --- /dev/null +++ b/modules/hitplotter.py @@ -0,0 +1,82 @@ +import matplotlib.pyplot as plt +import os + +class HitPlotter: + """ + Class for 2-D visualization of astroPix hits in real time. + """ + + def __init__(self, nPix = (40,40), d=0.5, outdir=None): + """ + Class for 2-D visualization of astroPix hits in real time. + + nPix: number of pixels in the array (int for square arrays or tuple) + d: Width of bars for strip visualization. + outdir: If not None, save problematic events as pdf images into this directory. + """ + + if isinstance(nPix, int): + self.nPix = (nPix, nPix) + else: + self.nPix = nPix + self.d = d + self.outdir = outdir + plt.gcf().set_size_inches(7,7, forward=True) + plt.axes().set_aspect('equal') + + if outdir is not None and not os.path.isdir(self.outdir): + os.makedirs(self.outdir) + + + def plot_event(self, row, col, eventID=None): + """ + Display the row/column hits for one astroPix event in real time. + In this 2-D visualization, one colored strip is plotted for + each row and column hit. + Strip colors correspond to the overall number of hits: + * Green for one row, one column. + * Red for more than two rows or columns. + * Orange otherwise. + + row: list or numpy.array of row hit locations + col: list of numpy.array of column hit locations + eventID: event number or timestamp (for plot title) + """ + + plt.clf() + ax = plt.gca() + + plt.axis([-1, self.nPix[1], -1, self.nPix[0]]) + + if (len(col) == 1) and (len(row) == 1): + theColor="green" + theSize = "x-large" + elif (len(col) > 2) or (len(row) > 2): + theColor="red" + theSize="x-small" + else: + theColor = "orange" + theSize = "small" + + for x in col: + plt.axvspan(x-self.d, x+self.d, alpha=0.4, facecolor=theColor, edgecolor="None" ) + + for y in row: + plt.axhspan(y-self.d, y+self.d, alpha=0.4, facecolor=theColor, edgecolor="None") + + + plt.xticks(col, weight = 'bold', color=theColor, size=theSize) + plt.yticks(row, weight = 'bold', color=theColor, size=theSize) + + title = f"Event {eventID}, {len(row)} + {len(col)} hits" + plt.title(title) + + plt.xlabel("Column") + plt.ylabel("Row") + plt.tight_layout() + plt.pause(1e-6) + + if self.outdir is not None and theColor in ["orange", "red"]: + plt.savefig(f"{self.outdir}/event_{eventID}.pdf") + + From 855d8cb8706c0965ae5c8427ac96c0e61d46c5eb Mon Sep 17 00:00:00 2001 From: Autumn Bauman Date: Wed, 6 Jul 2022 13:06:39 -0400 Subject: [PATCH 07/78] gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 3c7027ed..dd588e7a 100644 --- a/.gitignore +++ b/.gitignore @@ -139,3 +139,7 @@ cython_debug/ #pylint .pylint.d/ + + +new-sensor-test.py +astropy_validation.py \ No newline at end of file From 903c88954813fb5c48a7bb58316a5750a69d507e Mon Sep 17 00:00:00 2001 From: Autumn Bauman Date: Wed, 6 Jul 2022 14:11:49 -0400 Subject: [PATCH 08/78] Made hit visualisation work --- astropix.py | 2 +- beam_test.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/astropix.py b/astropix.py index a970c527..90612f6d 100644 --- a/astropix.py +++ b/astropix.py @@ -398,7 +398,7 @@ def decode_readout(self, readout:bytearray, i:int, printer: bool = False): 'Chip ID': id, 'payload': payload, 'location': location, - 'row/col': ('Col' if col else 'Row'), + 'rowcol': (1 if col else 0), 'timestamp': timestamp, 'tot_msb': tot_msb, 'tot_lsb': tot_lsb, diff --git a/beam_test.py b/beam_test.py index a54b570d..ac26664c 100644 --- a/beam_test.py +++ b/beam_test.py @@ -56,11 +56,13 @@ def main(args): # Prepares the file paths if args.saveascsv: # Here for csv csvpath = args.outdir + args.name + '_' + datetime.datetime.strftime("%Y%m%d-%H%M%S") + '.csv' + # And here for the text files/logs logpath = args.outdir + args.name + '_' + datetime.datetime.strftime("%Y%m%d-%H%M%S") + '.log' - # textfiles are always saved so we open it up a + # textfiles are always saved so we open it up logfile = open(logpath,'w') + # Writes all the config information to the file logfile.write(astro.get_log_header()) @@ -95,16 +97,19 @@ def main(args): if args.saveascsv: # Since we need the header only on the first hit readout this opens it in write mode first with header set true # and for all times after set false and append mode - hits.write_csv(csvpath, header=False if i!=0 else True, mode='a' if i != 0 else 'w') + hits.write_csv( + csvpath, + header=False if i!=0 else True, + mode='a' if i != 0 else 'w' + ) # This handels the hitplotting. Code by Henrike and Amanda if args.showhits: rows,columns=[],[] - if len(decList)>0:#safeguard against bad readouts without recorded decodable hits + if len(hits)>0:#safeguard against bad readouts without recorded decodable hits #Isolate row and column information from array returned from decoder - decList=np.array(decList) - location = np.array(decList[:,0]) - rowOrCol = np.array(decList[:,1]) + location = hits.location.to_numpy() + rowOrCol = hits.rowcol.to_numpy() rows = location[rowOrCol==0] columns = location[rowOrCol==1] plotter.plot_event( rows, columns, i) @@ -162,6 +167,7 @@ def main(args): parser.add_argument('-E', '--errormax', action='store', type=int, default='0', help='Maximum index errors allowed during decoding. DEFAULT 0') + parser.add_argument('-M', '--maxruns', type=int, action='store', default=None, help = 'Maximum number of readouts') From ebf9d5ca86e48f77598932dd0718f9b26442fdda Mon Sep 17 00:00:00 2001 From: Autumn Bauman Date: Thu, 7 Jul 2022 11:44:21 -0400 Subject: [PATCH 09/78] Added revisions from code review, fixed bugs --- astropix.py | 36 +++++++++++++++++++++++++----------- beam_test.py | 20 ++++++++++++-------- modules/setup_logger.py | 14 +++++++++++++- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/astropix.py b/astropix.py index 90612f6d..55a44d9d 100644 --- a/astropix.py +++ b/astropix.py @@ -146,6 +146,7 @@ def asic_update(self): asicbits = self.nexys.gen_asic_pattern(self._construct_asic_vector(), True) self.nexys.write(asicbits) + logger.info("Wrote configbits successfully") # Methods to update the internal variables. Please don't do it manually @@ -164,8 +165,9 @@ def update_asic_config(self, bias_cfg:dict = None, dac_cfg:dict = None, maskstr: self.dac_setup.update(dac_cfg) if maskstr is not None: self._make_digital_mask(maskstr) - else: logger.info("Nothing to do") - + else: + logger.info("update_asic_config() got no argumennts, nothing to do.") + return None self.asic_update() else: raise RuntimeError("Asic has not been initalized") @@ -220,10 +222,11 @@ def init_voltages(self, slot: int = 4, vcal:float = .908, vsupply: float = 2.7, slot:int = 4 - Position of voltage board vcal:float = 0.908 - Calibration of the voltage rails vsupply = 2.7 - Supply Voltage - vthreshold:float = None - ToT threshold value. Takes precedence over dacvals if set + vthreshold:float = None - ToT threshold value. Takes precedence over dacvals if set. UNITS: mV dacvals:tuple[int, list[float] - vboard dac settings. Must be fully specified if set. """ - default_vdac = (8, [0, 0, 1.1, 1, 0, 0, 1, 1.075]) + # The default values to pass to the voltage dac. Last value in list is threshold voltage, default 100mV or 1.1 + default_vdac = (8, [0, 0, 1.1, 1, 0, 0, 1, 1.100]) # used to ensure this has been called in the right order: self._voltages_exist = True @@ -231,8 +234,16 @@ def init_voltages(self, slot: int = 4, vcal:float = .908, vsupply: float = 2.7, if dacvals is None: dacvals = default_vdac # dacvals takes precidence over vthreshold + elif vthreshold is not None: - if vthreshold > 1.5 or vthreshold < 0: logging.warning("Threshold voltage out of range of sensor!") + # Turns from mV to V with the 1V offset normally present + vthreshold = (vthreshold/1000) + 1 + if vthreshold > 1.5 or vthreshold < 0: + logging.warning("Threshold voltage out of range of sensor!") + if vthreshold <= 0: + vthreshold = 1.100 + logging.error("Threshold value too low, setting to default 100mV") + dacvals[1][-1] = vthreshold # Create object self.vboard = Voltageboard(self.handle, slot, dacvals) @@ -256,7 +267,7 @@ def init_voltages(self, slot: int = 4, vcal:float = .908, vsupply: float = 2.7, inj.pulsesperset = 1 """ # Setup Injections - def init_injection(self, slot: int = 3, inj_voltage:float = None, inj_period:int = 100, clkdiv:int = 400, initdelay: int = 10000, cycle: float = 0, pulseperset: int = 1, dac_config:tuple[int, list[float]] = (2, [0.4, 0.0])): + def init_injection(self, slot: int = 3, inj_voltage:float = None, inj_period:int = 100, clkdiv:int = 400, initdelay: int = 10000, cycle: float = 0, pulseperset: int = 1, dac_config:tuple[int, list[float]] = None): """ Configure injections No required arguments. No returns. @@ -270,18 +281,21 @@ def init_injection(self, slot: int = 3, inj_voltage:float = None, inj_period:int pulseperset: int dac_config:tuple[int, list[float]]: injdac settings. Must be fully specified if set. """ - default_injdac = (2, [0.4, 0.0]) + # Default configuration for the dac + default_injdac = (2, [0.4, 0.3]) # Some fault tolerance try: self._voltages_exist except: - raise RuntimeError("init_voltages must be called first!") + raise RuntimeError("init_voltages must be called before init_injection!") + # Sets the dac_setup if it isn't specified if dac_config is None: dac_settings = default_injdac else: dac_settings = dac_config - # The dac_config takes presedence + # The dac_config takes presedence over a specified threshold. + # if inj_voltage is not None and dac_config is None: dac_settings[1][1] = inj_voltage @@ -323,7 +337,7 @@ def stop_injection(self): def hits_present(self): """ - Looks at interupt + Looks at interrupt Returns bool, True if present """ if (int.from_bytes(self.nexys.read_register(70),"big") == 0): @@ -404,7 +418,7 @@ def decode_readout(self, readout:bytearray, i:int, printer: bool = False): 'tot_lsb': tot_lsb, 'tot_total': tot_total, 'tot_us': ((tot_total * self.sampleclock_period_ns)/1000.0), - 'hit_bits': hit, + 'hit_bits': binascii.hexlify(hit), 'hittime': time.time() } hit_list.append(hits) diff --git a/beam_test.py b/beam_test.py index ac26664c..75c8bdf5 100644 --- a/beam_test.py +++ b/beam_test.py @@ -16,6 +16,9 @@ import argparse from modules.setup_logger import logger + + + logger = logging.getLogger(__name__) @@ -86,7 +89,7 @@ def main(args): hits = astro.decode_readout(readout, i, printer = True) except IndexError: errors += 1 - logger.error(f"Decoding failed. Failure {errors} of {max_errors}") + logger.error(f"Decoding failed. Failure {errors} of {max_errors} on readout {i}") # If it has errored out, this will exit the loop and program if errors > max_errors: logger.critical(f"Decoding failed {errors} times on an index error. Terminating Progam...") @@ -97,11 +100,12 @@ def main(args): if args.saveascsv: # Since we need the header only on the first hit readout this opens it in write mode first with header set true # and for all times after set false and append mode - hits.write_csv( - csvpath, - header=False if i!=0 else True, - mode='a' if i != 0 else 'w' - ) + with open(csvpath, 'a' if i != 0 else 'w') as csvfile: + hits.to_csv( + csvfile, + header=False if i!=0 else True + ) + csvfile.write('\n') # This handels the hitplotting. Code by Henrike and Amanda if args.showhits: @@ -162,8 +166,8 @@ def main(args): parser.add_argument('-m', '--mask', action='store', required=False, type=str, default = None, help = 'filepath to digital mask. Required to enable pixels not (0,0)') - parser.add_argument('-t', '--threshold', type = float, action='store', default=1.075, - help = 'Threshold voltage for digital ToT (in V). DEFAULT 1.075V') + parser.add_argument('-t', '--threshold', type = float, action='store', default=None, + help = 'Threshold voltage for digital ToT (in mV). DEFAULT 100mV') parser.add_argument('-E', '--errormax', action='store', type=int, default='0', help='Maximum index errors allowed during decoding. DEFAULT 0') diff --git a/modules/setup_logger.py b/modules/setup_logger.py index 8f561950..c592f888 100644 --- a/modules/setup_logger.py +++ b/modules/setup_logger.py @@ -7,6 +7,18 @@ """ import logging +import datetime + +# This sets the logger name. +logname = "./runlogs/AstropixRunlog_" + datetime.datetime.strftime("%Y%m%d-%H%M%S") + ".log" + +# +logging.basicConfig(filename=logname, + filemode='a', + format='%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s', + datefmt='%H:%M:%S', + level=logging.DEBUG) + + -logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) \ No newline at end of file From ad887133dc15cfc282ab844f1c941d0d6918b2ae Mon Sep 17 00:00:00 2001 From: Autumn Bauman Date: Fri, 8 Jul 2022 10:13:08 -0400 Subject: [PATCH 10/78] Sorted out injvoltage --- astropix.py | 12 ++++++++++-- beam_test.py | 6 +++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/astropix.py b/astropix.py index 55a44d9d..ceb202eb 100644 --- a/astropix.py +++ b/astropix.py @@ -282,7 +282,7 @@ def init_injection(self, slot: int = 3, inj_voltage:float = None, inj_period:int dac_config:tuple[int, list[float]]: injdac settings. Must be fully specified if set. """ # Default configuration for the dac - default_injdac = (2, [0.4, 0.3]) + default_injdac = (2, [0.3, 0.0]) # Some fault tolerance try: self._voltages_exist @@ -297,7 +297,15 @@ def init_injection(self, slot: int = 3, inj_voltage:float = None, inj_period:int # The dac_config takes presedence over a specified threshold. # if inj_voltage is not None and dac_config is None: - dac_settings[1][1] = inj_voltage + # elifs check to ensure we are not injecting a negative value because we don't have that ability + if inj_voltage < 0: + raise ValueError("Cannot inject a negative voltage!") + elif inj_voltage > 1800: + raise ValueError("Cannot inject more than 1800mV!") + else: + #Convert from mV to V + inj_voltage = inj_voltage / 1000 + dac_settings[1][0] = inj_voltage # Create the object! self.inj_volts = Voltageboard(self.handle, slot, dac_settings) diff --git a/beam_test.py b/beam_test.py index 75c8bdf5..3ec6e3af 100644 --- a/beam_test.py +++ b/beam_test.py @@ -144,7 +144,7 @@ def main(args): help='Option to give extra name to output files upon running') parser.add_argument('-o', '--outdir', default='.', required=False, - help='Output Directory') + help='Output Directory for all datafiles') parser.add_argument('-s', '--showhits', action='store_true', default=False, required=False, @@ -160,8 +160,8 @@ def main(args): parser.add_argument('-i', '--inject', action='store_true',default=False, help = 'Toggle injection on and off. DEFAULT: OFF') - parser.add_argument('-v','--vinj', action='store', default = 0.4, type=float, - help = 'Specify injection voltage. DEFAULT 0.4V') + parser.add_argument('-v','--vinj', action='store', default = 400, type=float, + help = 'Specify injection voltage (in mV). DEFAULT 400 mV') parser.add_argument('-m', '--mask', action='store', required=False, type=str, default = None, help = 'filepath to digital mask. Required to enable pixels not (0,0)') From d9eec0d0972f3265f3504f786b7ba6a47363129b Mon Sep 17 00:00:00 2001 From: Autumn Bauman Date: Thu, 14 Jul 2022 11:39:34 -0400 Subject: [PATCH 11/78] Final bug fixes --- astropix.py | 10 +++--- beam_test.py | 95 +++++++++++++++++++++++++++++++++++++++------------- test.py | 45 +++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 28 deletions(-) create mode 100644 test.py diff --git a/astropix.py b/astropix.py index ceb202eb..1b86a803 100644 --- a/astropix.py +++ b/astropix.py @@ -128,7 +128,7 @@ def asic_init(self, dac_setup: dict = None, bias_setup:dict = None, digital_mask else: self._make_analog_mask() self._make_digitalconfig() - self._make_reconfig() + self._make_digital_mask() # Loads it to the chip logger.info("LOADING TO ASIC...") self.asic_update() @@ -286,7 +286,7 @@ def init_injection(self, slot: int = 3, inj_voltage:float = None, inj_period:int # Some fault tolerance try: self._voltages_exist - except: + except Exception: raise RuntimeError("init_voltages must be called before init_injection!") # Sets the dac_setup if it isn't specified @@ -294,8 +294,8 @@ def init_injection(self, slot: int = 3, inj_voltage:float = None, inj_period:int dac_settings = default_injdac else: dac_settings = dac_config + # The dac_config takes presedence over a specified threshold. - # if inj_voltage is not None and dac_config is None: # elifs check to ensure we are not injecting a negative value because we don't have that ability if inj_voltage < 0: @@ -465,8 +465,8 @@ def _test_io(self): self.nexys.read_register(0x09) self.nexys.spi_reset() self.nexys.sr_readback_reset() - except: - raise Exception("Could not read or write from astropix!") + except Exception: + raise RuntimeError("Could not read or write from astropix!") # _make_digitalconfig(): Constructs the digitalconfig dictionairy. # Takes no arguments currently, and there is no way to update diff --git a/beam_test.py b/beam_test.py index 3ec6e3af..324a63fd 100644 --- a/beam_test.py +++ b/beam_test.py @@ -20,6 +20,24 @@ logger = logging.getLogger(__name__) + +# This is the dataframe which is written to the csv if the decoding fails +decode_fail_frame = pd.DataFrame({ + 'readout': np.nan, + 'Chip ID': np.nan, + 'payload': np.nan, + 'location': np.nan, + 'rowcol': np.nan, + 'timestamp': np.nan, + 'tot_msb': np.nan, + 'tot_lsb': np.nan, + 'tot_total': np.nan, + 'tot_us': np.nan, + 'hit_bits': np.nan, + 'hittime': np.nan + } +) + #Init stuffs @@ -52,6 +70,8 @@ def main(args): if args.inject: astro.start_injection() + + max_errors = args.errormax i = 0 errors = 0 # Sets the threshold @@ -59,13 +79,14 @@ def main(args): # Prepares the file paths if args.saveascsv: # Here for csv csvpath = args.outdir + args.name + '_' + datetime.datetime.strftime("%Y%m%d-%H%M%S") + '.csv' + # Opens the csv file + csvfile = open(csvpath, 'w') + # And here for the text files/logs logpath = args.outdir + args.name + '_' + datetime.datetime.strftime("%Y%m%d-%H%M%S") + '.log' - # textfiles are always saved so we open it up logfile = open(logpath,'w') - # Writes all the config information to the file logfile.write(astro.get_log_header()) @@ -74,33 +95,43 @@ def main(args): try: # By enclosing the main loop in try/except we are able to capture keyboard interupts cleanly - while True: #This loop doesn't need to use any conditional logic as all the exit cases are easier to handle with + while errors < max_errors: # Loop continues + # This might be possible to do in the loop declaration, but its a lot easier to simply add in this logic if args.maxhits is not None: if i >= args.maxhits: break if astro.hits_present(): # Checks if hits are present + # We aren't using timeit, just measuring the diffrence in ns + if args.timeit: start = time.time_ns() + time.sleep(.1) # this is probably not needed, will ask Nick + readout = astro.get_readout() # Gets the bytearray from the chip # Writes the hex version to hits logfile.write(f"{i}\t{str(binascii.hexlify(readout))}\n") + print(binascii.hexlify(readout)) # Added fault tolerance for decoding, the limits of which are set through arguments try: hits = astro.decode_readout(readout, i, printer = True) except IndexError: errors += 1 logger.error(f"Decoding failed. Failure {errors} of {max_errors} on readout {i}") - # If it has errored out, this will exit the loop and program + # We write out the failed decode dataframe + hits = decode_fail_frame + hits.readout = i + hits.hittime = time.time() + + # This loggs the end of it all if errors > max_errors: logger.critical(f"Decoding failed {errors} times on an index error. Terminating Progam...") - break - - continue + finally: i += 1 + + # If we are saving a csv this will write it out. if args.saveascsv: # Since we need the header only on the first hit readout this opens it in write mode first with header set true # and for all times after set false and append mode - with open(csvpath, 'a' if i != 0 else 'w') as csvfile: hits.to_csv( csvfile, header=False if i!=0 else True @@ -109,29 +140,39 @@ def main(args): # This handels the hitplotting. Code by Henrike and Amanda if args.showhits: - rows,columns=[],[] - if len(hits)>0:#safeguard against bad readouts without recorded decodable hits + # This ensures we aren't plotting NaN values. I don't know if this would break or not but better + # safe than sorry + if pd.isnull(hits.tot_msb.loc(0)): + pass + elif len(hits)>0:#safeguard against bad readouts without recorded decodable hits + rows,columns=[],[] #Isolate row and column information from array returned from decoder location = hits.location.to_numpy() rowOrCol = hits.rowcol.to_numpy() rows = location[rowOrCol==0] columns = location[rowOrCol==1] - plotter.plot_event( rows, columns, i) - # Increments the run counter - i += 1 + plotter.plot_event( rows, columns, i) + + # If we are logging runtime, this does it! + if args.timeit: + print(f"Read and decode took {(time.time_ns()-start)*10**-9}s") + # If no hits are present this waits for some to accumulate else: time.sleep(.1) - # Ends program cleanly when a keyboard interupt is sent. except KeyboardInterrupt: - logger.critical("Keyboard interupt. Program halt!") - - logfile.close() # Close open file - if args.inject: astro.stop_injection() #stops injection - astro.close() # Closes SPI - logger.info("Program terminated") + logger.info("Keyboard interupt. Program halt!") + # Catches other exceptions + except Exception as e: + logger.critical(f"Encountered Unexpected Exception! \n{e}") + finally: + logfile.close() # Close open file + if args.saveascsv: csvfile.close() + if args.inject: astro.stop_injection() #stops injection + astro.close() # Closes SPI + logger.info("Program terminated") # END OF PROGRAM @@ -141,7 +182,7 @@ def main(args): parser = argparse.ArgumentParser(description='Astropix Driver Code') parser.add_argument('-n', '--name', default='', required=False, - help='Option to give extra name to output files upon running') + help='Option to give additional name to output files upon running') parser.add_argument('-o', '--outdir', default='.', required=False, help='Output Directory for all datafiles') @@ -151,7 +192,7 @@ def main(args): help='Display hits in real time during data taking') parser.add_argument('-p', '--plotsave', action='store_true', default=False, required=False, - help='Save plots as image files. DEFAULT FALSE') + help='Save plots as image files. If set, will be saved in datadir DEFAULT FALSE') parser.add_argument('-c', '--saveascsv', action='store_true', default=False, required=False, @@ -174,7 +215,15 @@ def main(args): parser.add_argument('-M', '--maxruns', type=int, action='store', default=None, help = 'Maximum number of readouts') - + + parser.add_argument('--timeit', action="store_true", default=False, + help='Prints runtime from seeing a hit to finishing the decode to terminal') + """ + parser.add_argument('--ludicrous-speed', type=bool, action='store_true', default=False, + help="Fastest possible data collection. No decode, no output, no file.\ + Saves bitstreams in memory until keyboard interupt or other error and then writes them to file.\ + Use is not generally recommended") + """ parser.add_argument args = parser.parse_args() diff --git a/test.py b/test.py new file mode 100644 index 00000000..bd2e313d --- /dev/null +++ b/test.py @@ -0,0 +1,45 @@ +import argparse + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Astropix Driver Code') + parser.add_argument('-n', '--name', default='', required=False, + help='Option to give extra name to output files upon running') + + parser.add_argument('-o', '--outdir', default='.', required=False, + help='Output Directory for all datafiles') + + parser.add_argument('-s', '--showhits', action='store_true', + default=False, required=False, + help='Display hits in real time during data taking') + + parser.add_argument('-p', '--plotsave', action='store_true', default=False, required=False, + help='Save plots as image files. DEFAULT FALSE') + + parser.add_argument('-c', '--saveascsv', action='store_true', + default=False, required=False, + help='save output files as CSV. If False, save as txt') + + parser.add_argument('-i', '--inject', action='store_true',default=False, + help = 'Toggle injection on and off. DEFAULT: OFF') + + parser.add_argument('-v','--vinj', action='store', default = 400, type=float, + help = 'Specify injection voltage (in mV). DEFAULT 400 mV') + + parser.add_argument('-m', '--mask', action='store', required=False, type=str, default = None, + help = 'filepath to digital mask. Required to enable pixels not (0,0)') + + parser.add_argument('-t', '--threshold', type = float, action='store', default=None, + help = 'Threshold voltage for digital ToT (in mV). DEFAULT 100mV') + + parser.add_argument('-E', '--errormax', action='store', type=int, default='0', + help='Maximum index errors allowed during decoding. DEFAULT 0') + + parser.add_argument('-M', '--maxruns', type=int, action='store', default=None, + help = 'Maximum number of readouts') + + parser.add_argument('--timeit', action="store_true", default=False, + help='Prints runtime from seeing a hit to finishing the decode to terminal') + + args = parser.parse_args() + + print(args) \ No newline at end of file From d5dab182472d70c2baff6c032bb2207b055c87d3 Mon Sep 17 00:00:00 2001 From: Autumn Bauman Date: Thu, 14 Jul 2022 12:11:34 -0400 Subject: [PATCH 12/78] Fixed bugs and added loglevel setting from command --- astropix.py | 34 +++++++++++----------------------- beam_test.py | 32 ++++++++++++++++++++++++++++++-- modules/setup_logger.py | 6 +++--- 3 files changed, 44 insertions(+), 28 deletions(-) diff --git a/astropix.py b/astropix.py index 1b86a803..ef4b9ff8 100644 --- a/astropix.py +++ b/astropix.py @@ -184,27 +184,13 @@ def enable_spi(self): # Set SPI clockdivider # freq = 100 MHz/spi_clkdiv self.nexys.spi_clkdiv = 255 - - # This section is here in case it is needed, but I doubt it so - # it will stay commented out unless needed - - #asic.dacs['vn1'] = 5 - """ - # Generate bitvector for SPI ASIC config - asic_bitvector = self._construct_asic_vector() - spi_data = self.nexys.asic_spi_vector(asic_bitvector, True, 10) - - # Write Config via spi - # nexys.write_spi(spi_data, False, 8191) - """ - self.nexys.send_routing_cmd() - logger.info("SPI ENABLED") def close_connection(self): """ - Terminates the spi bus + Terminates the spi bus. + Takes no arguments. No returns. """ self.nexys.close() @@ -225,7 +211,9 @@ def init_voltages(self, slot: int = 4, vcal:float = .908, vsupply: float = 2.7, vthreshold:float = None - ToT threshold value. Takes precedence over dacvals if set. UNITS: mV dacvals:tuple[int, list[float] - vboard dac settings. Must be fully specified if set. """ - # The default values to pass to the voltage dac. Last value in list is threshold voltage, default 100mV or 1.1 + # The default values to pass to the voltage dac. Last value in list is threshold voltage, default 100mV or 1.1 + # From nicholas's beam_test.py: + # 3 = Vcasc2, 4=BL, 7=Vminuspix, 8=Thpix default_vdac = (8, [0, 0, 1.1, 1, 0, 0, 1, 1.100]) # used to ensure this has been called in the right order: @@ -239,10 +227,10 @@ def init_voltages(self, slot: int = 4, vcal:float = .908, vsupply: float = 2.7, # Turns from mV to V with the 1V offset normally present vthreshold = (vthreshold/1000) + 1 if vthreshold > 1.5 or vthreshold < 0: - logging.warning("Threshold voltage out of range of sensor!") + logger.warning("Threshold voltage out of range of sensor!") if vthreshold <= 0: vthreshold = 1.100 - logging.error("Threshold value too low, setting to default 100mV") + logger.error("Threshold value too low, setting to default 100mV") dacvals[1][-1] = vthreshold # Create object @@ -301,7 +289,7 @@ def init_injection(self, slot: int = 3, inj_voltage:float = None, inj_period:int if inj_voltage < 0: raise ValueError("Cannot inject a negative voltage!") elif inj_voltage > 1800: - raise ValueError("Cannot inject more than 1800mV!") + logger.warning("Cannot inject more than 1800mV!") else: #Convert from mV to V inj_voltage = inj_voltage / 1000 @@ -329,7 +317,7 @@ def start_injection(self): Takes no arguments and no return """ self.injector.start() - logging.info("BEGAN INJECTION") + logger.info("BEGAN INJECTION") def stop_injection(self): """ @@ -337,7 +325,7 @@ def stop_injection(self): Takes no arguments and no return """ self.injector.stop() - logging.info("STOPPED INJECTION") + logger.info("STOPPED INJECTION") ########################### Input and Output ############################# @@ -375,7 +363,7 @@ def get_readout(self): return readout - def decode_readout(self, readout:bytearray, i:int, printer: bool = False): + def decode_readout(self, readout:bytearray, i:int, printer: bool = True): """ Decodes readout diff --git a/beam_test.py b/beam_test.py index 324a63fd..5c157d44 100644 --- a/beam_test.py +++ b/beam_test.py @@ -5,6 +5,7 @@ """ #from msilib.schema import File +from http.client import SWITCHING_PROTOCOLS from astropix import astropix2 import modules.hitplotter as hitplotter import binascii @@ -18,8 +19,10 @@ from modules.setup_logger import logger +# This sets the logger name. +logname = "./runlogs/AstropixRunlog_" + datetime.datetime.strftime("%Y%m%d-%H%M%S") + ".log" + -logger = logging.getLogger(__name__) # This is the dataframe which is written to the csv if the decoding fails decode_fail_frame = pd.DataFrame({ @@ -166,7 +169,7 @@ def main(args): logger.info("Keyboard interupt. Program halt!") # Catches other exceptions except Exception as e: - logger.critical(f"Encountered Unexpected Exception! \n{e}") + logger.exception(f"Encountered Unexpected Exception! \n{e}") finally: logfile.close() # Close open file if args.saveascsv: csvfile.close() @@ -218,6 +221,9 @@ def main(args): parser.add_argument('--timeit', action="store_true", default=False, help='Prints runtime from seeing a hit to finishing the decode to terminal') + + parser.add_argument('-L', '--loglevel', type=str, choices = ['D', 'I', 'E', 'W', 'C'], action="store", default='I', + help='Set loglevel used. Options: D - debug, I - info, E - error, W - warning, C - critical. DEFAULT: D') """ parser.add_argument('--ludicrous-speed', type=bool, action='store_true', default=False, help="Fastest possible data collection. No decode, no output, no file.\ @@ -226,5 +232,27 @@ def main(args): """ parser.add_argument args = parser.parse_args() + + # Sets the loglevel + ll = args.loglevel + if ll == 'D': + loglevel = logging.DEBUG + elif ll == 'I': + loglevel = logging.INFO + elif ll == 'E': + loglevel = logging.ERROR + elif ll == 'W': + loglevel = logging.WARNING + elif ll == 'C': + loglevel = logging.CRITICAL + + # Loglevel + logging.basicConfig(filename=logname, + filemode='a', + format='%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s', + datefmt='%H:%M:%S', + level= loglevel) + + logger = logging.getLogger(__name__) main(args) \ No newline at end of file diff --git a/modules/setup_logger.py b/modules/setup_logger.py index c592f888..ecc9838d 100644 --- a/modules/setup_logger.py +++ b/modules/setup_logger.py @@ -11,14 +11,14 @@ # This sets the logger name. logname = "./runlogs/AstropixRunlog_" + datetime.datetime.strftime("%Y%m%d-%H%M%S") + ".log" - -# +""" +# Loglevel logging.basicConfig(filename=logname, filemode='a', format='%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s', datefmt='%H:%M:%S', level=logging.DEBUG) - +""" logger = logging.getLogger(__name__) \ No newline at end of file From befed0cbba94e65e3e70432300ceac8f994f1781 Mon Sep 17 00:00:00 2001 From: Autumn Bauman Date: Thu, 14 Jul 2022 13:58:35 -0400 Subject: [PATCH 13/78] Final changes to inital version --- astropix.py | 10 ++++++---- beam_test.py | 44 +++++++++++++++++++++++--------------------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/astropix.py b/astropix.py index ef4b9ff8..eb710c02 100644 --- a/astropix.py +++ b/astropix.py @@ -270,7 +270,9 @@ def init_injection(self, slot: int = 3, inj_voltage:float = None, inj_period:int dac_config:tuple[int, list[float]]: injdac settings. Must be fully specified if set. """ # Default configuration for the dac - default_injdac = (2, [0.3, 0.0]) + # 0.4 is injection voltage + # 2 is slot number for inj board + default_injdac = (2, [0.4, 0.0]) # Some fault tolerance try: self._voltages_exist @@ -352,13 +354,13 @@ def get_log_header(self): ############################ Decoder Stuffs ############################## # This function generates a list of the hits in the stream. Retuerns a bytearray - def get_readout(self): + def get_readout(self, bufferlength:int = 20): """ Reads hit buffer. - No arguments. + bufferlength:int - length of buffer to write. Multiplied by 8 to give number of bytes Returns bytearray """ - self.nexys.write_spi_bytes(20) + self.nexys.write_spi_bytes(bufferlength) readout = self.nexys.read_spi_fifo() return readout diff --git a/beam_test.py b/beam_test.py index 5c157d44..22c599f4 100644 --- a/beam_test.py +++ b/beam_test.py @@ -73,7 +73,6 @@ def main(args): if args.inject: astro.start_injection() - max_errors = args.errormax i = 0 @@ -82,9 +81,20 @@ def main(args): # Prepares the file paths if args.saveascsv: # Here for csv csvpath = args.outdir + args.name + '_' + datetime.datetime.strftime("%Y%m%d-%H%M%S") + '.csv' - # Opens the csv file - csvfile = open(csvpath, 'w') - + csvframe =pd.DataFrame(columns = [ + 'readout', + 'Chip ID', + 'payload', + 'location', + 'rowcol', + 'timestamp', + 'tot_msb', + 'tot_lsb', + 'tot_total', + 'tot_us', + 'hit_bits', + 'hittime' + ]) # And here for the text files/logs logpath = args.outdir + args.name + '_' + datetime.datetime.strftime("%Y%m%d-%H%M%S") + '.log' @@ -98,7 +108,7 @@ def main(args): try: # By enclosing the main loop in try/except we are able to capture keyboard interupts cleanly - while errors < max_errors: # Loop continues + while errors <= max_errors: # Loop continues # This might be possible to do in the loop declaration, but its a lot easier to simply add in this logic if args.maxhits is not None: @@ -108,7 +118,7 @@ def main(args): # We aren't using timeit, just measuring the diffrence in ns if args.timeit: start = time.time_ns() - time.sleep(.1) # this is probably not needed, will ask Nick + time.sleep(.1) # this is probably not needed, will ask Nicolas readout = astro.get_readout() # Gets the bytearray from the chip # Writes the hex version to hits @@ -119,7 +129,7 @@ def main(args): hits = astro.decode_readout(readout, i, printer = True) except IndexError: errors += 1 - logger.error(f"Decoding failed. Failure {errors} of {max_errors} on readout {i}") + logger.warning(f"Decoding failed. Failure {errors} of {max_errors} on readout {i}") # We write out the failed decode dataframe hits = decode_fail_frame hits.readout = i @@ -127,19 +137,12 @@ def main(args): # This loggs the end of it all if errors > max_errors: - logger.critical(f"Decoding failed {errors} times on an index error. Terminating Progam...") + logger.warning(f"Decoding failed {errors} times on an index error. Terminating Progam...") finally: i += 1 # If we are saving a csv this will write it out. - if args.saveascsv: - # Since we need the header only on the first hit readout this opens it in write mode first with header set true - # and for all times after set false and append mode - hits.to_csv( - csvfile, - header=False if i!=0 else True - ) - csvfile.write('\n') + csvframe = csvframe.concat(hits) # This handels the hitplotting. Code by Henrike and Amanda if args.showhits: @@ -161,7 +164,7 @@ def main(args): print(f"Read and decode took {(time.time_ns()-start)*10**-9}s") # If no hits are present this waits for some to accumulate - else: time.sleep(.1) + else: time.sleep(.001) # Ends program cleanly when a keyboard interupt is sent. @@ -171,9 +174,8 @@ def main(args): except Exception as e: logger.exception(f"Encountered Unexpected Exception! \n{e}") finally: - logfile.close() # Close open file - if args.saveascsv: csvfile.close() - if args.inject: astro.stop_injection() #stops injection + if args.saveascsv: hits.to_csv(csvpath) + logfile.close() # Close open file if args.inject: astro.stop_injection() #stops injection astro.close() # Closes SPI logger.info("Program terminated") # END OF PROGRAM @@ -195,7 +197,7 @@ def main(args): help='Display hits in real time during data taking') parser.add_argument('-p', '--plotsave', action='store_true', default=False, required=False, - help='Save plots as image files. If set, will be saved in datadir DEFAULT FALSE') + help='Save plots as image files. If set, will be saved in same dir as data. DEFAULT FALSE') parser.add_argument('-c', '--saveascsv', action='store_true', default=False, required=False, From 1cdfeaa7441f6ff1a81432fac524345544fd828d Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Thu, 14 Jul 2022 14:55:56 -0400 Subject: [PATCH 14/78] Bug fixes from start work --- astropix.py | 12 ++++++------ beam_test.py | 25 +++++++++++++------------ modules/setup_logger.py | 2 +- testdat/test20220714-145109.csv | 3 +++ testdat/test20220714-145238.csv | 3 +++ 5 files changed, 26 insertions(+), 19 deletions(-) create mode 100644 testdat/test20220714-145109.csv create mode 100644 testdat/test20220714-145238.csv diff --git a/astropix.py b/astropix.py index eb710c02..cf66349a 100644 --- a/astropix.py +++ b/astropix.py @@ -128,7 +128,7 @@ def asic_init(self, dac_setup: dict = None, bias_setup:dict = None, digital_mask else: self._make_analog_mask() self._make_digitalconfig() - self._make_digital_mask() + #self._make_digital_mask() # Loads it to the chip logger.info("LOADING TO ASIC...") self.asic_update() @@ -200,7 +200,7 @@ def close_connection(self): # Here we intitalize the 8 DAC voltageboard in slot 4. dacvals are carried over from past # scripts. Default from beam_test.py: # Use this: (8, [0, 0, 1.1, 1, 0, 0, 1, 1.035]) - def init_voltages(self, slot: int = 4, vcal:float = .908, vsupply: float = 2.7, vthreshold:float = None, dacvals: tuple[int, list[float]] = None): + def init_voltages(self, slot: int = 4, vcal:float = .989, vsupply: float = 2.7, vthreshold:float = None, dacvals: tuple[int, list[float]] = None): """ Configures the voltage board No required parameters. No return. @@ -255,7 +255,7 @@ def init_voltages(self, slot: int = 4, vcal:float = .908, vsupply: float = 2.7, inj.pulsesperset = 1 """ # Setup Injections - def init_injection(self, slot: int = 3, inj_voltage:float = None, inj_period:int = 100, clkdiv:int = 400, initdelay: int = 10000, cycle: float = 0, pulseperset: int = 1, dac_config:tuple[int, list[float]] = None): + def init_injection(self, slot: int = 3, inj_voltage:float = None, inj_period:int = 100, clkdiv:int = 300, initdelay: int = 100, cycle: float = 0, pulseperset: int = 1, dac_config:tuple[int, list[float]] = None): """ Configure injections No required arguments. No returns. @@ -272,7 +272,7 @@ def init_injection(self, slot: int = 3, inj_voltage:float = None, inj_period:int # Default configuration for the dac # 0.4 is injection voltage # 2 is slot number for inj board - default_injdac = (2, [0.4, 0.0]) + default_injdac = (2, [0.3, 0.0]) # Some fault tolerance try: self._voltages_exist @@ -286,7 +286,7 @@ def init_injection(self, slot: int = 3, inj_voltage:float = None, inj_period:int dac_settings = dac_config # The dac_config takes presedence over a specified threshold. - if inj_voltage is not None and dac_config is None: + if (inj_voltage is not None) and (dac_config is None): # elifs check to ensure we are not injecting a negative value because we don't have that ability if inj_voltage < 0: raise ValueError("Cannot inject a negative voltage!") @@ -554,6 +554,6 @@ def __int2nbit(self,value: int, nbits: int) -> BitArray: print(f'Allowed Values 0 - {2**nbits-1}') # A progress bar! So facny I know - def _wait_progress(seconds:int): + def _wait_progress(self, seconds:int): for _ in tqdm(range(seconds), desc=f'Wait {seconds} s'): time.sleep(1) diff --git a/beam_test.py b/beam_test.py index 22c599f4..ad9c9f62 100644 --- a/beam_test.py +++ b/beam_test.py @@ -20,7 +20,7 @@ # This sets the logger name. -logname = "./runlogs/AstropixRunlog_" + datetime.datetime.strftime("%Y%m%d-%H%M%S") + ".log" +#logname = "./runlogs/AstropixRunlog_" + datetime.now().strftime("%Y%m%d-%H%M%S") + ".log" @@ -38,7 +38,7 @@ 'tot_us': np.nan, 'hit_bits': np.nan, 'hittime': np.nan - } + }, index=[0] ) @@ -80,7 +80,7 @@ def main(args): # Prepares the file paths if args.saveascsv: # Here for csv - csvpath = args.outdir + args.name + '_' + datetime.datetime.strftime("%Y%m%d-%H%M%S") + '.csv' + csvpath = args.outdir +'/' + args.name + time.strftime("%Y%m%d-%H%M%S") + '.csv' csvframe =pd.DataFrame(columns = [ 'readout', 'Chip ID', @@ -97,7 +97,7 @@ def main(args): ]) # And here for the text files/logs - logpath = args.outdir + args.name + '_' + datetime.datetime.strftime("%Y%m%d-%H%M%S") + '.log' + logpath = args.outdir + '/' + args.name + time.strftime("%Y%m%d-%H%M%S") + '.log' # textfiles are always saved so we open it up logfile = open(logpath,'w') # Writes all the config information to the file @@ -111,8 +111,8 @@ def main(args): while errors <= max_errors: # Loop continues # This might be possible to do in the loop declaration, but its a lot easier to simply add in this logic - if args.maxhits is not None: - if i >= args.maxhits: break + if args.maxruns is not None: + if i >= args.maxruns: break if astro.hits_present(): # Checks if hits are present # We aren't using timeit, just measuring the diffrence in ns @@ -142,7 +142,8 @@ def main(args): # If we are saving a csv this will write it out. - csvframe = csvframe.concat(hits) + if args.saveascsv: + csvframe = pd.concat([csvframe, hits]) # This handels the hitplotting. Code by Henrike and Amanda if args.showhits: @@ -174,9 +175,10 @@ def main(args): except Exception as e: logger.exception(f"Encountered Unexpected Exception! \n{e}") finally: - if args.saveascsv: hits.to_csv(csvpath) + if args.saveascsv: hits.to_csv(csvpath) + if args.inject: astro.stop_injection() logfile.close() # Close open file if args.inject: astro.stop_injection() #stops injection - astro.close() # Closes SPI + astro.close_connection() # Closes SPI logger.info("Program terminated") # END OF PROGRAM @@ -206,7 +208,7 @@ def main(args): parser.add_argument('-i', '--inject', action='store_true',default=False, help = 'Toggle injection on and off. DEFAULT: OFF') - parser.add_argument('-v','--vinj', action='store', default = 400, type=float, + parser.add_argument('-v','--vinj', action='store', default = None, type=float, help = 'Specify injection voltage (in mV). DEFAULT 400 mV') parser.add_argument('-m', '--mask', action='store', required=False, type=str, default = None, @@ -249,8 +251,7 @@ def main(args): loglevel = logging.CRITICAL # Loglevel - logging.basicConfig(filename=logname, - filemode='a', + logging.basicConfig( format='%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s', datefmt='%H:%M:%S', level= loglevel) diff --git a/modules/setup_logger.py b/modules/setup_logger.py index ecc9838d..6ae34880 100644 --- a/modules/setup_logger.py +++ b/modules/setup_logger.py @@ -10,7 +10,7 @@ import datetime # This sets the logger name. -logname = "./runlogs/AstropixRunlog_" + datetime.datetime.strftime("%Y%m%d-%H%M%S") + ".log" +# logname = "./runlogs/AstropixRunlog_" + datetime.datetime.strftime("%Y%m%d-%H%M%S") + ".log" """ # Loglevel logging.basicConfig(filename=logname, diff --git a/testdat/test20220714-145109.csv b/testdat/test20220714-145109.csv new file mode 100644 index 00000000..ab1146c4 --- /dev/null +++ b/testdat/test20220714-145109.csv @@ -0,0 +1,3 @@ +,readout,Chip ID,payload,location,rowcol,timestamp,tot_msb,tot_lsb,tot_total,tot_us,hit_bits,hittime +0,0,0,4,0,0,63,15,141,3981,39.81,b'04003f0f8d',1657824670.1956813 +1,0,0,4,0,1,63,15,123,3963,39.63,b'04803f0f7b',1657824670.1966784 diff --git a/testdat/test20220714-145238.csv b/testdat/test20220714-145238.csv new file mode 100644 index 00000000..b6aa8ac3 --- /dev/null +++ b/testdat/test20220714-145238.csv @@ -0,0 +1,3 @@ +,readout,Chip ID,payload,location,rowcol,timestamp,tot_msb,tot_lsb,tot_total,tot_us,hit_bits,hittime +0,7,0,4,0,0,158,11,83,2899,28.99,b'04009e0b53',1657824765.2150674 +1,7,0,4,0,1,158,11,6,2822,28.22,b'04809e0b06',1657824765.216064 From c786c67eec6bc98b63aea97621a35f1c9885519a Mon Sep 17 00:00:00 2001 From: Autumn Bauman Date: Thu, 14 Jul 2022 17:23:22 -0400 Subject: [PATCH 15/78] fixed logging --- .gitignore | 7 ++++++- beam_test.py | 8 ++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index dd588e7a..a30bc629 100644 --- a/.gitignore +++ b/.gitignore @@ -142,4 +142,9 @@ cython_debug/ new-sensor-test.py -astropy_validation.py \ No newline at end of file +astropy_validation.py +test.py +testdat.py +beam_test_inj.py +beam_test_old.py +modules/asic.py \ No newline at end of file diff --git a/beam_test.py b/beam_test.py index ad9c9f62..1f4d4366 100644 --- a/beam_test.py +++ b/beam_test.py @@ -5,11 +5,10 @@ """ #from msilib.schema import File -from http.client import SWITCHING_PROTOCOLS +#from http.client import SWITCHING_PROTOCOLS from astropix import astropix2 import modules.hitplotter as hitplotter import binascii -import datetime import pandas as pd import numpy as np import time @@ -20,7 +19,7 @@ # This sets the logger name. -#logname = "./runlogs/AstropixRunlog_" + datetime.now().strftime("%Y%m%d-%H%M%S") + ".log" +logname = "./runlogs/AstropixRunlog_" + time.strftime("%Y%m%d-%H%M%S") + ".log" @@ -251,7 +250,8 @@ def main(args): loglevel = logging.CRITICAL # Loglevel - logging.basicConfig( + logging.basicConfig(filename=logname, + filemode='w' format='%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s', datefmt='%H:%M:%S', level= loglevel) From bec6757d6a2c179dfdc238b2d3773995f215288a Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Fri, 15 Jul 2022 10:49:50 -0400 Subject: [PATCH 16/78] More debugged with minor changes to csv output --- astropix.py | 5 ++- beam_test.py | 65 ++++++++++++++++-------------- testdat/testing20220715-101935.csv | 3 ++ testdat/testing20220715-102205.csv | 3 ++ testdat/testing20220715-102317.csv | 50 +++++++++++++++++++++++ 5 files changed, 94 insertions(+), 32 deletions(-) create mode 100644 testdat/testing20220715-101935.csv create mode 100644 testdat/testing20220715-102205.csv create mode 100644 testdat/testing20220715-102317.csv diff --git a/astropix.py b/astropix.py index cf66349a..52206532 100644 --- a/astropix.py +++ b/astropix.py @@ -393,7 +393,9 @@ def decode_readout(self, readout:bytearray, i:int, printer: bool = True): tot_total = (tot_msb << 8) + tot_lsb wrong_id = 0 if (id) == 0 else '\x1b[0;31;40m{}\x1b[0m'.format(id) - wrong_payload = 4 if (payload) == 4 else'\x1b[0;31;40m{}\x1b[0m'.format(payload) + wrong_payload = 4 if (payload) == 4 else'\x1b[0;31;40m{}\x1b[0m'.format(payload) + + # will give terminal output if desiered if printer: @@ -416,7 +418,6 @@ def decode_readout(self, readout:bytearray, i:int, printer: bool = True): 'tot_lsb': tot_lsb, 'tot_total': tot_total, 'tot_us': ((tot_total * self.sampleclock_period_ns)/1000.0), - 'hit_bits': binascii.hexlify(hit), 'hittime': time.time() } hit_list.append(hits) diff --git a/beam_test.py b/beam_test.py index 1f4d4366..07b4afcd 100644 --- a/beam_test.py +++ b/beam_test.py @@ -35,7 +35,6 @@ 'tot_lsb': np.nan, 'tot_total': np.nan, 'tot_us': np.nan, - 'hit_bits': np.nan, 'hittime': np.nan }, index=[0] ) @@ -91,9 +90,8 @@ def main(args): 'tot_lsb', 'tot_total', 'tot_us', - 'hit_bits', 'hittime' - ]) + ], index = [0]) # And here for the text files/logs logpath = args.outdir + '/' + args.name + time.strftime("%Y%m%d-%H%M%S") + '.log' @@ -123,9 +121,11 @@ def main(args): # Writes the hex version to hits logfile.write(f"{i}\t{str(binascii.hexlify(readout))}\n") print(binascii.hexlify(readout)) + # Added fault tolerance for decoding, the limits of which are set through arguments try: hits = astro.decode_readout(readout, i, printer = True) + except IndexError: errors += 1 logger.warning(f"Decoding failed. Failure {errors} of {max_errors} on readout {i}") @@ -137,31 +137,33 @@ def main(args): # This loggs the end of it all if errors > max_errors: logger.warning(f"Decoding failed {errors} times on an index error. Terminating Progam...") - finally: i += 1 - - - # If we are saving a csv this will write it out. - if args.saveascsv: - csvframe = pd.concat([csvframe, hits]) - - # This handels the hitplotting. Code by Henrike and Amanda - if args.showhits: - # This ensures we aren't plotting NaN values. I don't know if this would break or not but better - # safe than sorry - if pd.isnull(hits.tot_msb.loc(0)): - pass - elif len(hits)>0:#safeguard against bad readouts without recorded decodable hits - rows,columns=[],[] - #Isolate row and column information from array returned from decoder - location = hits.location.to_numpy() - rowOrCol = hits.rowcol.to_numpy() - rows = location[rowOrCol==0] - columns = location[rowOrCol==1] - plotter.plot_event( rows, columns, i) - - # If we are logging runtime, this does it! - if args.timeit: - print(f"Read and decode took {(time.time_ns()-start)*10**-9}s") + finally: + i += 1 + errors += 1 + + + # If we are saving a csv this will write it out. + if args.saveascsv: + csvframe = pd.concat([csvframe, hits]) + + # This handels the hitplotting. Code by Henrike and Amanda + if args.showhits: + # This ensures we aren't plotting NaN values. I don't know if this would break or not but better + # safe than sorry + if pd.isnull(hits.tot_msb.loc(0)): + pass + elif len(hits)>0:#safeguard against bad readouts without recorded decodable hits + rows,columns=[],[] + #Isolate row and column information from array returned from decoder + location = hits.location.to_numpy() + rowOrCol = hits.rowcol.to_numpy() + rows = location[rowOrCol==0] + columns = location[rowOrCol==1] + plotter.plot_event( rows, columns, i) + + # If we are logging runtime, this does it! + if args.timeit: + print(f"Read and decode took {(time.time_ns()-start)*10**-9}s") # If no hits are present this waits for some to accumulate else: time.sleep(.001) @@ -174,7 +176,8 @@ def main(args): except Exception as e: logger.exception(f"Encountered Unexpected Exception! \n{e}") finally: - if args.saveascsv: hits.to_csv(csvpath) + if args.saveascsv: + csvframe.to_csv(csvpath) if args.inject: astro.stop_injection() logfile.close() # Close open file if args.inject: astro.stop_injection() #stops injection astro.close_connection() # Closes SPI @@ -251,11 +254,13 @@ def main(args): # Loglevel logging.basicConfig(filename=logname, - filemode='w' + filemode='w', format='%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s', datefmt='%H:%M:%S', level= loglevel) + logging.getLogger().addHandler(logging.StreamHandler()) logger = logging.getLogger(__name__) + main(args) \ No newline at end of file diff --git a/testdat/testing20220715-101935.csv b/testdat/testing20220715-101935.csv new file mode 100644 index 00000000..ec4041ea --- /dev/null +++ b/testdat/testing20220715-101935.csv @@ -0,0 +1,3 @@ +,readout,Chip ID,payload,location,rowcol,timestamp,tot_msb,tot_lsb,tot_total,tot_us,hit_bits,hittime +0,27,0,4,0,0,154,1,228,484,4.84,b'04009a01e4',1657894804.5873694 +1,27,0,4,0,1,154,1,141,397,3.97,b'04809a018d',1657894804.5878274 diff --git a/testdat/testing20220715-102205.csv b/testdat/testing20220715-102205.csv new file mode 100644 index 00000000..b6b75d19 --- /dev/null +++ b/testdat/testing20220715-102205.csv @@ -0,0 +1,3 @@ +,readout,Chip ID,payload,location,rowcol,timestamp,tot_msb,tot_lsb,tot_total,tot_us,hit_bits,hittime +0,21,0,4,0,0,8,13,234,3562,35.62,b'0400080dea',1657894942.8555958 +1,21,0,4,0,1,8,13,147,3475,34.75,b'0480080d93',1657894942.8568814 diff --git a/testdat/testing20220715-102317.csv b/testdat/testing20220715-102317.csv new file mode 100644 index 00000000..5138b499 --- /dev/null +++ b/testdat/testing20220715-102317.csv @@ -0,0 +1,50 @@ +,readout,Chip ID,payload,location,rowcol,timestamp,tot_msb,tot_lsb,tot_total,tot_us,hit_bits,hittime +0,,,,,,,,,,,, +0,0,0,4,0,0,12,12,224,3296,32.96,b'04000c0ce0',1657894998.2064464 +1,0,0,4,0,1,12,12,205,3277,32.77,b'04800c0ccd',1657894998.2068715 +0,1,0,4,0,0,7,12,121,3193,31.93,b'0400070c79',1657894998.9953415 +1,1,0,4,0,1,7,12,40,3112,31.12,b'0480070c28',1657894998.998208 +0,2,0,4,0,0,130,13,3,3331,33.31,b'0400820d03',1657894999.8252373 +1,2,0,4,0,1,130,12,169,3241,32.41,b'0480820ca9',1657894999.8262022 +0,3,0,4,0,0,126,8,121,2169,21.69,b'04007e0879',1657895000.6487484 +1,3,0,4,0,1,126,8,40,2088,20.88,b'04807e0828',1657895000.6492324 +0,4,0,4,0,0,249,4,141,1165,11.65,b'0400f9048d',1657895001.4328961 +1,4,0,4,0,1,249,4,48,1072,10.72,b'0480f90430',1657895001.4328961 +0,5,0,4,0,0,244,7,138,1930,19.3,b'0400f4078a',1657895002.2485707 +1,5,0,4,0,1,244,7,58,1850,18.5,b'0480f4073a',1657895002.2485707 +0,6,0,4,0,0,240,5,221,1501,15.01,b'0400f005dd',1657895003.0553665 +1,6,0,4,0,1,240,5,142,1422,14.22,b'0480f0058e',1657895003.0558403 +2,6,0,4,0,0,111,7,98,1890,18.9,b'04006f0762',1657895003.0623457 +3,6,0,4,0,1,111,7,86,1878,18.78,b'04806f0756',1657895003.0629728 +0,7,0,4,0,0,107,8,234,2282,22.82,b'04006b08ea',1657895003.8679886 +1,7,0,4,0,1,107,8,149,2197,21.97,b'04806b0895',1657895003.8679886 +0,8,0,4,0,0,102,8,204,2252,22.52,b'04006608cc',1657895004.6572204 +1,8,0,4,0,1,102,8,122,2170,21.7,b'048066087a',1657895004.6576774 +2,8,0,4,0,0,230,6,100,1636,16.36,b'0400e60664',1657895004.664562 +3,8,0,4,0,1,230,6,91,1627,16.27,b'0480e6065b',1657895004.6649823 +0,9,0,4,0,0,225,15,98,3938,39.38,b'0400e10f62',1657895005.4632323 +1,9,0,4,0,1,225,15,13,3853,38.53,b'0480e10f0d',1657895005.4701962 +0,10,0,4,0,0,221,10,100,2660,26.6,b'0400dd0a64',1657895006.2731094 +1,10,0,4,0,1,221,10,22,2582,25.82,b'0480dd0a16',1657895006.2740643 +0,11,0,4,0,0,88,0,121,121,1.21,b'0400580079',1657895007.0611184 +1,11,0,4,0,1,88,0,33,33,0.33,b'0480580021',1657895007.0611184 +0,12,0,4,0,0,83,11,247,3063,30.63,b'0400530bf7',1657895007.8683128 +1,12,0,4,0,1,83,11,158,2974,29.74,b'0480530b9e',1657895007.868798 +0,13,0,4,0,0,79,9,181,2485,24.85,b'04004f09b5',1657895008.6836054 +1,13,0,4,0,1,79,9,97,2401,24.01,b'04804f0961',1657895008.6840682 +0,14,0,4,0,0,202,7,51,1843,18.43,b'0400ca0733',1657895009.5158913 +1,14,0,4,0,1,202,6,218,1754,17.54,b'0480ca06da',1657895009.5163755 +0,15,0,4,0,0,197,11,73,2889,28.89,b'0400c50b49',1657895010.3161747 +1,15,0,4,0,1,197,10,245,2805,28.05,b'0480c50af5',1657895010.3166537 +2,15,0,4,0,0,69,7,64,1856,18.56,b'0400450740',1657895010.3171427 +3,15,0,4,0,1,69,7,56,1848,18.48,b'0480450738',1657895010.3171427 +0,16,0,4,0,0,64,5,39,1319,13.19,b'0400400527',1657895011.108644 +1,16,0,4,0,1,64,4,213,1237,12.37,b'04804004d5',1657895011.1091433 +0,17,0,4,0,0,60,12,62,3134,31.34,b'04003c0c3e',1657895011.9026132 +1,17,0,4,0,1,60,11,235,3051,30.51,b'04803c0beb',1657895011.9026132 +0,18,0,4,0,0,55,14,96,3680,36.8,b'0400370e60',1657895012.6969688 +1,18,0,4,0,1,55,14,15,3599,35.99,b'0480370e0f',1657895012.6969688 +0,19,0,4,0,0,178,14,6,3590,35.9,b'0400b20e06',1657895013.5109427 +1,19,0,4,0,1,178,13,173,3501,35.01,b'0480b20dad',1657895013.511941 +0,20,0,4,0,0,174,13,208,3536,35.36,b'0400ae0dd0',1657895014.331009 +1,20,0,4,0,1,174,13,132,3460,34.6,b'0480ae0d84',1657895014.3319755 From a56b55855d03e9b264ca67e34d900f875d231c91 Mon Sep 17 00:00:00 2001 From: Autumn Bauman Date: Fri, 15 Jul 2022 13:20:28 -0400 Subject: [PATCH 17/78] Fixed logging, documented readme, cleaned files --- .gitignore | 3 +- README.md | 30 +++++- astropix.py | 2 +- beam_test.py | 50 +++++---- beam_test_inj.py | 157 ----------------------------- beam_test_old.py | 157 ----------------------------- modules/setup_logger.py | 5 +- sensor_reset.py | 18 ---- test.py | 45 --------- testdat/test20220714-145109.csv | 3 - testdat/test20220714-145238.csv | 3 - testdat/testing20220715-101935.csv | 3 - testdat/testing20220715-102205.csv | 3 - testdat/testing20220715-102317.csv | 50 --------- 14 files changed, 65 insertions(+), 464 deletions(-) delete mode 100644 beam_test_inj.py delete mode 100644 beam_test_old.py delete mode 100644 sensor_reset.py delete mode 100644 test.py delete mode 100644 testdat/test20220714-145109.csv delete mode 100644 testdat/test20220714-145238.csv delete mode 100644 testdat/testing20220715-101935.csv delete mode 100644 testdat/testing20220715-102205.csv delete mode 100644 testdat/testing20220715-102317.csv diff --git a/.gitignore b/.gitignore index a30bc629..43f74a38 100644 --- a/.gitignore +++ b/.gitignore @@ -147,4 +147,5 @@ test.py testdat.py beam_test_inj.py beam_test_old.py -modules/asic.py \ No newline at end of file +modules/asic.py +archive/ \ No newline at end of file diff --git a/README.md b/README.md index 1e6746e1..30023b2f 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ Must go in this order!! - bias_setup: dictionairy of values which will be used to change the defalt bias settings. Does not need to have a complete dictionairy, only values that you want to change. Default None - digital_mask: text data of 1s and 0s in a 35x35 grid (newline seperated rows) specifying what pixels are on and off. If not specified chip will be in analog mode 3. initializing voltages - - call `astro.init_voltages(slot, vcal, vsupply, vthreshold, [optional] dacvals)` + - call `astro.init_voltages([none required] slot, vcal, vsupply, vthreshold, [optional] dacvals)` - slot: Usually 4, tells chip where the board is - vcal: calibrated voltage. Usually 0.989 - vsupply: voltage to gecco board, usually 2.7 @@ -123,3 +123,31 @@ astro.get_readout() --> bytearray. Gets bytestream from the chip astro.decode_readout(readout, [opt] printer) --> list of dictionairies. printer prints the decoded values to terminal astro.start_injection() and astro.stop_injection() are self explainatory + +## Usage of beam_test.py + +beam_test.py is a rewritten version of beam_test.py which removes the need for asic.py, and moves most configuration to command arguments. +It has the ability to: +- Save csv files +- Plot hits in real time +- Configure threshold and injection voltages +- Enable digital output based on pixel masks + +Options: +| Argument | Usage | Purpose | Default | +| :--- | :--- | :--- | :--- | +| `-n` `--name` | `-n [SOMESTRING]` | Set additional name to be added to the timestamp in file outputs | None | +| `-o` `--outdir`| `-o [DIRECTORY]` | Directory to save all output files to. Will be created if it doesn't exist. | `./` | +| `-m` `--mask` | `-m [PATH]` | Enable a masked digital output. Takes a path to a text file specifying which pixels are enabled. If not specified will default to (0,0). | None| +| `-c` `--saveascsv` | `-c` | Toggle saving csv files on and off | Does not save | +| `-s` `--showhits` | `-s` | Display hits in real time | Off | +| `-p` `--plotsave` | `-p` | Saves real time plots as image files. Stored in outdir. | Does not save plots | +| `-t` `--threshold`| `-t [VOLTAGE]`| Sets digital threshold voltage in mV. | 100mV | +| `-i` `--inject`| `-i` | Toggles injection on or off. Injects 300mV unless specified. | Off| +| `-M` `--maxruns` | `-M [int]` | Sets the maximum number of readouts the code will process before exiting. | No maximum | +| `-E` `--errormax`| `-E [int]` | Amount of index errors encountered in the decode before the program terminates. | 0 | +| `-v` `--vinj` | `-v [VOLTAGE]` | Sets voltage of injection in mV. Does not enable injection. | 300mV | +| `-L` `--loglevel` | `-L [D,I,E,W,C]`| Loglevel to be stored. Applies to both console and file. Options: D - debug, I - info, E - error, W - warning, C - critical | I | +| `--timeit` | `--timeit` | Measures the time it took to decode and store a hitstream. | Off | + + diff --git a/astropix.py b/astropix.py index 52206532..2bdf0fd1 100644 --- a/astropix.py +++ b/astropix.py @@ -412,7 +412,7 @@ def decode_readout(self, readout:bytearray, i:int, printer: bool = True): 'Chip ID': id, 'payload': payload, 'location': location, - 'rowcol': (1 if col else 0), + 'isCol': (True if col else False), 'timestamp': timestamp, 'tot_msb': tot_msb, 'tot_lsb': tot_lsb, diff --git a/beam_test.py b/beam_test.py index 07b4afcd..605fcf41 100644 --- a/beam_test.py +++ b/beam_test.py @@ -8,6 +8,7 @@ #from http.client import SWITCHING_PROTOCOLS from astropix import astropix2 import modules.hitplotter as hitplotter +import os import binascii import pandas as pd import numpy as np @@ -19,6 +20,9 @@ # This sets the logger name. +logdir = "./runlogs/" +if os.path.exists(logdir) == False: + os.mkdir(logdir) logname = "./runlogs/AstropixRunlog_" + time.strftime("%Y%m%d-%H%M%S") + ".log" @@ -29,7 +33,7 @@ 'Chip ID': np.nan, 'payload': np.nan, 'location': np.nan, - 'rowcol': np.nan, + 'isCol': np.nan, 'timestamp': np.nan, 'tot_msb': np.nan, 'tot_lsb': np.nan, @@ -50,6 +54,9 @@ def main(args): masked = True with open(args.mask, 'r') as file: bitmask = file.read() + # Ensures output directory exists + if os.path.exists(args.outdir) == False: + os.mkdir(args.outdir) # Prepare everything, create the object astro = astropix2(inject=args.inject) @@ -84,24 +91,24 @@ def main(args): 'Chip ID', 'payload', 'location', - 'rowcol', + 'isCol', 'timestamp', 'tot_msb', 'tot_lsb', 'tot_total', 'tot_us', 'hittime' - ], index = [0]) + ]) # And here for the text files/logs - logpath = args.outdir + '/' + args.name + time.strftime("%Y%m%d-%H%M%S") + '.log' + bitpath = args.outdir + '/' + args.name + time.strftime("%Y%m%d-%H%M%S") + '.log' # textfiles are always saved so we open it up - logfile = open(logpath,'w') + bitfile = open(bitpath,'w') # Writes all the config information to the file - logfile.write(astro.get_log_header()) + bitfile.write(astro.get_log_header()) # Enables the hitplotter and uses logic on whether or not to save the images - if args.showhits: plotter = hitplotter.HitPlotter(35, outdir=args.outdir if args.plotsave else None) + if args.showhits: plotter = hitplotter.HitPlotter(35, outdir=(args.outdir if args.plotsave else None)) try: # By enclosing the main loop in try/except we are able to capture keyboard interupts cleanly @@ -119,7 +126,7 @@ def main(args): readout = astro.get_readout() # Gets the bytearray from the chip # Writes the hex version to hits - logfile.write(f"{i}\t{str(binascii.hexlify(readout))}\n") + bitfile.write(f"{i}\t{str(binascii.hexlify(readout))}\n") print(binascii.hexlify(readout)) # Added fault tolerance for decoding, the limits of which are set through arguments @@ -156,9 +163,9 @@ def main(args): rows,columns=[],[] #Isolate row and column information from array returned from decoder location = hits.location.to_numpy() - rowOrCol = hits.rowcol.to_numpy() - rows = location[rowOrCol==0] - columns = location[rowOrCol==1] + rowOrCol = hits.isCol.to_numpy() + rows = location[rowOrCol==False] + columns = location[rowOrCol==True] plotter.plot_event( rows, columns, i) # If we are logging runtime, this does it! @@ -177,9 +184,10 @@ def main(args): logger.exception(f"Encountered Unexpected Exception! \n{e}") finally: if args.saveascsv: + csvframe.index.name = "dec_order" csvframe.to_csv(csvpath) if args.inject: astro.stop_injection() - logfile.close() # Close open file if args.inject: astro.stop_injection() #stops injection + bitfile.close() # Close open file if args.inject: astro.stop_injection() #stops injection astro.close_connection() # Closes SPI logger.info("Program terminated") # END OF PROGRAM @@ -252,13 +260,17 @@ def main(args): elif ll == 'C': loglevel = logging.CRITICAL - # Loglevel - logging.basicConfig(filename=logname, - filemode='w', - format='%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s', - datefmt='%H:%M:%S', - level= loglevel) - logging.getLogger().addHandler(logging.StreamHandler()) + # Logging stuff! + # This was way harder than I expected... + formatter = logging.Formatter('%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s') + fh = logging.FileHandler(logname) + fh.setFormatter(formatter) + sh = logging.StreamHandler() + sh.setFormatter(formatter) + + logging.getLogger().addHandler(sh) + logging.getLogger().addHandler(fh) + logging.getLogger().setLevel(logging.DEBUG) logger = logging.getLogger(__name__) diff --git a/beam_test_inj.py b/beam_test_inj.py deleted file mode 100644 index 478e8e4c..00000000 --- a/beam_test_inj.py +++ /dev/null @@ -1,157 +0,0 @@ -# -*- coding: utf-8 -*- -"""""" -""" -Created on Sat Jun 26 00:10:56 2021 - -@author: Nicolas Striebig -""" - -from modules.asic import Asic -from modules.injectionboard import Injectionboard -from modules.nexysio import Nexysio -from modules.voltageboard import Voltageboard -from modules.decode import Decode - -from utils.utils import wait_progress - -import binascii - -import os -import time - -def main(): - - nexys = Nexysio() - - # Open FTDI Device with Index 0 - #handle = nexys.open(0) - handle = nexys.autoopen() - - # Write and read directly to register - # Example: Write 0x55 to register 0x09 and read it back - nexys.write_register(0x09, 0x55, True) - nexys.read_register(0x09) - - nexys.spi_reset() - nexys.sr_readback_reset() - - # - # Configure ASIC - # - - # Write to asicSR - asic = Asic(handle) - asic.update_asic() - - # Example: Update Config Bit - # asic.digitalconfig['En_Inj17'] = 1 - # asic.dacs['vn1'] = 63 - # asic.update_asic() - - # - # Configure Voltageboard - # - - # Configure 8 DAC Voltageboard in Slot 4 with list values - # 3 = Vcasc2, 4=BL, 7=Vminuspix, 8=Thpix - vboard1 = Voltageboard(handle, 4, (8, [0, 0, 1.1, 1, 0, 0, 1, 1.2])) - - # Set measured 1V for one-point calibration - vboard1.vcal = 0.989 - vboard1.vsupply = 3.3 - - # Update voltageboards - vboard1.update_vb() - - # Write only first 3 DACs, other DACs will be 0 - # vboard1.dacvalues = (8, [1.2, 1, 1]) - # vboard1.update_vb() - - # - # Configure Injectionboard - # - - # Set Injection level - injvoltage = Voltageboard(handle, 3, (2, [0.4, 0.0])) - injvoltage.vcal = vboard1.vcal - injvoltage.vsupply = vboard1.vsupply - injvoltage.update_vb() - - inj = Injectionboard(handle) - - # Set Injection Params for 330MHz patgen clock - inj.period = 100 - inj.clkdiv = 400 - inj.initdelay = 10000 - inj.cycle = 0 - inj.pulsesperset = 1 - - # - # SPI - # - - # Enable SPI - nexys.spi_enable() - nexys.spi_reset() - - # Set SPI clockdivider - # freq = 100 MHz/spi_clkdiv - nexys.spi_clkdiv = 10 - - #asic.dacs['vn1'] = 5 - - # Generate bitvector for SPI ASIC config - asic_bitvector = asic.gen_asic_vector() - spi_data = nexys.asic_spi_vector(asic_bitvector, True, 10) - - # Write Config via spi - # nexys.write_spi(spi_data, False, 8191) - - # Send Routing command - nexys.send_routing_cmd() - - # Reset SPI Read FIFO - - inj.start() - #inj.stop() - - wait_progress(3) - - #decode = Decode() - - - """ i = 0 - while os.path.exists("log/sample%s.log" % i): - i += 1 - - file = open("log/sample%s.log" % i, "w") """ - timestr = time.strftime("beam_INJECTION_%Y%m%d-%H%M%S") - file = open("log/%s.log" % timestr, "w") - file.write(f"Voltageboard settings: {vboard1.dacvalues}\n") - file.write(f"Digital: {asic.digitalconfig}\n") - file.write(f"Biasblock: {asic.biasconfig}\n") - file.write(f"DAC: {asic.dacs}\n") - file.write(f"Receiver: {asic.recconfig}\n") - - readout = bytearray() - - while True: - print("Reg: {}".format(int.from_bytes(nexys.read_register(70),"big"))) - if(int.from_bytes(nexys.read_register(70),"big") == 0): - time.sleep(0.1) - nexys.write_spi_bytes(10) - readout = nexys.read_spi_fifo() - file.write(str(binascii.hexlify(readout))) - - print(binascii.hexlify(readout)) - - #decode.decode_astropix2_hits(decode.hits_from_readoutstream(readout)) - - # inj.stop() - - # Close connection - nexys.close() - - -if __name__ == "__main__": - main() diff --git a/beam_test_old.py b/beam_test_old.py deleted file mode 100644 index fabbd8fd..00000000 --- a/beam_test_old.py +++ /dev/null @@ -1,157 +0,0 @@ -# -*- coding: utf-8 -*- -"""""" -""" -Created on Sat Jun 26 00:10:56 2021 - -@author: Nicolas Striebig -""" - -from modules.asic import Asic -from modules.injectionboard import Injectionboard -from modules.nexysio import Nexysio -from modules.voltageboard import Voltageboard -from modules.decode import Decode - -from utils.utils import wait_progress - -import binascii - -import os -import time - -def main(): - - nexys = Nexysio() - - # Open FTDI Device with Index 0 - #handle = nexys.open(0) - handle = nexys.autoopen() - - # Write and read directly to register - # Example: Write 0x55 to register 0x09 and read it back - nexys.write_register(0x09, 0x55, True) - nexys.read_register(0x09) - - nexys.spi_reset() - nexys.sr_readback_reset() - - # - # Configure ASIC - # - - # Write to asicSR - asic = Asic(handle) - asic.update_asic() - - # Example: Update Config Bit - # asic.digitalconfig['En_Inj17'] = 1 - # asic.dacs['vn1'] = 63 - # asic.update_asic() - - # - # Configure Voltageboard - # - - # Configure 8 DAC Voltageboard in Slot 4 with list values - # 3 = Vcasc2, 4=BL, 7=Vminuspix, 8=Thpix - vboard1 = Voltageboard(handle, 4, (8, [0, 0, 1.1, 1, 0, 0, 1, 1.1])) - - # Set measured 1V for one-point calibration - vboard1.vcal = 0.989 - vboard1.vsupply = 3.3 - - # Update voltageboards - vboard1.update_vb() - - # Write only first 3 DACs, other DACs will be 0 - # vboard1.dacvalues = (8, [1.2, 1, 1]) - # vboard1.update_vb() - - # - # Configure Injectionboard - # - - # Set Injection level - injvoltage = Voltageboard(handle, 3, (2, [0.4, 0.0])) - injvoltage.vcal = vboard1.vcal - injvoltage.vsupply = vboard1.vsupply - injvoltage.update_vb() - - inj = Injectionboard(handle) - - # Set Injection Params for 330MHz patgen clock - inj.period = 100 - inj.clkdiv = 400 - inj.initdelay = 10000 - inj.cycle = 0 - inj.pulsesperset = 1 - - # - # SPI - # - - # Enable SPI - nexys.spi_enable() - nexys.spi_reset() - - # Set SPI clockdivider - # freq = 100 MHz/spi_clkdiv - nexys.spi_clkdiv = 255 - - #asic.dacs['vn1'] = 5 - - # Generate bitvector for SPI ASIC config - asic_bitvector = asic.gen_asic_vector() - spi_data = nexys.asic_spi_vector(asic_bitvector, True, 10) - - # Write Config via spi - # nexys.write_spi(spi_data, False, 8191) - - # Send Routing command - nexys.send_routing_cmd() - - # Reset SPI Read FIFO - - #inj.start() - inj.stop() - - wait_progress(3) - - #decode = Decode() - - - """ i = 0 - while os.path.exists("log/sample%s.log" % i): - i += 1 - - file = open("log/sample%s.log" % i, "w") """ - timestr = time.strftime("beam_%Y%m%d-%H%M%S") - file = open("log/%s.log" % timestr, "w") - file.write(f"Voltageboard settings: {vboard1.dacvalues}\n") - file.write(f"Digital: {asic.digitalconfig}\n") - file.write(f"Biasblock: {asic.biasconfig}\n") - file.write(f"DAC: {asic.dacs}\n") - file.write(f"Receiver: {asic.recconfig}\n") - - readout = bytearray() - - while True: - print("Reg: {}", int.from_bytes(nexys.read_register(70),"big")) - if(int.from_bytes(nexys.read_register(70),"big") == 0): - time.sleep(0.1) - nexys.write_spi_bytes(20) - readout = nexys.read_spi_fifo() - file.write(str(binascii.hexlify(readout))) - - print(binascii.hexlify(readout)) - - #decode.decode_astropix2_hits(decode.hits_from_readoutstream(readout)) - - # inj.stop() - - # Close connection - nexys.close() - - -if __name__ == "__main__": - main() diff --git a/modules/setup_logger.py b/modules/setup_logger.py index 6ae34880..da10598b 100644 --- a/modules/setup_logger.py +++ b/modules/setup_logger.py @@ -7,7 +7,6 @@ """ import logging -import datetime # This sets the logger name. # logname = "./runlogs/AstropixRunlog_" + datetime.datetime.strftime("%Y%m%d-%H%M%S") + ".log" @@ -20,5 +19,5 @@ level=logging.DEBUG) """ - -logger = logging.getLogger(__name__) \ No newline at end of file +#formatter = logging.Formatter('%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s') +logger = logging.getLogger(__name__) diff --git a/sensor_reset.py b/sensor_reset.py deleted file mode 100644 index 992810d9..00000000 --- a/sensor_reset.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -Code to cleanly power cycle sensor remotely - -Driver code: Autumn -Nexys code: Nicolas -""" - -from modules.nexysio import Nexysio - -# This is the driver code to do this - -nexys = Nexysio() -handle = nexys.autoopen() - -nexys.chip_reset() - -# This may or may not work, or it could cause issues. I will comment it out if needed. -nexys.close() \ No newline at end of file diff --git a/test.py b/test.py deleted file mode 100644 index bd2e313d..00000000 --- a/test.py +++ /dev/null @@ -1,45 +0,0 @@ -import argparse - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description='Astropix Driver Code') - parser.add_argument('-n', '--name', default='', required=False, - help='Option to give extra name to output files upon running') - - parser.add_argument('-o', '--outdir', default='.', required=False, - help='Output Directory for all datafiles') - - parser.add_argument('-s', '--showhits', action='store_true', - default=False, required=False, - help='Display hits in real time during data taking') - - parser.add_argument('-p', '--plotsave', action='store_true', default=False, required=False, - help='Save plots as image files. DEFAULT FALSE') - - parser.add_argument('-c', '--saveascsv', action='store_true', - default=False, required=False, - help='save output files as CSV. If False, save as txt') - - parser.add_argument('-i', '--inject', action='store_true',default=False, - help = 'Toggle injection on and off. DEFAULT: OFF') - - parser.add_argument('-v','--vinj', action='store', default = 400, type=float, - help = 'Specify injection voltage (in mV). DEFAULT 400 mV') - - parser.add_argument('-m', '--mask', action='store', required=False, type=str, default = None, - help = 'filepath to digital mask. Required to enable pixels not (0,0)') - - parser.add_argument('-t', '--threshold', type = float, action='store', default=None, - help = 'Threshold voltage for digital ToT (in mV). DEFAULT 100mV') - - parser.add_argument('-E', '--errormax', action='store', type=int, default='0', - help='Maximum index errors allowed during decoding. DEFAULT 0') - - parser.add_argument('-M', '--maxruns', type=int, action='store', default=None, - help = 'Maximum number of readouts') - - parser.add_argument('--timeit', action="store_true", default=False, - help='Prints runtime from seeing a hit to finishing the decode to terminal') - - args = parser.parse_args() - - print(args) \ No newline at end of file diff --git a/testdat/test20220714-145109.csv b/testdat/test20220714-145109.csv deleted file mode 100644 index ab1146c4..00000000 --- a/testdat/test20220714-145109.csv +++ /dev/null @@ -1,3 +0,0 @@ -,readout,Chip ID,payload,location,rowcol,timestamp,tot_msb,tot_lsb,tot_total,tot_us,hit_bits,hittime -0,0,0,4,0,0,63,15,141,3981,39.81,b'04003f0f8d',1657824670.1956813 -1,0,0,4,0,1,63,15,123,3963,39.63,b'04803f0f7b',1657824670.1966784 diff --git a/testdat/test20220714-145238.csv b/testdat/test20220714-145238.csv deleted file mode 100644 index b6aa8ac3..00000000 --- a/testdat/test20220714-145238.csv +++ /dev/null @@ -1,3 +0,0 @@ -,readout,Chip ID,payload,location,rowcol,timestamp,tot_msb,tot_lsb,tot_total,tot_us,hit_bits,hittime -0,7,0,4,0,0,158,11,83,2899,28.99,b'04009e0b53',1657824765.2150674 -1,7,0,4,0,1,158,11,6,2822,28.22,b'04809e0b06',1657824765.216064 diff --git a/testdat/testing20220715-101935.csv b/testdat/testing20220715-101935.csv deleted file mode 100644 index ec4041ea..00000000 --- a/testdat/testing20220715-101935.csv +++ /dev/null @@ -1,3 +0,0 @@ -,readout,Chip ID,payload,location,rowcol,timestamp,tot_msb,tot_lsb,tot_total,tot_us,hit_bits,hittime -0,27,0,4,0,0,154,1,228,484,4.84,b'04009a01e4',1657894804.5873694 -1,27,0,4,0,1,154,1,141,397,3.97,b'04809a018d',1657894804.5878274 diff --git a/testdat/testing20220715-102205.csv b/testdat/testing20220715-102205.csv deleted file mode 100644 index b6b75d19..00000000 --- a/testdat/testing20220715-102205.csv +++ /dev/null @@ -1,3 +0,0 @@ -,readout,Chip ID,payload,location,rowcol,timestamp,tot_msb,tot_lsb,tot_total,tot_us,hit_bits,hittime -0,21,0,4,0,0,8,13,234,3562,35.62,b'0400080dea',1657894942.8555958 -1,21,0,4,0,1,8,13,147,3475,34.75,b'0480080d93',1657894942.8568814 diff --git a/testdat/testing20220715-102317.csv b/testdat/testing20220715-102317.csv deleted file mode 100644 index 5138b499..00000000 --- a/testdat/testing20220715-102317.csv +++ /dev/null @@ -1,50 +0,0 @@ -,readout,Chip ID,payload,location,rowcol,timestamp,tot_msb,tot_lsb,tot_total,tot_us,hit_bits,hittime -0,,,,,,,,,,,, -0,0,0,4,0,0,12,12,224,3296,32.96,b'04000c0ce0',1657894998.2064464 -1,0,0,4,0,1,12,12,205,3277,32.77,b'04800c0ccd',1657894998.2068715 -0,1,0,4,0,0,7,12,121,3193,31.93,b'0400070c79',1657894998.9953415 -1,1,0,4,0,1,7,12,40,3112,31.12,b'0480070c28',1657894998.998208 -0,2,0,4,0,0,130,13,3,3331,33.31,b'0400820d03',1657894999.8252373 -1,2,0,4,0,1,130,12,169,3241,32.41,b'0480820ca9',1657894999.8262022 -0,3,0,4,0,0,126,8,121,2169,21.69,b'04007e0879',1657895000.6487484 -1,3,0,4,0,1,126,8,40,2088,20.88,b'04807e0828',1657895000.6492324 -0,4,0,4,0,0,249,4,141,1165,11.65,b'0400f9048d',1657895001.4328961 -1,4,0,4,0,1,249,4,48,1072,10.72,b'0480f90430',1657895001.4328961 -0,5,0,4,0,0,244,7,138,1930,19.3,b'0400f4078a',1657895002.2485707 -1,5,0,4,0,1,244,7,58,1850,18.5,b'0480f4073a',1657895002.2485707 -0,6,0,4,0,0,240,5,221,1501,15.01,b'0400f005dd',1657895003.0553665 -1,6,0,4,0,1,240,5,142,1422,14.22,b'0480f0058e',1657895003.0558403 -2,6,0,4,0,0,111,7,98,1890,18.9,b'04006f0762',1657895003.0623457 -3,6,0,4,0,1,111,7,86,1878,18.78,b'04806f0756',1657895003.0629728 -0,7,0,4,0,0,107,8,234,2282,22.82,b'04006b08ea',1657895003.8679886 -1,7,0,4,0,1,107,8,149,2197,21.97,b'04806b0895',1657895003.8679886 -0,8,0,4,0,0,102,8,204,2252,22.52,b'04006608cc',1657895004.6572204 -1,8,0,4,0,1,102,8,122,2170,21.7,b'048066087a',1657895004.6576774 -2,8,0,4,0,0,230,6,100,1636,16.36,b'0400e60664',1657895004.664562 -3,8,0,4,0,1,230,6,91,1627,16.27,b'0480e6065b',1657895004.6649823 -0,9,0,4,0,0,225,15,98,3938,39.38,b'0400e10f62',1657895005.4632323 -1,9,0,4,0,1,225,15,13,3853,38.53,b'0480e10f0d',1657895005.4701962 -0,10,0,4,0,0,221,10,100,2660,26.6,b'0400dd0a64',1657895006.2731094 -1,10,0,4,0,1,221,10,22,2582,25.82,b'0480dd0a16',1657895006.2740643 -0,11,0,4,0,0,88,0,121,121,1.21,b'0400580079',1657895007.0611184 -1,11,0,4,0,1,88,0,33,33,0.33,b'0480580021',1657895007.0611184 -0,12,0,4,0,0,83,11,247,3063,30.63,b'0400530bf7',1657895007.8683128 -1,12,0,4,0,1,83,11,158,2974,29.74,b'0480530b9e',1657895007.868798 -0,13,0,4,0,0,79,9,181,2485,24.85,b'04004f09b5',1657895008.6836054 -1,13,0,4,0,1,79,9,97,2401,24.01,b'04804f0961',1657895008.6840682 -0,14,0,4,0,0,202,7,51,1843,18.43,b'0400ca0733',1657895009.5158913 -1,14,0,4,0,1,202,6,218,1754,17.54,b'0480ca06da',1657895009.5163755 -0,15,0,4,0,0,197,11,73,2889,28.89,b'0400c50b49',1657895010.3161747 -1,15,0,4,0,1,197,10,245,2805,28.05,b'0480c50af5',1657895010.3166537 -2,15,0,4,0,0,69,7,64,1856,18.56,b'0400450740',1657895010.3171427 -3,15,0,4,0,1,69,7,56,1848,18.48,b'0480450738',1657895010.3171427 -0,16,0,4,0,0,64,5,39,1319,13.19,b'0400400527',1657895011.108644 -1,16,0,4,0,1,64,4,213,1237,12.37,b'04804004d5',1657895011.1091433 -0,17,0,4,0,0,60,12,62,3134,31.34,b'04003c0c3e',1657895011.9026132 -1,17,0,4,0,1,60,11,235,3051,30.51,b'04803c0beb',1657895011.9026132 -0,18,0,4,0,0,55,14,96,3680,36.8,b'0400370e60',1657895012.6969688 -1,18,0,4,0,1,55,14,15,3599,35.99,b'0480370e0f',1657895012.6969688 -0,19,0,4,0,0,178,14,6,3590,35.9,b'0400b20e06',1657895013.5109427 -1,19,0,4,0,1,178,13,173,3501,35.01,b'0480b20dad',1657895013.511941 -0,20,0,4,0,0,174,13,208,3536,35.36,b'0400ae0dd0',1657895014.331009 -1,20,0,4,0,1,174,13,132,3460,34.6,b'0480ae0d84',1657895014.3319755 From 2fc2bb5a35ccf97afb132f2a16258e24295ad5a2 Mon Sep 17 00:00:00 2001 From: Autumn Bauman Date: Fri, 15 Jul 2022 13:21:55 -0400 Subject: [PATCH 18/78] readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 30023b2f..5220fddc 100644 --- a/README.md +++ b/README.md @@ -142,12 +142,12 @@ Options: | `-c` `--saveascsv` | `-c` | Toggle saving csv files on and off | Does not save | | `-s` `--showhits` | `-s` | Display hits in real time | Off | | `-p` `--plotsave` | `-p` | Saves real time plots as image files. Stored in outdir. | Does not save plots | -| `-t` `--threshold`| `-t [VOLTAGE]`| Sets digital threshold voltage in mV. | 100mV | +| `-t` `--threshold`| `-t [VOLTAGE]`| Sets digital threshold voltage in mV. | `100mV` | | `-i` `--inject`| `-i` | Toggles injection on or off. Injects 300mV unless specified. | Off| | `-M` `--maxruns` | `-M [int]` | Sets the maximum number of readouts the code will process before exiting. | No maximum | -| `-E` `--errormax`| `-E [int]` | Amount of index errors encountered in the decode before the program terminates. | 0 | -| `-v` `--vinj` | `-v [VOLTAGE]` | Sets voltage of injection in mV. Does not enable injection. | 300mV | -| `-L` `--loglevel` | `-L [D,I,E,W,C]`| Loglevel to be stored. Applies to both console and file. Options: D - debug, I - info, E - error, W - warning, C - critical | I | +| `-E` `--errormax`| `-E [int]` | Amount of index errors encountered in the decode before the program terminates. | `0` | +| `-v` `--vinj` | `-v [VOLTAGE]` | Sets voltage of injection in mV. Does not enable injection. | `300mV` | +| `-L` `--loglevel` | `-L [D,I,E,W,C]`| Loglevel to be stored. Applies to both console and file. Options: D - debug, I - info, E - error, W - warning, C - critical | `I` | | `--timeit` | `--timeit` | Measures the time it took to decode and store a hitstream. | Off | From 331543b9b9b2660bd9374697465d1b0e10a95f87 Mon Sep 17 00:00:00 2001 From: Autumn Bauman Date: Fri, 15 Jul 2022 14:13:15 -0400 Subject: [PATCH 19/78] Created voltagescan --- README.md | 2 +- Voltagescan.py | 176 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 Voltagescan.py diff --git a/README.md b/README.md index 5220fddc..99f59ff2 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ Options: | `-n` `--name` | `-n [SOMESTRING]` | Set additional name to be added to the timestamp in file outputs | None | | `-o` `--outdir`| `-o [DIRECTORY]` | Directory to save all output files to. Will be created if it doesn't exist. | `./` | | `-m` `--mask` | `-m [PATH]` | Enable a masked digital output. Takes a path to a text file specifying which pixels are enabled. If not specified will default to (0,0). | None| -| `-c` `--saveascsv` | `-c` | Toggle saving csv files on and off | Does not save | +| `-c` `--saveascsv` | `-c` | Toggle saving csv files on and off | Does not save csv | | `-s` `--showhits` | `-s` | Display hits in real time | Off | | `-p` `--plotsave` | `-p` | Saves real time plots as image files. Stored in outdir. | Does not save plots | | `-t` `--threshold`| `-t [VOLTAGE]`| Sets digital threshold voltage in mV. | `100mV` | diff --git a/Voltagescan.py b/Voltagescan.py new file mode 100644 index 00000000..49a460da --- /dev/null +++ b/Voltagescan.py @@ -0,0 +1,176 @@ +""" +Code to run power supply bias voltage scanning with a source. This will not be near as robust as the beam_test.py +but since it is just for my use (for now) I think it will do. +""" + +from astropix import astropix2 +import pandas as pd +import numpy as np +import logging +import binascii +import time +import os +#import [CONTROL PKG] as RC +from modules.setup_logger import logger + + +datadir = "biasscan_source" +psudir = "ps" +digitdir = "digital" + +vmin = -5 +vmax = -135 +vstep = -10 + +testlen = 60 + +psu_ip = '' + +maxlen = 4 * 60 * 60 + + +pspath = datadir + '/' + psudir +csvpath = datadir + '/' + digitdir + +basecrrnt = f"/bias_scan_{vmin}_{vmax}_{vstep}_Source_Ba-133_CURRENTS" + time.strftime("%Y%m%d-%H%M%S") +basedigit = f"/bias_scan_{vmin}_{vmax}_{vstep}_Source_Ba-133_DIGITAL" + time.strftime("%Y%m%d-%H%M%S") +basebits = f"/bias_scan_{vmin}_{vmax}_{vstep}_Source_Ba-133_BITSTREAMS" + time.strftime("%Y%m%d-%H%M%S") + + +if os.path.exists(pspath) == False: + os.mkdir(pspath) +if os.path.exists(csvpath) == False: + os.mkdir(csvpath) + +# This sets the logger name. +logdir = "./runlogs/" +if os.path.exists(logdir) == False: + os.mkdir(logdir) +logname = "./runlogs/AstropixRunlog_" + time.strftime("%Y%m%d-%H%M%S") + ".log" + +decode_fail_frame = pd.DataFrame({ + 'readout': np.nan, + 'Chip ID': np.nan, + 'payload': np.nan, + 'location': np.nan, + 'isCol': np.nan, + 'timestamp': np.nan, + 'tot_msb': np.nan, + 'tot_lsb': np.nan, + 'tot_total': np.nan, + 'tot_us': np.nan, + 'hittime': np.nan + }, index=[0] +) + + + + +def getData(astro:astropix2, PS, runtime, bias, basecrrnt, basedigit, basebits): + + maxtime = time.time() + runtime + crrntpth = basecrrnt + f"_{bias}V_bias.csv" + digitpth = basedigit + f"_{bias}V_bias.csv" + bitspth = basebits + f"_{bias}V_bias.log" + + csvframe =pd.DataFrame(columns = [ + 'readout', + 'Chip ID', + 'payload', + 'location', + 'isCol', + 'timestamp', + 'tot_msb', + 'tot_lsb', + 'tot_total', + 'tot_us', + 'hittime']) + + + bitfile = open(bitspth, 'w') + bitfile.write(astro.get_log_header()) + + returnval = 0 + + errors = 0 + maxerrors = 10 + try: + while (time.time() <= maxtime) and (errors <= maxerrors): + + if astro.hits_present(): # Checks if hits are present + time.sleep(.1) # this is probably not needed, will ask Nicolas + + readout = astro.get_readout() # Gets the bytearray from the chip + # Writes the hex version to hits + bitfile.write(f"{i}\t{str(binascii.hexlify(readout))}\n") + print(binascii.hexlify(readout)) + + # Added fault tolerance for decoding, the limits of which are set through arguments + try: + hits = astro.decode_readout(readout, i, printer = True) + + except IndexError: + errors += 1 + logger.warning(f"Decoding failed. Failure {errors} of {maxerrors} on readout {i}") + # We write out the failed decode dataframe + hits = decode_fail_frame + hits.readout = i + hits.hittime = time.time() + + # This loggs the end of it all + if errors > maxerrors: + logger.warning(f"Decoding failed {errors} times on an index error. Terminating Progam...") + returnval = 10 + finally: + i += 1 + errors += 1 + csvframe = pd.concat([csvframe, hits]) + except KeyboardInterrupt: + logger.info("Recieved Interrupt. Terminating program...") + finally: + csvframe.index.name = "dec_order" + csvframe.to_csv(csvpath) + # NEEDS TO BE FINISHED + data, nrows = PS.StopColection() + df = PS.to_csv(data, nrows) + df.to_csv(crrntpth) + + return returnval + + +def main(): + astro = astropix2() + astro.asic_init() + astro.init_voltages() + astro.init_injection() + astro.enable_spi() + logger.info("Chip configured") + astro.dump_fpga() + try: + for bias in range(vmin, vmax, vstep): + cont = getData(astro, PS, testlen, bias, basecrrnt, basedigit, basebits) + if cont == 10: + raise RuntimeError("Maximum errors exceded!") + except KeyboardInterrupt: + logger.info("Keyboard interup. Terminating...") + + except Exception as e: + logger.exception(f"e") + + + +if __name__ == "__main__": + formatter = logging.Formatter('%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s') + fh = logging.FileHandler(logname) + fh.setFormatter(formatter) + sh = logging.StreamHandler() + sh.setFormatter(formatter) + + logging.getLogger().addHandler(sh) + logging.getLogger().addHandler(fh) + logging.getLogger().setLevel(logging.DEBUG) + + logger = logging.getLogger(__name__) + + main() + From 9d400072b74f91fa6443b1e69f41abea7abfa046 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Fri, 15 Jul 2022 14:36:10 -0400 Subject: [PATCH 20/78] Polished logging --- .gitignore | 3 ++- astropix.py | 6 +++--- beam_test.py | 8 +++----- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 43f74a38..b777a65d 100644 --- a/.gitignore +++ b/.gitignore @@ -148,4 +148,5 @@ testdat.py beam_test_inj.py beam_test_old.py modules/asic.py -archive/ \ No newline at end of file +archive/ +*.csv \ No newline at end of file diff --git a/astropix.py b/astropix.py index 2bdf0fd1..31aaf128 100644 --- a/astropix.py +++ b/astropix.py @@ -319,7 +319,7 @@ def start_injection(self): Takes no arguments and no return """ self.injector.start() - logger.info("BEGAN INJECTION") + logger.info("Began injection") def stop_injection(self): """ @@ -327,7 +327,7 @@ def stop_injection(self): Takes no arguments and no return """ self.injector.stop() - logger.info("STOPPED INJECTION") + logger.info("Stopped injection") ########################### Input and Output ############################# @@ -389,7 +389,7 @@ def decode_readout(self, readout:bytearray, i:int, printer: bool = True): col = 1 if (int(hit[1]) >> 7 ) & 1 else 0 timestamp = int(hit[2]) tot_msb = int(hit[3]) & 0b1111 - tot_lsb = int(hit[4]) + tot_lsb = int(hit[4]) tot_total = (tot_msb << 8) + tot_lsb wrong_id = 0 if (id) == 0 else '\x1b[0;31;40m{}\x1b[0m'.format(id) diff --git a/beam_test.py b/beam_test.py index 605fcf41..7492294d 100644 --- a/beam_test.py +++ b/beam_test.py @@ -111,7 +111,7 @@ def main(args): if args.showhits: plotter = hitplotter.HitPlotter(35, outdir=(args.outdir if args.plotsave else None)) try: # By enclosing the main loop in try/except we are able to capture keyboard interupts cleanly - + while errors <= max_errors: # Loop continues # This might be possible to do in the loop declaration, but its a lot easier to simply add in this logic @@ -146,8 +146,6 @@ def main(args): logger.warning(f"Decoding failed {errors} times on an index error. Terminating Progam...") finally: i += 1 - errors += 1 - # If we are saving a csv this will write it out. if args.saveascsv: @@ -189,7 +187,7 @@ def main(args): if args.inject: astro.stop_injection() bitfile.close() # Close open file if args.inject: astro.stop_injection() #stops injection astro.close_connection() # Closes SPI - logger.info("Program terminated") + logger.info("Program terminated successfully") # END OF PROGRAM @@ -270,7 +268,7 @@ def main(args): logging.getLogger().addHandler(sh) logging.getLogger().addHandler(fh) - logging.getLogger().setLevel(logging.DEBUG) + logging.getLogger().setLevel(loglevel) logger = logging.getLogger(__name__) From 8318db6e61ada3dc7a40b4f8bb648ee5260f3791 Mon Sep 17 00:00:00 2001 From: Autumn Bauman Date: Tue, 19 Jul 2022 13:13:50 -0400 Subject: [PATCH 21/78] Added a blank mask for ease of use --- blankmask.txt | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 blankmask.txt diff --git a/blankmask.txt b/blankmask.txt new file mode 100644 index 00000000..da21fa5c --- /dev/null +++ b/blankmask.txt @@ -0,0 +1,35 @@ +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 From 4bda03d7c477f5d6600596b27964971e3b84dc42 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Tue, 19 Jul 2022 14:15:17 -0400 Subject: [PATCH 22/78] Updates --- Voltagescan.py | 112 +++++++++++++++++++++++++++++++-------- modules/pyKeithleyCtl.py | 104 ++++++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+), 23 deletions(-) create mode 100644 modules/pyKeithleyCtl.py diff --git a/Voltagescan.py b/Voltagescan.py index 49a460da..6f6fa19c 100644 --- a/Voltagescan.py +++ b/Voltagescan.py @@ -1,6 +1,11 @@ """ Code to run power supply bias voltage scanning with a source. This will not be near as robust as the beam_test.py but since it is just for my use (for now) I think it will do. + + +Keithley_IP = "169.254.127.39" +BiasHV = -60.0 #in Volt +maxCurrent = 0.001 #in Ampere """ from astropix import astropix2 @@ -10,43 +15,48 @@ import binascii import time import os -#import [CONTROL PKG] as RC +from modules.pyKeithleyCtl import KeithleySupply as RC from modules.setup_logger import logger -datadir = "biasscan_source" +datadir = "biasscan_weekend_7-15" psudir = "ps" digitdir = "digital" -vmin = -5 -vmax = -135 +vmin = -10 +vmax = -130 vstep = -10 -testlen = 60 +testlen = 6 * 60 * 60 + +stable_time = 5 * 60 -psu_ip = '' +Keithley_IP = "169.254.127.39" +BiasHV = -60.0 #in Volt +maxCurrent = 0.001 #in Ampere -maxlen = 4 * 60 * 60 + +maxlen = 10 * 60 * 60 pspath = datadir + '/' + psudir csvpath = datadir + '/' + digitdir -basecrrnt = f"/bias_scan_{vmin}_{vmax}_{vstep}_Source_Ba-133_CURRENTS" + time.strftime("%Y%m%d-%H%M%S") -basedigit = f"/bias_scan_{vmin}_{vmax}_{vstep}_Source_Ba-133_DIGITAL" + time.strftime("%Y%m%d-%H%M%S") -basebits = f"/bias_scan_{vmin}_{vmax}_{vstep}_Source_Ba-133_BITSTREAMS" + time.strftime("%Y%m%d-%H%M%S") +basecrrnt = pspath + f"/bias_scan_{vmin}_{vmax}_{vstep}_Source_Ba-133_CURRENTS" + time.strftime("%Y%m%d-%H%M%S") +basedigit = csvpath + f"/bias_scan_{vmin}_{vmax}_{vstep}_Source_Ba-133_DIGITAL" + time.strftime("%Y%m%d-%H%M%S") +basebits = csvpath + f"/bias_scan_{vmin}_{vmax}_{vstep}_Source_Ba-133_BITSTREAMS" + time.strftime("%Y%m%d-%H%M%S") if os.path.exists(pspath) == False: - os.mkdir(pspath) + os.makedirs(pspath) if os.path.exists(csvpath) == False: - os.mkdir(csvpath) + os.makedirs(csvpath) # This sets the logger name. logdir = "./runlogs/" if os.path.exists(logdir) == False: os.mkdir(logdir) -logname = "./runlogs/AstropixRunlog_" + time.strftime("%Y%m%d-%H%M%S") + ".log" +logname = "./runlogs/AstropixHVScanlog_" +f"{vmin}_{vmax}_{vstep}_"+ time.strftime("%Y%m%d-%H%M%S") + ".log" decode_fail_frame = pd.DataFrame({ 'readout': np.nan, @@ -65,6 +75,13 @@ +def printVoltages(PS): + print("Set voltage: ", PS.get_voltage(), "V") + print("Measured voltage:", PS.measure_voltage(), "V") + print("Max current: ", PS.get_ocp() + , "A") + print("Measured current:",format((float(PS.measure_current()) * (10**9)), '0.4f'), "nA") + def getData(astro:astropix2, PS, runtime, bias, basecrrnt, basedigit, basebits): @@ -95,6 +112,19 @@ def getData(astro:astropix2, PS, runtime, bias, basecrrnt, basedigit, basebits): errors = 0 maxerrors = 10 try: + + PS.enable_output() + # Time to stabilize + logger.info("HV Output on") + + logger.info("set voltage, waiting 5 min") + time.sleep(stable_time) + + PS.start_measurement(maxlen) # Turns on HV and starts taking data + + + logger.info("HV Logging Start") + i = 0 while (time.time() <= maxtime) and (errors <= maxerrors): if astro.hits_present(): # Checks if hits are present @@ -123,39 +153,75 @@ def getData(astro:astropix2, PS, runtime, bias, basecrrnt, basedigit, basebits): returnval = 10 finally: i += 1 - errors += 1 csvframe = pd.concat([csvframe, hits]) + + except KeyboardInterrupt: logger.info("Recieved Interrupt. Terminating program...") + returnval = 20 finally: - csvframe.index.name = "dec_order" - csvframe.to_csv(csvpath) - # NEEDS TO BE FINISHED - data, nrows = PS.StopColection() - df = PS.to_csv(data, nrows) + data, nRow = PS.stop_measurement() + PS.disable_output() + df = PS.to_csv(data, nRow) + df["VOLTAGE"] = bias df.to_csv(crrntpth) + # Writes digital data + csvframe.index.name = "dec_order" + csvframe.to_csv(digitpth) + + + return returnval def main(): - astro = astropix2() + PS = RC(Keithley_IP) + PS.clear() + PS.reset() + PS.set_voltage(-60) + PS.set_ocp(maxCurrent) + printVoltages(PS) + + # Quick check to make sure it is working + if input("Does this look correct? (Y/n)") == "n": + PS.disable_output() + PS.close() + + astro = astropix2(inject=False) astro.asic_init() astro.init_voltages() astro.init_injection() astro.enable_spi() logger.info("Chip configured") astro.dump_fpga() - try: - for bias in range(vmin, vmax, vstep): + try: # Main loop. + # Encased in try statement to gaurd against errors + for bias in range(vmin, vmax + vstep, vstep): + PS.set_voltage(bias) # sets the bias + time.sleep(1) + logger.info(f"HV supply voltage set:{bias}V") + # Runs the data gathering cont = getData(astro, PS, testlen, bias, basecrrnt, basedigit, basebits) + # checks to make sure it didn't fail if cont == 10: raise RuntimeError("Maximum errors exceded!") + if cont == 20: + raise KeyboardInterrupt + # Loops again + except KeyboardInterrupt: logger.info("Keyboard interup. Terminating...") except Exception as e: logger.exception(f"e") + + + finally: + PS.disable_output() + PS.close() + astro.close_connection() + @@ -168,7 +234,7 @@ def main(): logging.getLogger().addHandler(sh) logging.getLogger().addHandler(fh) - logging.getLogger().setLevel(logging.DEBUG) + logging.getLogger().setLevel(logging.INFO) logger = logging.getLogger(__name__) diff --git a/modules/pyKeithleyCtl.py b/modules/pyKeithleyCtl.py new file mode 100644 index 00000000..d347ba58 --- /dev/null +++ b/modules/pyKeithleyCtl.py @@ -0,0 +1,104 @@ +import numpy as np +import pyvisa as visa +import yaml +import time +import pandas as pd + +VISA_RM = visa.ResourceManager('@py') +class KeithleySupply(): + + '''Used to control a single Keithley 2450 Source Meter.''' + + + def __init__(self, address, n_ch=1, visa_resource_manager=VISA_RM): + resource_str = f'TCPIP0::{address:s}::INSTR' + self.resource = VISA_RM.open_resource(resource_str, write_termination='\n', read_termination='\n') + + self.write = self.resource.write + self.query = self.resource.query + + self.clear = self.resource.clear + self.close = self.resource.close + + @property + def IDN(self): + return self.ask("*IDN?") + + @property + def IDENTITY(self): + return f"IDN: {self.IDN.split(',')[-2]} IP: {self.IP}" + + def ask(self, question, verbose=False): + response = self.query(question) + if verbose: + print("Question: {0:s} - Response: {1:s}".format(question, str(response))) + return response + + def tell(self, statement): + return self.write(statement) + + def reset(self): + return self.write("*RST") + + def init(self): + return self.write("INIT") + + def wait(self): + return self.write("*WAI") + + def enable_output(self): + return self.tell(f"OUTP:STAT ON") + + def disable_output(self): + return self.tell(f"OUTP:STAT OFF") + + def set_voltage(self, voltage): + self.tell(f":SOURCE:FUNC VOLT") + self.tell(f":SOURCE:VOLT {voltage}") + + def get_voltage(self): + return self.ask(":SOURCE:VOLT?") + + def measure_current( self ): + return self.ask(":MEASURE:CURRENT:DC?") + + def measure_voltage( self ): + return self.ask(":MEASURE:VOLTAGE:DC?") + + def set_ocp(self, ocp): + self.tell(f":SOURCe:VOLTage:ILIMit {ocp}") + + def get_ocp(self): + return self.ask(":SOURCe:VOLTage:ILIMit?") + + def start_measurement(self, max_duration_s = 60*60, delay_s = 0.05): + self.tell('SENS:FUNC "CURR"') + self.tell("SENS:CURR:RANG:AUTO ON") + + self.tell(':TRACE:DELete "myBuffer"') + bufferSize = int(2.0*max_duration_s/delay_s) + + + self.tell(f'TRACE:MAKE "myBuffer", {bufferSize}' ) + self.tell(f':TRIGger:LOAD "LoopUntilEvent", COMM, 100, ENT, 1, "myBuffer"' ) + self.init() + + def stop_measurement(self, max_duration_s = 60*60, delay_s = 0.05): + self.write('*TRG') + nRow = int(self.ask(':TRAC:ACTUAL? "myBuffer"') ) + print(nRow) + result = self.ask(f':TRAC:DATA? 1, {nRow}, "myBuffer", REL, SEC, FRAC, SOUR, SOURSTAT, STAT, READ') + + return result, nRow + + def to_csv(self, result, nRow): + + nCol=7 + data=np.reshape( np.fromstring(result, sep=','), (nRow, nCol) ) + + df = pd.DataFrame(data, columns = "REL SEC FRAC SOUR SOURSTAT STAT READ".split() ) + + df.SOURSTAT = df.SOURSTAT.astype(int) + df.STAT = df.STAT.astype(int) + + return df \ No newline at end of file From c3057e69ba504c384c05c9e4d26188c4157e1bab Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Tue, 19 Jul 2022 14:18:35 -0400 Subject: [PATCH 23/78] synchronising repos --- .gitignore | 3 +- beam_test.py | 6 +- main.py | 134 --------------------------------------- readback_test.py | 159 ----------------------------------------------- 4 files changed, 7 insertions(+), 295 deletions(-) delete mode 100644 main.py delete mode 100644 readback_test.py diff --git a/.gitignore b/.gitignore index b777a65d..6a5941cd 100644 --- a/.gitignore +++ b/.gitignore @@ -149,4 +149,5 @@ beam_test_inj.py beam_test_old.py modules/asic.py archive/ -*.csv \ No newline at end of file +*.csv +beam_test_play.py \ No newline at end of file diff --git a/beam_test.py b/beam_test.py index 7492294d..2db7deb7 100644 --- a/beam_test.py +++ b/beam_test.py @@ -121,10 +121,14 @@ def main(args): if astro.hits_present(): # Checks if hits are present # We aren't using timeit, just measuring the diffrence in ns if args.timeit: start = time.time_ns() - + time.sleep(.1) # this is probably not needed, will ask Nicolas readout = astro.get_readout() # Gets the bytearray from the chip + + if args.timeit: + print(f"Readout took {(time.time_ns()-start)*10**-9}s") + # Writes the hex version to hits bitfile.write(f"{i}\t{str(binascii.hexlify(readout))}\n") print(binascii.hexlify(readout)) diff --git a/main.py b/main.py deleted file mode 100644 index 0cfb1ceb..00000000 --- a/main.py +++ /dev/null @@ -1,134 +0,0 @@ -# -*- coding: utf-8 -*- -"""""" -""" -Created on Sat Jun 26 00:10:56 2021 - -@author: Nicolas Striebig -""" - -from modules.asic import Asic -from modules.injectionboard import Injectionboard -from modules.nexysio import Nexysio -from modules.voltageboard import Voltageboard -from modules.decode import Decode - -from utils.utils import wait_progress - -import binascii - -def main(): - - nexys = Nexysio() - - # Open FTDI Device with Index 0 - #handle = nexys.open(0) - handle = nexys.autoopen() - - # Write and read directly to register - # Example: Write 0x55 to register 0x09 and read it back - nexys.write_register(0x09, 0x55, True) - nexys.read_register(0x09) - - # - # Configure ASIC - # - - # Write to asicSR - asic = Asic(handle) - asic.update_asic() - - # Example: Update Config Bit - # asic.digitalconfig['En_Inj17'] = 1 - # asic.dacs['vn1'] = 63 - # asic.update_asic() - - # - # Configure Voltageboard - # - - # Configure 8 DAC Voltageboard in Slot 4 with list values - # 3 = Vcasc2, 4=BL, 7=Vminuspix, 8=Thpix - vboard1 = Voltageboard(handle, 4, (8, [0, 0, 1.1, 1, 0, 0, 1, 1.1])) - - # Set measured 1V for one-point calibration - vboard1.vcal = 0.989 - vboard1.vsupply = 2.7 - - # Update voltageboards - vboard1.update_vb() - - # Write only first 3 DACs, other DACs will be 0 - # vboard1.dacvalues = (8, [1.2, 1, 1]) - # vboard1.update_vb() - - # - # Configure Injectionboard - # - - # Set Injection level - injvoltage = Voltageboard(handle, 3, (2, [0.3, 0.0])) - injvoltage.vcal = vboard1.vcal - injvoltage.vsupply = vboard1.vsupply - injvoltage.update_vb() - - inj = Injectionboard(handle) - - # Set Injection Params for 330MHz patgen clock - inj.period = 100 - inj.clkdiv = 4000 - inj.initdelay = 10000 - inj.cycle = 0 - inj.pulsesperset = 1 - - # - # SPI - # - - # Enable SPI - nexys.spi_enable() - nexys.spi_reset() - - # Set SPI clockdivider - # freq = 100 MHz/spi_clkdiv - nexys.spi_clkdiv = 255 - - asic.dacs['vn1'] = 5 - - # Generate bitvector for SPI ASIC config - asic_bitvector = asic.gen_asic_vector() - spi_data = nexys.asic_spi_vector(asic_bitvector, True, 10) - - # Write Config via spi - # nexys.write_spi(spi_data, False, 8191) - - # Send Routing command - nexys.send_routing_cmd() - - # Reset SPI Read FIFO - nexys.spi_reset() - - inj.start() - - wait_progress(3) - - # Write 8*1000 Bytes to MOSI - nexys.write_spi_bytes(1000) - - # Read (Width 8 Bytes) until read FIFO is empty - readout = nexys.read_spi_fifo() - - print(binascii.hexlify(readout)) - - decode = Decode() - list_hits = decode.hits_from_readoutstream(readout) - - decode.decode_astropix2_hits(list_hits) - - # inj.stop() - - # Close connection - nexys.close() - - -if __name__ == "__main__": - main() diff --git a/readback_test.py b/readback_test.py deleted file mode 100644 index 33ffcf36..00000000 --- a/readback_test.py +++ /dev/null @@ -1,159 +0,0 @@ -# -*- coding: utf-8 -*- -"""""" -""" -Created on Sat Jun 26 00:10:56 2021 - -@author: Nicolas Striebig -""" - -from modules.asic import Asic -from modules.injectionboard import Injectionboard -from modules.nexysio import Nexysio -from modules.voltageboard import Voltageboard -from modules.decode import Decode - -from utils.utils import wait_progress - -import binascii - -import os -import time - -def main(): - - nexys = Nexysio() - - # Open FTDI Device with Index 0 - #handle = nexys.open(0) - handle = nexys.autoopen() - - # Write and read directly to register - # Example: Write 0x55 to register 0x09 and read it back - nexys.write_register(0x09, 0x55, True) - nexys.read_register(0x09) - - nexys.spi_reset() - nexys.sr_readback_reset() - - # - # Configure ASIC - # - - # Write to asicSR - asic = Asic(handle) - asic.update_asic() - - # Example: Update Config Bit - # asic.digitalconfig['En_Inj17'] = 1 - # asic.dacs['vn1'] = 63 - # asic.update_asic() - - # - # Configure Voltageboard - # - - # Configure 8 DAC Voltageboard in Slot 4 with list values - # 3 = Vcasc2, 4=BL, 7=Vminuspix, 8=Thpix - vboard1 = Voltageboard(handle, 4, (8, [0, 0, 1.1, 1, 0, 0, 1, 1.2])) - - # Set measured 1V for one-point calibration - vboard1.vcal = 0.989 - vboard1.vsupply = 3.3 - - # Update voltageboards - vboard1.update_vb() - - # Write only first 3 DACs, other DACs will be 0 - # vboard1.dacvalues = (8, [1.2, 1, 1]) - # vboard1.update_vb() - - # - # Configure Injectionboard - # - - # Set Injection level - injvoltage = Voltageboard(handle, 3, (2, [0.4, 0.0])) - injvoltage.vcal = vboard1.vcal - injvoltage.vsupply = vboard1.vsupply - injvoltage.update_vb() - - inj = Injectionboard(handle) - - # Set Injection Params for 330MHz patgen clock - inj.period = 100 - inj.clkdiv = 400 - inj.initdelay = 10000 - inj.cycle = 0 - inj.pulsesperset = 1 - - # - # SPI - # - - # Enable SPI - nexys.spi_enable() - nexys.spi_reset() - - # Set SPI clockdivider - # freq = 100 MHz/spi_clkdiv - nexys.spi_clkdiv = 10 - - #asic.dacs['vn1'] = 5 - - # Generate bitvector for SPI ASIC config - asic_bitvector = asic.gen_asic_vector() - spi_data = nexys.asic_spi_vector(asic_bitvector, True, 10) - - # Write Config via spi - # nexys.write_spi(spi_data, False, 8191) - - # Send Routing command - nexys.send_routing_cmd() - - # Reset SPI Read FIFO - - inj.start() - #inj.stop() - - wait_progress(3) - - #decode = Decode() - - - """ i = 0 - while os.path.exists("log/sample%s.log" % i): - i += 1 - - file = open("log/sample%s.log" % i, "w") """ - timestr = time.strftime("beam_readback_%Y%m%d-%H%M%S") - file = open("log/%s.log" % timestr, "w") - file.write(f"Voltageboard settings: {vboard1.dacvalues}\n") - file.write(f"Digital: {asic.digitalconfig}\n") - file.write(f"Biasblock: {asic.biasconfig}\n") - file.write(f"DAC: {asic.dacs}\n") - file.write(f"Receiver: {asic.recconfig}\n") - - readout = bytearray() - nexys.sr_readback_reset() - - while True: - wait_progress(2) - print("Readback") - #Readback - asic.readback_asic() - #asic.update_asic() - wait_progress(1) - print(f"SR readbackConfig Reg: {nexys.read_register(0x3D)}\n") - readout = nexys.read_spi_fifo_readback() - - file.write(str(binascii.hexlify(readout))) - print(binascii.hexlify(readout)) - - # inj.stop() - - # Close connection - nexys.close() - - -if __name__ == "__main__": - main() From 7f5859f7b60a979927159cd8db6104c90395da1c Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Tue, 19 Jul 2022 14:55:43 -0400 Subject: [PATCH 24/78] Search for min 2 idle bytes to separate hits [hf] --- modules/decode.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/decode.py b/modules/decode.py index 7bc86e4f..c69ec198 100644 --- a/modules/decode.py +++ b/modules/decode.py @@ -33,9 +33,9 @@ def reverse_bitorder(self, data: bytearray): return data def find_idle_bytes_pos(self, readout: bytearray, - start_seq: bytearray = b'(\x3d{1,})[\0-\x0F]', #b'(\x3d*)', - idle_seq: bytearray = b'(\x3d{1,})[\0-\x0F]', - end_seq: bytearray = b'(\x3d{1,})') -> list: + start_seq: bytearray = b'(\x3d{2,})[\0-\x0F]', #b'(\x3d*)', + idle_seq: bytearray = b'(\x3d{2,})[\0-\x0F]', + end_seq: bytearray = b'(\x3d{2,})') -> list: """ Find idle Bytes From 71f0d86d8dd5aa34f95c6cb4625281403da25f7e Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Thu, 21 Jul 2022 11:03:52 -0400 Subject: [PATCH 25/78] read out shorter buffer --- beam_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beam_test.py b/beam_test.py index 2db7deb7..8833b209 100644 --- a/beam_test.py +++ b/beam_test.py @@ -122,9 +122,9 @@ def main(args): # We aren't using timeit, just measuring the diffrence in ns if args.timeit: start = time.time_ns() - time.sleep(.1) # this is probably not needed, will ask Nicolas + time.sleep(.001) # this is probably not needed, will ask Nicolas - readout = astro.get_readout() # Gets the bytearray from the chip + readout = astro.get_readout(3) # Gets the bytearray from the chip if args.timeit: print(f"Readout took {(time.time_ns()-start)*10**-9}s") From 7c65143885f050b77147ff1c4dcc439d62b6b156 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Thu, 21 Jul 2022 11:04:30 -0400 Subject: [PATCH 26/78] some example masks --- blankmask.txt => masks/blankmask.txt | 0 masks/mask_row0_col0.txt | 35 ++++++++++++++++++++++++++++ masks/mask_row0_col1.txt | 35 ++++++++++++++++++++++++++++ masks/mask_row1_col0.txt | 35 ++++++++++++++++++++++++++++ masks/mask_row1_col1.txt | 35 ++++++++++++++++++++++++++++ 5 files changed, 140 insertions(+) rename blankmask.txt => masks/blankmask.txt (100%) create mode 100644 masks/mask_row0_col0.txt create mode 100644 masks/mask_row0_col1.txt create mode 100644 masks/mask_row1_col0.txt create mode 100644 masks/mask_row1_col1.txt diff --git a/blankmask.txt b/masks/blankmask.txt similarity index 100% rename from blankmask.txt rename to masks/blankmask.txt diff --git a/masks/mask_row0_col0.txt b/masks/mask_row0_col0.txt new file mode 100644 index 00000000..6bb6ae16 --- /dev/null +++ b/masks/mask_row0_col0.txt @@ -0,0 +1,35 @@ +11111111111111111111111111111111110 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 \ No newline at end of file diff --git a/masks/mask_row0_col1.txt b/masks/mask_row0_col1.txt new file mode 100644 index 00000000..971b593d --- /dev/null +++ b/masks/mask_row0_col1.txt @@ -0,0 +1,35 @@ +11111111111111111111111111111111111 +11111111111111111111111111111111110 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 \ No newline at end of file diff --git a/masks/mask_row1_col0.txt b/masks/mask_row1_col0.txt new file mode 100644 index 00000000..0cefa020 --- /dev/null +++ b/masks/mask_row1_col0.txt @@ -0,0 +1,35 @@ +11111111111111111111111111111111101 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 \ No newline at end of file diff --git a/masks/mask_row1_col1.txt b/masks/mask_row1_col1.txt new file mode 100644 index 00000000..07874432 --- /dev/null +++ b/masks/mask_row1_col1.txt @@ -0,0 +1,35 @@ +11111111111111111111111111111111111 +11111111111111111111111111111111101 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 +11111111111111111111111111111111111 \ No newline at end of file From 002100b2d8a46f17bdc7578b7610db0301484c8e Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Thu, 21 Jul 2022 14:45:12 -0400 Subject: [PATCH 27/78] Fix injection settings; user-supplied values for analog readout column and injection column --- astropix.py | 57 +++++++++++++++++++++++++++------------------------- beam_test.py | 19 +++++++++++------- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/astropix.py b/astropix.py index 31aaf128..8ce0dfac 100644 --- a/astropix.py +++ b/astropix.py @@ -68,7 +68,7 @@ class astropix2: # Init just opens the chip and gets the handle. After this runs # asic_config also needs to be called to set it up. Seperating these # allows for simpler specifying of values. - def __init__(self, clock_period_ns = 10, inject:bool = False): + def __init__(self, clock_period_ns = 10, inject:int = None): """ Initalizes astropix object. No required arguments @@ -92,7 +92,7 @@ def __init__(self, clock_period_ns = 10, inject:bool = False): logger.info("FPGA test successful.") # Start putting the variables in for use down the line self.sampleclock_period_ns = clock_period_ns - self.inject = inject + self.injection_col = inject # Creates objects used later on self.decode = Decode(clock_period_ns) @@ -101,7 +101,7 @@ def __init__(self, clock_period_ns = 10, inject:bool = False): # Method to initalize the asic. This is taking the place of asic.py. # All of the interfacing is handeled through asic_update - def asic_init(self, dac_setup: dict = None, bias_setup:dict = None, digital_mask:str = None): + def asic_init(self, dac_setup: dict = None, bias_setup:dict = None, digital_mask:str = None, analog_col:int = None): """ self.asic_init() - initalize the asic configuration. Must be called first Positional arguments: None @@ -124,8 +124,8 @@ def asic_init(self, dac_setup: dict = None, bias_setup:dict = None, digital_mask self.bias_setup.update(bias_setup) if digital_mask is not None: - self._make_digital_mask(digital_mask) - else: self._make_analog_mask() + self._make_digital_mask(digital_mask,analog_col) + #else: self._make_analog_mask() self._make_digitalconfig() #self._make_digital_mask() @@ -151,7 +151,7 @@ def asic_update(self): # Methods to update the internal variables. Please don't do it manually # This updates the dac config - def update_asic_config(self, bias_cfg:dict = None, dac_cfg:dict = None, maskstr:str = None): + def update_asic_config(self, bias_cfg:dict = None, dac_cfg:dict = None, maskstr:str = None, analog_col:int=None): """ Updates and writes confgbits to asic @@ -164,7 +164,7 @@ def update_asic_config(self, bias_cfg:dict = None, dac_cfg:dict = None, maskstr: if dac_cfg is not None: self.dac_setup.update(dac_cfg) if maskstr is not None: - self._make_digital_mask(maskstr) + self._make_digital_mask(maskstr, analog_col) else: logger.info("update_asic_config() got no argumennts, nothing to do.") return None @@ -270,7 +270,7 @@ def init_injection(self, slot: int = 3, inj_voltage:float = None, inj_period:int dac_config:tuple[int, list[float]]: injdac settings. Must be fully specified if set. """ # Default configuration for the dac - # 0.4 is injection voltage + # 0.3 is injection voltage # 2 is slot number for inj board default_injdac = (2, [0.3, 0.0]) # Some fault tolerance @@ -291,7 +291,7 @@ def init_injection(self, slot: int = 3, inj_voltage:float = None, inj_period:int if inj_voltage < 0: raise ValueError("Cannot inject a negative voltage!") elif inj_voltage > 1800: - logger.warning("Cannot inject more than 1800mV!") + logger.warning("Cannot inject more than 1800mV, will use defaults") else: #Convert from mV to V inj_voltage = inj_voltage / 1000 @@ -479,20 +479,20 @@ def _make_digitalconfig(self): # Function to construct the reconfig dictionairy. This code is taken from # asic.py. # This simply sets it up for an analog run - def _make_analog_mask(self): - if self.inject: - bitconfig_col = 0b111_11111_11111_11111_11111_11111_11111_11101 #for injection - else: - bitconfig_col = 0b111_11111_11111_11111_11111_11111_11111_11100 #for noise - self.recconfig = {'ColConfig0': bitconfig_col} - i = 1 - while i < 35: - self.recconfig[f'ColConfig{i}'] = 0b001_11111_11111_11111_11111_11111_11111_11110 - i += 1 + #def _make_analog_mask(self): + # if self.inject: + # bitconfig_col = 0b111_11111_11111_11111_11111_11111_11111_11101 #for injection + # else: + # bitconfig_col = 0b111_11111_11111_11111_11111_11111_11111_11100 #for noise + # self.recconfig = {'ColConfig0': bitconfig_col} + # i = 1 + # while i < 35: + # self.recconfig[f'ColConfig{i}'] = 0b001_11111_11111_11111_11111_11111_11111_11110 + # i += 1 # used for digital working with the sensor. - def _make_digital_mask(self, digitmask:str): + def _make_digital_mask(self, digitmask:str, analog_col=None): # Cleans up the string, ensures it is only 1, 0, or \n bitmask = re.sub("[^01\n]", "", digitmask) # turn it into a list @@ -504,17 +504,20 @@ def _make_digital_mask(self, digitmask:str): self.recconfig = {} # used in construction i = 0 - # itterates through the list and does binairy magic on it + # iterates through the list and does binary magic on it for bits in bitlist: - # This works by adding entries to a dictionairy and then: + # This works by adding entries to a dictionary and then: # 1) creating a 35 bit space of zeros - # 2) converting the string to a binairy integer + # 2) converting the string to a binary integer # 3) shifting by one to make room for injection on/off bit - # 4) setting the injection bit if we want injection on this run - - self.recconfig[f"ColConfig{i}"] = (((0b00 << 35) + int(bits, 2)) << 1) + (0b1 if self.inject == True else 0) + # 4) setting the injection bit if we want injection on this run + # Bit 1: Analog output + # Bit 2: Injection + # last : Injection + analog_bit = 0b1 if (i == analog_col) else 0 + injection_bit = 0b1 if (i == self.injection_col) else 0 + self.recconfig[f"ColConfig{i}"] = (analog_bit << 37) + (injection_bit << 36) + (int(bits, 2) << 1) + injection_bit i += 1 - diff --git a/beam_test.py b/beam_test.py index 8833b209..e797ca7f 100644 --- a/beam_test.py +++ b/beam_test.py @@ -63,19 +63,19 @@ def main(args): # Passes mask if specified, else it creates an analog mask of (0,0) if masked: - astro.asic_init(digital_mask=bitmask) + astro.asic_init(digital_mask=bitmask, analog_col = args.analog) else: astro.asic_init() astro.init_voltages(vthreshold=args.threshold) # If injection is on initalize the board - if args.inject: + if args.inject is not None: astro.init_injection(inj_voltage=args.vinj) astro.enable_spi() logger.info("Chip configured") astro.dump_fpga() - if args.inject: + if args.inject is not None: astro.start_injection() @@ -106,6 +106,8 @@ def main(args): bitfile = open(bitpath,'w') # Writes all the config information to the file bitfile.write(astro.get_log_header()) + bitfile.write(str(args)) + bitfile.write("\n") # Enables the hitplotter and uses logic on whether or not to save the images if args.showhits: plotter = hitplotter.HitPlotter(35, outdir=(args.outdir if args.plotsave else None)) @@ -217,14 +219,17 @@ def main(args): default=False, required=False, help='save output files as CSV. If False, save as txt') - parser.add_argument('-i', '--inject', action='store_true',default=False, - help = 'Toggle injection on and off. DEFAULT: OFF') + parser.add_argument('-i', '--inject', action='store', default=None, type=int, + help = 'Turn on injection in the given column. Default: No injection') parser.add_argument('-v','--vinj', action='store', default = None, type=float, help = 'Specify injection voltage (in mV). DEFAULT 400 mV') - parser.add_argument('-m', '--mask', action='store', required=False, type=str, default = None, - help = 'filepath to digital mask. Required to enable pixels not (0,0)') + parser.add_argument('-m', '--mask', action='store', required=False, type=str, default = "./masks/mask_row0_col0.txt", + help = 'filepath to digital mask to enable digital readout. Default: No digital readout (all pixels off)') + + parser.add_argument('-a', '--analog', action='store', required=False, type=int, default = 0, + help = 'Turn on analog output in the given column. Default: Column 0. Set to None to turn off analog output.') parser.add_argument('-t', '--threshold', type = float, action='store', default=None, help = 'Threshold voltage for digital ToT (in mV). DEFAULT 100mV') From 6d2c4d43dbf552ca4e71412536be13526f2bf040 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Fri, 22 Jul 2022 11:10:56 -0400 Subject: [PATCH 28/78] include extra _ delimiter in name for easier readability --- beam_test.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/beam_test.py b/beam_test.py index e797ca7f..eab50d0c 100644 --- a/beam_test.py +++ b/beam_test.py @@ -82,10 +82,11 @@ def main(args): max_errors = args.errormax i = 0 errors = 0 # Sets the threshold + fname="" if not args.name else args.name+"_" # Prepares the file paths if args.saveascsv: # Here for csv - csvpath = args.outdir +'/' + args.name + time.strftime("%Y%m%d-%H%M%S") + '.csv' + csvpath = args.outdir +'/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.csv' csvframe =pd.DataFrame(columns = [ 'readout', 'Chip ID', @@ -101,7 +102,7 @@ def main(args): ]) # And here for the text files/logs - bitpath = args.outdir + '/' + args.name + time.strftime("%Y%m%d-%H%M%S") + '.log' + bitpath = args.outdir + '/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.log' # textfiles are always saved so we open it up bitfile = open(bitpath,'w') # Writes all the config information to the file @@ -120,6 +121,7 @@ def main(args): if args.maxruns is not None: if i >= args.maxruns: break + if astro.hits_present(): # Checks if hits are present # We aren't using timeit, just measuring the diffrence in ns if args.timeit: start = time.time_ns() From b37136dc8535b5ca43bff544810f16bf087daf3f Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Fri, 22 Jul 2022 11:20:36 -0400 Subject: [PATCH 29/78] add runtime argument for set length of time per run: -M, --maxtime (--maxruns now -r) [AS] --- beam_test.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/beam_test.py b/beam_test.py index eab50d0c..52ee039d 100644 --- a/beam_test.py +++ b/beam_test.py @@ -82,6 +82,8 @@ def main(args): max_errors = args.errormax i = 0 errors = 0 # Sets the threshold + if args.maxtime is not None: + end_time=time.time()+(args.maxtime*60.) fname="" if not args.name else args.name+"_" # Prepares the file paths @@ -120,6 +122,8 @@ def main(args): # This might be possible to do in the loop declaration, but its a lot easier to simply add in this logic if args.maxruns is not None: if i >= args.maxruns: break + if args.maxtime is not None: + if time.time() >= end_time: break if astro.hits_present(): # Checks if hits are present @@ -239,9 +243,12 @@ def main(args): parser.add_argument('-E', '--errormax', action='store', type=int, default='0', help='Maximum index errors allowed during decoding. DEFAULT 0') - parser.add_argument('-M', '--maxruns', type=int, action='store', default=None, + parser.add_argument('-r', '--maxruns', type=int, action='store', default=None, help = 'Maximum number of readouts') + parser.add_argument('-M', '--maxtime', type=float, action='store', default=None, + help = 'Maximum run time (in minutes)') + parser.add_argument('--timeit', action="store_true", default=False, help='Prints runtime from seeing a hit to finishing the decode to terminal') From 08781d607f578e67443cfea0ed6c9df536f8703f Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Mon, 25 Jul 2022 15:54:28 -0400 Subject: [PATCH 30/78] vthreshold command line argument takes prescedence over dacvals full array argument --- astropix.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/astropix.py b/astropix.py index 8ce0dfac..1379f998 100644 --- a/astropix.py +++ b/astropix.py @@ -125,7 +125,6 @@ def asic_init(self, dac_setup: dict = None, bias_setup:dict = None, digital_mask if digital_mask is not None: self._make_digital_mask(digital_mask,analog_col) - #else: self._make_analog_mask() self._make_digitalconfig() #self._make_digital_mask() @@ -144,6 +143,7 @@ def asic_update(self): Takes no input and does not return """ + self.nexys.chip_reset() asicbits = self.nexys.gen_asic_pattern(self._construct_asic_vector(), True) self.nexys.write(asicbits) logger.info("Wrote configbits successfully") @@ -218,12 +218,12 @@ def init_voltages(self, slot: int = 4, vcal:float = .989, vsupply: float = 2.7, # used to ensure this has been called in the right order: self._voltages_exist = True + # Set dacvals if dacvals is None: dacvals = default_vdac # dacvals takes precidence over vthreshold - - elif vthreshold is not None: + if vthreshold is not None: # Turns from mV to V with the 1V offset normally present vthreshold = (vthreshold/1000) + 1 if vthreshold > 1.5 or vthreshold < 0: @@ -233,6 +233,7 @@ def init_voltages(self, slot: int = 4, vcal:float = .989, vsupply: float = 2.7, logger.error("Threshold value too low, setting to default 100mV") dacvals[1][-1] = vthreshold + # Create object self.vboard = Voltageboard(self.handle, slot, dacvals) # Set calibrated values @@ -478,17 +479,6 @@ def _make_digitalconfig(self): # Function to construct the reconfig dictionairy. This code is taken from # asic.py. - # This simply sets it up for an analog run - #def _make_analog_mask(self): - # if self.inject: - # bitconfig_col = 0b111_11111_11111_11111_11111_11111_11111_11101 #for injection - # else: - # bitconfig_col = 0b111_11111_11111_11111_11111_11111_11111_11100 #for noise - # self.recconfig = {'ColConfig0': bitconfig_col} - # i = 1 - # while i < 35: - # self.recconfig[f'ColConfig{i}'] = 0b001_11111_11111_11111_11111_11111_11111_11110 - # i += 1 # used for digital working with the sensor. From 3b31dd01a0aec25f0712b9bb0fa52bf7a8da9291 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Mon, 25 Jul 2022 15:54:57 -0400 Subject: [PATCH 31/78] change default errormax value to 100 - dont want runs to end while on bench --- beam_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beam_test.py b/beam_test.py index 52ee039d..95ec7c82 100644 --- a/beam_test.py +++ b/beam_test.py @@ -240,8 +240,8 @@ def main(args): parser.add_argument('-t', '--threshold', type = float, action='store', default=None, help = 'Threshold voltage for digital ToT (in mV). DEFAULT 100mV') - parser.add_argument('-E', '--errormax', action='store', type=int, default='0', - help='Maximum index errors allowed during decoding. DEFAULT 0') + parser.add_argument('-E', '--errormax', action='store', type=int, default='100', + help='Maximum index errors allowed during decoding. DEFAULT 100') parser.add_argument('-r', '--maxruns', type=int, action='store', default=None, help = 'Maximum number of readouts') From dc922bb89da3a052aa9dc1672b7d6caaa042fe82 Mon Sep 17 00:00:00 2001 From: Autumn Bauman Date: Mon, 25 Jul 2022 16:40:03 -0400 Subject: [PATCH 32/78] Fixed Vthresh setting --- astropix.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/astropix.py b/astropix.py index 8ce0dfac..0e8772d5 100644 --- a/astropix.py +++ b/astropix.py @@ -221,18 +221,16 @@ def init_voltages(self, slot: int = 4, vcal:float = .989, vsupply: float = 2.7, # Set dacvals if dacvals is None: dacvals = default_vdac - # dacvals takes precidence over vthreshold - - elif vthreshold is not None: - # Turns from mV to V with the 1V offset normally present - vthreshold = (vthreshold/1000) + 1 - if vthreshold > 1.5 or vthreshold < 0: - logger.warning("Threshold voltage out of range of sensor!") - if vthreshold <= 0: - vthreshold = 1.100 - logger.error("Threshold value too low, setting to default 100mV") - - dacvals[1][-1] = vthreshold + # dacvals takes precidence over vthreshold + if vthreshold is not None: + # Turns from mV to V with the 1V offset normally present + vthreshold = (vthreshold/1000) + 1 + if vthreshold > 1.5 or vthreshold < 0: + logger.warning("Threshold voltage out of range of sensor!") + if vthreshold <= 0: + vthreshold = 1.100 + logger.error("Threshold value too low, setting to default 100mV") + dacvals[1][-1] = vthreshold # Create object self.vboard = Voltageboard(self.handle, slot, dacvals) # Set calibrated values From 0fc36248efdf180519afefb18537e58811428462 Mon Sep 17 00:00:00 2001 From: Autumn Bauman <63873018+Narg3000@users.noreply.github.com> Date: Tue, 2 Aug 2022 11:18:58 -0400 Subject: [PATCH 33/78] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a02ca897..d93f95d3 100644 --- a/README.md +++ b/README.md @@ -143,10 +143,10 @@ Options: | `-s` `--showhits` | `-s` | Display hits in real time | Off | | `-p` `--plotsave` | `-p` | Saves real time plots as image files. Stored in outdir. | Does not save plots | | `-t` `--threshold`| `-t [VOLTAGE]`| Sets digital threshold voltage in mV. | `100mV` | -| `-i` `--inject`| `-i` | Toggles injection on or off. Injects 300mV unless specified. | Off| +| `-i` `--inject`| `-i` [COL] | Toggles injection on or off at specified column. Injects 300mV unless specified. | Off| +| `-v` `--vinj` | `-v [VOLTAGE]` | Sets voltage of injection in mV. Does not enable injection. | `300mV` | | `-M` `--maxruns` | `-M [int]` | Sets the maximum number of readouts the code will process before exiting. | No maximum | | `-E` `--errormax`| `-E [int]` | Amount of index errors encountered in the decode before the program terminates. | `0` | -| `-v` `--vinj` | `-v [VOLTAGE]` | Sets voltage of injection in mV. Does not enable injection. | `300mV` | | `-a` `--analog` | `-a [COL]` | Enable analog output on specified column | `None` | | `-L` `--loglevel` | `-L [D,I,E,W,C]`| Loglevel to be stored. Applies to both console and file. Options: D - debug, I - info, E - error, W - warning, C - critical | `I` | | `--timeit` | `--timeit` | Measures the time it took to decode and store a hitstream. | Off | From e71b7b95aeabc425616364b9849b93837c930fec Mon Sep 17 00:00:00 2001 From: Autumn Bauman <63873018+Narg3000@users.noreply.github.com> Date: Tue, 2 Aug 2022 11:19:42 -0400 Subject: [PATCH 34/78] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d93f95d3..75cf3330 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ Options: | `-s` `--showhits` | `-s` | Display hits in real time | Off | | `-p` `--plotsave` | `-p` | Saves real time plots as image files. Stored in outdir. | Does not save plots | | `-t` `--threshold`| `-t [VOLTAGE]`| Sets digital threshold voltage in mV. | `100mV` | -| `-i` `--inject`| `-i` [COL] | Toggles injection on or off at specified column. Injects 300mV unless specified. | Off| +| `-i` `--inject`| `-i [COL]` | Toggles injection on or off at specified column. Injects 300mV unless specified. | Off| | `-v` `--vinj` | `-v [VOLTAGE]` | Sets voltage of injection in mV. Does not enable injection. | `300mV` | | `-M` `--maxruns` | `-M [int]` | Sets the maximum number of readouts the code will process before exiting. | No maximum | | `-E` `--errormax`| `-E [int]` | Amount of index errors encountered in the decode before the program terminates. | `0` | From 48c2f16f634348c0b0aa8afad5f4321c641d3774 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Mon, 8 Aug 2022 14:21:03 -0400 Subject: [PATCH 35/78] Fix stop injection condition [hf] --- beam_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beam_test.py b/beam_test.py index 95ec7c82..e0beaf2c 100644 --- a/beam_test.py +++ b/beam_test.py @@ -196,7 +196,7 @@ def main(args): if args.saveascsv: csvframe.index.name = "dec_order" csvframe.to_csv(csvpath) - if args.inject: astro.stop_injection() + if args.inject is not None: astro.stop_injection() bitfile.close() # Close open file if args.inject: astro.stop_injection() #stops injection astro.close_connection() # Closes SPI logger.info("Program terminated successfully") From eb46c0826230891cd56c3c032682c9eeb9d78941 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Mon, 8 Aug 2022 16:01:50 -0400 Subject: [PATCH 36/78] fix injection, set injection and analog independent of digital mask [hf] --- astropix.py | 27 ++++++++++++++++++++++----- beam_test.py | 4 ++-- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/astropix.py b/astropix.py index 52b19d1f..27e86ae5 100644 --- a/astropix.py +++ b/astropix.py @@ -95,7 +95,10 @@ def __init__(self, clock_period_ns = 10, inject:int = None): logger.info("FPGA test successful.") # Start putting the variables in for use down the line self.sampleclock_period_ns = clock_period_ns - self.injection_col = inject + if inject is None: + inject = (None, None) + self.injection_col = inject[1] + self.injection_row = inject[0] # Creates objects used later on self.decode = Decode(clock_period_ns) @@ -131,7 +134,7 @@ def asic_init(self, dac_setup: dict = None, bias_setup:dict = None, digital_mask # If maskstring is passed it will mask based on that if digital_mask is not None: - self._mask_from_string(digital_mask, analog_col) + self._mask_from_string(digital_mask, None) # If blankmask is set it will create a blank mask. Can be updated with # self.enable_pixel() and whatnot else: @@ -139,16 +142,29 @@ def asic_init(self, dac_setup: dict = None, bias_setup:dict = None, digital_mask # Turns on selected analog outputs if not a blank output if blankmask == False: if (analog_col is not None) and (analog_col <= self._num_cols): + logger.info(f"enabling anlalog output in column {analog_col}") self.enable_ampout_col(analog_col) self.enable_pixel(analog_col, ) else: # Enables pixel 0,0 only + logger.info(f"enabling anlalog output in column 0 (default)") self.enable_ampout_col(0) self.enable_pixel(0,0) # Turns on injection if so desired if self.injection_col is not None: self.enable_inj_col(self.injection_col) + ##analog output + if (analog_col is not None) and (analog_col <= self._num_cols): + logger.info(f"enabling anlalog output in column {analog_col}") + self.enable_ampout_col(analog_col, inplace=False) + + # Turns on injection if so desired + if self.injection_col is not None: + self.enable_inj_col(self.injection_col, inplace=False) + self.enable_inj_row(self.injection_row, inplace=False) + + self._make_digitalconfig() #self._make_digital_mask() # Loads it to the chip @@ -527,8 +543,9 @@ def _mask_from_string(self, digitmask:str, analog_col=None): # Bit 2: Injection # last : Injection analog_bit = 0b1 if (i == analog_col) else 0 - injection_bit = 0b1 if (i == self.injection_col) else 0 - self.recconfig[f"ColConfig{i}"] = (analog_bit << 37) + (injection_bit << 36) + (int(bits, 2) << 1) + injection_bit + injection_bit_col = 0 if (i == self.injection_col) else 0 + injection_bit_row = 0 if (i == self.injection_row) else 0 + self.recconfig[f"ColConfig{i}"] = (analog_bit << 37) + (injection_bit_col << 36) + (int(bits, 2) << 1) + injection_bit_row i += 1 def _make_blank_mask(self): @@ -601,7 +618,7 @@ def enable_ampout_col(self, col: int, inplace:bool=True): for i in range(self.get_num_cols()): if not i == col: - self.recconfig[f'ColConfig{i}'] = self.recconfig.get(f'ColConfig{col}') & 0b011_11111_11111_11111_11111_11111_11111_11111 + self.recconfig[f'ColConfig{i}'] = self.recconfig.get(f'ColConfig{i}', 0b001_11111_11111_11111_11111_11111_11111_11110) & 0b011_11111_11111_11111_11111_11111_11111_11111 if inplace: self.asic_update() def enable_pixel(self, col: int, row: int, inplace:bool=True): diff --git a/beam_test.py b/beam_test.py index e0beaf2c..de613794 100644 --- a/beam_test.py +++ b/beam_test.py @@ -225,8 +225,8 @@ def main(args): default=False, required=False, help='save output files as CSV. If False, save as txt') - parser.add_argument('-i', '--inject', action='store', default=None, type=int, - help = 'Turn on injection in the given column. Default: No injection') + parser.add_argument('-i', '--inject', action='store', default=None, type=int, nargs=2, + help = 'Turn on injection in the given row and column. Default: No injection') parser.add_argument('-v','--vinj', action='store', default = None, type=float, help = 'Specify injection voltage (in mV). DEFAULT 400 mV') From fe065b0bbaded1a0169c2dd80626c54542887deb Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Mon, 8 Aug 2022 16:20:13 -0400 Subject: [PATCH 37/78] simplify code when no maks is provided --- astropix.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/astropix.py b/astropix.py index 27e86ae5..a1764d4f 100644 --- a/astropix.py +++ b/astropix.py @@ -115,7 +115,7 @@ def asic_init(self, dac_setup: dict = None, bias_setup:dict = None, digital_mask dac_setup: dict - dictionairy of values passed to the configuration. Only needs values diffent from defaults bias_setup: dict - dict of values for the bias configuration Only needs key/vals for changes from default digital_mask: str - String of 1s and 0s in 35x35 arangement which masks the array. Needed to enable pixels not (0,0) - blankmask: bool - Create a blank mask (everything disabled) and things will be disabled manually + blankmask: bool - Create a blank mask (everything disabled). Pixels can be enabled manually analog_col: int - Sets a column to readout analog data from. """ @@ -139,20 +139,6 @@ def asic_init(self, dac_setup: dict = None, bias_setup:dict = None, digital_mask # self.enable_pixel() and whatnot else: self._make_blank_mask() - # Turns on selected analog outputs if not a blank output - if blankmask == False: - if (analog_col is not None) and (analog_col <= self._num_cols): - logger.info(f"enabling anlalog output in column {analog_col}") - self.enable_ampout_col(analog_col) - self.enable_pixel(analog_col, ) - - else: # Enables pixel 0,0 only - logger.info(f"enabling anlalog output in column 0 (default)") - self.enable_ampout_col(0) - self.enable_pixel(0,0) - # Turns on injection if so desired - if self.injection_col is not None: - self.enable_inj_col(self.injection_col) ##analog output if (analog_col is not None) and (analog_col <= self._num_cols): From ddefe63d698b3bc201fc04dcb9789c255257d9a2 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Mon, 8 Aug 2022 16:21:54 -0400 Subject: [PATCH 38/78] simplify code --- beam_test.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/beam_test.py b/beam_test.py index de613794..08239abe 100644 --- a/beam_test.py +++ b/beam_test.py @@ -170,12 +170,9 @@ def main(args): if pd.isnull(hits.tot_msb.loc(0)): pass elif len(hits)>0:#safeguard against bad readouts without recorded decodable hits - rows,columns=[],[] #Isolate row and column information from array returned from decoder - location = hits.location.to_numpy() - rowOrCol = hits.isCol.to_numpy() - rows = location[rowOrCol==False] - columns = location[rowOrCol==True] + rows = hits.location[~hits.isCol] + columns = hits.location[hits.isCol] plotter.plot_event( rows, columns, i) # If we are logging runtime, this does it! From b2e82131e76d213a34561287f65e79a0fe900f6f Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Mon, 8 Aug 2022 17:06:52 -0400 Subject: [PATCH 39/78] WIP script to loop over indiviual pixels and record digital data, based off beam_test.py - evolved functionality to come [AS] --- example_loop.py | 249 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 example_loop.py diff --git a/example_loop.py b/example_loop.py new file mode 100644 index 00000000..72f1c03e --- /dev/null +++ b/example_loop.py @@ -0,0 +1,249 @@ +""" +Simple script to loop through pixels enabling one at a time, using astropix.py +Author: Amanda Steinhebel +""" + +#from msilib.schema import File +#from http.client import SWITCHING_PROTOCOLS +from astropix import astropix2 +import modules.hitplotter as hitplotter +import os +import binascii +import pandas as pd +import numpy as np +import time +import logging +import argparse + +from modules.setup_logger import logger + + +# This sets the logger name. +logdir = "./runlogs/" +if os.path.exists(logdir) == False: + os.mkdir(logdir) +logname = "./runlogs/AstropixRunlog_" + time.strftime("%Y%m%d-%H%M%S") + ".log" + + + +# This is the dataframe which is written to the csv if the decoding fails +decode_fail_frame = pd.DataFrame({ + 'readout': np.nan, + 'Chip ID': np.nan, + 'payload': np.nan, + 'location': np.nan, + 'isCol': np.nan, + 'timestamp': np.nan, + 'tot_msb': np.nan, + 'tot_lsb': np.nan, + 'tot_total': np.nan, + 'tot_us': np.nan, + 'hittime': np.nan + }, index=[0] +) + + + +#Init +def main(args,row,col): + + # Ensures output directory exists + if os.path.exists(args.outdir) == False: + os.mkdir(args.outdir) + + # Prepare everything, create the object + astro = astropix2(inject=args.inject) + + # Initialie asic - blank array, no pixels enabled + astro.asic_init() + + #Enable single pixel in (col,row) + #Updates asic by default + astro.enable_pixel(col,row) + + astro.init_voltages(vthreshold=args.threshold) + # If injection is on initalize the board + if args.inject is not None: + astro.init_injection(inj_voltage=args.vinj) + astro.enable_spi() + logger.info("Chip configured") + astro.dump_fpga() + + if args.inject is not None: + astro.start_injection() + + + max_errors = args.errormax + i = 0 + errors = 0 # Sets the threshold + if args.maxtime is not None: + end_time=time.time()+(args.maxtime*60.) + fname="" if not args.name else args.name+"_" + + # Prepares the file paths + if args.saveascsv: # Here for csv + csvpath = args.outdir +'/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.csv' + csvframe =pd.DataFrame(columns = [ + 'readout', + 'Chip ID', + 'payload', + 'location', + 'isCol', + 'timestamp', + 'tot_msb', + 'tot_lsb', + 'tot_total', + 'tot_us', + 'hittime' + ]) + + # And here for the text files/logs + bitpath = args.outdir + '/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.log' + # textfiles are always saved so we open it up + bitfile = open(bitpath,'w') + # Writes all the config information to the file + bitfile.write(astro.get_log_header()) + bitfile.write(str(args)) + bitfile.write("\n") + + try: # By enclosing the main loop in try/except we are able to capture keyboard interupts cleanly + + while errors <= max_errors: # Loop continues + + # This might be possible to do in the loop declaration, but its a lot easier to simply add in this logic + if args.maxruns is not None: + if i >= args.maxruns: break + if args.maxtime is not None: + if time.time() >= end_time: break + + + if astro.hits_present(): # Checks if hits are present + + time.sleep(.001) # this is probably not needed, will ask Nicolas + + readout = astro.get_readout(3) # Gets the bytearray from the chip + + # Writes the hex version to hits + bitfile.write(f"{i}\t{str(binascii.hexlify(readout))}\n") + print(binascii.hexlify(readout)) + + # Added fault tolerance for decoding, the limits of which are set through arguments + try: + hits = astro.decode_readout(readout, i, printer = True) + + except IndexError: + errors += 1 + logger.warning(f"Decoding failed. Failure {errors} of {max_errors} on readout {i}") + # We write out the failed decode dataframe + hits = decode_fail_frame + hits.readout = i + hits.hittime = time.time() + + # This loggs the end of it all + if errors > max_errors: + logger.warning(f"Decoding failed {errors} times on an index error. Terminating Progam...") + finally: + i += 1 + + # If we are saving a csv this will write it out. + if args.saveascsv: + csvframe = pd.concat([csvframe, hits]) + + # If no hits are present this waits for some to accumulate + else: time.sleep(.001) + + + # Ends program cleanly when a keyboard interupt is sent. + except KeyboardInterrupt: + logger.info("Keyboard interupt. Program halt!") + # Catches other exceptions + except Exception as e: + logger.exception(f"Encountered Unexpected Exception! \n{e}") + finally: + if args.saveascsv: + csvframe.index.name = "dec_order" + csvframe.to_csv(csvpath) + if args.inject is not None: astro.stop_injection() + bitfile.close() # Close open file if args.inject: astro.stop_injection() #stops injection + astro.close_connection() # Closes SPI + logger.info("Program terminated successfully") + # END OF PROGRAM + + + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='Astropix Driver Code') + parser.add_argument('-n', '--name', default='', required=False, + help='Option to give additional name to output files upon running') + + parser.add_argument('-o', '--outdir', default='.', required=False, + help='Output Directory for all datafiles') + + parser.add_argument('-c', '--saveascsv', action='store_true', + default=False, required=False, + help='save output files as CSV. If False, save as txt') + + parser.add_argument('-i', '--inject', action='store', default=None, type=int, nargs=2, + help = 'Turn on injection in the given row and column. Default: No injection') + + parser.add_argument('-v','--vinj', action='store', default = None, type=float, + help = 'Specify injection voltage (in mV). DEFAULT 400 mV') + + parser.add_argument('-a', '--analog', action='store', required=False, type=int, default = 0, + help = 'Turn on analog output in the given column. Default: Column 0. Set to None to turn off analog output.') + + parser.add_argument('-t', '--threshold', type = float, action='store', default=None, + help = 'Threshold voltage for digital ToT (in mV). DEFAULT 100mV') + + parser.add_argument('-E', '--errormax', action='store', type=int, default='100', + help='Maximum index errors allowed during decoding. DEFAULT 100') + + parser.add_argument('-r', '--maxruns', type=int, action='store', default=None, + help = 'Maximum number of readouts') + + parser.add_argument('-M', '--maxtime', type=float, action='store', default=None, + help = 'Maximum run time (in minutes)') + + parser.add_argument('-L', '--loglevel', type=str, choices = ['D', 'I', 'E', 'W', 'C'], action="store", default='I', + help='Set loglevel used. Options: D - debug, I - info, E - error, W - warning, C - critical. DEFAULT: D') + + parser.add_argument + args = parser.parse_args() + + # Sets the loglevel + ll = args.loglevel + if ll == 'D': + loglevel = logging.DEBUG + elif ll == 'I': + loglevel = logging.INFO + elif ll == 'E': + loglevel = logging.ERROR + elif ll == 'W': + loglevel = logging.WARNING + elif ll == 'C': + loglevel = logging.CRITICAL + + # Logging stuff! + # This was way harder than I expected... + formatter = logging.Formatter('%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s') + fh = logging.FileHandler(logname) + fh.setFormatter(formatter) + sh = logging.StreamHandler() + sh.setFormatter(formatter) + + logging.getLogger().addHandler(sh) + logging.getLogger().addHandler(fh) + logging.getLogger().setLevel(loglevel) + + logger = logging.getLogger(__name__) + + rows=[i for i in range(3)] + cols=[j for j in range(3)] + for r in rows: + for c in cols: + print(args.inject) + args.inject=[r,c] + main(args,r,c) + time.sleep(2) \ No newline at end of file From 9cc5a6dfc1582361acd0de9af5877473a03cd512 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Tue, 9 Aug 2022 11:35:45 -0400 Subject: [PATCH 40/78] Finalize example_loop.py to loop over pixels and enable a single one at a time; ability to toggle injections on/off and input range of row/col to loop over [AS] --- example_loop.py | 79 ++++++++++++++++++------------------------------- 1 file changed, 29 insertions(+), 50 deletions(-) diff --git a/example_loop.py b/example_loop.py index 72f1c03e..3b92cc05 100644 --- a/example_loop.py +++ b/example_loop.py @@ -1,5 +1,7 @@ """ Simple script to loop through pixels enabling one at a time, using astropix.py +Based off beam_test.py + Author: Amanda Steinhebel """ @@ -25,7 +27,6 @@ logname = "./runlogs/AstropixRunlog_" + time.strftime("%Y%m%d-%H%M%S") + ".log" - # This is the dataframe which is written to the csv if the decoding fails decode_fail_frame = pd.DataFrame({ 'readout': np.nan, @@ -45,14 +46,17 @@ #Init -def main(args,row,col): +def main(args,row,col,injectPix): # Ensures output directory exists if os.path.exists(args.outdir) == False: os.mkdir(args.outdir) # Prepare everything, create the object - astro = astropix2(inject=args.inject) + if args.inject: + astro = astropix2(inject=injectPix) #enable injections + else: + astro = astropix2() #initialize without enabling injections # Initialie asic - blank array, no pixels enabled astro.asic_init() @@ -63,22 +67,20 @@ def main(args,row,col): astro.init_voltages(vthreshold=args.threshold) # If injection is on initalize the board - if args.inject is not None: + if args.inject: astro.init_injection(inj_voltage=args.vinj) astro.enable_spi() logger.info("Chip configured") astro.dump_fpga() - if args.inject is not None: + if args.inject: astro.start_injection() - - max_errors = args.errormax i = 0 - errors = 0 # Sets the threshold if args.maxtime is not None: end_time=time.time()+(args.maxtime*60.) - fname="" if not args.name else args.name+"_" + strPix = "_col"+str(col)+"_row"+str(row) + fname=strPix if not args.name else args.name+strPix+"_" # Prepares the file paths if args.saveascsv: # Here for csv @@ -108,9 +110,9 @@ def main(args,row,col): try: # By enclosing the main loop in try/except we are able to capture keyboard interupts cleanly - while errors <= max_errors: # Loop continues + while (True): # Loop continues - # This might be possible to do in the loop declaration, but its a lot easier to simply add in this logic + # Break conditions if args.maxruns is not None: if i >= args.maxruns: break if args.maxtime is not None: @@ -132,16 +134,11 @@ def main(args,row,col): hits = astro.decode_readout(readout, i, printer = True) except IndexError: - errors += 1 - logger.warning(f"Decoding failed. Failure {errors} of {max_errors} on readout {i}") # We write out the failed decode dataframe hits = decode_fail_frame hits.readout = i hits.hittime = time.time() - # This loggs the end of it all - if errors > max_errors: - logger.warning(f"Decoding failed {errors} times on an index error. Terminating Progam...") finally: i += 1 @@ -163,8 +160,8 @@ def main(args,row,col): if args.saveascsv: csvframe.index.name = "dec_order" csvframe.to_csv(csvpath) - if args.inject is not None: astro.stop_injection() - bitfile.close() # Close open file if args.inject: astro.stop_injection() #stops injection + if args.inject: astro.stop_injection() + bitfile.close() # Close open file astro.close_connection() # Closes SPI logger.info("Program terminated successfully") # END OF PROGRAM @@ -185,20 +182,14 @@ def main(args,row,col): default=False, required=False, help='save output files as CSV. If False, save as txt') - parser.add_argument('-i', '--inject', action='store', default=None, type=int, nargs=2, - help = 'Turn on injection in the given row and column. Default: No injection') + parser.add_argument('-i', '--inject', action='store_true', default=False, required=False, + help = 'Turn on injection. Default: No injection') parser.add_argument('-v','--vinj', action='store', default = None, type=float, help = 'Specify injection voltage (in mV). DEFAULT 400 mV') - parser.add_argument('-a', '--analog', action='store', required=False, type=int, default = 0, - help = 'Turn on analog output in the given column. Default: Column 0. Set to None to turn off analog output.') - parser.add_argument('-t', '--threshold', type = float, action='store', default=None, help = 'Threshold voltage for digital ToT (in mV). DEFAULT 100mV') - - parser.add_argument('-E', '--errormax', action='store', type=int, default='100', - help='Maximum index errors allowed during decoding. DEFAULT 100') parser.add_argument('-r', '--maxruns', type=int, action='store', default=None, help = 'Maximum number of readouts') @@ -206,27 +197,17 @@ def main(args,row,col): parser.add_argument('-M', '--maxtime', type=float, action='store', default=None, help = 'Maximum run time (in minutes)') - parser.add_argument('-L', '--loglevel', type=str, choices = ['D', 'I', 'E', 'W', 'C'], action="store", default='I', - help='Set loglevel used. Options: D - debug, I - info, E - error, W - warning, C - critical. DEFAULT: D') + parser.add_argument('-C', '--colrange', action='store', default=[0,33], type=int, nargs=2, + help = 'Loop over given range of columns. Default: 0 34') + + parser.add_argument('-R', '--rowrange', action='store', default=[0,33], type=int, nargs=2, + help = 'Loop over given range of rows. Default: 0 34') parser.add_argument args = parser.parse_args() - - # Sets the loglevel - ll = args.loglevel - if ll == 'D': - loglevel = logging.DEBUG - elif ll == 'I': - loglevel = logging.INFO - elif ll == 'E': - loglevel = logging.ERROR - elif ll == 'W': - loglevel = logging.WARNING - elif ll == 'C': - loglevel = logging.CRITICAL # Logging stuff! - # This was way harder than I expected... + loglevel = logging.INFO formatter = logging.Formatter('%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s') fh = logging.FileHandler(logname) fh.setFormatter(formatter) @@ -239,11 +220,9 @@ def main(args,row,col): logger = logging.getLogger(__name__) - rows=[i for i in range(3)] - cols=[j for j in range(3)] - for r in rows: - for c in cols: - print(args.inject) - args.inject=[r,c] - main(args,r,c) - time.sleep(2) \ No newline at end of file + #loop over full array by default, unless bounds are given as argument + for r in range(args.rowrange[0],args.rowrange[1]+1,1): + for c in range(args.colrange[0],args.colrange[1]+1,1): + injectPix=[r,c] + main(args,r,c,injectPix) + time.sleep(2) # to avoid loss of connection to Nexys \ No newline at end of file From 68a9a6fb1b9de98a64da1f007fa830b1b88b897b Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Tue, 9 Aug 2022 12:21:24 -0400 Subject: [PATCH 41/78] code to loop through user-defined range for a single defined DAC; run with injections or over noise; measure a single pixel [AS] --- loop_DACs.py | 234 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 loop_DACs.py diff --git a/loop_DACs.py b/loop_DACs.py new file mode 100644 index 00000000..c0c756ed --- /dev/null +++ b/loop_DACs.py @@ -0,0 +1,234 @@ +""" +Simple script to loop through DAC settings, enabling one single pixel at a time using astropix.py +Based off beam_test.py + +Author: Amanda Steinhebel +""" + +#from msilib.schema import File +#from http.client import SWITCHING_PROTOCOLS +from astropix import astropix2 +import modules.hitplotter as hitplotter +import os +import binascii +import pandas as pd +import numpy as np +import time +import logging +import argparse + +from modules.setup_logger import logger + + +# This sets the logger name. +logdir = "./runlogs/" +if os.path.exists(logdir) == False: + os.mkdir(logdir) +logname = "./runlogs/AstropixRunlog_" + time.strftime("%Y%m%d-%H%M%S") + ".log" + + +# This is the dataframe which is written to the csv if the decoding fails +decode_fail_frame = pd.DataFrame({ + 'readout': np.nan, + 'Chip ID': np.nan, + 'payload': np.nan, + 'location': np.nan, + 'isCol': np.nan, + 'timestamp': np.nan, + 'tot_msb': np.nan, + 'tot_lsb': np.nan, + 'tot_total': np.nan, + 'tot_us': np.nan, + 'hittime': np.nan + }, index=[0] +) + + + +#Init +def main(args,dac): + + # Ensures output directory exists + if os.path.exists(args.outdir) == False: + os.mkdir(args.outdir) + + # Prepare everything, create the object + if args.inject: + astro = astropix2(inject=args.pixel) #enable injections + else: + astro = astropix2() #initialize without enabling injections + + # Initialie asic - blank array, no pixels enabled, analog enabled for defined pixel or (0,0) by default + if args.DAC!="": + astro.asic_init(dac_setup={args.DAC: dac},analog_col = args.analog) + else: + astro.asic_init(analog_col = args.analog) + + #Enable single pixel from argument, or (0,0) if no pixel given + astro.enable_pixel(args.pixel[1],args.pixel[0]) + + astro.init_voltages(vthreshold=args.threshold) + # If injection is on initalize the board + if args.inject: + astro.init_injection(inj_voltage=args.vinj) + astro.enable_spi() + logger.info("Chip configured") + astro.dump_fpga() + + if args.inject: + astro.start_injection() + + i = 0 + if args.maxtime is not None: + end_time=time.time()+(args.maxtime*60.) + strDac = args.DAC+"_"+str(dac)+"_" + fname=strDac if not args.name else args.name+strDac + + # Prepares the file paths + if args.saveascsv: # Here for csv + csvpath = args.outdir +'/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.csv' + csvframe =pd.DataFrame(columns = [ + 'readout', + 'Chip ID', + 'payload', + 'location', + 'isCol', + 'timestamp', + 'tot_msb', + 'tot_lsb', + 'tot_total', + 'tot_us', + 'hittime' + ]) + + # And here for the text files/logs + bitpath = args.outdir + '/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.log' + # textfiles are always saved so we open it up + bitfile = open(bitpath,'w') + # Writes all the config information to the file + bitfile.write(astro.get_log_header()) + bitfile.write(str(args)) + bitfile.write("\n") + + try: # By enclosing the main loop in try/except we are able to capture keyboard interupts cleanly + + while (True): # Loop continues + + # Break conditions + if args.maxruns is not None: + if i >= args.maxruns: break + if args.maxtime is not None: + if time.time() >= end_time: break + + + if astro.hits_present(): # Checks if hits are present + + time.sleep(.001) # this is probably not needed, will ask Nicolas + + readout = astro.get_readout(3) # Gets the bytearray from the chip + + # Writes the hex version to hits + bitfile.write(f"{i}\t{str(binascii.hexlify(readout))}\n") + print(binascii.hexlify(readout)) + + # Added fault tolerance for decoding, the limits of which are set through arguments + try: + hits = astro.decode_readout(readout, i, printer = True) + + except IndexError: + # We write out the failed decode dataframe + hits = decode_fail_frame + hits.readout = i + hits.hittime = time.time() + + finally: + i += 1 + + # If we are saving a csv this will write it out. + if args.saveascsv: + csvframe = pd.concat([csvframe, hits]) + + # If no hits are present this waits for some to accumulate + else: time.sleep(.001) + + + # Ends program cleanly when a keyboard interupt is sent. + except KeyboardInterrupt: + logger.info("Keyboard interupt. Program halt!") + # Catches other exceptions + except Exception as e: + logger.exception(f"Encountered Unexpected Exception! \n{e}") + finally: + if args.saveascsv: + csvframe.index.name = "dec_order" + csvframe.to_csv(csvpath) + if args.inject: astro.stop_injection() + bitfile.close() # Close open file + astro.close_connection() # Closes SPI + logger.info("Program terminated successfully") + # END OF PROGRAM + + + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='Astropix Driver Code') + parser.add_argument('-n', '--name', default='', required=False, + help='Option to give additional name to output files upon running') + + parser.add_argument('-o', '--outdir', default='.', required=False, + help='Output Directory for all datafiles') + + parser.add_argument('-c', '--saveascsv', action='store_true', + default=False, required=False, + help='save output files as CSV. If False, save as txt') + + parser.add_argument('-i', '--inject', action='store_true', default=False, required=False, + help = 'Turn on injection. Default: No injection') + + parser.add_argument('-v','--vinj', action='store', default = None, type=float, + help = 'Specify injection voltage (in mV). DEFAULT 400 mV') + + parser.add_argument('-a', '--analog', action='store', required=False, type=int, default = 0, + help = 'Turn on analog output in the given column. Default: Column 0. Set to None to turn off analog output.') + + parser.add_argument('-t', '--threshold', type = float, action='store', default=None, + help = 'Threshold voltage for digital ToT (in mV). DEFAULT 100mV') + + parser.add_argument('-r', '--maxruns', type=int, action='store', default=None, + help = 'Maximum number of readouts') + + parser.add_argument('-M', '--maxtime', type=float, action='store', default=None, + help = 'Maximum run time (in minutes)') + + parser.add_argument('-p', '--pixel', action='store', default=[0,0], type=int, nargs=2, + help = 'Single enabled pixel (row col). Default: 0 0') + + parser.add_argument('-D', '--DAC', default='', required=False, + help = 'DAC value over which to scan. Default: None') + + parser.add_argument('-d', '--dacrange', action='store', default=[0,60,5], type=int, nargs=3, + help = 'Range to scan over DAC value and increment. Default: 0 60 5') + + parser.add_argument + args = parser.parse_args() + + # Logging stuff! + loglevel = logging.INFO + formatter = logging.Formatter('%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s') + fh = logging.FileHandler(logname) + fh.setFormatter(formatter) + sh = logging.StreamHandler() + sh.setFormatter(formatter) + + logging.getLogger().addHandler(sh) + logging.getLogger().addHandler(fh) + logging.getLogger().setLevel(loglevel) + + logger = logging.getLogger(__name__) + + #loop over full array by default, unless bounds are given as argument + for d in range(args.dacrange[0],args.dacrange[1],args.dacrange[2]): + main(args,d) + time.sleep(2) # to avoid loss of connection to Nexys \ No newline at end of file From 5fddd1260b48c0892ed8a8a97f3abfc7a04b030d Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Tue, 9 Aug 2022 14:13:09 -0400 Subject: [PATCH 42/78] Clarify default injection is 300mV [AS] --- astropix.py | 4 ++-- beam_test.py | 2 +- example_loop.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/astropix.py b/astropix.py index a1764d4f..c4a777c1 100644 --- a/astropix.py +++ b/astropix.py @@ -295,7 +295,7 @@ def init_injection(self, slot: int = 3, inj_voltage:float = None, inj_period:int dac_config:tuple[int, list[float]]: injdac settings. Must be fully specified if set. """ # Default configuration for the dac - # 0.3 is injection voltage + # 0.3 is (default) injection voltage # 2 is slot number for inj board default_injdac = (2, [0.3, 0.0]) # Some fault tolerance @@ -317,7 +317,7 @@ def init_injection(self, slot: int = 3, inj_voltage:float = None, inj_period:int raise ValueError("Cannot inject a negative voltage!") elif inj_voltage > 1800: logger.warning("Cannot inject more than 1800mV, will use defaults") - inj_voltage = 400 #Sets to 200 mV + inj_voltage = 300 #Sets to 300 mV inj_voltage = inj_voltage / 1000 dac_settings[1][0] = inj_voltage diff --git a/beam_test.py b/beam_test.py index 08239abe..3021bbc6 100644 --- a/beam_test.py +++ b/beam_test.py @@ -226,7 +226,7 @@ def main(args): help = 'Turn on injection in the given row and column. Default: No injection') parser.add_argument('-v','--vinj', action='store', default = None, type=float, - help = 'Specify injection voltage (in mV). DEFAULT 400 mV') + help = 'Specify injection voltage (in mV). DEFAULT 300 mV') parser.add_argument('-m', '--mask', action='store', required=False, type=str, default = "./masks/mask_row0_col0.txt", help = 'filepath to digital mask to enable digital readout. Default: No digital readout (all pixels off)') diff --git a/example_loop.py b/example_loop.py index 3b92cc05..7e5134ae 100644 --- a/example_loop.py +++ b/example_loop.py @@ -186,7 +186,7 @@ def main(args,row,col,injectPix): help = 'Turn on injection. Default: No injection') parser.add_argument('-v','--vinj', action='store', default = None, type=float, - help = 'Specify injection voltage (in mV). DEFAULT 400 mV') + help = 'Specify injection voltage (in mV). DEFAULT 300 mV') parser.add_argument('-t', '--threshold', type = float, action='store', default=None, help = 'Threshold voltage for digital ToT (in mV). DEFAULT 100mV') From fefef19397ccf09e2ae9514899f87cf2f86880aa Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Tue, 16 Aug 2022 11:33:59 -0400 Subject: [PATCH 43/78] fix naming [AS] --- loop_DACs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/loop_DACs.py b/loop_DACs.py index c0c756ed..6a3cf433 100644 --- a/loop_DACs.py +++ b/loop_DACs.py @@ -82,7 +82,7 @@ def main(args,dac): if args.maxtime is not None: end_time=time.time()+(args.maxtime*60.) strDac = args.DAC+"_"+str(dac)+"_" - fname=strDac if not args.name else args.name+strDac + fname=strDac if not args.name else args.name+"_"+strDac # Prepares the file paths if args.saveascsv: # Here for csv @@ -231,4 +231,4 @@ def main(args,dac): #loop over full array by default, unless bounds are given as argument for d in range(args.dacrange[0],args.dacrange[1],args.dacrange[2]): main(args,d) - time.sleep(2) # to avoid loss of connection to Nexys \ No newline at end of file + time.sleep(5) # to avoid loss of connection to Nexys \ No newline at end of file From 709d15de5b7f7f2bc37c4e9e74fcefb8c790601c Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Tue, 16 Aug 2022 11:50:56 -0400 Subject: [PATCH 44/78] reflect changes from most recent merge from Nicolas in astropix.py (since all changes were made in asic.py) - NOTE asic.py still exists (but is not used?) [AS] --- astropix.py | 55 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/astropix.py b/astropix.py index c4a777c1..4e266308 100644 --- a/astropix.py +++ b/astropix.py @@ -51,7 +51,7 @@ class astropix2: 'vncomp': 2, 'vpfoll': 60, 'nu16': 0, - 'vprec': 30, + 'vprec': 60, 'vnrec': 30 } @@ -495,7 +495,7 @@ def _make_digitalconfig(self): self.digitalconfig = {'interupt_pushpull': 1} for i in range(1,19): self.digitalconfig[f"En_Inj{i}"] = 0 - self.digitalconfig["ResetB"] = 0 + self.digitalconfig["Reset"] = 0 for i in range(0,8): self.digitalconfig[f'Extrabit{i}'] = 1 for i in range(8,15): @@ -552,28 +552,28 @@ def _make_blank_mask(self): # Methods from updated asic.py def get_num_cols(self): - """ - Returns number of columns + """Get number of columns + :returns: Number of columns """ return self._num_cols def get_num_rows(self): - """ - Returns number of rows + """Get number of rows + :returns: Number of rows """ return self._num_rows - def enable_inj_row(self, col: int, inplace:bool=True): + def enable_inj_row(self, row: int, inplace:bool=True): """ - Enable injection in specified column + Enable injection in specified row Takes: - col: int - Turns on rows? Maybe? + row: int - Row number inplace:bool - True - Updates asic after updating pixel mask """ - self.recconfig[f'ColConfig{col}'] = self.recconfig.get(f'ColConfig{col}', 0b001_11111_11111_11111_11111_11111_11111_11110) | 0b000_00000_00000_00000_00000_00000_00000_00001 + self.recconfig[f'ColConfig{row}'] = self.recconfig.get(f'ColConfig{row}', 0b001_11111_11111_11111_11111_11111_11111_11110) | 0b000_00000_00000_00000_00000_00000_00000_00001 if inplace: self.asic_update() @@ -582,7 +582,7 @@ def enable_inj_col(self, col: int, inplace:bool=True): Enable injection in specified column Takes: - col: int - Turns on rows? Maybe? + col: int - Column number inplace:bool - True - Updates asic after updating pixel mask """ self.recconfig[f'ColConfig{col}'] = self.recconfig.get(f'ColConfig{col}', 0b001_11111_11111_11111_11111_11111_11111_11110) | 0b010_00000_00000_00000_00000_00000_00000_00000 @@ -591,7 +591,7 @@ def enable_inj_col(self, col: int, inplace:bool=True): def enable_ampout_col(self, col: int, inplace:bool=True): """ - Enables analog output + Enables analog output, Select Col for analog mux and disable other cols Takes: col:int - Column to enable @@ -609,7 +609,7 @@ def enable_ampout_col(self, col: int, inplace:bool=True): def enable_pixel(self, col: int, row: int, inplace:bool=True): """ - Turns on specified pixel! + Turns on comparator in specified pixel Takes: col: int - Column of pixel @@ -622,7 +622,7 @@ def enable_pixel(self, col: int, row: int, inplace:bool=True): def disable_pixel(self, col: int, row: int, inplace:bool=True): """ - Turns off specified pixel! + Disable comparator in specified pixel Takes: col: int - Column of pixel @@ -634,12 +634,18 @@ def disable_pixel(self, col: int, row: int, inplace:bool=True): if inplace: self.asic_update() - def disable_row(self, col: int, row: int): + def disable_inj_row(self, row: int): + """Disable row injection switch + :param row: Row number + """ if(row < self._num_rows): - self.recconfig[f'ColConfig{col}'] = self.recconfig.get(f'ColConfig{col}', 0b001_11111_11111_11111_11111_11111_11111_11110) & 0b111_11111_11111_11111_11111_11111_11111_11110 + self.recconfig[f'ColConfig{row}'] = self.recconfig.get(f'ColConfig{row}', 0b001_11111_11111_11111_11111_11111_11111_11110) & 0b111_11111_11111_11111_11111_11111_11111_11110 - def disable_col(self, col: int, row: int): - if(row < self._num_rows): + def disable_inj_col(self, col: int): + """Disable col injection switch + :param col: Col number + """ + if(col < self._num_cols): self.recconfig[f'ColConfig{col}'] = self.recconfig.get(f'ColConfig{col}', 0b001_11111_11111_11111_11111_11111_11111_11110) & 0b101_11111_11111_11111_11111_11111_11111_11111 def get_pixel(self, col: int, row: int): @@ -655,7 +661,13 @@ def get_pixel(self, col: int, row: int): return False else: return True - + def reset_recconfig(self): + """Reset recconfig by disabling all pixels and disabling all injection switches and mux ouputs + """ + i = 0 + while i < self._num_cols: + self.recconfig[f'ColConfig{i}'] = 0b001_11111_11111_11111_11111_11111_11111_11110 + i += 1 # This is from asic.py, and it essentially takes all the parameters and puts @@ -690,7 +702,10 @@ def _construct_asic_vector(self, msbfirst:bool = False): def __int2nbit(self,value: int, nbits: int) -> BitArray: """Convert int to 6bit bitarray - :param value: DAC value 0-63 + :param value: Integer value + :param nbits: Number of bits + + :returns: Bitarray of specified length """ try: From 88cefbf4f30c25409086892abf19b3a140e5c838 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Wed, 24 Aug 2022 13:47:30 -0400 Subject: [PATCH 45/78] fix mask default - initialize empty array with no enabled pixels if no mask passed, not mask with (0,0) enabled. now action is reflected by help message --- beam_test.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/beam_test.py b/beam_test.py index 3021bbc6..afd95833 100644 --- a/beam_test.py +++ b/beam_test.py @@ -57,6 +57,7 @@ def main(args): # Ensures output directory exists if os.path.exists(args.outdir) == False: os.mkdir(args.outdir) + print(f"MASKED {masked}") # Prepare everything, create the object astro = astropix2(inject=args.inject) @@ -65,7 +66,7 @@ def main(args): if masked: astro.asic_init(digital_mask=bitmask, analog_col = args.analog) else: - astro.asic_init() + astro.asic_init(analog_col = args.analog) astro.init_voltages(vthreshold=args.threshold) # If injection is on initalize the board @@ -228,7 +229,7 @@ def main(args): parser.add_argument('-v','--vinj', action='store', default = None, type=float, help = 'Specify injection voltage (in mV). DEFAULT 300 mV') - parser.add_argument('-m', '--mask', action='store', required=False, type=str, default = "./masks/mask_row0_col0.txt", + parser.add_argument('-m', '--mask', action='store', required=False, type=str, default = None, help = 'filepath to digital mask to enable digital readout. Default: No digital readout (all pixels off)') parser.add_argument('-a', '--analog', action='store', required=False, type=int, default = 0, From 79e08a44e076f007360b2b8ae9e51049d911834a Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Wed, 24 Aug 2022 13:53:03 -0400 Subject: [PATCH 46/78] enable injection pixel if no mask is given so less arguments need be passed. if mask also given, then mask overrides [AS] --- beam_test.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/beam_test.py b/beam_test.py index afd95833..9442b5b3 100644 --- a/beam_test.py +++ b/beam_test.py @@ -45,7 +45,7 @@ -#Init stuffs +#Initialize def main(args): # Used for creating the mask @@ -57,17 +57,20 @@ def main(args): # Ensures output directory exists if os.path.exists(args.outdir) == False: os.mkdir(args.outdir) - print(f"MASKED {masked}") - + # Prepare everything, create the object astro = astropix2(inject=args.inject) - # Passes mask if specified, else it creates an analog mask of (0,0) + # Passes mask if specified, else it creates a blank mask with no active pixels if masked: astro.asic_init(digital_mask=bitmask, analog_col = args.analog) else: astro.asic_init(analog_col = args.analog) + #If injection, enable injection pixel unless mask has been given. Mask overrides + if not masked and args.inject is not None: + astro.enable_pixel(args.inject[1],args.inject[0]) + astro.init_voltages(vthreshold=args.threshold) # If injection is on initalize the board if args.inject is not None: @@ -224,7 +227,7 @@ def main(args): help='save output files as CSV. If False, save as txt') parser.add_argument('-i', '--inject', action='store', default=None, type=int, nargs=2, - help = 'Turn on injection in the given row and column. Default: No injection') + help = 'Turn on injection in the given row and column. If no mask (-m) given, then enables only this pixel. Overridden by mask (-m) Default: No injection') parser.add_argument('-v','--vinj', action='store', default = None, type=float, help = 'Specify injection voltage (in mV). DEFAULT 300 mV') From d51d5cb3874d2160c3f6ae695fceee994dc86bbe Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Wed, 24 Aug 2022 13:56:49 -0400 Subject: [PATCH 47/78] update README to clarify that running with no mask (-m) results in fully diabled digital array [AS] --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 75cf3330..0c9b37f5 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ Features: * SPI/QSPI Readout TODO: -* Chip config JSON import * (GUI) ## Installation @@ -138,7 +137,7 @@ Options: | :--- | :--- | :--- | :--- | | `-n` `--name` | `-n [SOMESTRING]` | Set additional name to be added to the timestamp in file outputs | None | | `-o` `--outdir`| `-o [DIRECTORY]` | Directory to save all output files to. Will be created if it doesn't exist. | `./` | -| `-m` `--mask` | `-m [PATH]` | Enable a masked digital output. Takes a path to a text file specifying which pixels are enabled. If not specified will default to (0,0). | None| +| `-m` `--mask` | `-m [PATH]` | Enable digital output of pixels passing mask. Takes a path to a text file specifying which pixels are enabled. If not specified will disable all pixels. | None / diabled array| | `-c` `--saveascsv` | `-c` | Toggle saving csv files on and off | Does not save csv | | `-s` `--showhits` | `-s` | Display hits in real time | Off | | `-p` `--plotsave` | `-p` | Saves real time plots as image files. Stored in outdir. | Does not save plots | From 2f367a4e8be3515ec97894092b4be18f7a2fe92c Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Wed, 24 Aug 2022 14:05:19 -0400 Subject: [PATCH 48/78] Initialize voltage board immediately upon instantiating astropix object - avoids having to run config twice because things are configured in the proper order [AS] --- README.md | 17 ++++++++--------- beam_test.py | 4 +++- example_loop.py | 2 +- loop_DACs.py | 3 ++- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 0c9b37f5..dab46af5 100644 --- a/README.md +++ b/README.md @@ -84,21 +84,20 @@ Must go in this order!! - optional arguments: - clock_period_ns, default 10 - inject, default `False`. When true configures the pixels to accept an injection voltage - -2. Initalizing the ASIC - - call `astro.asic_init()` - - Usage: `astro.asic_init([no required], dac_setup: dict, bias_setup: dict, digital_mask: str)` - - Optional arguments: - - dac_setup: dictionairy of values which will be used to change the defalt dac settings. Does not need to have a complete dictionairy, only values that you want to change. Default None - - bias_setup: dictionairy of values which will be used to change the defalt bias settings. Does not need to have a complete dictionairy, only values that you want to change. Default None - - digital_mask: text data of 1s and 0s in a 35x35 grid (newline seperated rows) specifying what pixels are on and off. If not specified chip will be in analog mode -3. initializing voltages +2. Initializing voltages - call `astro.init_voltages([none required] slot, vcal, vsupply, vthreshold, [optional] dacvals)` - slot: Usually 4, tells chip where the board is - vcal: calibrated voltage. Usually 0.989 - vsupply: voltage to gecco board, usually 2.7 - vthreshold: ToT threshold voltage. Usually 1.075 ish - optional, dacvals: if you want to configure the dac values, do that here +3. Initalizing the ASIC + - call `astro.asic_init()` + - Usage: `astro.asic_init([no required], dac_setup: dict, bias_setup: dict, digital_mask: str)` + - Optional arguments: + - dac_setup: dictionairy of values which will be used to change the defalt dac settings. Does not need to have a complete dictionairy, only values that you want to change. Default None + - bias_setup: dictionairy of values which will be used to change the defalt bias settings. Does not need to have a complete dictionairy, only values that you want to change. Default None + - digital_mask: text data of 1s and 0s in a 35x35 grid (newline seperated rows) specifying what pixels are on and off. If not specified chip will be in analog mode 4. initalizing injector board (optional) - call `astro.init_injection()` - Has following options and defaults: diff --git a/beam_test.py b/beam_test.py index 9442b5b3..cf88151e 100644 --- a/beam_test.py +++ b/beam_test.py @@ -61,6 +61,8 @@ def main(args): # Prepare everything, create the object astro = astropix2(inject=args.inject) + astro.init_voltages(vthreshold=args.threshold) + # Passes mask if specified, else it creates a blank mask with no active pixels if masked: astro.asic_init(digital_mask=bitmask, analog_col = args.analog) @@ -71,7 +73,7 @@ def main(args): if not masked and args.inject is not None: astro.enable_pixel(args.inject[1],args.inject[0]) - astro.init_voltages(vthreshold=args.threshold) + # If injection is on initalize the board if args.inject is not None: astro.init_injection(inj_voltage=args.vinj) diff --git a/example_loop.py b/example_loop.py index 7e5134ae..10bc8983 100644 --- a/example_loop.py +++ b/example_loop.py @@ -60,12 +60,12 @@ def main(args,row,col,injectPix): # Initialie asic - blank array, no pixels enabled astro.asic_init() + astro.init_voltages(vthreshold=args.threshold) #Enable single pixel in (col,row) #Updates asic by default astro.enable_pixel(col,row) - astro.init_voltages(vthreshold=args.threshold) # If injection is on initalize the board if args.inject: astro.init_injection(inj_voltage=args.vinj) diff --git a/loop_DACs.py b/loop_DACs.py index 6a3cf433..c0da90a4 100644 --- a/loop_DACs.py +++ b/loop_DACs.py @@ -63,11 +63,12 @@ def main(args,dac): astro.asic_init(dac_setup={args.DAC: dac},analog_col = args.analog) else: astro.asic_init(analog_col = args.analog) + + astro.init_voltages(vthreshold=args.threshold) #Enable single pixel from argument, or (0,0) if no pixel given astro.enable_pixel(args.pixel[1],args.pixel[0]) - astro.init_voltages(vthreshold=args.threshold) # If injection is on initalize the board if args.inject: astro.init_injection(inj_voltage=args.vinj) From a24d0a9e66296750c6d6bb072472f537e8721c9a Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Thu, 1 Sep 2022 14:34:18 -0400 Subject: [PATCH 49/78] update default dacs to optimized values for shorter pulse duration [AS] --- astropix.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/astropix.py b/astropix.py index 4e266308..d6b35e59 100644 --- a/astropix.py +++ b/astropix.py @@ -37,14 +37,14 @@ class astropix2: 'blres': 0, 'nu1': 0, 'vn1': 20, - 'vnfb': 1, + 'vnfb': 3,#1, 'vnfoll': 10, 'nu5': 0, 'nu6': 0, 'nu7': 0, 'nu8': 0, 'vn2': 0, - 'vnfoll2': 1, + 'vnfoll2': 4,#1, 'vnbias': 0, 'vpload': 5, 'nu13': 0, From 0a0729be503a3b05e65b6d93e74b74a38db7bad0 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Thu, 1 Sep 2022 15:32:10 -0400 Subject: [PATCH 50/78] Begin to prepare offline decoding of saved raw data [AS] --- astropix.py | 33 +++++++++++++++++++-------------- modules/decode.py | 2 ++ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/astropix.py b/astropix.py index d6b35e59..74d22eee 100644 --- a/astropix.py +++ b/astropix.py @@ -68,13 +68,14 @@ class astropix2: # Init just opens the chip and gets the handle. After this runs # asic_config also needs to be called to set it up. Seperating these # allows for simpler specifying of values. - def __init__(self, clock_period_ns = 10, inject:int = None): + def __init__(self, clock_period_ns = 10, inject:int = None, offline:bool=False): """ Initalizes astropix object. No required arguments Optional: clock_period_ns:int - period of main clock in ns inject:bool - if set to True will enable injection for the whole array. + offline:bool - if True, do not try to interface with chip """ # _asic_start tracks if the inital configuration has been run on the ASIC yet. @@ -85,20 +86,24 @@ def __init__(self, clock_period_ns = 10, inject:int = None): self._num_rows = 35 self._num_cols = 35 - self._asic_start = False - self.nexys = Nexysio() - self.handle = self.nexys.autoopen() - self._wait_progress(2) - # Ensure it is working - logger.info("Opened FPGA, testing...") - self._test_io() - logger.info("FPGA test successful.") - # Start putting the variables in for use down the line + if offline: + logger.info("Creating object for offline analysis") + else: + self._asic_start = False + self.nexys = Nexysio() + self.handle = self.nexys.autoopen() + self._wait_progress(2) + # Ensure it is working + logger.info("Opened FPGA, testing...") + self._test_io() + logger.info("FPGA test successful.") + # Start putting the variables in for use down the line + if inject is None: + inject = (None, None) + self.injection_col = inject[1] + self.injection_row = inject[0] + self.sampleclock_period_ns = clock_period_ns - if inject is None: - inject = (None, None) - self.injection_col = inject[1] - self.injection_row = inject[0] # Creates objects used later on self.decode = Decode(clock_period_ns) diff --git a/modules/decode.py b/modules/decode.py index 43bd568c..ed63e50b 100644 --- a/modules/decode.py +++ b/modules/decode.py @@ -87,6 +87,8 @@ def hits_from_readoutstream(self, readout: bytearray, reverse_bitorder: bool = T :returns: List of hits """ + if type(readout) == list: readout=bytearray(readout) + #Reverse Bitorder per byte if reverse_bitorder: readout = self.reverse_bitorder(readout) From ccccb1bcd6d40be591dd98960e6b531aaa6faa1e Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Fri, 2 Sep 2022 10:13:35 -0400 Subject: [PATCH 51/78] Offline script to decode saved .log files containing hex bitstreams. Can input a single file or a directory with .log files which are decoded and info saved in CSV as if -c was run with beam_test.py. [AS] --- decode_postRun.py | 135 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 decode_postRun.py diff --git a/decode_postRun.py b/decode_postRun.py new file mode 100644 index 00000000..aeb1362c --- /dev/null +++ b/decode_postRun.py @@ -0,0 +1,135 @@ +""" +Decode raw data (bitstreams) after data-taking, save decoded information in CSV format identical to when running beam_test.py with option -c + +Author: Amanda Steinhebel +amanda.l.steinhebel@nasa.gov +""" + +from astropix import astropix2 +import glob +import binascii +import pandas as pd +import numpy as np +import logging +import argparse +import re + +from modules.setup_logger import logger + +#Initialize +def main(args): + + #Allow only -f or -d to be evoked - not both + if args.fileInput and args.dirInput: + logger.error("Input a single file with -f OR a single directory with -d... not both! Try running again") + exit() + + #Define boolean for args.fileInput + f_in = True if args.fileInput is not None else False + + #Create objet + astro = astropix2(offline=True) + + #Define output file path + if args.outDir is not None: + outpath = args.outDir + elif f_in: + try: #Mac path + dirInd = args.fileInput.rindex('/') + except ValueError: #Windows path + dirInd = args.fileInput.rindex('\\') + outpath = args.fileInput[:dirInd+1] #add 1 to keep final delimiter in path + elif args.dirInput is not None: + outpath = args.dirInput + + #Symmetrize structure + inputFiles = [args.fileInput] if f_in else glob.glob(f'{args.dirInput}*.log') + + #Run over all input files + for infile in inputFiles: + + #Define output file name + csvname = re.split(r'\\|/',infile)[-1][:-4] #split Mac or OS path; identify file name and eliminate '.log' + csvpath = outpath + csvname + '_offline.csv' + + #Setup CSV structure + csvframe =pd.DataFrame(columns = [ + 'readout', + 'Chip ID', + 'payload', + 'location', + 'isCol', + 'timestamp', + 'tot_msb', + 'tot_lsb', + 'tot_total', + 'tot_us', + 'hittime' + ]) + + #Import data file + f=np.loadtxt(infile, skiprows=6, dtype=str) + + #isolate only bitstream without b'...' structure + strings = [a[2:-1] for a in f[:,1]] + + for s in strings: + #convert hex to binary and decode + rawdata = list(binascii.unhexlify(s)) + hits = astro.decode_readout(rawdata, 0, printer = args.printDecode) + #Overwrite hittime - computed during decoding + hits['hittime']=np.nan + #Populate csv + csvframe = pd.concat([csvframe, hits]) + + #Save csv + logger.info(f"Saving to {csvpath}") + csvframe.to_csv(csvpath) + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='Post-run decoding') + parser.add_argument('-f', '--fileInput', default=None, required=False, + help='Input data file to decode') + + parser.add_argument('-d', '--dirInput', default=None, required=False, + help='Input directory of data files to decode') + + parser.add_argument('-o', '--outDir', default=None, required=False, + help='Output Directory for all decoded datafiles. Defaults to directory raw data is saved in') + + parser.add_argument('-L', '--loglevel', type=str, choices = ['D', 'I', 'E', 'W', 'C'], action="store", default='I', + help='Set loglevel used. Options: D - debug, I - info, E - error, W - warning, C - critical. DEFAULT: D') + + parser.add_argument('-p', '--printDecode', action='store_true', default=False, required=False, + help='Print decoded info into terminal. Default: False') + + parser.add_argument + args = parser.parse_args() + + # Sets the loglevel + ll = args.loglevel + if ll == 'D': + loglevel = logging.DEBUG + elif ll == 'I': + loglevel = logging.INFO + elif ll == 'E': + loglevel = logging.ERROR + elif ll == 'W': + loglevel = logging.WARNING + elif ll == 'C': + loglevel = logging.CRITICAL + + # Logging - print to terminal only + formatter = logging.Formatter('%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s') + sh = logging.StreamHandler() + sh.setFormatter(formatter) + + logging.getLogger().addHandler(sh) + logging.getLogger().setLevel(loglevel) + + logger = logging.getLogger(__name__) + + + main(args) \ No newline at end of file From 21cb340662835cb7e7adb7691af724877176f258 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Thu, 29 Sep 2022 13:59:17 -0400 Subject: [PATCH 52/78] First testing of YAML configuration on bench - seeing weak analog signal and no digital signal, pow6 only pulling half the nominal current --- README.md | 16 ++-- astropix.py | 203 +++++++++++++++++++++++++++++++++++++++++++-------- beam_test.py | 25 +++++-- 3 files changed, 199 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index dab46af5..e87259d9 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ Must go in this order!! 1. Creating the instance - After import, call astropix2(). - - Usage: `astropix2([no required], clock_period_ns: int, inject: bool)` + - Usage: `astropix2([none required], clock_period_ns: int, inject: bool)` - optional arguments: - clock_period_ns, default 10 - inject, default `False`. When true configures the pixels to accept an injection voltage @@ -93,12 +93,13 @@ Must go in this order!! - optional, dacvals: if you want to configure the dac values, do that here 3. Initalizing the ASIC - call `astro.asic_init()` - - Usage: `astro.asic_init([no required], dac_setup: dict, bias_setup: dict, digital_mask: str)` + - Usage: `astro.asic_init(yaml:str, [opt] dac_setup: dict, bias_setup: dict, digital_mask: str)` - Optional arguments: - - dac_setup: dictionairy of values which will be used to change the defalt dac settings. Does not need to have a complete dictionairy, only values that you want to change. Default None - - bias_setup: dictionairy of values which will be used to change the defalt bias settings. Does not need to have a complete dictionairy, only values that you want to change. Default None + - yaml: string of name of configuration .yml file in /config/*.yml. If none given command-line, default set to config/testconfig.yml + - dac_setup: dictionary of values which will be used to change the defalt dac settings. Does not need to have a complete dictionary, only values that you want to change. Default None + - bias_setup: dictionary of values which will be used to change the defalt bias settings. Does not need to have a complete dictionary, only values that you want to change. Default None - digital_mask: text data of 1s and 0s in a 35x35 grid (newline seperated rows) specifying what pixels are on and off. If not specified chip will be in analog mode -4. initalizing injector board (optional) +4. Initalizing injector board (optional) - call `astro.init_injection()` - Has following options and defaults: - dac_settings:tuple[int, list[float]] = (2, [0.4, 0.0]) @@ -118,7 +119,9 @@ astro.hits_present() --> bool. Are thre any hits on the board currently? astro.get_readout() --> bytearray. Gets bytestream from the chip -astro.decode_readout(readout, [opt] printer) --> list of dictionairies. printer prints the decoded values to terminal +astro.decode_readout(readout, [opt] printer) --> list of dictionaries. Printer prints the decoded values to terminal + +astro.write_conf_to_yaml() --> write configuration settings to *.yml astro.start_injection() and astro.stop_injection() are self explainatory @@ -136,6 +139,7 @@ Options: | :--- | :--- | :--- | :--- | | `-n` `--name` | `-n [SOMESTRING]` | Set additional name to be added to the timestamp in file outputs | None | | `-o` `--outdir`| `-o [DIRECTORY]` | Directory to save all output files to. Will be created if it doesn't exist. | `./` | +| `-y` `--yaml`| `-y [NAME]` | Name of configuration file, assuming config/*.yml where * is passed. If not specified, uses config/testconfig.yml and disables all pixels | `testconfig` | | `-m` `--mask` | `-m [PATH]` | Enable digital output of pixels passing mask. Takes a path to a text file specifying which pixels are enabled. If not specified will disable all pixels. | None / diabled array| | `-c` `--saveascsv` | `-c` | Toggle saving csv files on and off | Does not save csv | | `-s` `--showhits` | `-s` | Display hits in real time | Off | diff --git a/astropix.py b/astropix.py index 74d22eee..066b2af6 100644 --- a/astropix.py +++ b/astropix.py @@ -18,6 +18,9 @@ import regex as re import binascii import time +import yaml + +import os.path, os # Logging stuff import logging @@ -32,6 +35,8 @@ class astropix2: + #Depreciated hardcoding in favor of YAML + """ # First the global defaults which will be used later DACS_CFG = { 'blres': 0, @@ -63,7 +68,7 @@ class astropix2: 'qon2': 0, 'qon3': 1, } - + """ # Init just opens the chip and gets the handle. After this runs # asic_config also needs to be called to set it up. Seperating these @@ -85,6 +90,7 @@ def __init__(self, clock_period_ns = 10, inject:int = None, offline:bool=False): # each time a parameter is changed. self._num_rows = 35 self._num_cols = 35 + self.asic_config = None if offline: logger.info("Creating object for offline analysis") @@ -106,18 +112,62 @@ def __init__(self, clock_period_ns = 10, inject:int = None, offline:bool=False): self.sampleclock_period_ns = clock_period_ns # Creates objects used later on self.decode = Decode(clock_period_ns) + + ##################### YAML CONFIGURATION ######################### + + # Methods to load/write configuration variables from/to YAML. + def load_conf_from_yaml(self, filename:str = None, chipversion:int = 2): + """Load ASIC config from yaml + :param filename: Name of yml file in config folder + """ + with open(filename, "r") as stream: + try: + dict_from_yml = yaml.safe_load(stream) + except yaml.YAMLError as exc: + logger.error(exc) + + try: + self.asic_config = dict_from_yml.get(f'astropix{chipversion}')['config'] + logger.info(f"Astropix{chipversion} config found!") + except: + logger.error(f"Astropix{chipversion} config not found") + + try: + self.num_cols = dict_from_yml[f'astropix{chipversion}'].get('geometry')['cols'] + self.num_rows = dict_from_yml[f'astropix{chipversion}'].get('geometry')['rows'] + logger.info(f"Astropix{chipversion} matrix dimensions found!") + except: + logger.error(f"Astropix{chipversion} matrix dimensions not found!") + + def write_conf_to_yaml(self, filename:str = None, chipversion:int = 2): + """Write ASIC config to yaml + + :param chipversion: Name of yml file in config folder + :param filename: Name of yml file in config folder + """ + with open(filename, "w") as stream: + try: + yaml.dump({f"astropix{chipversion}": \ + { + "geometry": {"cols": self.num_cols, "rows": self.num_rows},\ + "config" : self.asic_config}\ + }, + stream, default_flow_style=True, sort_keys=False) + + except yaml.YAMLError as exc: + logger.error(exc) ##################### ASIC METHODS FOR USERS ######################### # Method to initalize the asic. This is taking the place of asic.py. # All of the interfacing is handeled through asic_update - def asic_init(self, dac_setup: dict = None, bias_setup:dict = None, digital_mask:str = None, blankmask:bool = False, analog_col:int = None): + def asic_init(self, yaml:str = None, dac_setup: dict = None, bias_setup:dict = None, digital_mask:str = None, blankmask:bool = False, analog_col:int = None): """ self.asic_init() - initalize the asic configuration. Must be called first Positional arguments: None Optional: - dac_setup: dict - dictionairy of values passed to the configuration. Only needs values diffent from defaults + dac_setup: dict - dictionary of values passed to the configuration. Only needs values diffent from defaults bias_setup: dict - dict of values for the bias configuration Only needs key/vals for changes from default digital_mask: str - String of 1s and 0s in 35x35 arangement which masks the array. Needed to enable pixels not (0,0) blankmask: bool - Create a blank mask (everything disabled). Pixels can be enabled manually @@ -127,16 +177,44 @@ def asic_init(self, dac_setup: dict = None, bias_setup:dict = None, digital_mask # Now that the asic has been initalized we can go and make this true self._asic_start = True - # The use of update methods on the dictionairy allows for only the keys that + # The use of update methods on the dictionary allows for only the keys that # need changing to be passed to the function (hopefully) simplifying the interface - self.dac_setup = self.DACS_CFG + + # Get config values from YAML + try: + self.load_conf_from_yaml(yaml) + except Exception: + logger.error('Must pass a configuration file in the form of *.yml') + #Config stored in dictionary self.asic_config . This is used for configuration in asic_update. If any changes are made, make change to self.asic_config so that it is reflected on-chip when asic_update is called + + #Override yaml if arugments were given in run script + if bias_setup is not None: + self.biasconfig.update(bias_setup) + if dac_setup is not None: + self.dacconfig.update(dac_setup) + + + #DEPRECIATED FROM YAML INCLUSION + """ + #self.dac_setup = self.DACS_CFG + #use default DACs from YAML, isolate DAC value from array with bit allotment + self.dac_setup = {} + for key, value in self.asic_config['idacs'].items(): + self.dac_setup[key] = value[1] if dac_setup is not None: self.dac_setup.update(dac_setup) - self.bias_setup = self.BIAS_CFG + #self.bias_setup = self.BIAS_CFG + #use default bias config from YAML, isolate DAC value from array with bit allotment + self.bias_setup = {} + for key, value in self.asic_config['biasconfig'].items(): + self.bias_setup[key] = value[1] if bias_setup is not None: self.bias_setup.update(bias_setup) - + """ + + #MASKING + """ # If maskstring is passed it will mask based on that if digital_mask is not None: self._mask_from_string(digital_mask, None) @@ -144,10 +222,22 @@ def asic_init(self, dac_setup: dict = None, bias_setup:dict = None, digital_mask # self.enable_pixel() and whatnot else: self._make_blank_mask() + """ + #DEPRECIATED FROM YML + """ + #self._make_digitalconfig() - depreciated, treat like idacs and biasconfig from yaml + #asic_update sends full input from yaml to chip - don't need to set these presets at all? + + #self.digitalconfig = {} + #for key, value in self.asic_config['digitalconfig'].items(): + # while i 35: bitlist = bitlist[0:35] - # The dictionairy which is returned + # The dictionary which is returned self.recconfig = {} # used in construction i = 0 @@ -555,7 +662,30 @@ def _make_blank_mask(self): # Methods from updated asic.py + @property + def num_cols(self): + """Get/set number of columns + + :returns: Number of columns + """ + return self._num_cols + + @num_cols.setter + def num_cols(self, cols): + self._num_cols = cols + @property + def num_rows(self): + """Get/set number of rows + + :returns: Number of rows + """ + return self._num_rows + + @num_rows.setter + def num_rows(self, rows): + self._num_rows = rows + def get_num_cols(self): """Get number of columns :returns: Number of columns @@ -578,7 +708,8 @@ def enable_inj_row(self, row: int, inplace:bool=True): """ - self.recconfig[f'ColConfig{row}'] = self.recconfig.get(f'ColConfig{row}', 0b001_11111_11111_11111_11111_11111_11111_11110) | 0b000_00000_00000_00000_00000_00000_00000_00001 + if(row < self.num_rows): + self.asic_config['recconfig'][f'col{row}'][1] = self.asic_config['recconfig'].get(f'col{row}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] | 0b000_00000_00000_00000_00000_00000_00000_00001 if inplace: self.asic_update() @@ -590,7 +721,8 @@ def enable_inj_col(self, col: int, inplace:bool=True): col: int - Column number inplace:bool - True - Updates asic after updating pixel mask """ - self.recconfig[f'ColConfig{col}'] = self.recconfig.get(f'ColConfig{col}', 0b001_11111_11111_11111_11111_11111_11111_11110) | 0b010_00000_00000_00000_00000_00000_00000_00000 + if(col < self.num_cols): + self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'].get(f'col{col}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] | 0b010_00000_00000_00000_00000_00000_00000_00000 if inplace: self.asic_update() @@ -604,12 +736,13 @@ def enable_ampout_col(self, col: int, inplace:bool=True): No return """ + #Disable all analog pixels + for i in range(self.num_cols): + self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'][f'col{col}'][1] & 0b011_11111_11111_11111_11111_11111_11111_11111 - self.recconfig[f'ColConfig{col}'] = self.recconfig.get(f'ColConfig{col}', 0b001_11111_11111_11111_11111_11111_11111_11110) | 0b100_00000_00000_00000_00000_00000_00000_00000 - - for i in range(self.get_num_cols()): - if not i == col: - self.recconfig[f'ColConfig{i}'] = self.recconfig.get(f'ColConfig{i}', 0b001_11111_11111_11111_11111_11111_11111_11110) & 0b011_11111_11111_11111_11111_11111_11111_11111 + #Enable analog pixel in column + self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'][f'col{col}'][1] | 0b100_00000_00000_00000_00000_00000_00000_00000 + if inplace: self.asic_update() def enable_pixel(self, col: int, row: int, inplace:bool=True): @@ -621,8 +754,9 @@ def enable_pixel(self, col: int, row: int, inplace:bool=True): row: int - Row of pixel inplace:bool - True - Updates asic after updating pixel mask """ - if(row < self._num_rows): - self.recconfig[f'ColConfig{col}'] = self.recconfig.get(f'ColConfig{col}', 0b001_11111_11111_11111_11111_11111_11111_11110) & ~(2 << row) + if(row < self.num_rows and col < self.num_cols): + self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'].get(f'col{col}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] & ~(2 << row) + if inplace: self.asic_update() def disable_pixel(self, col: int, row: int, inplace:bool=True): @@ -634,8 +768,8 @@ def disable_pixel(self, col: int, row: int, inplace:bool=True): row: int - Row of pixel inplace:bool - True - Updates asic after updating pixel mask """ - if(row < self._num_rows): - self.recconfig[f'ColConfig{col}'] = self.recconfig.get(f'ColConfig{col}', 0b001_11111_11111_11111_11111_11111_11111_11110) | (2 << row) + if(row < self.num_rows and col < self.num_cols): + self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'].get(f'col{col}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] | (2 << row) if inplace: self.asic_update() @@ -679,13 +813,14 @@ def reset_recconfig(self): # them into a form ready to be loaded onto the board. # Parameters: msbfirst: Send vector MSB first - def _construct_asic_vector(self, msbfirst:bool = False): + def _construct_asic_vector(self, msbfirst:bool = False) -> BitArray: """Generate asic bitvector from digital, bias and dacconfig :param msbfirst: Send vector MSB first """ bitvector = BitArray() + """ for value in self.digitalconfig.values(): bitvector.append(self.__int2nbit(value, 1)) @@ -697,13 +832,17 @@ def _construct_asic_vector(self, msbfirst:bool = False): for value in self.recconfig.values(): bitvector.append(self.__int2nbit(value, 38)) + """ + + for key in self.asic_config: + for values in self.asic_config[key].values(): + bitvector.append(self.__int2nbit(values[1], values[0])) if not msbfirst: bitvector.reverse() - # print(f'Bitvector: {bitvector} \n') + return bitvector - return bitvector def __int2nbit(self,value: int, nbits: int) -> BitArray: """Convert int to 6bit bitarray diff --git a/beam_test.py b/beam_test.py index cf88151e..76dece86 100644 --- a/beam_test.py +++ b/beam_test.py @@ -61,22 +61,27 @@ def main(args): # Prepare everything, create the object astro = astropix2(inject=args.inject) - astro.init_voltages(vthreshold=args.threshold) + astro.init_voltages(vthreshold=args.threshold) #no updates in YAML + + #Define YAML path variables + pathdelim=os.path.sep #determine if Mac or Windows separators in path name + ymlpath="config"+pathdelim+args.yaml+".yml" # Passes mask if specified, else it creates a blank mask with no active pixels if masked: - astro.asic_init(digital_mask=bitmask, analog_col = args.analog) + astro.asic_init(yaml=args.yaml, digital_mask=bitmask, analog_col = args.analog) else: - astro.asic_init(analog_col = args.analog) + astro.asic_init(yaml=ymlpath, analog_col = args.analog) #If injection, enable injection pixel unless mask has been given. Mask overrides if not masked and args.inject is not None: astro.enable_pixel(args.inject[1],args.inject[0]) - # If injection is on initalize the board if args.inject is not None: astro.init_injection(inj_voltage=args.vinj) + + #Enable final configuration astro.enable_spi() logger.info("Chip configured") astro.dump_fpga() @@ -109,7 +114,10 @@ def main(args): 'hittime' ]) - # And here for the text files/logs + # Save final configuration to output file + ymlpathout="config"+pathdelim+args.yaml+"_"+time.strftime("%Y%m%d-%H%M%S")+".yml" + astro.write_conf_to_yaml(ymlpathout) + # Prepare text files/logs bitpath = args.outdir + '/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.log' # textfiles are always saved so we open it up bitfile = open(bitpath,'w') @@ -217,16 +225,19 @@ def main(args): parser.add_argument('-o', '--outdir', default='.', required=False, help='Output Directory for all datafiles') + parser.add_argument('-y', '--yaml', action='store', required=False, type=str, default = 'testconfig', + help = 'filepath (in config/ directory) .yml file containing chip configuration. Default: config/testconfig.yml (All pixels off)') + parser.add_argument('-s', '--showhits', action='store_true', default=False, required=False, help='Display hits in real time during data taking') parser.add_argument('-p', '--plotsave', action='store_true', default=False, required=False, - help='Save plots as image files. If set, will be saved in same dir as data. DEFAULT FALSE') + help='Save plots as image files. If set, will be saved in same dir as data. Default: FALSE') parser.add_argument('-c', '--saveascsv', action='store_true', default=False, required=False, - help='save output files as CSV. If False, save as txt') + help='save output files as CSV. If False, save as txt. Default: FALSE') parser.add_argument('-i', '--inject', action='store', default=None, type=int, nargs=2, help = 'Turn on injection in the given row and column. If no mask (-m) given, then enables only this pixel. Overridden by mask (-m) Default: No injection') From b9c494767f59811b6659bd27c1416de599b57873 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Thu, 6 Oct 2022 13:52:29 -0400 Subject: [PATCH 53/78] Continued bench testing of yaml config - delete depreciated methods in astropix.py and update some pixel identifying/masking methods with new yaml dictionary. Still do not see expected chip behavior --- astropix.py | 139 ++++----------------------------------------------- beam_test.py | 4 +- 2 files changed, 13 insertions(+), 130 deletions(-) diff --git a/astropix.py b/astropix.py index 066b2af6..8d3f21e8 100644 --- a/astropix.py +++ b/astropix.py @@ -16,59 +16,15 @@ from tqdm import tqdm import pandas as pd import regex as re -import binascii import time import yaml -import os.path, os - # Logging stuff import logging from modules.setup_logger import logger logger = logging.getLogger(__name__) - - -# Here are the default configuration values. -# This includes the DAC configurations, default registers, etc... - - - class astropix2: - #Depreciated hardcoding in favor of YAML - """ - # First the global defaults which will be used later - DACS_CFG = { - 'blres': 0, - 'nu1': 0, - 'vn1': 20, - 'vnfb': 3,#1, - 'vnfoll': 10, - 'nu5': 0, - 'nu6': 0, - 'nu7': 0, - 'nu8': 0, - 'vn2': 0, - 'vnfoll2': 4,#1, - 'vnbias': 0, - 'vpload': 5, - 'nu13': 0, - 'vncomp': 2, - 'vpfoll': 60, - 'nu16': 0, - 'vprec': 60, - 'vnrec': 30 - } - - BIAS_CFG = { - 'DisHiDR': 0, - 'q01': 0, - 'qon0': 0, - 'qon1': 1, - 'qon2': 0, - 'qon3': 1, - } - """ # Init just opens the chip and gets the handle. After this runs # asic_config also needs to be called to set it up. Seperating these @@ -152,7 +108,7 @@ def write_conf_to_yaml(self, filename:str = None, chipversion:int = 2): "geometry": {"cols": self.num_cols, "rows": self.num_rows},\ "config" : self.asic_config}\ }, - stream, default_flow_style=True, sort_keys=False) + stream, default_flow_style=False, sort_keys=False) except yaml.YAMLError as exc: logger.error(exc) @@ -192,26 +148,6 @@ def asic_init(self, yaml:str = None, dac_setup: dict = None, bias_setup:dict = N self.biasconfig.update(bias_setup) if dac_setup is not None: self.dacconfig.update(dac_setup) - - - #DEPRECIATED FROM YAML INCLUSION - """ - #self.dac_setup = self.DACS_CFG - #use default DACs from YAML, isolate DAC value from array with bit allotment - self.dac_setup = {} - for key, value in self.asic_config['idacs'].items(): - self.dac_setup[key] = value[1] - if dac_setup is not None: - self.dac_setup.update(dac_setup) - - #self.bias_setup = self.BIAS_CFG - #use default bias config from YAML, isolate DAC value from array with bit allotment - self.bias_setup = {} - for key, value in self.asic_config['biasconfig'].items(): - self.bias_setup[key] = value[1] - if bias_setup is not None: - self.bias_setup.update(bias_setup) - """ #MASKING """ @@ -223,17 +159,6 @@ def asic_init(self, yaml:str = None, dac_setup: dict = None, bias_setup:dict = N else: self._make_blank_mask() """ - - #DEPRECIATED FROM YML - """ - #self._make_digitalconfig() - depreciated, treat like idacs and biasconfig from yaml - #asic_update sends full input from yaml to chip - don't need to set these presets at all? - - #self.digitalconfig = {} - #for key, value in self.asic_config['digitalconfig'].items(): - # while i BitArray: """ bitvector = BitArray() - """ - for value in self.digitalconfig.values(): - bitvector.append(self.__int2nbit(value, 1)) - - for value in self.bias_setup.values(): - bitvector.append(self.__int2nbit(value, 1)) - - for value in self.dac_setup.values(): - bitvector.append(self.__int2nbit(value, 6)) - - for value in self.recconfig.values(): - bitvector.append(self.__int2nbit(value, 38)) - """ - + for key in self.asic_config: for values in self.asic_config[key].values(): + print(values) bitvector.append(self.__int2nbit(values[1], values[0])) + """ + for values in self.asic_config['biasconfig'].values(): + bitvector.append(self.__int2nbit(values[1], values[0])) + """ if not msbfirst: bitvector.reverse() @@ -853,11 +735,12 @@ def __int2nbit(self,value: int, nbits: int) -> BitArray: """ try: + print(len(BitArray(uint=value, length=nbits))) return BitArray(uint=value, length=nbits) except ValueError: print(f'Allowed Values 0 - {2**nbits-1}') - # A progress bar! So facny I know + # progress bar def _wait_progress(self, seconds:int): for _ in tqdm(range(seconds), desc=f'Wait {seconds} s'): time.sleep(1) diff --git a/beam_test.py b/beam_test.py index 76dece86..2a70b070 100644 --- a/beam_test.py +++ b/beam_test.py @@ -59,7 +59,7 @@ def main(args): os.mkdir(args.outdir) # Prepare everything, create the object - astro = astropix2(inject=args.inject) + astro = astropix2(inject=args.inject) #no updates in YAML astro.init_voltages(vthreshold=args.threshold) #no updates in YAML @@ -69,7 +69,7 @@ def main(args): # Passes mask if specified, else it creates a blank mask with no active pixels if masked: - astro.asic_init(yaml=args.yaml, digital_mask=bitmask, analog_col = args.analog) + astro.asic_init(yaml=ymlpath, digital_mask=bitmask, analog_col = args.analog) else: astro.asic_init(yaml=ymlpath, analog_col = args.analog) From 7364d7378222ea1f4768573fc40020ea1a88deb9 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Thu, 6 Oct 2022 15:31:26 -0400 Subject: [PATCH 54/78] add debug statements for config bits --- astropix.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/astropix.py b/astropix.py index 8d3f21e8..efb1c5b2 100644 --- a/astropix.py +++ b/astropix.py @@ -710,11 +710,11 @@ def _construct_asic_vector(self, msbfirst:bool = False) -> BitArray: """ bitvector = BitArray() - for key in self.asic_config: + logger.debug(key) for values in self.asic_config[key].values(): - print(values) bitvector.append(self.__int2nbit(values[1], values[0])) + logger.debug(self.__int2nbit(values[1], values[0])) """ for values in self.asic_config['biasconfig'].values(): bitvector.append(self.__int2nbit(values[1], values[0])) @@ -723,6 +723,8 @@ def _construct_asic_vector(self, msbfirst:bool = False) -> BitArray: if not msbfirst: bitvector.reverse() + logger.debug(bitvector) + return bitvector def __int2nbit(self,value: int, nbits: int) -> BitArray: From 3afbcf783aded695c70c771ac67a2aec81c5ac43 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Thu, 6 Oct 2022 15:32:56 -0400 Subject: [PATCH 55/78] update config files to pass proper number of digital bits - now yml config reproduces signal seen before this change --- config/config_c0_r0.yml | 91 +++++++++++++++++++++++++++++++++++++++++ config/testconfig.yml | 4 +- 2 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 config/config_c0_r0.yml diff --git a/config/config_c0_r0.yml b/config/config_c0_r0.yml new file mode 100644 index 00000000..e2b05712 --- /dev/null +++ b/config/config_c0_r0.yml @@ -0,0 +1,91 @@ +# Example Astropix2 config +# +# The following yaml should comply to the following structure. +# It is important, that the order of configbits under the config section, corresponds to the actual order in the chip +# +# astropix: +# geometry: +# cols: +# rows: +# config: +# : +# : [, ] +# ... +# +--- +astropix2: + geometry: + cols: 35 + rows: 35 + config: + digitalconfig: + interrupt_pushpull: [1, 0b1] + en_inj: [19, 0b0] + reset: [1, 0b1] + extrabits: [14, 0b0] + + biasconfig: + DisHiDR: [1, 0b0] + q01: [1, 0b0] + qon0: [1, 0b0] + qon1: [1, 0b1] + qon2: [1, 0b0] + qon3: [1, 0b1] + + idacs: + blres: [6, 0] + nu1: [6, 0] + vn1: [6, 20] + vnfb: [6, 3] + vnfoll: [6, 10] + nu5: [6, 0] + nu6: [6, 0] + nu7: [6, 0] + nu8: [6, 0] + vn2: [6, 0] + vnfoll2: [6, 4] + vnbias: [6, 0] + vpload: [6, 5] + nu13: [6, 0] + vncomp: [6, 2] + vpfoll: [6, 60] + nu16: [6, 0] + vprec: [6, 60] + vnrec: [6, 30] + + recconfig: + col0: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col1: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col2: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col3: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col4: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col5: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col6: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col7: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col8: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col9: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col10: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col11: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col12: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col13: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col14: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col15: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col16: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col17: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col18: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col19: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col20: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col21: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col22: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col23: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col24: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col25: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col26: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col27: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col28: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col29: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col30: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col31: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col32: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col33: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col34: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] \ No newline at end of file diff --git a/config/testconfig.yml b/config/testconfig.yml index c39a9d2b..74704131 100644 --- a/config/testconfig.yml +++ b/config/testconfig.yml @@ -20,9 +20,9 @@ astropix2: config: digitalconfig: interrupt_pushpull: [1, 0b1] - en_inj: [20, 0b0] + en_inj: [19, 0b0] reset: [1, 0b1] - extrabits: [15, 0b0] + extrabits: [14, 0b0] biasconfig: DisHiDR: [1, 0b1] From 5feda07ab94cb2a312436a4c114a60c462b8e936 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Fri, 7 Oct 2022 13:55:47 -0400 Subject: [PATCH 56/78] remove debug print statement --- astropix.py | 1 - 1 file changed, 1 deletion(-) diff --git a/astropix.py b/astropix.py index efb1c5b2..b181e1db 100644 --- a/astropix.py +++ b/astropix.py @@ -737,7 +737,6 @@ def __int2nbit(self,value: int, nbits: int) -> BitArray: """ try: - print(len(BitArray(uint=value, length=nbits))) return BitArray(uint=value, length=nbits) except ValueError: print(f'Allowed Values 0 - {2**nbits-1}') From f7b2755a277989f6ed9d883d6578588993d1e0d5 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Fri, 7 Oct 2022 13:59:18 -0400 Subject: [PATCH 57/78] Update digitalconfig bit allotment values so config works on GSFC bench - no changes to testconfig --- config/config_c0_r0.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/config_c0_r0.yml b/config/config_c0_r0.yml index 77a29300..99d26f7f 100644 --- a/config/config_c0_r0.yml +++ b/config/config_c0_r0.yml @@ -20,9 +20,9 @@ astropix2: config: digitalconfig: interrupt_pushpull: [1, 0b1] - en_inj: [18, 0b0] + en_inj: [19, 0b0] reset: [1, 0b0] - extrabits: [15, 0b0] + extrabits: [14, 0b0] biasconfig: DisHiDR: [1, 0b0] From 9df71e530dbd4478857e72c0ea16f62edb2bf6ee Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Fri, 7 Oct 2022 14:36:41 -0400 Subject: [PATCH 58/78] actually enable pixels in these config test files --- config/config_c0_r0.yml | 2 +- config/config_c0_r1.yml | 91 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 config/config_c0_r1.yml diff --git a/config/config_c0_r0.yml b/config/config_c0_r0.yml index 99d26f7f..e6fe1064 100644 --- a/config/config_c0_r0.yml +++ b/config/config_c0_r0.yml @@ -54,7 +54,7 @@ astropix2: vnrec: [6, 30] recconfig: - col0: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col0: [38, 0b001_11111_11111_11111_11111_11111_11111_11100] col1: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] col2: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] col3: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] diff --git a/config/config_c0_r1.yml b/config/config_c0_r1.yml new file mode 100644 index 00000000..6e050633 --- /dev/null +++ b/config/config_c0_r1.yml @@ -0,0 +1,91 @@ +# Example Astropix2 config +# +# The following yaml should comply to the following structure. +# It is important, that the order of configbits under the config section, corresponds to the actual order in the chip +# +# astropix: +# geometry: +# cols: +# rows: +# config: +# : +# : [, ] +# ... +# +--- +astropix2: + geometry: + cols: 35 + rows: 35 + config: + digitalconfig: + interrupt_pushpull: [1, 0b1] + en_inj: [19, 0b0] + reset: [1, 0b0] + extrabits: [14, 0b0] + + biasconfig: + DisHiDR: [1, 0b0] + q01: [1, 0b0] + qon0: [1, 0b0] + qon1: [1, 0b1] + qon2: [1, 0b0] + qon3: [1, 0b1] + + idacs: + blres: [6, 0] + nu1: [6, 0] + vn1: [6, 20] + vnfb: [6, 3] + vnfoll: [6, 10] + nu5: [6, 0] + nu6: [6, 0] + nu7: [6, 0] + nu8: [6, 0] + vn2: [6, 0] + vnfoll2: [6, 4] + vnbias: [6, 0] + vpload: [6, 5] + nu13: [6, 0] + vncomp: [6, 2] + vpfoll: [6, 60] + nu16: [6, 0] + vprec: [6, 60] + vnrec: [6, 30] + + recconfig: + col0: [38, 0b001_11111_11111_11111_11111_11111_11111_11010] + col1: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col2: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col3: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col4: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col5: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col6: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col7: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col8: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col9: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col10: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col11: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col12: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col13: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col14: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col15: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col16: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col17: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col18: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col19: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col20: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col21: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col22: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col23: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col24: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col25: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col26: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col27: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col28: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col29: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col30: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col31: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col32: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col33: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col34: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] \ No newline at end of file From 5a271e0afb41079d8ddb5efbe299a93c96089609 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Fri, 7 Oct 2022 14:37:14 -0400 Subject: [PATCH 59/78] remove masking input requiring txt files - create mask from yml --- beam_test.py | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/beam_test.py b/beam_test.py index 2a70b070..b4d438b0 100644 --- a/beam_test.py +++ b/beam_test.py @@ -48,12 +48,6 @@ #Initialize def main(args): - # Used for creating the mask - masked = False - if args.mask is not None: - masked = True - with open(args.mask, 'r') as file: - bitmask = file.read() # Ensures output directory exists if os.path.exists(args.outdir) == False: os.mkdir(args.outdir) @@ -67,14 +61,11 @@ def main(args): pathdelim=os.path.sep #determine if Mac or Windows separators in path name ymlpath="config"+pathdelim+args.yaml+".yml" - # Passes mask if specified, else it creates a blank mask with no active pixels - if masked: - astro.asic_init(yaml=ymlpath, digital_mask=bitmask, analog_col = args.analog) - else: - astro.asic_init(yaml=ymlpath, analog_col = args.analog) + #Initiate asic with pixel mask as defined in yaml and analog pixel in row0 defined with input argument -a + astro.asic_init(yaml=ymlpath, analog_col = args.analog) - #If injection, enable injection pixel unless mask has been given. Mask overrides - if not masked and args.inject is not None: + #If injection, ensure injection pixel is enabled + if args.inject is not None: astro.enable_pixel(args.inject[1],args.inject[0]) # If injection is on initalize the board @@ -240,16 +231,13 @@ def main(args): help='save output files as CSV. If False, save as txt. Default: FALSE') parser.add_argument('-i', '--inject', action='store', default=None, type=int, nargs=2, - help = 'Turn on injection in the given row and column. If no mask (-m) given, then enables only this pixel. Overridden by mask (-m) Default: No injection') + help = 'Turn on injection in the given row and column. Default: No injection') parser.add_argument('-v','--vinj', action='store', default = None, type=float, help = 'Specify injection voltage (in mV). DEFAULT 300 mV') - parser.add_argument('-m', '--mask', action='store', required=False, type=str, default = None, - help = 'filepath to digital mask to enable digital readout. Default: No digital readout (all pixels off)') - parser.add_argument('-a', '--analog', action='store', required=False, type=int, default = 0, - help = 'Turn on analog output in the given column. Default: Column 0. Set to None to turn off analog output.') + help = 'Turn on analog output in the given column. Default: Column 0.') parser.add_argument('-t', '--threshold', type = float, action='store', default=None, help = 'Threshold voltage for digital ToT (in mV). DEFAULT 100mV') From 21ceed72a0b4a9835f052dfea744524ebf9f870a Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Fri, 7 Oct 2022 14:38:02 -0400 Subject: [PATCH 60/78] Remove option to supply txt file wtih pixel mask and use from yml instead. Mask can still be updated in the code with enable_pixel, enable_inj_row, etc. Removed some functionality by not allowing these input txt files at all. --- astropix.py | 72 ++++++----------------------------------------------- 1 file changed, 7 insertions(+), 65 deletions(-) diff --git a/astropix.py b/astropix.py index b181e1db..dea7aa0f 100644 --- a/astropix.py +++ b/astropix.py @@ -118,14 +118,13 @@ def write_conf_to_yaml(self, filename:str = None, chipversion:int = 2): # Method to initalize the asic. This is taking the place of asic.py. # All of the interfacing is handeled through asic_update - def asic_init(self, yaml:str = None, dac_setup: dict = None, bias_setup:dict = None, digital_mask:str = None, blankmask:bool = False, analog_col:int = None): + def asic_init(self, yaml:str = None, dac_setup: dict = None, bias_setup:dict = None, blankmask:bool = False, analog_col:int = None): """ self.asic_init() - initalize the asic configuration. Must be called first Positional arguments: None Optional: dac_setup: dict - dictionary of values passed to the configuration. Only needs values diffent from defaults bias_setup: dict - dict of values for the bias configuration Only needs key/vals for changes from default - digital_mask: str - String of 1s and 0s in 35x35 arangement which masks the array. Needed to enable pixels not (0,0) blankmask: bool - Create a blank mask (everything disabled). Pixels can be enabled manually analog_col: int - Sets a column to readout analog data from. """ @@ -149,17 +148,6 @@ def asic_init(self, yaml:str = None, dac_setup: dict = None, bias_setup:dict = N if dac_setup is not None: self.dacconfig.update(dac_setup) - #MASKING - """ - # If maskstring is passed it will mask based on that - if digital_mask is not None: - self._mask_from_string(digital_mask, None) - # If blankmask is set it will create a blank mask. Can be updated with - # self.enable_pixel() and whatnot - else: - self._make_blank_mask() - """ - ##analog output if (analog_col is not None) and (analog_col <= self._num_cols): logger.info(f"enabling analog output in column {analog_col}") @@ -190,7 +178,7 @@ def asic_update(self): # Methods to update the internal variables. Please don't do it manually # This updates the dac config - def update_asic_config(self, bias_cfg:dict = None, dac_cfg:dict = None, maskstr:str = None, analog_col:int=None): + def update_asic_config(self, bias_cfg:dict = None, dac_cfg:dict = None, analog_col:int=None): """ Updates and writes confgbits to asic @@ -202,8 +190,6 @@ def update_asic_config(self, bias_cfg:dict = None, dac_cfg:dict = None, maskstr: self.asic_config['biasconfig'].update(bias_cfg) if dac_cfg is not None: self.asic_config['idacs'].update(dac_cfg) - if maskstr is not None: - self._make_digital_mask(maskstr, analog_col) else: logger.info("update_asic_config() got no argumennts, nothing to do.") return None @@ -510,51 +496,6 @@ def _test_io(self): # Function to construct the reconfig dictionary. This code is taken from # asic.py. - # used for digital working with the sensor. - - def _mask_from_string(self, digitmask:str, analog_col=None): - # Cleans up the string, ensures it is only 1, 0, or \n - bitmask = re.sub("[^01\n]", "", digitmask) - # turn it into a list - bitlist = bitmask.split("\n") - # Remove any extra rows that creeped in - if len(bitlist) > 35: - bitlist = bitlist[0:35] - # The dictionary which is returned - self.recconfig = {} - # used in construction - i = 0 - # iterates through the list and does binary magic on it - for bits in bitlist: - # This works by adding entries to a dictionary and then: - # 1) creating a 35 bit space of zeros - # 2) converting the string to a binary integer - # 3) shifting by one to make room for injection on/off bit - # 4) setting the injection bit if we want injection on this run - # Bit 1: Analog output - # Bit 2: Injection - # last : Injection - analog_bit = 0b1 if (i == analog_col) else 0 - injection_bit_col = 0 if (i == self.injection_col) else 0 - injection_bit_row = 0 if (i == self.injection_row) else 0 - self.recconfig[f"ColConfig{i}"] = (analog_bit << 37) + (injection_bit_col << 36) + (int(bits, 2) << 1) + injection_bit_row - i += 1 - - def _make_blank_mask(self): - """ - Makes blank mask with no injections enabled and all outputs disabled. - Should be used with methods such as astropix2.enable_inj_col() and astropix2.enable_pixel() - - Internal method called by asic_init() - """ - - self.recconfig = {'ColConfig0': 0b001_11111_11111_11111_11111_11111_11111_11110} - i = 1 - while i < self._num_cols: - self.recconfig[f'ColConfig{i}'] = 0b001_11111_11111_11111_11111_11111_11111_11110 - i += 1 - - # Methods from updated asic.py @property def num_cols(self): @@ -669,15 +610,16 @@ def disable_inj_row(self, row: int): """Disable row injection switch :param row: Row number """ - if(row < self._num_rows): - self.recconfig[f'ColConfig{row}'] = self.recconfig.get(f'ColConfig{row}', 0b001_11111_11111_11111_11111_11111_11111_11110) & 0b111_11111_11111_11111_11111_11111_11111_11110 + if(row < self.num_rows): + self.asic_config['recconfig'][f'col{row}'][1] = self.asic_config['recconfig'].get(f'col{row}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] & 0b111_11111_11111_11111_11111_11111_11111_11110 + def disable_inj_col(self, col: int): """Disable col injection switch :param col: Col number """ - if(col < self._num_cols): - self.recconfig[f'ColConfig{col}'] = self.recconfig.get(f'ColConfig{col}', 0b001_11111_11111_11111_11111_11111_11111_11110) & 0b101_11111_11111_11111_11111_11111_11111_11111 + if(col < self.num_cols): + self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'].get(f'col{col}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] & 0b101_11111_11111_11111_11111_11111_11111_11111 def get_pixel(self, col: int, row: int): """ From 12c9f27519b522a5513208a6894922cadfe9e528 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Fri, 7 Oct 2022 14:40:28 -0400 Subject: [PATCH 61/78] update README to reflect that txt mask is no longer an input option --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 85ea63ad..bf5a32cb 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,6 @@ Options: | `-n` `--name` | `-n [SOMESTRING]` | Set additional name to be added to the timestamp in file outputs | None | | `-o` `--outdir`| `-o [DIRECTORY]` | Directory to save all output files to. Will be created if it doesn't exist. | `./` | | `-y` `--yaml`| `-y [NAME]` | Name of configuration file, assuming config/*.yml where * is passed. If not specified, uses config/testconfig.yml and disables all pixels | `testconfig` | -| `-m` `--mask` | `-m [PATH]` | Enable digital output of pixels passing mask. Takes a path to a text file specifying which pixels are enabled. If not specified will disable all pixels. | None / diabled array| | `-c` `--saveascsv` | `-c` | Toggle saving csv files on and off | Does not save csv | | `-s` `--showhits` | `-s` | Display hits in real time | Off | | `-p` `--plotsave` | `-p` | Saves real time plots as image files. Stored in outdir. | Does not save plots | From 5e8cfb81a9cb7ee9664c7d444eb8c4fa0fb0ce39 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Mon, 17 Oct 2022 14:14:03 -0400 Subject: [PATCH 62/78] remove directory of masks - depreciated by yaml config --- masks/blankmask.txt | 35 ----------------------------------- masks/mask_row0_col0.txt | 35 ----------------------------------- masks/mask_row0_col1.txt | 35 ----------------------------------- masks/mask_row1_col0.txt | 35 ----------------------------------- masks/mask_row1_col1.txt | 35 ----------------------------------- 5 files changed, 175 deletions(-) delete mode 100644 masks/blankmask.txt delete mode 100644 masks/mask_row0_col0.txt delete mode 100644 masks/mask_row0_col1.txt delete mode 100644 masks/mask_row1_col0.txt delete mode 100644 masks/mask_row1_col1.txt diff --git a/masks/blankmask.txt b/masks/blankmask.txt deleted file mode 100644 index da21fa5c..00000000 --- a/masks/blankmask.txt +++ /dev/null @@ -1,35 +0,0 @@ -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 diff --git a/masks/mask_row0_col0.txt b/masks/mask_row0_col0.txt deleted file mode 100644 index 6bb6ae16..00000000 --- a/masks/mask_row0_col0.txt +++ /dev/null @@ -1,35 +0,0 @@ -11111111111111111111111111111111110 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 \ No newline at end of file diff --git a/masks/mask_row0_col1.txt b/masks/mask_row0_col1.txt deleted file mode 100644 index 971b593d..00000000 --- a/masks/mask_row0_col1.txt +++ /dev/null @@ -1,35 +0,0 @@ -11111111111111111111111111111111111 -11111111111111111111111111111111110 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 \ No newline at end of file diff --git a/masks/mask_row1_col0.txt b/masks/mask_row1_col0.txt deleted file mode 100644 index 0cefa020..00000000 --- a/masks/mask_row1_col0.txt +++ /dev/null @@ -1,35 +0,0 @@ -11111111111111111111111111111111101 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 \ No newline at end of file diff --git a/masks/mask_row1_col1.txt b/masks/mask_row1_col1.txt deleted file mode 100644 index 07874432..00000000 --- a/masks/mask_row1_col1.txt +++ /dev/null @@ -1,35 +0,0 @@ -11111111111111111111111111111111111 -11111111111111111111111111111111101 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 -11111111111111111111111111111111111 \ No newline at end of file From 76540e9dc2848c633aa7962a9179bdaee23bf000 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Mon, 17 Oct 2022 14:34:03 -0400 Subject: [PATCH 63/78] rename backend dir to core and retain modules dir for extensions --- astropix.py | 15 +-- beam_test.py | 1 + {modules => core}/__init__.py | 0 {modules => core}/asic.py | 2 +- {modules => core}/decode.py | 0 {modules => core}/injectionboard.py | 2 +- {modules => core}/nexysio.py | 2 +- {modules => core}/spi.py | 0 {modules => core}/voltageboard.py | 2 +- main.py | 136 ---------------------------- 10 files changed, 13 insertions(+), 147 deletions(-) rename {modules => core}/__init__.py (100%) rename {modules => core}/asic.py (99%) rename {modules => core}/decode.py (100%) rename {modules => core}/injectionboard.py (99%) rename {modules => core}/nexysio.py (99%) rename {modules => core}/spi.py (100%) rename {modules => core}/voltageboard.py (98%) delete mode 100644 main.py diff --git a/astropix.py b/astropix.py index dea7aa0f..cc0676af 100644 --- a/astropix.py +++ b/astropix.py @@ -1,17 +1,18 @@ """ -Central module of astropix. This incorporates all of the various modules from the original -The class methods of all the other modules are inherited here. +Central module of astropix. This incorporates all of the various modules from the original 'module' directory backend (now 'core') +The class methods of all the other modules/cores are inherited here. Author: Autumn Bauman +Maintained by: Amanda Steinhebel, amanda.l.steinhebel@nasa.gov """ # Needed modules. They all import their own suppourt libraries, # and eventually there will be a list of which ones are needed to run from typing import Dict -from modules.spi import Spi -from modules.nexysio import Nexysio -from modules.decode import Decode -from modules.injectionboard import Injectionboard -from modules.voltageboard import Voltageboard +from core.spi import Spi +from core.nexysio import Nexysio +from core.decode import Decode +from core.injectionboard import Injectionboard +from core.voltageboard import Voltageboard from bitstring import BitArray from tqdm import tqdm import pandas as pd diff --git a/beam_test.py b/beam_test.py index b4d438b0..8d83fbe6 100644 --- a/beam_test.py +++ b/beam_test.py @@ -2,6 +2,7 @@ Updated version of beam_test.py using the astropix.py module Author: Autumn Bauman +Maintained by: Amanda Steinhebel, amanda.l.steinhebel@nasa.gov """ #from msilib.schema import File diff --git a/modules/__init__.py b/core/__init__.py similarity index 100% rename from modules/__init__.py rename to core/__init__.py diff --git a/modules/asic.py b/core/asic.py similarity index 99% rename from modules/asic.py rename to core/asic.py index ba029163..18dc898f 100644 --- a/modules/asic.py +++ b/core/asic.py @@ -13,7 +13,7 @@ from bitstring import BitArray from dataclasses import dataclass -from modules.nexysio import Nexysio +from core.nexysio import Nexysio from modules.setup_logger import logger diff --git a/modules/decode.py b/core/decode.py similarity index 100% rename from modules/decode.py rename to core/decode.py diff --git a/modules/injectionboard.py b/core/injectionboard.py similarity index 99% rename from modules/injectionboard.py rename to core/injectionboard.py index 0f591c7a..a908eb42 100644 --- a/modules/injectionboard.py +++ b/core/injectionboard.py @@ -8,7 +8,7 @@ import logging -from modules.nexysio import Nexysio +from core.nexysio import Nexysio from modules.setup_logger import logger PG_RESET = 2 diff --git a/modules/nexysio.py b/core/nexysio.py similarity index 99% rename from modules/nexysio.py rename to core/nexysio.py index 8ce8f1ad..d94603c3 100644 --- a/modules/nexysio.py +++ b/core/nexysio.py @@ -14,7 +14,7 @@ import binascii import time -from modules.spi import Spi +from core.spi import Spi from modules.setup_logger import logger READ_ADRESS = 0x00 diff --git a/modules/spi.py b/core/spi.py similarity index 100% rename from modules/spi.py rename to core/spi.py diff --git a/modules/voltageboard.py b/core/voltageboard.py similarity index 98% rename from modules/voltageboard.py rename to core/voltageboard.py index 4f0be26f..597232a7 100644 --- a/modules/voltageboard.py +++ b/core/voltageboard.py @@ -7,7 +7,7 @@ """ from bitstring import BitArray -from modules.nexysio import Nexysio +from core.nexysio import Nexysio class Voltageboard(Nexysio): diff --git a/main.py b/main.py deleted file mode 100644 index 196569df..00000000 --- a/main.py +++ /dev/null @@ -1,136 +0,0 @@ -# -*- coding: utf-8 -*- -"""""" -""" -Created on Sat Jun 26 00:10:56 2021 - -@author: Nicolas Striebig -""" - -from modules.asic import Asic -from modules.injectionboard import Injectionboard -from modules.nexysio import Nexysio -from modules.voltageboard import Voltageboard -from modules.decode import Decode - -from utils.utils import wait_progress - -import binascii - -def main(): - - nexys = Nexysio() - - # Open FTDI Device with Index 0 - #handle = nexys.open(0) - handle = nexys.autoopen() - - # Write and read directly to register - # Example: Write 0x55 to register 0x09 and read it back - nexys.write_register(0x09, 0x55, True) - nexys.read_register(0x09) - - # - # Configure ASIC - # - - # Write to asicSR - asic = Asic(handle) - asic.load_conf_from_yaml(2,"testconfig") - #asic.asic_config['idacs']['vncomp'] = 60 - asic.write_conf_to_yaml(2,"testconfig_write") - asic.load_conf_from_yaml(2,"testconfig_write") - asic.update_asic() - - # Example: Update Config Bit - # asic.digitalconfig['En_Inj17'] = 1 - # asic.dacs['vn1'] = 63 - # asic.update_asic() - - # - # Configure Voltageboard - # - - # Configure 8 DAC Voltageboard in Slot 4 with list values - # 3 = Vcasc2, 4=BL, 7=Vminuspix, 8=Thpix - vboard1 = Voltageboard(handle, 4, (8, [0, 0, 1.1, 1, 0, 0, 1, 1.1])) - - # Set measured 1V for one-point calibration - vboard1.vcal = 0.989 - vboard1.vsupply = 2.7 - - # Update voltageboards - vboard1.update_vb() - - # Write only first 3 DACs, other DACs will be 0 - # vboard1.dacvalues = (8, [1.2, 1, 1]) - # vboard1.update_vb() - - # - # Configure Injectionboard - # - - # Set Injection level - injvoltage = Voltageboard(handle, 3, (2, [0.3, 0.0])) - injvoltage.vcal = vboard1.vcal - injvoltage.vsupply = vboard1.vsupply - injvoltage.update_vb() - - inj = Injectionboard(handle) - - # Set Injection Params for 330MHz patgen clock - inj.period = 100 - inj.clkdiv = 4000 - inj.initdelay = 10000 - inj.cycle = 0 - inj.pulsesperset = 1 - - # - # SPI - # - - # Enable SPI - nexys.spi_enable() - nexys.spi_reset() - - # Set SPI clockdivider - # freq = 100 MHz/spi_clkdiv - nexys.spi_clkdiv = 255 - - # Generate bitvector for SPI ASIC config - asic_bitvector = asic.gen_asic_vector() - spi_data = nexys.asic_spi_vector(asic_bitvector, True, 10) - - # Write Config via spi - # nexys.write_spi(spi_data, False, 8191) - - # Send Routing command - nexys.send_routing_cmd() - - # Reset SPI Read FIFO - nexys.spi_reset() - - inj.start() - - wait_progress(3) - - # Write 8*1000 Bytes to MOSI - nexys.write_spi_bytes(1000) - - # Read (Width 8 Bytes) until read FIFO is empty - readout = nexys.read_spi_fifo() - - print(binascii.hexlify(readout)) - - decode = Decode() - list_hits = decode.hits_from_readoutstream(readout) - - decode.decode_astropix2_hits(list_hits) - - # inj.stop() - - # Close connection - nexys.close() - - -if __name__ == "__main__": - main() From d5dcbd7719bfeaf278bcb4a10548288689131260 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Mon, 17 Oct 2022 15:30:34 -0400 Subject: [PATCH 64/78] move asic backend functions back into core asic.py script and out of astropix.py --- astropix.py | 272 ++++++++------------------------------------------- core/asic.py | 205 +++++++++++++++++++------------------- 2 files changed, 142 insertions(+), 335 deletions(-) diff --git a/astropix.py b/astropix.py index cc0676af..abb01bab 100644 --- a/astropix.py +++ b/astropix.py @@ -13,6 +13,7 @@ from core.decode import Decode from core.injectionboard import Injectionboard from core.voltageboard import Voltageboard +from core.asic import Asic from bitstring import BitArray from tqdm import tqdm import pandas as pd @@ -45,9 +46,6 @@ def __init__(self, clock_period_ns = 10, inject:int = None, offline:bool=False): # to put in custom configurations and allows for less writing to the chip, # only doing it once at init or when settings need to be changed as opposed to # each time a parameter is changed. - self._num_rows = 35 - self._num_cols = 35 - self.asic_config = None if offline: logger.info("Creating object for offline analysis") @@ -84,14 +82,14 @@ def load_conf_from_yaml(self, filename:str = None, chipversion:int = 2): logger.error(exc) try: - self.asic_config = dict_from_yml.get(f'astropix{chipversion}')['config'] + self.asic.asic_config = dict_from_yml.get(f'astropix{chipversion}')['config'] logger.info(f"Astropix{chipversion} config found!") except: logger.error(f"Astropix{chipversion} config not found") try: - self.num_cols = dict_from_yml[f'astropix{chipversion}'].get('geometry')['cols'] - self.num_rows = dict_from_yml[f'astropix{chipversion}'].get('geometry')['rows'] + self.asic._num_cols = dict_from_yml[f'astropix{chipversion}'].get('geometry')['cols'] + self.asic._num_rows = dict_from_yml[f'astropix{chipversion}'].get('geometry')['rows'] logger.info(f"Astropix{chipversion} matrix dimensions found!") except: logger.error(f"Astropix{chipversion} matrix dimensions not found!") @@ -106,8 +104,8 @@ def write_conf_to_yaml(self, filename:str = None, chipversion:int = 2): try: yaml.dump({f"astropix{chipversion}": \ { - "geometry": {"cols": self.num_cols, "rows": self.num_rows},\ - "config" : self.asic_config}\ + "geometry": {"cols": self.asic._num_cols, "rows": self.asic._num_rows},\ + "config" : self.asic.asic_config}\ }, stream, default_flow_style=False, sort_keys=False) @@ -130,51 +128,51 @@ def asic_init(self, yaml:str = None, dac_setup: dict = None, bias_setup:dict = N analog_col: int - Sets a column to readout analog data from. """ - # Now that the asic has been initalized we can go and make this true self._asic_start = True - # The use of update methods on the dictionary allows for only the keys that - # need changing to be passed to the function (hopefully) simplifying the interface + + self.asic = Asic(self.handle, self.nexys) + + #Override yaml if arguments were given in run script + if bias_setup is not None: + self.biasconfig.update(bias_setup) + if dac_setup is not None: + self.dacconfig.update(dac_setup) # Get config values from YAML try: self.load_conf_from_yaml(yaml) except Exception: logger.error('Must pass a configuration file in the form of *.yml') - #Config stored in dictionary self.asic_config . This is used for configuration in asic_update. If any changes are made, make change to self.asic_config so that it is reflected on-chip when asic_update is called - - #Override yaml if arugments were given in run script - if bias_setup is not None: - self.biasconfig.update(bias_setup) - if dac_setup is not None: + #Config stored in dictionary self.asic_config . This is used for configuration in asic_update. + #If any changes are made, make change to self.asic_config so that it is reflected on-chip when + # asic_update is called self.dacconfig.update(dac_setup) - ##analog output - if (analog_col is not None) and (analog_col <= self._num_cols): + # Set analog output + if (analog_col is not None) and (analog_col <= self.asic._num_cols): logger.info(f"enabling analog output in column {analog_col}") - self.enable_ampout_col(analog_col, inplace=False) + self.asic.enable_ampout_col(analog_col, inplace=False) # Turns on injection if so desired if self.injection_col is not None: - self.enable_inj_col(self.injection_col, inplace=False) - self.enable_inj_row(self.injection_row, inplace=False) + self.asic.enable_inj_col(self.injection_col, inplace=False) + self.asic.enable_inj_row(self.injection_row, inplace=False) - # Loads it to the chip + # Load config it to the chip logger.info("LOADING TO ASIC...") self.asic_update() logger.info("ASIC SUCCESSFULLY CONFIGURED") + #Interface with asic.py + def enable_pixel(self, col: int, row: int, inplace:bool=True): + self.asic.enable_pixel(col, row, inplace) + # The method to write data to the asic. Called whenever somthing is changed # or after a group of changes are done. Taken straight from asic.py. def asic_update(self): - """ - Remakes configbits and writes to asic. - Takes no input and does not return - """ - self.nexys.chip_reset() - asicbits = self.nexys.gen_asic_pattern(self._construct_asic_vector(), True) - self.nexys.write(asicbits) - logger.info("Wrote configbits successfully") + self.nexys.chip_reset() + self.asic.asic_update() # Methods to update the internal variables. Please don't do it manually @@ -188,16 +186,15 @@ def update_asic_config(self, bias_cfg:dict = None, dac_cfg:dict = None, analog_c """ if self.asic_start: if bias_cfg is not None: - self.asic_config['biasconfig'].update(bias_cfg) + self.asic.asic_config['biasconfig'].update(bias_cfg) if dac_cfg is not None: - self.asic_config['idacs'].update(dac_cfg) + self.asic.asic_config['idacs'].update(dac_cfg) else: logger.info("update_asic_config() got no argumennts, nothing to do.") return None self.asic_update() else: raise RuntimeError("Asic has not been initalized") - def enable_spi(self): """ Starts spi bus. @@ -376,17 +373,17 @@ def get_log_header(self): """ #Get config dictionaries from yaml digitalconfig = {} - for key in self.asic_config['digitalconfig']: - digitalconfig[key]=self.asic_config['digitalconfig'][key][1] + for key in self.asic.asic_config['digitalconfig']: + digitalconfig[key]=self.asic.asic_config['digitalconfig'][key][1] biasconfig = {} - for key in self.asic_config['biasconfig']: - biasconfig[key]=self.asic_config['biasconfig'][key][1] + for key in self.asic.asic_config['biasconfig']: + biasconfig[key]=self.asic.asic_config['biasconfig'][key][1] dacconfig = {} - for key in self.asic_config['idacs']: - dacconfig[key]=self.asic_config['idacs'][key][1] + for key in self.asic.asic_config['idacs']: + dacconfig[key]=self.asic.asic_config['idacs'][key][1] arrayconfig = {} - for key in self.asic_config['recconfig']: - arrayconfig[key]=self.asic_config['recconfig'][key][1] + for key in self.asic.asic_config['recconfig']: + arrayconfig[key]=self.asic.asic_config['recconfig'][key][1] # This is not a nice line, but its the most efficent way to get all the values in the same place. return f"Voltageboard settings: {self.vboard.dacvalues}\n" + f"Digital: {digitalconfig}\n" +f"Biasblock: {biasconfig}\n" + f"DAC: {dacconfig}\n" + f"Receiver: {arrayconfig}\n" @@ -484,7 +481,6 @@ def dump_fpga(self): # _test_io(): A function to read and write a register on the chip to see if # everythign is working. # It takes no arguments - def _test_io(self): try: # Attempts to write to and read from a register self.nexys.write_register(0x09, 0x55, True) @@ -494,196 +490,6 @@ def _test_io(self): except Exception: raise RuntimeError("Could not read or write from astropix!") - # Function to construct the reconfig dictionary. This code is taken from - # asic.py. - - # Methods from updated asic.py - @property - def num_cols(self): - """Get/set number of columns - - :returns: Number of columns - """ - return self._num_cols - - @num_cols.setter - def num_cols(self, cols): - self._num_cols = cols - - @property - def num_rows(self): - """Get/set number of rows - - :returns: Number of rows - """ - return self._num_rows - - @num_rows.setter - def num_rows(self, rows): - self._num_rows = rows - - def get_num_cols(self): - """Get number of columns - :returns: Number of columns - """ - return self._num_cols - - def get_num_rows(self): - """Get number of rows - :returns: Number of rows - """ - return self._num_rows - - def enable_inj_row(self, row: int, inplace:bool=True): - """ - Enable injection in specified row - - Takes: - row: int - Row number - inplace:bool - True - Updates asic after updating pixel mask - - """ - - if(row < self.num_rows): - self.asic_config['recconfig'][f'col{row}'][1] = self.asic_config['recconfig'].get(f'col{row}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] | 0b000_00000_00000_00000_00000_00000_00000_00001 - - if inplace: self.asic_update() - - def enable_inj_col(self, col: int, inplace:bool=True): - """ - Enable injection in specified column - - Takes: - col: int - Column number - inplace:bool - True - Updates asic after updating pixel mask -""" - if(col < self.num_cols): - self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'].get(f'col{col}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] | 0b010_00000_00000_00000_00000_00000_00000_00000 - if inplace: self.asic_update() - - - def enable_ampout_col(self, col: int, inplace:bool=True): - """ - Enables analog output, Select Col for analog mux and disable other cols - - Takes: - col:int - Column to enable - inplace:bool - True - Updates asic after updating pixel mask - """ - #Disable all analog pixels - for i in range(self.num_cols): - self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'][f'col{col}'][1] & 0b011_11111_11111_11111_11111_11111_11111_11111 - - #Enable analog pixel in column - self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'][f'col{col}'][1] | 0b100_00000_00000_00000_00000_00000_00000_00000 - - if inplace: self.asic_update() - - def enable_pixel(self, col: int, row: int, inplace:bool=True): - """ - Turns on comparator in specified pixel - - Takes: - col: int - Column of pixel - row: int - Row of pixel - inplace:bool - True - Updates asic after updating pixel mask - """ - if(row < self.num_rows and col < self.num_cols): - self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'].get(f'col{col}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] & ~(2 << row) - - if inplace: self.asic_update() - - def disable_pixel(self, col: int, row: int, inplace:bool=True): - """ - Disable comparator in specified pixel - - Takes: - col: int - Column of pixel - row: int - Row of pixel - inplace:bool - True - Updates asic after updating pixel mask - """ - if(row < self.num_rows and col < self.num_cols): - self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'].get(f'col{col}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] | (2 << row) - if inplace: self.asic_update() - - - def disable_inj_row(self, row: int): - """Disable row injection switch - :param row: Row number - """ - if(row < self.num_rows): - self.asic_config['recconfig'][f'col{row}'][1] = self.asic_config['recconfig'].get(f'col{row}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] & 0b111_11111_11111_11111_11111_11111_11111_11110 - - - def disable_inj_col(self, col: int): - """Disable col injection switch - :param col: Col number - """ - if(col < self.num_cols): - self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'].get(f'col{col}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] & 0b101_11111_11111_11111_11111_11111_11111_11111 - - def get_pixel(self, col: int, row: int): - """ - Checks if a given pixel is enabled - - Takes: - col: int - column of pixel - row: int - row of pixel - """ - if(row < self._num_rows): - if( self.recconfig.get(f'ColConfig{col}') & (1<<(row+1))): - return False - else: - return True - def reset_recconfig(self): - """Reset recconfig by disabling all pixels and disabling all injection switches and mux ouputs - """ - for key in self.asic_config['recconfig']: - self.asic_config['recconfig'][key][1] = 0b001_11111_11111_11111_11111_11111_11111_11110 - - - # This is from asic.py, and it essentially takes all the parameters and puts - # them into a form ready to be loaded onto the board. - # Parameters: msbfirst: Send vector MSB first - - def _construct_asic_vector(self, msbfirst:bool = False) -> BitArray: - """Generate asic bitvector from digital, bias and dacconfig - - :param msbfirst: Send vector MSB first - """ - bitvector = BitArray() - - for key in self.asic_config: - logger.debug(key) - for values in self.asic_config[key].values(): - bitvector.append(self.__int2nbit(values[1], values[0])) - logger.debug(self.__int2nbit(values[1], values[0])) - """ - for values in self.asic_config['biasconfig'].values(): - bitvector.append(self.__int2nbit(values[1], values[0])) - """ - - if not msbfirst: - bitvector.reverse() - - logger.debug(bitvector) - - return bitvector - - def __int2nbit(self,value: int, nbits: int) -> BitArray: - """Convert int to 6bit bitarray - - :param value: Integer value - :param nbits: Number of bits - - :returns: Bitarray of specified length - """ - - try: - return BitArray(uint=value, length=nbits) - except ValueError: - print(f'Allowed Values 0 - {2**nbits-1}') - # progress bar def _wait_progress(self, seconds:int): for _ in tqdm(range(seconds), desc=f'Wait {seconds} s'): diff --git a/core/asic.py b/core/asic.py index 18dc898f..38b1f0b3 100644 --- a/core/asic.py +++ b/core/asic.py @@ -4,6 +4,7 @@ Created on Fri Jun 25 16:28:27 2021 @author: Nicolas Striebig +Editor for astropix.py module: Autumn Bauman Astropix2 Configbits """ @@ -22,12 +23,12 @@ class Asic(Nexysio): """Configure ASIC""" - def __init__(self, handle) -> None: + def __init__(self, handle, nexys) -> None: self._handle = handle + self.nexys = nexys self._chipversion = None - self._num_rows = 35 self._num_cols = 35 @@ -68,175 +69,175 @@ def num_rows(self): @num_rows.setter def num_rows(self, rows): self._num_rows = rows + + def get_num_cols(self): + """Get number of columns + :returns: Number of columns + """ + return self._num_cols - def enable_inj_row(self, row: int): - """Enable Row injection switch + def get_num_rows(self): + """Get number of rows + :returns: Number of rows + """ + return self._num_rows + + def asic_update(self): + """ + Remakes configbits and writes to asic. + Takes no input and does not return + """ + if self._chipversion == 1: + dummybits = self.nexys.gen_asic_pattern(BitArray(uint=0, length=245), True) # Not needed for v2 + self.nexys.write(dummybits) + + # Write config + asicbits = self.nexys.gen_asic_pattern(self._construct_asic_vector(), True) + self.nexys.write(asicbits) + logger.info("Wrote configbits successfully") + + def enable_inj_row(self, row: int, inplace:bool=True): + """ + Enable injection in specified row + + Takes: + row: int - Row number + inplace:bool - True - Updates asic after updating pixel mask - :param row: Row number """ + if(row < self.num_rows): self.asic_config['recconfig'][f'col{row}'][1] = self.asic_config['recconfig'].get(f'col{row}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] | 0b000_00000_00000_00000_00000_00000_00000_00001 + + if inplace: self.asic_update() - def enable_inj_col(self, col: int): - """Enable col injection switch + def enable_inj_col(self, col: int, inplace:bool=True): + """ + Enable injection in specified column - :param col: Col number + Takes: + col: int - Column number + inplace:bool - True - Updates asic after updating pixel mask """ if(col < self.num_cols): self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'].get(f'col{col}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] | 0b010_00000_00000_00000_00000_00000_00000_00000 + if inplace: self.asic_update() - def enable_ampout_col(self, col: int): - """Select Col for analog mux and disable other cols - :param col: Col number + def enable_ampout_col(self, col: int, inplace:bool=True): """ + Enables analog output, Select Col for analog mux and disable other cols + + Takes: + col:int - Column to enable + inplace:bool - True - Updates asic after updating pixel mask + """ + #Disable all analog pixels for i in range(self.num_cols): - self.asic_config['recconfig'][f'col{i}'][1] = self.asic_config['recconfig'][f'col{i}'][1] & 0b011_11111_11111_11111_11111_11111_11111_11111 + self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'][f'col{col}'][1] & 0b011_11111_11111_11111_11111_11111_11111_11111 + #Enable analog pixel in column self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'][f'col{col}'][1] | 0b100_00000_00000_00000_00000_00000_00000_00000 + + if inplace: self.asic_update() - def enable_pixel(self, col: int, row: int): - """Enable pixel comparator for specified pixel + def enable_pixel(self, col: int, row: int, inplace:bool=True): + """ + Turns on comparator in specified pixel - :param col: Col number - :param row: Row number + Takes: + col: int - Column of pixel + row: int - Row of pixel + inplace:bool - True - Updates asic after updating pixel mask """ if(row < self.num_rows and col < self.num_cols): self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'].get(f'col{col}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] & ~(2 << row) - def disable_pixel(self, col: int, row: int): - """Disable pixel comparator for specified pixel + if inplace: self.asic_update() - :param col: Col number - :param row: Row number + def disable_pixel(self, col: int, row: int, inplace:bool=True): + """ + Disable comparator in specified pixel + + Takes: + col: int - Column of pixel + row: int - Row of pixel + inplace:bool - True - Updates asic after updating pixel mask """ if(row < self.num_rows and col < self.num_cols): self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'].get(f'col{col}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] | (2 << row) + if inplace: self.asic_update() + def disable_inj_row(self, row: int): """Disable row injection switch - :param row: Row number """ if(row < self.num_rows): self.asic_config['recconfig'][f'col{row}'][1] = self.asic_config['recconfig'].get(f'col{row}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] & 0b111_11111_11111_11111_11111_11111_11111_11110 + def disable_inj_col(self, col: int): """Disable col injection switch - :param col: Col number """ if(col < self.num_cols): self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'].get(f'col{col}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] & 0b101_11111_11111_11111_11111_11111_11111_11111 def get_pixel(self, col: int, row: int): - """Check if Pixel is enabled + """ + Checks if a given pixel is enabled - :param col: Col number - :param row: Row number + Takes: + col: int - column of pixel + row: int - row of pixel """ - if(row < self.num_rows): - if( self.asic_config['recconfig'].get(f'col{col}')[1] & (1<<(row+1))): + if(row < self._num_rows): + if( self.recconfig.get(f'ColConfig{col}') & (1<<(row+1))): return False else: return True - def reset_recconfig(self): """Reset recconfig by disabling all pixels and disabling all injection switches and mux ouputs """ for key in self.asic_config['recconfig']: self.asic_config['recconfig'][key][1] = 0b001_11111_11111_11111_11111_11111_11111_11110 - - @staticmethod - def __int2nbit(value: int, nbits: int) -> BitArray: - """Convert int to 6bit bitarray - - :param value: Integer value - :param nbits: Number of bits - - :returns: Bitarray of specified length - """ - - try: - return BitArray(uint=value, length=nbits) - except ValueError: - print(f'Allowed Values 0 - {2**nbits-1}') - - def load_conf_from_yaml(self, chipversion: int, filename: str): - """Load ASIC config from yaml - - - :param filename: Name of yml file in config folder - """ - self.chipversion = chipversion - - with open(f"config/{filename}.yml", "r") as stream: - try: - dict_from_yml = yaml.safe_load(stream) - except yaml.YAMLError as exc: - logger.error(exc) - - try: - self.asic_config = dict_from_yml.get(f'astropix{chipversion}')['config'] - logger.info(f"Astropix{chipversion} config found!") - except: - logger.error(f"Astropix{chipversion} config not found") - - try: - self.num_cols = dict_from_yml[f'astropix{chipversion}'].get('geometry')['cols'] - self.num_rows = dict_from_yml[f'astropix{chipversion}'].get('geometry')['rows'] - logger.info(f"Astropix{chipversion} matrix dimensions found!") - except: - logger.error(f"Astropix{chipversion} matrix dimensions not found!") - - def write_conf_to_yaml(self, chipversion: int, filename: str): - """Write ASIC config to yaml - - :param chipversion: Name of yml file in config folder - :param filename: Name of yml file in config folder - """ - with open(f"config/{filename}.yml", "w") as stream: - try: - yaml.dump({f"astropix{chipversion}": \ - { - "geometry": {"cols": self.num_cols, "rows": self.num_rows},\ - "config" : self.asic_config}\ - }, - stream, default_flow_style=False, sort_keys=False) - - except yaml.YAMLError as exc: - logger.error(exc) - - - def gen_asic_vector(self, msbfirst: bool = False) -> BitArray: + + def _construct_asic_vector(self, msbfirst:bool = False) -> BitArray: """Generate asic bitvector from digital, bias and dacconfig :param msbfirst: Send vector MSB first """ - bitvector = BitArray() for key in self.asic_config: + logger.debug(key) for values in self.asic_config[key].values(): bitvector.append(self.__int2nbit(values[1], values[0])) + logger.debug(self.__int2nbit(values[1], values[0])) if not msbfirst: bitvector.reverse() - return bitvector + logger.debug(bitvector) - def update_asic(self) -> None: - """Update ASIC""" + return bitvector - if self.chipversion == 1: - dummybits = self.gen_asic_pattern(BitArray(uint=0, length=245), True) # Not needed for v2 - self.write(dummybits) + def __int2nbit(self,value: int, nbits: int) -> BitArray: + """Convert int to 6bit bitarray - # Write config - asicbits = self.gen_asic_pattern(self.gen_asic_vector(), True) - self.write(asicbits) + :param value: Integer value + :param nbits: Number of bits + + :returns: Bitarray of specified length + """ + + try: + return BitArray(uint=value, length=nbits) + except ValueError: + print(f'Allowed Values 0 - {2**nbits-1}') def readback_asic(self): asicbits = self.gen_asic_pattern(self.gen_asic_vector(), True, readback_mode = True) print(asicbits) - self.write(asicbits) + self.nexys.write(asicbits) \ No newline at end of file From 9684afef7e864401dd7ac45c60fc2457f0b87c20 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Mon, 17 Oct 2022 16:30:41 -0400 Subject: [PATCH 65/78] update example scripts for yaml config --- Voltagescan.py | 242 ------------------------------------------------ beam_test.py | 8 +- example_loop.py | 19 +++- loop_DACs.py | 19 +++- 4 files changed, 32 insertions(+), 256 deletions(-) delete mode 100644 Voltagescan.py diff --git a/Voltagescan.py b/Voltagescan.py deleted file mode 100644 index 6f6fa19c..00000000 --- a/Voltagescan.py +++ /dev/null @@ -1,242 +0,0 @@ -""" -Code to run power supply bias voltage scanning with a source. This will not be near as robust as the beam_test.py -but since it is just for my use (for now) I think it will do. - - -Keithley_IP = "169.254.127.39" -BiasHV = -60.0 #in Volt -maxCurrent = 0.001 #in Ampere -""" - -from astropix import astropix2 -import pandas as pd -import numpy as np -import logging -import binascii -import time -import os -from modules.pyKeithleyCtl import KeithleySupply as RC -from modules.setup_logger import logger - - -datadir = "biasscan_weekend_7-15" -psudir = "ps" -digitdir = "digital" - -vmin = -10 -vmax = -130 -vstep = -10 - -testlen = 6 * 60 * 60 - -stable_time = 5 * 60 - -Keithley_IP = "169.254.127.39" -BiasHV = -60.0 #in Volt -maxCurrent = 0.001 #in Ampere - - -maxlen = 10 * 60 * 60 - - -pspath = datadir + '/' + psudir -csvpath = datadir + '/' + digitdir - -basecrrnt = pspath + f"/bias_scan_{vmin}_{vmax}_{vstep}_Source_Ba-133_CURRENTS" + time.strftime("%Y%m%d-%H%M%S") -basedigit = csvpath + f"/bias_scan_{vmin}_{vmax}_{vstep}_Source_Ba-133_DIGITAL" + time.strftime("%Y%m%d-%H%M%S") -basebits = csvpath + f"/bias_scan_{vmin}_{vmax}_{vstep}_Source_Ba-133_BITSTREAMS" + time.strftime("%Y%m%d-%H%M%S") - - -if os.path.exists(pspath) == False: - os.makedirs(pspath) -if os.path.exists(csvpath) == False: - os.makedirs(csvpath) - -# This sets the logger name. -logdir = "./runlogs/" -if os.path.exists(logdir) == False: - os.mkdir(logdir) -logname = "./runlogs/AstropixHVScanlog_" +f"{vmin}_{vmax}_{vstep}_"+ time.strftime("%Y%m%d-%H%M%S") + ".log" - -decode_fail_frame = pd.DataFrame({ - 'readout': np.nan, - 'Chip ID': np.nan, - 'payload': np.nan, - 'location': np.nan, - 'isCol': np.nan, - 'timestamp': np.nan, - 'tot_msb': np.nan, - 'tot_lsb': np.nan, - 'tot_total': np.nan, - 'tot_us': np.nan, - 'hittime': np.nan - }, index=[0] -) - - - -def printVoltages(PS): - print("Set voltage: ", PS.get_voltage(), "V") - print("Measured voltage:", PS.measure_voltage(), "V") - print("Max current: ", PS.get_ocp() - , "A") - print("Measured current:",format((float(PS.measure_current()) * (10**9)), '0.4f'), "nA") - - -def getData(astro:astropix2, PS, runtime, bias, basecrrnt, basedigit, basebits): - - maxtime = time.time() + runtime - crrntpth = basecrrnt + f"_{bias}V_bias.csv" - digitpth = basedigit + f"_{bias}V_bias.csv" - bitspth = basebits + f"_{bias}V_bias.log" - - csvframe =pd.DataFrame(columns = [ - 'readout', - 'Chip ID', - 'payload', - 'location', - 'isCol', - 'timestamp', - 'tot_msb', - 'tot_lsb', - 'tot_total', - 'tot_us', - 'hittime']) - - - bitfile = open(bitspth, 'w') - bitfile.write(astro.get_log_header()) - - returnval = 0 - - errors = 0 - maxerrors = 10 - try: - - PS.enable_output() - # Time to stabilize - logger.info("HV Output on") - - logger.info("set voltage, waiting 5 min") - time.sleep(stable_time) - - PS.start_measurement(maxlen) # Turns on HV and starts taking data - - - logger.info("HV Logging Start") - i = 0 - while (time.time() <= maxtime) and (errors <= maxerrors): - - if astro.hits_present(): # Checks if hits are present - time.sleep(.1) # this is probably not needed, will ask Nicolas - - readout = astro.get_readout() # Gets the bytearray from the chip - # Writes the hex version to hits - bitfile.write(f"{i}\t{str(binascii.hexlify(readout))}\n") - print(binascii.hexlify(readout)) - - # Added fault tolerance for decoding, the limits of which are set through arguments - try: - hits = astro.decode_readout(readout, i, printer = True) - - except IndexError: - errors += 1 - logger.warning(f"Decoding failed. Failure {errors} of {maxerrors} on readout {i}") - # We write out the failed decode dataframe - hits = decode_fail_frame - hits.readout = i - hits.hittime = time.time() - - # This loggs the end of it all - if errors > maxerrors: - logger.warning(f"Decoding failed {errors} times on an index error. Terminating Progam...") - returnval = 10 - finally: - i += 1 - csvframe = pd.concat([csvframe, hits]) - - - except KeyboardInterrupt: - logger.info("Recieved Interrupt. Terminating program...") - returnval = 20 - finally: - data, nRow = PS.stop_measurement() - PS.disable_output() - df = PS.to_csv(data, nRow) - df["VOLTAGE"] = bias - df.to_csv(crrntpth) - - # Writes digital data - csvframe.index.name = "dec_order" - csvframe.to_csv(digitpth) - - - - return returnval - - -def main(): - PS = RC(Keithley_IP) - PS.clear() - PS.reset() - PS.set_voltage(-60) - PS.set_ocp(maxCurrent) - printVoltages(PS) - - # Quick check to make sure it is working - if input("Does this look correct? (Y/n)") == "n": - PS.disable_output() - PS.close() - - astro = astropix2(inject=False) - astro.asic_init() - astro.init_voltages() - astro.init_injection() - astro.enable_spi() - logger.info("Chip configured") - astro.dump_fpga() - try: # Main loop. - # Encased in try statement to gaurd against errors - for bias in range(vmin, vmax + vstep, vstep): - PS.set_voltage(bias) # sets the bias - time.sleep(1) - logger.info(f"HV supply voltage set:{bias}V") - # Runs the data gathering - cont = getData(astro, PS, testlen, bias, basecrrnt, basedigit, basebits) - # checks to make sure it didn't fail - if cont == 10: - raise RuntimeError("Maximum errors exceded!") - if cont == 20: - raise KeyboardInterrupt - # Loops again - - except KeyboardInterrupt: - logger.info("Keyboard interup. Terminating...") - - except Exception as e: - logger.exception(f"e") - - - finally: - PS.disable_output() - PS.close() - astro.close_connection() - - - - -if __name__ == "__main__": - formatter = logging.Formatter('%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s') - fh = logging.FileHandler(logname) - fh.setFormatter(formatter) - sh = logging.StreamHandler() - sh.setFormatter(formatter) - - logging.getLogger().addHandler(sh) - logging.getLogger().addHandler(fh) - logging.getLogger().setLevel(logging.INFO) - - logger = logging.getLogger(__name__) - - main() - diff --git a/beam_test.py b/beam_test.py index 8d83fbe6..a9ce612e 100644 --- a/beam_test.py +++ b/beam_test.py @@ -65,12 +65,9 @@ def main(args): #Initiate asic with pixel mask as defined in yaml and analog pixel in row0 defined with input argument -a astro.asic_init(yaml=ymlpath, analog_col = args.analog) - #If injection, ensure injection pixel is enabled + #If injection, ensure injection pixel is enabled and initialize if args.inject is not None: astro.enable_pixel(args.inject[1],args.inject[0]) - - # If injection is on initalize the board - if args.inject is not None: astro.init_injection(inj_voltage=args.vinj) #Enable final configuration @@ -279,8 +276,7 @@ def main(args): elif ll == 'C': loglevel = logging.CRITICAL - # Logging stuff! - # This was way harder than I expected... + # Logging formatter = logging.Formatter('%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s') fh = logging.FileHandler(logname) fh.setFormatter(formatter) diff --git a/example_loop.py b/example_loop.py index 10bc8983..db6d214d 100644 --- a/example_loop.py +++ b/example_loop.py @@ -58,9 +58,14 @@ def main(args,row,col,injectPix): else: astro = astropix2() #initialize without enabling injections - # Initialie asic - blank array, no pixels enabled - astro.asic_init() - astro.init_voltages(vthreshold=args.threshold) + astro.init_voltages(vthreshold=args.threshold) #no updates in YAML + + #Define YAML path variables + pathdelim=os.path.sep #determine if Mac or Windows separators in path name + ymlpath="config"+pathdelim+args.yaml+".yml" + + #Initiate asic with pixel mask as defined in yaml + astro.asic_init(yaml=ymlpath) #Enable single pixel in (col,row) #Updates asic by default @@ -99,6 +104,9 @@ def main(args,row,col,injectPix): 'hittime' ]) + # Save final configuration to output file + ymlpathout="config"+pathdelim+args.yaml+"_"+time.strftime("%Y%m%d-%H%M%S")+".yml" + astro.write_conf_to_yaml(ymlpathout) # And here for the text files/logs bitpath = args.outdir + '/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.log' # textfiles are always saved so we open it up @@ -177,6 +185,9 @@ def main(args,row,col,injectPix): parser.add_argument('-o', '--outdir', default='.', required=False, help='Output Directory for all datafiles') + + parser.add_argument('-y', '--yaml', action='store', required=False, type=str, default = 'testconfig', + help = 'filepath (in config/ directory) .yml file containing chip configuration. Default: config/testconfig.yml (All pixels off)') parser.add_argument('-c', '--saveascsv', action='store_true', default=False, required=False, @@ -206,7 +217,7 @@ def main(args,row,col,injectPix): parser.add_argument args = parser.parse_args() - # Logging stuff! + # Logging loglevel = logging.INFO formatter = logging.Formatter('%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s') fh = logging.FileHandler(logname) diff --git a/loop_DACs.py b/loop_DACs.py index c0da90a4..6c08d367 100644 --- a/loop_DACs.py +++ b/loop_DACs.py @@ -58,14 +58,18 @@ def main(args,dac): else: astro = astropix2() #initialize without enabling injections + astro.init_voltages(vthreshold=args.threshold) + + #Define YAML path variables + pathdelim=os.path.sep #determine if Mac or Windows separators in path name + ymlpath="config"+pathdelim+args.yaml+".yml" + # Initialie asic - blank array, no pixels enabled, analog enabled for defined pixel or (0,0) by default if args.DAC!="": - astro.asic_init(dac_setup={args.DAC: dac},analog_col = args.analog) + astro.asic_init(yaml=ymlpath, dac_setup={args.DAC: dac},analog_col = args.analog) else: - astro.asic_init(analog_col = args.analog) + astro.asic_init(yaml=ymlpath, analog_col = args.analog) - astro.init_voltages(vthreshold=args.threshold) - #Enable single pixel from argument, or (0,0) if no pixel given astro.enable_pixel(args.pixel[1],args.pixel[0]) @@ -102,6 +106,9 @@ def main(args,dac): 'hittime' ]) + # Save final configuration to output file + ymlpathout="config"+pathdelim+args.yaml+"_"+time.strftime("%Y%m%d-%H%M%S")+".yml" + astro.write_conf_to_yaml(ymlpathout) # And here for the text files/logs bitpath = args.outdir + '/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.log' # textfiles are always saved so we open it up @@ -180,6 +187,10 @@ def main(args,dac): parser.add_argument('-o', '--outdir', default='.', required=False, help='Output Directory for all datafiles') + + parser.add_argument('-y', '--yaml', action='store', required=False, type=str, default = 'testconfig', + help = 'filepath (in config/ directory) .yml file containing chip configuration. Default: config/testconfig.yml (All pixels off)') + parser.add_argument('-c', '--saveascsv', action='store_true', default=False, required=False, From 06261532e5651a29c111a045037fe55280c9696c Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Mon, 17 Oct 2022 16:31:04 -0400 Subject: [PATCH 66/78] update bias config or dac config appropriately when called in a run script --- astropix.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/astropix.py b/astropix.py index abb01bab..916ce9a3 100644 --- a/astropix.py +++ b/astropix.py @@ -133,12 +133,6 @@ def asic_init(self, yaml:str = None, dac_setup: dict = None, bias_setup:dict = N self.asic = Asic(self.handle, self.nexys) - #Override yaml if arguments were given in run script - if bias_setup is not None: - self.biasconfig.update(bias_setup) - if dac_setup is not None: - self.dacconfig.update(dac_setup) - # Get config values from YAML try: self.load_conf_from_yaml(yaml) @@ -147,7 +141,15 @@ def asic_init(self, yaml:str = None, dac_setup: dict = None, bias_setup:dict = N #Config stored in dictionary self.asic_config . This is used for configuration in asic_update. #If any changes are made, make change to self.asic_config so that it is reflected on-chip when # asic_update is called + + #Override yaml if arguments were given in run script + self.update_asic_config(bias_setup, dac_setup) + """ + if bias_setup is not None: + self.biasconfig.update(bias_setup) + if dac_setup is not None: self.dacconfig.update(dac_setup) + """ # Set analog output if (analog_col is not None) and (analog_col <= self.asic._num_cols): @@ -184,13 +186,15 @@ def update_asic_config(self, bias_cfg:dict = None, dac_cfg:dict = None, analog_c bias_cfg:dict - Updates the bias settings. Only needs key/value pairs which need updated dac_cfg:dict - Updates DAC settings. Only needs key/value pairs which need updated """ - if self.asic_start: + if self._asic_start: if bias_cfg is not None: - self.asic.asic_config['biasconfig'].update(bias_cfg) + for key in bias_cfg: + self.asic.asic_config['biasconfig'][key][1]=bias_cfg[key] if dac_cfg is not None: - self.asic.asic_config['idacs'].update(dac_cfg) + for key in dac_cfg: + self.asic.asic_config['idacs'][key][1]=dac_cfg[key] else: - logger.info("update_asic_config() got no argumennts, nothing to do.") + logger.info("update_asic_config() got no arguments, nothing to do.") return None self.asic_update() else: raise RuntimeError("Asic has not been initalized") From 15fb092895da2120f531239a70c0be848beb3adf Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Thu, 20 Oct 2022 15:35:49 -0400 Subject: [PATCH 67/78] Update decoder clock period to agree with firmware --- astropix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astropix.py b/astropix.py index 916ce9a3..464e07f5 100644 --- a/astropix.py +++ b/astropix.py @@ -31,7 +31,7 @@ class astropix2: # Init just opens the chip and gets the handle. After this runs # asic_config also needs to be called to set it up. Seperating these # allows for simpler specifying of values. - def __init__(self, clock_period_ns = 10, inject:int = None, offline:bool=False): + def __init__(self, clock_period_ns = 5, inject:int = None, offline:bool=False): """ Initalizes astropix object. No required arguments From 675b2c7dfe9861e81b1a2ddb75533edd64a61e7a Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Thu, 20 Oct 2022 15:36:34 -0400 Subject: [PATCH 68/78] Update scripts so yaml config files saved in same out dir as data files --- beam_test.py | 2 +- loop_DACs.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/beam_test.py b/beam_test.py index a9ce612e..a89062de 100644 --- a/beam_test.py +++ b/beam_test.py @@ -104,7 +104,7 @@ def main(args): ]) # Save final configuration to output file - ymlpathout="config"+pathdelim+args.yaml+"_"+time.strftime("%Y%m%d-%H%M%S")+".yml" + ymlpathout=args.outdir +pathdelim+args.yaml+"_"+time.strftime("%Y%m%d-%H%M%S")+".yml" astro.write_conf_to_yaml(ymlpathout) # Prepare text files/logs bitpath = args.outdir + '/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.log' diff --git a/loop_DACs.py b/loop_DACs.py index 6c08d367..bf5753ff 100644 --- a/loop_DACs.py +++ b/loop_DACs.py @@ -107,7 +107,7 @@ def main(args,dac): ]) # Save final configuration to output file - ymlpathout="config"+pathdelim+args.yaml+"_"+time.strftime("%Y%m%d-%H%M%S")+".yml" + ymlpathout=args.outdir+pathdelim+args.yaml+"_"+time.strftime("%Y%m%d-%H%M%S")+".yml" astro.write_conf_to_yaml(ymlpathout) # And here for the text files/logs bitpath = args.outdir + '/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.log' From 977fa3f56d8d42fbdb164a43ee52bf152093581d Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Mon, 31 Oct 2022 13:16:56 -0400 Subject: [PATCH 69/78] update readme with clarifier about passing arguments to astropix module - do not pass as numpy objects --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index bf5a32cb..df40b897 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,8 @@ It has the ability to: - Configure threshold and injection voltages - Enable digital output based on pixel masks +CAUTION : try not to pass arguments to astropix.py as numpy objects - if looping through a numpy array, typecast to int, float, etc for the argument call or features may not work as intended (ie - pixels may not be activated/deactivated as expected) + Options: | Argument | Usage | Purpose | Default | | :--- | :--- | :--- | :--- | From 0642595938b97e21435f22d44b78799ca6492037 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Tue, 15 Nov 2022 16:05:35 -0500 Subject: [PATCH 70/78] move wait to before autoopen of nexys board --- astropix.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/astropix.py b/astropix.py index 464e07f5..ff451437 100644 --- a/astropix.py +++ b/astropix.py @@ -52,8 +52,9 @@ def __init__(self, clock_period_ns = 5, inject:int = None, offline:bool=False): else: self._asic_start = False self.nexys = Nexysio() - self.handle = self.nexys.autoopen() self._wait_progress(2) + self.handle = self.nexys.autoopen() + # Ensure it is working logger.info("Opened FPGA, testing...") self._test_io() From 620f36c902de128360a7ece051dac1b2e6436eb6 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Tue, 15 Nov 2022 16:06:34 -0500 Subject: [PATCH 71/78] data collection script for threshold scanning of full array --- thresholdScan.py | 177 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 thresholdScan.py diff --git a/thresholdScan.py b/thresholdScan.py new file mode 100644 index 00000000..7a94f35d --- /dev/null +++ b/thresholdScan.py @@ -0,0 +1,177 @@ +""" +Simple script to loop through pixels enabling one at a time, using astropix.py +Based off beam_test.py + +Author: Amanda Steinhebel +""" + +from astropix import astropix2 +import modules.hitplotter as hitplotter +import os +import binascii +import pandas as pd +import numpy as np +import time +import logging +import argparse + +from modules.setup_logger import logger + + +# This sets the logger name. +logdir = "./runlogs/" +if os.path.exists(logdir) == False: + os.mkdir(logdir) +logname = "./runlogs/AstropixRunlog_" + time.strftime("%Y%m%d-%H%M%S") + ".log" + + + +#Init +def main(args,row,col,dataF, fpgaCon:bool=True, fpgaDiscon:bool=True): + + # Ensures output directory exists + if os.path.exists(args.outdir) == False: + os.mkdir(args.outdir) + + if fpgaCon: + # Prepare everything, create the object + global astro + logger.info('Initiate FPGA connection') + astro = astropix2() #initialize without enabling injections + + astro.init_voltages(vthreshold=args.threshold) + + #Define YAML path variables + pathdelim=os.path.sep #determine if Mac or Windows separators in path name + ymlpath="config"+pathdelim+args.yaml+".yml" + + #Initiate asic with pixel mask as defined in yaml + astro.asic_init(yaml=ymlpath) + + #Enable single pixel in (col,row) + #Updates asic by default + astro.enable_pixel(col,row) + + + astro.enable_spi() + logger.info("Chip configured") + astro.dump_fpga() + + i = 0 + if args.maxtime is not None: + end_time=time.time()+(args.maxtime*60.) + strPix = "_"+str(args.threshold)[:-2]+"mVThresh_col"+str(col)+"_row"+str(row)+"_" + fname=strPix if not args.name else args.name+strPix+"_" + + # Prepares the file paths + # Save final configuration to output file + ymlpathout=outdir+pathdelim+args.yaml+"_"+fname+time.strftime("%Y%m%d-%H%M%S")+".yml" + astro.write_conf_to_yaml(ymlpathout) + # And here for the text files/logs + bitpath = outdir + pathdelim + fname + time.strftime("%Y%m%d-%H%M%S") + '.log' + bitfile = open(bitpath,'w') + # Writes all the config information to the file + bitfile.write(astro.get_log_header()) + bitfile.write(str(args)) + bitfile.write("\n") + + try: # By enclosing the main loop in try/except we are able to capture keyboard interupts cleanly + while (True): # Loop continues + # Break conditions + if args.maxtime is not None: + if time.time() >= end_time: break + + if astro.hits_present(): # Checks if hits are present + + time.sleep(.001) # this is probably not needed, will ask Nicolas + + readout = astro.get_readout(3) # Gets the bytearray from the chip + + # Writes the hex version to hits + bitfile.write(f"{i}\t{str(binascii.hexlify(readout))}\n") + #print(binascii.hexlify(readout)) + i += 1 + astro.asic_update() #must be done after every interrupt check + + # If no hits are present this waits for some to accumulate + else: time.sleep(.001) + # Ends program cleanly when a keyboard interupt is sent. + except KeyboardInterrupt: + logger.info("Keyboard interupt. Program halt!") + # Catches other exceptions + except Exception as e: + logger.exception(f"Encountered Unexpected Exception! \n{e}") + finally: + interrfile = open(interrpath,'a+') + interrfile.write(f"{r} \t {i} \n") + interrfile.close() + bitfile.close() # Close open file + if fpgaDiscon: + astro.close_connection() # Closes SPI + logger.info('FPGA Connection ended') + logger.info("Program terminated successfully") + # END OF PROGRAM + + + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='Astropix Driver Code') + parser.add_argument('-n', '--name', default='', required=False, + help='Option to give additional name to output files upon running') + + parser.add_argument('-o', '--outdir', default='.', required=False, + help='Output Directory for all datafiles') + + parser.add_argument('-y', '--yaml', action='store', required=False, type=str, default = 'testconfig', + help = 'filepath (in config/ directory) .yml file containing chip configuration. Default: config/testconfig.yml (All pixels off)') + + parser.add_argument('-t', '--threshold', type = float, action='store', default=None, + help = 'Threshold voltage for digital ToT (in mV). DEFAULT 100mV') + + parser.add_argument('-M', '--maxtime', type=float, action='store', default=None, + help = 'Maximum run time (in minutes)') + + parser.add_argument('-C', '--colrange', action='store', default=[0,34], type=int, nargs=2, + help = 'Loop over given range of columns. Default: 0 34') + + parser.add_argument('-R', '--rowrange', action='store', default=[0,34], type=int, nargs=2, + help = 'Loop over given range of rows. Default: 0 34') + + parser.add_argument + args = parser.parse_args() + + # Logging + loglevel = logging.INFO + formatter = logging.Formatter('%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s') + fh = logging.FileHandler(logname) + fh.setFormatter(formatter) + sh = logging.StreamHandler() + sh.setFormatter(formatter) + + logging.getLogger().addHandler(sh) + logging.getLogger().addHandler(fh) + logging.getLogger().setLevel(loglevel) + + logger = logging.getLogger(__name__) + + #loop over threshold values for long weekend run + threshs = [25., 50., 75., 100., 150., 200.] + for t in threshs: + args.threshold = t + outdir = args.outdir + str(args.threshold)[:-2] + 'mV' + #loop over full array by default, unless bounds are given as argument + for c in range(args.colrange[0],args.colrange[1]+1,1): + interrpath = outdir + '/counts_' + str(args.threshold)[:-2] + 'mVThresh_' + args.name +'_col' + str(c) + "_"+ time.strftime("%Y%m%d-%H%M%S") + '.txt' + interrfile = open(interrpath,'a+') + interrfile.write(f"Row \t Counts \n") + interrfile.close() + for r in range(args.rowrange[0],args.rowrange[1]+1,1): + if r==args.rowrange[0] and c==args.colrange[0]:#first pixel probed - connect to FPGA but leave open + main(args,r,c, interrpath, fpgaDiscon=False) + elif r==args.rowrange[1] and c==args.colrange[1]: #final pixel probed - disconnect from FPGA upon completion + main(args,r,c, interrpath, fpgaCon=False) + else: #for bulk of pixels, FPGA is already open. Do not reconnect and do not disconnect when completed, leave it open for the next pixel + main(args,r,c, interrpath, fpgaCon=False, fpgaDiscon=False) + \ No newline at end of file From 42a05b7c57d7acdc85c069c6f11d235d1977bd39 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Wed, 16 Nov 2022 14:21:45 -0500 Subject: [PATCH 72/78] clarify language in thresholdScan header about script usage --- thresholdScan.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/thresholdScan.py b/thresholdScan.py index 7a94f35d..ac5a0584 100644 --- a/thresholdScan.py +++ b/thresholdScan.py @@ -1,6 +1,8 @@ """ -Simple script to loop through pixels enabling one at a time, using astropix.py -Based off beam_test.py +Script to loop through pixels enabling one at a time, using astropix.py. +For a variety of threshold values, loop over full array and only record the number of times +the comparator would have fired. +Based off beam_test.py and example_loop.py Author: Amanda Steinhebel """ From ce22e19dba5a8a414d07316904fbcc9b861ca1a8 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Wed, 16 Nov 2022 15:35:46 -0500 Subject: [PATCH 73/78] example script for running an injection scan over a single pixel --- injectionScan.py | 300 +++++++++++++++++++++++++++++++++++++++++++++++ thresholdScan.py | 6 +- 2 files changed, 304 insertions(+), 2 deletions(-) create mode 100644 injectionScan.py diff --git a/injectionScan.py b/injectionScan.py new file mode 100644 index 00000000..39f47c51 --- /dev/null +++ b/injectionScan.py @@ -0,0 +1,300 @@ +""" +Script to loop through injection voltage values enabling a single pixel. Test all injection values on one pixel + +Author: Amanda Steinhebel +""" + +#from msilib.schema import File +#from http.client import SWITCHING_PROTOCOLS +from astropix import astropix2 +import modules.hitplotter as hitplotter +import os +import binascii +import pandas as pd +import numpy as np +import time +import logging +import argparse + +from modules.setup_logger import logger + + +# This sets the logger name. +logdir = "./runlogs/" +if os.path.exists(logdir) == False: + os.mkdir(logdir) +logname = "./runlogs/AstropixRunlog_" + time.strftime("%Y%m%d-%H%M%S") + ".log" + + + +# This is the dataframe which is written to the csv if the decoding fails +decode_fail_frame = pd.DataFrame({ + 'readout': np.nan, + 'Chip ID': np.nan, + 'payload': np.nan, + 'location': np.nan, + 'isCol': np.nan, + 'timestamp': np.nan, + 'tot_msb': np.nan, + 'tot_lsb': np.nan, + 'tot_total': np.nan, + 'tot_us': np.nan, + 'hittime': np.nan + }, index=[0] +) + + + +#Initialize +def main(args,fpgaCon:bool=True, fpgaDiscon:bool=True): + + # Ensures output directory exists + if os.path.exists(args.outdir) == False: + os.mkdir(args.outdir) + + if fpgaCon: + # Prepare everything, create the object + global astro + logger.info('Initiate FPGA connection') + astro = astropix2(inject=args.inject) #initialize without enabling injections + + astro.init_voltages(vthreshold=args.threshold) #no updates in YAML + + #Define YAML path variables + pathdelim=os.path.sep #determine if Mac or Windows separators in path name + ymlpath="config"+pathdelim+args.yaml+".yml" + + #Initiate asic with pixel mask as defined in yaml and analog pixel in row0 defined with input argument -a + astro.asic_init(yaml=ymlpath, analog_col = args.analog) + + #If injection, ensure injection pixel is enabled and initialize + if args.inject is not None: + astro.enable_pixel(args.inject[1],args.inject[0]) + astro.init_injection(inj_voltage=args.vinj) + + #Enable final configuration + astro.enable_spi() + logger.info("Chip configured") + astro.dump_fpga() + + if args.inject is not None: + astro.start_injection() + + + max_errors = args.errormax + i = 0 + errors = 0 # Sets the threshold + if args.maxtime is not None: + end_time=time.time()+(args.maxtime*60.) + fname="" if not args.name else args.name+"_" + + # Prepares the file paths + if args.saveascsv: # Here for csv + csvpath = args.outdir +'/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.csv' + csvframe =pd.DataFrame(columns = [ + 'readout', + 'Chip ID', + 'payload', + 'location', + 'isCol', + 'timestamp', + 'tot_msb', + 'tot_lsb', + 'tot_total', + 'tot_us', + 'hittime' + ]) + + # Save final configuration to output file + ymlpathout=args.outdir +pathdelim+args.yaml+"_"+time.strftime("%Y%m%d-%H%M%S")+".yml" + astro.write_conf_to_yaml(ymlpathout) + # Prepare text files/logs + bitpath = args.outdir + '/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.log' + # textfiles are always saved so we open it up + bitfile = open(bitpath,'w') + # Writes all the config information to the file + bitfile.write(astro.get_log_header()) + bitfile.write(str(args)) + bitfile.write("\n") + + # Enables the hitplotter and uses logic on whether or not to save the images + if args.showhits: plotter = hitplotter.HitPlotter(35, outdir=(args.outdir if args.plotsave else None)) + + try: # By enclosing the main loop in try/except we are able to capture keyboard interupts cleanly + + while errors <= max_errors: # Loop continues + + # This might be possible to do in the loop declaration, but its a lot easier to simply add in this logic + if args.maxruns is not None: + if i >= args.maxruns: break + if args.maxtime is not None: + if time.time() >= end_time: break + + + if astro.hits_present(): # Checks if hits are present + # We aren't using timeit, just measuring the diffrence in ns + if args.timeit: start = time.time_ns() + + time.sleep(.001) # this is probably not needed, will ask Nicolas + + readout = astro.get_readout(3) # Gets the bytearray from the chip + + if args.timeit: + print(f"Readout took {(time.time_ns()-start)*10**-9}s") + + # Writes the hex version to hits + bitfile.write(f"{i}\t{str(binascii.hexlify(readout))}\n") + print(binascii.hexlify(readout)) + + # Added fault tolerance for decoding, the limits of which are set through arguments + try: + hits = astro.decode_readout(readout, i, printer = True) + + except IndexError: + errors += 1 + logger.warning(f"Decoding failed. Failure {errors} of {max_errors} on readout {i}") + # We write out the failed decode dataframe + hits = decode_fail_frame + hits.readout = i + hits.hittime = time.time() + + # This loggs the end of it all + if errors > max_errors: + logger.warning(f"Decoding failed {errors} times on an index error. Terminating Progam...") + finally: + i += 1 + + # If we are saving a csv this will write it out. + if args.saveascsv: + csvframe = pd.concat([csvframe, hits]) + + # This handels the hitplotting. Code by Henrike and Amanda + if args.showhits: + # This ensures we aren't plotting NaN values. I don't know if this would break or not but better + # safe than sorry + if pd.isnull(hits.tot_msb.loc(0)): + pass + elif len(hits)>0:#safeguard against bad readouts without recorded decodable hits + #Isolate row and column information from array returned from decoder + rows = hits.location[~hits.isCol] + columns = hits.location[hits.isCol] + plotter.plot_event( rows, columns, i) + + # If we are logging runtime, this does it! + if args.timeit: + print(f"Read and decode took {(time.time_ns()-start)*10**-9}s") + + # If no hits are present this waits for some to accumulate + else: time.sleep(.001) + + + # Ends program cleanly when a keyboard interupt is sent. + except KeyboardInterrupt: + logger.info("Keyboard interupt. Program halt!") + # Catches other exceptions + except Exception as e: + logger.exception(f"Encountered Unexpected Exception! \n{e}") + finally: + if args.saveascsv: + csvframe.index.name = "dec_order" + csvframe.to_csv(csvpath) + if args.inject is not None: astro.stop_injection() + bitfile.close() # Close open file + if fpgaDiscon: + astro.close_connection() # Closes SPI + logger.info('FPGA Connection ended') + logger.info("Program terminated successfully") + # END OF PROGRAM + + + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='Astropix Driver Code') + parser.add_argument('-n', '--name', default='', required=False, + help='Option to give additional name to output files upon running') + + parser.add_argument('-o', '--outdir', default='.', required=False, + help='Output Directory for all datafiles') + + parser.add_argument('-y', '--yaml', action='store', required=False, type=str, default = 'testconfig', + help = 'filepath (in config/ directory) .yml file containing chip configuration. Default: config/testconfig.yml (All pixels off)') + + parser.add_argument('-s', '--showhits', action='store_true', + default=False, required=False, + help='Display hits in real time during data taking') + + parser.add_argument('-p', '--plotsave', action='store_true', default=False, required=False, + help='Save plots as image files. If set, will be saved in same dir as data. Default: FALSE') + + parser.add_argument('-c', '--saveascsv', action='store_true', + default=False, required=False, + help='save output files as CSV. If False, save as txt. Default: FALSE') + + parser.add_argument('-i', '--inject', action='store', default=None, type=int, nargs=2, + help = 'Turn on injection in the given row and column. Default: No injection') + + parser.add_argument('-v','--vinj', action='store', default = None, type=float, + help = 'Specify injection voltage (in mV). DEFAULT 300 mV') + + parser.add_argument('-a', '--analog', action='store', required=False, type=int, default = 0, + help = 'Turn on analog output in the given column. Default: Column 0.') + + parser.add_argument('-t', '--threshold', type = float, action='store', default=None, + help = 'Threshold voltage for digital ToT (in mV). DEFAULT 100mV') + + parser.add_argument('-E', '--errormax', action='store', type=int, default='100', + help='Maximum index errors allowed during decoding. DEFAULT 100') + + parser.add_argument('-r', '--maxruns', type=int, action='store', default=None, + help = 'Maximum number of readouts') + + parser.add_argument('-M', '--maxtime', type=float, action='store', default=None, + help = 'Maximum run time (in minutes)') + + parser.add_argument('--timeit', action="store_true", default=False, + help='Prints runtime from seeing a hit to finishing the decode to terminal') + + parser.add_argument('-L', '--loglevel', type=str, choices = ['D', 'I', 'E', 'W', 'C'], action="store", default='I', + help='Set loglevel used. Options: D - debug, I - info, E - error, W - warning, C - critical. DEFAULT: D') + """ + parser.add_argument('--ludicrous-speed', type=bool, action='store_true', default=False, + help="Fastest possible data collection. No decode, no output, no file.\ + Saves bitstreams in memory until keyboard interupt or other error and then writes them to file.\ + Use is not generally recommended") + """ + parser.add_argument + args = parser.parse_args() + + # Sets the loglevel + ll = args.loglevel + if ll == 'D': + loglevel = logging.DEBUG + elif ll == 'I': + loglevel = logging.INFO + elif ll == 'E': + loglevel = logging.ERROR + elif ll == 'W': + loglevel = logging.WARNING + elif ll == 'C': + loglevel = logging.CRITICAL + + # Logging + formatter = logging.Formatter('%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s') + fh = logging.FileHandler(logname) + fh.setFormatter(formatter) + sh = logging.StreamHandler() + sh.setFormatter(formatter) + + logging.getLogger().addHandler(sh) + logging.getLogger().addHandler(fh) + logging.getLogger().setLevel(loglevel) + + logger = logging.getLogger(__name__) + + injs = [100, 200, 300, 400, 500, 600, 700, 800, 900] + for i,inj in enumerate(injs): + + args.vinj = inj + main(args) \ No newline at end of file diff --git a/thresholdScan.py b/thresholdScan.py index ac5a0584..174f76e0 100644 --- a/thresholdScan.py +++ b/thresholdScan.py @@ -41,7 +41,7 @@ def main(args,row,col,dataF, fpgaCon:bool=True, fpgaDiscon:bool=True): logger.info('Initiate FPGA connection') astro = astropix2() #initialize without enabling injections - astro.init_voltages(vthreshold=args.threshold) + astro.init_voltages(vthreshold=args.threshold) #Define YAML path variables pathdelim=os.path.sep #determine if Mac or Windows separators in path name @@ -162,7 +162,9 @@ def main(args,row,col,dataF, fpgaCon:bool=True, fpgaDiscon:bool=True): threshs = [25., 50., 75., 100., 150., 200.] for t in threshs: args.threshold = t - outdir = args.outdir + str(args.threshold)[:-2] + 'mV' + outdir = args.outdir + "/" + str(args.threshold)[:-2] + 'mV' + if os.path.exists(outdir) == False: + os.mkdir(outdir) #loop over full array by default, unless bounds are given as argument for c in range(args.colrange[0],args.colrange[1]+1,1): interrpath = outdir + '/counts_' + str(args.threshold)[:-2] + 'mVThresh_' + args.name +'_col' + str(c) + "_"+ time.strftime("%Y%m%d-%H%M%S") + '.txt' From f5c5d3ecb9c2a43a0cd3239e697f07bd1e98596f Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Wed, 16 Nov 2022 16:39:26 -0500 Subject: [PATCH 74/78] remove comment --- beam_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beam_test.py b/beam_test.py index a89062de..40b60c18 100644 --- a/beam_test.py +++ b/beam_test.py @@ -197,7 +197,7 @@ def main(args): csvframe.index.name = "dec_order" csvframe.to_csv(csvpath) if args.inject is not None: astro.stop_injection() - bitfile.close() # Close open file if args.inject: astro.stop_injection() #stops injection + bitfile.close() # Close open file astro.close_connection() # Closes SPI logger.info("Program terminated successfully") # END OF PROGRAM From 98c8505914a64725123b74df3b0e546721dd330c Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Wed, 16 Nov 2022 16:39:43 -0500 Subject: [PATCH 75/78] add output file naming for injection scan --- injectionScan.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/injectionScan.py b/injectionScan.py index 39f47c51..0d8ef84b 100644 --- a/injectionScan.py +++ b/injectionScan.py @@ -86,7 +86,7 @@ def main(args,fpgaCon:bool=True, fpgaDiscon:bool=True): errors = 0 # Sets the threshold if args.maxtime is not None: end_time=time.time()+(args.maxtime*60.) - fname="" if not args.name else args.name+"_" + fname="" if not args.name else newname+"_" # Prepares the file paths if args.saveascsv: # Here for csv @@ -295,6 +295,6 @@ def main(args,fpgaCon:bool=True, fpgaDiscon:bool=True): injs = [100, 200, 300, 400, 500, 600, 700, 800, 900] for i,inj in enumerate(injs): - + newname = args.name + "_" + str(inj) + "mVinj" args.vinj = inj main(args) \ No newline at end of file From 3b1ecec26548d813a172a24490581067abab2901 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Mon, 12 Dec 2022 13:10:24 -0500 Subject: [PATCH 76/78] Update default config files - testconfig uses defaults set by Nicolas with AS optimization illustrated in comments. config_c0_r0 activates pixel c0r0 and utilizes all optimized DACs identified by AS --- config/config_c0_r0.yml | 4 ++-- config/testconfig.yml | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/config/config_c0_r0.yml b/config/config_c0_r0.yml index e6fe1064..e2b5afb5 100644 --- a/config/config_c0_r0.yml +++ b/config/config_c0_r0.yml @@ -50,8 +50,8 @@ astropix2: vncomp: [6, 2] vpfoll: [6, 60] nu16: [6, 0] - vprec: [6, 60] - vnrec: [6, 30] + vprec: [6, 15] + vnrec: [6, 2] recconfig: col0: [38, 0b001_11111_11111_11111_11111_11111_11111_11100] diff --git a/config/testconfig.yml b/config/testconfig.yml index 841c5dd8..f176bf5c 100644 --- a/config/testconfig.yml +++ b/config/testconfig.yml @@ -36,22 +36,22 @@ astropix2: blres: [6, 0] nu1: [6, 0] vn1: [6, 20] - vnfb: [6, 1] + vnfb: [6, 1] #3 vnfoll: [6, 10] nu5: [6, 0] nu6: [6, 0] nu7: [6, 0] nu8: [6, 0] vn2: [6, 0] - vnfoll2: [6, 1] - vnbias: [6, 10] + vnfoll2: [6, 1] #4 + vnbias: [6, 10] #0 vpload: [6, 5] nu13: [6, 0] vncomp: [6, 2] vpfoll: [6, 60] nu16: [6, 0] - vprec: [6, 60] - vnrec: [6, 30] + vprec: [6, 60] #15 + vnrec: [6, 30] #2 recconfig: col0: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] From aa1736b7aa38ad109f9b8903773722ec0fae11e3 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Mon, 12 Dec 2022 13:11:37 -0500 Subject: [PATCH 77/78] script to take injection data from a single pixel at a time, scanning over full array. Update astropix.py to allow injection enabling outside of asic_init [AS[ --- astropix.py | 7 ++ pixelScan_injection.py | 213 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 pixelScan_injection.py diff --git a/astropix.py b/astropix.py index ff451437..edc88745 100644 --- a/astropix.py +++ b/astropix.py @@ -171,6 +171,13 @@ def asic_init(self, yaml:str = None, dac_setup: dict = None, bias_setup:dict = N def enable_pixel(self, col: int, row: int, inplace:bool=True): self.asic.enable_pixel(col, row, inplace) + #Turn on injection of different pixel than the one used in _init_ + def enable_injection(self, col:int, row:int, inplace:bool=True): + #self.injection_col = col + #self.injection_row = row + self.asic.enable_inj_col(col, inplace) + self.asic.enable_inj_row(row, inplace) + # The method to write data to the asic. Called whenever somthing is changed # or after a group of changes are done. Taken straight from asic.py. def asic_update(self): diff --git a/pixelScan_injection.py b/pixelScan_injection.py new file mode 100644 index 00000000..21aa78a9 --- /dev/null +++ b/pixelScan_injection.py @@ -0,0 +1,213 @@ +""" +Script to loop through pixels enabling one at a time, using astropix.py. +Loop over full array and record from each pixel individually. +Based off beam_test.py and example_loop.py + +Author: Amanda Steinhebel +""" + +from astropix import astropix2 +import modules.hitplotter as hitplotter +import os +import binascii +import pandas as pd +import numpy as np +import time +import logging +import argparse + +from modules.setup_logger import logger + + +# This sets the logger name. +logdir = "./runlogs/" +if os.path.exists(logdir) == False: + os.mkdir(logdir) +logname = "./runlogs/AstropixRunlog_" + time.strftime("%Y%m%d-%H%M%S") + ".log" + + + +#Init +def main(args,row,col, fpgaCon:bool=True, fpgaDiscon:bool=True): + + # Ensures output directory exists + if os.path.exists(args.outdir) == False: + os.mkdir(args.outdir) + + if fpgaCon: + # Prepare everything, create the object + global astro + logger.info('Initiate FPGA connection') + if boolInj: + astro = astropix2(inject=[row,col]) + else: + astro = astropix2() + + astro.init_voltages(vthreshold=args.threshold) + + #Define YAML path variables + pathdelim=os.path.sep #determine if Mac or Windows separators in path name + ymlpath="config"+pathdelim+args.yaml+".yml" + + #Initiate asic with pixel mask as defined in yaml + #Updates injection pixel + astro.asic_init(yaml=ymlpath, analog_col=col) + + #Enable single pixel in (col,row) + #Updates asic by default + astro.enable_pixel(col,row) + + #If injection, ensure injection pixel is enabled and initialize + if boolInj is not None: + astro.enable_injection(col,row) + astro.init_injection(inj_voltage=args.vinj) + + astro.enable_spi() + logger.info("Chip configured") + astro.dump_fpga() + + if boolInj is not None: + astro.start_injection() + + i=0 + if args.maxtime is not None: + end_time=time.time()+(args.maxtime*60.) + strPix = "_col"+str(col)+"_row"+str(row)+"_" + fname=strPix if not args.name else args.name+strPix+"_" + + # Prepares the file paths + if args.saveascsv: # Here for csv + csvpath = args.outdir +'/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.csv' + csvframe =pd.DataFrame(columns = [ + 'readout', + 'Chip ID', + 'payload', + 'location', + 'isCol', + 'timestamp', + 'tot_msb', + 'tot_lsb', + 'tot_total', + 'tot_us', + 'hittime' + ]) + + # Prepares the file paths + # Save final configuration to output file + ymlpathout=args.outdir+pathdelim+args.yaml+"_"+fname+time.strftime("%Y%m%d-%H%M%S")+".yml" + astro.write_conf_to_yaml(ymlpathout) + # And here for the text files/logs + bitpath = args.outdir + pathdelim + fname + time.strftime("%Y%m%d-%H%M%S") + '.log' + bitfile = open(bitpath,'w') + # Writes all the config information to the file + bitfile.write(astro.get_log_header()) + bitfile.write(str(args)) + bitfile.write("\n") + + try: # By enclosing the main loop in try/except we are able to capture keyboard interupts cleanly + while (True): # Loop continues + # Break conditions + if args.maxtime is not None: + if time.time() >= end_time: break + + if astro.hits_present(): # Checks if hits are present + + time.sleep(.001) # this is probably not needed, will ask Nicolas + + readout = astro.get_readout(3) # Gets the bytearray from the chip + + # Writes the hex version to hits + bitfile.write(f"{i}\t{str(binascii.hexlify(readout))}\n") + #print(binascii.hexlify(readout)) + + # Added fault tolerance for decoding, the limits of which are set through arguments + try: + hits = astro.decode_readout(readout, i, printer = True) + finally: + i += 1 + # If we are saving a csv this will write it out. + if args.saveascsv: + csvframe = pd.concat([csvframe, hits]) + # If no hits are present this waits for some to accumulate + else: time.sleep(.001) + # Ends program cleanly when a keyboard interupt is sent. + except KeyboardInterrupt: + logger.info("Keyboard interupt. Program halt!") + # Catches other exceptions + except Exception as e: + logger.exception(f"Encountered Unexpected Exception! \n{e}") + finally: + if args.saveascsv: + csvframe.index.name = "dec_order" + csvframe.to_csv(csvpath) + if boolInj is not None: astro.stop_injection() + bitfile.close() # Close open file + if fpgaDiscon: + astro.close_connection() # Closes SPI + logger.info('FPGA Connection ended') + logger.info("Program terminated successfully") + # END OF PROGRAM + + + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='Astropix Driver Code') + parser.add_argument('-n', '--name', default='', required=False, + help='Option to give additional name to output files upon running') + + parser.add_argument('-o', '--outdir', default='.', required=False, + help='Output Directory for all datafiles') + + parser.add_argument('-y', '--yaml', action='store', required=False, type=str, default = 'testconfig', + help = 'filepath (in config/ directory) .yml file containing chip configuration. Default: config/testconfig.yml (All pixels off)') + + parser.add_argument('-t', '--threshold', type = float, action='store', default=None, + help = 'Threshold voltage for digital ToT (in mV). DEFAULT 100mV') + + parser.add_argument('-M', '--maxtime', type=float, action='store', default=None, + help = 'Maximum run time (in minutes)') + + parser.add_argument('-C', '--colrange', action='store', default=[0,34], type=int, nargs=2, + help = 'Loop over given range of columns. Default: 0 34') + + parser.add_argument('-R', '--rowrange', action='store', default=[0,34], type=int, nargs=2, + help = 'Loop over given range of rows. Default: 0 34') + + parser.add_argument('-v','--vinj', action='store', default = None, type=float, + help = 'Specify injection voltage (in mV) to turn on injection. If argument not used, injection not enabled. DEFAULT None') + parser.add_argument('-c', '--saveascsv', action='store_true', + default=False, required=False, + help='save output files as CSV. If False, save as txt. Default: FALSE') + + + parser.add_argument + args = parser.parse_args() + + # Logging + loglevel = logging.INFO + formatter = logging.Formatter('%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s') + fh = logging.FileHandler(logname) + fh.setFormatter(formatter) + sh = logging.StreamHandler() + sh.setFormatter(formatter) + + logging.getLogger().addHandler(sh) + logging.getLogger().addHandler(fh) + logging.getLogger().setLevel(loglevel) + + logger = logging.getLogger(__name__) + + boolInj = True if args.vinj is not None else False + + #loop over full array by default, unless bounds are given as argument + for c in range(args.colrange[0],args.colrange[1]+1,1): + for r in range(args.rowrange[0],args.rowrange[1]+1,1): + if r==args.rowrange[0] and c==args.colrange[0]:#first pixel probed - connect to FPGA but leave open + main(args,r,c, fpgaDiscon=False) + elif r==args.rowrange[1] and c==args.colrange[1]: #final pixel probed - disconnect from FPGA upon completion + main(args,r,c, fpgaCon=False) + else: #for bulk of pixels, FPGA is already open. Do not reconnect and do not disconnect when completed, leave it open for the next pixel + main(args,r,c, fpgaCon=False, fpgaDiscon=False) + \ No newline at end of file From f14743795c52875b1f25c8f6e46844f0210d9660 Mon Sep 17 00:00:00 2001 From: gs66c235 Date: Mon, 12 Dec 2022 15:15:51 -0500 Subject: [PATCH 78/78] [AS] first bench test - add telescope dict to yamls, configure/run chip with beam_test.py. Will need updates to other example scripts to match path structure and check advanced functionality but baseline running works --- astropix.py | 16 ++++++-- beam_test.py | 8 +--- config/config_none.yml | 93 ++++++++++++++++++++++++++++++++++++++++++ config/testconfig.yml | 2 + core/asic.py | 8 ++-- core/decode.py | 6 +-- 6 files changed, 114 insertions(+), 19 deletions(-) create mode 100644 config/config_none.yml diff --git a/astropix.py b/astropix.py index 2df5302d..eebb4b0c 100644 --- a/astropix.py +++ b/astropix.py @@ -20,6 +20,7 @@ import regex as re import time import yaml +import os # Logging stuff import logging @@ -74,10 +75,12 @@ def __init__(self, clock_period_ns = 5, inject:int = None, offline:bool=False): #writing done here def write_conf_to_yaml(self, filename:str = None): - """Write ASIC config to yaml - :param chipversion: Name of yml file in config folder + """ + Write ASIC config to yaml + :param chipversion: chip version :param filename: Name of yml file in config folder """ + dicttofile ={self.asic.chip: { "telescope": {"nchips": self.asic.num_chips}, @@ -91,7 +94,7 @@ def write_conf_to_yaml(self, filename:str = None): else: dicttofile[self.asic.chip]['config'] = self.asic.asic_config - with open(f"config/{filename}.yml", "w", encoding="utf-8") as stream: + with open(f"{filename}", "w", encoding="utf-8") as stream: try: yaml.dump(dicttofile, stream, default_flow_style=False, sort_keys=False) @@ -120,8 +123,13 @@ def asic_init(self, yaml:str = None, dac_setup: dict = None, bias_setup:dict = N self.asic = Asic(self.handle, self.nexys) # Get config values from YAML + #set chip version + self.asic.chipversion=2 + #Define YAML path variables + pathdelim=os.path.sep #determine if Mac or Windows separators in path name + ymlpath="."+pathdelim+"config"+pathdelim+yaml+".yml" try: - self.asic.load_conf_from_yaml(yaml) + self.asic.load_conf_from_yaml(self.asic.chipversion, ymlpath) except Exception: logger.error('Must pass a configuration file in the form of *.yml') #Config stored in dictionary self.asic_config . This is used for configuration in asic_update. diff --git a/beam_test.py b/beam_test.py index 40b60c18..544ea30d 100644 --- a/beam_test.py +++ b/beam_test.py @@ -58,12 +58,8 @@ def main(args): astro.init_voltages(vthreshold=args.threshold) #no updates in YAML - #Define YAML path variables - pathdelim=os.path.sep #determine if Mac or Windows separators in path name - ymlpath="config"+pathdelim+args.yaml+".yml" - #Initiate asic with pixel mask as defined in yaml and analog pixel in row0 defined with input argument -a - astro.asic_init(yaml=ymlpath, analog_col = args.analog) + astro.asic_init(yaml=args.yaml, analog_col = args.analog) #If injection, ensure injection pixel is enabled and initialize if args.inject is not None: @@ -104,7 +100,7 @@ def main(args): ]) # Save final configuration to output file - ymlpathout=args.outdir +pathdelim+args.yaml+"_"+time.strftime("%Y%m%d-%H%M%S")+".yml" + ymlpathout=args.outdir +"/"+args.yaml+"_"+time.strftime("%Y%m%d-%H%M%S")+".yml" astro.write_conf_to_yaml(ymlpathout) # Prepare text files/logs bitpath = args.outdir + '/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.log' diff --git a/config/config_none.yml b/config/config_none.yml new file mode 100644 index 00000000..c3a42695 --- /dev/null +++ b/config/config_none.yml @@ -0,0 +1,93 @@ +# Example Astropix2 config +# +# The following yaml should comply to the following structure. +# It is important, that the order of configbits under the config section, corresponds to the actual order in the chip +# +# astropix: +# geometry: +# cols: +# rows: +# config: +# : +# : [, ] +# ... +# +--- +astropix2: + telescope: + nchips: 1 + geometry: + cols: 35 + rows: 35 + config: + digitalconfig: + interrupt_pushpull: [1, 0b1] + en_inj: [19, 0b0] + reset: [1, 0b0] + extrabits: [14, 0b0] + + biasconfig: + DisHiDR: [1, 0b0] + q01: [1, 0b0] + qon0: [1, 0b0] + qon1: [1, 0b1] + qon2: [1, 0b0] + qon3: [1, 0b1] + + idacs: + blres: [6, 0] + nu1: [6, 0] + vn1: [6, 20] + vnfb: [6, 3] + vnfoll: [6, 10] + nu5: [6, 0] + nu6: [6, 0] + nu7: [6, 0] + nu8: [6, 0] + vn2: [6, 0] + vnfoll2: [6, 4] + vnbias: [6, 0] + vpload: [6, 5] + nu13: [6, 0] + vncomp: [6, 2] + vpfoll: [6, 60] + nu16: [6, 0] + vprec: [6, 15] + vnrec: [6, 2] + + recconfig: + col0: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col1: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col2: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col3: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col4: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col5: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col6: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col7: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col8: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col9: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col10: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col11: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col12: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col13: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col14: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col15: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col16: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col17: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col18: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col19: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col20: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col21: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col22: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col23: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col24: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col25: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col26: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col27: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col28: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col29: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col30: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col31: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col32: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col33: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col34: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] \ No newline at end of file diff --git a/config/testconfig.yml b/config/testconfig.yml index f176bf5c..de65f864 100644 --- a/config/testconfig.yml +++ b/config/testconfig.yml @@ -14,6 +14,8 @@ # --- astropix2: + telescope: + nchips: 1 geometry: cols: 35 rows: 35 diff --git a/core/asic.py b/core/asic.py index c834a189..39ed389a 100644 --- a/core/asic.py +++ b/core/asic.py @@ -106,7 +106,7 @@ def num_chips(self): def num_chips(self, chips): self._num_chips = chips - def enable_inj_row(self, row: int): + def enable_inj_row(self, row: int, inplace:bool=True): """ Enable injection in specified row @@ -231,16 +231,16 @@ def __int2nbit(value: int, nbits: int) -> BitArray: def load_conf_from_yaml(self, chipversion: int, filename: str, **kwargs) -> None: """Load ASIC config from yaml - - + :param chipversion: AstroPix version :param filename: Name of yml file in config folder """ + chipname = kwargs.get('chipname', 'astropix') self.chipversion = chipversion self.chipname = chipname - with open(f"config/{filename}.yml", "r", encoding="utf-8") as stream: + with open(f"{filename}", "r", encoding="utf-8") as stream: try: dict_from_yml = yaml.safe_load(stream) except yaml.YAMLError as exc: diff --git a/core/decode.py b/core/decode.py index 6a42dad6..1e232e60 100644 --- a/core/decode.py +++ b/core/decode.py @@ -47,11 +47,7 @@ def hits_from_readoutstream(self, readout: bytearray, reverse_bitorder: bool = T hitlist = [] i=0 - #require one idle byte - #idle_byte = 0xbc if reverse_bitorder else 0x3d - #require two idle bytes - idle_byte = 0xbcbc if reverse_bitorder else 0x3d3d - + idle_byte = 0xbc if reverse_bitorder else 0x3d while i < length: if readout[i] == idle_byte or readout[i] == 0xff: