From 46ab0d1d67be0e898348b44eb69d2f84e954f265 Mon Sep 17 00:00:00 2001
From: Daniel Spitzbart <daniel.spitzbart@cern.ch>
Date: Fri, 14 Apr 2023 16:12:59 -0400
Subject: [PATCH 1/4] letting the code run without ipbus install

---
 tamalero/KCU.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/tamalero/KCU.py b/tamalero/KCU.py
index 44566a2..e65230b 100644
--- a/tamalero/KCU.py
+++ b/tamalero/KCU.py
@@ -1,7 +1,10 @@
 """
 Control board class (KCU105). Depends on uhal.
 """
-import uhal
+try:
+    import uhal
+except ModuleNotFoundError:
+    print("Running without uhal (ipbus not installed with correct python bindings)")
 from tamalero.colors import red, green
 
 
-- 
GitLab


From a16fd5e280b1914b294865ce7d13f04019d5523f Mon Sep 17 00:00:00 2001
From: Daniel Spitzbart <daniel.spitzbart@cern.ch>
Date: Fri, 14 Apr 2023 16:40:14 -0400
Subject: [PATCH 2/4] examples for software emulator

---
 README.md          |  44 ++++-
 output/pixel_1.png | Bin 0 -> 30095 bytes
 test_ETROC.py      | 410 +++++++++++++++++++++++----------------------
 3 files changed, 255 insertions(+), 199 deletions(-)
 create mode 100644 output/pixel_1.png

diff --git a/README.md b/README.md
index b74fcff..0fe370f 100644
--- a/README.md
+++ b/README.md
@@ -44,11 +44,52 @@ pip install --editable .
 
 To install IPbus please see the [IPbus user guide](https://ipbus.web.cern.ch/doc/user/html/software/installation.html).
 
+The software emulator also runs without ipbus installed.
+To use a container with preinstalled dependencies, refer to [this section](https://gitlab.cern.ch/cms-etl-electronics/module_test_sw#using-docker) (needs docker installed).
+
 ## Running the code
 
 To properly set all paths run `source setup.sh`.
 
-A minimal example of usage of this package is given in `test_tamalero.py`, which can be run as:
+### Software only
+
+A software emulator of ETROC2 has been implemented, and examples of its usage are implemented in `test_ETROC.py`.
+The simplest example requests a handful of data words by sending L1As at different threshold values.
+
+``` bash
+ipython3 -i test_ETROC.py
+```
+
+Which should return something like
+
+``` bash
+Running without uhal (ipbus not installed with correct python bindings)
+Sending 10 L1As and reading back data, for the following thresholds:
+[203.0, 202.7, 202.4, 202.1, 201.8, 201.5, 201.2, 200.9, 200.6, 200.3]
+Threshold at th=203.0mV
+Vth set to 203.000000.
+('header', {'elink': 0, 'sof': 0, 'eof': 0, 'full': 0, 'any_full': 0, 'global_full': 0, 'l1counter': 1, 'type': 0, 'bcid': 0})
+('trailer', {'elink': 0, 'sof': 0, 'eof': 0, 'full': 0, 'any_full': 0, 'global_full': 0, 'chipid': 25152, 'status': 0, 'hits': 0, 'crc': 0})
+Threshold at th=202.7mV
+Vth set to 202.700000.
+('header', {'elink': 0, 'sof': 0, 'eof': 0, 'full': 0, 'any_full': 0, 'global_full': 0, 'l1counter': 2, 'type': 0, 'bcid': 0})
+('trailer', {'elink': 0, 'sof': 0, 'eof': 0, 'full': 0, 'any_full': 0, 'global_full': 0, 'chipid': 25152, 'status': 0, 'hits': 0, 'crc': 0})
+Threshold at th=202.4mV
+...
+```
+
+A threshold scan can be run with
+
+``` bash
+ipython3 -i test_ETROC.py -- --vth --fitplots
+```
+
+The threshold scans will produce S-curves for each pixel.
+![](output/pixel_1.png)
+
+### With a physical Readout Board
+
+A minimal example of usage of this package with a physical readout board (v1 or v2) is given in `test_tamalero.py`, which can be run as:
 `ipython3 -i test_tamalero.py`
 
 The code is organized similar to the physical objects.
@@ -72,6 +113,7 @@ rb_0.connect_KCU(kcu)
 ```
 
 **Note:** Control hub is now required for using the KCU, as shown in the default `ipb_path` of the KCU (i.e. `"chtcp-2.0://localhost:10203?target=192.168.0.11:50001"` instead of `"ipbusudp-2.0://192.168.0.11:50001"`). `tamalero` won't run otherwise.
+Control hub is part of the IPbus package and can be started with e.g. `/opt/cactus/bin/controlhub_start`.
 
 We can then configure the RB and get a status of the lpGBT:
 ```
diff --git a/output/pixel_1.png b/output/pixel_1.png
new file mode 100644
index 0000000000000000000000000000000000000000..e88142cf762f3b5fc6a4fb624ac27c54d6175cb0
GIT binary patch
literal 30095
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

literal 0
HcmV?d00001

diff --git a/test_ETROC.py b/test_ETROC.py
index cd36216..f8aeadd 100644
--- a/test_ETROC.py
+++ b/test_ETROC.py
@@ -14,71 +14,6 @@ try:
 except ImportError:
     from yaml import Loader, Dumper
 
-
-# initiate
-ETROC2 = ETROC(usefake=True) # currently using Software ETROC2 (fake)
-DF = DataFrame('ETROC2')
-
-# argsparser
-import argparse
-argParser = argparse.ArgumentParser(description = "Argument parser")
-argParser.add_argument('--test_readwrite', action='store_true', default=False, help="Test simple read/write functionality?")
-argParser.add_argument('--vth', action='store_true', default=False, help="Parse Vth scan plots?")
-argParser.add_argument('--rerun', action='store_true', default=False, help="Rerun Vth scan and overwrite data?")
-argParser.add_argument('--fitplots', action='store_true', default=False, help="Create individual vth fit plots for all pixels?")
-args = argParser.parse_args()
-
-
-# ==============================
-# === Test simple read/write ===
-# ==============================
-if args.test_readwrite:
-    print("<--- Test simple read/write --->")
-    print("Testing read/write to addresses...")
-    for r in range(16):
-        for c in range(16):
-            for n in range(32):
-                regadr = 'PixR%dC%dCfg%d'%(r,c,n)
-                ETROC2.wr_adr(regadr, 1)
-                readval = ETROC2.rd_adr(regadr)
-                if not(readval == 1):
-                    raise Exception('Test failed for %s, value read was %d.'%(regname,readval))
-            for n in range(8):
-                regadr = 'PixR%dC%dSta%d'%(r,c,n)
-                ETROC2.wr_adr(regadr, 1)
-                readval = ETROC2.rd_adr(regadr)
-                if not(readval == 1):
-                    raise Exception('Test failed for %s, value read was %d.'%(regname,readval))
-    print("Test passed.\n")
-    
-    print("Testing read/write for shared pixels...")
-    for n in range(32):
-        regadr = 'PixR%dC%dCfg%d'%(1,1,n)
-        ETROC2.wr_adr(regadr, 1)
-        for r in range(16):
-            for c in range(16):
-                readval = ETROC2.rd_adr(regadr)
-                if not(readval == 1):
-                    raise Exception('Test failed for %s, value read was %d.'%(regname,readval))
-    print("Test passed.\n")
-    
-    print("Testing read/write with register names...")
-    with open(os.path.expandvars('$TAMALERO_BASE/address_table/ETROC2.yaml'), 'r') as f:
-        regnames = load(f, Loader=Loader)
-    for regname in list(regnames.keys()):
-        for pix in range(256):
-            ETROC2.wr_reg(regname, pix, 1)
-            readval = ETROC2.rd_reg(regname, pix)
-            if not(readval == 1):
-                raise Exception('Test failed for %s, value read was %d.'%(regname,readval))
-    print("Test passed.\n")
-
-
-# ==============================
-# ======= Test Vth scan ========
-# ==============================
-
-
 # ====== HELPER FUNCTIONS ======
 
 # run N L1A's and return packaged ETROC2 dataformat
@@ -118,13 +53,13 @@ def sigmoid_fit(x_axis, y_axis):
 def parse_data(data, N_pix):
     results = np.zeros(N_pix)
     pix_w = int(round(np.sqrt(N_pix)))
-    
+
     for word in data:
         datatype, res = DF.read(word)
         if datatype == 'data':
             pix = toPixNum(res['row_id'], res['col_id'], pix_w)
             results[pix] += 1
- 
+
     return results
 
 
@@ -135,7 +70,7 @@ def vth_scan(ETROC2):
     vth_step =   .25 # step size
     N_steps  = int((vth_max-vth_min)/vth_step)+1 # number of steps
     N_pix    = 16*16 # total number of pixels
-    
+
     vth_axis    = np.linspace(vth_min, vth_max, N_steps)
     run_results = np.empty([N_steps, N_pix])
 
@@ -143,138 +78,217 @@ def vth_scan(ETROC2):
         ETROC2.set_Vth_mV(vth)
         i = int(round((vth-vth_min)/vth_step))
         run_results[i] = parse_data(run(N_l1a), N_pix)
-    
+
     # transpose so each 1d list is for a pixel & normalize
     run_results = run_results.transpose()/N_l1a
     return [vth_axis.tolist(), run_results.tolist()]
 
 
-# ========= Vth SCAN =========
-if args.vth:
-    print("<--- Testing Vth scan --->")
-    
-    # run only if no saved data or we want to rerun
-    if (not os.path.isfile("results/vth_scan.json")) or args.rerun:
-
-        # scan
-        print("No data. Run new vth scan...")
-        result_data = vth_scan(ETROC2)
-        
-        #save
-        if not os.path.isdir('results'):
-            os.makedirs('results') 
-
-        with open("results/vth_scan.json", "w") as outfile:
-            json.dump(result_data, outfile)
-            print("Data saved to results/vth_scan.json\n")
-
-
-    # read and parse vth scan data
-    with open('results/vth_scan.json', 'r') as openfile:
-        vth_scan_data = json.load(openfile)
-    
-    vth_axis = np.array(vth_scan_data[0])
-    hit_rate = np.array(vth_scan_data[1])
-    
-    vth_min = vth_axis[0]  # vth scan range
-    vth_max = vth_axis[-1]
-    N_pix   = len(hit_rate) # total # of pixels
-    N_pix_w = int(round(np.sqrt(N_pix))) # N_pix in NxN layout
-
-
-    # ======= PERFORM FITS =======
-    
-    # fit to sigmoid and save to NxN layout
-    slopes = np.empty([N_pix_w, N_pix_w])
-    means  = np.empty([N_pix_w, N_pix_w])
-    widths = np.empty([N_pix_w, N_pix_w])
-    
-    for pix in range(N_pix):
-        fitresults = sigmoid_fit(vth_axis, hit_rate[pix])
-        r, c = fromPixNum(pix, N_pix_w)
-        slopes[r][c] = fitresults[0]
-        means[r][c]  = fitresults[1]
-        widths[r][c] = 4/fitresults[0]
-    
-    # print out results nicely
-    for r in range(N_pix_w):
-        for c in range(N_pix_w):
-            pix = toPixNum(r, c, N_pix_w)
-            print("{:8s}".format("#"+str(pix)), end='')
-        print("")
-        for c in range(N_pix_w):
-            print("%4.2f"%means[r][c], end='  ')
-        print("")
-        for c in range(N_pix_w):
-            print("+-%2.2f"%widths[r][c], end='  ')
-        print("\n")
-
-
-    # ======= PLOT RESULTS =======
-    
-    # fit results per pixel & save
-    if args.fitplots:
-        print('Creating plots and saving in ./results/...')
-        print('This may take a while.')
-        for expix in range(256):
-            exr   = expix%N_pix_w
-            exc   = int(np.floor(expix/N_pix_w))
-    
-            fig, ax = plt.subplots()
-    
-            plt.title("S curve fit example (pixel #%d)"%expix)
-            plt.xlabel("Vth")
-            plt.ylabel("hit rate")
-    
-            plt.plot(vth_axis, hit_rate[expix], '.-')
-            fit_func = sigmoid(slopes[exr][exc], vth_axis, means[exr][exc])
-            plt.plot(vth_axis, fit_func)
-            plt.axvline(x=means[exr][exc], color='r', linestyle='--')
-            plt.axvspan(means[exr][exc]-widths[exr][exc], means[exr][exc]+widths[exr][exc],
-                        color='r', alpha=0.1)
-    
-            plt.xlim(vth_min, vth_max)
-            plt.grid(True)
-            plt.legend(["data","fit","baseline"])
-    
-            fig.savefig(f'results/pixel_{expix}.png')
-            plt.close(fig)
-            del fig, ax
-    
-    # 2D histogram of the mean
-    fig, ax = plt.subplots()
-    plt.title("Mean values of baseline voltage")
-    cax = ax.matshow(means)
-    
-    fig.colorbar(cax)
-    ax.set_xticks(np.arange(N_pix_w))
-    ax.set_yticks(np.arange(N_pix_w))
-    
-    for i in range(N_pix_w):
-        for j in range(N_pix_w):
-            text = ax.text(j, i, "%.2f\n+/-%.2f"%(means[i,j],widths[i,j]),
-                    ha="center", va="center", color="w", fontsize="xx-small")
-    
-    fig.savefig(f'results/sigmoid_mean_2D.png')
-    plt.show()
-    
-    plt.close(fig)
-    del fig, ax
-    
-    # 2D histogram of the width
-    fig, ax = plt.subplots()
-    plt.title("Width of the sigmoid")
-    cax = ax.matshow(
-        widths,
-        cmap='RdYlGn_r',
-        vmin=0, vmax=5,
-    )
-    
-    fig.colorbar(cax)
-    ax.set_xticks(np.arange(N_pix_w))
-    ax.set_yticks(np.arange(N_pix_w))
-    
-    #cax.set_zlim(0, 10)
-    
-    fig.savefig(f'results/sigmoid_width_2D.png')
-    plt.show()
+if __name__ == '__main__':
+
+    # initiate
+    ETROC2 = ETROC(usefake=True) # currently using Software ETROC2 (fake)
+    DF = DataFrame('ETROC2')
+
+    # argsparser
+    import argparse
+    argParser = argparse.ArgumentParser(description = "Argument parser")
+    argParser.add_argument('--test_readwrite', action='store_true', default=False, help="Test simple read/write functionality?")
+    argParser.add_argument('--vth', action='store_true', default=False, help="Parse Vth scan plots?")
+    argParser.add_argument('--rerun', action='store_true', default=False, help="Rerun Vth scan and overwrite data?")
+    argParser.add_argument('--fitplots', action='store_true', default=False, help="Create individual vth fit plots for all pixels?")
+    args = argParser.parse_args()
+
+
+    # ==============================
+    # === Test simple read/write ===
+    # ==============================
+    # FIXME this needs to be fixed for new ETROC2 register table / structure
+    if args.test_readwrite:
+        print("<--- Test simple read/write --->")
+        print("Testing read/write to addresses...")
+        for r in range(16):
+            for c in range(16):
+                for n in range(32):
+                    regadr = 'PixR%dC%dCfg%d'%(r,c,n)
+                    ETROC2.wr_adr(regadr, 1)
+                    readval = ETROC2.rd_adr(regadr)
+                    if not(readval == 1):
+                        raise Exception('Test failed for %s, value read was %d.'%(regname,readval))
+                for n in range(8):
+                    regadr = 'PixR%dC%dSta%d'%(r,c,n)
+                    ETROC2.wr_adr(regadr, 1)
+                    readval = ETROC2.rd_adr(regadr)
+                    if not(readval == 1):
+                        raise Exception('Test failed for %s, value read was %d.'%(regname,readval))
+        print("Test passed.\n")
+
+        print("Testing read/write for shared pixels...")
+        for n in range(32):
+            regadr = 'PixR%dC%dCfg%d'%(1,1,n)
+            ETROC2.wr_adr(regadr, 1)
+            for r in range(16):
+                for c in range(16):
+                    readval = ETROC2.rd_adr(regadr)
+                    if not(readval == 1):
+                        raise Exception('Test failed for %s, value read was %d.'%(regname,readval))
+        print("Test passed.\n")
+
+        print("Testing read/write with register names...")
+        with open(os.path.expandvars('$TAMALERO_BASE/address_table/ETROC2.yaml'), 'r') as f:
+            regnames = load(f, Loader=Loader)
+        for regname in list(regnames.keys()):
+            for pix in range(256):
+                ETROC2.wr_reg(regname, pix, 1)
+                readval = ETROC2.rd_reg(regname, pix)
+                if not(readval == 1):
+                    raise Exception('Test failed for %s, value read was %d.'%(regname,readval))
+        print("Test passed.\n")
+
+
+    # ==============================
+    # ======= Test Vth scan ========
+    # ==============================
+
+
+
+    # ========= Vth SCAN =========
+    if args.vth:
+        print("<--- Testing Vth scan --->")
+
+        # run only if no saved data or we want to rerun
+        if (not os.path.isfile("results/vth_scan.json")) or args.rerun:
+
+            # scan
+            print("No data. Run new vth scan...")
+            result_data = vth_scan(ETROC2)
+
+            #save
+            if not os.path.isdir('results'):
+                os.makedirs('results')
+
+            with open("results/vth_scan.json", "w") as outfile:
+                json.dump(result_data, outfile)
+                print("Data saved to results/vth_scan.json\n")
+
+
+        # read and parse vth scan data
+        with open('results/vth_scan.json', 'r') as openfile:
+            vth_scan_data = json.load(openfile)
+
+        vth_axis = np.array(vth_scan_data[0])
+        hit_rate = np.array(vth_scan_data[1])
+
+        vth_min = vth_axis[0]  # vth scan range
+        vth_max = vth_axis[-1]
+        N_pix   = len(hit_rate) # total # of pixels
+        N_pix_w = int(round(np.sqrt(N_pix))) # N_pix in NxN layout
+
+
+        # ======= PERFORM FITS =======
+
+        # fit to sigmoid and save to NxN layout
+        slopes = np.empty([N_pix_w, N_pix_w])
+        means  = np.empty([N_pix_w, N_pix_w])
+        widths = np.empty([N_pix_w, N_pix_w])
+
+        for pix in range(N_pix):
+            fitresults = sigmoid_fit(vth_axis, hit_rate[pix])
+            r, c = fromPixNum(pix, N_pix_w)
+            slopes[r][c] = fitresults[0]
+            means[r][c]  = fitresults[1]
+            widths[r][c] = 4/fitresults[0]
+
+        # print out results nicely
+        for r in range(N_pix_w):
+            for c in range(N_pix_w):
+                pix = toPixNum(r, c, N_pix_w)
+                print("{:8s}".format("#"+str(pix)), end='')
+            print("")
+            for c in range(N_pix_w):
+                print("%4.2f"%means[r][c], end='  ')
+            print("")
+            for c in range(N_pix_w):
+                print("+-%2.2f"%widths[r][c], end='  ')
+            print("\n")
+
+
+        # ======= PLOT RESULTS =======
+
+        # fit results per pixel & save
+        if args.fitplots:
+            print('Creating plots and saving in ./results/...')
+            print('This may take a while.')
+            for expix in range(256):
+                exr   = expix%N_pix_w
+                exc   = int(np.floor(expix/N_pix_w))
+
+                fig, ax = plt.subplots()
+
+                plt.title("S curve fit example (pixel #%d)"%expix)
+                plt.xlabel("Vth")
+                plt.ylabel("hit rate")
+
+                plt.plot(vth_axis, hit_rate[expix], '.-')
+                fit_func = sigmoid(slopes[exr][exc], vth_axis, means[exr][exc])
+                plt.plot(vth_axis, fit_func)
+                plt.axvline(x=means[exr][exc], color='r', linestyle='--')
+                plt.axvspan(means[exr][exc]-widths[exr][exc], means[exr][exc]+widths[exr][exc],
+                            color='r', alpha=0.1)
+
+                plt.xlim(vth_min, vth_max)
+                plt.grid(True)
+                plt.legend(["data","fit","baseline"])
+
+                fig.savefig(f'results/pixel_{expix}.png')
+                plt.close(fig)
+                del fig, ax
+
+        # 2D histogram of the mean
+        fig, ax = plt.subplots()
+        plt.title("Mean values of baseline voltage")
+        cax = ax.matshow(means)
+
+        fig.colorbar(cax)
+        ax.set_xticks(np.arange(N_pix_w))
+        ax.set_yticks(np.arange(N_pix_w))
+
+        for i in range(N_pix_w):
+            for j in range(N_pix_w):
+                text = ax.text(j, i, "%.2f\n+/-%.2f"%(means[i,j],widths[i,j]),
+                        ha="center", va="center", color="w", fontsize="xx-small")
+
+        fig.savefig(f'results/sigmoid_mean_2D.png')
+        plt.show()
+
+        plt.close(fig)
+        del fig, ax
+
+        # 2D histogram of the width
+        fig, ax = plt.subplots()
+        plt.title("Width of the sigmoid")
+        cax = ax.matshow(
+            widths,
+            cmap='RdYlGn_r',
+            vmin=0, vmax=5,
+        )
+
+        fig.colorbar(cax)
+        ax.set_xticks(np.arange(N_pix_w))
+        ax.set_yticks(np.arange(N_pix_w))
+
+        #cax.set_zlim(0, 10)
+
+        fig.savefig(f'results/sigmoid_width_2D.png')
+        plt.show()
+
+    else:
+        thresholds = [203-x*0.3 for x in range(10)]
+        print("Sending 10 L1As and reading back data, for the following thresholds:")
+        print(thresholds)
+        for th in thresholds:
+            print(f'Threshold at {th=}mV')
+            ETROC2.set_Vth_mV(th)  # anything between 196 and 203 should give reasonable numbers of hits
+            data = ETROC2.runL1A()  # this will spit out data for a single event, with an occupancy corresponding to the previously set threshold
+            for d in data:
+                print(DF.read(d))
-- 
GitLab


From 204c722f9e65442a196a56d6101c06d499634d28 Mon Sep 17 00:00:00 2001
From: Daniel Spitzbart <daniel.spitzbart@cern.ch>
Date: Fri, 14 Apr 2023 16:48:24 -0400
Subject: [PATCH 3/4] adding software emulator to CI (WIP)

---
 .gitlab-ci.yml             |  1 +
 test_ETROC.py              | 10 ++++++++++
 tests/software_emulator.sh | 22 ++++++++++++++++++++++
 3 files changed, 33 insertions(+)
 create mode 100644 tests/software_emulator.sh

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 77a4ea8..5964919 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -24,3 +24,4 @@ make_test:
     - source setup.sh
     - source /media/data_hdd/Xilinx/Vivado/2021.1/settings64.sh
     - source tests/startup.sh -i 210308B0B4F5 -k 192.168.0.12 -p 192.168.2.3:ch2
+    - source tests/software_emulator.sh
diff --git a/test_ETROC.py b/test_ETROC.py
index f8aeadd..5aaf6bd 100644
--- a/test_ETROC.py
+++ b/test_ETROC.py
@@ -7,6 +7,7 @@ from scipy.optimize import curve_fit
 from matplotlib import pyplot as plt
 
 import os
+import sys
 import json
 from yaml import load, dump
 try:
@@ -290,5 +291,14 @@ if __name__ == '__main__':
             print(f'Threshold at {th=}mV')
             ETROC2.set_Vth_mV(th)  # anything between 196 and 203 should give reasonable numbers of hits
             data = ETROC2.runL1A()  # this will spit out data for a single event, with an occupancy corresponding to the previously set threshold
+            unpacked = [DF.read(d) for d in data]
             for d in data:
                 print(DF.read(d))
+
+
+        if unpacked[-1][1]['hits'] == len(unpacked)-2:
+            print("Very simple check passed.")
+            sys.exit(0)
+        else:
+            print("Data looks inconsistent.")
+            sys.exit(1)
diff --git a/tests/software_emulator.sh b/tests/software_emulator.sh
new file mode 100644
index 0000000..7c33544
--- /dev/null
+++ b/tests/software_emulator.sh
@@ -0,0 +1,22 @@
+#! /bin/bash
+
+#### Predefined variables and functions ####
+RED='\033[1;31m'
+GREEN='\033[1;32m'
+BLUE='\033[1;34m'
+NC='\033[0m'
+
+function info() { echo -e "${BLUE}${@}${NC}"; }
+function error() { echo -e "${RED}${@}${NC}"; }
+function success() { echo -e "${GREEN}${@}${NC}"; }
+
+# run test_tamalero with power up
+info "Running test_ETROC..."
+/usr/bin/env python3 test_ETROC.py
+EXIT=$?
+if [ ${EXIT} -ne 0 ]; then
+	error "Failure when running test_ETROC.py; exit code is ${EXIT}. Blocking merge."
+	return ${EXIT}
+else
+	success "Success! test_ETROC.py exit with code ${EXIT}"
+fi
-- 
GitLab


From e88b69dacee819ca9bca0437a94c6ef4015af2ab Mon Sep 17 00:00:00 2001
From: Daniel Spitzbart <daniel.spitzbart@cern.ch>
Date: Fri, 14 Apr 2023 16:52:10 -0400
Subject: [PATCH 4/4] adding scipy to requirements (S-curve fits)

---
 requirements.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/requirements.txt b/requirements.txt
index e939ad3..e594c68 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,7 @@
 PyYAML==5.3.1
 requests==2.22.0 
 numpy==1.24.2
+scipy==1.8.0
 yahist==1.14.0
 mplhep==0.3.26
 tqdm==4.64.1
-- 
GitLab