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