Skip to content

Commit 52f5f94

Browse files
committed
Initial support for a multi-task property status database
This adds initial support for an sqlite database that is shared across multiple tasks of a single SBY file and that can track the status of individual properties. The amount of information tracked in the database is currently quite minimal and depends on the engine and options used. This can be incrementally extended in the future. The ways in which the information in the database can be queries is even more limited for this initial version, consisting of a single '--status' option which lists all properties and their status.
1 parent 5c649c8 commit 52f5f94

8 files changed

+463
-5
lines changed

sbysrc/sby.py

+45
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from sby_cmdline import parser_func
2323
from sby_core import SbyConfig, SbyTask, SbyAbort, SbyTaskloop, process_filename, dress_message
2424
from sby_jobserver import SbyJobClient, process_jobserver_environment
25+
from sby_status import SbyStatusDb
2526
import time, platform, click
2627

2728
process_jobserver_environment() # needs to be called early
@@ -55,6 +56,39 @@
5556
sequential = args.sequential
5657
jobcount = args.jobcount
5758
init_config_file = args.init_config_file
59+
status_show = args.status
60+
status_reset = args.status_reset
61+
62+
if status_show or status_reset:
63+
target = workdir_prefix or workdir or sbyfile
64+
if not os.path.isdir(target) and target.endswith('.sby'):
65+
target = target[:-4]
66+
if not os.path.isdir(target):
67+
print(f"ERROR: No directory found at {target!r}.", file=sys.stderr)
68+
sys.exit(1)
69+
70+
try:
71+
with open(f"{target}/status.path", "r") as status_path_file:
72+
status_path = f"{target}/{status_path_file.read().rstrip()}"
73+
except FileNotFoundError:
74+
status_path = f"{target}/status.sqlite"
75+
76+
if not os.path.exists(status_path):
77+
print(f"ERROR: No status database found at {status_path!r}.", file=sys.stderr)
78+
sys.exit(1)
79+
80+
status_db = SbyStatusDb(status_path, task=None)
81+
82+
if status_show:
83+
status_db.print_status_summary()
84+
sys.exit(0)
85+
86+
if status_reset:
87+
status_db.reset()
88+
89+
status_db.db.close()
90+
sys.exit(0)
91+
5892

5993
if sbyfile is not None:
6094
if os.path.isdir(sbyfile):
@@ -356,6 +390,7 @@ def start_task(taskloop, taskname):
356390

357391
my_opt_tmpdir = opt_tmpdir
358392
my_workdir = None
393+
my_status_db = None
359394

360395
if workdir is not None:
361396
my_workdir = workdir
@@ -364,10 +399,12 @@ def start_task(taskloop, taskname):
364399
my_workdir = workdir_prefix
365400
else:
366401
my_workdir = workdir_prefix + "_" + taskname
402+
my_status_db = f"../{os.path.basename(workdir_prefix)}/status.sqlite"
367403

368404
if my_workdir is None and sbyfile is not None and not my_opt_tmpdir:
369405
my_workdir = sbyfile[:-4]
370406
if taskname is not None:
407+
my_status_db = f"../{os.path.basename(my_workdir)}/status.sqlite"
371408
my_workdir += "_" + taskname
372409

373410
if my_workdir is not None:
@@ -399,6 +436,14 @@ def start_task(taskloop, taskname):
399436
with open(f"{my_workdir}/.gitignore", "w") as gitignore:
400437
print("*", file=gitignore)
401438

439+
if my_status_db is not None:
440+
os.makedirs(f"{my_workdir}/{os.path.dirname(my_status_db)}", exist_ok=True)
441+
if os.getenv("SBY_WORKDIR_GITIGNORE"):
442+
with open(f"{my_workdir}/{os.path.dirname(my_status_db)}/.gitignore", "w") as gitignore:
443+
print("*", file=gitignore)
444+
with open(f"{my_workdir}/status.path", "w") as status_path:
445+
print(my_status_db, file=status_path)
446+
402447
junit_ts_name = os.path.basename(sbyfile[:-4]) if sbyfile is not None else workdir if workdir is not None else "stdin"
403448
junit_tc_name = taskname if taskname is not None else "default"
404449

sbysrc/sby_autotune.py

+1
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ def log(self, message):
378378

379379
def run(self):
380380
self.task.handle_non_engine_options()
381+
self.task.setup_status_db(':memory:')
381382
self.config = self.task.autotune_config or SbyAutotuneConfig()
382383

383384
if "expect" not in self.task.options:

sbysrc/sby_cmdline.py

+5
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ def parser_func():
6969
parser.add_argument("--setup", action="store_true", dest="setupmode",
7070
help="set up the working directory and exit")
7171

72+
parser.add_argument("--status", action="store_true", dest="status",
73+
help="summarize the contents of the status database")
74+
parser.add_argument("--statusreset", action="store_true", dest="status_reset",
75+
help="reset the contents of the status database")
76+
7277
parser.add_argument("--init-config-file", dest="init_config_file",
7378
help="create a default .sby config file")
7479
parser.add_argument("sbyfile", metavar="<jobname>.sby | <dirname>", nargs="?",

sbysrc/sby_core.py

+50-4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from select import select
2828
from time import monotonic, localtime, sleep, strftime
2929
from sby_design import SbyProperty, SbyModule, design_hierarchy
30+
from sby_status import SbyStatusDb
3031

3132
all_procs_running = []
3233

@@ -674,20 +675,41 @@ def engine_summary(self, engine_idx):
674675
self.engine_summaries[engine_idx] = SbyEngineSummary(engine_idx)
675676
return self.engine_summaries[engine_idx]
676677

677-
def add_event(self, *args, **kwargs):
678+
def add_event(self, *args, update_status=True, **kwargs):
678679
event = SbySummaryEvent(*args, **kwargs)
680+
681+
engine = self.engine_summary(event.engine_idx)
682+
683+
if update_status:
684+
status_metadata = dict(source="summary_event", engine=engine.engine)
685+
679686
if event.prop:
680687
if event.type == "$assert":
681688
event.prop.status = "FAIL"
682689
if event.path:
683690
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+
)
684697
if event.prop:
685698
if event.type == "$cover":
686699
event.prop.status = "PASS"
687700
if event.path:
688701
event.prop.tracefiles.append(event.path)
689-
690-
engine = self.engine_summary(event.engine_idx)
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+
)
708+
if event.prop and update_status:
709+
self.task.status_db.set_task_property_status(
710+
event.prop,
711+
data=status_metadata
712+
)
691713

692714
if event.trace not in engine.traces:
693715
engine.traces[event.trace] = SbyTraceSummary(event.trace, path=event.path, engine_case=event.engine_case)
@@ -1041,6 +1063,10 @@ def instance_hierarchy_callback(retcode):
10411063
if self.design == None:
10421064
with open(f"{self.workdir}/model/design.json") as f:
10431065
self.design = design_hierarchy(f)
1066+
self.status_db.create_task_properties([
1067+
prop for prop in self.design.properties_by_path.values()
1068+
if not prop.type.assume_like
1069+
])
10441070

10451071
def instance_hierarchy_error_callback(retcode):
10461072
self.precise_prop_status = False
@@ -1186,8 +1212,13 @@ def proc_failed(self, proc):
11861212
self.status = "ERROR"
11871213
self.terminate()
11881214

1215+
def pass_unknown_asserts(self, data):
1216+
for prop in self.design.pass_unknown_asserts():
1217+
self.status_db.set_task_property_status(prop, data=data)
1218+
11891219
def update_status(self, new_status):
11901220
assert new_status in ["PASS", "FAIL", "UNKNOWN", "ERROR"]
1221+
self.status_db.set_task_status(new_status)
11911222

11921223
if new_status == "UNKNOWN":
11931224
return
@@ -1199,7 +1230,7 @@ def update_status(self, new_status):
11991230
assert self.status != "FAIL"
12001231
self.status = "PASS"
12011232
if self.opt_mode in ("bmc", "prove") and self.design:
1202-
self.design.pass_unknown_asserts()
1233+
self.pass_unknown_asserts(dict(source="task_status"))
12031234

12041235
elif new_status == "FAIL":
12051236
assert self.status != "PASS"
@@ -1258,6 +1289,19 @@ def handle_non_engine_options(self):
12581289

12591290
self.handle_bool_option("assume_early", True)
12601291

1292+
def setup_status_db(self, status_path=None):
1293+
if hasattr(self, 'status_db'):
1294+
return
1295+
1296+
if status_path is None:
1297+
try:
1298+
with open(f"{self.workdir}/status.path", "r") as status_path_file:
1299+
status_path = f"{self.workdir}/{status_path_file.read().rstrip()}"
1300+
except FileNotFoundError:
1301+
status_path = f"{self.workdir}/status.sqlite"
1302+
1303+
self.status_db = SbyStatusDb(status_path, self)
1304+
12611305
def setup_procs(self, setupmode):
12621306
self.handle_non_engine_options()
12631307
if self.opt_smtc is not None:
@@ -1285,6 +1329,8 @@ def setup_procs(self, setupmode):
12851329
self.retcode = 0
12861330
return
12871331

1332+
self.setup_status_db()
1333+
12881334
if self.opt_make_model is not None:
12891335
for name in self.opt_make_model.split(","):
12901336
self.model(name.strip())

sbysrc/sby_design.py

+7
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ def from_flavor(c, name):
8888
return c.FAIR
8989
raise ValueError("Unknown property type: " + name)
9090

91+
@property
92+
def assume_like(self):
93+
return self in [self.ASSUME, self.FAIR]
94+
9195
name: str
9296
path: Tuple[str, ...]
9397
type: Type
@@ -171,9 +175,12 @@ class SbyDesign:
171175
properties_by_path: dict = field(default_factory=dict)
172176

173177
def pass_unknown_asserts(self):
178+
updated = []
174179
for prop in self.hierarchy:
175180
if prop.type == prop.Type.ASSERT and prop.status == "UNKNOWN":
176181
prop.status = "PASS"
182+
updated.append(prop)
183+
return updated
177184

178185

179186
def cell_path(cell):

sbysrc/sby_engine_smtbmc.py

+5
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ def output_callback(line):
233233
cell_name = match[3] or match[2]
234234
prop = task.design.hierarchy.find_property_by_cellname(cell_name, trans_dict=smt2_trans)
235235
prop.status = "FAIL"
236+
task.status_db.set_task_property_status(prop, data=dict(source="smtbmc", engine=f"engine_{engine_idx}"))
236237
last_prop.append(prop)
237238
return line
238239

@@ -241,6 +242,7 @@ def output_callback(line):
241242
cell_name = match[2] or match[1]
242243
prop = task.design.hierarchy.find_property_by_cellname(cell_name, trans_dict=smt2_trans)
243244
prop.status = "PASS"
245+
task.status_db.set_task_property_status(prop, data=dict(source="smtbmc", engine=f"engine_{engine_idx}"))
244246
last_prop.append(prop)
245247
return line
246248

@@ -271,6 +273,7 @@ def output_callback(line):
271273
cell_name = match[2]
272274
prop = task.design.hierarchy.find_property_by_cellname(cell_name, trans_dict=smt2_trans)
273275
prop.status = "FAIL"
276+
task.status_db.set_task_property_status(prop, data=dict(source="smtbmc", engine=f"engine_{engine_idx}"))
274277

275278
return line
276279

@@ -288,6 +291,8 @@ def exit_callback(retcode):
288291
def last_exit_callback():
289292
if mode == "bmc" or mode == "cover":
290293
task.update_status(proc_status)
294+
if proc_status == "FAIL" and mode == "bmc" and keep_going:
295+
task.pass_unknown_asserts(dict(source="smtbmc", keep_going=True, engine=f"engine_{engine_idx}"))
291296
proc_status_lower = proc_status.lower() if proc_status == "PASS" else proc_status
292297
task.summary.set_engine_status(engine_idx, proc_status_lower)
293298

0 commit comments

Comments
 (0)