Merge pull request #195 from jix/sbyproc-truncated-output
[SymbiYosys.git] / sbysrc / sby_engine_btor.py
1 #
2 # SymbiYosys (sby) -- Front-end for Yosys-based formal verification flows
3 #
4 # Copyright (C) 2016 Claire Xenia Wolf <claire@yosyshq.com>
5 #
6 # Permission to use, copy, modify, and/or distribute this software for any
7 # purpose with or without fee is hereby granted, provided that the above
8 # copyright notice and this permission notice appear in all copies.
9 #
10 # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 #
18
19 import re, os, getopt
20 from types import SimpleNamespace
21 from sby_core import SbyProc
22
23 def run(mode, task, engine_idx, engine):
24 random_seed = None
25
26 opts, solver_args = getopt.getopt(engine[1:], "", ["seed="])
27
28 if len(solver_args) == 0:
29 task.error("Missing solver command.")
30
31 for o, a in opts:
32 if o == "--seed":
33 random_seed = a
34 else:
35 task.error("Unexpected BTOR engine options.")
36
37 if solver_args[0] == "btormc":
38 solver_cmd = ""
39 if random_seed:
40 solver_cmd += f"BTORSEED={random_seed} "
41 solver_cmd += task.exe_paths["btormc"] + f""" --stop-first {0 if mode == "cover" else 1} -v 1 -kmax {task.opt_depth - 1}"""
42 if task.opt_skip is not None:
43 solver_cmd += f" -kmin {task.opt_skip}"
44 solver_cmd += " ".join([""] + solver_args[1:])
45
46 elif solver_args[0] == "pono":
47 if random_seed:
48 task.error("Setting the random seed is not available for the pono solver.")
49 if task.opt_skip is not None:
50 task.error("The btor engine supports the option skip only for the btormc solver.")
51 solver_cmd = task.exe_paths["pono"] + f" --witness -v 1 -e bmc -k {task.opt_depth - 1}"
52 solver_cmd += " ".join([""] + solver_args[1:])
53
54 else:
55 task.error(f"Invalid solver command {solver_args[0]}.")
56
57 common_state = SimpleNamespace()
58 common_state.solver_status = None
59 common_state.produced_cex = 0
60 common_state.expected_cex = 1
61 common_state.wit_file = None
62 common_state.assert_fail = False
63 common_state.produced_traces = []
64 common_state.print_traces_max = 5
65 common_state.running_procs = 0
66
67 def print_traces_and_terminate():
68 if mode == "cover":
69 if common_state.assert_fail:
70 proc_status = "FAIL"
71 elif common_state.expected_cex == 0:
72 proc_status = "pass"
73 elif common_state.solver_status == "sat":
74 proc_status = "pass"
75 elif common_state.solver_status == "unsat":
76 proc_status = "FAIL"
77 else:
78 task.error(f"engine_{engine_idx}: Engine terminated without status.")
79 else:
80 if common_state.expected_cex == 0:
81 proc_status = "pass"
82 elif common_state.solver_status == "sat":
83 proc_status = "FAIL"
84 elif common_state.solver_status == "unsat":
85 proc_status = "pass"
86 else:
87 task.error(f"engine_{engine_idx}: Engine terminated without status.")
88
89 task.update_status(proc_status.upper())
90 task.log(f"engine_{engine_idx}: Status returned by engine: {proc_status}")
91 task.summary.append(f"""engine_{engine_idx} ({" ".join(engine)}) returned {proc_status}""")
92
93 if len(common_state.produced_traces) == 0:
94 task.log(f"""engine_{engine_idx}: Engine did not produce a{" counter" if mode != "cover" else "n "}example.""")
95 elif len(common_state.produced_traces) <= common_state.print_traces_max:
96 task.summary.extend(common_state.produced_traces)
97 else:
98 task.summary.extend(common_state.produced_traces[:common_state.print_traces_max])
99 excess_traces = len(common_state.produced_traces) - common_state.print_traces_max
100 task.summary.append(f"""and {excess_traces} further trace{"s" if excess_traces > 1 else ""}""")
101
102 task.terminate()
103
104 if mode == "cover":
105 def output_callback2(line):
106 match = re.search(r"Assert failed in test", line)
107 if match:
108 common_state.assert_fail = True
109 return line
110 else:
111 def output_callback2(line):
112 return line
113
114 def make_exit_callback(suffix):
115 def exit_callback2(retcode):
116 vcdpath = f"{task.workdir}/engine_{engine_idx}/trace{suffix}.vcd"
117 if os.path.exists(vcdpath):
118 common_state.produced_traces.append(f"""{"" if mode == "cover" else "counterexample "}trace: {vcdpath}""")
119
120 common_state.running_procs -= 1
121 if (common_state.running_procs == 0):
122 print_traces_and_terminate()
123
124 return exit_callback2
125
126 def output_callback(line):
127 if mode == "cover":
128 if solver_args[0] == "btormc":
129 match = re.search(r"calling BMC on ([0-9]+) properties", line)
130 if match:
131 common_state.expected_cex = int(match[1])
132 if common_state.produced_cex != 0:
133 task.error(f"engine_{engine_idx}: Unexpected engine output (property count).")
134
135 else:
136 task.error(f"engine_{engine_idx}: BTOR solver '{solver_args[0]}' is currently not supported in cover mode.")
137
138 if (common_state.produced_cex < common_state.expected_cex) and line == "sat":
139 if common_state.wit_file != None:
140 task.error(f"engine_{engine_idx}: Unexpected engine output (sat).")
141 if common_state.expected_cex == 1:
142 common_state.wit_file = open(f"{task.workdir}/engine_{engine_idx}/trace.wit", "w")
143 else:
144 common_state.wit_file = open(f"""{task.workdir}/engine_{engine_idx}/trace{common_state.produced_cex}.wit""", "w")
145 if solver_args[0] != "btormc":
146 proc.log("Found satisfiability witness.")
147
148 if common_state.wit_file:
149 print(line, file=common_state.wit_file)
150 if line == ".":
151 if common_state.expected_cex == 1:
152 suffix = ""
153 else:
154 suffix = common_state.produced_cex
155 proc2 = SbyProc(
156 task,
157 f"engine_{engine_idx}_{common_state.produced_cex}",
158 task.model("btor"),
159 "cd {dir} ; btorsim -c --vcd engine_{idx}/trace{i}.vcd --hierarchical-symbols --info model/design_btor{s}.info model/design_btor{s}.btor engine_{idx}/trace{i}.wit".format(dir=task.workdir, idx=engine_idx, i=suffix, s='_single' if solver_args[0] == 'pono' else ''),
160 logfile=open(f"{task.workdir}/engine_{engine_idx}/logfile2.txt", "w")
161 )
162 proc2.output_callback = output_callback2
163 proc2.exit_callback = make_exit_callback(suffix)
164 proc2.checkretcode = True
165 common_state.running_procs += 1
166
167 common_state.produced_cex += 1
168 common_state.wit_file.close()
169 common_state.wit_file = None
170 if common_state.produced_cex == common_state.expected_cex:
171 common_state.solver_status = "sat"
172
173 else:
174 if solver_args[0] == "btormc":
175 if "calling BMC on" in line:
176 return line
177 if "SATISFIABLE" in line:
178 return line
179 if "bad state properties at bound" in line:
180 return line
181 if "deleting model checker:" in line:
182 if common_state.solver_status is None:
183 common_state.solver_status = "unsat"
184 return line
185
186 elif solver_args[0] == "pono":
187 if line == "unknown":
188 if common_state.solver_status is None:
189 common_state.solver_status = "unsat"
190 return "No CEX found."
191 if line not in ["b0"]:
192 return line
193
194 print(line, file=proc.logfile)
195
196 return None
197
198 def exit_callback(retcode):
199 if common_state.expected_cex != 0:
200 if common_state.solver_status is None:
201 task.error(f"engine_{engine_idx}: Could not determine engine status.")
202
203 if common_state.solver_status == "unsat":
204 if common_state.expected_cex == 1:
205 with open(f"""{task.workdir}/engine_{engine_idx}/trace.wit""", "w") as wit_file:
206 print("unsat", file=wit_file)
207 else:
208 for i in range(common_state.produced_cex, common_state.expected_cex):
209 with open(f"{task.workdir}/engine_{engine_idx}/trace{i}.wit", "w") as wit_file:
210 print("unsat", file=wit_file)
211
212 common_state.running_procs -= 1
213 if (common_state.running_procs == 0):
214 print_traces_and_terminate()
215
216 proc = SbyProc(
217 task,
218 f"engine_{engine_idx}", task.model("btor"),
219 f"cd {task.workdir}; {solver_cmd} model/design_btor{'_single' if solver_args[0]=='pono' else ''}.btor",
220 logfile=open(f"{task.workdir}/engine_{engine_idx}/logfile.txt", "w")
221 )
222 proc.checkretcode = True
223 if solver_args[0] == "pono":
224 proc.retcodes = [0, 1, 255] # UNKNOWN = -1, FALSE = 0, TRUE = 1, ERROR = 2
225 proc.output_callback = output_callback
226 proc.exit_callback = exit_callback
227 common_state.running_procs += 1