惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

宝玉的分享
宝玉的分享
T
Threat Research - Cisco Blogs
H
Hacker News: Front Page
N
News and Events Feed by Topic
Know Your Adversary
Know Your Adversary
Cisco Talos Blog
Cisco Talos Blog
SecWiki News
SecWiki News
C
Cisco Blogs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Tor Project blog
K
Kaspersky official blog
Forbes - Security
Forbes - Security
Webroot Blog
Webroot Blog
Schneier on Security
Schneier on Security
P
Privacy & Cybersecurity Law Blog
H
Heimdal Security Blog
Y
Y Combinator Blog
The GitHub Blog
The GitHub Blog
S
SegmentFault 最新的问题
V
Vulnerabilities – Threatpost
T
Tenable Blog
T
Tailwind CSS Blog
P
Privacy International News Feed
WordPress大学
WordPress大学
大猫的无限游戏
大猫的无限游戏
小众软件
小众软件
博客园 - Franky
Hacker News: Ask HN
Hacker News: Ask HN
Jina AI
Jina AI
C
Cybersecurity and Infrastructure Security Agency CISA
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
雷峰网
雷峰网
Vercel News
Vercel News
A
About on SuperTechFans
爱范儿
爱范儿
Simon Willison's Weblog
Simon Willison's Weblog
AWS News Blog
AWS News Blog
The Last Watchdog
The Last Watchdog
Engineering at Meta
Engineering at Meta
Spread Privacy
Spread Privacy
Security Archives - TechRepublic
Security Archives - TechRepublic
博客园 - 司徒正美
量子位
博客园 - 三生石上(FineUI控件)
J
Java Code Geeks
Hacker News - Newest:
Hacker News - Newest: "LLM"
Recorded Future
Recorded Future
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Martin Fowler
Martin Fowler
Project Zero
Project Zero

KuangjuX(狂且)

提高 Directory Cache 性能 高速缓存技术(Cache) Chisel 学习笔记 随笔(一) 指令级并行技术 TLB MMU 笔记(基于 MIPS) MIT-6.S081-mmap MIT-6.S081 Networking MIT-6.S081 Lazy Page Allocation MIT-6.S081 Copy on Write Fork MIT-6.S081 User Level Threads 2021年终总结 xv6-riscv中的KPTI机制 RISC -V N扩展 io_uring 阅读笔记 在Ubuntu中升级Linux内核 飞书Rust实习面试 Rust并发安全总结 Rust String 用法总结
Verilator 学习笔记
KuangjuX · 2022-02-20 · via KuangjuX(狂且)

Introduction

Verilator 是一个 verilog/systemverilog 的仿真器,但是它不能直接代替 vivado xsim 这些事件驱动的仿真器。Verilator 是一个基于周期的仿真器,这意味着它不会评估单个周期内的时间,也不会模拟精确的电路时序。相反,通常每个时钟周期评估一次电路状态,因此无法观察到任何时钟周期内毛刺,并且不支持定时信号延迟。

由于 Verilator 是基于周期的,它不能用于时序仿真、反向注释网表、异步(无时钟)逻辑,或者一般来说任何涉及时间概念的信号变化 - 每当评估电路时,所有输出都会立即切换。

然而,由于时钟边沿之间的一切都被忽略了,Verilator 的模拟运行速度非常快,非常适合模拟具有一个或多个时钟的同步数字逻辑电路的功能,或者用于从 Verilog/SystemVerilog 代码创建软件模型以用于软件开发。

由于 Verilator 是基于周期的仿真器,因此对于 systemVerilog 并非完全支持(我们一般也用不到),同时对于 verilog/systemverilog 的检查很严格,同时 Verilator 不支持一些不可综合的代码(例如 $display(), $finish(), $fatal(), 一些版本的 $assert() 或者其他)。

使用 Verilator 来仿真时需要 HDL 作为源代码和 C++ 作为测试代码来共同完成,使用 Verilator 来仿真是非常快速的。

在这里我们写了一个简单的 alu 程序来说明 Verilator 的使用:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/****** 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;

在使用 C++ 编写测试用例之前,我们需要首先将 sv 代码编译成 C++ 代码:

1
verilator --cc alu.sv

cc 参数告诉 Verilator 将源代码转化成 C++ 代码,在编译之后生成了一个 obj_dir 文件夹,其中 mk 文件用于使用 Make 构建仿真的可执行程序,.h.cpp 文件包含我们源代码实现的信息。

接下来我们使用 C++ 写一个测试文件用于测试我们的 ALU:

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
27
28
29
30
31
32
33
34
35
36
#include <stdlib.h>
#include <iostream>
#include <verilated.h>
#include <verilated_vcd_c.h>
#include "Valu.h"
#include "Valu___024unit.h"


#define MAX_SIM_TIME 20
vluint64_t sim_time = 0;

int main(int argc, char** argv, char** env) {

Valu *dut = new Valu;


Verilated::traceEverOn(true);
VerilatedVcdC *m_trace = new VerilatedVcdC;
dut->trace(m_trace, 5);
m_trace->open("waveform.vcd");

while (sim_time < MAX_SIM_TIME) {

dut->clk ^= 1;

dut->eval();

m_trace->dump(sim_time);

sim_time++;
}

m_trace->close();
delete dut;
exit(EXIT_SUCCESS);
}

关于测试文件的作用已经作为注释写在了代码中,接下来我们将编译我们的测试文件并进行仿真,此时需要运行 Verilator 并重新生成包含测试用例的 .mk 文件:

1
$ verilator -Wall --trace -cc alu.sv --exe tb_alu.cpp
  • -Wall 表示开启 C++ 所有警告
  • --trace 表示开启波形跟踪

随后我们进行编译:

1
$ make -C obj_dir -f Valu.mk Valu

在编译之后,我们执行 obj_dir/Valu 进行仿真,此时会生成波形图 waveform.vcd,我们只需要执行 gtkwave waveform.vcd 即可查看波形图。

Basics of Systemverilog verification using C++

Randomized initial values

Verilator 是一个两阶段的仿真器,这意味着它仅仅支持逻辑信号 0 和 1,不支持逻辑信号 X,对 Z 信号也仅仅是有限的支持。因此 Verilator 初始化所有的信号为 0。幸运的是,我们可以改变这种行为通过命令行参数,我们可以要求 Verilator 初始化所有的值为 1 或者其他随机数,这将帮助我们检查我们重置信号是否工作了。

为了帮助我们的测试用例初始化为随机数,我们需要调用 Verilated::commandArgs(argc, argv); 在创建 DUT 对象前。

1
2
3
4
int main(int argc, char** argv, char** env) {
Verilated::commandArgs(argc, argv);
Valu *dut = new Valu;
<...>

之后我们还需要添加我们的编译选项 --x-assign unique--x-initial unique,结果如下:

1
verilator -Wall --trace --x-assign unique --x-initial unique -cc $(MODULE).sv --exe tb_$(MODULE).cpp

最终,我们需要通过添加 +verilator+rand+reset+2 在执行我们的仿真可执行文件时:

1
@./obj_dir/V$(MODULE) +verilator+rand+reset+2

DUT Reset

在我们的测试用例中,我们可以添加:

1
2
3
4
5
6
7
8
dut->rst = 0;
if(sim_time > 1 && sim_time < 5){
dut->rst = 1;
dut->a_in = 0;
dut->b_in = 0;
dut->op_in = 0;
dut->in_valid = 0;
}

来进行信号的重新设置。

Basic Verification

在我们的测试程序中,我们可以使用 Verilated::gotFinished() 来停止仿真(相当于 $finish())。

Verilator Examples

我们首先看一个测试用例的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdlib.h>
#include "Vmodule.h"
#include "verilated.h"

int main(int argc, char **argv) {

Verilated::commandArgs(argc, argv);


Vmodule *tb = new Vmodule;


while(!Verilated::gotFinish()) {
tb->i_clk = 1;
tb->eval();
tb->i_clk = 0;
tb->eval();
} exit(EXIT_SUCCESS);
}

在这个测试中,当我们将 i_clk 从 0 变为 1 的时候将会造成所有 @(posedge i_clk) 的逻辑块运行。因此我们测试的作用就是在一个循环中不断修改时钟并进行执行。仿真结束当 verilog 执行了 $finished() 或者使用 Ctrl-C 来终止进程。

现在我们尝试根据这个功能使用一个 TESRBENCH 类来包裹所需的功能:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
template<class MODULE>	class TESTBENCH {
unsigned long m_tickcount;
MODULE *m_core;

TESTBENCH(void) {
m_core = new MODULE;
m_tickcount = 0l;
}

virtual ~TESTBENCH(void) {
delete m_core;
m_core = NULL;
}

virtual void reset(void) {
m_core->i_reset = 1;

this->tick();
m_core->i_reset = 0;
}

virtual void tick(void) {

m_tickcount++;




m_core->i_clk = 0;
m_core->eval();




m_core->i_clk = 1;
m_core->eval();


m_core->i_clk = 0;
m_core->eval();
}

virtual bool done(void) { return (Verilated::gotFinish()); }
}

我们的 TESTBENCH 类提供两个方法: tick()reset() ,并且我们希望在任何时刻检查是否 Verilator 的状态为 $finished

主程序的修改如下:

1
2
3
4
5
6
7
8
9
10
#include "testbench.h"

int main(int argc, char **argv) {
Verilated::commandArgs(argc, argv);
TESTBENCH<Vmodule> *tb = new TESTBENCH<Vmodule>();

while(!tb->done()) {
tb->tick();
} exit(EXIT_SUCCESS);
}

在测试的时候我们也可以 printf() 一些关键的信号帮助我们调试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class	MODULE_TB : public TESTBENCH<Vmodule> {

virtual void tick(void) {


TESTBENCH<Vmodule>::tick();



printf("%8ld: %s %s ...\n", m_tickcount,
(m_core->v__DOT__wb_cyc)?"CYC":" ",
(m_core->v__DOT__wb_stb)?"STB":" ",
... );
}
}

除此之外我们也可以添加一些信号的判断来帮助我们决定是否进行输出一些信息:

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
27
28
29
30
31
32
33
class	MODULE_TB : public TESTBENCH<Vmodule> {

virtual void tick(void) {


TESTBENCH<Vmodule>::tick();

bool writeout = false;







if ((m_core->v__DOT__wb_stb)&&(!m_core->v__DOT__wb_stall))
writeout = true;



if (m_core->v__DOT__wb_ack)
writeout = true;

if (writeout) {


printf("%8ld: %s %s ...\n", m_tickcount,
(m_core->v__DOT__wb_cyc)?"CYC":" ",
(m_core->v__DOT__wb_stb)?"STB":" ",
... );
}
}
}

如果我们想去使用 gtkwave 来生成波形的话,我们需要在 TESTBENCH 在执行时不断生成波形:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <verilated_vcd_c.h>

template<class MODULE> class TESTBENCH {

VerilatedVcdC *m_trace;
...

TESTBENCH(void) {



Verilated::traceEverOn(true);
...
}


virtual void opentrace(const char *vcdname) {
if (!m_trace) {
m_trace = new VerilatedVcdC;
m_core->trace(m_trace, 99);
m_trace->open(vcdname);
}
}


virtual void close(void) {
if (m_trace) {
m_trace->close();
m_trace = NULL;
}
}

virtual void tick(void) {


m_tickcount++;







m_core->i_clk = 0;
m_core->eval();






if(trace) m_trace->dump(10*m_tickcount-2);


m_core->i_clk = 1;
m_core->eval();
if(trace) m_trace->dump(10*m_tickcount);


m_core->i_clk = 0;
m_core->eval();
if (m_trace) {



m_trace->dump(10*m_tickcount+5);




m_trace->flush();
}
}
}

References