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: