yowasp-yosys 0.55.0.3.post946.dev0__py3-none-any.whl → 0.56.0.141.post974.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.
Files changed (32) hide show
  1. yowasp_yosys/sby.py +39 -8
  2. yowasp_yosys/share/include/frontends/ast/ast.h +34 -38
  3. yowasp_yosys/share/include/kernel/celltypes.h +6 -0
  4. yowasp_yosys/share/include/kernel/consteval.h +5 -1
  5. yowasp_yosys/share/include/kernel/constids.inc +1 -0
  6. yowasp_yosys/share/include/kernel/ffinit.h +3 -3
  7. yowasp_yosys/share/include/kernel/ffmerge.h +1 -1
  8. yowasp_yosys/share/include/kernel/hashlib.h +43 -16
  9. yowasp_yosys/share/include/kernel/io.h +382 -8
  10. yowasp_yosys/share/include/kernel/json.h +2 -2
  11. yowasp_yosys/share/include/kernel/log.h +1 -0
  12. yowasp_yosys/share/include/kernel/register.h +42 -4
  13. yowasp_yosys/share/include/kernel/rtlil.h +6 -2
  14. yowasp_yosys/share/include/kernel/satgen.h +6 -4
  15. yowasp_yosys/share/include/kernel/sigtools.h +130 -26
  16. yowasp_yosys/share/include/kernel/yosys_common.h +9 -0
  17. yowasp_yosys/share/include/passes/techmap/libparse.h +235 -0
  18. yowasp_yosys/share/python3/sby_autotune.py +1 -1
  19. yowasp_yosys/share/python3/sby_cmdline.py +13 -0
  20. yowasp_yosys/share/python3/sby_core.py +208 -85
  21. yowasp_yosys/share/python3/sby_design.py +4 -0
  22. yowasp_yosys/share/python3/sby_engine_abc.py +15 -4
  23. yowasp_yosys/share/python3/sby_engine_aiger.py +14 -9
  24. yowasp_yosys/share/python3/sby_engine_btor.py +15 -4
  25. yowasp_yosys/share/python3/sby_engine_smtbmc.py +40 -27
  26. yowasp_yosys/share/python3/sby_status.py +388 -115
  27. yowasp_yosys/yosys.wasm +0 -0
  28. {yowasp_yosys-0.55.0.3.post946.dev0.dist-info → yowasp_yosys-0.56.0.141.post974.dev0.dist-info}/METADATA +1 -1
  29. {yowasp_yosys-0.55.0.3.post946.dev0.dist-info → yowasp_yosys-0.56.0.141.post974.dev0.dist-info}/RECORD +32 -31
  30. {yowasp_yosys-0.55.0.3.post946.dev0.dist-info → yowasp_yosys-0.56.0.141.post974.dev0.dist-info}/WHEEL +0 -0
  31. {yowasp_yosys-0.55.0.3.post946.dev0.dist-info → yowasp_yosys-0.56.0.141.post974.dev0.dist-info}/entry_points.txt +0 -0
  32. {yowasp_yosys-0.55.0.3.post946.dev0.dist-info → yowasp_yosys-0.56.0.141.post974.dev0.dist-info}/top_level.txt +0 -0
@@ -17,9 +17,11 @@
17
17
  #
18
18
 
19
19
  import os, re, sys, signal, platform, click
20
+ import time
20
21
  if os.name == "posix":
21
22
  import resource, fcntl
22
23
  import subprocess
24
+ from pathlib import Path
23
25
  from dataclasses import dataclass, field
24
26
  from collections import defaultdict
25
27
  from typing import Optional
@@ -43,12 +45,8 @@ signal.signal(signal.SIGINT, force_shutdown)
43
45
  signal.signal(signal.SIGTERM, force_shutdown)
44
46
 
45
47
  def process_filename(filename):
46
- if filename.startswith("~/"):
47
- filename = os.environ['HOME'] + filename[1:]
48
-
49
48
  filename = os.path.expandvars(filename)
50
-
51
- return filename
49
+ return Path(filename).expanduser()
52
50
 
53
51
  def dress_message(workdir, logmessage):
54
52
  tm = localtime()
@@ -97,6 +95,7 @@ class SbyProc:
97
95
  self.silent = silent
98
96
  self.wait = False
99
97
  self.job_lease = None
98
+ self.next_db = 0.0
100
99
 
101
100
  self.task.update_proc_pending(self)
102
101
 
@@ -148,8 +147,8 @@ class SbyProc:
148
147
  if self.error_callback is not None:
149
148
  self.error_callback(retcode)
150
149
 
151
- def terminate(self, timeout=False):
152
- if (self.task.opt_wait or self.wait) and not timeout:
150
+ def terminate(self, force=False):
151
+ if (self.task.opt_wait or self.wait) and not force:
153
152
  return
154
153
  if self.running:
155
154
  if not self.silent:
@@ -175,6 +174,36 @@ class SbyProc:
175
174
  if self.finished or self.terminated or self.exited:
176
175
  return
177
176
 
177
+ for task in self.task.taskloop.tasks_done:
178
+ if task.name in self.task.cancelledby:
179
+ if not self.silent:
180
+ self.task.log(f"Cancelled by {task.name!r} task")
181
+ self.task.cancel()
182
+ return
183
+
184
+ if self.task.status_cancels and time.time() >= self.next_db:
185
+ tasks_status = self.task.status_db.all_tasks_status()
186
+ for task_status in tasks_status.values():
187
+ if (task_status["status"] in ["PASS", "FAIL", "CANCELLED"] and
188
+ task_status["name"] in self.task.cancelledby):
189
+ if not self.silent:
190
+ status_time = time.localtime(task_status["status_created"])
191
+ if status_time.tm_yday == time.localtime().tm_yday:
192
+ # same day, format time only
193
+ time_format = r"%H:%M:%S"
194
+ else:
195
+ time_format = r"%x %H:%M:%S"
196
+ self.task.log(
197
+ f'Cancelled by {task_status["name"]!r} task '
198
+ f'with status {task_status["status"]!r} '
199
+ f'at {time.strftime(time_format, status_time)} '
200
+ '(consider calling sby with --statusreset if this seems wrong)'
201
+ )
202
+ self.task.cancel()
203
+ return
204
+ # don't hit the database every poll
205
+ self.next_db = time.time() + 10
206
+
178
207
  if not self.running:
179
208
  for dep in self.deps:
180
209
  if not dep.finished:
@@ -218,6 +247,10 @@ class SbyProc:
218
247
  if self.job_lease:
219
248
  self.job_lease.done()
220
249
 
250
+ if self.terminated:
251
+ # task already terminated, do not finish
252
+ return
253
+
221
254
  if not self.silent:
222
255
  self.task.log(f"{click.style(self.info, fg='magenta')}: finished (returncode={self.p.returncode})")
223
256
 
@@ -282,6 +315,7 @@ class SbyConfig:
282
315
  self.autotune_config = None
283
316
  self.files = dict()
284
317
  self.verbatim_files = dict()
318
+ self.cancelledby = list()
285
319
  pass
286
320
 
287
321
  def parse_config(self, f):
@@ -402,6 +436,12 @@ class SbyConfig:
402
436
  import sby_autotune
403
437
  self.autotune_config = sby_autotune.SbyAutotuneConfig()
404
438
  continue
439
+
440
+ if section == "cancelledby":
441
+ mode = "cancelledby"
442
+ if args is not None:
443
+ self.error(f"sby file syntax error: '[cancelledby]' section does not accept any arguments. got {args}")
444
+ continue
405
445
 
406
446
  if section == "file":
407
447
  mode = "file"
@@ -436,6 +476,12 @@ class SbyConfig:
436
476
  if mode == "autotune":
437
477
  self.autotune_config.config_line(self, line)
438
478
  continue
479
+
480
+ if mode == "cancelledby":
481
+ taskname = line.strip()
482
+ if taskname:
483
+ self.cancelledby.append(taskname)
484
+ continue
439
485
 
440
486
  if mode == "engines":
441
487
  args = line.strip().split()
@@ -529,7 +575,7 @@ class SbyConfig:
529
575
  self.error(f"sby file syntax error: '[files]' section entry expects up to 2 arguments, {len(entries)} specified")
530
576
 
531
577
  if len(entries) == 1:
532
- self.files[os.path.basename(entries[0])] = entries[0]
578
+ self.files[Path(entries[0]).name] = entries[0]
533
579
  elif len(entries) == 2:
534
580
  self.files[entries[0]] = entries[1]
535
581
 
@@ -553,6 +599,7 @@ class SbyTaskloop:
553
599
  self.procs_pending = []
554
600
  self.procs_running = []
555
601
  self.tasks = []
602
+ self.tasks_done = []
556
603
  self.poll_now = False
557
604
  self.jobclient = jobclient
558
605
 
@@ -605,6 +652,7 @@ class SbyTaskloop:
605
652
  self.tasks.append(task)
606
653
  else:
607
654
  task.exit_callback()
655
+ self.tasks_done.append(task)
608
656
 
609
657
  for task in self.tasks:
610
658
  task.exit_callback()
@@ -631,6 +679,8 @@ class SbyTraceSummary:
631
679
  path: Optional[str] = field(default=None)
632
680
  engine_case: Optional[str] = field(default=None)
633
681
  events: dict = field(default_factory=lambda: defaultdict(lambda: defaultdict(list)))
682
+ trace_ids: dict[str, int] = field(default_factory=lambda: dict())
683
+ last_ext: Optional[str] = field(default=None)
634
684
 
635
685
  @property
636
686
  def kind(self):
@@ -682,42 +732,62 @@ class SbySummary:
682
732
 
683
733
  if update_status:
684
734
  status_metadata = dict(source="summary_event", engine=engine.engine)
735
+ if event.step:
736
+ status_metadata["step"] = event.step
685
737
 
738
+ add_trace = False
686
739
  if event.prop:
687
- if event.type == "$assert":
740
+ if event.type is None:
741
+ event.type = event.prop.celltype
742
+ elif event.type == "$assert":
688
743
  event.prop.status = "FAIL"
689
- if event.path:
690
- event.prop.tracefiles.append(event.path)
691
- if update_status:
692
- self.task.status_db.add_task_property_data(
693
- event.prop,
694
- "trace",
695
- data=dict(path=event.path, step=event.step, **status_metadata),
696
- )
697
- if event.prop:
698
- if event.type == "$cover":
744
+ add_trace = True
745
+ elif event.type == "$cover":
699
746
  event.prop.status = "PASS"
700
- if event.path:
701
- event.prop.tracefiles.append(event.path)
702
- if update_status:
703
- self.task.status_db.add_task_property_data(
704
- event.prop,
705
- "trace",
706
- data=dict(path=event.path, step=event.step, **status_metadata),
707
- )
747
+ add_trace = True
748
+
749
+ trace_id = None
750
+ trace_path = None
751
+ if event.trace:
752
+ # get or create trace summary
753
+ try:
754
+ trace_summary = engine.traces[event.trace]
755
+ except KeyError:
756
+ trace_summary = SbyTraceSummary(event.trace, path=event.path, engine_case=event.engine_case)
757
+ engine.traces[event.trace] = trace_summary
758
+
759
+ if event.path:
760
+ trace_path = Path(event.path)
761
+ trace_ext = trace_path.suffix
762
+ trace_summary.last_ext = trace_ext
763
+ try:
764
+ # use existing tracefile for this extension
765
+ trace_id = trace_summary.trace_ids[trace_ext]
766
+ except KeyError:
767
+ # add tracefile to database
768
+ trace_id = self.task.status_db.add_task_trace(event.trace, event.path, trace_ext[1:], event.engine_case)
769
+ trace_summary.trace_ids[trace_ext] = trace_id
770
+ elif trace_summary.path:
771
+ # use existing tracefile for last extension
772
+ trace_path = Path(trace_summary.path)
773
+ trace_ext = trace_summary.last_ext
774
+ trace_id = trace_summary.trace_ids[trace_ext]
775
+
776
+ if event.type:
777
+ by_type = trace_summary.events[event.type]
778
+ if event.hdlname:
779
+ by_type[event.hdlname].append(event)
780
+
708
781
  if event.prop and update_status:
782
+ # update property status in database
709
783
  self.task.status_db.set_task_property_status(
710
784
  event.prop,
711
- data=status_metadata
785
+ trace_id=trace_id,
786
+ data=status_metadata,
712
787
  )
713
788
 
714
- if event.trace not in engine.traces:
715
- engine.traces[event.trace] = SbyTraceSummary(event.trace, path=event.path, engine_case=event.engine_case)
716
-
717
- if event.type:
718
- by_type = engine.traces[event.trace].events[event.type]
719
- if event.hdlname:
720
- by_type[event.hdlname].append(event)
789
+ if trace_path and add_trace:
790
+ event.prop.tracefiles.append(str(trace_path))
721
791
 
722
792
  def set_engine_status(self, engine_idx, status, case=None):
723
793
  engine_summary = self.engine_summary(engine_idx)
@@ -759,10 +829,21 @@ class SbySummary:
759
829
  break
760
830
  case_suffix = f" [{trace.engine_case}]" if trace.engine_case else ""
761
831
  if trace.path:
762
- if short:
763
- yield f"{trace.kind}{case_suffix}: {self.task.workdir}/{trace.path}"
764
- else:
765
- yield f"{trace.kind}{case_suffix}: {trace.path}"
832
+ # print single preferred trace
833
+ preferred_exts = [".fst", ".vcd"]
834
+ if trace.last_ext not in preferred_exts: preferred_exts.append(trace.last_ext)
835
+ for ext in trace.trace_ids.keys():
836
+ if ext not in preferred_exts: preferred_exts.append(ext)
837
+ for ext in preferred_exts:
838
+ if ext not in trace.trace_ids:
839
+ continue
840
+ if short:
841
+ path = Path(self.task.workdir) / trace.path
842
+ else:
843
+ path = Path(trace.path)
844
+ yield f"{trace.kind}{case_suffix}: {path.with_suffix(ext)}"
845
+ if short:
846
+ break
766
847
  else:
767
848
  yield f"{trace.kind}{case_suffix}: <{trace.trace}>"
768
849
  produced_traces = True
@@ -785,15 +866,18 @@ class SbySummary:
785
866
  break
786
867
 
787
868
  event = same_events[0]
788
- steps = sorted(e.step for e in same_events)
869
+ # uniquify steps and ignore events with missing steps
870
+ steps = sorted(set(e.step for e in same_events if e.step))
789
871
  if short and len(steps) > step_limit:
790
872
  excess = len(steps) - step_limit
791
873
  steps = [str(step) for step in steps[:step_limit]]
792
874
  omitted_excess = True
793
875
  steps[-1] += f" and {excess} further step{'s' if excess != 1 else ''}"
794
876
 
795
- steps = f"step{'s' if len(steps) > 1 else ''} {', '.join(map(str, steps))}"
796
- yield f" {desc} {event.hdlname} at {event.src} in {steps}"
877
+ event_string = f" {desc} {hdlname} at {event.src}"
878
+ if steps:
879
+ event_string += f" step{'s' if len(steps) > 1 else ''} {', '.join(map(str, steps))}"
880
+ yield event_string
797
881
 
798
882
  if not produced_traces:
799
883
  yield f"{engine.engine} did not produce any traces"
@@ -801,7 +885,7 @@ class SbySummary:
801
885
  if self.unreached_covers is None and self.task.opt_mode == 'cover' and self.task.status != "PASS" and self.task.design:
802
886
  self.unreached_covers = []
803
887
  for prop in self.task.design.hierarchy:
804
- if prop.type == prop.Type.COVER and prop.status == "UNKNOWN":
888
+ if prop.type == prop.Type.COVER and prop.status in ["UNKNOWN", "FAIL"]:
805
889
  self.unreached_covers.append(prop)
806
890
 
807
891
  if self.unreached_covers:
@@ -825,12 +909,15 @@ class SbySummary:
825
909
 
826
910
 
827
911
  class SbyTask(SbyConfig):
828
- def __init__(self, sbyconfig, workdir, early_logs, reusedir, taskloop=None, logfile=None):
912
+ def __init__(self, sbyconfig, workdir, early_logs, reusedir, status_cancels=False, taskloop=None, logfile=None, name=None, live_formats=[]):
829
913
  super().__init__()
830
914
  self.used_options = set()
831
915
  self.models = dict()
832
916
  self.workdir = workdir
833
917
  self.reusedir = reusedir
918
+ self.status_cancels = status_cancels
919
+ self.name = name
920
+ self.live_formats = live_formats
834
921
  self.status = "UNKNOWN"
835
922
  self.total_time = 0
836
923
  self.expect = list()
@@ -942,35 +1029,44 @@ class SbyTask(SbyConfig):
942
1029
  raise SbyAbort(logmessage)
943
1030
 
944
1031
  def makedirs(self, path):
945
- if self.reusedir and os.path.isdir(path):
1032
+ path = Path(path)
1033
+ if self.reusedir and path.is_dir():
946
1034
  rmtree(path, ignore_errors=True)
947
- if not os.path.isdir(path):
948
- os.makedirs(path)
1035
+ path.mkdir(parents=True, exist_ok=True)
949
1036
 
950
- def copy_src(self):
951
- self.makedirs(self.workdir + "/src")
1037
+ def copy_src(self, linkmode=False):
1038
+ outdir = Path(self.workdir) / "src"
1039
+ self.makedirs(outdir)
952
1040
 
953
1041
  for dstfile, lines in self.verbatim_files.items():
954
- dstfile = self.workdir + "/src/" + dstfile
955
- self.log(f"Writing '{dstfile}'.")
1042
+ dstfile = outdir / dstfile
1043
+ self.log(f"Writing '{dstfile.absolute()}'.")
1044
+ dstfile.parent.mkdir(parents=True, exist_ok=True)
956
1045
 
957
1046
  with open(dstfile, "w") as f:
958
1047
  for line in lines:
959
1048
  f.write(line)
960
1049
 
961
1050
  for dstfile, srcfile in self.files.items():
962
- if dstfile.startswith("/") or dstfile.startswith("../") or ("/../" in dstfile):
1051
+ dstfile = Path(dstfile)
1052
+ if dstfile.is_absolute() or ".." in dstfile.parts:
963
1053
  self.error(f"destination filename must be a relative path without /../: {dstfile}")
964
- dstfile = self.workdir + "/src/" + dstfile
1054
+ dstfile = outdir / dstfile
965
1055
 
966
1056
  srcfile = process_filename(srcfile)
967
1057
 
968
- basedir = os.path.dirname(dstfile)
969
- if basedir != "" and not os.path.exists(basedir):
970
- os.makedirs(basedir)
1058
+ basedir = dstfile.parent
1059
+ basedir.mkdir(parents=True, exist_ok=True)
1060
+
1061
+ if linkmode:
1062
+ verb = "Link"
1063
+ else:
1064
+ verb = "Copy"
1065
+ self.log(f"{verb} '{srcfile.absolute()}' to '{dstfile.absolute()}'.")
971
1066
 
972
- self.log(f"Copy '{os.path.abspath(srcfile)}' to '{os.path.abspath(dstfile)}'.")
973
- if os.path.isdir(srcfile):
1067
+ if linkmode:
1068
+ os.symlink(srcfile.resolve(), dstfile)
1069
+ elif srcfile.is_dir():
974
1070
  copytree(srcfile, dstfile, dirs_exist_ok=True)
975
1071
  else:
976
1072
  copyfile(srcfile, dstfile)
@@ -999,12 +1095,12 @@ class SbyTask(SbyConfig):
999
1095
  self.__dict__["opt_" + option_name] = default_value
1000
1096
 
1001
1097
  def make_model(self, model_name):
1002
- if not os.path.isdir(f"{self.workdir}/model"):
1003
- os.makedirs(f"{self.workdir}/model")
1098
+ modeldir = Path(self.workdir) / "model"
1099
+ modeldir.mkdir(exist_ok=True)
1004
1100
 
1005
1101
  if model_name == "prep":
1006
- with open(f"""{self.workdir}/model/design_prep.ys""", "w") as f:
1007
- print(f"# running in {self.workdir}/model/", file=f)
1102
+ with open(modeldir / "design_prep.ys", "w") as f:
1103
+ print(f"# running in {modeldir}/", file=f)
1008
1104
  print(f"""read_rtlil design.il""", file=f)
1009
1105
  if not self.opt_skip_prep:
1010
1106
  print("scc -select; simplemap; select -clear", file=f)
@@ -1020,7 +1116,10 @@ class SbyTask(SbyConfig):
1020
1116
  if self.opt_mode in ["bmc", "prove"]:
1021
1117
  print("chformal -live -fair -cover -remove", file=f)
1022
1118
  if self.opt_mode == "cover":
1023
- print("chformal -live -fair -remove", file=f)
1119
+ if self.opt_cover_assert:
1120
+ print("chformal -live -fair -remove", file=f)
1121
+ else:
1122
+ print("chformal -live -fair -assert -remove", file=f)
1024
1123
  if self.opt_mode == "live":
1025
1124
  print("chformal -assert2assume", file=f)
1026
1125
  print("chformal -cover -remove", file=f)
@@ -1045,7 +1144,7 @@ class SbyTask(SbyConfig):
1045
1144
  return [proc]
1046
1145
 
1047
1146
  if model_name == "base":
1048
- with open(f"""{self.workdir}/model/design.ys""", "w") as f:
1147
+ with open(modeldir / "design.ys", "w") as f:
1049
1148
  print(f"# running in {self.workdir}/src/", file=f)
1050
1149
  for cmd in self.script:
1051
1150
  print(cmd, file=f)
@@ -1067,7 +1166,7 @@ class SbyTask(SbyConfig):
1067
1166
 
1068
1167
  def instance_hierarchy_callback(retcode):
1069
1168
  if self.design == None:
1070
- with open(f"{self.workdir}/model/design.json") as f:
1169
+ with open(modeldir / "design.json") as f:
1071
1170
  self.design = design_hierarchy(f)
1072
1171
  self.status_db.create_task_properties([
1073
1172
  prop for prop in self.design.properties_by_path.values()
@@ -1083,8 +1182,8 @@ class SbyTask(SbyConfig):
1083
1182
  return [proc]
1084
1183
 
1085
1184
  if re.match(r"^smt2(_syn)?(_nomem)?(_stbv|_stdt)?$", model_name):
1086
- with open(f"{self.workdir}/model/design_{model_name}.ys", "w") as f:
1087
- print(f"# running in {self.workdir}/model/", file=f)
1185
+ with open(modeldir / f"design_{model_name}.ys", "w") as f:
1186
+ print(f"# running in {modeldir}/", file=f)
1088
1187
  print(f"""read_rtlil design_prep.il""", file=f)
1089
1188
  print("hierarchy -smtcheck", file=f)
1090
1189
  print("delete */t:$print", file=f)
@@ -1117,8 +1216,8 @@ class SbyTask(SbyConfig):
1117
1216
  return [proc]
1118
1217
 
1119
1218
  if re.match(r"^btor(_syn)?(_nomem)?$", model_name):
1120
- with open(f"{self.workdir}/model/design_{model_name}.ys", "w") as f:
1121
- print(f"# running in {self.workdir}/model/", file=f)
1219
+ with open(modeldir / f"design_{model_name}.ys", "w") as f:
1220
+ print(f"# running in {modeldir}/", file=f)
1122
1221
  print(f"""read_rtlil design_prep.il""", file=f)
1123
1222
  print("hierarchy -simcheck", file=f)
1124
1223
  print("delete */t:$print", file=f)
@@ -1153,8 +1252,8 @@ class SbyTask(SbyConfig):
1153
1252
  return [proc]
1154
1253
 
1155
1254
  if model_name == "aig":
1156
- with open(f"{self.workdir}/model/design_aiger.ys", "w") as f:
1157
- print(f"# running in {self.workdir}/model/", file=f)
1255
+ with open(modeldir / "design_aiger.ys", "w") as f:
1256
+ print(f"# running in {modeldir}/", file=f)
1158
1257
  print("read_rtlil design_prep.il", file=f)
1159
1258
  print("delete */t:$print", file=f)
1160
1259
  print("hierarchy -simcheck", file=f)
@@ -1180,7 +1279,7 @@ class SbyTask(SbyConfig):
1180
1279
  self,
1181
1280
  "aig",
1182
1281
  self.model("prep"),
1183
- f"""cd {self.workdir}/model; {self.exe_paths["yosys"]} -ql design_aiger.log design_aiger.ys"""
1282
+ f"""cd {modeldir}; {self.exe_paths["yosys"]} -ql design_aiger.log design_aiger.ys"""
1184
1283
  )
1185
1284
  proc.checkretcode = True
1186
1285
 
@@ -1191,8 +1290,8 @@ class SbyTask(SbyConfig):
1191
1290
  self,
1192
1291
  model_name,
1193
1292
  self.model("aig"),
1194
- f"""cd {self.workdir}/model; {self.exe_paths["abc"]} -c 'read_aiger design_aiger.aig; fold{" -s" if self.opt_aigfolds else ""}; strash; write_aiger design_aiger_fold.aig'""",
1195
- logfile=open(f"{self.workdir}/model/design_aiger_fold.log", "w")
1293
+ f"""cd {modeldir}; {self.exe_paths["abc"]} -c 'read_aiger design_aiger.aig; fold{" -s" if self.opt_aigfolds else ""}; strash; write_aiger design_aiger_fold.aig'""",
1294
+ logfile=open(f"{modeldir}/design_aiger_fold.log", "w")
1196
1295
  )
1197
1296
  proc.checkretcode = True
1198
1297
 
@@ -1205,25 +1304,39 @@ class SbyTask(SbyConfig):
1205
1304
  self.models[model_name] = self.make_model(model_name)
1206
1305
  return self.models[model_name]
1207
1306
 
1208
- def terminate(self, timeout=False):
1307
+ def terminate(self, timeout=False, cancel=False):
1209
1308
  if timeout:
1210
1309
  self.timeout_reached = True
1211
1310
  for proc in list(self.procs_running):
1212
- proc.terminate(timeout=timeout)
1311
+ proc.terminate(timeout or cancel)
1213
1312
  for proc in list(self.procs_pending):
1214
- proc.terminate(timeout=timeout)
1313
+ proc.terminate(timeout or cancel)
1314
+ if timeout:
1315
+ self.update_unknown_props(dict(source="timeout"))
1316
+
1317
+ def cancel(self):
1318
+ self.terminate(cancel=True)
1319
+ self.update_status("CANCELLED")
1215
1320
 
1216
1321
  def proc_failed(self, proc):
1217
1322
  # proc parameter used by autotune override
1218
1323
  self.status = "ERROR"
1219
1324
  self.terminate()
1220
1325
 
1326
+ def update_unknown_props(self, data):
1327
+ for prop in self.design.hierarchy:
1328
+ if prop.status != "UNKNOWN":
1329
+ continue
1330
+ if ((prop.type == prop.Type.ASSERT and self.opt_mode in ["bmc", "prove"]) or
1331
+ (prop.type == prop.Type.COVER and self.opt_mode == "cover")):
1332
+ self.status_db.set_task_property_status(prop, data=data)
1333
+
1221
1334
  def pass_unknown_asserts(self, data):
1222
1335
  for prop in self.design.pass_unknown_asserts():
1223
1336
  self.status_db.set_task_property_status(prop, data=data)
1224
1337
 
1225
- def update_status(self, new_status):
1226
- assert new_status in ["PASS", "FAIL", "UNKNOWN", "ERROR"]
1338
+ def update_status(self, new_status, step = None):
1339
+ assert new_status in ["PASS", "FAIL", "UNKNOWN", "ERROR", "CANCELLED"]
1227
1340
  self.status_db.set_task_status(new_status)
1228
1341
 
1229
1342
  if new_status == "UNKNOWN":
@@ -1236,7 +1349,10 @@ class SbyTask(SbyConfig):
1236
1349
  assert self.status != "FAIL"
1237
1350
  self.status = "PASS"
1238
1351
  if self.opt_mode in ("bmc", "prove") and self.design:
1239
- self.pass_unknown_asserts(dict(source="task_status"))
1352
+ data = {"source": "task_status"}
1353
+ if step:
1354
+ data["step"] = step
1355
+ self.pass_unknown_asserts(data)
1240
1356
 
1241
1357
  elif new_status == "FAIL":
1242
1358
  assert self.status != "PASS"
@@ -1245,6 +1361,9 @@ class SbyTask(SbyConfig):
1245
1361
  elif new_status == "ERROR":
1246
1362
  self.status = "ERROR"
1247
1363
 
1364
+ elif new_status == "CANCELLED":
1365
+ self.status = "CANCELLED"
1366
+
1248
1367
  else:
1249
1368
  assert 0
1250
1369
 
@@ -1263,7 +1382,7 @@ class SbyTask(SbyConfig):
1263
1382
  self.used_options.add("expect")
1264
1383
 
1265
1384
  for s in self.expect:
1266
- if s not in ["PASS", "FAIL", "UNKNOWN", "ERROR", "TIMEOUT"]:
1385
+ if s not in ["PASS", "FAIL", "UNKNOWN", "ERROR", "TIMEOUT", "CANCELLED"]:
1267
1386
  self.error(f"Invalid expect value: {s}")
1268
1387
 
1269
1388
  if self.opt_mode != "live":
@@ -1294,6 +1413,9 @@ class SbyTask(SbyConfig):
1294
1413
  self.handle_bool_option("skip_prep", False)
1295
1414
 
1296
1415
  self.handle_bool_option("assume_early", True)
1416
+
1417
+ if self.opt_mode == "cover":
1418
+ self.handle_bool_option("cover_assert", False)
1297
1419
 
1298
1420
  def setup_status_db(self, status_path=None):
1299
1421
  if hasattr(self, 'status_db'):
@@ -1306,9 +1428,9 @@ class SbyTask(SbyConfig):
1306
1428
  except FileNotFoundError:
1307
1429
  status_path = f"{self.workdir}/status.sqlite"
1308
1430
 
1309
- self.status_db = SbyStatusDb(status_path, self)
1431
+ self.status_db = SbyStatusDb(status_path, self, live_formats=self.live_formats)
1310
1432
 
1311
- def setup_procs(self, setupmode):
1433
+ def setup_procs(self, setupmode, linkmode=False):
1312
1434
  self.handle_non_engine_options()
1313
1435
  if self.opt_smtc is not None:
1314
1436
  for engine_idx, engine in self.engine_list():
@@ -1329,7 +1451,7 @@ class SbyTask(SbyConfig):
1329
1451
  if self.reusedir:
1330
1452
  rmtree(f"{self.workdir}/model", ignore_errors=True)
1331
1453
  else:
1332
- self.copy_src()
1454
+ self.copy_src(linkmode)
1333
1455
 
1334
1456
  if setupmode:
1335
1457
  self.retcode = 0
@@ -1400,7 +1522,7 @@ class SbyTask(SbyConfig):
1400
1522
  else:
1401
1523
  self.log("summary: " + click.style(line, fg="green" if self.status in self.expect else "red", bold=True))
1402
1524
 
1403
- assert self.status in ["PASS", "FAIL", "UNKNOWN", "ERROR", "TIMEOUT"]
1525
+ assert self.status in ["PASS", "FAIL", "UNKNOWN", "ERROR", "TIMEOUT", "CANCELLED"]
1404
1526
 
1405
1527
  if self.status in self.expect:
1406
1528
  self.retcode = 0
@@ -1410,6 +1532,7 @@ class SbyTask(SbyConfig):
1410
1532
  if self.status == "UNKNOWN": self.retcode = 4
1411
1533
  if self.status == "TIMEOUT": self.retcode = 8
1412
1534
  if self.status == "ERROR": self.retcode = 16
1535
+ if self.status == "CANCELLED": self.retcode = 32
1413
1536
 
1414
1537
  def write_summary_file(self):
1415
1538
  with open(f"{self.workdir}/{self.status}", "w") as f:
@@ -111,6 +111,10 @@ class SbyProperty:
111
111
  def celltype(self):
112
112
  return f"${str(self.type).lower()}"
113
113
 
114
+ @property
115
+ def kind(self):
116
+ return str(self.type)
117
+
114
118
  @property
115
119
  def hdlname(self):
116
120
  return pretty_path(self.path).rstrip()
@@ -18,6 +18,7 @@
18
18
 
19
19
  import re, getopt
20
20
  import json
21
+ import os
21
22
  from sby_core import SbyProc
22
23
  from sby_engine_aiger import aigsmt_exit_callback, aigsmt_trace_callback
23
24
 
@@ -173,18 +174,25 @@ def run(mode, task, engine_idx, engine):
173
174
  aiger_props.append(task.design.properties_by_path.get(tuple(path)))
174
175
 
175
176
  if keep_going:
176
- match = re.match(r"Writing CEX for output ([0-9]+) to engine_[0-9]+/(.*)\.aiw", line)
177
+ match = re.match(r"Writing CEX for output ([0-9]+) to (engine_[0-9]+/(.*)\.aiw)", line)
177
178
  if match:
178
179
  output = int(match[1])
180
+ tracefile = match[2]
181
+ name = match[3]
182
+ trace, _ = os.path.splitext(name)
183
+ task.summary.add_event(engine_idx=engine_idx, trace=trace, path=tracefile)
179
184
  prop = aiger_props[output]
180
185
  if prop:
181
186
  prop.status = "FAIL"
182
- task.status_db.set_task_property_status(prop, data=dict(source="abc pdr", engine=f"engine_{engine_idx}"))
187
+ task.summary.add_event(
188
+ engine_idx=engine_idx, trace=trace,
189
+ hdlname=prop.hdlname, src=prop.location, prop=prop,
190
+ )
183
191
  disproved.add(output)
184
192
  proc_status = "FAIL"
185
193
  proc = aigsmt_trace_callback(task, engine_idx, proc_status,
186
194
  run_aigsmt=run_aigsmt, smtbmc_vcd=smtbmc_vcd, smtbmc_append=smtbmc_append, sim_append=sim_append,
187
- name=match[2],
195
+ name=name,
188
196
  )
189
197
  proc.register_exit_callback(exit_callback)
190
198
  procs_running += 1
@@ -198,7 +206,10 @@ def run(mode, task, engine_idx, engine):
198
206
  prop = aiger_props[output]
199
207
  if prop:
200
208
  prop.status = "PASS"
201
- task.status_db.set_task_property_status(prop, data=dict(source="abc pdr", engine=f"engine_{engine_idx}"))
209
+ task.summary.add_event(
210
+ engine_idx=engine_idx, trace=None,
211
+ hdlname=prop.hdlname, src=prop.location, prop=prop,
212
+ )
202
213
  proved.add(output)
203
214
 
204
215
  match = re.match(r"^Simulation of [0-9]+ frames for [0-9]+ rounds with [0-9]+ restarts did not assert POs.", line)