yowasp-yosys 0.38.0.92.post687.dev0__py3-none-any.whl → 0.39.0.165.post702.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
yowasp_yosys/sby.py CHANGED
@@ -22,6 +22,7 @@ import json, os, sys, shutil, tempfile, re
22
22
  from sby_cmdline import parser_func
23
23
  from sby_core import SbyConfig, SbyTask, SbyAbort, SbyTaskloop, process_filename, dress_message
24
24
  from sby_jobserver import SbyJobClient, process_jobserver_environment
25
+ from sby_status import SbyStatusDb
25
26
  import time, platform, click
26
27
 
27
28
  process_jobserver_environment() # needs to be called early
@@ -55,6 +56,42 @@ autotune_config = args.autotune_config
55
56
  sequential = args.sequential
56
57
  jobcount = args.jobcount
57
58
  init_config_file = args.init_config_file
59
+ status_show = args.status
60
+ status_reset = args.status_reset
61
+
62
+ if status_show or status_reset:
63
+ target = workdir_prefix or workdir or sbyfile
64
+ if target is None:
65
+ print("ERROR: Specify a .sby config file or working directory to use --status.")
66
+ sys.exit(1)
67
+ if not os.path.isdir(target) and target.endswith('.sby'):
68
+ target = target[:-4]
69
+ if not os.path.isdir(target):
70
+ print(f"ERROR: No directory found at {target!r}.", file=sys.stderr)
71
+ sys.exit(1)
72
+
73
+ try:
74
+ with open(f"{target}/status.path", "r") as status_path_file:
75
+ status_path = f"{target}/{status_path_file.read().rstrip()}"
76
+ except FileNotFoundError:
77
+ status_path = f"{target}/status.sqlite"
78
+
79
+ if not os.path.exists(status_path):
80
+ print(f"ERROR: No status database found at {status_path!r}.", file=sys.stderr)
81
+ sys.exit(1)
82
+
83
+ status_db = SbyStatusDb(status_path, task=None)
84
+
85
+ if status_show:
86
+ status_db.print_status_summary()
87
+ sys.exit(0)
88
+
89
+ if status_reset:
90
+ status_db.reset()
91
+
92
+ status_db.db.close()
93
+ sys.exit(0)
94
+
58
95
 
59
96
  if sbyfile is not None:
60
97
  if os.path.isdir(sbyfile):
@@ -260,6 +297,8 @@ def read_sbyconfig(sbydata, taskname):
260
297
 
261
298
 
262
299
  sbydata = list()
300
+ if sbyfile is None:
301
+ print("Reading .sby configuration from stdin:")
263
302
  with (open(sbyfile, "r") if sbyfile is not None else sys.stdin) as f:
264
303
  for line in f:
265
304
  sbydata.append(line)
@@ -356,6 +395,7 @@ def start_task(taskloop, taskname):
356
395
 
357
396
  my_opt_tmpdir = opt_tmpdir
358
397
  my_workdir = None
398
+ my_status_db = None
359
399
 
360
400
  if workdir is not None:
361
401
  my_workdir = workdir
@@ -364,10 +404,12 @@ def start_task(taskloop, taskname):
364
404
  my_workdir = workdir_prefix
365
405
  else:
366
406
  my_workdir = workdir_prefix + "_" + taskname
407
+ my_status_db = f"../{os.path.basename(workdir_prefix)}/status.sqlite"
367
408
 
368
409
  if my_workdir is None and sbyfile is not None and not my_opt_tmpdir:
369
410
  my_workdir = sbyfile[:-4]
370
411
  if taskname is not None:
412
+ my_status_db = f"../{os.path.basename(my_workdir)}/status.sqlite"
371
413
  my_workdir += "_" + taskname
372
414
 
373
415
  if my_workdir is not None:
@@ -399,6 +441,14 @@ def start_task(taskloop, taskname):
399
441
  with open(f"{my_workdir}/.gitignore", "w") as gitignore:
400
442
  print("*", file=gitignore)
401
443
 
444
+ if my_status_db is not None:
445
+ os.makedirs(f"{my_workdir}/{os.path.dirname(my_status_db)}", exist_ok=True)
446
+ if os.getenv("SBY_WORKDIR_GITIGNORE"):
447
+ with open(f"{my_workdir}/{os.path.dirname(my_status_db)}/.gitignore", "w") as gitignore:
448
+ print("*", file=gitignore)
449
+ with open(f"{my_workdir}/status.path", "w") as status_path:
450
+ print(my_status_db, file=status_path)
451
+
402
452
  junit_ts_name = os.path.basename(sbyfile[:-4]) if sbyfile is not None else workdir if workdir is not None else "stdin"
403
453
  junit_tc_name = taskname if taskname is not None else "default"
404
454
 
@@ -625,11 +625,11 @@ struct value : public expr_base<value<Bits>> {
625
625
  value<Bits + 1> remainder;
626
626
  value<Bits + 1> dividend = sext<Bits + 1>();
627
627
  value<Bits + 1> divisor = other.template sext<Bits + 1>();
628
- if (dividend.is_neg()) dividend = dividend.neg();
629
- if (divisor.is_neg()) divisor = divisor.neg();
628
+ if (is_neg()) dividend = dividend.neg();
629
+ if (other.is_neg()) divisor = divisor.neg();
630
630
  std::tie(quotient, remainder) = dividend.udivmod(divisor);
631
- if (dividend.is_neg() != divisor.is_neg()) quotient = quotient.neg();
632
- if (dividend.is_neg()) remainder = remainder.neg();
631
+ if (is_neg() != other.is_neg()) quotient = quotient.neg();
632
+ if (is_neg()) remainder = remainder.neg();
633
633
  return {quotient.template trunc<Bits>(), remainder.template trunc<Bits>()};
634
634
  }
635
635
  };
@@ -1010,22 +1010,24 @@ struct observer {
1010
1010
  // Default member initializers would make this a non-aggregate-type in C++11, so they are commented out.
1011
1011
  struct fmt_part {
1012
1012
  enum {
1013
- STRING = 0,
1013
+ LITERAL = 0,
1014
1014
  INTEGER = 1,
1015
- CHARACTER = 2,
1016
- VLOG_TIME = 3,
1015
+ STRING = 2,
1016
+ UNICHAR = 3,
1017
+ VLOG_TIME = 4,
1017
1018
  } type;
1018
1019
 
1019
- // STRING type
1020
+ // LITERAL type
1020
1021
  std::string str;
1021
1022
 
1022
- // INTEGER/CHARACTER types
1023
+ // INTEGER/STRING/UNICHAR types
1023
1024
  // + value<Bits> val;
1024
1025
 
1025
- // INTEGER/CHARACTER/VLOG_TIME types
1026
+ // INTEGER/STRING/VLOG_TIME types
1026
1027
  enum {
1027
1028
  RIGHT = 0,
1028
1029
  LEFT = 1,
1030
+ NUMERIC = 2,
1029
1031
  } justify; // = RIGHT;
1030
1032
  char padding; // = '\0';
1031
1033
  size_t width; // = 0;
@@ -1033,7 +1035,14 @@ struct fmt_part {
1033
1035
  // INTEGER type
1034
1036
  unsigned base; // = 10;
1035
1037
  bool signed_; // = false;
1036
- bool plus; // = false;
1038
+ enum {
1039
+ MINUS = 0,
1040
+ PLUS_MINUS = 1,
1041
+ SPACE_MINUS = 2,
1042
+ } sign; // = MINUS;
1043
+ bool hex_upper; // = false;
1044
+ bool show_base; // = false;
1045
+ bool group; // = false;
1037
1046
 
1038
1047
  // VLOG_TIME type
1039
1048
  bool realtime; // = false;
@@ -1049,11 +1058,12 @@ struct fmt_part {
1049
1058
  // We might want to replace some of these bit() calls with direct
1050
1059
  // chunk access if it turns out to be slow enough to matter.
1051
1060
  std::string buf;
1061
+ std::string prefix;
1052
1062
  switch (type) {
1053
- case STRING:
1063
+ case LITERAL:
1054
1064
  return str;
1055
1065
 
1056
- case CHARACTER: {
1066
+ case STRING: {
1057
1067
  buf.reserve(Bits/8);
1058
1068
  for (int i = 0; i < Bits; i += 8) {
1059
1069
  char ch = 0;
@@ -1067,35 +1077,76 @@ struct fmt_part {
1067
1077
  break;
1068
1078
  }
1069
1079
 
1080
+ case UNICHAR: {
1081
+ uint32_t codepoint = val.template get<uint32_t>();
1082
+ if (codepoint >= 0x10000)
1083
+ buf += (char)(0xf0 | (codepoint >> 18));
1084
+ else if (codepoint >= 0x800)
1085
+ buf += (char)(0xe0 | (codepoint >> 12));
1086
+ else if (codepoint >= 0x80)
1087
+ buf += (char)(0xc0 | (codepoint >> 6));
1088
+ else
1089
+ buf += (char)codepoint;
1090
+ if (codepoint >= 0x10000)
1091
+ buf += (char)(0x80 | ((codepoint >> 12) & 0x3f));
1092
+ if (codepoint >= 0x800)
1093
+ buf += (char)(0x80 | ((codepoint >> 6) & 0x3f));
1094
+ if (codepoint >= 0x80)
1095
+ buf += (char)(0x80 | ((codepoint >> 0) & 0x3f));
1096
+ break;
1097
+ }
1098
+
1070
1099
  case INTEGER: {
1071
- size_t width = Bits;
1100
+ bool negative = signed_ && val.is_neg();
1101
+ if (negative) {
1102
+ prefix = "-";
1103
+ val = val.neg();
1104
+ } else {
1105
+ switch (sign) {
1106
+ case MINUS: break;
1107
+ case PLUS_MINUS: prefix = "+"; break;
1108
+ case SPACE_MINUS: prefix = " "; break;
1109
+ }
1110
+ }
1111
+
1112
+ size_t val_width = Bits;
1072
1113
  if (base != 10) {
1073
- width = 0;
1114
+ val_width = 1;
1074
1115
  for (size_t index = 0; index < Bits; index++)
1075
1116
  if (val.bit(index))
1076
- width = index + 1;
1117
+ val_width = index + 1;
1077
1118
  }
1078
1119
 
1079
1120
  if (base == 2) {
1080
- for (size_t i = width; i > 0; i--)
1081
- buf += (val.bit(i - 1) ? '1' : '0');
1121
+ if (show_base)
1122
+ prefix += "0b";
1123
+ for (size_t index = 0; index < val_width; index++) {
1124
+ if (group && index > 0 && index % 4 == 0)
1125
+ buf += '_';
1126
+ buf += (val.bit(index) ? '1' : '0');
1127
+ }
1082
1128
  } else if (base == 8 || base == 16) {
1129
+ if (show_base)
1130
+ prefix += (base == 16) ? (hex_upper ? "0X" : "0x") : "0o";
1083
1131
  size_t step = (base == 16) ? 4 : 3;
1084
- for (size_t index = 0; index < width; index += step) {
1132
+ for (size_t index = 0; index < val_width; index += step) {
1133
+ if (group && index > 0 && index % (4 * step) == 0)
1134
+ buf += '_';
1085
1135
  uint8_t value = val.bit(index) | (val.bit(index + 1) << 1) | (val.bit(index + 2) << 2);
1086
1136
  if (step == 4)
1087
1137
  value |= val.bit(index + 3) << 3;
1088
- buf += "0123456789abcdef"[value];
1138
+ buf += (hex_upper ? "0123456789ABCDEF" : "0123456789abcdef")[value];
1089
1139
  }
1090
- std::reverse(buf.begin(), buf.end());
1091
1140
  } else if (base == 10) {
1092
- bool negative = signed_ && val.is_neg();
1093
- if (negative)
1094
- val = val.neg();
1141
+ if (show_base)
1142
+ prefix += "0d";
1095
1143
  if (val.is_zero())
1096
1144
  buf += '0';
1097
1145
  value<(Bits > 4 ? Bits : 4)> xval = val.template zext<(Bits > 4 ? Bits : 4)>();
1146
+ size_t index = 0;
1098
1147
  while (!xval.is_zero()) {
1148
+ if (group && index > 0 && index % 3 == 0)
1149
+ buf += '_';
1099
1150
  value<(Bits > 4 ? Bits : 4)> quotient, remainder;
1100
1151
  if (Bits >= 4)
1101
1152
  std::tie(quotient, remainder) = xval.udivmod(value<(Bits > 4 ? Bits : 4)>{10u});
@@ -1103,11 +1154,18 @@ struct fmt_part {
1103
1154
  std::tie(quotient, remainder) = std::make_pair(value<(Bits > 4 ? Bits : 4)>{0u}, xval);
1104
1155
  buf += '0' + remainder.template trunc<4>().template get<uint8_t>();
1105
1156
  xval = quotient;
1157
+ index++;
1106
1158
  }
1107
- if (negative || plus)
1108
- buf += negative ? '-' : '+';
1109
- std::reverse(buf.begin(), buf.end());
1110
1159
  } else assert(false && "Unsupported base for fmt_part");
1160
+ if (justify == NUMERIC && group && padding == '0') {
1161
+ int group_size = base == 10 ? 3 : 4;
1162
+ while (prefix.size() + buf.size() < width) {
1163
+ if (buf.size() % (group_size + 1) == group_size)
1164
+ buf += '_';
1165
+ buf += '0';
1166
+ }
1167
+ }
1168
+ std::reverse(buf.begin(), buf.end());
1111
1169
  break;
1112
1170
  }
1113
1171
 
@@ -1123,17 +1181,29 @@ struct fmt_part {
1123
1181
 
1124
1182
  std::string str;
1125
1183
  assert(width == 0 || padding != '\0');
1126
- if (justify == RIGHT && buf.size() < width) {
1127
- size_t pad_width = width - buf.size();
1128
- if (padding == '0' && (buf.front() == '+' || buf.front() == '-')) {
1129
- str += buf.front();
1130
- buf.erase(0, 1);
1131
- }
1132
- str += std::string(pad_width, padding);
1184
+ if (prefix.size() + buf.size() < width) {
1185
+ size_t pad_width = width - prefix.size() - buf.size();
1186
+ switch (justify) {
1187
+ case LEFT:
1188
+ str += prefix;
1189
+ str += buf;
1190
+ str += std::string(pad_width, padding);
1191
+ break;
1192
+ case RIGHT:
1193
+ str += std::string(pad_width, padding);
1194
+ str += prefix;
1195
+ str += buf;
1196
+ break;
1197
+ case NUMERIC:
1198
+ str += prefix;
1199
+ str += std::string(pad_width, padding);
1200
+ str += buf;
1201
+ break;
1202
+ }
1203
+ } else {
1204
+ str += prefix;
1205
+ str += buf;
1133
1206
  }
1134
- str += buf;
1135
- if (justify == LEFT && buf.size() < width)
1136
- str += std::string(width - buf.size(), padding);
1137
1207
  return str;
1138
1208
  }
1139
1209
  };
@@ -49,11 +49,16 @@
49
49
  // <file> ::= <file-header> <definitions> <sample>+
50
50
  // <file-header> ::= 0x52585843 0x00004c54
51
51
  // <definitions> ::= <packet-define>* <packet-end>
52
- // <sample> ::= <packet-sample> <packet-change>* <packet-end>
52
+ // <sample> ::= <packet-sample> (<packet-change> | <packet-diag>)* <packet-end>
53
53
  // <packet-define> ::= 0xc0000000 ...
54
54
  // <packet-sample> ::= 0xc0000001 ...
55
55
  // <packet-change> ::= 0x0??????? <chunk>+ | 0x1??????? <index> <chunk>+ | 0x2??????? | 0x3???????
56
56
  // <chunk>, <index> ::= 0x????????
57
+ // <packet-diag> ::= <packet-break> | <packet-print> | <packet-assert> | <packet-assume>
58
+ // <packet-break> ::= 0xc0000010 <message> <source-location>
59
+ // <packet-print> ::= 0xc0000011 <message> <source-location>
60
+ // <packet-assert> ::= 0xc0000012 <message> <source-location>
61
+ // <packet-assume> ::= 0xc0000013 <message> <source-location>
57
62
  // <packet-end> ::= 0xFFFFFFFF
58
63
  //
59
64
  // The replay log contains sample data, however, it does not cover the entire design. Rather, it only contains sample
@@ -61,6 +66,10 @@
61
66
  // a minimum, and recording speed to a maximum. The player samples any missing data by setting the design state items
62
67
  // to the same values they had during recording, and re-evaluating the design.
63
68
  //
69
+ // Packets for diagnostics (prints, breakpoints, assertions, and assumptions) are used solely for diagnostics emitted
70
+ // by the C++ testbench driving the simulation, and are not recorded while evaluating the design. (Diagnostics emitted
71
+ // by the RTL can be reconstructed at replay time, so recording them would be a waste of space.)
72
+ //
64
73
  // Limits
65
74
  // ------
66
75
  //
@@ -109,6 +118,33 @@
109
118
 
110
119
  namespace cxxrtl {
111
120
 
121
+ // A single diagnostic that can be manipulated as an object (including being written to and read from a file).
122
+ // This differs from the base CXXRTL interface, where diagnostics can only be emitted via a procedure call, and are
123
+ // not materialized as objects.
124
+ struct diagnostic {
125
+ // The `BREAK` flavor corresponds to a breakpoint, which is a diagnostic type that can currently only be emitted
126
+ // by the C++ testbench code.
127
+ enum flavor {
128
+ BREAK = 0,
129
+ PRINT = 1,
130
+ ASSERT = 2,
131
+ ASSUME = 3,
132
+ };
133
+
134
+ flavor type;
135
+ std::string message;
136
+ std::string location; // same format as the `src` attribute of `$print` or `$check` cell
137
+
138
+ diagnostic()
139
+ : type(BREAK) {}
140
+
141
+ diagnostic(flavor type, const std::string &message, const std::string &location)
142
+ : type(type), message(message), location(location) {}
143
+
144
+ diagnostic(flavor type, const std::string &message, const char *file, unsigned line)
145
+ : type(type), message(message), location(std::string(file) + ':' + std::to_string(line)) {}
146
+ };
147
+
112
148
  // A spool stores CXXRTL design state changes in a file.
113
149
  class spool {
114
150
  public:
@@ -127,7 +163,7 @@ public:
127
163
 
128
164
  static constexpr uint32_t PACKET_SAMPLE = 0xc0000001;
129
165
  enum sample_flag : uint32_t {
130
- EMPTY = 0,
166
+ EMPTY = 0,
131
167
  INCREMENTAL = 1,
132
168
  };
133
169
 
@@ -139,6 +175,9 @@ public:
139
175
  static constexpr uint32_t PACKET_CHANGEL = 0x20000000/* | ident */;
140
176
  static constexpr uint32_t PACKET_CHANGEH = 0x30000000/* | ident */;
141
177
 
178
+ static constexpr uint32_t PACKET_DIAGNOSTIC = 0xc0000010/* | diagnostic::flavor */;
179
+ static constexpr uint32_t DIAGNOSTIC_MASK = 0x0000000f;
180
+
142
181
  static constexpr uint32_t PACKET_END = 0xffffffff;
143
182
 
144
183
  // Writing spools.
@@ -281,6 +320,12 @@ public:
281
320
  emit_word(data[offset]);
282
321
  }
283
322
 
323
+ void write_diagnostic(const diagnostic &diagnostic) {
324
+ emit_word(PACKET_DIAGNOSTIC | diagnostic.type);
325
+ emit_string(diagnostic.message);
326
+ emit_string(diagnostic.location);
327
+ }
328
+
284
329
  void write_end() {
285
330
  emit_word(PACKET_END);
286
331
  }
@@ -397,11 +442,16 @@ public:
397
442
  return true;
398
443
  }
399
444
 
400
- bool read_change_header(uint32_t &header, ident_t &ident) {
445
+ bool read_header(uint32_t &header) {
401
446
  header = absorb_word();
402
- if (header == PACKET_END)
403
- return false;
404
- assert((header & ~(CHANGE_MASK | MAXIMUM_IDENT)) == 0);
447
+ return header != PACKET_END;
448
+ }
449
+
450
+ // This method must be separate from `read_change_data` because `chunks` and `depth` can only be looked up
451
+ // if `ident` is known.
452
+ bool read_change_ident(uint32_t header, ident_t &ident) {
453
+ if ((header & ~(CHANGE_MASK | MAXIMUM_IDENT)) != 0)
454
+ return false; // some other packet
405
455
  ident = header & MAXIMUM_IDENT;
406
456
  return true;
407
457
  }
@@ -427,6 +477,18 @@ public:
427
477
  for (size_t offset = 0; offset < chunks; offset++)
428
478
  data[chunks * index + offset] = absorb_word();
429
479
  }
480
+
481
+ bool read_diagnostic(uint32_t header, diagnostic &diagnostic) {
482
+ if ((header & ~DIAGNOSTIC_MASK) != PACKET_DIAGNOSTIC)
483
+ return false; // some other packet
484
+ uint32_t type = header & DIAGNOSTIC_MASK;
485
+ assert(type == diagnostic::BREAK || type == diagnostic::PRINT ||
486
+ type == diagnostic::ASSERT || type == diagnostic::ASSUME);
487
+ diagnostic.type = (diagnostic::flavor)type;
488
+ diagnostic.message = absorb_string();
489
+ diagnostic.location = absorb_string();
490
+ return true;
491
+ }
430
492
  };
431
493
 
432
494
  // Opening spools. For certain uses of the record/replay mechanism, two distinct open files (two open files, i.e.
@@ -584,6 +646,18 @@ public:
584
646
  return changed;
585
647
  }
586
648
 
649
+ void record_diagnostic(const diagnostic &diagnostic) {
650
+ assert(streaming);
651
+
652
+ // Emit an incremental delta cycle per diagnostic to simplify the logic of the recorder. This is inefficient, but
653
+ // diagnostics should be rare enough that this inefficiency does not matter. If it turns out to be an issue, this
654
+ // code should be changed to accumulate diagnostics to a buffer that is flushed in `record_{complete,incremental}`
655
+ // and also in `advance_time` before the timestamp is changed. (Right now `advance_time` never writes to the spool.)
656
+ writer.write_sample(/*incremental=*/true, pointer++, timestamp);
657
+ writer.write_diagnostic(diagnostic);
658
+ writer.write_end();
659
+ }
660
+
587
661
  void flush() {
588
662
  writer.flush();
589
663
  }
@@ -657,8 +731,9 @@ public:
657
731
  streaming = true;
658
732
 
659
733
  // Establish the initial state of the design.
660
- initialized = replay();
661
- assert(initialized);
734
+ std::vector<diagnostic> diagnostics;
735
+ initialized = replay(&diagnostics);
736
+ assert(initialized && diagnostics.empty());
662
737
  }
663
738
 
664
739
  // Returns the pointer of the current sample.
@@ -690,8 +765,8 @@ public:
690
765
  // If this function returns `true`, then `current_pointer() == at_pointer`, and the module contains values that
691
766
  // correspond to this pointer in the replay log. To obtain a valid pointer, call `current_pointer()`; while pointers
692
767
  // are monotonically increasing for each consecutive sample, using arithmetic operations to create a new pointer is
693
- // not allowed.
694
- bool rewind_to(spool::pointer_t at_pointer) {
768
+ // not allowed. The `diagnostics` argument, if not `nullptr`, receives the diagnostics recorded in this sample.
769
+ bool rewind_to(spool::pointer_t at_pointer, std::vector<diagnostic> *diagnostics) {
695
770
  assert(initialized);
696
771
 
697
772
  // The pointers in the replay log start from one that is greater than `at_pointer`. In this case the pointer will
@@ -707,9 +782,12 @@ public:
707
782
  reader.rewind(position_it->second);
708
783
 
709
784
  // Replay samples until eventually arriving to `at_pointer` or encountering end of file.
710
- while(replay()) {
785
+ while(replay(diagnostics)) {
711
786
  if (pointer == at_pointer)
712
787
  return true;
788
+
789
+ if (diagnostics)
790
+ diagnostics->clear();
713
791
  }
714
792
  return false;
715
793
  }
@@ -717,8 +795,8 @@ public:
717
795
  // If this function returns `true`, then `current_time() <= at_or_before_timestamp`, and the module contains values
718
796
  // that correspond to `current_time()` in the replay log. If `current_time() == at_or_before_timestamp` and there
719
797
  // are several consecutive samples with the same time, the module contains values that correspond to the first of
720
- // these samples.
721
- bool rewind_to_or_before(const time &at_or_before_timestamp) {
798
+ // these samples. The `diagnostics` argument, if not `nullptr`, receives the diagnostics recorded in this sample.
799
+ bool rewind_to_or_before(const time &at_or_before_timestamp, std::vector<diagnostic> *diagnostics) {
722
800
  assert(initialized);
723
801
 
724
802
  // The timestamps in the replay log start from one that is greater than `at_or_before_timestamp`. In this case
@@ -734,7 +812,7 @@ public:
734
812
  reader.rewind(position_it->second);
735
813
 
736
814
  // Replay samples until eventually arriving to or past `at_or_before_timestamp` or encountering end of file.
737
- while (replay()) {
815
+ while (replay(diagnostics)) {
738
816
  if (timestamp == at_or_before_timestamp)
739
817
  break;
740
818
 
@@ -743,14 +821,17 @@ public:
743
821
  break;
744
822
  if (next_timestamp > at_or_before_timestamp)
745
823
  break;
824
+
825
+ if (diagnostics)
826
+ diagnostics->clear();
746
827
  }
747
828
  return true;
748
829
  }
749
830
 
750
831
  // If this function returns `true`, then `current_pointer()` and `current_time()` are updated for the next sample
751
832
  // and the module now contains values that correspond to that sample. If it returns `false`, there was no next sample
752
- // to read.
753
- bool replay() {
833
+ // to read. The `diagnostics` argument, if not `nullptr`, receives the diagnostics recorded in the next sample.
834
+ bool replay(std::vector<diagnostic> *diagnostics) {
754
835
  assert(streaming);
755
836
 
756
837
  bool incremental;
@@ -771,11 +852,16 @@ public:
771
852
  }
772
853
 
773
854
  uint32_t header;
774
- spool::ident_t ident;
775
- variable var;
776
- while (reader.read_change_header(header, ident)) {
777
- variable &var = variables.at(ident);
778
- reader.read_change_data(header, var.chunks, var.depth, var.curr);
855
+ while (reader.read_header(header)) {
856
+ spool::ident_t ident;
857
+ diagnostic diag;
858
+ if (reader.read_change_ident(header, ident)) {
859
+ variable &var = variables.at(ident);
860
+ reader.read_change_data(header, var.chunks, var.depth, var.curr);
861
+ } else if (reader.read_diagnostic(header, diag)) {
862
+ if (diagnostics)
863
+ diagnostics->push_back(diag);
864
+ } else assert(false && "Unrecognized packet header");
779
865
  }
780
866
  return true;
781
867
  }
@@ -53,22 +53,24 @@ struct VerilogFmtArg {
53
53
  // Must be kept in sync with `struct fmt_part` in backends/cxxrtl/runtime/cxxrtl/cxxrtl.h!
54
54
  struct FmtPart {
55
55
  enum {
56
- STRING = 0,
56
+ LITERAL = 0,
57
57
  INTEGER = 1,
58
- CHARACTER = 2,
59
- VLOG_TIME = 3,
58
+ STRING = 2,
59
+ UNICHAR = 3,
60
+ VLOG_TIME = 4,
60
61
  } type;
61
62
 
62
- // STRING type
63
+ // LITERAL type
63
64
  std::string str;
64
65
 
65
- // INTEGER/CHARACTER types
66
+ // INTEGER/STRING/UNICHAR types
66
67
  RTLIL::SigSpec sig;
67
68
 
68
- // INTEGER/CHARACTER/VLOG_TIME types
69
+ // INTEGER/STRING/VLOG_TIME types
69
70
  enum {
70
71
  RIGHT = 0,
71
72
  LEFT = 1,
73
+ NUMERIC = 2,
72
74
  } justify = RIGHT;
73
75
  char padding = '\0';
74
76
  size_t width = 0;
@@ -76,7 +78,14 @@ struct FmtPart {
76
78
  // INTEGER type
77
79
  unsigned base = 10;
78
80
  bool signed_ = false;
79
- bool plus = false;
81
+ enum {
82
+ MINUS = 0,
83
+ PLUS_MINUS = 1,
84
+ SPACE_MINUS = 2,
85
+ } sign = MINUS;
86
+ bool hex_upper = false;
87
+ bool show_base = false;
88
+ bool group = false;
80
89
 
81
90
  // VLOG_TIME type
82
91
  bool realtime = false;
@@ -86,7 +95,7 @@ struct Fmt {
86
95
  public:
87
96
  std::vector<FmtPart> parts;
88
97
 
89
- void append_string(const std::string &str);
98
+ void append_literal(const std::string &str);
90
99
 
91
100
  void parse_rtlil(const RTLIL::Cell *cell);
92
101
  void emit_rtlil(RTLIL::Cell *cell) const;
@@ -149,7 +149,7 @@ template <typename T, typename C = std::less<T>, typename OPS = hash_ops<T>> cla
149
149
  std::map<T, int, C> node_to_index;
150
150
  std::vector<std::set<int, IndirectCmp>> edges;
151
151
  std::vector<T> sorted;
152
- std::set<std::set<T, C>> loops;
152
+ std::set<std::vector<T>> loops;
153
153
 
154
154
  TopoSort() : indirect_cmp(nodes)
155
155
  {
@@ -220,10 +220,10 @@ template <typename T, typename C = std::less<T>, typename OPS = hash_ops<T>> cla
220
220
  if (active_cells[root_index]) {
221
221
  found_loops = true;
222
222
  if (analyze_loops) {
223
- std::set<T, C> loop;
223
+ std::vector<T> loop;
224
224
  for (int i = GetSize(active_stack) - 1; i >= 0; i--) {
225
225
  const int index = active_stack[i];
226
- loop.insert(nodes[index]);
226
+ loop.push_back(nodes[index]);
227
227
  if (index == root_index)
228
228
  break;
229
229
  }
@@ -378,6 +378,7 @@ class SbyAutotune:
378
378
 
379
379
  def run(self):
380
380
  self.task.handle_non_engine_options()
381
+ self.task.setup_status_db(':memory:')
381
382
  self.config = self.task.autotune_config or SbyAutotuneConfig()
382
383
 
383
384
  if "expect" not in self.task.options:
@@ -69,6 +69,11 @@ def parser_func():
69
69
  parser.add_argument("--setup", action="store_true", dest="setupmode",
70
70
  help="set up the working directory and exit")
71
71
 
72
+ parser.add_argument("--status", action="store_true", dest="status",
73
+ help="summarize the contents of the status database")
74
+ parser.add_argument("--statusreset", action="store_true", dest="status_reset",
75
+ help="reset the contents of the status database")
76
+
72
77
  parser.add_argument("--init-config-file", dest="init_config_file",
73
78
  help="create a default .sby config file")
74
79
  parser.add_argument("sbyfile", metavar="<jobname>.sby | <dirname>", nargs="?",