3
0
Fork 0
mirror of https://github.com/YosysHQ/sby.git synced 2025-04-05 22:14:08 +00:00

Merge branch 'YosysHQ:master' into fifo_example

This commit is contained in:
KrystalDelusion 2022-08-01 20:25:15 +12:00 committed by GitHub
commit 672a559b92
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 431 additions and 72 deletions

View file

@ -0,0 +1,30 @@
# Autotune demo
This directory contains a simple sequential integer divider circuit. The
verilog implementation in [divider.sv](divider.sv) comes with assertions that
this circuit will always produce the correct result and will always finish
within a fixed number of cycles. The circuit has the divider bit-width as
parameter.
Increasing the WIDTH parameter quickly turns proving those assertions into a
very difficult proof for fully autmated solvers. This makes it a good example
for the `--autotune` option which tries different backend engines to find the
best performing engine configuration for a given verification task.
The [divider.sby](divider.sby) file defines 3 tasks named `small`, `medium` and
`large` which configure the divider with different bit-widths. To verify the
`small` divider using the default engine run:
sby -f divider.sby small
To automatically try different backend engines using autotune, run
sby --autotune -f divider.sby small
The `small` task should finish quickly using both the default engine and using
autotune. The `medium` and `large` tasks take significantly longer and show
greater differences between engine configurations. Note that the `large` tasks
can take many minutes to hours, depending on the machine you are using.
You can learn more about Sby's autotune feature from [Sby's
documentation](https://symbiyosys.readthedocs.io/en/latest/autotune.html).

View file

@ -0,0 +1,24 @@
[tasks]
small default
medium
large
[options]
mode prove
small: depth 11
medium: depth 15
large: depth 19
[engines]
smtbmc
[script]
small: read -define WIDTH=8
medium: read -define WIDTH=12
large: read -define WIDTH=16
read -formal divider.sv
prep -top divider
[files]
divider.sv

View file

@ -0,0 +1,85 @@
`ifndef WIDTH
`define WIDTH 4
`endif
module divider #(
parameter WIDTH=`WIDTH
) (
input wire clk,
input wire start,
input wire [WIDTH-1:0] dividend,
input wire [WIDTH-1:0] divisor,
output reg done,
output reg [WIDTH-1:0] quotient,
output wire [WIDTH-1:0] remainder
);
reg [WIDTH-1:0] acc;
reg [WIDTH*2-1:0] sub;
reg [WIDTH-1:0] pos;
assign remainder = acc;
always @(posedge clk) begin
if (start) begin
acc <= dividend;
quotient <= 0;
sub <= divisor << (WIDTH - 1);
pos <= 1 << (WIDTH - 1);
done <= 0;
end else if (!done) begin
if (acc >= sub) begin
acc <= acc - sub[WIDTH-1:0];
quotient <= quotient + pos;
end
sub <= sub >> 1;
{pos, done} <= pos;
end
end
`ifdef FORMAL
reg [WIDTH-1:0] start_dividend = 0;
reg [WIDTH-1:0] start_divisor = 0;
reg started = 0;
reg finished = 0;
reg [$clog2(WIDTH + 1):0] counter = 0;
always @(posedge clk) begin
// Bound the number of cycles until the result is ready
assert (counter <= WIDTH);
if (started) begin
if (finished || done) begin
finished <= 1;
// Make sure result stays until we start a new division
assert (done);
// Check the result
if (start_divisor == 0) begin
assert (&quotient);
assert (remainder == start_dividend);
end else begin
assert (quotient == start_dividend / start_divisor);
assert (remainder == start_dividend % start_divisor);
end
end else begin
counter <= counter + 1'b1;
end
end
// Track the requested inputs
if (start) begin
start_divisor <= divisor;
start_dividend <= dividend;
started <= 1;
counter <= 0;
finished <= 0;
end
end
`endif
endmodule

View file

@ -3,7 +3,7 @@ Autotune: Automatic Engine Selection
Selecting the best performing engine for a given verification task often
requires some amount of trial and error. To reduce the manual work required for
this, sby offers the ``--autotune`` option which takes an ``.sby`` file and
this, sby offers the ``--autotune`` option. This takes an ``.sby`` file and
runs it using engines and engine configurations. At the end it produces a
report listing the fastest engines among these candidates.
@ -11,15 +11,15 @@ Using Autotune
--------------
To run autotune, you can add the ``--autotune`` option to your usual sby
invokation. For example if you usually run ``sby demo.sby`` you would run ``sby
--autotune demo.sby`` instead. When the ``.sby`` file contains multiple tasks,
autotune is run for each task independently. As without ``--autotune``, it is
possible to specify which tasks to run on the command line.
invocation. For example, if you usually run ``sby demo.sby`` you would run
``sby --autotune demo.sby`` instead. When the ``.sby`` file contains multiple
tasks, autotune is run for each task independently. As without ``--autotune``,
it is possible to specify which tasks to run on the command line.
Autotune runs without requiring further interaction and will eventually print a
Autotune runs without requiring further interaction, and will eventually print a
list of engine configurations and their respective solving times. To
permanently use an engine configuration you can copy if from the ``sby
--autotune`` output into the ``[engines]`` section of your ``.sby`` file.
permanently use an engine configuration you can copy it from the
``sby --autotune`` output into the ``[engines]`` section of your ``.sby`` file.
Autotune Log Output
-------------------
@ -37,9 +37,9 @@ once and will be reused to run every candidate engine.
SBY [demo] base: finished (returncode=0)
SBY [demo] prepared model 'base'
This is followed by selecting the engine candidates to run. For this the design
This is followed by selecting the engine candidates to run. The design
and sby configuration are analyzed to skip engines that are not compatible or
unlikely to work well. When engines is skipped due to a recommendation, a
unlikely to work well. When an engine is skipped due to a recommendation, a
corresponding log message is displayed as well as the total number of
candidates to try:
@ -49,7 +49,7 @@ candidates to try:
SBY [demo] testing 16 engine configurations...
After this, the candidate engine configurations are started. Depending on the
configuration engines can run in parallel. The engine output itself is not
configuration, engines can run in parallel. The engine output itself is not
logged to stdout when running autotune, so you will only see messages about
starting an engine:
@ -77,7 +77,7 @@ be interspersed with other log output.
SBY [demo] smt2: finished (returncode=0)
SBY [demo] prepared model 'smt2'
Whenever an engine finishes a log message is printed:
Whenever an engine finishes, a log message is printed:
.. code-block:: text
@ -91,7 +91,7 @@ When an engine takes longer than the current hard timeout, it is stopped:
SBY [demo] engine_2 (smtbmc bitwuzla): timeout (150 seconds)
Depending on the configuration, autotune will also stop an engine earlier when
reaching a soft timeout. In that case, when no other engine finishes in less
reaching a soft timeout. If no other engine finishes in less
time, the engine will be retried later with a longer soft timeout:
.. code-block:: text
@ -99,7 +99,7 @@ time, the engine will be retried later with a longer soft timeout:
SBY [demo] engine_0 (smtbmc boolector): timeout (60 seconds, will be retried if necessary)
Finally at the end a summary of all finished engines is printed, sorted by
Finally, a summary of all finished engines is printed, sorted by
their solving time:
.. code-block:: text
@ -112,8 +112,8 @@ their solving time:
If any tried engine encounters an error or produces an unexpected result,
autotune will also output a list of failed engines. Note that when the sby file
does not contain the ``expect`` option, autotune defaults to ``expect
pass,fail`` to simplify running autotune on a verification task with a
does not contain the ``expect`` option, autotune defaults to
``expect pass,fail`` to simplify running autotune on a verification task with a
currently unknown outcome.
Configuring Autotune
@ -121,7 +121,7 @@ Configuring Autotune
Autotune can be configured by adding an ``[autotune]`` section to the ``.sby``
file. Each line in that section has the form ``option_name value``, the
possible options and their supported values are described below. In addition
possible options and their supported values are described below. In addition,
the ``--autotune-config`` command line option can be used to specify a file
containing further autotune options, using the same syntax. When both are used,
the command line option takes precedence. This makes it easy to run autotune

View file

@ -402,6 +402,8 @@ if (workdir is not None) and (len(tasknames) != 1):
sys.exit(1)
def run_task(taskname):
sbyconfig, _, _, _ = read_sbyconfig(sbydata, taskname)
my_opt_tmpdir = opt_tmpdir
my_workdir = None
@ -461,7 +463,6 @@ def run_task(taskname):
else:
junit_filename = "junit"
sbyconfig, _, _, _ = read_sbyconfig(sbydata, taskname)
task = SbyTask(sbyconfig, my_workdir, early_logmsgs, reusedir)
for k, v in exe_paths.items():

View file

@ -176,13 +176,57 @@ class SbyProc:
fcntl.fcntl(self.p.stdout, fcntl.F_SETFL, fl | os.O_NONBLOCK)
else:
self.p = subprocess.Popen(self.cmdline, shell=True, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
self.p = subprocess.Popen(self.cmdline + " & exit !errorlevel!", shell=True, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
stderr=(subprocess.STDOUT if self.logstderr else None))
self.task.update_proc_running(self)
self.running = True
return
self.read_output()
if self.p.poll() is not None:
# The process might have written something since the last time we checked
self.read_output()
if not self.silent:
self.task.log(f"{self.info}: finished (returncode={self.p.returncode})")
self.task.update_proc_stopped(self)
self.running = False
self.exited = True
if os.name == "nt":
if self.p.returncode == 9009:
returncode = 127
else:
returncode = self.p.returncode & 0xff
else:
returncode = self.p.returncode
if returncode == 127:
if not self.silent:
self.task.log(f"{self.info}: COMMAND NOT FOUND. ERROR.")
self.handle_error(returncode)
self.terminated = True
self.task.proc_failed(self)
return
if self.checkretcode and returncode not in self.retcodes:
if not self.silent:
self.task.log(f"{self.info}: task failed. ERROR.")
self.handle_error(returncode)
self.terminated = True
self.task.proc_failed(self)
return
self.handle_exit(returncode)
self.finished = True
for next_proc in self.notify:
next_proc.poll()
return
def read_output(self):
while True:
outs = self.p.stdout.readline().decode("utf-8")
if len(outs) == 0: break
@ -193,36 +237,6 @@ class SbyProc:
self.linebuffer = ""
self.handle_output(outs)
if self.p.poll() is not None:
if not self.silent:
self.task.log(f"{self.info}: finished (returncode={self.p.returncode})")
self.task.update_proc_stopped(self)
self.running = False
self.exited = True
if self.p.returncode == 127:
if not self.silent:
self.task.log(f"{self.info}: COMMAND NOT FOUND. ERROR.")
self.handle_error(self.p.returncode)
self.terminated = True
self.task.proc_failed(self)
return
if self.checkretcode and self.p.returncode not in self.retcodes:
if not self.silent:
self.task.log(f"{self.info}: task failed. ERROR.")
self.handle_error(self.p.returncode)
self.terminated = True
self.task.proc_failed(self)
return
self.handle_exit(self.p.returncode)
self.finished = True
for next_proc in self.notify:
next_proc.poll()
return
class SbyAbort(BaseException):
pass
@ -552,7 +566,8 @@ class SbyTask(SbyConfig):
if not os.path.isdir(f"{self.workdir}/model"):
os.makedirs(f"{self.workdir}/model")
def print_common_prep():
def print_common_prep(check):
print("scc -select; simplemap; select -clear", file=f)
if self.opt_multiclock:
print("clk2fflogic", file=f)
else:
@ -569,7 +584,7 @@ class SbyTask(SbyConfig):
print("setundef -anyseq", file=f)
print("opt -keepdc -fast", file=f)
print("check", file=f)
print("hierarchy -simcheck", file=f)
print(f"hierarchy {check}", file=f)
if model_name == "base":
with open(f"""{self.workdir}/model/design.ys""", "w") as f:
@ -577,7 +592,7 @@ class SbyTask(SbyConfig):
for cmd in self.script:
print(cmd, file=f)
# the user must designate a top module in [script]
print("hierarchy -simcheck", file=f)
print("hierarchy -smtcheck", file=f)
print(f"""write_jny -no-connections ../model/design.json""", file=f)
print(f"""write_rtlil ../model/design.il""", file=f)
@ -610,7 +625,7 @@ class SbyTask(SbyConfig):
print("memory_map", file=f)
else:
print("memory_nordff", file=f)
print_common_prep()
print_common_prep("-smtcheck")
if "_syn" in model_name:
print("techmap", file=f)
print("opt -fast", file=f)
@ -643,7 +658,7 @@ class SbyTask(SbyConfig):
print("memory_map", file=f)
else:
print("memory_nordff", file=f)
print_common_prep()
print_common_prep("-simcheck")
print("flatten", file=f)
print("setundef -undriven -anyseq", file=f)
if "_syn" in model_name:
@ -653,7 +668,7 @@ class SbyTask(SbyConfig):
print("abc", file=f)
print("opt_clean", file=f)
else:
print("opt -fast", file=f)
print("opt -fast -keepdc", file=f)
print("delete -output", file=f)
print("dffunmap", file=f)
print("stat", file=f)
@ -675,7 +690,7 @@ class SbyTask(SbyConfig):
print(f"# running in {self.workdir}/model/", file=f)
print("read_ilang design.il", file=f)
print("memory_map", file=f)
print_common_prep()
print_common_prep("-simcheck")
print("flatten", file=f)
print("setundef -undriven -anyseq", file=f)
print("setattr -unset keep", file=f)

View file

@ -41,6 +41,7 @@ def run(mode, task, engine_idx, engine):
elif abc_command[0] == "pdr":
if mode != "prove":
task.error("ABC command 'pdr' is only valid in prove mode.")
abc_command[0] += f" -v"
else:
task.error(f"Invalid ABC command {abc_command[0]}.")

View file

@ -2,11 +2,27 @@ test:
.PHONY: test clean refresh help
OS_NAME := $(shell python3 -c "import os;print(os.name)")
ifeq (nt,$(OS_NAME))
ifeq (quoted,$(shell echo "quoted"))
OS_NAME := nt-unix-like
endif
endif
ifeq (nt,$(OS_NAME))
$(error This Makefile requires unix-like tools and shell, e.g. MSYS2.)
endif
help:
@cat make/help.txt
export SBY_WORKDIR_GITIGNORE=1
export SBY_MAIN=$(realpath $(dir $(firstword $(MAKEFILE_LIST)))/../sbysrc/sby.py)
export SBY_WORKDIR_GITIGNORE := 1
SBY_MAIN := $(realpath $(dir $(firstword $(MAKEFILE_LIST)))/../sbysrc/sby.py)
ifeq (nt-unix-like,$(OS_NAME))
SBY_MAIN := $(shell cygpath -w $(SBY_MAIN))
endif
export SBY_MAIN
make/rules/collect.mk: make/collect_tests.py
python3 make/collect_tests.py

View file

@ -4,7 +4,8 @@ import re
tests = []
checked_dirs = []
SAFE_PATH = re.compile(r"^[a-zA-Z0-9_./]*$")
SAFE_PATH = re.compile(r"^[a-zA-Z0-9_./\\]*$")
def collect(path):
# don't pick up any paths that need escaping nor any sby workdirs
@ -15,8 +16,6 @@ def collect(path):
for entry in path.glob("*.sby"):
filename = str(entry)
if not SAFE_PATH.match(filename):
continue
if not re.match(r"^[a-zA-Z0-9_./]*$", filename):
print(f"skipping {filename!r}, use only [a-zA-Z0-9_./] in filenames")
continue
tests.append(entry)
@ -25,6 +24,10 @@ def collect(path):
collect(entry)
def unix_path(path):
return "/".join(path.parts)
collect(Path("."))
collect(Path("../docs/examples"))
@ -33,16 +36,18 @@ out_file.parent.mkdir(exist_ok=True)
with out_file.open("w") as output:
for checked_dir in checked_dirs:
print(f"{out_file}: {checked_dir}", file=output)
for test in tests:
print(f"make/rules/test/{test}.mk: {test}", file=output)
test_unix = unix_path(test)
print(f"make/rules/test/{test_unix}.mk: {test_unix}", file=output)
for ext in [".sh", ".py"]:
script_file = test.parent / (test.stem + ext)
if script_file.exists():
print(f"make/rules/test/{test}.mk: {script_file}", file=output)
print(f"make/rules/test/{test}.mk: make/test_rules.py", file=output)
script_file_unix = unix_path(script_file)
print(f"make/rules/test/{test_unix}.mk: {script_file_unix}", file=output)
print(f"make/rules/test/{test_unix}.mk: make/test_rules.py", file=output)
for test in tests:
print(f"-include make/rules/test/{test}.mk", file=output)
test_unix = unix_path(test)
print(f"-include make/rules/test/{test_unix}.mk", file=output)

View file

@ -4,6 +4,7 @@ REQUIRED_TOOLS = {
("smtbmc", "yices"): ["yices-smt2"],
("smtbmc", "z3"): ["z3"],
("smtbmc", "cvc4"): ["cvc4"],
("smtbmc", "cvc5"): ["cvc5"],
("smtbmc", "mathsat"): ["mathsat"],
("smtbmc", "boolector"): ["boolector"],
("smtbmc", "bitwuzla"): ["bitwuzla"],

View file

@ -7,6 +7,11 @@ from pathlib import Path
from required_tools import REQUIRED_TOOLS
def unix_path(path):
return "/".join(path.parts)
sby_file = Path(sys.argv[1])
sby_dir = sby_file.parent
@ -56,7 +61,10 @@ with rules_file.open("w") as rules:
solvers.add(solver)
engine_solvers.add((engine, solver))
if any(line.startswith("read -verific") or line.startswith("verific") for line in info["script"]):
if any(
line.startswith("read -verific") or line.startswith("verific")
for line in info["script"]
):
required_tools.add("verific")
required_tools = sorted(required_tools)
@ -66,12 +74,17 @@ with rules_file.open("w") as rules:
shell_script = sby_dir / f"{sby_file.stem}.sh"
if shell_script.exists():
command = f"cd {sby_dir} && SBY_FILE={sby_file.name} WORKDIR={workdirname} TASK={task} bash {shell_script.name}"
else:
command = f"cd {sby_dir} && python3 $(SBY_MAIN) -f {sby_file.name} {task}"
sby_dir_unix = unix_path(sby_dir)
print(f"\t@python3 make/required_tools.py run {target} {shlex.quote(command)} {shlex.join(required_tools)}", file=rules)
if shell_script.exists():
command = f"cd {sby_dir_unix} && env SBY_FILE={sby_file.name} WORKDIR={workdirname} TASK={task} bash {shell_script.name}"
else:
command = f"cd {sby_dir_unix} && python3 $(SBY_MAIN) -f {sby_file.name} {task}"
print(
f"\t@python3 make/required_tools.py run {target} {shlex.quote(command)} {shlex.join(required_tools)}",
file=rules,
)
print(f".PHONY: clean-{target}", file=rules)
print(f"clean-{target}:", file=rules)

View file

@ -0,0 +1,43 @@
[tasks]
btor
smt
btor_m btor multiclock
smt_m smt multiclock
[options]
mode bmc
multiclock: multiclock on
[engines]
#smtbmc
btor: btor btormc
smt: smtbmc boolector
[script]
read_verilog -formal const_clocks.sv
prep -flatten -top top
[file const_clocks.sv]
module top(
input clk,
input [7:0] d
);
(* keep *)
wire [7:0] some_const = $anyconst;
wire [7:0] q;
ff ff1(.clk(1'b0), .d(d), .q(q));
initial assume (some_const == q);
initial assume (q != 0);
always @(posedge clk) assert(some_const == q);
endmodule
module ff(input clk, input [7:0] d, (* keep *) output reg [7:0] q);
always @(posedge clk) q <= d;
endmodule

View file

@ -0,0 +1,23 @@
[options]
mode cover
[engines]
smtbmc boolector
[script]
read -formal fake_loop.sv
hierarchy -top fake_loop
proc
[file fake_loop.sv]
module fake_loop(input clk, input a, input b, output [9:0] x);
wire [9:0] ripple;
reg [9:0] prev_ripple = 9'b0;
always @(posedge clk) prev_ripple <= ripple;
assign ripple = {ripple[8:0], a} ^ prev_ripple; // only cyclic at the coarse-grain level
assign x = ripple[9] + b;
always @(posedge clk) cover(ripple[9]);
endmodule

View file

@ -0,0 +1,39 @@
[options]
mode bmc
[engines]
smtbmc boolector
[script]
read_verilog -formal ff_xinit_opt.sv
prep -flatten -top top
opt -fast -keepdc
[file ff_xinit_opt.sv]
module top(
input clk,
input [7:0] d
);
(* keep *)
wire [7:0] some_const = $anyconst;
wire [7:0] q1;
wire [7:0] q2;
ff ff1(.clk(clk), .d(q1), .q(q1));
ff ff2(.clk(1'b0), .d(d), .q(q2));
initial assume (some_const == q1);
initial assume (some_const == q2);
initial assume (q1 != 0);
initial assume (q2 != 0);
always @(posedge clk) assert(some_const == q1);
always @(posedge clk) assert(some_const == q2);
endmodule
module ff(input clk, input [7:0] d, (* keep *) output reg [7:0] q);
always @(posedge clk) q <= d;
endmodule

View file

@ -0,0 +1,31 @@
[options]
mode bmc
depth 1
expect error
[engines]
smtbmc
[script]
read_verilog -formal test.v
prep -top top
[file test.v]
(* blackbox *)
module submod(a, b);
input [7:0] a;
output [7:0] b;
endmodule
module top;
wire [7:0] a = $anyconst, b;
submod submod(
.a(a),
.b(b)
);
always @* begin
assert(~a == b);
end
endmodule

View file

@ -0,0 +1,32 @@
[options]
mode bmc
depth 1
[engines]
smtbmc
[script]
read_verilog -formal test.v
prep -top top
[file test.v]
(* blackbox *)
(* smtlib2_module *)
module submod(a, b);
input [7:0] a;
(* smtlib2_comb_expr = "(bvnot a)" *)
output [7:0] b;
endmodule
module top;
wire [7:0] a = $anyconst, b;
submod submod(
.a(a),
.b(b)
);
always @* begin
assert(~a == b);
end
endmodule