Verilator可将硬件描述语言Verilog/SystemVerilog编译成优化后的支持多线程的高级语言C++/SystemC模型,通过在高级语言中例化硬件描述语言,进而使用高级语言的特性实现对Verilog/SystemVerilog的验证。简单来说,Verilator对Verilog/SystemVerilog等硬件描述语言添加了一个基于C++/SystemC的wrapper,而后借助C++和Verilator的特定库实现对Verilog/SystemVerilog的高效仿真。
Verilator 本质上是一个 Verilog/SystemVerilog 模拟器。它具有商业级、超快速、免费和开源等特性,但是它不能直接替代 Modelsim、 Questa Sim、 Synopsys VCS、 Vivado Xsim 和其他基于事件的模拟器。Verilator 是一个基于周期的模拟器,这意味着它不能在单个时钟周期内计算时间,也不能模拟精确的电路时序。因此,在Verilator中电路状态通常每个时钟周期评估一次,因此不能观察到任何周期内的小故障,并且不支持定时信号延迟。
速度特性
由于Verilator是基于周期的,因此它不能用于时序模拟、反向注释网表、异步(无时钟)逻辑,或者任何涉及时间概念的信号变化。无论何时评估电路,所有输出都会瞬间切换。
然而,由于时钟边缘之间的一切都被忽略了,Verilator 的模拟运行速度非常快(比SystemC模型快10倍左右,比Verilog仿真器快100倍左右,且支持多线程),在模拟具有一个或多个时钟的同步数字逻辑电路的功能,或从Verilog/SystemVerilog 代码中创建软件模型以用于软件开发方面非常有效。
代码特性
由于 verilator 是基于周期的,它不能完全支持 IEEE Verilog 和 SystemVerilog 标准(综合工具通常也不是完全兼容的)。Verilator 对提供的 Verilog/SystemVerilog 代码非常严格。除了不支持任何时间延迟,它也不会接受大多数不可综合的代码 ,因而不能简单地用Verilator来综合基于 SystemVerilog的 测试平台。
因为 Verilator 不支持不可综合的代码,所以与其他模拟器相比,它更接近于综合工具的行为,这带来了意想不到的好处。它将迫使用户编写更好的可综合代码,从而有可能减少在后续开发过程中可能会遇到的问题数量。
1. 安装Verilator
参考官方手册:
# Prerequisites:
#sudo apt-get install git help2man perl python3 make autoconf g++ flex bison ccache
#sudo apt-get install libgoogle-perftools-dev numactl perl-doc
#sudo apt-get install libfl2 # Ubuntu only (ignore if gives error)
#sudo apt-get install libfl-dev # Ubuntu only (ignore if gives error)
#sudo apt-get install zlibc zlib1g zlib1g-dev # Ubuntu only (ignore if gives error)
git clone https://github.com/verilator/verilator # Only first time
# Every time you need to build:
unsetenv VERILATOR_ROOT # For csh; ignore error if on bash
unset VERILATOR_ROOT # For bash
cd verilator
git pull # Make sure git repository is up-to-date
git tag # See what versions exist
#git checkout master # Use development branch (e.g. recent bug fixes)
#git checkout stable # Use most recent stable release
#git checkout v{version} # Switch to specified release version
autoconf # Create ./configure script
./configure --prefix /opt/verilator_path # Configure install path and create Makefile
make -j `nproc` # Build Verilator itself (if error, try just 'make')
sudo make install
安装后检查版本信息:
yyx@yyx-virtual-machine:~/riscv/projects/LITEX/litex$ verilator --version
Verilator 4.028 2020-02-06 rev v4.026-92-g890cecc1
2. 上手Verilator
/****** alu.sv ******/
typedef enum logic [1:0] {
add = 2'h1,
sub = 2'h2,
nop = 2'h0
} operation_t /*verilator public*/;
module alu #(
parameter WIDTH = 6
) (
input clk,
input rst,
input operation_t op_in,
input [WIDTH-1:0] a_in,
input [WIDTH-1:0] b_in,
input in_valid,
output logic [WIDTH-1:0] out,
output logic out_valid
);
operation_t op_in_r;
logic [WIDTH-1:0] a_in_r;
logic [WIDTH-1:0] b_in_r;
logic in_valid_r;
logic [WIDTH-1:0] result;
// Register all inputs
always_ff @ (posedge clk, posedge rst) begin
if (rst) begin
op_in_r <= '0;
a_in_r <= '0;
b_in_r <= '0;
in_valid_r <= '0;
end else begin
op_in_r <= op_in;
a_in_r <= a_in;
b_in_r <= b_in;
in_valid_r <= in_valid;
end
end
// Compute the result
always_comb begin
result = '0;
if (in_valid_r) begin
case (op_in_r)
add: result = a_in_r + b_in_r;
sub: result = a_in_r + (~b_in_r+1'b1);
default: result = '0;
endcase
end
end
// Register outputs
always_ff @ (posedge clk, posedge rst) begin
if (rst) begin
out <= '0;
out_valid <= '0;
end else begin
out <= result;
out_valid <= in_valid_r;
end
end
endmodule;
上述代码的预期结果如下:
2.1 模型转换
使用下面的指令可将alu.sv转换成对应的C++模型,其中的—cc参数指明转换的目的语言为C++,同样,也可使用—sc参数转换为对应的SystemC模型。
verilator --cc [alu.sv](http://alu.sv/)
另外,通过运行指令”verilator —help“,可查看Verilator的所有内置参数的含义,用于日常使用查询,如下:
上述指令运行结束后,会生成一个obj_dir目录,该目录下包含了C++模型等一系列文件,且认真观察就会发现,大部分文件的命名都是以”V+模块名称”的前缀形式。如下:
其中主要需要关注如下几个文件:
- Valu.h:该文件是生成C++模型的基本头文件,包含了alu类的声明,用于在测试中声明dut。
- Valu___024unit.h:alu类的内部头文件,包含了alu中operation_t类型的声明。
2.2 构建测试文件
基于上述alu文件,一个基本的基于Verilator C++测试文件如下:
#include <stdlib.h>
#include <iostream>
#include <verilated.h> //Verilator库的头文件
#include <verilated_vcd_c.h> //VCD波形输出头文件
#include "Valu.h"
#include "Valu___024unit.h"
#define MAX_SIM_TIME 200 //定义模拟的时钟边沿数(包括上下边沿)
vluint64_t sim_time = 0;
int main(int argc, char** argv, char** env) {
// 实例化一个 Valu 类型的对象 dut
Valu *dut = new Valu;
// 开启波形跟踪
Verilated::traceEverOn(true);
// 实例化一个 VerilatedVcdC 类型的对象 m_trace,用于波形跟踪
VerilatedVcdC *m_trace = new VerilatedVcdC;
// 将 m_trace 与 dut 进行关联,其中5表示波形的采样深度为5级以下
dut->trace(m_trace, 5);
m_trace->open("waveform.vcd");
int a,b,invalid;
while (sim_time < MAX_SIM_TIME) {// 循环执行仿真
dut->clk ^= 1;// 取反时钟信号
if(dut->clk == 0){
a = rand() % 32;// 生成随机数 a
b = rand() % 32;// 生成随机数 b
dut->a_in = a;
dut->b_in = b;
dut->op_in = rand() % 3;
invalid = rand() % 2;
dut->in_valid = invalid;
}
//调用eval(), 计算 ALU 模块中的所有信号值
dut->eval();
if(dut->clk == 0 && invalid == 1){
printf("sim_time = %ld,a = %d, b = %d, out = %d out_valid = %d\n",sim_time, a, b, dut->out,dut->out_valid);
}
//将所有跟踪的信号值写入波形转储文件
m_trace->dump(sim_time);
sim_time++; // 模拟时钟边沿数加1
}
// 关闭波形文件
m_trace->close();
delete dut;
exit(EXIT_SUCCESS);
}
需要注意以下几点:
- dut→clk不需要初始化,直接异或进行时钟取反操作;
- 仔细观察可知,MAX_SIM_TIME并不是指仿真时间,而是指时钟跳变的次数。而在Verilator中,跳变一次默认是1ns,因而在上述的程序中,仿真时间为200ns,对应100个时钟周期,一个时钟周期为2ns。
- 结合上一条时钟说明,通过if(dut→clk==0),使输入数据一个时钟周期跳变一次,如果不加判断,会导致数据一个时钟跳变就变化一次。
2.3 构建仿真脚本
为了生成仿真波形文件,需要通过运行仿真、编译和运行可执行文件三个步骤。
1.运行仿真,执行指令:
verilator -Wall $(TOP).sv main.cpp --cc --exe --trace --top-module $(TOP)
其中,TOP为顶层文件文件名称变量,此处为alu。
-Wall参数用于打开运行仿真过程中CPP文件中的所有警告信息。同样,也存在关闭运行过程中的警告信息参数-Wno_fatal。例如,在使用-Wall参数情况下,当打印仿真时间sim_time的值时,若采用%d的输出格式,会出现如下警告信息:
而当使用-Wno-fatal参数时,会隐藏上述警告信息。
—exe:声明创建可执行文件;
—trace:打开波形跟踪功能;
—top-module:声明顶层文件名
2.编译:
make -C obj_dir -f V$(TOP).mk V$(TOP)
-C:指定执行的文件目录
-f:指定make执行的文件
即在当前obj_dir目录的Valu.mk文件中构建Valu目标,生成Valu可执行,在本例程中生成的Valu.mk文件的Valu规则如下:
3.运行可执行文件
运行指令:
./obj_dir/Valu
最终整合在Makefile文件中如下:
default: compile
TOP ?= alu
obj_dir/V$(TOP).mk:
verilator -Wall $(TOP).sv main.cpp --cc --exe --trace --top-module $(TOP)
compile: obj_dir/V$(TOP).mk
make -C obj_dir -f V$(TOP).mk V$(TOP)
run:compile
obj_dir/V$(TOP)
.PHONY: default compile clean run
clean:
rm -rf obj_dir
3. 波形查看
采用gtkwave软件,查看波形指令如下:
gtkwave waveform.vcd
4. 参考链接
https://itsembedded.com/dhd_list/