artdaq_demo  3.13.00
ToyHardwareInterface.cc
1 #include "artdaq-demo/Generators/ToyHardwareInterface/ToyHardwareInterface.hh"
2 #define TRACE_NAME "ToyHardwareInterface"
3 #include "artdaq-core-demo/Overlays/FragmentType.hh"
4 #include "artdaq-core-demo/Overlays/ToyFragment.hh"
5 #include "artdaq/DAQdata/Globals.hh"
6 
7 #include "cetlib_except/exception.h"
8 #include "fhiclcpp/ParameterSet.h"
9 
10 #include <unistd.h>
11 #include <cstdlib>
12 #include <iostream>
13 #include <random>
14 #include <thread>
15 
16 // JCF, Mar-17-2016
17 
18 // ToyHardwareInterface is meant to mimic a vendor-provided hardware
19 // API, usable within the the ToySimulator fragment generator. For
20 // purposes of realism, it's a C++03-style API, as opposed to, say, one
21 // based in C++11 capable of taking advantage of smart pointers, etc.
22 
23 ToyHardwareInterface::ToyHardwareInterface(fhicl::ParameterSet const& ps)
24  : taking_data_(false)
25  , change_after_N_seconds_(ps.get<size_t>("change_after_N_seconds", std::numeric_limits<size_t>::max()))
26  , pause_after_N_seconds_(ps.get<size_t>("pause_after_N_seconds", 0))
27  , exception_after_N_seconds_(ps.get<bool>("exception_after_N_seconds", false))
28  , exit_after_N_seconds_(ps.get<bool>("exit_after_N_seconds", false))
29  , abort_after_N_seconds_(ps.get<bool>("abort_after_N_seconds", false))
30  , hang_after_N_seconds_(ps.get<bool>("hang_after_N_seconds", false))
31  , fragment_type_(demo::toFragmentType(ps.get<std::string>("fragment_type")))
32  , maxADCvalue_(static_cast<size_t>(pow(2, NumADCBits()) - 1)) // MUST be after "fragment_type"
33  , distribution_type_(static_cast<DistributionType>(ps.get<int>("distribution_type")))
34  , configured_rates_()
35  , engine_(ps.get<int64_t>("random_seed", 314159))
36  , uniform_distn_(new std::uniform_int_distribution<demo::ToyFragment::adc_t>(0, maxADCvalue_))
37  , gaussian_distn_(new std::normal_distribution<double>(0.5 * maxADCvalue_, 0.1 * maxADCvalue_))
38  , start_time_(fake_time_)
39  , rate_start_time_(fake_time_)
40  , rate_send_calls_(0)
41  , serial_number_((*uniform_distn_)(engine_))
42 {
43  bool planned_disruption = exception_after_N_seconds_ || exit_after_N_seconds_ || abort_after_N_seconds_;
44 
45  if (planned_disruption && change_after_N_seconds_ == std::numeric_limits<size_t>::max())
46  {
47  throw cet::exception("HardwareInterface") << "A FHiCL parameter designed to create a disruption has been " // NOLINT(cert-err60-cpp)
48  "set, so \"change_after_N_seconds\" should be set as well";
49  }
50 
51  if (ps.has_key("nADCcounts") || !ps.has_key("rate_table"))
52  {
53  // OLD Config style
54  auto counts1 = ps.get<size_t>("nADCcounts", 40);
55  auto counts2 = ps.get<size_t>("nADCcounts_after_N_seconds", counts1);
56 
57  auto throttle = ps.get<size_t>("throttle_usecs", 100000);
58  auto between = ps.get<size_t>("usecs_between_sends", 0);
59  auto wait = throttle + between;
60  auto rate = wait > 0 ? 1000000 / wait : 0;
61 
62  RateInfo before;
63  before.size_bytes = counts1 * sizeof(demo::ToyFragment::Header::data_t) + sizeof(demo::ToyFragment::Header);
64  before.rate_hz = rate;
65  before.duration = change_after_N_seconds_ != std::numeric_limits<size_t>::max()
66  ? std::chrono::microseconds(1000000 * change_after_N_seconds_)
67  : std::chrono::microseconds(1000000);
68  configured_rates_.push_back(before);
69 
70  if (change_after_N_seconds_ != std::numeric_limits<size_t>::max())
71  {
72  RateInfo after;
73  after.size_bytes = counts2 * sizeof(demo::ToyFragment::Header::data_t) + sizeof(demo::ToyFragment::Header);
74  after.rate_hz = rate;
75  after.duration = std::chrono::microseconds(1000000 * change_after_N_seconds_);
76  configured_rates_.push_back(after);
77  }
78  }
79  else
80  {
81  // NEW Config style
82  auto fhicl_rates = ps.get<std::vector<fhicl::ParameterSet>>("rate_table");
83 
84  for (auto& pps : fhicl_rates)
85  {
86  RateInfo this_rate;
87  this_rate.size_bytes = pps.get<size_t>("size_bytes");
88  this_rate.rate_hz = pps.get<size_t>("rate_hz");
89  this_rate.duration = std::chrono::microseconds(pps.get<size_t>("duration_us", 1000000));
90  configured_rates_.push_back(this_rate);
91  }
92  }
93 
94  bool first = true;
95  for (auto& rate : configured_rates_)
96  {
97  TLOG(TLVL_INFO) << (first ? "W" : ", then w") << "ill generate " << rate.size_bytes << " B Fragments at " << rate.rate_hz << " Hz for " << rate.duration.count() << " us";
98  first = false;
99  }
100 
101  current_rate_ = configured_rates_.begin();
102 }
103 
104 // JCF, Mar-18-2017
105 
106 // "StartDatataking" is meant to mimic actions one would take when
107 // telling the hardware to start sending data - the uploading of
108 // values to registers, etc.
109 
111 {
112  taking_data_ = true;
113  rate_send_calls_ = 0;
114  current_rate_ = configured_rates_.begin();
115  start_time_ = std::chrono::steady_clock::now();
116  rate_start_time_ = start_time_;
117 }
118 
120 {
121  taking_data_ = false;
122  start_time_ = fake_time_;
123  rate_start_time_ = fake_time_;
124 }
125 
126 void ToyHardwareInterface::FillBuffer(char* buffer, size_t* bytes_read)
127 {
128  TLOG(TLVL_TRACE) << "FillBuffer BEGIN";
129  if (taking_data_)
130  {
131  auto elapsed_secs_since_datataking_start = artdaq::TimeUtils::GetElapsedTime(start_time_);
132  if (elapsed_secs_since_datataking_start < 0) elapsed_secs_since_datataking_start = 0;
133 
134  if (static_cast<size_t>(elapsed_secs_since_datataking_start) >= change_after_N_seconds_)
135  {
136  if (abort_after_N_seconds_)
137  {
138  TLOG(TLVL_ERROR) << "Engineered Abort!";
139  std::abort();
140  }
141  else if (exit_after_N_seconds_)
142  {
143  TLOG(TLVL_ERROR) << "Engineered Exit!";
144  std::exit(1);
145  }
146  else if (exception_after_N_seconds_)
147  {
148  TLOG(TLVL_ERROR) << "Engineered Exception!";
149  throw cet::exception("HardwareInterface") // NOLINT(cert-err60-cpp)
150  << "This is an engineered exception designed for testing purposes";
151  }
152  else if (hang_after_N_seconds_)
153  {
154  TLOG(TLVL_ERROR) << "Pretending that the hardware has hung! Variable name for gdb: hardwareIsHung";
155  volatile bool hardwareIsHung = true;
156  // Pretend the hardware hangs
157  while (hardwareIsHung)
158  {
159  usleep(10000);
160  }
161  }
162 
163  if ((pause_after_N_seconds_ != 0u) && (static_cast<size_t>(elapsed_secs_since_datataking_start) % change_after_N_seconds_ == 0))
164  {
165  TLOG(TLVL_DEBUG + 3) << "pausing " << pause_after_N_seconds_ << " seconds";
166  sleep(pause_after_N_seconds_);
167  TLOG(TLVL_DEBUG + 3) << "resuming after pause of " << pause_after_N_seconds_ << " seconds";
168  }
169  }
170 
171  TLOG(TLVL_DEBUG + 3) << "FillBuffer: Setting bytes_read to " << sizeof(demo::ToyFragment::Header) + bytes_to_nWords_(current_rate_->size_bytes) * sizeof(demo::ToyFragment::Header::data_t);
172  *bytes_read = sizeof(demo::ToyFragment::Header) + bytes_to_nWords_(current_rate_->size_bytes) * sizeof(demo::ToyFragment::Header::data_t);
173  TLOG(TLVL_DEBUG + 3) << "FillBuffer: Making the fake data, starting with the header";
174 
175  // Can't handle a fragment whose size isn't evenly divisible by
176  // the demo::ToyFragment::Header::data_t type size in bytes
177  // std::cout << "Bytes to read: " << *bytes_read << ", sizeof(demo::ToyFragment::Header::data_t): " <<
178  // sizeof(demo::ToyFragment::Header::data_t) << std::endl;
179  assert(*bytes_read % sizeof(demo::ToyFragment::Header::data_t) == 0);
180 
181  auto* header = reinterpret_cast<demo::ToyFragment::Header*>(buffer); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
182 
183  header->event_size = *bytes_read / sizeof(demo::ToyFragment::Header::data_t);
184  header->trigger_number = 99;
185  header->distribution_type = static_cast<uint8_t>(distribution_type_);
186 
187  TLOG(TLVL_DEBUG + 3) << "FillBuffer: Generating nADCcounts ADC values ranging from 0 to max based on the desired distribution";
188 
189  std::function<demo::ToyFragment::adc_t()> generator;
190  demo::ToyFragment::adc_t gen_seed = 0;
191 
192  switch (distribution_type_)
193  {
195  generator = [&]() { return static_cast<demo::ToyFragment::adc_t>((*uniform_distn_)(engine_)); };
196  break;
197 
199  generator = [&]() {
200  do
201  {
202  gen_seed = static_cast<demo::ToyFragment::adc_t>(std::round((*gaussian_distn_)(engine_)));
203  } while (gen_seed > maxADCvalue_);
204  return gen_seed;
205  };
206  break;
207 
209  generator = [&]() {
210  if (++gen_seed > maxADCvalue_)
211  {
212  gen_seed = 0;
213  }
214  return gen_seed;
215  };
216  }
217  break;
218 
220  case DistributionType::uninit2:
221  break;
222 
223  default:
224  throw cet::exception("HardwareInterface") << "Unknown distribution type specified"; // NOLINT(cert-err60-cpp)
225  }
226 
227  if (distribution_type_ != DistributionType::uninitialized && distribution_type_ != DistributionType::uninit2)
228  {
229  TLOG(TLVL_DEBUG + 3) << "FillBuffer: Calling generate_n";
230  std::generate_n(reinterpret_cast<demo::ToyFragment::adc_t*>(reinterpret_cast<demo::ToyFragment::Header*>(buffer) + 1), // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic)
231  bytes_to_nADCs_(current_rate_->size_bytes), generator);
232  }
233  }
234  else
235  {
236  throw cet::exception("ToyHardwareInterface") << "Attempt to call FillBuffer when not sending data"; // NOLINT(cert-err60-cpp)
237  }
238 
239  auto now = std::chrono::steady_clock::now();
240  auto next = next_trigger_time_();
241 
242  if (next > now)
243  {
244  std::this_thread::sleep_until(next_trigger_time_());
245  }
246  ++rate_send_calls_;
247  TLOG(TLVL_TRACE) << "FillBuffer END";
248 }
249 
251 {
252  *buffer = reinterpret_cast<char*>( // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
253  new uint8_t[sizeof(demo::ToyFragment::Header) + maxADCcounts_() * sizeof(demo::ToyFragment::Header::data_t)]);
254 }
255 
256 void ToyHardwareInterface::FreeReadoutBuffer(const char* buffer) { delete[] buffer; }
257 
259 {
260  // Pretend that the "BoardType" is some vendor-defined integer which
261  // differs from the fragment_type_ we want to use as developers (and
262  // which must be between 1 and 224, inclusive) so add an offset
263  return static_cast<int>(fragment_type_) + 1000;
264 }
265 
266 std::chrono::microseconds ToyHardwareInterface::rate_to_delay_(std::size_t hz) { return std::chrono::microseconds(static_cast<int>(1000000.0 / hz)); }
267 
268 std::chrono::steady_clock::time_point ToyHardwareInterface::next_trigger_time_()
269 {
270  auto next_time = rate_start_time_ + (rate_send_calls_ + 1) * rate_to_delay_(current_rate_->rate_hz);
271  if (next_time > rate_start_time_ + current_rate_->duration)
272  {
273  if (++current_rate_ == configured_rates_.end()) current_rate_ = configured_rates_.begin();
274  rate_send_calls_ = 0;
275  rate_start_time_ = next_time;
276  }
277  return next_time;
278 }
279 
280 size_t ToyHardwareInterface::bytes_to_nWords_(size_t bytes)
281 {
282  if (bytes < sizeof(demo::ToyFragment::Header)) return 0;
283  return ceil((bytes - sizeof(demo::ToyFragment::Header)) / static_cast<double>(sizeof(demo::ToyFragment::Header::data_t)));
284 }
285 
286 size_t ToyHardwareInterface::bytes_to_nADCs_(size_t bytes)
287 {
288  if (bytes < sizeof(demo::ToyFragment::Header)) return 0;
289  return ceil((bytes - sizeof(demo::ToyFragment::Header)) / static_cast<double>(sizeof(demo::ToyFragment::adc_t)));
290 }
291 
292 size_t ToyHardwareInterface::maxADCcounts_()
293 {
294  size_t max_bytes = 0;
295  for (auto& rate : configured_rates_)
296  {
297  if (rate.size_bytes > max_bytes) max_bytes = rate.size_bytes;
298  }
299  return bytes_to_nWords_(max_bytes);
300 }
301 
303 {
304  switch (fragment_type_)
305  {
306  case demo::FragmentType::TOY1:
307  return 12;
308  break;
309  case demo::FragmentType::TOY2:
310  return 14;
311  break;
312  default:
313  throw cet::exception("ToyHardwareInterface") << "Unknown board type " << fragment_type_ << " (" // NOLINT(cert-err60-cpp)
314  << demo::fragmentTypeToString(fragment_type_) << ").\n";
315  };
316 }
317 
319 {
320  // Serial number is generated from the uniform distribution on initialization of the class
321  return serial_number_;
322 }
int NumADCBits() const
Get the number of ADC bits used in generating data.
void StartDatataking()
&quot;StartDatataking&quot; is meant to mimic actions one would take when telling the hardware to start sending...
void StopDatataking()
Performs shutdown actions.
void FillBuffer(char *buffer, size_t *bytes_read)
Use configured generator to fill a buffer with data.
DistributionType
Allow for the selection of output distribution.
void FreeReadoutBuffer(const char *buffer)
Release the given buffer to the hardware.
A monotonically-increasing distribution.
ToyHardwareInterface(fhicl::ParameterSet const &ps)
Construct and configure ToyHardwareInterface.
A use-after-free expliot distribution.
void AllocateReadoutBuffer(char **buffer)
Request a buffer from the hardware.
int SerialNumber() const
Gets the serial number of the simulated hardware.
int BoardType() const
Return the &quot;board type&quot; of the simulated hardware.