3
0
Fork 0
mirror of https://github.com/YosysHQ/sby.git synced 2025-08-10 23:30:53 +00:00

Merge branch 'main' into krys/symlink

This commit is contained in:
KrystalDelusion 2025-07-09 10:01:30 +12:00 committed by GitHub
commit 1130847901
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 1016 additions and 324 deletions

View file

@ -198,6 +198,9 @@ options are:
| | | indicated in SBY's log output). | | | | indicated in SBY's log output). |
| | | Values: ``on``, ``off``. Default: ``on`` | | | | Values: ``on``, ``off``. Default: ``on`` |
+-------------------+------------+---------------------------------------------------------+ +-------------------+------------+---------------------------------------------------------+
| ``cover_assert`` | ``cover`` | Check for assertion properties during ``cover`` mode. |
| | | Values: ``on``, ``off``. Default: ``off`` |
+-------------------+------------+---------------------------------------------------------+
Engines section Engines section
--------------- ---------------

View file

@ -22,7 +22,7 @@ import json, os, sys, shutil, tempfile, re
from sby_cmdline import parser_func from sby_cmdline import parser_func
from sby_core import SbyConfig, SbyTask, SbyAbort, SbyTaskloop, process_filename, dress_message from sby_core import SbyConfig, SbyTask, SbyAbort, SbyTaskloop, process_filename, dress_message
from sby_jobserver import SbyJobClient, process_jobserver_environment from sby_jobserver import SbyJobClient, process_jobserver_environment
from sby_status import SbyStatusDb from sby_status import SbyStatusDb, remove_db, FileInUseError
import time, platform, click import time, platform, click
release_version = 'unknown SBY version' release_version = 'unknown SBY version'
@ -62,12 +62,15 @@ jobcount = args.jobcount
init_config_file = args.init_config_file init_config_file = args.init_config_file
status_show = args.status status_show = args.status
status_reset = args.status_reset status_reset = args.status_reset
status_live_csv = args.livecsv
status_show_csv = args.statuscsv
status_latest = args.status_latest
if autotune and linkmode: if autotune and linkmode:
print("ERROR: --link flag currently not available with --autotune") print("ERROR: --link flag currently not available with --autotune")
sys.exit(1) sys.exit(1)
if status_show or status_reset: if status_show or status_reset or status_show_csv:
target = workdir_prefix or workdir or sbyfile target = workdir_prefix or workdir or sbyfile
if target is None: if target is None:
print("ERROR: Specify a .sby config file or working directory to use --status.") print("ERROR: Specify a .sby config file or working directory to use --status.")
@ -90,15 +93,26 @@ if status_show or status_reset:
status_db = SbyStatusDb(status_path, task=None) status_db = SbyStatusDb(status_path, task=None)
if status_show:
status_db.print_status_summary()
sys.exit(0)
if status_reset: if status_reset:
status_db.reset() status_db.reset()
elif status_db.test_schema():
print(f"ERROR: Status database does not match expected formatted. Use --statusreset to reset.")
sys.exit(1)
if status_show:
status_db.print_status_summary(status_latest)
if status_show_csv:
status_db.print_status_summary_csv(tasknames, status_latest)
status_db.db.close() status_db.db.close()
if status_live_csv:
print(f"WARNING: --livecsv flag found but not used.")
sys.exit(0) sys.exit(0)
elif status_latest:
print(f"WARNING: --latest flag found but not used.")
if sbyfile is not None: if sbyfile is not None:
@ -455,6 +469,12 @@ def start_task(taskloop, taskname):
print("*", file=gitignore) print("*", file=gitignore)
with open(f"{my_workdir}/status.path", "w") as status_path: with open(f"{my_workdir}/status.path", "w") as status_path:
print(my_status_db, file=status_path) print(my_status_db, file=status_path)
if os.path.exists(f"{my_workdir}/{my_status_db}") and opt_force:
try:
remove_db(f"{my_workdir}/{my_status_db}")
except FileInUseError:
# don't delete an open database
pass
junit_ts_name = os.path.basename(sbyfile[:-4]) if sbyfile is not None else workdir if workdir is not None else "stdin" junit_ts_name = os.path.basename(sbyfile[:-4]) if sbyfile is not None else workdir if workdir is not None else "stdin"
junit_tc_name = taskname if taskname is not None else "default" junit_tc_name = taskname if taskname is not None else "default"
@ -470,7 +490,7 @@ def start_task(taskloop, taskname):
else: else:
junit_filename = "junit" junit_filename = "junit"
task = SbyTask(sbyconfig, my_workdir, early_logmsgs, reusedir, taskloop) task = SbyTask(sbyconfig, my_workdir, early_logmsgs, reusedir, taskloop, name=taskname, live_csv=status_live_csv)
for k, v in exe_paths.items(): for k, v in exe_paths.items():
task.exe_paths[k] = v task.exe_paths[k] = v

View file

@ -29,6 +29,8 @@ def parser_func(release_version='unknown SBY version'):
help="maximum number of processes to run in parallel") help="maximum number of processes to run in parallel")
parser.add_argument("--sequential", action="store_true", dest="sequential", parser.add_argument("--sequential", action="store_true", dest="sequential",
help="run tasks in sequence, not in parallel") help="run tasks in sequence, not in parallel")
parser.add_argument("--livecsv", action="store_true", dest="livecsv",
help="print live updates of property statuses during task execution in csv format")
parser.add_argument("--autotune", action="store_true", dest="autotune", parser.add_argument("--autotune", action="store_true", dest="autotune",
help="automatically find a well performing engine and engine configuration for each task") help="automatically find a well performing engine and engine configuration for each task")
@ -75,6 +77,10 @@ def parser_func(release_version='unknown SBY version'):
parser.add_argument("--status", action="store_true", dest="status", parser.add_argument("--status", action="store_true", dest="status",
help="summarize the contents of the status database") help="summarize the contents of the status database")
parser.add_argument("--statuscsv", action="store_true", dest="statuscsv",
help="print the most recent status for each property in csv format")
parser.add_argument("--latest", action="store_true", dest="status_latest",
help="only check statuses from the most recent run of a task")
parser.add_argument("--statusreset", action="store_true", dest="status_reset", parser.add_argument("--statusreset", action="store_true", dest="status_reset",
help="reset the contents of the status database") help="reset the contents of the status database")

View file

@ -20,6 +20,7 @@ import os, re, sys, signal, platform, click
if os.name == "posix": if os.name == "posix":
import resource, fcntl import resource, fcntl
import subprocess import subprocess
from pathlib import Path
from dataclasses import dataclass, field from dataclasses import dataclass, field
from collections import defaultdict from collections import defaultdict
from typing import Optional from typing import Optional
@ -631,6 +632,8 @@ class SbyTraceSummary:
path: Optional[str] = field(default=None) path: Optional[str] = field(default=None)
engine_case: Optional[str] = field(default=None) engine_case: Optional[str] = field(default=None)
events: dict = field(default_factory=lambda: defaultdict(lambda: defaultdict(list))) events: dict = field(default_factory=lambda: defaultdict(lambda: defaultdict(list)))
trace_ids: dict[str, int] = field(default_factory=lambda: dict())
last_ext: Optional[str] = field(default=None)
@property @property
def kind(self): def kind(self):
@ -682,42 +685,62 @@ class SbySummary:
if update_status: if update_status:
status_metadata = dict(source="summary_event", engine=engine.engine) status_metadata = dict(source="summary_event", engine=engine.engine)
if event.step:
status_metadata["step"] = event.step
add_trace = False
if event.prop: if event.prop:
if event.type == "$assert": if event.type is None:
event.type = event.prop.celltype
elif event.type == "$assert":
event.prop.status = "FAIL" event.prop.status = "FAIL"
if event.path: add_trace = True
event.prop.tracefiles.append(event.path) elif event.type == "$cover":
if update_status:
self.task.status_db.add_task_property_data(
event.prop,
"trace",
data=dict(path=event.path, step=event.step, **status_metadata),
)
if event.prop:
if event.type == "$cover":
event.prop.status = "PASS" event.prop.status = "PASS"
if event.path: add_trace = True
event.prop.tracefiles.append(event.path)
if update_status: trace_id = None
self.task.status_db.add_task_property_data( trace_path = None
event.prop, if event.trace:
"trace", # get or create trace summary
data=dict(path=event.path, step=event.step, **status_metadata), try:
) trace_summary = engine.traces[event.trace]
except KeyError:
trace_summary = SbyTraceSummary(event.trace, path=event.path, engine_case=event.engine_case)
engine.traces[event.trace] = trace_summary
if event.path:
trace_path = Path(event.path)
trace_ext = trace_path.suffix
trace_summary.last_ext = trace_ext
try:
# use existing tracefile for this extension
trace_id = trace_summary.trace_ids[trace_ext]
except KeyError:
# add tracefile to database
trace_id = self.task.status_db.add_task_trace(event.trace, event.path, trace_ext[1:], event.engine_case)
trace_summary.trace_ids[trace_ext] = trace_id
elif trace_summary.path:
# use existing tracefile for last extension
trace_path = Path(trace_summary.path)
trace_ext = trace_summary.last_ext
trace_id = trace_summary.trace_ids[trace_ext]
if event.type:
by_type = trace_summary.events[event.type]
if event.hdlname:
by_type[event.hdlname].append(event)
if event.prop and update_status: if event.prop and update_status:
# update property status in database
self.task.status_db.set_task_property_status( self.task.status_db.set_task_property_status(
event.prop, event.prop,
data=status_metadata trace_id=trace_id,
data=status_metadata,
) )
if event.trace not in engine.traces: if trace_path and add_trace:
engine.traces[event.trace] = SbyTraceSummary(event.trace, path=event.path, engine_case=event.engine_case) event.prop.tracefiles.append(str(trace_path))
if event.type:
by_type = engine.traces[event.trace].events[event.type]
if event.hdlname:
by_type[event.hdlname].append(event)
def set_engine_status(self, engine_idx, status, case=None): def set_engine_status(self, engine_idx, status, case=None):
engine_summary = self.engine_summary(engine_idx) engine_summary = self.engine_summary(engine_idx)
@ -759,10 +782,21 @@ class SbySummary:
break break
case_suffix = f" [{trace.engine_case}]" if trace.engine_case else "" case_suffix = f" [{trace.engine_case}]" if trace.engine_case else ""
if trace.path: if trace.path:
if short: # print single preferred trace
yield f"{trace.kind}{case_suffix}: {self.task.workdir}/{trace.path}" preferred_exts = [".fst", ".vcd"]
else: if trace.last_ext not in preferred_exts: preferred_exts.append(trace.last_ext)
yield f"{trace.kind}{case_suffix}: {trace.path}" for ext in trace.trace_ids.keys():
if ext not in preferred_exts: preferred_exts.append(ext)
for ext in preferred_exts:
if ext not in trace.trace_ids:
continue
if short:
path = Path(self.task.workdir) / trace.path
else:
path = Path(trace.path)
yield f"{trace.kind}{case_suffix}: {path.with_suffix(ext)}"
if short:
break
else: else:
yield f"{trace.kind}{case_suffix}: <{trace.trace}>" yield f"{trace.kind}{case_suffix}: <{trace.trace}>"
produced_traces = True produced_traces = True
@ -785,15 +819,18 @@ class SbySummary:
break break
event = same_events[0] event = same_events[0]
steps = sorted(e.step for e in same_events) # uniquify steps and ignore events with missing steps
steps = sorted(set(e.step for e in same_events if e.step))
if short and len(steps) > step_limit: if short and len(steps) > step_limit:
excess = len(steps) - step_limit excess = len(steps) - step_limit
steps = [str(step) for step in steps[:step_limit]] steps = [str(step) for step in steps[:step_limit]]
omitted_excess = True omitted_excess = True
steps[-1] += f" and {excess} further step{'s' if excess != 1 else ''}" steps[-1] += f" and {excess} further step{'s' if excess != 1 else ''}"
steps = f"step{'s' if len(steps) > 1 else ''} {', '.join(map(str, steps))}" event_string = f" {desc} {hdlname} at {event.src}"
yield f" {desc} {event.hdlname} at {event.src} in {steps}" if steps:
event_string += f" step{'s' if len(steps) > 1 else ''} {', '.join(map(str, steps))}"
yield event_string
if not produced_traces: if not produced_traces:
yield f"{engine.engine} did not produce any traces" yield f"{engine.engine} did not produce any traces"
@ -801,7 +838,7 @@ class SbySummary:
if self.unreached_covers is None and self.task.opt_mode == 'cover' and self.task.status != "PASS" and self.task.design: if self.unreached_covers is None and self.task.opt_mode == 'cover' and self.task.status != "PASS" and self.task.design:
self.unreached_covers = [] self.unreached_covers = []
for prop in self.task.design.hierarchy: for prop in self.task.design.hierarchy:
if prop.type == prop.Type.COVER and prop.status == "UNKNOWN": if prop.type == prop.Type.COVER and prop.status in ["UNKNOWN", "FAIL"]:
self.unreached_covers.append(prop) self.unreached_covers.append(prop)
if self.unreached_covers: if self.unreached_covers:
@ -825,12 +862,14 @@ class SbySummary:
class SbyTask(SbyConfig): class SbyTask(SbyConfig):
def __init__(self, sbyconfig, workdir, early_logs, reusedir, taskloop=None, logfile=None): def __init__(self, sbyconfig, workdir, early_logs, reusedir, taskloop=None, logfile=None, name=None, live_csv=False):
super().__init__() super().__init__()
self.used_options = set() self.used_options = set()
self.models = dict() self.models = dict()
self.workdir = workdir self.workdir = workdir
self.reusedir = reusedir self.reusedir = reusedir
self.name = name
self.live_csv = live_csv
self.status = "UNKNOWN" self.status = "UNKNOWN"
self.total_time = 0 self.total_time = 0
self.expect = list() self.expect = list()
@ -1027,7 +1066,10 @@ class SbyTask(SbyConfig):
if self.opt_mode in ["bmc", "prove"]: if self.opt_mode in ["bmc", "prove"]:
print("chformal -live -fair -cover -remove", file=f) print("chformal -live -fair -cover -remove", file=f)
if self.opt_mode == "cover": if self.opt_mode == "cover":
print("chformal -live -fair -remove", file=f) if self.opt_cover_assert:
print("chformal -live -fair -remove", file=f)
else:
print("chformal -live -fair -assert -remove", file=f)
if self.opt_mode == "live": if self.opt_mode == "live":
print("chformal -assert2assume", file=f) print("chformal -assert2assume", file=f)
print("chformal -cover -remove", file=f) print("chformal -cover -remove", file=f)
@ -1219,17 +1261,27 @@ class SbyTask(SbyConfig):
proc.terminate(timeout=timeout) proc.terminate(timeout=timeout)
for proc in list(self.procs_pending): for proc in list(self.procs_pending):
proc.terminate(timeout=timeout) proc.terminate(timeout=timeout)
if timeout:
self.update_unknown_props(dict(source="timeout"))
def proc_failed(self, proc): def proc_failed(self, proc):
# proc parameter used by autotune override # proc parameter used by autotune override
self.status = "ERROR" self.status = "ERROR"
self.terminate() self.terminate()
def update_unknown_props(self, data):
for prop in self.design.hierarchy:
if prop.status != "UNKNOWN":
continue
if ((prop.type == prop.Type.ASSERT and self.opt_mode in ["bmc", "prove"]) or
(prop.type == prop.Type.COVER and self.opt_mode == "cover")):
self.status_db.set_task_property_status(prop, data=data)
def pass_unknown_asserts(self, data): def pass_unknown_asserts(self, data):
for prop in self.design.pass_unknown_asserts(): for prop in self.design.pass_unknown_asserts():
self.status_db.set_task_property_status(prop, data=data) self.status_db.set_task_property_status(prop, data=data)
def update_status(self, new_status): def update_status(self, new_status, step = None):
assert new_status in ["PASS", "FAIL", "UNKNOWN", "ERROR"] assert new_status in ["PASS", "FAIL", "UNKNOWN", "ERROR"]
self.status_db.set_task_status(new_status) self.status_db.set_task_status(new_status)
@ -1243,7 +1295,10 @@ class SbyTask(SbyConfig):
assert self.status != "FAIL" assert self.status != "FAIL"
self.status = "PASS" self.status = "PASS"
if self.opt_mode in ("bmc", "prove") and self.design: if self.opt_mode in ("bmc", "prove") and self.design:
self.pass_unknown_asserts(dict(source="task_status")) data = {"source": "task_status"}
if step:
data["step"] = step
self.pass_unknown_asserts(data)
elif new_status == "FAIL": elif new_status == "FAIL":
assert self.status != "PASS" assert self.status != "PASS"
@ -1301,6 +1356,9 @@ class SbyTask(SbyConfig):
self.handle_bool_option("skip_prep", False) self.handle_bool_option("skip_prep", False)
self.handle_bool_option("assume_early", True) self.handle_bool_option("assume_early", True)
if self.opt_mode == "cover":
self.handle_bool_option("cover_assert", False)
def setup_status_db(self, status_path=None): def setup_status_db(self, status_path=None):
if hasattr(self, 'status_db'): if hasattr(self, 'status_db'):
@ -1313,7 +1371,7 @@ class SbyTask(SbyConfig):
except FileNotFoundError: except FileNotFoundError:
status_path = f"{self.workdir}/status.sqlite" status_path = f"{self.workdir}/status.sqlite"
self.status_db = SbyStatusDb(status_path, self) self.status_db = SbyStatusDb(status_path, self, live_csv=self.live_csv)
def setup_procs(self, setupmode, linkmode=False): def setup_procs(self, setupmode, linkmode=False):
self.handle_non_engine_options() self.handle_non_engine_options()

View file

@ -111,6 +111,10 @@ class SbyProperty:
def celltype(self): def celltype(self):
return f"${str(self.type).lower()}" return f"${str(self.type).lower()}"
@property
def kind(self):
return str(self.type)
@property @property
def hdlname(self): def hdlname(self):
return pretty_path(self.path).rstrip() return pretty_path(self.path).rstrip()

View file

@ -18,6 +18,7 @@
import re, getopt import re, getopt
import json import json
import os
from sby_core import SbyProc from sby_core import SbyProc
from sby_engine_aiger import aigsmt_exit_callback, aigsmt_trace_callback from sby_engine_aiger import aigsmt_exit_callback, aigsmt_trace_callback
@ -173,18 +174,25 @@ def run(mode, task, engine_idx, engine):
aiger_props.append(task.design.properties_by_path.get(tuple(path))) aiger_props.append(task.design.properties_by_path.get(tuple(path)))
if keep_going: if keep_going:
match = re.match(r"Writing CEX for output ([0-9]+) to engine_[0-9]+/(.*)\.aiw", line) match = re.match(r"Writing CEX for output ([0-9]+) to (engine_[0-9]+/(.*)\.aiw)", line)
if match: if match:
output = int(match[1]) output = int(match[1])
tracefile = match[2]
name = match[3]
trace, _ = os.path.splitext(name)
task.summary.add_event(engine_idx=engine_idx, trace=trace, path=tracefile)
prop = aiger_props[output] prop = aiger_props[output]
if prop: if prop:
prop.status = "FAIL" prop.status = "FAIL"
task.status_db.set_task_property_status(prop, data=dict(source="abc pdr", engine=f"engine_{engine_idx}")) task.summary.add_event(
engine_idx=engine_idx, trace=trace,
hdlname=prop.hdlname, src=prop.location, prop=prop,
)
disproved.add(output) disproved.add(output)
proc_status = "FAIL" proc_status = "FAIL"
proc = aigsmt_trace_callback(task, engine_idx, proc_status, proc = aigsmt_trace_callback(task, engine_idx, proc_status,
run_aigsmt=run_aigsmt, smtbmc_vcd=smtbmc_vcd, smtbmc_append=smtbmc_append, sim_append=sim_append, run_aigsmt=run_aigsmt, smtbmc_vcd=smtbmc_vcd, smtbmc_append=smtbmc_append, sim_append=sim_append,
name=match[2], name=name,
) )
proc.register_exit_callback(exit_callback) proc.register_exit_callback(exit_callback)
procs_running += 1 procs_running += 1
@ -198,7 +206,10 @@ def run(mode, task, engine_idx, engine):
prop = aiger_props[output] prop = aiger_props[output]
if prop: if prop:
prop.status = "PASS" prop.status = "PASS"
task.status_db.set_task_property_status(prop, data=dict(source="abc pdr", engine=f"engine_{engine_idx}")) task.summary.add_event(
engine_idx=engine_idx, trace=None,
hdlname=prop.hdlname, src=prop.location, prop=prop,
)
proved.add(output) proved.add(output)
match = re.match(r"^Simulation of [0-9]+ frames for [0-9]+ rounds with [0-9]+ restarts did not assert POs.", line) match = re.match(r"^Simulation of [0-9]+ frames for [0-9]+ rounds with [0-9]+ restarts did not assert POs.", line)

View file

@ -202,11 +202,13 @@ def aigsmt_trace_callback(task, engine_idx, proc_status, *, run_aigsmt, smtbmc_v
proc2_status = None proc2_status = None
last_prop = [] last_prop = []
recorded_last = False
current_step = None current_step = None
def output_callback2(line): def output_callback2(line):
nonlocal proc2_status nonlocal proc2_status
nonlocal last_prop nonlocal last_prop
nonlocal recorded_last
nonlocal current_step nonlocal current_step
smt2_trans = {'\\':'/', '|':'/'} smt2_trans = {'\\':'/', '|':'/'}
@ -218,6 +220,8 @@ def aigsmt_trace_callback(task, engine_idx, proc_status, *, run_aigsmt, smtbmc_v
match = re.match(r"^## [0-9: ]+ .* in step ([0-9]+)\.\.", line) match = re.match(r"^## [0-9: ]+ .* in step ([0-9]+)\.\.", line)
if match: if match:
last_prop = []
recorded_last = False
current_step = int(match[1]) current_step = int(match[1])
return line return line
@ -233,32 +237,33 @@ def aigsmt_trace_callback(task, engine_idx, proc_status, *, run_aigsmt, smtbmc_v
cell_name = match[3] or match[2] cell_name = match[3] or match[2]
prop = task.design.hierarchy.find_property(path, cell_name, trans_dict=smt2_trans) prop = task.design.hierarchy.find_property(path, cell_name, trans_dict=smt2_trans)
prop.status = "FAIL" prop.status = "FAIL"
task.status_db.set_task_property_status(prop, data=dict(source="aigsmt", engine=f"engine_{engine_idx}"))
last_prop.append(prop) last_prop.append(prop)
return line return line
match = re.match(r"^## [0-9: ]+ Writing trace to VCD file: (\S+)", line) match = re.match(r"^## [0-9: ]+ Writing trace to (VCD|Yosys witness) file: (\S+)", line)
if match: if match:
tracefile = match[1] tracefile = match[2]
trace = os.path.basename(tracefile)[:-4] trace, _ = os.path.splitext(os.path.basename(tracefile))
task.summary.add_event(engine_idx=engine_idx, trace=trace, path=tracefile) task.summary.add_event(engine_idx=engine_idx, trace=trace, path=tracefile)
if match and last_prop:
for p in last_prop: for p in last_prop:
task.summary.add_event( task.summary.add_event(
engine_idx=engine_idx, trace=trace, engine_idx=engine_idx, trace=trace,
type=p.celltype, hdlname=p.hdlname, src=p.location, step=current_step) type=p.celltype, hdlname=p.hdlname, src=p.location,
p.tracefiles.append(tracefile) step=current_step, prop=p,
last_prop = [] )
recorded_last = True
return line return line
return line return line
def exit_callback2(retcode): def exit_callback2(retcode):
nonlocal last_prop, recorded_last
if proc2_status is None: if proc2_status is None:
task.error(f"engine_{engine_idx}: Could not determine aigsmt status.") task.error(f"engine_{engine_idx}: Could not determine aigsmt status.")
if proc2_status != "FAIL": if proc2_status != "FAIL":
task.error(f"engine_{engine_idx}: Unexpected aigsmt status.") task.error(f"engine_{engine_idx}: Unexpected aigsmt status.")
if len(last_prop) and not recorded_last:
task.error(f"engine_{engine_idx}: Found properties without trace.")
proc2.output_callback = output_callback2 proc2.output_callback = output_callback2
proc2.register_exit_callback(exit_callback2) proc2.register_exit_callback(exit_callback2)

View file

@ -77,6 +77,7 @@ def run(mode, task, engine_idx, engine):
common_state.wit_file = None common_state.wit_file = None
common_state.assert_fail = False common_state.assert_fail = False
common_state.running_procs = 0 common_state.running_procs = 0
common_state.current_step = None
def print_traces_and_terminate(): def print_traces_and_terminate():
if mode == "cover": if mode == "cover":
@ -90,6 +91,7 @@ def run(mode, task, engine_idx, engine):
proc_status = "FAIL" proc_status = "FAIL"
else: else:
task.error(f"engine_{engine_idx}: Engine terminated without status.") task.error(f"engine_{engine_idx}: Engine terminated without status.")
task.update_unknown_props(dict(source="btor", engine=f"engine_{engine_idx}"))
else: else:
if common_state.expected_cex == 0: if common_state.expected_cex == 0:
proc_status = "pass" proc_status = "pass"
@ -100,7 +102,7 @@ def run(mode, task, engine_idx, engine):
else: else:
task.error(f"engine_{engine_idx}: Engine terminated without status.") task.error(f"engine_{engine_idx}: Engine terminated without status.")
task.update_status(proc_status.upper()) task.update_status(proc_status.upper(), common_state.current_step)
task.summary.set_engine_status(engine_idx, proc_status) task.summary.set_engine_status(engine_idx, proc_status)
task.terminate() task.terminate()
@ -117,9 +119,11 @@ def run(mode, task, engine_idx, engine):
def make_exit_callback(suffix): def make_exit_callback(suffix):
def exit_callback2(retcode): def exit_callback2(retcode):
vcdpath = f"engine_{engine_idx}/trace{suffix}.vcd" trace = f"trace{suffix}"
if os.path.exists(f"{task.workdir}/{vcdpath}"): vcdpath = f"engine_{engine_idx}/{trace}.vcd"
task.summary.add_event(engine_idx=engine_idx, trace=f'trace{suffix}', path=vcdpath, type="$cover" if mode == "cover" else "$assert") trace_path = f"{task.workdir}/{vcdpath}"
if os.path.exists(trace_path):
task.summary.add_event(engine_idx=engine_idx, trace=trace, path=vcdpath, type="$cover" if mode == "cover" else "$assert")
common_state.running_procs -= 1 common_state.running_procs -= 1
if (common_state.running_procs == 0): if (common_state.running_procs == 0):
@ -140,6 +144,7 @@ def run(mode, task, engine_idx, engine):
common_state.expected_cex = int(match[1]) common_state.expected_cex = int(match[1])
if common_state.produced_cex != 0: if common_state.produced_cex != 0:
task.error(f"engine_{engine_idx}: Unexpected engine output (property count).") task.error(f"engine_{engine_idx}: Unexpected engine output (property count).")
task.update_unknown_props(dict(source="btor_init", engine=f"engine_{engine_idx}"))
else: else:
task.error(f"engine_{engine_idx}: BTOR solver '{solver_args[0]}' is currently not supported in cover mode.") task.error(f"engine_{engine_idx}: BTOR solver '{solver_args[0]}' is currently not supported in cover mode.")
@ -205,6 +210,9 @@ def run(mode, task, engine_idx, engine):
if solver_args[0] == "btormc": if solver_args[0] == "btormc":
if "calling BMC on" in line: if "calling BMC on" in line:
return line return line
match = re.match(r".*at bound k = (\d+).*", line)
if match:
common_state.current_step = int(match[1])
if "SATISFIABLE" in line: if "SATISFIABLE" in line:
return line return line
if "bad state properties at bound" in line: if "bad state properties at bound" in line:
@ -215,6 +223,9 @@ def run(mode, task, engine_idx, engine):
return line return line
elif solver_args[0] == "pono": elif solver_args[0] == "pono":
match = re.match(r".*at bound (\d+).*", line)
if match:
common_state.current_step = int(match[1])
if line == "unknown": if line == "unknown":
if common_state.solver_status is None: if common_state.solver_status is None:
common_state.solver_status = "unsat" common_state.solver_status = "unsat"

View file

@ -184,16 +184,20 @@ def run(mode, task, engine_idx, engine):
proc_status = None proc_status = None
last_prop = [] last_prop = []
recorded_last = False
pending_sim = None pending_sim = None
current_step = None current_step = None
procs_running = 1 procs_running = 1
failed_assert = False
def output_callback(line): def output_callback(line):
nonlocal proc_status nonlocal proc_status
nonlocal last_prop nonlocal last_prop
nonlocal recorded_last
nonlocal pending_sim nonlocal pending_sim
nonlocal current_step nonlocal current_step
nonlocal procs_running nonlocal procs_running
nonlocal failed_assert
if pending_sim: if pending_sim:
sim_proc = sim_witness_trace(procname, task, engine_idx, pending_sim, append=sim_append, inductive=mode == "prove_induction") sim_proc = sim_witness_trace(procname, task, engine_idx, pending_sim, append=sim_append, inductive=mode == "prove_induction")
@ -210,7 +214,14 @@ def run(mode, task, engine_idx, engine):
match = re.match(r"^## [0-9: ]+ .* in step ([0-9]+)\.\.", line) match = re.match(r"^## [0-9: ]+ .* in step ([0-9]+)\.\.", line)
if match: if match:
last_prop = []
recorded_last = False
if mode == "prove_induction":
return line
last_step = current_step
current_step = int(match[1]) current_step = int(match[1])
if current_step != last_step and last_step is not None:
task.update_unknown_props(dict(source="smtbmc", engine=f"engine_{engine_idx}", step=last_step))
return line return line
match = re.match(r"^## [0-9: ]+ Status: FAILED", line) match = re.match(r"^## [0-9: ]+ Status: FAILED", line)
@ -235,11 +246,11 @@ def run(mode, task, engine_idx, engine):
match = re.match(r"^## [0-9: ]+ Assert failed in ([^:]+): (\S+)(?: \((\S+)\))?", line) match = re.match(r"^## [0-9: ]+ Assert failed in ([^:]+): (\S+)(?: \((\S+)\))?", line)
if match: if match:
failed_assert = not keep_going
path = parse_mod_path(match[1]) path = parse_mod_path(match[1])
cell_name = match[3] or match[2] cell_name = match[3] or match[2]
prop = task.design.hierarchy.find_property(path, cell_name, trans_dict=smt2_trans) prop = task.design.hierarchy.find_property(path, cell_name, trans_dict=smt2_trans)
prop.status = "FAIL" prop.status = "FAIL"
task.status_db.set_task_property_status(prop, data=dict(source="smtbmc", engine=f"engine_{engine_idx}"))
last_prop.append(prop) last_prop.append(prop)
return line return line
@ -249,39 +260,37 @@ def run(mode, task, engine_idx, engine):
cell_name = match[3] or match[2] cell_name = match[3] or match[2]
prop = task.design.hierarchy.find_property(path, cell_name, trans_dict=smt2_trans) prop = task.design.hierarchy.find_property(path, cell_name, trans_dict=smt2_trans)
prop.status = "PASS" prop.status = "PASS"
task.status_db.set_task_property_status(prop, data=dict(source="smtbmc", engine=f"engine_{engine_idx}"))
last_prop.append(prop) last_prop.append(prop)
return line return line
if smtbmc_vcd and not task.opt_fst: match = re.match(r"^## [0-9: ]+ Writing trace to (VCD|Yosys witness) file: (\S+)", line)
match = re.match(r"^## [0-9: ]+ Writing trace to VCD file: (\S+)", line) if match:
if match: tracefile = match[2]
tracefile = match[1] if match[1] == "Yosys witness" and (task.opt_fst or task.opt_vcd_sim):
trace = os.path.basename(tracefile)[:-4]
engine_case = mode.split('_')[1] if '_' in mode else None
task.summary.add_event(engine_idx=engine_idx, trace=trace, path=tracefile, engine_case=engine_case)
if match and last_prop:
for p in last_prop:
task.summary.add_event(
engine_idx=engine_idx, trace=trace,
type=p.celltype, hdlname=p.hdlname, src=p.location, step=current_step)
p.tracefiles.append(tracefile)
last_prop = []
return line
else:
match = re.match(r"^## [0-9: ]+ Writing trace to Yosys witness file: (\S+)", line)
if match:
tracefile = match[1]
pending_sim = tracefile pending_sim = tracefile
trace, _ = os.path.splitext(os.path.basename(tracefile))
engine_case = mode.split('_')[1] if '_' in mode else None
task.summary.add_event(engine_idx=engine_idx, trace=trace, path=tracefile, engine_case=engine_case)
for p in last_prop:
task.summary.add_event(
engine_idx=engine_idx, trace=trace,
type=p.celltype, hdlname=p.hdlname, src=p.location,
step=current_step, prop=p,
)
recorded_last = True
return line
match = re.match(r"^## [0-9: ]+ Unreached cover statement at ([^:]+): (\S+)(?: \((\S+)\))?", line) match = re.match(r"^## [0-9: ]+ Unreached cover statement at ([^:]+): (\S+)(?: \((\S+)\))?", line)
if match: if match and not failed_assert:
path = parse_mod_path(match[1]) path = parse_mod_path(match[1])
cell_name = match[3] or match[2] cell_name = match[3] or match[2]
prop = task.design.hierarchy.find_property(path, cell_name, trans_dict=smt2_trans) prop = task.design.hierarchy.find_property(path, cell_name, trans_dict=smt2_trans)
prop.status = "FAIL" prop.status = "FAIL"
task.status_db.set_task_property_status(prop, data=dict(source="smtbmc", engine=f"engine_{engine_idx}")) task.summary.add_event(
engine_idx=engine_idx, trace=None,
hdlname=prop.hdlname, src=prop.location,
step=current_step, prop=prop,
)
return line return line
@ -292,15 +301,19 @@ def run(mode, task, engine_idx, engine):
last_exit_callback() last_exit_callback()
def exit_callback(retcode): def exit_callback(retcode):
nonlocal last_prop, recorded_last
if proc_status is None: if proc_status is None:
task.error(f"engine_{engine_idx}: Engine terminated without status.") task.error(f"engine_{engine_idx}: Engine terminated without status.")
if len(last_prop) and not recorded_last:
task.error(f"engine_{engine_idx}: Found properties without trace.")
simple_exit_callback(retcode) simple_exit_callback(retcode)
def last_exit_callback(): def last_exit_callback():
nonlocal current_step
if mode == "bmc" or mode == "cover": if mode == "bmc" or mode == "cover":
task.update_status(proc_status) task.update_status(proc_status, current_step)
if proc_status == "FAIL" and mode == "bmc" and keep_going: if proc_status == "FAIL" and mode == "bmc" and keep_going:
task.pass_unknown_asserts(dict(source="smtbmc", keep_going=True, engine=f"engine_{engine_idx}")) task.pass_unknown_asserts(dict(source="smtbmc", keep_going=True, engine=f"engine_{engine_idx}", step=current_step))
proc_status_lower = proc_status.lower() if proc_status == "PASS" else proc_status proc_status_lower = proc_status.lower() if proc_status == "PASS" else proc_status
task.summary.set_engine_status(engine_idx, proc_status_lower) task.summary.set_engine_status(engine_idx, proc_status_lower)
if not keep_going: if not keep_going:
@ -332,7 +345,7 @@ def run(mode, task, engine_idx, engine):
assert False assert False
if task.basecase_pass and task.induction_pass: if task.basecase_pass and task.induction_pass:
task.update_status("PASS") task.update_status("PASS", current_step)
task.summary.append("successful proof by k-induction.") task.summary.append("successful proof by k-induction.")
if not keep_going: if not keep_going:
task.terminate() task.terminate()

View file

@ -4,6 +4,8 @@ import sqlite3
import os import os
import time import time
import json import json
import click
import re
from collections import defaultdict from collections import defaultdict
from functools import wraps from functools import wraps
from pathlib import Path from pathlib import Path
@ -13,69 +15,130 @@ from sby_design import SbyProperty, pretty_path
Fn = TypeVar("Fn", bound=Callable[..., Any]) Fn = TypeVar("Fn", bound=Callable[..., Any])
SQLSCRIPT = """\
CREATE TABLE task (
id INTEGER PRIMARY KEY,
workdir TEXT,
name TEXT,
mode TEXT,
created REAL
);
CREATE TABLE task_status (
id INTEGER PRIMARY KEY,
task INTEGER,
status TEXT,
data TEXT,
created REAL,
FOREIGN KEY(task) REFERENCES task(id)
);
CREATE TABLE task_property (
id INTEGER PRIMARY KEY,
task INTEGER,
src TEXT,
name TEXT,
hdlname TEXT,
kind TEXT,
created REAL,
FOREIGN KEY(task) REFERENCES task(id)
);
CREATE TABLE task_property_status (
id INTEGER PRIMARY KEY,
task_property INTEGER,
task_trace INTEGER,
status TEXT,
data TEXT,
created REAL,
FOREIGN KEY(task_property) REFERENCES task_property(id),
FOREIGN KEY(task_trace) REFERENCES task_trace(id)
);
CREATE TABLE task_trace (
id INTEGER PRIMARY KEY,
task INTEGER,
trace TEXT,
path TEXT,
kind TEXT,
engine_case TEXT,
created REAL,
FOREIGN KEY(task) REFERENCES task(id)
);"""
def transaction(method: Fn) -> Fn: def transaction(method: Fn) -> Fn:
@wraps(method) @wraps(method)
def wrapper(self: SbyStatusDb, *args: Any, **kwargs: Any) -> Any: def wrapper(self: SbyStatusDb, *args: Any, **kwargs: Any) -> Any:
if self._transaction_active: if self.con.in_transaction:
return method(self, *args, **kwargs) return method(self, *args, **kwargs)
try: try:
self.log_debug(f"begin {method.__name__!r} transaction") with self.con:
self.db.execute("begin") self.log_debug(f"begin {method.__name__!r} transaction")
self._transaction_active = True self.db.execute("begin")
result = method(self, *args, **kwargs) result = method(self, *args, **kwargs)
self.db.execute("commit")
self._transaction_active = False
self.log_debug(f"comitted {method.__name__!r} transaction")
return result
except sqlite3.OperationalError as err:
self.log_debug(f"failed {method.__name__!r} transaction {err}")
self.db.rollback()
self._transaction_active = False
except Exception as err: except Exception as err:
self.log_debug(f"failed {method.__name__!r} transaction {err}") self.log_debug(f"failed {method.__name__!r} transaction {err}")
self.db.rollback() if not isinstance(err, sqlite3.OperationalError):
self._transaction_active = False raise
raise if re.match(r"table \w+ has no column named \w+", err.args[0]):
err.add_note("SBY status database can be reset with --statusreset")
raise
else:
self.log_debug(f"comitted {method.__name__!r} transaction")
return result
try: try:
self.log_debug( with self.con:
f"retrying {method.__name__!r} transaction once in immediate mode" self.log_debug(
) f"retrying {method.__name__!r} transaction once in immediate mode"
self.db.execute("begin immediate") )
self._transaction_active = True self.db.execute("begin immediate")
result = method(self, *args, **kwargs) result = method(self, *args, **kwargs)
self.db.execute("commit")
self._transaction_active = False
self.log_debug(f"comitted {method.__name__!r} transaction")
return result
except Exception as err: except Exception as err:
self.log_debug(f"failed {method.__name__!r} transaction {err}") self.log_debug(f"failed {method.__name__!r} transaction {err}")
self.db.rollback()
self._transaction_active = False
raise raise
else:
self.log_debug(f"comitted {method.__name__!r} transaction")
return result
return wrapper # type: ignore return wrapper # type: ignore
class FileInUseError(Exception):
def __init__(self, *args, file: Path|str = "file"):
super().__init__(f"Found {file}, try again later", *args)
class SbyStatusDb: class SbyStatusDb:
def __init__(self, path: Path, task, timeout: float = 5.0): def __init__(self, path: Path, task, timeout: float = 5.0, live_csv = False):
self.debug = False self.debug = False
self.task = task self.task = task
self._transaction_active = False self.live_csv = live_csv
setup = not os.path.exists(path) self.con = sqlite3.connect(path, isolation_level=None, timeout=timeout)
self.db = self.con.cursor()
self.db = sqlite3.connect(path, isolation_level=None, timeout=timeout)
self.db.row_factory = sqlite3.Row self.db.row_factory = sqlite3.Row
self.db.execute("PRAGMA journal_mode=WAL").fetchone() err_count = 0
self.db.execute("PRAGMA synchronous=0").fetchone() err_max = 3
while True:
try:
self.db.execute("PRAGMA journal_mode=WAL")
self.db.execute("PRAGMA synchronous=0")
self.db.execute("PRAGMA foreign_keys=ON")
except sqlite3.OperationalError as err:
if "database is locked" not in err.args[0]:
raise
err_count += 1
if err_count > err_max:
err.add_note(f"Failed to acquire lock after {err_count} attempts, aborting")
raise
backoff = err_count / 10.0
self.log_debug(f"Database locked, retrying in {backoff}s")
time.sleep(backoff)
else:
break
if setup: self._setup()
self._setup()
if task is not None: if task is not None:
self.task_id = self.create_task(workdir=task.workdir, mode=task.opt_mode) self.start_time = time.time()
self.task_id = self.create_task(workdir=task.workdir, name=task.name, mode=task.opt_mode, now=self.start_time)
def log_debug(self, *args): def log_debug(self, *args):
if self.debug: if self.debug:
@ -86,59 +149,25 @@ class SbyStatusDb:
@transaction @transaction
def _setup(self): def _setup(self):
script = """ for statement in SQLSCRIPT.split(";\n"):
CREATE TABLE task ( statement = statement.strip().replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS")
id INTEGER PRIMARY KEY,
workdir TEXT,
mode TEXT,
created REAL
);
CREATE TABLE task_status (
id INTEGER PRIMARY KEY,
task INTEGER,
status TEXT,
data TEXT,
created REAL,
FOREIGN KEY(task) REFERENCES task(id)
);
CREATE TABLE task_property (
id INTEGER PRIMARY KEY,
task INTEGER,
src TEXT,
name TEXT,
created REAL,
FOREIGN KEY(task) REFERENCES task(id)
);
CREATE TABLE task_property_status (
id INTEGER PRIMARY KEY,
task_property INTEGER,
status TEXT,
data TEXT,
created REAL,
FOREIGN KEY(task_property) REFERENCES task_property(id)
);
CREATE TABLE task_property_data (
id INTEGER PRIMARY KEY,
task_property INTEGER,
kind TEXT,
data TEXT,
created REAL,
FOREIGN KEY(task_property) REFERENCES task_property(id)
);
"""
for statement in script.split(";\n"):
statement = statement.strip()
if statement: if statement:
self.db.execute(statement) self.db.execute(statement)
def test_schema(self) -> bool:
schema = self.db.execute("SELECT sql FROM sqlite_master;").fetchall()
schema_script = '\n'.join(str(sql[0] + ';') for sql in schema)
self._tables = re.findall(r"CREATE TABLE (\w+) \(", schema_script)
return schema_script != SQLSCRIPT
@transaction @transaction
def create_task(self, workdir: str, mode: str) -> int: def create_task(self, workdir: str, name: str, mode: str, now:float) -> int:
return self.db.execute( return self.db.execute(
""" """
INSERT INTO task (workdir, mode, created) INSERT INTO task (workdir, name, mode, created)
VALUES (:workdir, :mode, :now) VALUES (:workdir, :name, :mode, :now)
""", """,
dict(workdir=workdir, mode=mode, now=time.time()), dict(workdir=workdir, name=name, mode=mode, now=now),
).lastrowid ).lastrowid
@transaction @transaction
@ -150,14 +179,16 @@ class SbyStatusDb:
now = time.time() now = time.time()
self.db.executemany( self.db.executemany(
""" """
INSERT INTO task_property (name, src, task, created) INSERT INTO task_property (name, src, hdlname, task, kind, created)
VALUES (:name, :src, :task, :now) VALUES (:name, :src, :hdlname, :task, :kind, :now)
""", """,
[ [
dict( dict(
name=json.dumps(prop.path), name=json.dumps(prop.path),
src=prop.location or "", src=prop.location or "",
hdlname=prop.hdlname,
task=task_id, task=task_id,
kind=prop.kind,
now=now, now=now,
) )
for prop in properties for prop in properties
@ -195,55 +226,66 @@ class SbyStatusDb:
def set_task_property_status( def set_task_property_status(
self, self,
property: SbyProperty, property: SbyProperty,
status: Optional[str] = None, trace_id: Optional[int] = None,
data: Any = None, data: Any = None,
): ):
if status is None:
status = property.status
now = time.time() now = time.time()
self.db.execute( self.db.execute(
""" """
INSERT INTO task_property_status ( INSERT INTO task_property_status (
task_property, status, data, created task_property, task_trace, status, data, created
) )
VALUES ( VALUES (
(SELECT id FROM task_property WHERE task = :task AND name = :name), (SELECT id FROM task_property WHERE task = :task AND name = :name),
:status, :data, :now :trace_id, :status, :data, :now
) )
""", """,
dict( dict(
task=self.task_id, task=self.task_id,
trace_id=trace_id,
name=json.dumps(property.path), name=json.dumps(property.path),
status=status, status=property.status,
data=json.dumps(data), data=json.dumps(data),
now=now, now=now,
), ),
) )
if self.live_csv:
row = self.get_status_data_joined(self.db.lastrowid)
csvline = format_status_data_csvline(row)
self.task.log(f"{click.style('csv', fg='yellow')}: {csvline}")
@transaction @transaction
def add_task_property_data(self, property: SbyProperty, kind: str, data: Any): def add_task_trace(
self,
trace: str,
path: str,
kind: str,
engine_case: Optional[str] = None,
task_id: Optional[int] = None,
):
if task_id is None:
task_id = self.task_id
now = time.time() now = time.time()
self.db.execute( return self.db.execute(
""" """
INSERT INTO task_property_data ( INSERT INTO task_trace (
task_property, kind, data, created trace, task, path, engine_case, kind, created
) )
VALUES ( VALUES (
(SELECT id FROM task_property WHERE task = :task AND name = :name), :trace, :task, :path, :engine_case, :kind, :now
:kind, :data, :now
) )
""", """,
dict( dict(
task=self.task_id, trace=trace,
name=json.dumps(property.path), task=task_id,
path=path,
engine_case=engine_case,
kind=kind, kind=kind,
data=json.dumps(data), now=now
now=now, )
), ).lastrowid
)
@transaction
def all_tasks(self): def all_tasks(self):
rows = self.db.execute( rows = self.db.execute(
""" """
@ -253,7 +295,6 @@ class SbyStatusDb:
return {row["id"]: dict(row) for row in rows} return {row["id"]: dict(row) for row in rows}
@transaction
def all_task_properties(self): def all_task_properties(self):
rows = self.db.execute( rows = self.db.execute(
""" """
@ -264,12 +305,10 @@ class SbyStatusDb:
def get_result(row): def get_result(row):
row = dict(row) row = dict(row)
row["name"] = tuple(json.loads(row.get("name", "[]"))) row["name"] = tuple(json.loads(row.get("name", "[]")))
row["data"] = json.loads(row.get("data", "null"))
return row return row
return {row["id"]: get_result(row) for row in rows} return {row["id"]: get_result(row) for row in rows}
@transaction
def all_task_property_statuses(self): def all_task_property_statuses(self):
rows = self.db.execute( rows = self.db.execute(
""" """
@ -285,7 +324,6 @@ class SbyStatusDb:
return {row["id"]: get_result(row) for row in rows} return {row["id"]: get_result(row) for row in rows}
@transaction
def all_status_data(self): def all_status_data(self):
return ( return (
self.all_tasks(), self.all_tasks(),
@ -294,20 +332,35 @@ class SbyStatusDb:
) )
@transaction @transaction
def reset(self): def _reset(self):
self.db.execute("""DELETE FROM task_property_status""") hard_reset = self.test_schema()
self.db.execute("""DELETE FROM task_property_data""") # table names can't be parameters, so we need to use f-strings
self.db.execute("""DELETE FROM task_property""") # but it is safe to use here because it comes from the regex "\w+"
self.db.execute("""DELETE FROM task_status""") for table in self._tables:
self.db.execute("""DELETE FROM task""") if hard_reset:
self.log_debug(f"dropping {table}")
self.db.execute(f"DROP TABLE {table}")
else:
self.log_debug(f"clearing {table}")
self.db.execute(f"DELETE FROM {table}")
if hard_reset:
self._setup()
def print_status_summary(self): def reset(self):
self.db.execute("PRAGMA foreign_keys=OFF")
self._reset()
self.db.execute("PRAGMA foreign_keys=ON")
def print_status_summary(self, latest: bool):
tasks, task_properties, task_property_statuses = self.all_status_data() tasks, task_properties, task_property_statuses = self.all_status_data()
latest_task_ids = filter_latest_task_ids(tasks)
properties = defaultdict(set) properties = defaultdict(set)
uniquify_paths = defaultdict(dict) uniquify_paths = defaultdict(dict)
def add_status(task_property, status): def add_status(task_property, status):
if latest and task_property["task"] not in latest_task_ids:
return
display_name = task_property["name"] display_name = task_property["name"]
if display_name[-1].startswith("$"): if display_name[-1].startswith("$"):
@ -334,6 +387,90 @@ class SbyStatusDb:
for display_name, statuses in sorted(properties.items()): for display_name, statuses in sorted(properties.items()):
print(pretty_path(display_name), combine_statuses(statuses)) print(pretty_path(display_name), combine_statuses(statuses))
def get_status_data_joined(self, status_id: int):
row = self.db.execute(
"""
SELECT task.name as 'task_name', task.mode, task.workdir, task.created, task_property.kind,
task_property.src as 'location', task_property.name, task_property.hdlname, task_property_status.status,
task_property_status.data, task_property_status.created as 'status_created',
task_property_status.id, task_trace.path, task_trace.kind as trace_kind
FROM task
INNER JOIN task_property ON task_property.task=task.id
INNER JOIN task_property_status ON task_property_status.task_property=task_property.id
LEFT JOIN task_trace ON task_property_status.task_trace=task_trace.id
WHERE task_property_status.id=:status_id;
""",
dict(status_id=status_id)
).fetchone()
return parse_status_data_row(row)
def all_status_data_joined(self):
rows = self.db.execute(
"""
SELECT task.id as 'task_id', task.name as 'task_name', task.mode, task.workdir, task.created, task_property.kind,
task_property.src as 'location', task_property.name, task_property.hdlname, task_property_status.status,
task_property_status.data, task_property_status.created as 'status_created',
task_property_status.id, task_trace.path, task_trace.kind as trace_kind
FROM task
INNER JOIN task_property ON task_property.task=task.id
INNER JOIN task_property_status ON task_property_status.task_property=task_property.id
LEFT JOIN task_trace ON task_property_status.task_trace=task_trace.id;
"""
).fetchall()
return {row["id"]: parse_status_data_row(row) for row in rows}
def print_status_summary_csv(self, tasknames: list[str], latest: bool):
# get all statuses
all_properties = self.all_status_data_joined()
latest_task_ids = filter_latest_task_ids(self.all_tasks())
# print csv header
csvheader = format_status_data_csvline(None)
print(csvheader)
# find summary for each task/property combo
prop_map: dict[(str, str), dict[str, (int, int)]] = {}
for row, prop_status in all_properties.items():
if tasknames and prop_status['task_name'] not in tasknames:
continue
if latest and prop_status['task_id'] not in latest_task_ids:
continue
status = prop_status['status']
this_depth = prop_status['data'].get('step')
this_kind = prop_status['trace_kind']
key = (prop_status['task_name'], prop_status['hdlname'])
try:
prop_status_map = prop_map[key]
except KeyError:
prop_map[key] = prop_status_map = {}
current_depth, _ = prop_status_map.get(status, (None, None))
update_map = False
if current_depth is None:
update_map = True
elif this_depth is not None:
if status == 'FAIL' and this_depth < current_depth:
# earliest fail
update_map = True
elif status != 'FAIL' and this_depth > current_depth:
# latest non-FAIL
update_map = True
elif this_depth == current_depth and this_kind in ['fst', 'vcd']:
# prefer traces over witness files
update_map = True
if update_map:
prop_status_map[status] = (this_depth, row)
for prop in prop_map.values():
# ignore UNKNOWNs if there are other statuses
if len(prop) > 1 and "UNKNOWN" in prop:
del prop["UNKNOWN"]
for _, row in prop.values():
csvline = format_status_data_csvline(all_properties[row])
print(csvline)
def combine_statuses(statuses): def combine_statuses(statuses):
statuses = set(statuses) statuses = set(statuses)
@ -342,3 +479,93 @@ def combine_statuses(statuses):
statuses.discard("UNKNOWN") statuses.discard("UNKNOWN")
return ",".join(sorted(statuses)) return ",".join(sorted(statuses))
def parse_status_data_row(raw: sqlite3.Row):
row_dict = dict(raw)
row_dict["name"] = json.loads(row_dict.get("name", "null"))
row_dict["data"] = json.loads(row_dict.get("data") or "{}")
return row_dict
def format_status_data_csvline(row: dict|None) -> str:
if row is None:
csv_header = [
"time",
"task_name",
"mode",
"engine",
"name",
"location",
"kind",
"status",
"trace",
"depth",
]
return ','.join(csv_header)
else:
engine = row['data'].get('engine', row['data'].get('source'))
try:
time = row['status_created'] - row['created']
except TypeError:
time = 0
name = row['hdlname']
depth = row['data'].get('step')
try:
trace_path = Path(row['workdir']) / row['path']
except TypeError:
trace_path = None
csv_line = [
round(time, 2),
row['task_name'],
row['mode'],
engine,
name or pretty_path(row['name']),
row['location'],
row['kind'],
row['status'] or "UNKNOWN",
trace_path,
depth,
]
return ','.join("" if v is None else str(v) for v in csv_line)
def filter_latest_task_ids(all_tasks: dict[int, dict[str]]):
latest: dict[str, int] = {}
for task_id, task_dict in all_tasks.items():
latest[task_dict["workdir"]] = task_id
return list(latest.values())
def remove_db(path):
path = Path(path)
lock_exts = [".sqlite-wal", ".sqlite-shm"]
maybe_locked = False
for lock_file in [path.with_suffix(ext) for ext in lock_exts]:
if lock_file.exists():
# lock file may be a false positive if it wasn't cleaned up
maybe_locked = True
break
if not maybe_locked:
# safe to delete
os.remove(path)
return
# test database directly
with sqlite3.connect(path, isolation_level="EXCLUSIVE", timeout=1) as con:
cur = con.cursor()
# single result rows
cur.row_factory = lambda _, r: r[0]
# changing journal_mode is disallowed if there are multiple connections
try:
cur.execute("PRAGMA journal_mode=DELETE")
except sqlite3.OperationalError as err:
if "database is locked" in err.args[0]:
raise FileInUseError(file=path)
else:
raise
# no other connections, delete all tables
drop_script = cur.execute("SELECT name FROM sqlite_master WHERE type = 'table';").fetchall()
for table in drop_script:
print(table)
cur.execute(f"DROP TABLE {table}")

View file

@ -7,6 +7,7 @@ preunsat
[options] [options]
mode cover mode cover
depth 1 depth 1
cover_assert on
pass: expect pass pass: expect pass
fail: expect fail fail: expect fail

2
tests/statusdb/Makefile Normal file
View file

@ -0,0 +1,2 @@
SUBDIR=statusdb
include ../make/subdir.mk

65
tests/statusdb/mixed.py Normal file
View file

@ -0,0 +1,65 @@
import json
import sqlite3
import sys
def get_prop_type(prop: str):
prop = json.loads(prop or "[]")
name_parts = prop[-1].split("_")
if name_parts[0] == "\\check":
# read_verilog style
# \check_cover_mixed_v...
return name_parts[1]
else:
# verific style
# \assert_auto_verificsva_cc...
return name_parts[0][1:]
def main():
workdir = sys.argv[1]
with open(f"{workdir}/status.path", "r") as status_path_file:
status_path = f"{workdir}/{status_path_file.read().rstrip()}"
# read only database
con = sqlite3.connect(f"file:{status_path}?mode=ro", uri=True)
db = con.cursor()
# custom sql to get all task property statuses for the current workdir
rows = db.execute(
"""
SELECT task.id, task_property.id, task_property.name, task_property.src, task_property_status.status
FROM task
LEFT JOIN task_property ON task_property.task=task.id
LEFT JOIN task_property_status ON task_property_status.task_property=task_property.id
WHERE task.workdir=:workdir
ORDER BY task_property_status.id DESC;
""",
{"workdir": workdir}
).fetchall()
# only check the most recent iteration of the test
last_id = 0
for row_id, _, _, _, _ in rows:
if row_id > last_id:
last_id = row_id
# only check the last status of a property
checked_props = set()
for row_id, prop_id, prop, src, status in rows:
if row_id != last_id:
continue
if prop_id in checked_props:
continue
checked_props.add(prop_id)
prop_type = get_prop_type(prop)
valid_status: list[None|str] = []
if workdir in ["mixed_bmc", "mixed_assert"] and prop_type == "assert":
valid_status = ["FAIL"]
elif workdir == "mixed_bmc" and prop_type == "cover":
valid_status = [None, "UNKNOWN"]
elif workdir == "mixed_assert" and prop_type == "cover":
valid_status = ["PASS", None, "UNKNOWN"]
elif workdir == "mixed_no_assert" and prop_type == "assert":
valid_status = [None, "UNKNOWN"]
elif workdir == "mixed_no_assert" and prop_type == "cover":
valid_status = ["PASS"]
assert status in valid_status, f"Unexpected {prop_type} status {status} for {prop} ({src})"
if __name__ == "__main__":
main()

24
tests/statusdb/mixed.sby Normal file
View file

@ -0,0 +1,24 @@
[tasks]
no_assert cover
assert cover
bmc
[options]
cover: mode cover
bmc: mode bmc
bmc: depth 1
assert: cover_assert on
no_assert: expect pass
~no_assert: expect fail
[engines]
cover: smtbmc boolector
bmc: smtbmc boolector
[script]
read -formal mixed.v
prep -top test
[files]
mixed.v

4
tests/statusdb/mixed.sh Normal file
View file

@ -0,0 +1,4 @@
#!/bin/bash
set -e
python3 $SBY_MAIN -f $SBY_FILE $TASK
python3 ${SBY_FILE%.sby}.py $WORKDIR

16
tests/statusdb/mixed.v Normal file
View file

@ -0,0 +1,16 @@
module test (input CP, CN, input A, B, output reg XP, XN);
reg [7:0] counter = 0;
always @* begin
assume (A || B);
assume (!A || !B);
assert (A == B);
cover (counter == 3 && A);
cover (counter == 3 && B);
end
always @(posedge CP)
counter <= counter + 1;
always @(posedge CP)
XP <= A;
always @(negedge CN)
XN <= B;
endmodule

14
tests/statusdb/reset.sby Normal file
View file

@ -0,0 +1,14 @@
[options]
mode bmc
depth 100
expect fail
[engines]
smtbmc --keep-going boolector
[script]
read -formal reset.sv
prep -top demo
[files]
reset.sv

11
tests/statusdb/reset.sh Normal file
View file

@ -0,0 +1,11 @@
#!/bin/bash
set -e
# run task
python3 $SBY_MAIN -f $SBY_FILE $TASK
# task has 3 properties
python3 $SBY_MAIN -f $SBY_FILE --status | wc -l | grep -q '3'
# resetting task should work
python3 $SBY_MAIN -f $SBY_FILE --statusreset
# clean database should have no properties
python3 $SBY_MAIN -f $SBY_FILE --status | wc -l | grep -q '0'

21
tests/statusdb/reset.sv Normal file
View file

@ -0,0 +1,21 @@
module demo (
input clk,
output reg [5:0] counter
);
initial counter = 0;
always @(posedge clk) begin
if (counter == 15)
counter <= 0;
else
counter <= counter + 1;
end
`ifdef FORMAL
always @(posedge clk) begin
assert (1);
assert (counter < 7);
assert (0);
end
`endif
endmodule

130
tests/statusdb/timeout.sby Normal file
View file

@ -0,0 +1,130 @@
[tasks]
btor_bmc: btor bmc
btor_fin_bmc: btor bmc fin
pono_bmc: pono bmc
pono_fin_bmc: pono bmc fin
btor_cover: btor cover
btor_fin_cover: btor cover fin
smt_bmc: smt bmc
smt_fin_bmc: smt bmc fin
smt_cover: smt cover
smt_fin_cover: smt cover fin
smt_prove: smt prove
smt_fin_prove: smt prove fin
smt_fail: smtfail bmc fail
smt_fin_fail: smtfail bmc fail fin
aig_bmc: aigbmc bmc
aig_fin_bmc: aigbmc bmc fin
aig_prove: aiger prove
aig_fin_prove: aiger prove fin
abc_bmc: abcbmc bmc
abc_fin_bmc: abcbmc bmc fin
abc_prove: abc prove
abc_fin_prove: abc prove fin
abc_fail: abcfail prove fail
abc_fin_fail: abcfail prove fail fin
[options]
bmc: mode bmc
cover: mode cover
prove: mode prove
fin:
expect PASS,FAIL,UNKNOWN
depth 10
--
~fin:
expect TIMEOUT
depth 40000
timeout 1
--
[engines]
btor: btor btormc
pono: btor pono
smt: smtbmc bitwuzla
smtfail: smtbmc --keep-going bitwuzla
aigbmc: aiger aigbmc
aiger: aiger suprove
abcbmc: abc bmc3
abc: abc pdr
abcfail: abc --keep-going pdr
[script]
fin: read -define WIDTH=4
~fin: read -define WIDTH=8
fail: read -define FAIL=1
read -sv timeout.sv
prep -top top
[file timeout.sv]
module top #(
parameter WIDTH = `WIDTH
) (
input clk,
input load,
input [WIDTH-1:0] a,
input [WIDTH-1:0] b,
output reg [WIDTH-1:0] q,
output reg [WIDTH-1:0] r,
output reg done
);
reg [WIDTH-1:0] a_reg = 0;
reg [WIDTH-1:0] b_reg = 1;
initial begin
q <= 0;
r <= 0;
done <= 1;
end
reg [WIDTH-1:0] q_step = 1;
reg [WIDTH-1:0] r_step = 1;
// This is not how you design a good divider circuit!
always @(posedge clk) begin
if (load) begin
a_reg <= a;
b_reg <= b;
q <= 0;
r <= a;
q_step <= 1;
r_step <= b;
done <= b == 0;
end else begin
if (r_step <= r) begin
q <= q + q_step;
r <= r - r_step;
if (!r_step[WIDTH-1]) begin
r_step <= r_step << 1;
q_step <= q_step << 1;
end
end else begin
if (!q_step[0]) begin
r_step <= r_step >> 1;
q_step <= q_step >> 1;
end else begin
done <= 1;
end
end
end
end
always @(posedge clk) begin
assert (1); // trivial
`ifdef FAIL
assert (0);
`endif
assert (r_step == b_reg * q_step); // Helper invariant
assert (q * b_reg + r == a_reg); // Main invariant & correct output relationship
if (done) assert (r <= b_reg - 1); // Output range
cover (done);
cover (done && b_reg == 0);
cover (r != a_reg);
cover (r == a_reg);
cover (0); // unreachable
end
endmodule

22
tests/statusdb/timeout.sh Normal file
View file

@ -0,0 +1,22 @@
#!/bin/bash
set -e
python3 $SBY_MAIN -f $SBY_FILE $TASK
STATUS_CSV=${WORKDIR}/status.csv
python3 $SBY_MAIN -f $SBY_FILE $TASK --statuscsv --latest | tee $STATUS_CSV
if [[ $TASK =~ "_cover" ]]; then
wc -l $STATUS_CSV | grep -q '6'
grep "COVER" $STATUS_CSV | wc -l | grep -q '5'
elif [[ $TASK =~ "_fail" ]]; then
wc -l $STATUS_CSV | grep -q '6'
grep "ASSERT" $STATUS_CSV | wc -l | grep -q '5'
grep "FAIL" $STATUS_CSV | wc -l | grep -q '1'
else
wc -l $STATUS_CSV | grep -q '5'
grep "ASSERT" $STATUS_CSV | wc -l | grep -q '4'
fi
if [[ $TASK == "smt_cover" ]]; then
grep "PASS" $STATUS_CSV | wc -l | grep -q '4'
fi

View file

@ -8,6 +8,9 @@ cover: mode cover
bmc: mode bmc bmc: mode bmc
bmc: depth 1 bmc: depth 1
cover: expect pass
~cover: expect fail
[engines] [engines]
cover: btor btormc cover: btor btormc
btormc: btor btormc btormc: btor btormc

View file

@ -3,7 +3,7 @@ module test (input CP, CN, input A, B, output reg XP, XN);
always @* begin always @* begin
assume (A || B); assume (A || B);
assume (!A || !B); assume (!A || !B);
assert (A != B); assert (A == B);
cover (counter == 3 && A); cover (counter == 3 && A);
cover (counter == 3 && B); cover (counter == 3 && B);
end end

View file

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "addr2line" name = "addr2line"
@ -30,63 +30,65 @@ dependencies = [
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.4" version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"anstyle-parse", "anstyle-parse",
"anstyle-query", "anstyle-query",
"anstyle-wincon", "anstyle-wincon",
"colorchoice", "colorchoice",
"is_terminal_polyfill",
"utf8parse", "utf8parse",
] ]
[[package]] [[package]]
name = "anstyle" name = "anstyle"
version = "1.0.4" version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]] [[package]]
name = "anstyle-parse" name = "anstyle-parse"
version = "0.2.2" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [ dependencies = [
"utf8parse", "utf8parse",
] ]
[[package]] [[package]]
name = "anstyle-query" name = "anstyle-query"
version = "1.0.0" version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]] [[package]]
name = "anstyle-wincon" name = "anstyle-wincon"
version = "3.0.1" version = "3.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"once_cell",
"windows-sys", "windows-sys",
] ]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.69" version = "0.3.71"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
dependencies = [ dependencies = [
"addr2line", "addr2line",
"cc", "cc",
@ -99,17 +101,17 @@ dependencies = [
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.4.1" version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.83" version = "1.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c"
dependencies = [ dependencies = [
"libc", "shlex",
] ]
[[package]] [[package]]
@ -120,9 +122,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.4.8" version = "4.5.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -130,9 +132,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.4.8" version = "4.5.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -143,9 +145,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.4.7" version = "4.5.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -155,15 +157,15 @@ dependencies = [
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.6.0" version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]] [[package]]
name = "color-eyre" name = "color-eyre"
version = "0.6.2" version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"color-spantrace", "color-spantrace",
@ -176,9 +178,9 @@ dependencies = [
[[package]] [[package]]
name = "color-spantrace" name = "color-spantrace"
version = "0.2.0" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"owo-colors", "owo-colors",
@ -188,15 +190,15 @@ dependencies = [
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
version = "1.0.0" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.6" version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys", "windows-sys",
@ -204,9 +206,9 @@ dependencies = [
[[package]] [[package]]
name = "eyre" name = "eyre"
version = "0.6.8" version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
dependencies = [ dependencies = [
"indenter", "indenter",
"once_cell", "once_cell",
@ -224,9 +226,9 @@ dependencies = [
[[package]] [[package]]
name = "flussab-aiger" name = "flussab-aiger"
version = "0.1.0" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "378b3a9970d0163162e8b3c9a4d9b2eef98be95d624cbac5b207278b157886d2" checksum = "b02e7477c54279a2b99ede3a9248a66a254a1f55ba377ac465329af596737a89"
dependencies = [ dependencies = [
"flussab", "flussab",
"num-traits", "num-traits",
@ -236,15 +238,15 @@ dependencies = [
[[package]] [[package]]
name = "gimli" name = "gimli"
version = "0.28.0" version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.4.1" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]] [[package]]
name = "indenter" name = "indenter"
@ -252,6 +254,12 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]] [[package]]
name = "itoap" name = "itoap"
version = "1.0.1" version = "1.0.1"
@ -260,60 +268,60 @@ checksum = "9028f49264629065d057f340a86acb84867925865f73bbf8d47b4d149a7e88b8"
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.150" version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.11" version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.6.4" version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.7.1" version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
dependencies = [ dependencies = [
"adler", "adler",
] ]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.17" version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [ dependencies = [
"autocfg", "autocfg",
] ]
[[package]] [[package]]
name = "object" name = "object"
version = "0.32.1" version = "0.32.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.18.0" version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]] [[package]]
name = "owo-colors" name = "owo-colors"
@ -323,39 +331,39 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.13" version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.69" version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.33" version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.23" version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.24" version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ad981d6c340a49cdc40a1028d9c6084ec7e9fa33fcb839cab656a267071e234" checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"errno", "errno",
@ -374,16 +382,22 @@ dependencies = [
] ]
[[package]] [[package]]
name = "strsim" name = "shlex"
version = "0.10.0" version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.39" version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -392,9 +406,9 @@ dependencies = [
[[package]] [[package]]
name = "terminal_size" name = "terminal_size"
version = "0.3.0" version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed"
dependencies = [ dependencies = [
"rustix", "rustix",
"windows-sys", "windows-sys",
@ -402,18 +416,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.50" version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.50" version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -422,9 +436,9 @@ dependencies = [
[[package]] [[package]]
name = "thread_local" name = "thread_local"
version = "1.1.7" version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
@ -432,9 +446,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.40" version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [ dependencies = [
"pin-project-lite", "pin-project-lite",
"tracing-core", "tracing-core",
@ -442,9 +456,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing-core" name = "tracing-core"
version = "0.1.32" version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"valuable", "valuable",
@ -452,9 +466,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing-error" name = "tracing-error"
version = "0.2.0" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db"
dependencies = [ dependencies = [
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
@ -462,9 +476,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing-subscriber" name = "tracing-subscriber"
version = "0.3.18" version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
dependencies = [ dependencies = [
"sharded-slab", "sharded-slab",
"thread_local", "thread_local",
@ -473,40 +487,41 @@ dependencies = [
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.12" version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.2.1" version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "valuable" name = "valuable"
version = "0.1.0" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.48.0" version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [ dependencies = [
"windows-targets", "windows-targets",
] ]
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.48.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm", "windows_aarch64_gnullvm",
"windows_aarch64_msvc", "windows_aarch64_msvc",
"windows_i686_gnu", "windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc", "windows_i686_msvc",
"windows_x86_64_gnu", "windows_x86_64_gnu",
"windows_x86_64_gnullvm", "windows_x86_64_gnullvm",
@ -515,45 +530,51 @@ dependencies = [
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.48.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.48.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.48.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.48.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.48.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.48.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.48.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "zwohash" name = "zwohash"