Verifying the VeeR core

This example uses the VeeR EH1 RISC-V Core. In a new folder, run git clone https://github.com/chipsalliance/Cores-VeeR-EH1.

Note

This example requires Yosys built with Verific for parsing the input design. Ask us for an evaluation license for TabbyCAD to try this yourself.

Note

For more on the .sby file format, refer to https://yosyshq.readthedocs.io/projects/sby/en/latest/reference.html.

Loading the design

In the same directory as Cores-VeeR-EH1, run sby --init-config-file veer. This will produce a file, veer.sby with the following content:

veer.sby generated from sby --init-config-file veer
[options]
mode bmc

[engines]
smtbmc

[script]
read -formal veer.sv
prep -top top

[files]
veer.sv

Next up we need to add all of the files to veer.sby and read them into Yosys. The VeeR core comes with a handy veer.core file which lists all of the RTL source and includes needed. We’re going to make a command file called veer.f which we can read with Verific to load all of our source files together.

First, we start with the include directories. Both include files listed are in the design/include/ directory, so we can use a single +incdir+design/include. Next we want to list all of the source files: copy the list from veer.core and make sure to remove the - and any indentation at the start of each line. You should end up with something like the following:

[file veer.f]
+incdir+design/include
+incdir+snapshots/default
snapshots/default/common_defines.vh
design/include/veer_types.sv
design/lib/beh_lib.sv
design/mem.sv
design/pic_ctrl.sv
design/dma_ctrl.sv
design/ifu/ifu_aln_ctl.sv
design/ifu/ifu_compress_ctl.sv
design/ifu/ifu_ifc_ctl.sv
design/ifu/ifu_bp_ctl.sv
design/ifu/ifu_ic_mem.sv
design/ifu/ifu_mem_ctl.sv
design/ifu/ifu_iccm_mem.sv
design/ifu/ifu.sv
design/dec/dec_decode_ctl.sv
design/dec/dec_gpr_ctl.sv
design/dec/dec_ib_ctl.sv
design/dec/dec_tlu_ctl.sv
design/dec/dec_trigger.sv
design/dec/dec.sv
design/exu/exu_alu_ctl.sv
design/exu/exu_mul_ctl.sv
design/exu/exu_div_ctl.sv
design/exu/exu.sv
design/lsu/lsu.sv
design/lsu/lsu_bus_buffer.sv
design/lsu/lsu_clkdomain.sv
design/lsu/lsu_addrcheck.sv
design/lsu/lsu_lsc_ctl.sv
design/lsu/lsu_stbuf.sv
design/lsu/lsu_bus_intf.sv
design/lsu/lsu_ecc.sv
design/lsu/lsu_dccm_mem.sv
design/lsu/lsu_dccm_ctl.sv
design/lsu/lsu_trigger.sv
design/dbg/dbg.sv
design/dmi/dmi_wrapper.v
design/dmi/dmi_jtag_to_core_sync.v
design/dmi/rvjtag_tap.sv
design/lib/mem_lib.sv
design/lib/ahb_to_axi4.sv
design/lib/axi4_to_ahb.sv
design/veer.sv
design/veer_wrapper.sv

We have included two extra lines in there, one for +incdir+snapshots/default, and one for snapshots/default/common_defines.vh. The snapshots/default directory is generated by the configs/veer.config perl script and provides macro definitions which configure the VeeR core as needed. Provided perl is installed on the system, we can get Yosys to call this script for us when we run SBY. We also need to update the [script] section to read our new veer.f command file and change the top module to veer_wrapper. This gives us the following:

[script]
exec -expect-return 0 -- bash -c 'RV_ROOT=$PWD ./configs/veer.config'
read -f -formal veer.f
prep -top veer_wrapper

The perl script expects an environment variable, RV_ROOT, which defines the root location in which to generate. By using bash -c we can use the SBY output directory (accessed via $PWD) as this root location. The -f flag in the read command is passed to Verific to signal that a command file is being read.

Finally we have the [files] section:

[files]
Cores-VeeR-EH1/design
Cores-VeeR-EH1/configs
Cores-VeeR-EH1/tools

In addition to the source files in the design directory, we are also including the config script in configs, which itself references the files in the tools directory. By defining veer.f in the SBY file, we skip the need to include it in the files list.

Running SBY now we might see something like the following:

SBY [veer] prep: ERROR: CLK tck on $verific$ir_reg$design/dmi/rvjtag_tap.sv:142$7750 ($dff) from module rvjtag_tap_default also used with opposite polarity, run clk2fflogic instead.

This tells us that we have a clock signal, tck in this case, which is triggering on both positive and negative edges. This means we need to enable multiclock mode by adding multiclock on to the [options] section. At this point we can now run BMC and get our first failed counter trace. The model checker can start the design in any state, including those which would normally be unreachable, so it might not be a very useful trace; but we have managed to successfully get the design all the way to the solvers.

Formal setup

Depending on the state of your design you may wish to specify additional properties for the solver. In particular, we need to set up constraints for the clock and reset signals. When using Verific we can do this by binding a module which provides assumptions on the input signals like so:

[file formal.sv]
module formal_setup();
    (* gclk *) reg gclk;
    reg clk = '0;
    always @(posedge gclk)
        clk <= !clk;

    reg [3:0] rst_counter = 2;
    always @(posedge clk)
        if (rst_counter)
            rst_counter -= 1;

    wire rst = rst_counter > 0;

    always @* begin
        setup_clk: assume (veer_wrapper.clk == clk);
        setup_rst_l: assume (veer_wrapper.rst_l == !rst);
        setup_dbg_rst_l: assume (veer_wrapper.dbg_rst_l == !rst);
    end

endmodule

bind veer_wrapper formal_setup formal_setup_inst(.*);

Again we are providing a file from within the SBY file and do not need to add it to the [files] section. We do, however, need to tell Verific to read it. We do this by adding formal.sv on a new line after design/veer_wrapper.sv in the veer.f command file.

First we connect the global clock with the line (* gclk *) reg gclk;. Each rising edge of the global clock corresponds to one step (or frame) in the solver. By inverting the clk register on each posedge, each step of the solver alternates between a rising edge and a falling edge of the clk. Then we need to have an assumption which says our top level clock, veer_wrapper.clk in this case, is equivalent to clk.

We can also add a reset, using a counter to control the number of cycles it should be held high. As with the clock, we then add an assumption to the corresponding top level signal or signals. If the reset signal is active low then the assumption should be == !rst. We can add constraints (assumptions) or any other property to any signal as follows:

always @* begin
    // always high
    assume (top.a);
    // always low
    assert (!top.b);
    // deeply nested
    cover (top.x.y.z);
    // conditional
    if (rst)
        assume (top.c);
end

// SVA properties
assume property(@(posedge clk) disable iff(rst) $rose(top.d) |-> top.e == '0);

Say you wanted to see a list of properties in the design. This can be achieved with the select command in Yosys, using the -list flag and the tee option to export to a file. We might add something like the following to the [script] section:

tee -o ../props.txt log Cover cells:
tee -a ../props.txt select t:$cover -list
tee -a ../props.txt log
tee -a ../props.txt log Assert cells:
tee -a ../props.txt select t:$assert -list
tee -a ../props.txt log
tee -a ../props.txt log Assume cells:
tee -a ../props.txt select t:$assume -list

Running SBY now we get the following:

veer/props.txt
Cover cells:

Assert cells:
dma_ctrl_default/assert_dma_axi_awlen_check
** other cells of type $assert **

Assume cells:
formal_setup/setup_clk
formal_setup/setup_dbg_rst_l
formal_setup/setup_rst_l
veer/logfile.txt
SBY [veer] Writing 'veer/src/formal.sv'.
SBY [veer] Writing 'veer/src/veer.f'.
SBY [veer] Copy 'Cores-VeeR-EH1/design' to 'veer/src/design'.
SBY [veer] Copy 'Cores-VeeR-EH1/configs' to 'veer/src/configs'.
SBY [veer] Copy 'Cores-VeeR-EH1/tools' to 'veer/src/tools'.
SBY [veer] engine_0: smtbmc
...
SBY [veer] summary: engine_0 (smtbmc) returned FAIL
SBY [veer] summary: counterexample trace: veer/engine_0/trace.vcd
SBY [veer] summary:   failed assertion veer_wrapper.veer.dma_ctrl.assert_dma_axi_awlen_check at design/dma_ctrl.sv:535.32-536.81 in step 4
SBY [veer] DONE (FAIL, rc=2)

We can see from this that the dma_ctrl module is expecting a valid AXI connection, which is currently missing. We could add assumptions on the inputs that match the assertions, but we already have perfectly good properties that we can use: the assertions themselves.

Changing properties

The chformal command in Yosys enables formal properties to be changed at run time. With this command we can convert assertions to assumptions (as well as the reverse), or entirely remove properties that we are not currently interested in.

From our previous run we know that the assert cell dma_ctrl_default/assert_dma_axi_awlen_check fails. We could add the following line after calling prep to remove it:

chformal -remove dma_ctrl_default/assert_dma_axi_awlen_check

But then we would just be left with another assertion in the same module failing. This also has the side effect that if anything else in the design is relying on the (now missing) assertion being correct, it will also fail. Instead, let’s add the following:

chformal -assert -assert2assume dma_ctrl_default/assert_dma_axi_*

We can see from the command reference page for chformal (or by running calling help chformal within Yosys) that we can specify the type of formal property to target, and that the final argument is a selection rather than simply the name of a property. Thus we use a wildcard to match all of the dma_ctrl properties related to AXI, limited to just assert cells, and then convert them to assume cells. Provided the AXI is properly connected and valid, this ensures that any other properties relying on the dma_ctrl can also be verified.

The keep-going flag

At this stage of the process we may be ready to run the solver against all of our properties rather than stopping at the first failure, and increasing the depth of BMC. We can do this by adding the --keep-going flag to our engine, e.g. smtbmc --keep-going. In order to keep run time down there are a few things worth considering. First is the -flatten flag for prep. This will flatten the circuit and allow additional course grain optimizations. The next is adding cutpoints for elements which are computationally difficult to prove. This could look like the following:

[options]
mode bmc
multiclock on
depth 40

[engines]
smtbmc --keep-going

[script]
exec -expect-return 0 -- bash -c 'RV_ROOT=$PWD ./configs/veer.config'
read -f -formal veer.f
prep -flatten -top veer_wrapper
chformal -assert -assert2assume veer_wrapper/veer.dma_ctrl.assert_dma_axi_*
cutpoint t:$mul t:$mem_v2

Note that flattening the hierarchy means we have the dma_ctrl assertions need to be referenced slightly differently. Rather than dma_ctrl_default/assert... we now see veer_wrapper/veer.dma_ctrl.assert....

We have also added cutpoints for all $mul and $mem_v2 cells, i.e. multipliers and memory blocks. The cutpoint command disconnects the inputs to any matching cell, and connects the outputs to $anyseq cells, allowing the solver to to assign any value to the outputs provided it does not invalidate any assume cells. This does of course mean that any properties which rely on correct and valid multipliers or memories will now fail, but we can verify those separately.

Tasks and verification IP

This final step brings us to the veer.sby file in the source repository for this application note. Let’s start with the newly added [tasks] section and the updated [options] and [engines] sections:

[tasks]
bmc
pdr
cover
axi_bmc: axi bmc
axi_pdr: axi pdr
axi_cover: axi cover

[options]
bmc:
mode bmc
multiclock on
depth 40
--
pdr:
mode prove
multiclock on
--
cover:
mode cover
multiclock on
--

[engines]
bmc: smtbmc --keep-going
pdr: abc --keep-going pdr
cover: smtbmc

There is now a task for each of bmc, prove, and cover modes. The options for the bmc task are the same as we had before, while pdr and cover select their corresponding mode and enable multiclock. For more on how to use tasks, refer to https://yosyshq.readthedocs.io/projects/sby/en/latest/reference.html.

We also have a tag, axi, which we will be using to optionally enable verification of the dma_ctrl AXI interface we were previously converting to assumptions. The axi_bmc, axi_pdr, and axi_cover tasks then correspond the earlier tasks but with an AXI destination verification IP connected.

In the same directory as the veer.sby you have been working on, run git clone https://github.com/YosysHQ-GmbH/SVA-AXI4-FVIP to download the verification IP we will be using. Following the example in yosyshq_tabbycad_example_setup.sby, we update the [files] section and veer.f file (omitted for brevity) to load the requisite files. For the bind file, we use dma_destination.sv, a modified version of destination template configured to match the interface in dma_ctrl.

[files]
Cores-VeeR-EH1/design
Cores-VeeR-EH1/configs
Cores-VeeR-EH1/tools

axi:
# Packages
SVA-AXI4-FVIP/AXI4/src/amba_axi4_protocol_checker_pkg.sv
SVA-AXI4-FVIP/AXI4/src/amba_axi4_low_power_channel.sv
SVA-AXI4-FVIP/AXI4/src/axi4_spec/amba_axi4_single_interface_requirements.sv
SVA-AXI4-FVIP/AXI4/src/axi4_spec/amba_axi4_definition_of_axi4_lite.sv
SVA-AXI4-FVIP/AXI4/src/axi4_spec/amba_axi4_atomic_accesses.sv
SVA-AXI4-FVIP/AXI4/src/axi4_spec/amba_axi4_transaction_structure.sv
SVA-AXI4-FVIP/AXI4/src/axi4_spec/amba_axi4_transaction_attributes.sv
SVA-AXI4-FVIP/AXI4/src/axi4_spec/amba_axi4_low_power_interface.sv
SVA-AXI4-FVIP/AXI4/src/axi4_lib/amba_axi4_write_response_dependencies.sv
SVA-AXI4-FVIP/AXI4/src/axi4_lib/amba_axi4_exclusive_access_source_perspective.sv

# Modules containing the properties
SVA-AXI4-FVIP/AXI4/src/amba_axi4_protocol_checker.sv
SVA-AXI4-FVIP/AXI4/src/amba_axi4_read_address_channel.sv
SVA-AXI4-FVIP/AXI4/src/amba_axi4_read_data_channel.sv
SVA-AXI4-FVIP/AXI4/src/amba_axi4_write_data_channel.sv
SVA-AXI4-FVIP/AXI4/src/amba_axi4_write_response_channel.sv
SVA-AXI4-FVIP/AXI4/src/amba_axi4_write_address_channel.sv

# Bind file
dma_destination.sv
--

Where before we were unconditionally converting the dma_ctrl AXI assertions, we now only want to do so if we are not running one of the axi_ tasks. The rest of our script section remains the same:

[script]
exec -expect-return 0 -- bash -c 'RV_ROOT=$PWD ./configs/veer.config'
read -f -formal veer.f
prep -flatten -top veer_wrapper

# dma_axi requires axi to be setup
# otherwise, convert axi asserts to assume
~axi: chformal -assert -assert2assume veer_wrapper/veer.dma_ctrl.assert_dma_axi_*

cutpoint t:$mul t:$mem_v2

The dma_ctrl module itself needs a minor adjustment which is included in dma_ctrl.patch. This change removes the clock gating while still keeping the dma_bus_clk signal connected. And finally, we need to add a constraint to the formal.sv file to keep the veer_wrapper.dma_bus_clk_en high.

With this, we can now perform verification on the VeeR core via bounded and unbounded model checking as well as generate traces for all cover statements, and we can optionally enable verification of the DMA AXI controller.