【注意】最后更新于 September 16, 2022,文中内容可能已过时,请谨慎使用。
Yosys 是一个开源的 Verilog HDL 综合工具包。它支持将电路的状态转换编码为 SMT-LIBv2 中的函数。由此出发,可以对电路进行一系列形式化验证。
注意
本文仅对相关工具的一般使用方法进行介绍,不涉及验证原理、算法分析等内容。
本文讨论的内容均为 Assertion Based Verification (ABV),除此之外 Yosys 还支持 Symbolic Model Checking、Formal Equivalence Checking,相关用法有待进一步整理。
Motivating Example
test.v
定义了一个简单的时序逻辑电路(此示例电路来自 Yosys Command Reference Manual),它只有输出,没有输入。它的输出可以理解为一个数列 $y_{n+1}=\text{or}(\text{shl}(y_n),\text{neg}(y_n))$,首项 $y_0$ 的值是电路的初始状态。
1
2
3
4
| module test(input clk, output reg [3:0] y);
always @(posedge clk)
y <= (y << 1) | ^y;
endmodule
|
我们的目标是验证输出 y
的值不可能从一个非零值变为一个零值。为了验证该性质,需要将其写为 SMT-LIBv2 表达式,交给 SMT solver 求解。因此,首先需要明白编码电路的方式。
使用以下综合脚本(synthesis script)test.ys
指示 Yosys 对电路进行变换,得到 test.smt2
文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # Read Verilog source file and convert to internal representation.
read_verilog test.v
# Elaborate the design hierarchy.
# Should always be the first command after reading the design.
hierarchy -check -top test
# Convert “processes” (the internal representation of behavioral Verilog code)
# into multiplexers and registers.
proc
# Perform some basic optimizations and cleanups.
opt
# Check for obvious problems in the design.
check -assert
# Write a SMT-LIBv2 description of the current design.
write_smt2 test.smt2
|
理解 synthesis script
以上脚本包含了3种类型的指令:
- Frontends(前端):从文件中读取输入(一般为 Verilog 代码)
- Passes(“趟”):在电路上应用等价变换
- Backends(后端):将处理后的电路输出到文件(支持不同的格式,如 Verilog, BLIF, EDIF, SPICE, BTOR 等)
这也是所有典型的 Yosys synthesis script 都具有的结构,由此可见综合的过程与编译的过程非常相似。事实上,Yosys 定义了一种电路的中间表示格式 RTLIL (RTL Intermediate Language),所有的 Passes 都是在以此 IR 表示的抽象语法树(AST)上实现的。
Yosys 输出的文件如下。此文件中定义了一个对应电路状态的类型 |<mod>_s|
;电路中的输入(input)、输出(output)、寄存器(register)、线路(wire)都有各自对应的函数,这些函数名为 |<mod>_n <wirename>|
,它们接受一个电路状态作为输入,返回 Bool 类型或 BitVec 类型,对应具体的值。另外一个重要的函数为 |<mod>_t|
,它接受两个状态 state, next_state 作为输入,当两者之间存在状态转换关系时,则返回 True,反之则返回 False。它们组合起来编码了电路的行为。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| ; SMT-LIBv2 description generated by Yosys 0.21+7 (git sha1 d98738db5, clang 10.0.0-4ubuntu1 -fPIC -Os)
; yosys-smt2-module test
(declare-sort |test_s| 0)
(declare-fun |test_is| (|test_s|) Bool)
(declare-fun |test#0| (|test_s|) Bool) ; \clk
; yosys-smt2-input clk 1
; yosys-smt2-clock clk posedge
; yosys-smt2-witness {"offset": 0, "path": ["\\clk"], "smtname": "clk", "type": "posedge", "width": 1}
; yosys-smt2-witness {"offset": 0, "path": ["\\clk"], "smtname": "clk", "type": "input", "width": 1}
(define-fun |test_n clk| ((state |test_s|)) Bool (|test#0| state))
; yosys-smt2-witness {"offset": 0, "path": ["\\y"], "smtname": 1, "type": "reg", "width": 4}
(declare-fun |test#1| (|test_s|) (_ BitVec 4)) ; \y
; yosys-smt2-output y 4
; yosys-smt2-register y 4
(define-fun |test_n y| ((state |test_s|)) (_ BitVec 4) (|test#1| state))
(define-fun |test#2| ((state |test_s|)) Bool (xor (= ((_ extract 0 0) (|test#1| state)) #b1) (= ((_ extract 1 1) (|test#1| state)) #b1) (= ((_ extract 2 2) (|test#1| state)) #b1) (= ((_ extract 3 3) (|test#1| state)) #b1))) ; $reduce_xor$test.v:3$3_Y
(define-fun |test#3| ((state |test_s|)) (_ BitVec 4) (bvor (concat ((_ extract 2 0) (|test#1| state)) #b0) (concat #b000 (ite (|test#2| state) #b1 #b0)))) ; $0\y[3:0]
(define-fun |test_a| ((state |test_s|)) Bool true)
(define-fun |test_u| ((state |test_s|)) Bool true)
(define-fun |test_i| ((state |test_s|)) Bool true)
(define-fun |test_h| ((state |test_s|)) Bool true)
(define-fun |test_t| ((state |test_s|) (next_state |test_s|)) Bool
(= (|test#3| state) (|test#1| next_state)) ; $procdff$5 \y
) ; end of module test
; yosys-smt2-topmod test
; end of yosys output
|
现在,为了表示上述性质,可以定义两个状态 s1, s2,它们满足:
- s1 到 s2 存在状态转换关系
- s1 中
y != 0
- s2 中
y == 0
之后交给 SMT solver 验证其可满足性,若不能满足,则验证了不存在这样的情况。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| ; we need QF_UFBV for this proof
(set-logic QF_UFBV)
; insert the auto-generated code here
%%
; declare two state variables s1 and s2
(declare-fun s1 () test_s)
(declare-fun s2 () test_s)
; state s2 is the successor of state s1
(assert (test_t s1 s2))
; we are looking for a model with y non-zero in s1
(assert (distinct (|test_n y| s1) #b0000))
; we are looking for a model with y zero in s2
(assert (= (|test_n y| s2) #b0000))
; is there such a model?
(check-sat)
|
将上面的 test.ys
最后一行修改为:
1
| write_smt2 -tpl test.tpl test.smt2
|
新的输出文件中包含了模版文件 test.tpl
的内容,并将其中的 %%
替换为了原本 write_smt2
命令的输出,可以将它作为 SMT solver 的输入。
例如,调用 z3 进行求解,得到 unsat 的结果。
使用 SymbiYosys 进行验证
上一节介绍的方法需要用户理解 write_smt2
命令的输出,并直接使用其中定义的函数,才能将需要验证的性质编写为 SMT-LIBv2 格式的表达式,这样不免有些繁琐。Yosys 还提供了 SymbiYosys (sby) 工具,它可以理解为一个前端驱动程序(front-end driver program),支持解析用户在源文件中定义的断言(assertions),直接尝试进行证明。
来看另一个例子,下面是用 System Verilog 定义的一个计数器(此示例代码来自参考资料中 Formal Verification of RISC-V cores with riscv-formal 这一幻灯片的第3~4页)。
1
2
3
4
5
6
7
8
9
10
11
12
13
| module hello (
input clk, rst,
output [3:0] cnt
);
reg [3:0] cnt = 0;
always @(posedge clk) begin
if (rst)
cnt <= 0;
else
cnt <= cnt + 1;
end
endmodule
|
现在来定义此电路需要满足的性质。首先用1个 assume
语句表明验证的前提(也就是在求解器考虑的所有情形中,此性质都得到满足);assert
语句则是求解器需要证明的性质。
1
2
3
4
5
6
7
| module hello (/* ... */);
/* ... */
`ifdef FORMAL
always @* assume (cnt != 10);
always @* assert (cnt != 15);
`endif
endmodule
|
SymbiYosys 使用一个 .sby
文件来描述验证过程中执行的任务,文件中包含若干个节(section),每个节由方括号括起的小标题表示。下面的文件中,[options]
节将证明模式设置为“使用 Unbounded model check 验证 safety properties”,并且将 k-induction 的深度设置为10;[script]
中是处理输入文件用到的 Yosys 命令;输入文件列在 [files]
节中。关于 .sby
文件语法的更多信息请参考 Reference for .sby file format,这里并不展开说明。
1
2
3
4
5
6
7
8
9
10
11
12
13
| [options]
mode prove
depth 10
[engines]
smtbmc z3
[script]
read_verilog -formal hello.sv
prep -top hello
[files]
hello.sv
|
将上面的两个文件放在同一目录下,然后调用 sby
程序,即可获得证明结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
| $ sby -f hello.sby
...
SBY 19:06:39 [hello] engine_0.induction: finished (returncode=0)
SBY 19:06:39 [hello] engine_0: Status returned by engine for induction: pass
...
SBY 19:06:39 [hello] engine_0.basecase: finished (returncode=0)
SBY 19:06:39 [hello] engine_0: Status returned by engine for basecase: pass
SBY 19:06:39 [hello] summary: Elapsed clock time [H:MM:SS (secs)]: 0:00:00 (0)
SBY 19:06:39 [hello] summary: Elapsed process time [H:MM:SS (secs)]: 0:00:00 (0)
SBY 19:06:39 [hello] summary: engine_0 (smtbmc z3) returned pass for induction
SBY 19:06:39 [hello] summary: engine_0 (smtbmc z3) returned pass for basecase
SBY 19:06:39 [hello] summary: successful proof by k-induction.
SBY 19:06:39 [hello] DONE (PASS, rc=0)
|
参考资料
文章作者
Yike Zhou
上次更新
2022-09-16
许可协议
知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议