3 # SymbiYosys (sby) -- Front-end for Yosys-based formal verification flows
5 # Copyright (C) 2016 Clifford Wolf <clifford@clifford.at>
7 # Permission to use, copy, modify, and/or distribute this software for any
8 # purpose with or without fee is hereby granted, provided that the above
9 # copyright notice and this permission notice appear in all copies.
11 # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 import os
, sys
, getopt
, shutil
, tempfile
22 from sby_core
import SbyJob
, SbyAbort
23 from time
import localtime
40 sby [options] [<jobname>.sby [tasknames] | <dirname>]
43 set workdir name. default: <jobname> (without .sby)
46 remove workdir if it already exists
49 backup workdir if it already exists
52 run in a temporary workdir (remove when finished)
55 add taskname (useful when sby file is read from stdin)
58 throw an exception (incl stack trace) for most errors
60 --yosys <path_to_executable>
61 --abc <path_to_executable>
62 --smtbmc <path_to_executable>
63 --suprove <path_to_executable>
64 --aigbmc <path_to_executable>
65 --avy <path_to_executable>
66 --btormc <path_to_executable>
67 configure which executable to use for the respective tool
70 print the pre-processed configuration file
73 print the list of tasks
76 set up the working directory and exit
81 opts
, args
= getopt
.getopt(sys
.argv
[1:], "d:btfT:E", ["yosys=",
82 "abc=", "smtbmc=", "suprove=", "aigbmc=", "avy=", "btormc=",
83 "dumpcfg", "dumptasks", "setup"])
101 exe_paths
["yosys"] = a
104 elif o
== "--smtbmc":
105 exe_paths
["smtbmc"] = a
106 elif o
== "--suprove":
107 exe_paths
["suprove"] = a
108 elif o
== "--aigbmc":
109 exe_paths
["aigbmc"] = a
112 elif o
== "--btormc":
113 exe_paths
["btormc"] = a
114 elif o
== "--dumpcfg":
116 elif o
== "--dumptasks":
125 if os
.path
.isdir(sbyfile
):
127 sbyfile
+= "/config.sby"
129 if not opt_force
and os
.path
.exists(workdir
+ "/model"):
130 print("ERROR: Use -f to re-run in existing directory.", file=sys
.stderr
)
133 print("ERROR: Can't use tasks when running in existing directory.", file=sys
.stderr
)
136 print("ERROR: Can't use --setup with existing directory.", file=sys
.stderr
)
139 for f
in "PASS FAIL UNKNOWN ERROR TIMEOUT".split():
140 if os
.path
.exists(workdir
+ "/" + f
):
141 os
.remove(workdir
+ "/" + f
)
142 elif not sbyfile
.endswith(".sby"):
143 print("ERROR: Sby file does not have .sby file extension.", file=sys
.stderr
)
150 early_logmsgs
= list()
152 def early_log(workdir
, msg
):
154 early_logmsgs
.append("SBY %2d:%02d:%02d [%s] %s" % (tm
.tm_hour
, tm
.tm_min
, tm
.tm_sec
, workdir
, msg
))
155 print(early_logmsgs
[-1])
158 def read_sbyconfig(sbydata
, taskname
):
163 tasks_section
= False
164 task_tags_active
= set()
165 task_tags_all
= set()
166 task_skip_block
= False
167 task_skiping_blocks
= False
169 def handle_line(line
):
170 nonlocal pycode
, tasks_section
, task_tags_active
, task_tags_all
171 nonlocal task_skip_block
, task_skiping_blocks
173 line
= line
.rstrip("\n")
174 line
= line
.rstrip("\r")
176 if pycode
is not None:
177 if line
== "--pycode-end--":
178 gdict
= globals().copy()
179 gdict
["task"] = taskname
180 gdict
["tags"] = set(task_tags_active
)
181 gdict
["output_lines"] = list()
182 exec("def output(line):\n output_lines.append(line)\n" + pycode
, gdict
)
184 for line
in gdict
["output_lines"]:
187 pycode
+= line
+ "\n"
190 if line
== "--pycode-begin--":
194 if tasks_section
and line
.startswith("["):
195 tasks_section
= False
197 if task_skiping_blocks
:
199 task_skip_block
= False
200 task_skiping_blocks
= False
203 found_task_tag
= False
204 task_skip_line
= False
206 for t
in task_tags_all
:
207 if line
.startswith(t
+":"):
208 line
= line
[len(t
)+1:].lstrip()
209 match
= t
in task_tags_active
210 elif line
.startswith("~"+t
+":"):
211 line
= line
[len(t
)+2:].lstrip()
212 match
= t
not in task_tags_active
217 task_skiping_blocks
= True
218 task_skip_block
= not match
219 task_skip_line
= True
221 task_skip_line
= not match
223 found_task_tag
= True
226 if len(task_tags_all
) and not found_task_tag
:
227 tokens
= line
.split()
228 if len(tokens
) > 0 and tokens
[0][0] == line
[0] and tokens
[0].endswith(":"):
229 print("ERROR: Invalid task specifier \"%s\"." % tokens
[0], file=sys
.stderr
)
232 if task_skip_line
or task_skip_block
:
238 if line
.startswith("#"):
242 tasklist
.append(line
[0])
244 if taskname
== line
[0]:
245 task_tags_active
.add(t
)
248 elif line
== "[tasks]":
259 return cfgdata
, tasklist
263 with (open(sbyfile
, "r") if sbyfile
is not None else sys
.stdin
) as f
:
268 assert len(tasknames
) < 2
269 sbyconfig
, _
= read_sbyconfig(sbydata
, tasknames
[0] if len(tasknames
) else None)
270 print("\n".join(sbyconfig
))
273 if len(tasknames
) == 0:
274 _
, tasknames
= read_sbyconfig(sbydata
, None)
275 if len(tasknames
) == 0:
279 for task
in tasknames
:
284 if (workdir
is not None) and (len(tasknames
) != 1):
285 print("ERROR: Exactly one task is required when workdir is specified.", file=sys
.stderr
)
288 def run_job(taskname
):
290 my_opt_tmpdir
= opt_tmpdir
292 if my_workdir
is None and sbyfile
is not None and not my_opt_tmpdir
:
293 my_workdir
= sbyfile
[:-4]
294 if taskname
is not None:
295 my_workdir
+= "_" + taskname
297 if my_workdir
is not None:
300 while os
.path
.exists("%s.bak%03d" % (my_workdir
, backup_idx
)):
302 early_log(my_workdir
, "Moving direcory '%s' to '%s'." % (my_workdir
, "%s.bak%03d" % (my_workdir
, backup_idx
)))
303 shutil
.move(my_workdir
, "%s.bak%03d" % (my_workdir
, backup_idx
))
305 if opt_force
and not reusedir
:
306 early_log(my_workdir
, "Removing direcory '%s'." % (my_workdir
))
308 shutil
.rmtree(my_workdir
, ignore_errors
=True)
312 elif os
.path
.isdir(my_workdir
):
313 print("ERROR: Direcory '%s' already exists." % (my_workdir
))
316 os
.makedirs(my_workdir
)
320 my_workdir
= tempfile
.mkdtemp()
322 junit_ts_name
= os
.path
.basename(sbyfile
[:-4]) if sbyfile
is not None else workdir
if workdir
is not None else "stdin"
323 junit_tc_name
= taskname
if taskname
is not None else "default"
326 junit_filename
= os
.path
.basename(my_workdir
)
327 elif sbyfile
is not None:
328 junit_filename
= os
.path
.basename(sbyfile
[:-4])
329 if taskname
is not None:
330 junit_filename
+= "_" + taskname
331 elif taskname
is not None:
332 junit_filename
= taskname
334 junit_filename
= "junit"
336 sbyconfig
, _
= read_sbyconfig(sbydata
, taskname
)
337 job
= SbyJob(sbyconfig
, my_workdir
, early_logmsgs
, reusedir
)
339 for k
, v
in exe_paths
.items():
351 job
.log("Removing direcory '%s'." % (my_workdir
))
352 shutil
.rmtree(my_workdir
, ignore_errors
=True)
355 job
.log("SETUP COMPLETE (rc=%d)" % (job
.retcode
))
357 job
.log("DONE (%s, rc=%d)" % (job
.status
, job
.retcode
))
360 if not my_opt_tmpdir
and not setupmode
:
361 with
open("%s/%s.xml" % (job
.workdir
, junit_filename
), "w") as f
:
362 junit_errors
= 1 if job
.retcode
== 16 else 0
363 junit_failures
= 1 if job
.retcode
!= 0 and junit_errors
== 0 else 0
364 print('<?xml version="1.0" encoding="UTF-8"?>', file=f
)
365 print('<testsuites disabled="0" errors="%d" failures="%d" tests="1" time="%d">' % (junit_errors
, junit_failures
, job
.total_time
), file=f
)
366 print('<testsuite disabled="0" errors="%d" failures="%d" name="%s" skipped="0" tests="1" time="%d">' % (junit_errors
, junit_failures
, junit_ts_name
, job
.total_time
), file=f
)
367 print('<properties>', file=f
)
368 print('<property name="os" value="%s"/>' % os
.name
, file=f
)
369 print('</properties>', file=f
)
370 print('<testcase classname="%s" name="%s" status="%s" time="%d">' % (junit_ts_name
, junit_tc_name
, job
.status
, job
.total_time
), file=f
)
372 print('<error message="%s" type="%s"/>' % (job
.status
, job
.status
), file=f
)
374 print('<failure message="%s" type="%s"/>' % (job
.status
, job
.status
), file=f
)
375 print('<system-out>', end
="", file=f
)
376 with
open("%s/logfile.txt" % (job
.workdir
), "r") as logf
:
378 print(line
.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """), end
="", file=f
)
379 print('</system-out></testcase></testsuite></testsuites>', file=f
)
380 with
open("%s/.stamp" % (job
.workdir
), "w") as f
:
381 print("%s %d %d" % (job
.status
, job
.retcode
, job
.total_time
), file=f
)
388 retcode |
= run_job(t
)