diff --git a/configs/dataformat.yaml b/configs/dataformat.yaml index 9ac80f3cad26d09a364d8b63020531e6f85396c0..4471c25e28e8f5d19b3a4a6f2728a069a2ae1e0a 100644 --- a/configs/dataformat.yaml +++ b/configs/dataformat.yaml @@ -78,9 +78,9 @@ ETROC2: frame: 0x0000000000 mask: 0x8000000000 types: - 0: [ea, col_id, row_id, toa, cal, tot] + 0: [ea, col_id, row_id, toa, cal, tot, elink, full, any_full, global_full] 1: [ea, col_id, row_id, col_id2, row_id2, bcid, counter_a, elink, full, any_full, global_full] - 2: [ea, col_id, row_id, col_id2, row_id2, counter_b, test_pattern] + 2: [ea, col_id, row_id, col_id2, row_id2, counter_b, test_pattern, elink, full, any_full, global_full] data: header: # Define the different masks here? elink: diff --git a/configs/emulator_v2.yaml b/configs/emulator_v2.yaml index 71972d95ca4a28cf970baea75f4f1fc53cb79e59..1d2ea926c55c030680f7b696990acc93ef2628b6 100644 --- a/configs/emulator_v2.yaml +++ b/configs/emulator_v2.yaml @@ -65,6 +65,7 @@ modules: master: lpgbt channel: 1 status: mod_d01 + reset: None 2: elinks: [[[4],[4]], [[6],[6]], [[16],[16]], [[18],[18]]] addresses: [0x72, 0x72, 0x72, 0x72] @@ -72,10 +73,12 @@ modules: master: sca channel: 3 status: mod_d09 + reset: None 3: - elinks: [[[8],[8]], [[10],[10]], [[12],[12]], [[14],[14]]] - addresses: [0x72, 0x72, 0x72, 0x72] - i2c: - master: sca - channel: 0 - status: mod_d17 + elinks: [[[8],[8]], [[10],[10]], [[12],[12]], [[14],[14]]] + addresses: [0x72, 0x72, 0x72, 0x72] + i2c: + master: sca + channel: 0 + status: mod_d17 + reset: None diff --git a/tamalero/ETROC.py b/tamalero/ETROC.py index 2480ffa0b3b14d27c02e5877a72f689b31f62f40..7a88e3b20ae9cbe70b0085bbc22e34d0db3a2842 100644 --- a/tamalero/ETROC.py +++ b/tamalero/ETROC.py @@ -1,6 +1,7 @@ """ For ETROC control """ +import time from tamalero.utils import load_yaml, ffs, bit_count from tamalero.colors import red, green, yellow @@ -20,6 +21,7 @@ class ETROC(): elinks={0:[0]}, verbose=False, strict=True, + reset=None, ): self.isfake = False self.I2C_master = rb.DAQ_LPGBT if master.lower() == 'lpgbt' else rb.SCA @@ -29,6 +31,7 @@ class ETROC(): self.i2c_channel = i2c_channel self.i2c_adr = i2c_adr self.elinks = elinks + self.reset_pin = reset self.is_connected() if self.connected: self.ver = self.get_ver() @@ -237,6 +240,16 @@ class ETROC(): all_pass &= comp return all_pass + def reset(self, hard=False): + if hard: + self.rb.SCA.set_gpio(self.reset_pin, 0) + time.sleep(0.1) + self.rb.SCA.set_gpio(self.reset_pin, 1) + else: + self.wr_reg("asyResetGlobalReadout", 0) + time.sleep(0.1) + self.wr_reg("asyResetGlobalReadout", 1) + # ============================ # === MONITORING FUNCTIONS === # ============================ @@ -319,6 +332,7 @@ class ETROC(): def default_config(self): # FIXME should use higher level functions for better readability if self.connected: + self.reset() # soft reset of the global readout self.set_singlePort('both') self.set_mergeTriggerData('separate') self.disable_Scrambler() @@ -334,6 +348,7 @@ class ETROC(): # ======================= def QInj_set(self, charge, delay, row=0, col=0, broadcast=True, reset=True): + # FIXME this is a bad name, given that set_QInj also exists """ High-level function to set the charge injection in the ETROC; requires \'charge\' (in fC) and \'delay\' (in 781 ps steps). @@ -352,15 +367,16 @@ class ETROC(): """ if broadcast: self.set_ChargeInjReset(False) # Reset charge injection module + self.disable_QInj(broadcast=broadcast) # Only disable charge injection for specified pixel else: self.disable_QInj(row=row, col=col, broadcast=broadcast) # Only disable charge injection for specified pixel def QInj_read(self, row=0, col=0, broadcast=True): if broadcast: - qinj = [[self.get_Qinj(row=y, col=x) for x in range(16)] for y in range(16)] + qinj = [[self.get_QInj(row=y, col=x) for x in range(16)] for y in range(16)] return qinj else: - return self.get_Qinj(row=row, col=col) + return self.get_QInj(row=row, col=col) def auto_threshold_scan(self): # FIXME not yet fully working @@ -545,7 +561,7 @@ class ETROC(): def set_workMode(self, mode, row=0, col=0, broadcast=True): val = {'normal': 0b00, 'self test fixed': 0b01, 'self test random': 0b10} try: - self.wr_reg('workMode', val(mode), row=row, col=col, broadcast=broadcast) + self.wr_reg('workMode', val[mode], row=row, col=col, broadcast=broadcast) except KeyError: print('Choose between \'normal\', \'self test fixed\', \'self test random\'.') diff --git a/tamalero/FIFO.py b/tamalero/FIFO.py index 9d78d4f23a4673d0e1f25a0bcf37468186a5126a..76e2aca28be95392f96bfa5a4e4209de8b34fee4 100644 --- a/tamalero/FIFO.py +++ b/tamalero/FIFO.py @@ -24,7 +24,10 @@ def merge_words(res): # offset is only needed when zero suppression is turned off, and packet boundaries are not defined # it relies on the fact that the second 32 bit word is half empty (8 bit ETROC data + 12 bits meta data) # if we ever add more meta data this has to be revisited - offset = 1 if (res[1] > res[0]) else 0 + #offset = 1 if (res[1] > res[0]) else 0 + offset = 0 + print(f"## Offset is {offset=}") + #offset = 0 res = res[offset:] #empty_frame_mask = np.array(res[0::2]) > (2**8) # masking empty fifo entries #print(res) @@ -105,12 +108,18 @@ class FIFO: def send_l1a(self, count=1): for i in range(count): - self.rb.kcu.write_node("SYSTEM.L1A_PULSE", 1) + try: + self.rb.kcu.write_node("SYSTEM.L1A_PULSE", 1) + except: + print("Couldn't send pulse.") def send_QInj(self, count=1, delay=0): self.rb.kcu.write_node("READOUT_BOARD_%s.L1A_INJ_DLY"%self.rb.rb, delay) for i in range(count): - self.rb.kcu.write_node("READOUT_BOARD_%s.L1A_QINJ_PULSE" % self.rb.rb, 0x01) + try: + self.rb.kcu.write_node("READOUT_BOARD_%s.L1A_QINJ_PULSE" % self.rb.rb, 0x01) + except: + print("Couldn't send pulse.") def reset(self): self.rb.kcu.write_node("READOUT_BOARD_%s.FIFO_RESET" % self.rb.rb, 0x01) @@ -169,8 +178,16 @@ class FIFO: try: return self.rb.kcu.read_node(f"READOUT_BOARD_{self.rb.rb}.RX_FIFO_OCCUPANCY").value() except uhal_exception: - print("uhal UDP error in FIFO.get_occupancy") - raise + print("uhal UDP error in FIFO.get_occupancy, trying again") + return self.rb.kcu.read_node(f"READOUT_BOARD_{self.rb.rb}.RX_FIFO_OCCUPANCY").value() + #raise + + def is_full(self): + try: + return self.rb.kcu.read_node(f"READOUT_BOARD_{self.rb.rb}.RX_FIFO_FULL").value() + except uhal_exception: + print("uhal UDP error in FIFO.is_full, trying again") + return self.rb.kcu.read_node(f"READOUT_BOARD_{self.rb.rb}.RX_FIFO_FULL").value() def get_lost_word_count(self): return self.rb.kcu.read_node(f"READOUT_BOARD_{self.rb.rb}.RX_FIFO_LOST_WORD_CNT").value() diff --git a/tamalero/Module.py b/tamalero/Module.py index af512bc3a91c5d42125ef56bf1aa84971443d476..7efbb33eb86664c510bcdd6b1ac7afdf9d36d3b8 100644 --- a/tamalero/Module.py +++ b/tamalero/Module.py @@ -38,6 +38,7 @@ class Module: #elinks = self.config['elinks'][j], i2c_adr = self.config['addresses'][j], strict = strict, + reset = self.config['reset'], )) #def configure(self): @@ -82,6 +83,7 @@ class Module: self.locked = {0:[], 1:[]} self.unlocked = {0:[], 1:[]} for etroc in self.ETROCs: + etroc.get_elink_status() for i in [0,1]: if i in etroc.links_locked: for j, link in enumerate(etroc.elinks[i]): diff --git a/test_ETROC.py b/test_ETROC.py index e71ae89dd1b490a8fe5847dca657402ece41302c..205ec9e5085650d1cb66d65846c01539f802e29d 100644 --- a/test_ETROC.py +++ b/test_ETROC.py @@ -8,10 +8,12 @@ from tamalero.colors import red, green, yellow import numpy as np from scipy.optimize import curve_fit from matplotlib import pyplot as plt +from tqdm import tqdm import os import sys import json +import time from yaml import load, dump try: from yaml import CLoader as Loader, CDumper as Dumper @@ -108,6 +110,9 @@ if __name__ == '__main__': argParser.add_argument('--module', action='store', default=0, choices=['1','2','3'], help="Module to test") argParser.add_argument('--host', action='store', default='localhost', help="Hostname for control hub") argParser.add_argument('--partial', action='store_true', default=False, help="Only read data from corners and edges") + argParser.add_argument('--qinj', action='store_true', default=False, help="Run some charge injection tests") + argParser.add_argument('--hard_reset', action='store_true', default=False, help="Hard reset of selected ETROC2 chip") + argParser.add_argument('--mode', action='store', default=['dual'], choices=['dual', 'single'], help="Port mode for ETROC2") args = argParser.parse_args() @@ -151,7 +156,6 @@ if __name__ == '__main__': print("Test passed.\n") elif args.test_chip: - # FIXME this is still hardcoded kcu = get_kcu(args.kcu, control_hub=True, host=args.host, verbose=False) if (kcu == 0): # if not basic connection was established the get_kcu function returns 0 @@ -190,11 +194,19 @@ if __name__ == '__main__': modules[module-1].show_status() etroc = modules[module-1].ETROCs[0] - print(f"Setting the ETROC in single port mode ('right')") - etroc.set_singlePort("right") + if args.hard_reset: + etroc.reset(hard=True) + etroc.default_config() - #etroc = ETROC(rb=rb_0, i2c_adr=96, i2c_channel=1, elinks={0:[0,2]}) + if args.mode == 'single': + print(f"Setting the ETROC in single port mode ('right')") + etroc.set_singlePort("right") + elif args.mode == 'dual': + print(f"Setting the ETROC in dual port mode ('both')") + etroc.set_singlePort("both") + #etroc = ETROC(rb=rb_0, i2c_adr=96, i2c_channel=1, elinks={0:[0,2]}) + print("\n - Checking peripheral configuration:") etroc.print_perif_conf() @@ -242,11 +254,6 @@ if __name__ == '__main__': else: print(f"Failed: {test0=}, {test1=}, {test2=}, {test3=}") - - etroc.wr_reg('serRateLeft', 0) - etroc.wr_reg('serRateRight', 0) - - # NOTE below is WIP code for tests of the actual data readout from tamalero.FIFO import FIFO from tamalero.DataFrame import DataFrame @@ -256,11 +263,6 @@ if __name__ == '__main__': fifo.select_elink(2) fifo.ready() - ### this does in theory reset the ETROC, but not sure if it comes back up properly - #rb_0.SCA.set_gpio_direction('mod_d07', 1) - #rb_0.SCA.set_gpio('mod_d07', 0) - #rb_0.SCA.set_gpio('mod_d07', 1) - print("\n - Checking elinks") locked = kcu.read_node(f"READOUT_BOARD_0.ETROC_LOCKED").value() if (locked & 0b101) == 5: @@ -272,18 +274,19 @@ if __name__ == '__main__': else: print(red('No elink is locked.')) + fifo.send_l1a(10) + _ = fifo.pretty_read(df) + etroc.reset() + print("\n - Getting internal test data") - #fifo.reset() etroc.wr_reg("selfTestOccupancy", 2, broadcast=True) - etroc.wr_reg("singlePort", 0x0) - etroc.wr_reg("mergeTriggerData", 0x1) - #etroc.wr_reg("") if not args.partial: etroc.wr_reg("workMode", 0x1, broadcast=True) else: etroc.wr_reg("workMode", 0x0, broadcast=True) - # center pixels + ## center pixels + #etroc.wr_reg("workMode", 0x1, row=15, col=7) etroc.wr_reg("workMode", 0x1, row=7, col=7) etroc.wr_reg("workMode", 0x1, row=7, col=8) etroc.wr_reg("workMode", 0x1, row=8, col=7) @@ -359,7 +362,7 @@ if __name__ == '__main__': ax=ax, fontsize=15, ) - name = 'hit_matrix' + name = 'hit_matrix_internal_test_pattern' fig.savefig(os.path.join(plot_dir, "{}.pdf".format(name))) fig.savefig(os.path.join(plot_dir, "{}.png".format(name))) @@ -369,6 +372,143 @@ if __name__ == '__main__': for x in fifo.pretty_read(df): print(x) + #etroc.QInj_unset(broadcast=True) + fifo.reset() + if not args.partial: + print("Will use workMode 1 to get some occupancy (no noise or charge injection)") + etroc.wr_reg("workMode", 0x1, broadcast=True) # this was missing + for j in range(5): + print(j) + ### Another occupancy map + i = 0 + occupancy = 0 + print("\n - Will send L1As until FIFO is full.") + + #etroc.QInj_set(30, 0, row=3, col=3, broadcast=False) + start_time = time.time() + with tqdm(total=65536) as pbar: + while not fifo.is_full(): + fifo.send_l1a() + #fifo.send_QInj(delay=j) + #fifo.send_QInj() + i +=1 + if i%100 == 0: + tmp = fifo.get_occupancy() + pbar.update(tmp-occupancy) + occupancy = tmp + #if time.time()-start_time>5: + # print("Time out") + # break + + test_data = [] + while fifo.get_occupancy() > 0: + test_data += fifo.pretty_read(df) + + hits_total = np.zeros((16,16)) + hit_matrix = hist.Hist(col_axis,row_axis) + n_events_total = 0 + n_events_hit = 0 + for d in test_data: + if d[0] == 'trailer': + n_events_total += 1 + if d[1]['hits'] > 0: + n_events_hit += 1 + if d[0] == 'data': + hit_matrix.fill(row=d[1]['row_id'], col=d[1]['col_id']) + hits_total[d[1]['row_id']][d[1]['col_id']] += 1 + # NOTE could do some CRC check. + + print(f"Got number of total events {n_events_total=}") + print(f"Events with at least one hit {n_events_hit=}") + + fig, ax = plt.subplots(1,1,figsize=(7,7)) + hit_matrix.plot2d( + ax=ax, + ) + ax.set_ylabel(r'$Row$') + ax.set_xlabel(r'$Column$') + hep.cms.label( + "ETL Preliminary", + data=True, + lumi='0', + com=0, + loc=0, + ax=ax, + fontsize=15, + ) + name = 'hit_matrix_external_L1A' + fig.savefig(os.path.join(plot_dir, "{}.pdf".format(name))) + fig.savefig(os.path.join(plot_dir, "{}.png".format(name))) + + + print("\nOccupancy vs column:") + hit_matrix[{"row":sum}].show(columns=100) + print("\nOccupancy vs row:") + hit_matrix[{"col":sum}].show(columns=100) + + if args.qinj: + etroc.wr_reg("workMode", 0x0, broadcast=True) + fifo.reset() + q = 30 + delay = 3 + i = 4 + j = 3 + print(f"\n - Will send L1a/QInj pulse with delay of {delay} cycles and charge of {q} fC") + print(f"\n - to pixel at Row {i}, Col {j}.") + for m in range(5): + etroc.QInj_set(q, delay, row=i, col=j, broadcast = False) + with tqdm(total=65536) as pbar: + while not fifo.is_full(): + try: + kcu.write_node('READOUT_BOARD_0.L1A_QINJ_PULSE', 1) + except: + print('uhal._core.exception: Failed to pulse', file) + etroc.QInj_unset(broadcast = True) + test_data = [] + while fifo.get_occupancy() > 0: + test_data += fifo.pretty_read(df) + + hits_total = np.zeros((16,16)) + hit_matrix = hist.Hist(col_axis,row_axis) + n_events_total = 0 + n_events_hit = 0 + for d in test_data: + if d[0] == 'trailer': + n_events_total += 1 + if d[1]['hits'] > 0: + n_events_hit += 1 + if d[0] == 'data': + hit_matrix.fill(row=d[1]['row_id'], col=d[1]['col_id']) + hits_total[d[1]['row_id']][d[1]['col_id']] += 1 + # NOTE could do some CRC check. + + print(f"Got number of total events {n_events_total=}") + print(f"Events with at least one hit {n_events_hit=}") + + fig, ax = plt.subplots(1,1,figsize=(7,7)) + hit_matrix.plot2d( + ax=ax, + ) + ax.set_ylabel(r'$Row$') + ax.set_xlabel(r'$Column$') + hep.cms.label( + "ETL Preliminary", + data=True, + lumi='0', + com=0, + loc=0, + ax=ax, + fontsize=15, + ) + name = f'hit_matrix_external_L1A_QInj_Pulse_'+str(m) + fig.savefig(os.path.join(plot_dir, "{}.pdf".format(name))) + fig.savefig(os.path.join(plot_dir, "{}.png".format(name))) + + print("\nOccupancy vs column:") + hit_matrix[{"row":sum}].show(columns=100) + print("\nOccupancy vs row:") + hit_matrix[{"col":sum}].show(columns=100) + elif args.vth: