摘要:本文主要介绍了一些在verilog中的行为级语法,并且提供了大量的运行实际例子,可以通过这些例子感受行为级语法在仿真中的巨大作用。
概述:行为级语法是RTL级的上一层,或者说是比RTL级更高级的语法,其语法更加符合人类思维的描述方式。行为级语法可用于快速验证算法的正确性,或快速构建一个复杂的系统模型。仿真验证中使用行为级的语法可以充分发挥仿真平台的优越性,用更加简介、高效的语法语句简化并加快仿真系统的构建,让设计者把更多的时间和精力集中在设计和调试上。行为级多以直接赋值、指定延迟、算术运算等不可综合的形式进行,不关注电路的具体结构,因为其并不需要直接综合成实际的电路结构并运行到FPGA器件中。
1.initial和always
initial和always语句所实现的行为级语法遵循硬件固有的并行性特点,在仿真过程中可以独立并行地运作。在一个测试脚本中无论是有一个还是多个initial或always语句,它们都会从仿真的0ns开始并行工作。initial语句在整个仿真过程中只会运行一次,而always语句则会不断重复运行,直到仿真运行停止。
`timescale 1ns/1ns
module testbench_top();
reg a, b;
initial begin
a='b1;
b='b0;
end
initial begin
#1000;
$stop;
end
always begin
#50 a=~a;
end
always begin
#100 b=~b;
end
endmodule
2.时间刻度`timescale
`timescale对事件单位代表的物理时间进行定义,其后接时间单位和事件精度,两者之间用反斜杠/进行分隔。语法结构为`timescale (时间单位)/(时间精度)。
3.时间戳 $time
时间戳time是一个系统函数,可以返回一个64位的整数来表示当前的仿真时刻,返回值的单位和\`timescale定义的时间单位一致。时间戳time在使用$display打印时,若指定输出格式为%t,则输出数据以`timescale定义的时间精度为单位;若指定输出格式为%d,则输出数据以`timescale定义的时间单位为单位。示例代码如下:
`timescale 1ns/1ps
module testbench_top();
initial begin
#10;
$display("%0tps", $time);
#5.481;
$display("%0tps", $time);
#10.58289;
$display("%0tps", $time);
$display("%0dns", $time);
$stop;
end
endmodule
运行结果如下:
此处$time返回的是一个整数值,因此虽然时间精度为ps,返回的整数值也会将后面的小数进行四舍五入,因此我们得到的是整数的时间单位。
4.时间戳$realtime
时间戳realtime是一个系统函数,返回的时间数字是一个实型数;该实型数也是以时间尺度为基准的。时间戳realtime在使用$display进行打印时,若指定输出格式为%t,则输出数据以`timescale定义的时间精度为单位;若指定输出格式为%f,则输出数据以`timescale定义的时间单位为单位。示例代码如下:
`timescale 1ns/1ps
module testbench_top();
initial begin
#10;
$display("%0fns", $realtime);
#5.481;
$display("%0fns", $realtime);
#10.58289;
$display("%0fns", $realtime);
$display("%0tps", $realtime);
$stop;
end
endmodule
运行结果如下:
5.持续赋值assign和deassign
使用assign语句可以对变量进行持续的赋值,deassign则恰恰相反,可以结束assign语句对某个变量的持续赋值。示例代码如下:
`timescale 1ns/1ps
module testbench_top();
`define CLK_PERIORD 10
reg clk;
initial begin
clk <= 0;
end
always #(`CLK_PERIOD/2) clk = ~clk;
reg a;
initial begin
a=0;
#100;
assign a=clk;
#100;
deassign a;
#100;
$stop;
end
endmodule
6.持续赋值force和release
force语句和assign语句有类似的功能,可以实现持续地赋值。但是force语句除了可以对变量进行持续赋值,甚至还可以对一个已经使用assign赋值了的变量进行强制赋值,而release语句则可以终止force语句的赋值。示例代码如下:
`timescale 1ns/1ps
module testbench_top();
`define CLK_PERIOD 10
reg clk;
initial begin
clk <= 0;
end
always #(`CLK_PERIOD/2) clk = ~clk;
reg a;
initial begin
assign a = clk;
#100;
force a = 0;
#100;
release a;
#100;
$stop;
end
endmodule
7.循环语句forever、repeat、while和for
行为级语法支持forever、repeat、while和for这4类循环语句,可以实现对一段代码的不执行、一次执行或多次执行。
(1)forever
forever意为永远,表示连续执行forever语句后的逻辑功能;当最后一行代码执行完成后,再从第一行代码开始重新执行,如此反复,直到仿真结束。如果用begin...end规定执行范围,执行范围从begin开始,以end结束。forever语句必须写到initial语句中。其语法结构如下:
(2)repeat
repeat意为重复,表示连续执行repeat后的逻辑功能,其后括号中的数值是执行的次数,当最后一行代码执行完成后,再从第一行代码开始执行,直到完成执行的次数后停止。如果用begin...end规定执行范围,执行范围从begin开始,以end结束。forever语句必须写到initial语句中。其语法结构如下:
示例代码如下:
module testbench_top();
integer cnt;
reg en;
initial begin
en=0;
cnt=0;
#100;
repeat(8) begin
en=1;
cnt=cnt+1;
#10;
end
en=0;
cnt=0;
#100;
$stop;
end
endmodule
(3)while
while表示"当...时",当while后括号中的表达式为TRUE时,连续执行while语句后的逻辑功能;当最后一行代码执行完成后,再从第一行代码开始执行,直到表达式为FALSE时停止。如果用begin...end规定执行范围,执行范围从begin开始,以end结束。其语法结构如下:
示例代码如下:
module testbench_top();
`define CLK_PERIOD 10
reg clk;
initial begin
clk <=0;
end
always #(`CLK_PERIOD/2) clk = ~clk;
reg [7:0] cnt=0;
always @(posedge clk)
cnt <= cnt + 1;
initial begin
$display("Simulation Start.");
while(cnt < 8) begin
$display("cnt=%0d", cnt);
@(posedge clk);
#1;
end
$display("Simulation End.");
$stop;
end
endmodule
运行结果如下:
(4)for语句根据其表达式中的变量初始、变化和判断条件,实现其后逻辑功能的多次执行。for后面的括号中有3个表达式;首先以for后面括号中的第一个表达式为初始条件;当for后面括号中第2个表达式为TRUE时,连续执行for语句后的逻辑功能;当最后一行代码执行完成之后,执行一次for后面括号中的第3个表达式;再回头执行for后面的第2个表达式,若为TRUE,则继续执行其后的逻辑功能,如此反复,直到第二个表达式为FALSE时停止。其语法结构如下:
示例代码如下:
module testbench_top();
`define CLK_PERIOD 10
reg clk;
initial begin
clk <= 0;
end
always #(`CLK_PERIOD/2) clk = ~clk;
integer cnt;
initial begin
$display("Simulation Start.");
for (cnt=4; cnt<=8; cnt=cnt+1) begin
$display("cnt=%0d", cnt);
@(posedge clk);
end
end
endmodule
运行结果如下:
8.时间控制
在行为级语法的过程赋值中的事件控制主要有两大类,一类使用#实现精确的时间延时控制,另一类使用事件表达式(如@和wait语句)实现事件触发的时间控制。
(1)时间延时#
时间延时#可后接数值、常量表达式(如定义的参数)或变量,表示延时若干个时间单位,时间单位和精度由`timescale语法申明。若#后的延时时间设置值中某些位出现x或z,则延时时间将被强制设定为0;若#后的延时时间设置值为负数,则延时时间将被强制设定为2s。示例代码如下:
`timescale 1ns/1ps
module testbench_top();
parameter DELAY_PARA=50;
real delay_vari;
real t1,t2,t3,t4;
initial begin
delay_vari=25.338;
t1=$realtime;
#100;
t2=$realtime;
$display("#100 = %0fns", (t2-t1));
#delay_vari;
t3=$realtime;
$display("#delay_vari=%0fns", (t3-t2));
#DELAY_PARA;
t4=$realtime;
$display("#DELAY_PARA=%0fns", (t4-t3));
$finish;
end
endmodule
运行结构如下:
(2)事件触发@
事件触发@可以基于某个信号或变量的取值变化,也可以基于某个数据位的方向变化,如使用posedge或negedge分别表示取值从0变化为1或从1变化为0。示例代码如下:
`timescale 1ns/1ps
module testbench_top();
reg en;
integer dly;
initial begin
en=0;
#10;
en=1;
#10;
en=0;
end
initial begin
dly=0;
#20;
dly=1;
#20;
dly=3;
#20;
dly=4;
end
initial begin
@(posedge en);
$display("@(negedhe en) at %0fns", $realtime);
@(negedge en);
$display("@(negedge en) at %0fns", $realtime);
@(dly == 3);
$display("@(dly == 3) at %0fns", $realtime);
end
endmodule
运行结果如下:
(3)事件等待wait
事件等待wait为电平敏感的事件控制语法。wait后的括号内的表达式若为false,则一致等待,直到表达式为true,才执行后续逻辑代码。示例代码如下:
`timescale 1ns/1ps
module testbench_top();
reg [7:0] cnt=0;
always #10 cnt=cnt+1;
initial begin
wait(cnt==10) begin
$display("cnt==10 at %0dns", $time);
end
$stop;
end
endmodule
9.顺序块begin end和并行块
行为级仿真中,begin end之间的多个使用阻塞赋值=的赋值语句,是按照顺序依次执行的;fork join之间的多个赋值语句则是并行执行的。具体的示例代码如下:
`timescale 1ns/1ps
module testbench_top();
reg a1=0, b1=0, c1=0;
reg a2=0, b2=0, c2=0;
initial begin
$monitor("a1=%b, b1=%b, c1=%b, a2=%b, b2=%b, c2=%b at %0dns", a1, b1, c1, a2, b2, c2, $time);
end
initial begin
#10 a1=1;
#10 b1=1;
#10 c1=1;
end
#10;
$stop;
initial begin
fork
#5 a2=1;
#5 b2=1;
#5 c2=1;
join
end
endmodule
运行结果如下:
10.数据类型转换函数
以下4个系统函数支持real和int、real和bit数据类型之间的互相转换。real_data为任意real类型的数据,调用rtoi(real_data)函数将返回real_data转换为int类型后的数据。int_data为任意int类型的数据,调用itor(int_data)函数将返回int_data转换为real类型后的数据。real_data为任意real类型的数据,调用realtobits(real_data)函数将返回real_data转换为64位二进制寄存器类型后的数据。bits_data为任意64位二进制寄存器类型的数据,调用bitstoreal(bits_data)函数将返回bits_data转换为real类型后的数据。
示例代码如下:
`timescale 1ns/1ps
module testbench_top();
integer int_a=108;
real real_a=59.23;
integer int_b;
real real_b;
initial begin
int_b = $rtoi(real_a);
real_b = $itor(int_a);
$display("int_b=$rtoi(real_a) = $rtoi(%0f) = %0d", real_a, int_b);
$display("real_b=$itor(int_a) = $itor(%0d) = %0f", int_a, real_b);
$stop;
end
endmodule
11.随机数生成$random
每次调用系统函数random(seed)都可以产生一个新的32位随机数。如果不设置seed,则每次取得的随机数都是相同的。也就是说,random所产生的随机数,其实对于系统而言,不过是提前预设好的32位数据数组而已。使用$random产生的随机数,在seed一致的情况下,都是调用了系统中预设的同一套数据数组,因此它们的值总是一致的。示例代码如下:
reg [23:0] rand;
rand = $random % 100; // 产生一个-99~99范围内的随机数
reg [23:0] rand;
rand = {$random} % 100; // 通过位拼接操作产生0~99范围的随机数,添加位拼接会认为是random[23]...random[0]拼接而成
reg [23:0] rand;
rand = min + {$random} % (max - min + 1); //产生一个在min,max之间的随机数
12.显示任务display和write
系统显示任务display和write在仿真测试中是最为常用的信息显示方式。display和write任务最主要的区别在于,display在一次输出后自动换行,而write则不会,其余用法基本类似。其语法结构如下:
任务名可以是display、displayb、displayo、displayh、write、writeb、writeo或writeh。格式由%和格式字符组成,信号为要显示的信号名,信号数量和格式数量必须对应。若不指定显示信号的格式,即使用display或write,则信号现实的格式会默认为十进制,其余类似。下面给出可用输出格式列表以及一些常用的特殊字符如下:
示例代码如下:
`timescale 1ns/1ns
module testbench_top();
reg [31:0] rval;
initial begin
rval=101;
$display("rval = %h hex %d decimal", rval, rval);
$display("rval = %0h hex %0d decimal", rval, rval);
$display("rval = %o octal \n %b bin", rval, rval);
$display("rval has %c ascii character valuel", rval);
$display("current scope is %m");
$display("%s is ascii value for 101", 101);
#101;
$display("Simulation time is %t", $time);
$stop;
end
endmodule
运行结果如下:
注意,除了十进制显示之外,高位的0会默认以空格填充,其他进制显示时会将高位的0显示出来。在%和格式字符之间可以添加数字0,就可以隐藏前置的0或空格,是的第一个非0数字顶格显示。
13.监视任务$monitor
系统监视任务monitor在仿真测试脚本中可以实现对任何变量或表达式取值的监视和显示。monitor语法结构以及用法都与display类似。当monitor任务中包含一个或多个监控信号并运行时,若参数列表中有任何的变量或表达式的值发生变化,则所有参数列表中的信号值都将输出并显示。同一时刻,若两个或多个参数的值同时发生变化,则此时将会合并一次输出并显示。monitor任务在申明后默认开启,在其运行器件,若调用系统任务monitoroff,则关闭monitor,直到调用系统任务monitoron后将重新开启$monitor。其语法结构如下:
示例代码如下:
initial begin
@(posedge rst_n);
$display("o_cnt is %d at %0dns", o_cnt, $time);
end
always @(posedge clk) begin
if (o_cnt == 4'd5) $monitoroff;
else if(o_cnt == 4'd12) $monitoron;
end
14.仿真终止finish和stop
系统任务$finish被调用时将会退出仿真工具并返回操作系统。其语法结构如下:
系统任务stop被调用时,仿真工具被挂起,仿真运行停止。语法结构与num取值与finish一样。
End!