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

Updating from feedback

Primarily addressing Nak's comments on the PR first.
Of note is the change from separate files to a single file.
Changed to boolector engine and bmc by default.
Updated install instructions to move z3 to optional and boolector to
recommended.
Literal code includes use :lines: option.
This commit is contained in:
KrystalDelusion 2022-06-30 12:06:12 +12:00
parent 069197aeaa
commit 907db48ac9
5 changed files with 268 additions and 285 deletions

View file

@ -1,10 +1,8 @@
[tasks]
prove_oss prove oss
noskip prove oss
cover_oss cover oss
prove_tabby prove tabby
cover_tabby cover tabby
prove_oss cover_oss : default
basic bmc
nofullskip prove
cover
basic cover : default
[options]
cover:
@ -13,18 +11,17 @@ mode cover
prove:
mode prove
--
bmc:
mode bmc
--
[engines]
cover: smtbmc z3
prove: abc pdr
smtbmc boolector
[script]
read -sv fifo.sv
tabby: read -define USE_VERIFIC=1
noskip: read -define NOSKIP=1
read -formal top.sv
nofullskip: read -define NO_FULL_SKIP=1
read -formal fifo.sv
prep -top fifo
[files]
fifo.sv
top.sv

View file

@ -1,23 +1,3 @@
// Define the fifo storage
module storage (
input wen, ren, clk, rst_n,
input [3:0] waddr, raddr,
input [7:0] wdata,
output [7:0] rdata
);
parameter MAX_DATA = 16;
// 8 bit data, fifo depth 16 / 4 bit address
// reset not defined
reg [7:0] data [MAX_DATA-1:0];
always @(posedge clk) begin
if (wen)
data[waddr] <= wdata;
end
assign rdata = data[raddr];
endmodule
// address generator/counter
module addr_gen (
input en, clk, rst_n,
@ -39,7 +19,171 @@ module addr_gen (
addr <= 0;
else
addr <= addr + 1;
else
addr <= addr;
end
endmodule
// Define our top level fifo entity
module fifo (
input wen, ren, clk, rst_n,
input [7:0] wdata,
output [7:0] rdata,
output [4:0] count,
output full, empty
);
parameter MAX_DATA = 16;
// wire up our sub modules
wire [3:0] waddr, raddr;
wire wskip, rskip;
// fifo storage
// 8 bit data, fifo depth 16 / 4 bit address
// reset not defined
reg [7:0] data [MAX_DATA-1:0];
always @(posedge clk) begin
if (wen)
data[waddr] <= wdata;
end
assign rdata = data[raddr];
addr_gen #(.MAX_DATA(MAX_DATA)) fifo_writer (
.en (wen || wskip),
.clk (clk ),
.rst_n (rst_n),
.addr (waddr)
);
addr_gen #(.MAX_DATA(MAX_DATA)) fifo_reader (
.en (ren || rskip),
.clk (clk ),
.rst_n (rst_n),
.addr (raddr)
);
// internals
reg [4:0] data_count;
initial begin
data_count <= 0;
end
always @(posedge clk or negedge rst_n) begin
if (~rst_n)
data_count <= 0;
else if (wen && !ren && data_count < MAX_DATA)
data_count <= data_count + 1;
else if (ren && !wen && data_count > 0)
data_count <= data_count - 1;
end
assign full = data_count == MAX_DATA;
assign empty = (data_count == 0) && rst_n;
assign count = data_count;
`ifndef NO_FULL_SKIP
// write while full => overwrite oldest data, move read pointer
assign rskip = wen && !ren && data_count >= MAX_DATA;
// read while empty => read invalid data, keep write pointer in sync
assign wskip = ren && !wen && data_count == 0;
`else
assign rskip = 0;
assign wskip = 0;
`endif // NO_FULL_SKIP
`ifdef FORMAL
// observers
wire [4:0] addr_diff;
assign addr_diff = waddr >= raddr
? waddr - raddr
: waddr + MAX_DATA - raddr;
reg init = 0;
always @(posedge clk) begin
if (rst_n)
init <= 1;
// if init is low we don't care about the value of rst_n
// if init is high (rst_n has ben high), then rst_n must remain high
assume (!init || init && rst_n);
end
// tests
always @(posedge clk) begin
if (rst_n) begin
// waddr and raddr can only be non zero if reset is high
w_nreset: cover (waddr || raddr);
// count never less than zero, or more than max
a_uflow: assert (count >= 0);
a_uflow2: assert (raddr >= 0);
a_oflow: assert (count <= MAX_DATA);
a_oflow2: assert (waddr < MAX_DATA);
// count should be equal to the difference between writer and reader address
a_count_diff: assert (count == addr_diff
|| count == MAX_DATA && addr_diff == 0);
// count should only be able to increase or decrease by 1
a_counts: assert (count == 0
|| count == $past(count)
|| count == $past(count) + 1
|| count == $past(count) - 1);
// read/write addresses can only increase (or stay the same)
a_raddr: assert (raddr == 0
|| raddr == $past(raddr)
|| raddr == $past(raddr + 1));
a_waddr: assert (waddr == 0
|| waddr == $past(waddr)
|| waddr == $past(waddr + 1));
// full and empty work as expected
a_full: assert (!full || full && count == MAX_DATA);
w_full: cover (wen && !ren && count == MAX_DATA-1);
a_empty: assert (!empty || empty && count == 0);
w_empty: cover (ren && !wen && count == 1);
// can we corrupt our data?
w_overfill: cover ($past(rskip) && raddr);
w_underfill: cover ($past(wskip) && waddr);
end else begin
// waddr and raddr are zero while reset is low
a_reset: assert (!waddr && !raddr);
w_reset: cover (~rst_n);
// outputs are zero while reset is low
a_zero_out: assert (!empty && !full && !count);
end
end
`ifdef VERIFIC
// if we have verific we can also do the following additional tests
always @(posedge clk) begin
if (rst_n) begin
// read/write enables enable
ap_raddr2: assert property (ren |=> $changed(raddr));
ap_waddr2: assert property (wen |=> $changed(waddr));
// read/write needs enable UNLESS full/empty
ap_raddr3: assert property (!ren && !full |=> $stable(raddr));
ap_waddr3: assert property (!wen && !empty |=> $stable(waddr));
// can we corrupt our data?
ap_overfill: assert property (wen && full |=> $changed(raddr));
ap_underfill: assert property (ren && empty |=> $changed(waddr));
// change data when writing (and only when writing) so we can line
// up reads with writes
assume property (wen |=> $changed(wdata));
assume property (!wen |=> $stable(wdata));
end
end
`else // !VERIFIC
// without verific we are more limited in describing the above assumption
always @(posedge clk) begin
assume ((wen && wdata != $past(wdata))
|| (!wen && wdata == $past(wdata)));
end
`endif // VERIFIC
`endif // FORMAL
endmodule

View file

@ -1,167 +0,0 @@
// Define our top level fifo entity
module fifo (
input wen, ren, clk, rst_n,
input [7:0] wdata,
output [7:0] rdata,
output [4:0] count,
output full, empty
);
parameter MAX_DATA = 16;
// internals
reg [4:0] data_count;
initial begin
data_count <= 0;
end
// wire up our sub modules
wire [3:0] waddr, raddr;
wire wskip, rskip;
storage #(.MAX_DATA(MAX_DATA)) fifo_storage (
.wen (wen ),
.ren (ren ),
.clk (clk ),
.rst_n (rst_n),
.waddr (waddr),
.raddr (raddr),
.wdata (wdata),
.rdata (rdata)
);
addr_gen #(.MAX_DATA(MAX_DATA)) fifo_writer (
.en (wen || wskip),
.clk (clk ),
.rst_n (rst_n),
.addr (waddr)
);
addr_gen #(.MAX_DATA(MAX_DATA)) fifo_reader (
.en (ren || rskip),
.clk (clk ),
.rst_n (rst_n),
.addr (raddr)
);
always @(posedge clk or negedge rst_n) begin
if (~rst_n)
data_count <= 0;
else if (wen && !ren && data_count < MAX_DATA)
data_count <= data_count + 1;
else if (ren && !wen && data_count > 0)
data_count <= data_count - 1;
else
data_count <= data_count;
end
assign full = data_count == MAX_DATA;
assign empty = (data_count == 0) && rst_n;
assign count = data_count;
`ifndef NOSKIP
// write while full => overwrite oldest data, move read pointer
assign rskip = wen && !ren && data_count >= MAX_DATA;
// read while empty => read invalid data, keep write pointer in sync
assign wskip = ren && !wen && data_count == 0;
`else
assign rskip = 0;
assign wskip = 0;
`endif // NOSKIP
`ifdef FORMAL
// observers
wire [4:0] addr_diff;
assign addr_diff = waddr >= raddr
? waddr - raddr
: waddr + MAX_DATA - raddr;
reg init = 0;
always @(posedge clk) begin
if (rst_n)
init <= 1;
// if init is low we don't care about the value of rst_n
// if init is high (rst_n has ben high), then rst_n must remain high
assume (!init || init && rst_n);
end
// tests
always @(posedge clk) begin
if (rst_n) begin
// waddr and raddr can only be non zero if reset is high
w_nreset: cover (waddr || raddr);
// count never less than zero, or more than max
a_uflow: assert (count >= 0);
a_uflow2: assert (raddr >= 0);
a_oflow: assert (count <= MAX_DATA);
a_oflow2: assert (waddr < MAX_DATA);
// count should be equal to the difference between writer and reader address
a_count_diff: assert (count == addr_diff
|| count == MAX_DATA && addr_diff == 0);
// count should only be able to increase or decrease by 1
a_counts: assert (count == 0
|| count == $past(count)
|| count == $past(count) + 1
|| count == $past(count) - 1);
// read/write addresses can only increase (or stay the same)
a_raddr: assert (raddr == 0
|| raddr == $past(raddr)
|| raddr == $past(raddr + 1));
a_waddr: assert (waddr == 0
|| waddr == $past(waddr)
|| waddr == $past(waddr + 1));
// full and empty work as expected
a_full: assert (!full || full && count == MAX_DATA);
w_full: cover (wen && !ren && count == MAX_DATA-1);
a_empty: assert (!empty || empty && count == 0);
w_empty: cover (ren && !wen && count == 1);
// can we corrupt our data?
w_overfill: cover ($past(rskip) && raddr);
w_underfill: cover ($past(wskip) && waddr);
end else begin
// waddr and raddr are zero while reset is low
a_reset: assert (!waddr && !raddr);
w_reset: cover (~rst_n);
// outputs are zero while reset is low
a_zero_out: assert (!empty && !full && !count);
end
end
`ifdef USE_VERIFIC
// if we have verific we can also do the following additional tests
always @(posedge clk) begin
if (rst_n) begin
// read/write enables enable
ap_raddr2: assert property (ren |=> $changed(raddr));
ap_waddr2: assert property (wen |=> $changed(waddr));
// read/write needs enable UNLESS full/empty
ap_raddr3: assert property (!ren && !full |=> $stable(raddr));
ap_waddr3: assert property (!wen && !empty |=> $stable(waddr));
// can we corrupt our data?
ap_overfill: assert property (wen && full |=> $changed(raddr));
ap_underfill: assert property (ren && empty |=> $changed(waddr));
// change data when writing (and only when writing) so we can line
// up reads with writes
assume property (wen |=> $changed(wdata));
assume property (!wen |=> $stable(wdata));
end
end
`else // !USE_VERIFIC
// without verific we are more limited in describing the above assumption
always @(posedge clk) begin
assume ((wen && wdata != $past(wdata))
|| (!wen && wdata == $past(wdata)));
end
`endif // USE_VERIFIC
`endif // FORMAL
endmodule

View file

@ -3,12 +3,12 @@
Installation guide
==================
This document will guide you through the process of installing SymbiYosys.
This document will guide you through the process of installing sby.
CAD suite(s)
************
SymbiYosys (sby) is part of the `Tabby CAD Suite
Sby (SymbiYosys) is part of the `Tabby CAD Suite
<https://www.yosyshq.com/tabby-cad-datasheet>`_ and the `OSS CAD Suite
<https://github.com/YosysHQ/oss-cad-suite-build>`_! The easiest way to use sby
is to install the binary software suite, which contains all required
@ -32,9 +32,9 @@ CAD Suite, please visit https://www.yosyshq.com/tabby-cad-datasheet.
Installing from source
**********************
Follow the instructions below to install SymbiYosys and its dependencies.
Yosys, SymbiYosys, and Z3 are non-optional. The other packages are only
required for some engine configurations.
Follow the instructions below to install sby and its dependencies. Yosys and sby
are non-optional. Boolector is recommended to install but not required. The
other packages are only required for some engine configurations.
Prerequisites
-------------
@ -59,7 +59,7 @@ https://yosyshq.net/yosys/
https://people.eecs.berkeley.edu/~alanmi/abc/
Next install Yosys, Yosys-SMTBMC and ABC (``yosys-abc``):
Note that this will install Yosys, Yosys-SMTBMC and ABC (as ``yosys-abc``):
.. code-block:: text
@ -68,8 +68,8 @@ Next install Yosys, Yosys-SMTBMC and ABC (``yosys-abc``):
make -j$(nproc)
sudo make install
SymbiYosys
^^^^^^^^^^
sby
^^^
https://github.com/YosysHQ/sby
@ -79,25 +79,38 @@ https://github.com/YosysHQ/sby
cd sby
sudo make install
Z3
^^
Recommended components
----------------------
https://github.com/Z3Prover/z3/wiki
Boolector
^^^^^^^^^
https://boolector.github.io
.. code-block:: text
git clone https://github.com/boolector/boolector
cd boolector
./contrib/setup-btor2tools.sh
./contrib/setup-lingeling.sh
./configure.sh
make -C build -j$(nproc)
sudo cp build/bin/{boolector,btor*} /usr/local/bin/
sudo cp deps/btor2tools/bin/btorsim /usr/local/bin/
git clone https://github.com/Z3Prover/z3
cd z3
python scripts/mk_make.py
cd build
make -j$(nproc)
sudo make install
To use the ``btor`` engine you will need to install btor2tools from
`commit c35cf1c <https://github.com/Boolector/btor2tools/commit/c35cf1c>`_ or
newer.
Optional components
-------------------
Additional solver engines can be installed as per their instructions, links are
provided below.
Z3
^^^
https://github.com/Z3Prover/z3
Yices 2
^^^^^^^
http://yices.csl.sri.com/
@ -111,12 +124,3 @@ super_prove
Avy
^^^
https://arieg.bitbucket.io/avy/
Boolector
^^^^^^^^^
http://fmv.jku.at/boolector/
https://github.com/boolector/boolector
To use the ``btor`` engine you additionally need a newer version of btorsim
than the boolector setup script builds: https://github.com/boolector/btor2tools

View file

@ -2,8 +2,9 @@
Getting started
===============
.. note:: This tutorial assumes sby installation as per the :ref:`install-doc`.
It is also recommended to install
.. note::
This tutorial assumes sby and boolector installation as per the
:ref:`install-doc`. For this tutorial, it is also recommended to install
`GTKWave <http://gtkwave.sourceforge.net/>`_, an open source VCD viewer.
First In, First Out (FIFO) buffer
@ -21,33 +22,40 @@ a FIFO is
they arrive at the queue's tail.
In hardware we can create such a construct by providing two addresses into a
register file. See the Verilog code below for the two main modules of an
example implementation.
register file. This tutorial will use an example implementation provided in
`fifo.sv`.
First, the address generator module:
.. literalinclude:: ../examples/fifo/fifo.sv
:language: systemverilog
:lines: 1-23
This module is instantiated twice; once for the write address and once for the
read address. In both cases, the address will start at and reset to 0, and will
increment by 1 when an enable signal is received. When the address pointers
increment from the maximum storage value they reset back to 0, providing a
circular queue.
Next, the register file:
.. literalinclude:: ../examples/fifo/fifo.sv
:language: systemverilog
:lines: 39-47
Notice that this register design includes a synchronous write and asynchronous
read. Each word is 8 bits, and up to 16 words can be stored in the buffer. The
address generator module will be instantiated twice; once for the write address
and once for the read address. In both cases, the address will start at and
reset to 0, and will increment by 1 when an enable signal is received. When the
address pointers increment from the maximum storage value they reset back to 0,
providing a circular queue. The top level design implemented, can be found in
``top.sv``.
read. Each word is 8 bits, and up to 16 words can be stored in the buffer.
Verification properties
***********************
In order to verify our design we must first define properties that it must
satisfy. For example, there must never be a negative number of values in the
FIFO. Similarly, there must never be more than there is memory available. By
assigning a signal to count the number of values in the buffer, we can make the
following assertions in the code:
satisfy. For example, there must never be more than there is memory available.
By assigning a signal to count the number of values in the buffer, we can make
the following assertion in the code:
.. code-block:: systemverilog
a_uflow: assert (count >= 0);
a_oflow: assert (count <= MAX_DATA);
It is also possible to use the prior value of a signal for comparison. This can
@ -86,36 +94,30 @@ SymbiYosys
SymbiYosys (sby) uses a .sby file to define a set of tasks used for
verification.
**prove_oss**
Prove mode (unbounded model check), for use with OSS CAD Suite.
**basic**
Bounded model check of design.
**noskip**
Demonstration of failing model check with OSS CAD Suite.
**nofullskip**
Demonstration of failing model using an unbounded model check.
**cover_oss**
Cover mode (testing cover statements), for use with OSS CAD Suite.
**cover**
Cover mode (testing cover statements).
**prove_tabby**
Prove mode, for use with Tabby CAD Suite.
**cover_tabby**
Cover mode, for use with Tabby CAD Suite.
The use of the ``:default`` tag indicates that by default, prove_oss and
cover_oss should be run if no tasks are specified, such as when running the
command below.
The use of the ``:default`` tag indicates that by default, basic and cover
should be run if no tasks are specified, such as when running the command below.
sby fifo.sby
.. note:: The default set of tests should all pass. If this is not the case
there may be a problem with the installation of sby or one of its solvers.
.. note::
The default set of tests should all pass. If this is not the case there may
be a problem with the installation of sby or one of its solvers.
To see what happens when a test fails, the below command can be used. Note the
use of the ``-f`` flag to automatically overwrite existing task output. While
this may not be necessary on the first run, it is quite useful when making
adjustments to code and rerunning tests to validate.
sby -f fifo.sby noskip
sby -f fifo.sby nofullskip
The noskip task disables the code shown below. Because the count signal has
been written such that it cannot exceed MAX_DATA, removing this code will lead
@ -125,36 +127,38 @@ overflow occur and the oldest data be written.
.. code-block:: systemverilog
`ifndef NOSKIP
`ifndef NO_FULL_SKIP
// write while full => overwrite oldest data, move read pointer
assign rskip = wen && !ren && data_count >= MAX_DATA;
// read while empty => read invalid data, keep write pointer in sync
assign wskip = ren && !wen && data_count == 0;
`endif // NOSKIP
`endif // NO_FULL_SKIP
The last few lines of output for the noskip task should be similar to the
following:
.. code-block:: text
SBY [fifo_noskip] engine_0: ## 0:00:00 BMC failed!
SBY [fifo_noskip] engine_0: ## 0:00:00 Assert failed in fifo: a_count_diff
SBY [fifo_noskip] engine_0: ## 0:00:00 Writing trace to VCD file: engine_0/trace.vcd
SBY [fifo_noskip] engine_0: ## 0:00:00 Writing trace to Verilog testbench: engine_0/trace_tb.v
SBY [fifo_noskip] engine_0: ## 0:00:00 Writing trace to constraints file: engine_0/trace.smtc
SBY [fifo_noskip] engine_0: ## 0:00:00 Status: FAILED
SBY [fifo_noskip] engine_0: finished (returncode=1)
SBY [fifo_noskip] summary: Elapsed clock time [H:MM:SS (secs)]: 0:00:01 (1)
SBY [fifo_noskip] summary: Elapsed process time unvailable on Windows
SBY [fifo_noskip] summary: engine_0 (abc pdr) returned FAIL
SBY [fifo_noskip] summary: counterexample trace: fifo_noskip/engine_0/trace.vcd
SBY [fifo_noskip] DONE (FAIL, rc=2)
SBY [fifo_nofullskip] engine_0.basecase: ## 0:00:00 Assert failed in fifo: a_count_diff
SBY [fifo_nofullskip] engine_0.basecase: ## 0:00:00 Assert failed in fifo: ap_underfill
SBY [fifo_nofullskip] engine_0.basecase: ## 0:00:00 Writing trace to VCD file: engine_0/trace.vcd
SBY [fifo_nofullskip] engine_0.basecase: ## 0:00:00 Writing trace to Verilog testbench: engine_0/trace_tb.v
SBY [fifo_nofullskip] engine_0.basecase: ## 0:00:00 Writing trace to constraints file: engine_0/trace.smtc
SBY [fifo_nofullskip] engine_0.basecase: ## 0:00:00 Status: failed
SBY [fifo_nofullskip] engine_0.basecase: finished (returncode=1)
SBY [fifo_nofullskip] engine_0: Status returned by engine for basecase: FAIL
SBY [fifo_nofullskip] engine_0.induction: terminating process
SBY [fifo_nofullskip] summary: Elapsed clock time [H:MM:SS (secs)]: 0:00:02 (2)
SBY [fifo_nofullskip] summary: Elapsed process time unvailable on Windows
SBY [fifo_nofullskip] summary: engine_0 (smtbmc boolector) returned FAIL for basecase
SBY [fifo_nofullskip] summary: counterexample trace: fifo_nofullskip/engine_0/trace.vcd
SBY [fifo_nofullskip] DONE (FAIL, rc=2)
SBY The following tasks failed: ['noskip']
Using the ``noskip.gtkw`` file provided, use the below command to examine the
error trace.
gtkwave fifo_noskip/engine_0/trace.vcd noskip.gtkw
gtkwave fifo_nofullskip/engine_0/trace.vcd noskip.gtkw
This should result in something similar to the below image. We can immediately
see that ``data_count`` and ``addr_diff`` are different. Looking a bit deeper
@ -166,26 +170,26 @@ to a higher value than the write address.
.. image:: media/gtkwave_noskip.png
During correct operation, the ``w_underfill`` witness will cover the underflow
case. Examining ``fifo_cover_oss/logfile.txt`` will reveal which trace file
case. Examining ``fifo_cover/logfile.txt`` will reveal which trace file
includes the witness we are looking for. If this file doesn't exist, run the
code below.
sby fifo.sby fifo_cover_oss
sby fifo.sby cover
Searching the file for ``w_underfill`` will reveal the below.
.. code-block:: text
$ grep "w_underfill" fifo_cover_oss/logfile.txt -A 1
SBY [fifo_cover_oss] engine_0: ## 0:00:00 Reached cover statement at w_underfill in step 2.
SBY [fifo_cover_oss] engine_0: ## 0:00:00 Writing trace to VCD file: engine_0/trace2.vcd
$ grep "w_underfill" fifo_cover/logfile.txt -A 1
SBY [fifo_cover] engine_0: ## 0:00:00 Reached cover statement at w_underfill in step 2.
SBY [fifo_cover] engine_0: ## 0:00:00 Writing trace to VCD file: engine_0/trace2.vcd
We can then run gtkwave with the trace file indicated to see the correct
operation as in the image below. When the buffer is empty, a read with no write
will result in the ``wksip`` signal going high, incrementing *both* read and
write addresses and avoiding underflow.
gtkwave fifo_cover_oss/engine_0/trace2.vcd noskip.gtkw
gtkwave fifo_cover/engine_0/trace2.vcd noskip.gtkw
.. image:: media/gtkwave_coverskip.png
@ -198,8 +202,9 @@ Until this point, all of the properties described have been *immediate*
assertions. As the name suggests, immediate assertions are evaluated
immediately whereas concurrent assertions allow for the capture of sequences of
events which occur across time. The use of concurrent assertions requires a
more advanced parser, such as Verific. Verific is included for use in the
*Tabby CAD Suite*.
more advanced series of checks. Using a parser such as Verific supports these
checks *without* having to write out potentially complicated state machines.
Verific is included for use in the *Tabby CAD Suite*.
With concurrent assertions we are able to verify more fully that our enables and
status flags work as desired. For example, we can assert that if the read