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