SatCat5
sim_utils.cc
1 // Copyright 2021-2025 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include <cmath>
7 #include <cstdio>
8 #include <ctime>
9 #include <map>
10 #include <hal_test/catch.hpp>
11 #include <hal_test/sim_cfgbus.h>
12 #include <hal_test/sim_utils.h>
13 #include <satcat5/io_core.h>
14 #include <satcat5/log.h>
15 #include <satcat5/utils.h>
16 
28 
29 // Global PRNG using the Catch2 framework.
30 static Catch::SimplePcg32 global_prng = Catch::rng();
31 
33  // Set a consistent seed for unit-testing purposes.
34  global_prng.seed(0xED743CC4u);
35  return true;
36 }
37 
39  return u8(global_prng());
40 }
41 
43  return global_prng();
44 }
45 
47  u64 msb = rand_u32(), lsb = rand_u32();
48  return (msb << 32) | lsb;
49 }
50 
51 std::string satcat5::test::sim_filename(const char* pre, const char* ext) {
52  // Persistent counter lookup for each unique prefix.
53  static std::map<std::string, unsigned> counts;
54  if (counts.find(pre) == counts.end()) counts[pre] = 0;
55  unsigned idx = counts[pre]++;
56  // Construct the filename.
57  char buff[256];
58  snprintf(buff, sizeof(buff), "simulations/%s_%03u.%s", pre, idx, ext);
59  return std::string(buff);
60 }
61 
62 bool satcat5::test::write(Writeable* dst, unsigned nbytes, const u8* data) {
63  dst->write_bytes(nbytes, data);
64  return dst->write_finalize();
65 }
66 
67 bool satcat5::test::write(Writeable* dst, const std::string& dat) {
68  dst->write_bytes(dat.length(), dat.c_str());
69  return dst->write_finalize();
70 }
71 
72 bool satcat5::test::read(Readable* src, unsigned nbytes, const u8* data) {
73  // Sanity check: Null source only matches a null string.
74  if (!src && nbytes) {
75  log::Log(log::ERROR, "Unexpected null source.");
76  return false;
77  } else if (!src) {
78  return true;
79  }
80 
81  // Even if the lengths don't match, compare as much as we can.
82  unsigned rcvd = src->get_read_ready(), match = 0;
83  for (unsigned a = 0 ; a < nbytes && src->get_read_ready() ; ++a) {
84  u8 next = src->read_u8();
85  if (next == data[a]) {
86  ++match;
87  } else {
88  log::Log(log::ERROR, "String mismatch @ index")
89  .write10(a).write(next).write(data[a]);
90  }
91  }
92 
93  // End-of-frame cleanup.
94  src->read_finalize();
95 
96  // Check for leftover bytes in either direction.
97  if (rcvd > nbytes) {
98  log::Log(log::ERROR, "Unexpected trailing bytes").write10(rcvd - nbytes);
99  return false;
100  } else if (rcvd < nbytes) {
101  log::Log(log::ERROR, "Missing expected bytes").write10(nbytes - rcvd);
102  return false;
103  } else {
104  return (match == nbytes);
105  }
106 }
107 
108 bool satcat5::test::read(Readable* src, const std::string& ref) {
109  return satcat5::test::read(src, ref.size(), (const u8*)ref.c_str());
110 }
111 
112 bool satcat5::test::read(satcat5::io::ArrayRead src, const std::string& ref) {
113  return satcat5::test::read(&src, ref);
114 }
115 
116 void satcat5::test::write_random_bytes(Writeable* dst, unsigned nbytes) {
117  for (unsigned a = 0 ; a < nbytes ; ++a)
118  dst->write_u8(rand_u8());
119 }
120 
121 bool satcat5::test::write_random_final(Writeable* dst, unsigned nbytes) {
122  write_random_bytes(dst, nbytes);
123  return dst->write_finalize();
124 }
125 
127  // Read from both sources until the end.
128  const unsigned PRINT_LIMIT = 100;
129  unsigned diff = 0;
130  for (unsigned a = 0 ; src1->get_read_ready() && src2->get_read_ready() ; ++a) {
131  u8 x = src1->read_u8(), y = src2->read_u8();
132  if (x != y) {
133  if (++diff <= PRINT_LIMIT) {
134  log::Log(log::ERROR, "Stream mismatch @ index")
135  .write10(a).write(x).write(y);
136  }
137  }
138  }
139 
140  if (diff > PRINT_LIMIT) {
141  log::Log(log::ERROR, "Stream mismatch truncated, additional errors")
142  .write10(u32(diff - PRINT_LIMIT));
143  }
144 
145  // Any leftover bytes in either sources?
146  unsigned trail = src1->get_read_ready() + src2->get_read_ready();
147  if (trail > 0) {
148  log::Log(log::ERROR, "Unexpected trailing bytes").write10(trail);
149  }
150 
151  // Cleanup before returning the result.
152  src1->read_finalize();
153  src2->read_finalize();
154  return (diff == 0) && (trail == 0);
155 }
156 
157 CborParser::CborParser(Readable* src, bool verbose)
158  : m_len(src->get_read_ready())
159 {
160  REQUIRE(m_len > 0);
161  REQUIRE(m_len <= sizeof(m_dat));
162  src->read_bytes(m_len, m_dat);
163  src->read_finalize();
164  if (verbose) {
165  log::Log(log::DEBUG, "Raw CBOR").write(m_dat, m_len);
166  }
167 }
168 
169 #if SATCAT5_CBOR_ENABLE
170 
171 // A null item for indicating decoder errors.
172 static const QCBORItem ITEM_ERROR = {
173  QCBOR_TYPE_NONE, // Type (value)
174  QCBOR_TYPE_NONE, // Type (label)
175  0, 0, 0, 0, // Metadata
176  {.int64=0}, // Value
177  {.int64=0}, // Label
178  0, // Tags
179 };
180 
181 QCBORItem CborParser::get(u32 key_req) const {
182  // Open a QCBOR parser object.
183  QCBORDecodeContext cbor;
184  QCBORDecode_Init(&cbor, {m_dat, m_len}, QCBOR_DECODE_MODE_NORMAL);
185 
186  // First item should be the top-level dictionary.
187  QCBORItem item;
188  int errcode = QCBORDecode_GetNext(&cbor, &item);
189  if (errcode || item.uDataType != QCBOR_TYPE_MAP) return ITEM_ERROR;
190 
191  // Read key/value pairs until we find the desired key.
192  // Or, if no match is found, return ITEM_ERROR.
193  // (Iterating over the entire dictionary each time is inefficient
194  // but simple, and we don't need high performance for unit tests.)
195  u32 key_rcvd = 0;
196  while (1) {
197  errcode = QCBORDecode_GetNext(&cbor, &item); // Read key + value
198  if (errcode) return ITEM_ERROR;
199  if (item.uNestingLevel > 1) continue;
200  if (item.uLabelType == QCBOR_TYPE_INT64) {
201  errcode = QCBOR_Int64ToUInt32(item.label.int64, &key_rcvd);
202  if (errcode) return ITEM_ERROR;
203  if (key_req == key_rcvd) return item; // Key match?
204  }
205  }
206 }
207 
208 QCBORItem CborParser::get(const char* key_req) const {
209  // Convert key to a UsefulBuf object.
210  UsefulBufC key_buf = UsefulBuf_FromSZ(key_req);
211 
212  // Open a QCBOR parser object.
213  QCBORDecodeContext cbor;
214  QCBORDecode_Init(&cbor, {m_dat, m_len}, QCBOR_DECODE_MODE_NORMAL);
215 
216  // First item should be the top-level dictionary.
217  QCBORItem item;
218  int errcode = QCBORDecode_GetNext(&cbor, &item);
219  if (errcode || item.uDataType != QCBOR_TYPE_MAP) return ITEM_ERROR;
220 
221  // Read key/value pairs until we find the desired key.
222  // Or, if no match is found, return ITEM_ERROR.
223  // (Iterating over the entire dictionary each time is inefficient
224  // but simple, and we don't need high performance for unit tests.)
225  while (1) {
226  errcode = QCBORDecode_GetNext(&cbor, &item); // Read key + value
227  if (errcode) return ITEM_ERROR;
228  if (item.uNestingLevel > 1) continue;
229  if (item.uLabelType == QCBOR_TYPE_BYTE_STRING ||
230  item.uLabelType == QCBOR_TYPE_TEXT_STRING) {
231  int diff = UsefulBuf_Compare(key_buf, item.label.string);
232  if (diff == 0) return item; // Key match?
233  }
234  }
235 }
236 
237 #endif // SATCAT5_CBOR_ENABLE
238 
240  satcat5::eth::Dispatch* dispatch,
241  const satcat5::eth::MacType& ethertype)
242  : satcat5::eth::Protocol(dispatch, ethertype)
243 {
244  // Nothing else to initialize.
245 }
246 
248  satcat5::log::Log(satcat5::log::INFO, "Frame received")
249  .write(m_etype.value).write(", Len")
250  .write10((u16)src.get_read_ready());
251 }
252 
254  : satcat5::cfg::ConfigBusMmap(m_regs, satcat5::irq::IRQ_NONE)
255 {
256  clear_all();
257 }
258 
260  for (unsigned a = 0 ; a < cfg::MAX_DEVICES ; ++a)
261  clear_dev(a, val);
262 }
263 
264 void MockConfigBusMmap::clear_dev(unsigned devaddr, u32 val) {
265  u32* dev = m_regs + devaddr * satcat5::cfg::REGS_PER_DEVICE;
266  for (unsigned a = 0 ; a < cfg::REGS_PER_DEVICE ; ++a)
267  dev[a] = val;
268 }
269 
272 }
273 
275  : satcat5::cfg::Interrupt(cfg)
276  , m_cfg(cfg)
277  , m_regaddr(0)
278 {
279  // Nothing else to initialize.
280 }
281 
282 static constexpr u32 MOCK_IRQ_ENABLE = (1u << 0);
283 static constexpr u32 MOCK_IRQ_REQUEST = (1u << 1);
284 
286  : satcat5::cfg::Interrupt(cfg, 0, regaddr)
287  , m_cfg(cfg)
288  , m_regaddr(regaddr)
289 {
290  // Nothing else to initialize.
291 }
292 
294  u32 rdval;
295  if (m_regaddr) {
296  // Register mode -> Always set request bit, fire only if enabled.
297  m_cfg->read(m_regaddr, rdval);
298  m_cfg->write(m_regaddr, rdval | MOCK_IRQ_REQUEST);
299  if (rdval & MOCK_IRQ_ENABLE) m_cfg->irq_poll();
300  } else {
301  // No-register mode -> Always fire as if enabled.
302  m_cfg->irq_poll();
303  }
304 }
305 
307  : HeapAllocator(len)
308  , ArrayRead(m_buffptr, len)
309  , m_len(len)
310 {
311  for (unsigned a = 0 ; a < len ; ++a)
312  m_buffptr[a] = (u8)rand_u32();
313 }
314 
316  read_reset(m_len);
317  return this;
318 }
319 
321  : m_count(0)
322  , m_sum(0.0)
323  , m_sumsq(0.0)
324  , m_min(0.0)
325  , m_max(0.0)
326 {
327  // Nothing else to initialize.
328 }
329 
330 void Statistics::add(double x) {
331  if ((m_count == 0) || (x < m_min)) m_min = x;
332  if ((m_count == 0) || (x > m_max)) m_max = x;
333  ++m_count;
334  m_sum += x;
335  m_sumsq += x*x;
336 }
337 
338 double Statistics::mean() const
339  { return m_sum / m_count; }
340 double Statistics::msq() const
341  { return m_sumsq / m_count; }
342 double Statistics::rms() const
343  { return sqrt(msq()); }
344 double Statistics::std() const
345  { return sqrt(var()); }
346 double Statistics::var() const
347  { return msq() - mean()*mean(); }
348 double Statistics::min() const
349  { return m_min; }
350 double Statistics::max() const
351  { return m_max; }
352 
353 TimerSimulation::TimerSimulation()
354  : TimeRef(1000000), m_tref(0), m_tnow(0)
355 {
356  // Always use this simulation clock as the reference.
357  timekeeper.set_clock(this);
358 }
359 
360 TimerSimulation::~TimerSimulation() {
361  // Cleanup links established in the constructor.
362  timekeeper.set_clock(0);
363 }
364 
366  // Each call to raw() increments a few microseconds, to avoid
367  // stalling functions like busywait_usec().
368  m_tnow += 5;
369  // If a full millisecond has elapsed, notify the timekeeper.
370  if (m_tnow - m_tref >= 1000) {
371  m_tref = m_tnow;
372  timekeeper.request_poll();
373  }
374  return m_tnow;
375 }
376 
378  // Confirm this clock is still the timekeeping reference.
379  if (timekeeper.get_clock() != this)
380  timekeeper.set_clock(this);
381  // Step time forward to the next millisecond boundary.
382  m_tnow += 1000 - (m_tnow % 1000);
383  m_tref = m_tnow;
384  // Notify timekeeper that at least one millisecond has elapsed.
385  timekeeper.request_poll();
386 }
387 
388 void TimerSimulation::sim_wait(unsigned dly_msec) {
389  // Sanity check before we start...
390  if (dly_msec > 10000000)
391  log::Log(log::WARNING, "Excessive delay request").write10(dly_msec);
392  for (unsigned a = 0 ; a < dly_msec ; ++a) {
393  sim_step();
394  satcat5::poll::service_all();
395  }
396 }
Generic ConfigBus API.
Definition: cfgbus_core.h:124
virtual satcat5::cfg::IoStatus read(unsigned regaddr, u32 &rdval)=0
Basic single-register read operation.
void irq_poll()
Poll all registered ConfigBus interrupt handlers.
Definition: cfgbus_core.cc:111
virtual satcat5::cfg::IoStatus write(unsigned regaddr, u32 wrval)=0
Basic single-register write operation.
void irq_event() override
Method called whenever an interrupt is triggered.
Definition: cfgbus_core.cc:193
Implemention of "net::Dispatch" for Ethernet frames.
Definition: eth_dispatch.h:21
Ephemeral Readable interface for a simple array.
Definition: io_readable.h:206
void read_reset(unsigned len)
Reset read position to the start of the backing array, and set the readable length to the specified v...
Definition: io_readable.cc:277
Limited read of next N bytes.
Definition: io_readable.h:255
unsigned get_read_ready() const override
How many bytes can be read without blocking?
Definition: io_readable.cc:293
Abstract API for reading byte-streams and packets.
Definition: io_readable.h:68
u8 read_u8()
One of many functions for reading integer/floating point values, see details.
Definition: io_readable.cc:44
virtual bool read_bytes(unsigned nbytes, void *dst)
Read 0 or more bytes into a buffer.
Definition: io_readable.cc:190
virtual void read_finalize()
Consume any remaining bytes in this frame, if applicable.
Definition: io_readable.cc:270
virtual unsigned get_read_ready() const =0
How many bytes can be read without blocking?
Abstract API for writing byte-streams and packets.
Definition: io_writeable.h:24
virtual void write_bytes(unsigned nbytes, const void *src)
Write 0 or more bytes from a buffer.
void write_u8(u8 data)
One of many functions for writing integer/floating point values, see details.
Definition: io_writeable.cc:20
virtual bool write_finalize()
Mark end of frame and release temporary working data.
The Log class creates and formats one log message.
Definition: log.h:195
Log & write10(s32 val)
Print integer as a decimal value with no leading zeros.
Definition: log.cc:261
Log & write(const char *str)
Formatting methods for various data types.
Definition: log.cc:198
void request_poll()
Call this method to request polling at a later time.
Definition: polling.cc:208
void set_clock(satcat5::util::TimeRef *timer)
Immediately set the system time reference.
Definition: polling.cc:250
satcat5::util::TimeRef * get_clock() const
Get the system time reference, if one is set.
Definition: polling.cc:246
A simple CBOR decoder for use in unit tests.
Definition: sim_utils.h:95
QCBORItem get(const char *key) const
Attempt to fetch top-level QCBOR item for the given key.
Definition: sim_utils.cc:208
Log any received Ethernet traffic of the designated type.
Definition: sim_utils.h:225
LogProtocol(satcat5::eth::Dispatch *dispatch, const satcat5::eth::MacType &ethertype)
Attach this object to an Ethernet interface and set filter.
Definition: sim_utils.cc:239
void frame_rcvd(satcat5::io::LimitedRead &src) override
Dispatch calls frame_rcvd(...) for each incoming frame with with a matching net::Type value.
Definition: sim_utils.cc:247
Mockup for a memory-mapped ConfigBus.
Definition: sim_utils.h:238
void clear_all(u32 val=0)
Clear all registers for all devices.
Definition: sim_utils.cc:259
u32 m_regs[satcat5::cfg::MAX_TOTAL_REGS]
Simulated register-map for up to 256 devices.
Definition: sim_utils.h:251
void clear_dev(unsigned devaddr, u32 val=0)
Clear all registers for the specified device-ID.
Definition: sim_utils.cc:264
void irq_event()
Make event-handler accessible (normally private)
Definition: sim_utils.cc:270
MockConfigBusMmap()
Create this mock interface.
Definition: sim_utils.cc:253
Mockup for a ConfigBus interrupt register.
Definition: sim_utils.h:255
void fire()
Trigger a virtual interrupt.
Definition: sim_utils.cc:293
MockInterrupt(satcat5::cfg::ConfigBus *cfg)
No associated register, assumes interrupt has fired.
Definition: sim_utils.cc:274
Readable source for a pseudorandom block of data.
Definition: sim_utils.h:276
RandomSource(unsigned len)
Generate a pseudorandom block of data.
Definition: sim_utils.cc:306
satcat5::io::Readable * read()
Prepare to read or re-read data from start of block.
Definition: sim_utils.cc:315
unsigned len() const
Length of the internal block.
Definition: sim_utils.h:283
Measure various statistics of a discrete-time series.
Definition: sim_utils.h:293
double m_max
Running maximum.
Definition: sim_utils.h:309
double rms() const
Root-mean-square.
Definition: sim_utils.cc:342
double var() const
Variance.
Definition: sim_utils.cc:346
Statistics()
Create an empty dataset.
Definition: sim_utils.cc:320
double std() const
Standard deviation.
Definition: sim_utils.cc:344
void add(double x)
Add a new data point.
Definition: sim_utils.cc:330
double min() const
Minimum over all inputs.
Definition: sim_utils.cc:348
double msq() const
Mean-square.
Definition: sim_utils.cc:340
double m_sumsq
Sum of squares.
Definition: sim_utils.h:307
double max() const
Maximum over all inputs.
Definition: sim_utils.cc:350
double m_sum
Sum of inputs.
Definition: sim_utils.h:306
unsigned m_count
Number of data points.
Definition: sim_utils.h:305
double m_min
Running minimum.
Definition: sim_utils.h:308
double mean() const
Mean of all data points.
Definition: sim_utils.cc:338
Timekeeper object for granular simulation of elapsed time.
Definition: sim_utils.h:313
void sim_step()
Step forward one millisecond.
Definition: sim_utils.cc:377
void sim_wait(unsigned dly_msec)
Step forward N milliseconds.
Definition: sim_utils.cc:388
u32 raw() override
Read current time in arbitrary "ticks".
Definition: sim_utils.cc:365
u8 *const m_buffptr
Pointer to the underlying buffer.
Definition: posix_utils.h:52
I/O interface core definitions.
Diagnostic logging to UART and/or Ethernet ports.
Timekeeper timekeeper
There is a single global instance of the Timekeeper class.
Definition: polling.cc:50
General-purpose ConfigBus emulator utilities.
Miscellaneous simulation and test helper functions.
u8 rand_u8()
Reproducible PRNG used for unit tests.
Definition: sim_utils.cc:38
bool write_random_final(satcat5::io::Writeable *dst, unsigned nbytes)
Write random bytes and finalize.
Definition: sim_utils.cc:121
u32 rand_u32()
Reproducible PRNG used for unit tests.
Definition: sim_utils.cc:42
u64 rand_u64()
Reproducible PRNG used for unit tests.
Definition: sim_utils.cc:46
bool read(satcat5::io::Readable *src, unsigned nbytes, const u8 *data)
Compare next frame to reference array.
Definition: sim_utils.cc:72
bool read_equal(satcat5::io::Readable *src1, satcat5::io::Readable *src2)
Check if two streams are equal.
Definition: sim_utils.cc:126
std::string sim_filename(const char *pre, const char *ext)
Generate a unique filename for storing unit-test results.
Definition: sim_utils.cc:51
bool pre_test_reset()
Reset the global PRNG state used for rand_*(), below.
Definition: sim_utils.cc:32
bool write(satcat5::io::Writeable *dst, unsigned nbytes, const u8 *data)
Write byte array and finalize.
Definition: sim_utils.cc:62
void write_random_bytes(satcat5::io::Writeable *dst, unsigned nbytes)
Write random bytes, without finalizing.
Definition: sim_utils.cc:116
EtherType field (uint16) is used a protocol-ID [1536..65535].
Definition: eth_header.h:96
u16 value
The 16-bit value is stored in processor-native order.
Definition: eth_header.h:98
Timestamp for measuring elapsed time.
Definition: timeref.h:48
Miscellaneous mathematical utility functions.