LDMX Software
HgcrocEmulator.cxx
1
2#include "Tools/HgcrocEmulator.h"
3
4namespace ldmx {
5
7 // settings of readout chip that are the same for all chips
8 // used in actual digitization
9 noise_ = ps.getParameter<bool>("noise");
10 timingJitter_ = ps.getParameter<double>("timingJitter");
11 rateUpSlope_ = ps.getParameter<double>("rateUpSlope");
12 timeUpSlope_ = ps.getParameter<double>("timeUpSlope");
13 rateDnSlope_ = ps.getParameter<double>("rateDnSlope");
14 timeDnSlope_ = ps.getParameter<double>("timeDnSlope");
15 timePeak_ = ps.getParameter<double>("timePeak");
16 clockCycle_ = ps.getParameter<double>("clockCycle");
17 nADCs_ = ps.getParameter<int>("nADCs");
18 iSOI_ = ps.getParameter<int>("iSOI");
19
20 // Time -> clock counts conversion
21 // time [ns] * ( 2^10 / max time in ns ) = clock counts
22 ns_ = 1024. / clockCycle_;
23
24 hit_merge_ns_ = 0.05; // combine at 50 ps level
25
26 // Configure the pulse shape function
28 TF1("pulseFunc",
29 "[0]*((1.0+exp([1]*(-[2]+[3])))*(1.0+exp([5]*(-[6]+[3]))))/"
30 "((1.0+exp([1]*(x-[2]+[3]-[4])))*(1.0+exp([5]*(x-[6]+[3]-[4]))))",
31 0.0, (double)nADCs_ * clockCycle_);
32 pulseFunc_.FixParameter(0, 1.0); // amplitude is set externally
33 pulseFunc_.FixParameter(1, rateUpSlope_);
34 pulseFunc_.FixParameter(2, timeUpSlope_);
35 pulseFunc_.FixParameter(3, timePeak_);
36 pulseFunc_.FixParameter(4, 0); // not using time offset in this way
37 pulseFunc_.FixParameter(5, rateDnSlope_);
38 pulseFunc_.FixParameter(6, timeDnSlope_);
39}
40
41void HgcrocEmulator::seedGenerator(uint64_t seed) {
42 noiseInjector_ = std::make_unique<TRandom3>(seed);
43}
44
46 const int &channelID,
47 std::vector<std::pair<double, double>> &arriving_pulses,
48 std::vector<ldmx::HgcrocDigiCollection::Sample> &digiToAdd) const {
49 // step 0: prepare ourselves for emulation
50
51 digiToAdd.clear(); // make sure it is clean
52
53 // Configure chip settings based off of table (that may have been passed)
54 double totMax = getCondition(channelID, "TOT_MAX");
55 double padCapacitance = getCondition(channelID, "PAD_CAPACITANCE");
56 double gain = this->gain(channelID);
57 double pedestal = this->pedestal(channelID);
58 double toaThreshold = getCondition(channelID, "TOA_THRESHOLD");
59 double totThreshold = getCondition(channelID, "TOT_THRESHOLD");
60 // measTime defines the point in the BX where an in-time
61 // (time=0 in times vector) hit would arrive.
62 // Used to determine BX boundaries and TOA behavior.
63 double measTime = getCondition(channelID, "MEAS_TIME");
64 double drainRate = getCondition(channelID, "DRAIN_RATE");
65 double readoutThresholdFloat = this->readoutThreshold(channelID);
66 int readoutThreshold = int(readoutThresholdFloat);
67
68 // sort by amplitude
69 // ==> makes sure that puleses are merged towards higher ones
70 std::sort(
71 arriving_pulses.begin(), arriving_pulses.end(),
72 [](const std::pair<double, double> &a,
73 const std::pair<double, double> &b) { return a.first > b.first; });
74
75 // step 1: gather voltages into groups separated by (programmable) ns, single
76 // pass
78
79 for (auto hit : arriving_pulses) pulse.addOrMerge(hit, hit_merge_ns_);
80
81 // TODO step 2: add timing jitter
82 // if (noise_) pulse.jitter();
83
85
86 // step 3: go through each BX sample one by one
87 bool wasTOA = false;
88 for (int iADC = 0; iADC < nADCs_; iADC++) {
89 double startBX = (iADC - iSOI_) * clockCycle_ - measTime;
90
91 // step 3b: check each merged hit to see if it peaks in this BX. If so,
92 // check its peak time to see if it's over TOT or TOA.
93 bool startTOT = false;
94 bool overTOA = false;
95 double toverTOA = -1;
96 double toverTOT = -1;
97 for (auto hit : pulse.hits()) {
98 int hitBX = int((hit.second + measTime) / clockCycle_ + iSOI_);
99 if (hitBX != iADC)
100 continue; // if this hit wasn't in the current BX, continue...
101
102 double vpeak = pulse(hit.second);
103
104 if (vpeak > totThreshold) {
105 startTOT = true;
106 if (toverTOT < hit.second)
107 toverTOT = hit.second; // use the latest time in the window
108 }
109
110 if (vpeak > toaThreshold) {
111 if (!overTOA || hit.second < toverTOA) toverTOA = hit.second;
112 overTOA = true;
113 }
114
115 } // loop over sim hits
116
117 // check for the case of a TOA even though the peak is in the next BX
118 if (!overTOA && pulse(startBX + clockCycle_) > toaThreshold) {
119 if (pulse(startBX) < toaThreshold) {
120 // pulse crossed TOA threshold somewhere between the start of this
121 // basket and the end
122 overTOA = true;
123 toverTOA = startBX + clockCycle_;
124 }
125 }
126
127 if (startTOT) {
128 // above TOT threshold -> do TOT readout mode
129
130 // @TODO NO NOISE
131 // CompositePulse includes pedestal, we need to remove it
132 // when calculating the charge deposited.
133 double charge_deposited =
134 (pulse(toverTOT) - gain * pedestal) * padCapacitance;
135
136 // Measure Time Over Threshold (TOT) by using the drain rate.
137 // 1. Use drain rate to see how long it takes for the charge to drain off
138 // 2. Translate this into DIGI samples
139
140 // Assume linear drain with slope drain rate:
141 // y-intercept = pulse amplitude
142 // slope = drain rate
143 // ==> x-intercept = amplitude / rate
144 // actual time over threshold using the real signal voltage amplitude
145 double tot = charge_deposited / drainRate;
146
147 // calculate the TDC counts for this tot measurement
148 // internally, the chip uses 12 bits (2^12 = 4096)
149 // to measure a maximum of tot Max [ns]
150 int tdc_counts = int(tot * 4096 / totMax) + pedestal;
151
152 // were we already over TOA? TOT is reported in BX where TOA went over
153 // threshold...
154 int toa{0};
155 if (wasTOA) {
156 // TOA was in the past
157 toa = digiToAdd.back().toa();
158 } else {
159 // TOA is here and we need to find it
160 double timecross = pulse.findCrossing(startBX, toverTOT, toaThreshold);
161 toa = int((timecross - startBX) * ns_);
162 // keep inside valid limits
163 if (toa == 0) toa = 1;
164 if (toa > 1023) toa = 1023;
165 }
166
167 digiToAdd.emplace_back(
168 false, true, // mark as a TOT measurement
169 (iADC > 0) ? digiToAdd.at(iADC - 1).adc_t()
170 : pedestal, // ADC t-1 is first measurement
171 tdc_counts, // TOT
172 toa // TOA is third measurement
173 );
174
175 // TODO: properly handle saturation and recovery, eventually.
176 // Now just kill everything...
177 while (digiToAdd.size() < nADCs_) {
178 digiToAdd.emplace_back(true, false, // flags to mark type of sample
179 0x3FF, 0x3FF, 0);
180 }
181
182 return true; // always readout
183 } else {
184 // determine the voltage at the sampling time
185 double bxvolts = pulse((iADC - iSOI_) * clockCycle_);
186 // add noise if requested
187 if (noise_) bxvolts += noise(channelID);
188 // convert to integer and keep in range (handle low and high saturation)
189 int adc = bxvolts / gain;
190 if (adc < 0) adc = 0;
191 if (adc > 1023) adc = 1023;
192
193 // check for TOA
194 int toa(0);
195 if (pulse(startBX) < toaThreshold && overTOA) {
196 double timecross = pulse.findCrossing(startBX, toverTOA, toaThreshold);
197 toa = int((timecross - startBX) * ns_);
198 // keep inside valid limits
199 if (toa == 0) toa = 1;
200 if (toa > 1023) toa = 1023;
201 wasTOA = true;
202 } else {
203 wasTOA = false;
204 }
205
206 digiToAdd.emplace_back(
207 false, false, // use flags to mark this sample as an ADC measurement
208 (iADC > 0) ? digiToAdd.at(iADC - 1).adc_t()
209 : pedestal, // ADC t-1 is first measurement
210 adc, // ADC[t] is the second field
211 toa // TOA is third measurement
212 );
213 } // TOT or ADC Mode
214 } // sampling baskets
215
216 // we only get here if we never went into TOT mode
217 // check the SOI to see if we should read out
218 return digiToAdd.at(iSOI_).adc_t() >= readoutThreshold;
219} // HgcrocEmulator::digitize
220
221std::vector<ldmx::HgcrocDigiCollection::Sample> HgcrocEmulator::noiseDigi(
222 const int &channel, const double &soi_amplitude) const {
223 // get chip conditions from emulator
224 double pedestal{this->pedestal(channel)};
225 double gain{this->gain(channel)};
226 // fill a digi with noise samples
227 std::vector<ldmx::HgcrocDigiCollection::Sample> noise_digi;
228 for (int iADC{0}; iADC < nADCs_; iADC++) {
229 // gen noise for ADC samples
230 int adc_tm1{static_cast<int>(pedestal)};
231 if (iADC > 0)
232 adc_tm1 = noise_digi.at(iADC - 1).adc_t();
233 else
234 adc_tm1 += noise(channel) / gain;
235 int adc_t{static_cast<int>(pedestal + noise(channel) / gain)};
236
237 if (iADC == iSOI_) adc_t += soi_amplitude / gain;
238
239 // set toa to 0 (not determined)
240 // put new sample into noise digi
241 noise_digi.emplace_back(false, false, adc_tm1, adc_t, 0);
242 } // samples in noise digi
243 return noise_digi;
244}
245
246} // namespace ldmx
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:27
T getParameter(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:89
const std::vector< std::pair< double, double > > & hits() const
Get list of individual pulses that are entering the chip.
double findCrossing(double low, double high, double level, double prec=0.01)
Find the time at which we cross the input level.
void addOrMerge(const std::pair< double, double > &hit, double hit_merge_ns)
Put another hit into this composite pulse.
double readoutThreshold(const int &id) const
Readout Threshold (ADC Counts)
double rateUpSlope_
Rate of Up Slope in Pulse Shape [1/ns].
void seedGenerator(uint64_t seed)
Seed the emulator for random number generation.
double noise(const int &channelID) const
Get random noise amplitdue for input channel [mV].
double pedestal(const int &id) const
Pedestal [ADC Counts] for input channel.
double getCondition(int id, const std::string &name) const
Get condition for input chip ID, condition name, and default value.
double hit_merge_ns_
Hit merging time [ns].
double timeUpSlope_
Time of Up Slope relative to Pulse Shape Fit [ns].
double timePeak_
Time of Peak relative to pulse shape fit [ns].
int iSOI_
Index for the Sample Of Interest in the list of digi samples.
bool digitize(const int &channelID, std::vector< std::pair< double, double > > &arriving_pulses, std::vector< ldmx::HgcrocDigiCollection::Sample > &digiToAdd) const
Digitize the signals from the simulated hits.
std::unique_ptr< TRandom3 > noiseInjector_
Generates Gaussian noise on top of real hits.
double gain(const int &channelID) const
Gain for input channel.
double timingJitter_
Jitter of timing mechanism in the chip [ns].
int nADCs_
Depth of ADC buffer.
bool noise_
Put noise in channels, only configure to false if testing.
double timeDnSlope_
Time of Down Slope relative to Pulse Shape Fit [ns].
TF1 pulseFunc_
Functional shape of signal pulse in time.
double clockCycle_
Time interval for chip clock [ns].
std::vector< ldmx::HgcrocDigiCollection::Sample > noiseDigi(const int &channel, const double &soi_amplitude=0) const
Generate a digi of pure noise.
double rateDnSlope_
Rate of Down Slope in Pulse Shape [1/ns].
HgcrocEmulator(const framework::config::Parameters &ps)
Constructor.
double ns_
Conversion from time [ns] to counts.