2. 使用Hdlbits网页FPGA代码综合仿真设计

给大家推荐一款网页版的 Verilog代码编辑仿真验证平台,这个平台是国外的一家开源FPGA学习网站,通过“https://hdlbits.01xz.net/wiki/Main_Page”地址链接进入网页,在该网页上可以进行Verilog代码的编写、综合,而且最后还能够仿真出波形来验证设计代码的正确性,该验证平台是基于Icarus Verilog(简称iVerilog,比较著名的开源HDL仿真工具,也有对应的安装版本)的,让你随时随地只需登录网页就能够享受Verilog编程仿真的乐趣!

2.1. 官方模板演示

  1. 首先打开“https://hdlbits.01xz.net/wiki/Main_Page”,打开后的界面如下图所示,全英文显示。如果感觉自己的英文水平欠佳,可以使用谷歌浏览器打开该网页,并选择在线翻译功能,翻译的正确率还是很高的。

Hdlbit002

图 77‑1

2、点击Simulation下的“Run a Simulation(lcarus Verilog)”。

Hdlbit003

图 77‑2

3、打开后的界面如下图所示,代码编辑框中给出了一个简单的例子。

Hdlbit004

图 77‑3

  1. 点击下面的“Submit(new window)“在新界面中进行仿真。

Hdlbit005

图 77‑4

5、在新打开的界面中我们可以看到编译的信息和仿真波形图。

Hdlbit006

图 77‑5

2.2. 实例演示

虽然看完了官方的模板演示,但我们要想立刻仿真验证自己设计的代码并不是那么容易,需要进行一番摸索。下面就是大家进行一个呼吸灯的设计实例演示。

1、学习过FPGA的朋友都知道要想对FPGA逻辑进行仿真一定要具备两个文件,一个是RTL代码文件,用来综合生成硬件电路的部分;第二个就是Testbench文件,用来验证RTL代码功能的仿真文件,这两者缺一不可。

2、根据观察发现官方模板中的代码编辑部分有两个module,大家也都知道一个.v 文件中只能有一个模块,也就是只能有一个module,而这里面有两个,那肯定就不对了。再仔细观察会发现代码编辑区域中的上半部分就是Testbench,而下半部分则是RTL代码,再结合仿真出的波形来更看验证了这个想法。原来 RTL 代码和Testbench都写在了一个编辑框里。

3、但是我们在提供的模板中发现一些我们平时几乎没有见过的新语法,如第4行的“initial `probe_start”、第6行的“`probe(clk)”、第26行的“`probe(in)”,通过模板的注释和多次实验发现这是官方定义的一个”宏“,也就是通过这个”宏“调用“probe”探针的功能,我们不用管这个”宏“是如何定义的,我们只需要会调用就可以了。

4、下面我们通过该网页来仿真验证一下自己设计的呼吸灯的例子。详细代码如下(呼吸灯逻辑和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
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
\`timescale 1ns/1ns

//----------------Tesebench-----------------
module top_module; //仿真文件名必须是“top_module”

reg sclk;
reg rst_n;

wire led;

initial \`probe_start; // Start the timing diagram

\`probe(sclk); // Probe signal "clk",这是加载的系统时钟,只能在Tesebench中加载

//初始化
initial begin
sclk = 1'b0;
rst_n <= 1'b0;
#200
rst_n <= 1'b1;
#5000 //一定要设置仿真停止时间,如果仿真结束时间太久会提示
$finish;
end

//产生20ns的时钟
always #10 sclk = ~sclk;

//为了减少仿真时间我们在仿真中重定义参数,不影响RTL代码中参数的值
defparam breath_led_inst.CNT_1US_MAX = 1;
defparam breath_led_inst.CNT_1MS_MAX = 2;
defparam breath_led_inst.CNT_1S_MAX = 2;

//----------------breath_led-----------------
breath_led breath_led_inst(
.sclk (sclk ), //input sclk
.rst_n(rst_n), //input rst_n

.led (led ) //output led
);

endmodule

//----------------RTL-----------------
module breath_led
#(
parameter CNT_1US_MAX = 6'd49,
parameter CNT_1MS_MAX = 10'd999,
parameter CNT_1S_MAX = 10'd999
)
(
input wire sclk ,
input wire rst_n ,

output reg led
);

reg [5:0] cnt_1us;
reg [9:0] cnt_1ms;
reg [9:0] cnt_1s;
reg cnt_1us_flag;
reg cnt_1ms_flag;
reg cnt_1s_flag;

//cnt_1us:1us计数器
always@(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
cnt_1us <= 6'b0;
else if(cnt_1us == CNT_1US_MAX)
cnt_1us <= 6'b0;
else
cnt_1us <= cnt_1us + 1'b1;

//cnt_1us_flag:1us计数器标志信号
always@(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
cnt_1us_flag <= 1'b0;
else if(cnt_1us == CNT_1US_MAX)
cnt_1us_flag <= 1'b1;
else
cnt_1us_flag <= 1'b0;

//cnt_1ms:1ms计数器
always@(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
cnt_1ms <= 10'b0;
else if(cnt_1ms == CNT_1MS_MAX && cnt_1us_flag == 1'b1)
cnt_1ms <= 10'b0;
else if(cnt_1us_flag == 1'b1)
cnt_1ms <= cnt_1ms + 1'b1;

//cnt_1ms_flag:1ms计数器标志信号
always@(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
cnt_1ms_flag <= 1'b0;
else if(cnt_1ms == CNT_1MS_MAX && cnt_1us_flag == 1'b1)
cnt_1ms_flag <= 1'b1;
else
cnt_1ms_flag <= 1'b0;

//cnt_1s:1s计数器
always@(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
cnt_1s <= 10'b0;
else if(cnt_1s == CNT_1S_MAX && cnt_1ms_flag == 1'b1)
cnt_1s <= 10'b0;
else if(cnt_1ms_flag == 1'b1)
cnt_1s <= cnt_1s + 1'b1;

//cnt_1s_flag:1s计数器标志信号
always@(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
cnt_1s_flag <= 1'b0;
else if(cnt_1s == CNT_1S_MAX && cnt_1ms_flag == 1'b1)
cnt_1s_flag <= ~cnt_1s_flag;

//led:一个LED灯
always@(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
led <= 1'b0;
else if((cnt_1s_flag == 1'b1 && cnt_1ms <= cnt_1s)
\|\| (cnt_1s_flag == 1'b0 && cnt_1ms > cnt_1s))
led <= 1'b1;
else
led <=1'b0;

//添加要观察的信号名
\`probe(rst_n ); // Sub-modules can also have \`probe()
\`probe(cnt_1us ); // Sub-modules can also have \`probe()
\`probe(cnt_1us_flag ); // Sub-modules can also have \`probe()
\`probe(cnt_1ms ); // Sub-modules can also have \`probe()
\`probe(cnt_1ms_flag ); // Sub-modules can also have \`probe()
\`probe(cnt_1s ); // Sub-modules can also have \`probe()
\`probe(cnt_1s_flag ); // Sub-modules can also have \`probe()
\`probe(led ); // Sub-modules can also have \`probe()

endmodule

5、将上面编写好的Testbench代码和RTL代码放到一个文件中(Testbench在上面,RTL代码在下面,仅在该平台仿真时可以将两种文件放在一起,在其他平台仿真时要独立放到两个.v文件中),然后复制粘贴到代码编辑框中,点击“Submit(new window)“执行仿真。

Hdlbit007

图 77‑6

6、也可以将写好的Testbench代码和RTL代码放到同一个.v文件中,然后点击下面的代码编辑框下面的“Upload a source file…”,在展开的界面中选择添加.v文件后,再点击”Upload and simulate”启动仿真。

Hdlbit008

图 77‑7

Hdlbit009

图 77‑8

7、仿真波形如下所示,因为界面空间有限,拖动波形显示框下面的滚动条可以看到后面显示的波形。

Hdlbit010

图 77‑9

8、再波形显示框中右击鼠标可以选择保存为PNG格式或者SVG格式,将完整的波形信息保存下来。

Hdlbit011

图 77‑10

9、保存为SVG格式后的完整波形图如下所示。

Hdlbit012

图 77‑11

10、如果我们在第58行处代码设置一个错误后,再点击执行仿真,此时仿真界面中不会显示波形,而是提示错误的内容,将错误修改后再执行仿真即可。

Hdlbit013

图 77‑12

Hdlbit014

图 77‑13

11、该网页还有其他更多有趣的功能,如组合逻辑代码编写训练、时序逻辑代码编写训练、单片机嵌入式仿真等等,有兴趣的朋友可以自己探索,这里不再一一演示。