CMS 3D CMS Logo

HGCFEElectronics.cc
Go to the documentation of this file.
1 
5 
6 #include "vdt/vdtMath.h"
7 
8 using namespace hgc_digi;
9 
10 //
11 template <class DFr>
13  : fwVersion_{ps.getParameter<uint32_t>("fwVersion")},
14  adcPulse_{},
15  pulseAvgT_{},
16  tdcForToAOnset_fC_{},
17  adcSaturation_fC_{-1.0},
18  adcLSB_fC_{},
19  tdcLSB_fC_{},
20  tdcSaturation_fC_{-1.0},
21  adcThreshold_fC_{},
22  tdcOnset_fC_{},
23  toaLSB_ns_{},
24  tdcResolutionInNs_{1e-9}, // set time resolution very small by default
25  targetMIPvalue_ADC_{},
26  jitterNoise2_ns_{},
27  jitterConstant2_ns_{},
28  noise_fC_{},
29  toaMode_(WEIGHTEDBYE) {
30  edm::LogVerbatim("HGCFE") << "[HGCFEElectronics] running with version " << fwVersion_ << std::endl;
31  if (ps.exists("adcPulse")) {
32  auto temp = ps.getParameter<std::vector<double> >("adcPulse");
33  for (unsigned i = 0; i < temp.size(); ++i) {
34  adcPulse_[i] = (float)temp[i];
35  }
36  // normalize adc pulse
37  for (unsigned i = 0; i < adcPulse_.size(); ++i) {
38  adcPulse_[i] = adcPulse_[i] / adcPulse_[2];
39  }
40  temp = ps.getParameter<std::vector<double> >("pulseAvgT");
41  for (unsigned i = 0; i < temp.size(); ++i) {
42  pulseAvgT_[i] = (float)temp[i];
43  }
44  }
45  if (ps.exists("adcNbits")) {
46  uint32_t adcNbits = ps.getParameter<uint32_t>("adcNbits");
47  adcSaturation_fC_ = ps.getParameter<double>("adcSaturation_fC");
48  adcLSB_fC_ = adcSaturation_fC_ / pow(2., adcNbits);
49  edm::LogVerbatim("HGCFE") << "[HGCFEElectronics] " << adcNbits << " bit ADC defined"
50  << " with LSB=" << adcLSB_fC_ << " saturation to occur @ " << adcSaturation_fC_
51  << std::endl;
52  }
53 
54  if (ps.exists("tdcNbits")) {
55  tdcNbits_ = ps.getParameter<uint32_t>("tdcNbits");
56  setTDCfsc(ps.getParameter<double>("tdcSaturation_fC"));
57  edm::LogVerbatim("HGCFE") << "[HGCFEElectronics] " << tdcNbits_ << " bit TDC defined with LSB=" << tdcLSB_fC_
58  << " saturation to occur @ " << tdcSaturation_fC_
59  << " (NB lowered by 1 part in a million)" << std::endl;
60  }
61  if (ps.exists("targetMIPvalue_ADC"))
62  targetMIPvalue_ADC_ = ps.getParameter<uint32_t>("targetMIPvalue_ADC");
63  if (ps.exists("adcThreshold_fC"))
64  adcThreshold_fC_ = ps.getParameter<double>("adcThreshold_fC");
65  if (ps.exists("tdcOnset_fC"))
66  tdcOnset_fC_ = ps.getParameter<double>("tdcOnset_fC");
67  if (ps.exists("tdcForToAOnset_fC")) {
68  auto temp = ps.getParameter<std::vector<double> >("tdcForToAOnset_fC");
69  if (temp.size() == tdcForToAOnset_fC_.size()) {
70  std::copy_n(temp.begin(), temp.size(), tdcForToAOnset_fC_.begin());
71  } else {
72  throw cms::Exception("BadConfiguration") << " HGCFEElectronics wrong size for ToA thresholds ";
73  }
74  }
75  if (ps.exists("toaLSB_ns"))
76  toaLSB_ns_ = ps.getParameter<double>("toaLSB_ns");
77  if (ps.exists("tdcChargeDrainParameterisation")) {
78  for (auto val : ps.getParameter<std::vector<double> >("tdcChargeDrainParameterisation")) {
79  tdcChargeDrainParameterisation_.push_back((float)val);
80  }
81  }
82  if (ps.exists("tdcResolutionInPs"))
83  tdcResolutionInNs_ = ps.getParameter<double>("tdcResolutionInPs") * 1e-3; // convert to ns
84  if (ps.exists("toaMode"))
85  toaMode_ = ps.getParameter<uint32_t>("toaMode");
86 
87  if (ps.exists("jitterNoise_ns")) {
88  auto temp = ps.getParameter<std::vector<double> >("jitterNoise_ns");
89  if (temp.size() == jitterNoise2_ns_.size()) {
90  std::copy_n(temp.begin(), temp.size(), jitterNoise2_ns_.begin());
91  } else {
92  throw cms::Exception("BadConfiguration") << " HGCFEElectronics wrong size for ToA jitterNoise ";
93  }
94  }
95  if (ps.exists("jitterConstant_ns")) {
96  auto temp = ps.getParameter<std::vector<double> >("jitterConstant_ns");
97  if (temp.size() == jitterConstant2_ns_.size()) {
98  std::copy_n(temp.begin(), temp.size(), jitterConstant2_ns_.begin());
99  } else {
100  throw cms::Exception("BadConfiguration") << " HGCFEElectronics wrong size for ToA jitterConstant ";
101  }
102  }
103 }
104 
105 //
106 template <class DFr>
108  DFr& dataFrame, HGCSimHitData& chargeColl, uint32_t thrADC, float lsbADC, uint32_t gainIdx, float maxADC) {
109  bool debug(false);
110 
111 #ifdef EDM_ML_DEBUG
112  for (int it = 0; it < (int)(chargeColl.size()); it++)
113  debug |= (chargeColl[it] > adcThreshold_fC_);
114 #endif
115 
116  if (debug)
117  edm::LogVerbatim("HGCFE") << "[runTrivialShaper]" << std::endl;
118 
119  if (lsbADC < 0)
120  lsbADC = adcLSB_fC_;
121  if (maxADC < 0)
122  // lower adcSaturation_fC_ by one part in a million
123  // to ensure largest charge converted in bits is 0xfff==4095, not 0x1000
124  // no effect on charges loewer than; no impact on cpu time, only done once
125  maxADC = adcSaturation_fC_ * (1 - 1e-6);
126  for (int it = 0; it < (int)(chargeColl.size()); it++) {
127  //brute force saturation, maybe could to better with an exponential like saturation
128  const uint32_t adc = std::floor(std::min(chargeColl[it], maxADC) / lsbADC);
129  HGCSample newSample;
130  newSample.set(adc > thrADC, false, gainIdx, 0, adc);
131  dataFrame.setSample(it, newSample);
132 
133  if (debug)
134  edm::LogVerbatim("HGCFE") << adc << " (" << chargeColl[it] << "/" << adcLSB_fC_ << ") ";
135  }
136 
137  if (debug) {
138  std::ostringstream msg;
139  dataFrame.print(msg);
140  edm::LogVerbatim("HGCFE") << msg.str() << std::endl;
141  }
142 }
143 
144 //
145 template <class DFr>
147  HGCSimHitData& chargeColl,
148  uint32_t thrADC,
149  float lsbADC,
150  uint32_t gainIdx,
151  float maxADC,
153  //convolute with pulse shape to compute new ADCs
154  newCharge.fill(0.f);
155  bool debug(false);
156  for (int it = 0; it < (int)(chargeColl.size()); it++) {
157  const float charge(chargeColl[it]);
158  if (charge == 0.f)
159  continue;
160 
161 #ifdef EDM_ML_DEBUG
162  debug |= (charge > adcThreshold_fC_);
163 #endif
164 
165  if (debug)
166  edm::LogVerbatim("HGCFE") << "\t Redistributing SARS ADC" << charge << " @ " << it;
167 
168  for (int ipulse = -2; ipulse < (int)(adcPulse.size()) - 2; ipulse++) {
169  if (it + ipulse < 0)
170  continue;
171  if (it + ipulse >= (int)(dataFrame.size()))
172  continue;
173  const float chargeLeak = charge * adcPulse[(ipulse + 2)];
174  newCharge[it + ipulse] += chargeLeak;
175 
176  if (debug)
177  edm::LogVerbatim("HGCFE") << " | " << it + ipulse << " " << chargeLeak;
178  }
179 
180  if (debug)
181  edm::LogVerbatim("HGCFE") << std::endl;
182  }
183 
184  for (int it = 0; it < (int)(newCharge.size()); it++) {
185  //brute force saturation, maybe could to better with an exponential like saturation
186  const uint32_t adc = std::floor(std::min(newCharge[it], maxADC) / lsbADC);
187  HGCSample newSample;
188  newSample.set(adc > thrADC, false, gainIdx, 0, adc);
189  dataFrame.setSample(it, newSample);
190 
191  if (debug)
192  edm::LogVerbatim("HGCFE") << adc << " (" << std::min(newCharge[it], maxADC) << "/" << lsbADC << " ) ";
193  }
194 
195  if (debug) {
196  std::ostringstream msg;
197  dataFrame.print(msg);
198  edm::LogVerbatim("HGCFE") << msg.str() << std::endl;
199  }
200 }
201 
202 //
203 template <class DFr>
205  HGCSimHitData& chargeColl,
206  HGCSimHitData& toaColl,
207  CLHEP::HepRandomEngine* engine,
208  uint32_t thrADC,
209  float lsbADC,
210  uint32_t gainIdx,
211  float maxADC,
212  int thickness,
213  float tdcOnsetAuto,
215  busyFlags.fill(false);
216  totFlags.fill(false);
217  toaFlags.fill(false);
218  newCharge.fill(0.f);
219  toaFromToT.fill(0.f);
220 
221 #ifdef EDM_ML_DEBUG
222  constexpr bool debug_state(true);
223 #else
224  constexpr bool debug_state(false);
225 #endif
226 
227  bool debug = debug_state;
228 
229  float timeToA = 0.f;
230 
231  //configure the ADC <-> TDC transition depending on the value passed as argument
232  double tdcOnset(maxADC);
233  if (maxADC < 0) {
234  maxADC = adcSaturation_fC_;
235  tdcOnset = tdcOnset_fC_;
236  }
237 
238  //configure the ADC LSB depending on the value passed as argument
239  if (lsbADC < 0)
240  lsbADC = adcLSB_fC_;
241 
242  //first look at time
243  //for pileup look only at intime signals
244  //ToA is in central BX if fired -- std::floor(BX/25.)+9;
245  int fireBX = 9;
246  //noise fluctuation on charge is added after ToA computation
247  //do not recheck the ToA firing threshold tdcForToAOnset_fC_[thickness-1] not to bias the efficiency
248  //to be done properly with realistic ToA shaper and jitter for the moment accounted in the smearing
249  if (toaColl[fireBX] != 0.f) {
250  timeToA = toaColl[fireBX];
251  float jitter = getTimeJitter(chargeColl[fireBX], thickness);
252  if (jitter != 0)
253  timeToA = CLHEP::RandGaussQ::shoot(engine, timeToA, jitter);
254  else if (tdcResolutionInNs_ != 0)
255  timeToA = CLHEP::RandGaussQ::shoot(engine, timeToA, tdcResolutionInNs_);
256  if (timeToA >= 0.f && timeToA <= 25.f)
257  toaFlags[fireBX] = true;
258  }
259 
260  //now look at charge
261  //first identify bunches which will trigger ToT
262  //if(debug_state) edm::LogVerbatim("HGCFE") << "[runShaperWithToT]" << std::endl;
263  for (int it = 0; it < (int)(chargeColl.size()); ++it) {
264  debug = debug_state;
265  //if already flagged as busy it can't be re-used to trigger the ToT
266  if (busyFlags[it])
267  continue;
268 
269  if (tdcOnsetAuto < 0) {
270  tdcOnsetAuto = tdcOnset_fC_;
271  }
272  //if below TDC onset will be handled by SARS ADC later
273  float charge = chargeColl[it];
274  if (charge < tdcOnset) {
275  debug = false;
276  continue;
277  }
278 
279  //raise TDC mode for charge computation
280  //ToA anyway fired independently will be sorted out with realistic ToA dedicated shaper
281  float toa = timeToA;
282  totFlags[it] = true;
283 
284  if (debug)
285  edm::LogVerbatim("HGCFE") << "\t q=" << charge << " fC with <toa>=" << toa << " ns, triggers ToT @ " << it
286  << std::endl;
287 
288  //compute total charge to be integrated and integration time
289  //needs a loop as ToT will last as long as there is charge to dissipate
290  int busyBxs(0);
291  float totalCharge(charge), finalToA(toa), integTime(0);
292  while (true) {
293  //compute integration time in ns and # bunches
294  //float newIntegTime(0);
295  int poffset = 0;
296  float charge_offset = 0.f;
297  const float charge_kfC(totalCharge * 1e-3);
298  if (charge_kfC < tdcChargeDrainParameterisation_[3]) {
299  //newIntegTime=tdcChargeDrainParameterisation_[0]*pow(charge_kfC,2)+tdcChargeDrainParameterisation_[1]*charge_kfC+tdcChargeDrainParameterisation_[2];
300  } else if (charge_kfC < tdcChargeDrainParameterisation_[7]) {
301  poffset = 4;
302  charge_offset = tdcChargeDrainParameterisation_[3];
303  //newIntegTime=tdcChargeDrainParameterisation_[4]*pow(charge_kfC-tdcChargeDrainParameterisation_[3],2)+tdcChargeDrainParameterisation_[5]*(charge_kfC-tdcChargeDrainParameterisation_[3])+tdcChargeDrainParameterisation_[6];
304  } else {
305  poffset = 8;
306  charge_offset = tdcChargeDrainParameterisation_[7];
307  //newIntegTime=tdcChargeDrainParameterisation_[8]*pow(charge_kfC-tdcChargeDrainParameterisation_[7],2)+tdcChargeDrainParameterisation_[9]*(charge_kfC-tdcChargeDrainParameterisation_[7])+tdcChargeDrainParameterisation_[10];
308  }
309  const float charge_mod = charge_kfC - charge_offset;
310  const float newIntegTime =
311  ((tdcChargeDrainParameterisation_[poffset] * charge_mod + tdcChargeDrainParameterisation_[poffset + 1]) *
312  charge_mod +
313  tdcChargeDrainParameterisation_[poffset + 2]);
314 
315  const int newBusyBxs = std::floor(newIntegTime / 25.f) + 1;
316 
317  //if no update is needed regarding the number of bunches,
318  //then the ToT integration time has converged
319  integTime = newIntegTime;
320  if (newBusyBxs == busyBxs)
321  break;
322 
323  //update charge integrated during ToT
324  if (debug) {
325  if (busyBxs == 0)
326  edm::LogVerbatim("HGCFE") << "\t Intial busy estimate=" << integTime << " ns = " << newBusyBxs << " bxs"
327  << std::endl;
328  else
329  edm::LogVerbatim("HGCFE") << "\t ...integrated charge overflows initial busy estimate, interating again"
330  << std::endl;
331  }
332 
333  //update number of busy bunches
334  busyBxs = newBusyBxs;
335 
336  //reset charge to be integrated
337  totalCharge = charge;
338  if (toaMode_ == WEIGHTEDBYE)
339  finalToA = toa * charge;
340 
341  //add leakage from previous bunches in SARS ADC mode
342  for (int jt = 0; jt < it; ++jt) {
343  const unsigned int deltaT = (it - jt);
344  if ((deltaT + 2) >= adcPulse.size() || chargeColl[jt] == 0.f || totFlags[jt] || busyFlags[jt])
345  continue;
346 
347  const float leakCharge = chargeColl[jt] * adcPulse[deltaT + 2];
348  totalCharge += leakCharge;
349  if (toaMode_ == WEIGHTEDBYE)
350  finalToA += leakCharge * pulseAvgT_[deltaT + 2];
351 
352  if (debug)
353  edm::LogVerbatim("HGCFE") << "\t\t leaking " << chargeColl[jt] << " fC @ deltaT=-" << deltaT << " -> +"
354  << leakCharge << " with avgT=" << pulseAvgT_[deltaT + 2] << std::endl;
355  }
356 
357  //add contamination from posterior bunches
358  for (int jt = it + 1; jt < it + busyBxs && jt < dataFrame.size(); ++jt) {
359  //this charge will be integrated in TDC mode
360  //disable for SARS ADC
361  busyFlags[jt] = true;
362 
363  const float extraCharge = chargeColl[jt];
364  if (extraCharge == 0.f)
365  continue;
366  if (debug)
367  edm::LogVerbatim("HGCFE") << "\t\t adding " << extraCharge << " fC @ deltaT=+" << (jt - it) << std::endl;
368 
369  totalCharge += extraCharge;
370  if (toaMode_ == WEIGHTEDBYE)
371  finalToA += extraCharge * toaColl[jt];
372  }
373 
374  //finalize ToA contamination
375  if (toaMode_ == WEIGHTEDBYE)
376  finalToA /= totalCharge;
377  }
378 
379  newCharge[it] = (totalCharge - tdcOnset);
380 
381  if (debug)
382  edm::LogVerbatim("HGCFE") << "\t Final busy estimate=" << integTime << " ns = " << busyBxs << " bxs" << std::endl
383  << "\t Total integrated=" << totalCharge << " fC <toa>=" << toaFromToT[it]
384  << " (raw=" << finalToA << ") ns " << std::endl;
385 
386  //last fC (tdcOnset) are dissipated trough pulse
387  if (it + busyBxs < (int)(newCharge.size())) {
388  const float deltaT2nextBx((busyBxs * 25 - integTime));
389  const float tdcOnsetLeakage(tdcOnset * vdt::fast_expf(-deltaT2nextBx / tdcChargeDrainParameterisation_[11]));
390  if (debug)
391  edm::LogVerbatim("HGCFE") << "\t Leaking remainder of TDC onset " << tdcOnset << " fC, to be dissipated in "
392  << deltaT2nextBx << " DeltaT/tau=" << deltaT2nextBx << " / "
393  << tdcChargeDrainParameterisation_[11] << " ns, adds " << tdcOnsetLeakage << " fC @ "
394  << it + busyBxs << " bx (first free bx)" << std::endl;
395  newCharge[it + busyBxs] += tdcOnsetLeakage;
396  }
397  }
398 
399  //including the leakage from bunches in SARS ADC when not declared busy or in ToT
400  auto runChargeSharing = [&]() {
401  int ipulse = 0;
402  for (int it = 0; it < (int)(chargeColl.size()); ++it) {
403  //if busy, charge has been already integrated
404  //if(debug) edm::LogVerbatim("HGCFE") << "\t SARS ADC pulse activated @ " << it << " : ";
405  if (!totFlags[it] & !busyFlags[it]) {
406  const int start = std::max(0, 2 - it);
407  const int stop = std::min((int)adcPulse.size(), (int)newCharge.size() - it + 2);
408  for (ipulse = start; ipulse < stop; ++ipulse) {
409  const int itoffset = it + ipulse - 2;
410  //notice that if the channel is already busy,
411  //it has already been affected by the leakage of the SARS ADC
412  //if(totFlags[itoffset] || busyFlags[itoffset]) continue;
413  if (!totFlags[itoffset] & !busyFlags[itoffset]) {
414  newCharge[itoffset] += chargeColl[it] * adcPulse[ipulse];
415  }
416  //if(debug) edm::LogVerbatim("HGCFE") << " | " << itoffset << " " << chargeColl[it]*adcPulse[ipulse] << "( " << chargeColl[it] << "->";
417  //if(debug) edm::LogVerbatim("HGCFE") << newCharge[itoffset] << ") ";
418  }
419  }
420 
421  if (debug)
422  edm::LogVerbatim("HGCFE") << std::endl;
423  }
424  };
425  runChargeSharing();
426 
427  //For the future need to understand how to deal with toa for out of time signals
428  //and for that should keep track of the BX firing the ToA somewhere (also to restore the use of finalToA)
429  /*
430  float finalToA(0.);
431  for(int it=0; it<(int)(newCharge.size()); it++){
432  if(toaFlags[it]){
433  finalToA = toaFromToT[it];
434  //to avoid +=25 for small negative time taken as 0
435  while(finalToA < -1.e-5) finalToA+=25.f;
436  while(finalToA > 25.f) finalToA-=25.f;
437  toaFromToT[it] = finalToA;
438  }
439  }
440  */
441  //timeToA is already in 0-25ns range by construction
442 
443  //set new ADCs and ToA
444  if (debug)
445  edm::LogVerbatim("HGCFE") << "\t final result : ";
446  for (int it = 0; it < (int)(newCharge.size()); it++) {
447  if (debug)
448  edm::LogVerbatim("HGCFE") << chargeColl[it] << " -> " << newCharge[it] << " ";
449 
450  HGCSample newSample;
451  if (totFlags[it] || busyFlags[it]) {
452  if (totFlags[it]) {
453  //brute force saturation, maybe could to better with an exponential like saturation
454  const float saturatedCharge(std::min(newCharge[it], tdcSaturation_fC_));
455  //working version for in-time PU and signal
456  newSample.set(
457  true, true, gainIdx, (uint16_t)(timeToA / toaLSB_ns_), (uint16_t)(std::floor(saturatedCharge / tdcLSB_fC_)));
458  if (toaFlags[it])
459  newSample.setToAValid(true);
460  } else {
461  newSample.set(false, true, gainIdx, 0, 0);
462  }
463  } else {
464  //brute force saturation, maybe could to better with an exponential like saturation
465  const uint16_t adc = std::floor(std::min(newCharge[it], maxADC) / lsbADC);
466  //working version for in-time PU and signal
467  newSample.set(adc > thrADC, false, gainIdx, (uint16_t)(timeToA / toaLSB_ns_), adc);
468  if (toaFlags[it])
469  newSample.setToAValid(true);
470  }
471  dataFrame.setSample(it, newSample);
472  }
473 
474  if (debug) {
475  std::ostringstream msg;
476  dataFrame.print(msg);
477  edm::LogVerbatim("HGCFE") << msg.str() << std::endl;
478  }
479 }
480 
481 // cause the compiler to generate the appropriate code
483 template class HGCFEElectronics<HGCalDataFrame>;
Definition: start.py:1
Log< level::Info, true > LogVerbatim
T getParameter(std::string const &) const
Definition: ParameterSet.h:303
void runShaperWithToT(DFr &dataFrame, hgc::HGCSimHitData &chargeColl, hgc::HGCSimHitData &toa, CLHEP::HepRandomEngine *engine, uint32_t thrADC, float lsbADC, uint32_t gainIdx, float maxADC, int thickness, float tdcOnsetAuto, const hgc_digi::FEADCPulseShape &adcPulse)
implements pulse shape and switch to time over threshold including deadtime
wrapper for a data word
Definition: HGCSample.h:13
std::array< HGCSimData_t, nSamples > HGCSimHitData
HGCFEElectronics(const edm::ParameterSet &ps)
CTOR.
double f[11][100]
void setToAValid(bool toaFired)
Definition: HGCSample.h:47
#define debug
Definition: HDRShower.cc:19
tuple msg
Definition: mps_check.py:285
void runSimpleShaper(DFr &dataFrame, hgc::HGCSimHitData &chargeColl, uint32_t thrADC, float lsbADC, uint32_t gainIdx, float maxADC, const hgc_digi::FEADCPulseShape &adcPulse)
applies a shape to each time sample and propagates the tails to the subsequent time samples ...
float fast_expf(float x)
void runTrivialShaper(DFr &dataFrame, hgc::HGCSimHitData &chargeColl, uint32_t thrADC, float lsbADC, uint32_t gainIdx, float maxADC)
converts charge to digis without pulse shape
models the behavior of the front-end electronics
void set(bool thr, bool mode, uint16_t gain, uint16_t toa, uint16_t data)
Definition: HGCSample.h:49
Power< A, B >::type pow(const A &a, const B &b)
Definition: Power.h:29
uint16_t *__restrict__ uint16_t const *__restrict__ adc
std::array< float, 6 > FEADCPulseShape