Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: TDC measurement features #32

Open
wants to merge 52 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
12f48e8
ENH: Add TDC module for CMOS (LEMO_RX1)
cbespin Mar 29, 2023
7c28bc7
BUG: Fix TDC CMOS FW address
cbespin Mar 30, 2023
ca50c11
MNT: Refactor TDC analysis
cbespin Mar 31, 2023
fff4886
ENH: Optimize delay between INJ and BCID CLK
lschall May 23, 2023
1ea83b1
ADD: ToT calibration skript on per pixel level
lschall May 23, 2023
9edaea6
ENH: Run analysis and plotting for calibrate_tot
lschall May 30, 2023
2ffb2cd
FIX: Issue #28 (Memory usage)
lschall May 30, 2023
e7e0b6f
MAINT: Needed analysis utils methods
lschall May 30, 2023
face919
DBG: Add ILA module for debugging
cbespin Jun 5, 2023
b93d8c9
Revert "DBG: Add ILA module for debugging"
cbespin Jun 5, 2023
634ce4c
ADD: Charge calibration done before clusterizing
lschall Jun 5, 2023
8a0e71f
FIX: codestyle
lschall Jun 5, 2023
1a39dbb
ENH: TDC analysis, remove CMOS TDC
cbespin Jun 29, 2023
a4296d4
MNT: Latest TDC firmware
cbespin Jan 18, 2024
7268740
CI: fix reference for codestyle check
cbespin Feb 14, 2024
1007cf8
TST: Update libraries and verilator CI version
cbespin Feb 14, 2024
bbf1ad0
Clean up import
cbespin Feb 14, 2024
f082cf8
ENH: Update simulation utils
cbespin Feb 14, 2024
fb2e715
CI: Fix dependency instal for ubuntu-22.04
cbespin Feb 14, 2024
434fbde
CI: Update dependency install for ubuntu-22.04
cbespin Feb 14, 2024
7b9845b
Merge pull request #37 from SiLab-Bonn/ci_update
cbespin Feb 15, 2024
5ea314f
ENH: Automized calling of tot calibration file in scans
lschall Feb 15, 2024
94e3e2f
ENH: Electron conversion in plotting
lschall Feb 15, 2024
013207b
REV: DC/AC coupled conversion factor
lschall Feb 15, 2024
ec97276
REV: Convert correct axis in cluster charge plot
lschall Feb 15, 2024
3ce44f1
EHN: secondary electron axis for selected plots
lschall Feb 15, 2024
9432404
ENH: secondary electron axis
lschall Feb 15, 2024
3e244ad
MAINT: Naming scheme of fit functions
lschall Feb 20, 2024
5bbfd88
MAINT: Minor reviews of plotting changes
lschall Feb 20, 2024
d54e731
MAINT: fix codestyle
lschall Feb 20, 2024
85b0bf6
MAINT: Call charge calibration in all cases and fix variable hist sizes
lschall Feb 20, 2024
31de3ae
REV: Naming and calling of 'electron_axis'
lschall Feb 20, 2024
9f4a241
MAINT: typos in variables and condition
lschall Feb 20, 2024
d7a35fe
Merge pull request #33 from SiLab-Bonn/calibrate_tot
lschall Feb 20, 2024
325724a
ADD: Dedicated external trigger scan
lschall Feb 20, 2024
5a7035d
MAINT: Adapting and renaming 'simple_scan' to 'source_scan'
lschall Feb 20, 2024
2f0ccaa
MAINT: Adapt scan names in plotting accordingly
lschall Feb 20, 2024
ab43f3e
FIX: codestyle
lschall Feb 20, 2024
7446a41
FIX: Calling of electron conversion factors in plotting
lschall Feb 22, 2024
b67c6a6
FIX: Set all 'use_electron_offset' to False as they are not used (for…
lschall Feb 22, 2024
e0cb494
REV: Keep scan_timeout feature for external trigger scan
lschall Feb 23, 2024
9711935
Merge pull request #38 from SiLab-Bonn/ext_trigger_scan
lschall Feb 23, 2024
3ad5bed
ENH: Consistent naming
cbespin Jul 9, 2024
d703a8d
ENH: Add TDC module for CMOS (LEMO_RX1)
cbespin Mar 29, 2023
812cb9c
BUG: Fix TDC CMOS FW address
cbespin Mar 30, 2023
62a4ca6
MNT: Refactor TDC analysis
cbespin Mar 31, 2023
8414230
ENH: TDC analysis, remove CMOS TDC
cbespin Jun 29, 2023
4295f99
MNT: Latest TDC firmware
cbespin Jan 18, 2024
499fc5f
ENH: Consistent naming
cbespin Jul 9, 2024
22d909e
Merge branch 'cmos_tdc' of https://github.com/SiLab-Bonn/tj-monopix2-…
cbespin Jul 9, 2024
96f95c0
STY: Fix codestyle
cbespin Jul 9, 2024
c9483eb
STY: Fix codestyle
cbespin Jul 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/codestyle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ jobs:
- name: Install flake8
run: pip install flake8
- name: Check code
run: while read in; do flake8 --extend-ignore E501,W503 "$in"; done < <(git diff --name-only --diff-filter=ACMTUXB origin/master -- '*.py')
run: while read in; do flake8 --extend-ignore E501,W503 "$in"; done < <(git diff --name-only --diff-filter=ACMTUXB origin/development -- '*.py')
6 changes: 3 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ on:
jobs:
tests:
name: Python ${{matrix.python-version}} | ${{matrix.sim}}
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
env:
SIM: ${{matrix.sim}}
strategy:
fail-fast: false
matrix:
include:
- sim: verilator
sim-version: v4.106
sim-version: v5.020
python-version: '3.10'
steps:
- uses: actions/checkout@v3
Expand All @@ -45,7 +45,7 @@ jobs:
- name: Install Verilator
if: matrix.sim == 'verilator'
run: |
sudo apt install -y --no-install-recommends make g++ perl python3 autoconf flex bison libfl2 libfl-dev zlibc zlib1g zlib1g-dev
sudo apt install -y --no-install-recommends make g++ help2man perl python3 autoconf flex bison libfl2 libfl-dev zlib1g zlib1g-dev
git clone https://github.com/verilator/verilator.git -b ${{matrix.sim-version}}
cd verilator
autoconf
Expand Down
9 changes: 5 additions & 4 deletions firmware/src/tjmonopix2_core.v
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,8 @@ rrp_arbiter
.DATA_IN({
RX_FIFO_DATA,
TLU_FIFO_DATA,
TDC_FIFO_DATA}),
TDC_FIFO_DATA
}),
.READ_GRANT({
RX_FIFO_READ,
TLU_FIFO_READ,
Expand Down Expand Up @@ -612,7 +613,7 @@ pulse_gen #(

// ----- TDC ----- //
localparam CLKDV = 4; // division factor from 160 MHz clock to DV_CLK (here 40 MHz)
wire [CLKDV * 4 - 1:0] FAST_TRIGGER_OUT;
// wire [CLKDV * 4 - 1:0] FAST_TRIGGER_OUT;
// wire LEMO_RX0_FROM_TDC;
// wire HITOR_FROM_TDC;

Expand All @@ -624,12 +625,12 @@ tdc_s3 #(
.DATA_IDENTIFIER(4'b0010),
.FAST_TDC(1),
.FAST_TRIGGER(1),
.BROADCAST(0) // generate for first TDC module the 640MHz sampled trigger signal and share it with other modules using TRIGGER input
.BROADCAST(0) // generate for LVDS TDC module the 640MHz sampled trigger signal and share it with other modules using TRIGGER input
) i_tdc (
.CLK320(CLK320), // 320 MHz
.CLK160(CLK160), // 160 MHz
.DV_CLK(CLK40), // 40 MHz
.TDC_IN(LVDS_HITOR), // HITOR
.TDC_IN(LVDS_HITOR), // LVDS HITOR (DP)
.TDC_OUT(),
.TRIG_IN(LEMO_RX[0]),
.TRIG_OUT(),
Expand Down
70 changes: 57 additions & 13 deletions tjmonopix2/analysis/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@


class Analysis(object):
def __init__(self, raw_data_file=None, analyzed_data_file=None,
def __init__(self, raw_data_file=None, analyzed_data_file=None, tot_calib_file=None,
store_hits=True, cluster_hits=False, analyze_tdc=False, use_tdc_trigger_dist=False,
build_events=False, chunk_size=1000000, **_):
self.log = logger.setup_derived_logger('Analysis')
Expand All @@ -32,6 +32,7 @@ def __init__(self, raw_data_file=None, analyzed_data_file=None,
self.chunk_size = chunk_size
self.analyze_tdc = analyze_tdc
self.use_tdc_trigger_dist = use_tdc_trigger_dist
self.tot_calib_file = tot_calib_file

if self.build_events:
self.cluster_hits = True
Expand Down Expand Up @@ -62,6 +63,7 @@ def _get_configs(self):
self.scan_config = au.ConfigDict(in_file.root.configuration_in.scan.scan_config[:])
self.chip_settings = au.ConfigDict(in_file.root.configuration_in.chip.settings[:])
self.tlu_config = au.ConfigDict(in_file.root.configuration_in.bench.TLU[:])
self.tdc_config = au.ConfigDict(in_file.root.configuration_in.bench.TDC[:])

def __enter__(self):
return self
Expand Down Expand Up @@ -156,7 +158,7 @@ def _setup_clusterizer(self):
('frame', 'u1'),
('column', 'u2'),
('row', 'u2'),
('charge', 'u1'),
('charge', 'u2'),
('timestamp', 'i8')]
cluster_fields = {'event_number': 'event_number',
'column': 'column',
Expand All @@ -172,7 +174,7 @@ def _setup_clusterizer(self):
cluster_description = [('event_number', 'u4'),
('id', '<u2'),
('size', '<u2'),
('tot', '<u2'),
('tot', '<u4'),
('seed_col', '<u2'),
('seed_row', '<u2'),
('mean_col', '<f4'),
Expand Down Expand Up @@ -254,14 +256,18 @@ def end_of_cluster_function(hits, clusters, cluster_size,
noisy_pixels, disabled_pixels,
seed_hit_index)

if self.tot_calib_file:
max_hit_charge = 2048
else:
max_hit_charge = 128
# Initialize clusterizer with custom hit/cluster fields
self.clz = HitClusterizer(
hit_fields=hit_fields,
hit_dtype=hit_dtype,
cluster_fields=cluster_fields,
cluster_dtype=self.cluster_dtype,
min_hit_charge=1,
max_hit_charge=128,
max_hit_charge=max_hit_charge,
column_cluster_distance=5,
row_cluster_distance=5,
frame_cluster_distance=1,
Expand Down Expand Up @@ -294,6 +300,9 @@ def analyze_data(self):
if self.build_events:
trigger_n, trigger_ts, event_n = 0, 0, 0
event_table = self._create_table(out_file, name='Hits', title='event_data', dtype=au.event_dtype)
if self.tot_calib_file is not None:
with tb.open_file(self.tot_calib_file, 'r') as calib_file:
self.tot_calib = calib_file.root.InjTotCalibration[:]
if self.cluster_hits:
cluster_table = out_file.create_table(
out_file.root, name='Cluster',
Expand All @@ -302,11 +311,21 @@ def analyze_data(self):
filters=tb.Filters(complib='blosc',
complevel=5,
fletcher32=False))
if self.tot_calib_file:
cs_tot_size = 2048
else:
cs_tot_size = 256
hist_cs_size = np.zeros(shape=(30, ), dtype=np.uint32)
hist_cs_tot = np.zeros(shape=(256, ), dtype=np.uint32)
hist_cs_tot = np.zeros(shape=(cs_tot_size, ), dtype=np.uint32)
hist_cs_shape = np.zeros(shape=(300, ), dtype=np.int32)

interpreter = RawDataInterpreter(n_scan_params=n_scan_params, trigger_data_format=self.tlu_config['DATA_FORMAT'])
# Setup interpreter
interpreter = RawDataInterpreter(
n_scan_params=n_scan_params,
trigger_data_format=self.tlu_config['DATA_FORMAT'],
en_trigger_dist=self.tdc_config['EN_TRIGGER_DIST']
)

self.last_chunk = False
pbar = tqdm(total=n_words, unit=' Words', unit_scale=True)
upd = 0
Expand Down Expand Up @@ -336,6 +355,7 @@ def analyze_data(self):
if self.build_events:
data_to_clusterizer = event_dat
else:
hit_dat = hit_dat[hit_dat['col'] < 1000] # Can only call tot_calib for hit data
hit_data_cs_fmt = np.zeros(len(hit_dat), dtype=au.event_dtype)
hit_data_cs_fmt['event_number'][:] = hit_dat['timestamp'][:]
hit_data_cs_fmt['trigger_number'][:] = -1
Expand All @@ -346,11 +366,19 @@ def analyze_data(self):
hit_data_cs_fmt['timestamp'][:] = hit_dat['timestamp'][:]
data_to_clusterizer = hit_data_cs_fmt

if self.tot_calib_file:
data_to_clusterizer['charge'][:] = au._inv_tot_response_func(
data_to_clusterizer['charge'][:],
self.tot_calib[data_to_clusterizer[:]['column'], data_to_clusterizer[:]['row']][:, 0],
self.tot_calib[data_to_clusterizer[:]['column'], data_to_clusterizer[:]['row']][:, 1],
self.tot_calib[data_to_clusterizer[:]['column'], data_to_clusterizer[:]['row']][:, 2]
)

_, cluster = self.clz.cluster_hits(data_to_clusterizer)
cluster_table.append(cluster)
# Create actual cluster hists
cs_size = np.bincount(cluster['size'], minlength=30)[:30]
cs_tot = np.bincount(cluster['tot'], minlength=256)[:256]
cs_tot = np.bincount(cluster['tot'], minlength=512)[:512]
sel = np.logical_and(cluster['cluster_shape'] > 0, cluster['cluster_shape'] < 300)
cs_shape = np.bincount(cluster['cluster_shape'][sel], minlength=300)[:300]
# Add to total hists
Expand All @@ -360,13 +388,13 @@ def analyze_data(self):
pbar.update(upd)
pbar.close()

hist_occ, hist_tot, hist_tdc = interpreter.get_histograms()
hist_occ, hist_tot, hist_tdc, hist_trigger_dist = interpreter.get_histograms()

self._create_additional_hit_data(hist_occ, hist_tot)
self._create_additional_hit_data(hist_occ, hist_tot, hist_tdc, hist_trigger_dist)
if self.cluster_hits:
self._create_additional_cluster_data(hist_cs_size, hist_cs_tot, hist_cs_shape)

def _create_additional_hit_data(self, hist_occ, hist_tot):
def _create_additional_hit_data(self, hist_occ, hist_tot, hist_tdc, hist_trigger_dist):
with tb.open_file(self.analyzed_data_file, 'r+') as out_file:
scan_id = self.run_config['scan_id']

Expand All @@ -385,7 +413,23 @@ def _create_additional_hit_data(self, hist_occ, hist_tot):
complevel=5,
fletcher32=False))

# if self.analyze_tdc: # Only store if TDC analysis is used.
if self.analyze_tdc: # Only store if TDC analysis is used.
out_file.create_carray(out_file.root,
name='HistTdc',
title='TDC Histogram',
obj=hist_tdc,
filters=tb.Filters(complib='blosc',
complevel=5,
fletcher32=False))

out_file.create_carray(out_file.root,
name='HistTriggerDist',
title='Trigger Dist Histogram',
obj=hist_trigger_dist,
filters=tb.Filters(complib='blosc',
complevel=5,
fletcher32=False))

# out_file.create_carray(out_file.root,
# name='HistTdcStatus',
# title='Tdc status Histogram',
Expand All @@ -394,11 +438,11 @@ def _create_additional_hit_data(self, hist_occ, hist_tot):
# complevel=5,
# fletcher32=False))

if scan_id in ['threshold_scan']:
if scan_id in ['threshold_scan', 'calibrate_tot']:
n_injections = self.scan_config['n_injections']
hist_scurve = hist_occ.reshape((self.rows * self.columns, -1))

if scan_id in ['threshold_scan']:
if scan_id in ['threshold_scan', 'calibrate_tot']:
scan_params = [self.scan_config['VCAL_HIGH'] - v for v in range(self.scan_config['VCAL_LOW_start'],
self.scan_config['VCAL_LOW_stop'], self.scan_config['VCAL_LOW_step'])]
self.threshold_map, self.noise_map, self.chi2_map = au.fit_scurves_multithread(hist_scurve, scan_params, n_injections, optimize_fit_range=False)
Expand Down
62 changes: 61 additions & 1 deletion tjmonopix2/analysis/analysis_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
("frame", "<u1"),
("column", "<u2"),
("row", "<u2"),
("charge", "<u1"),
("charge", "<u2"),
("timestamp", "<i8"),
])

Expand Down Expand Up @@ -78,6 +78,15 @@ def _type_cast(self, key, val):
return key, val


def _tot_response_func(x, a, b, d):
return (a / x + 1 / b) * (x - d)


@numba.njit
def _inv_tot_response_func(tot, a, b, d):
return (np.sqrt(b**2 * (a - tot)**2 + 2 * b * d * (a + tot) + d**2) - b * a + b * tot + d) * 0.5


def scurve(x, A, mu, sigma):
return 0.5 * A * erf((x - mu) / (np.sqrt(2) * sigma)) + 0.5 * A

Expand Down Expand Up @@ -424,3 +433,54 @@ def fit_scurves_multithread(scurves, scan_params, n_injections=None, invert_x=Fa
sig2D = np.reshape(sig, (512, 512))
chi2ndf2D = np.reshape(chi2ndf, (512, 512))
return thr2D, sig2D, chi2ndf2D


def fit_tot_response_multithread(tot_avg, scan_params):

scurves_masked = np.ma.masked_array(tot_avg)

logger.info("Start injection ToT calibration fit on %d CPU core(s)", mp.cpu_count())
partialfit_tot_inj_func = partial(_fit_tot_response, scan_params=scan_params)

result_list = imap_bar(partialfit_tot_inj_func, scurves_masked.tolist(), unit=' Fits', unit_scale=True) # Masked array entries to list leads to NaNs
result_array = np.array(result_list)
logger.info("Fit finished")
return np.reshape(result_array, (512, 512, 4))


def _fit_tot_response(data, scan_params):
'''
Fit one pixel data with injection Tot calibration function.
Has to be global function for the multiprocessing module.

Returns:
(m, b, c, d, chi2/ndf)
'''

# Typecast to working types
data = np.array(data, dtype=float)
# Scipy bug: fit does not work on float32 values, without any error message
scan_params = np.array(scan_params, dtype=float)

# Deselect masked values (== nan)
x = scan_params[~np.isnan(data)]
y = data[~np.isnan(data)]
yerr = np.ones(len(y)) / 2 # Assume +/- 0.5 because of integer values of ToT

# Only fit data that is fittable
if np.all(np.isnan(y)) or x.shape[0] < 3 or len(x[y > 0]) == 0:
return (0., 0., 0., 0.)

p0 = [40, 0.005, 0.1]

try:
with warnings.catch_warnings():
warnings.simplefilter("ignore", OptimizeWarning)
popt = curve_fit(f=lambda x, a, b, d: _tot_response_func(x, a, b, d),
xdata=x[y > 0], ydata=y[y > 0], p0=p0, sigma=yerr[y > 0],
absolute_sigma=True)[0]
chi2 = np.sum((y - _tot_response_func(x, *popt)) ** 2)
except RuntimeError: # fit failed
return (0., 0., 0., 0.)

return (*popt, chi2 / (y.shape[0] - 3 - 1))
Loading
Loading