Verilog语法之常用行为级语法

摘要:本文主要介绍了一些在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!

相关推荐
博览鸿蒙6 小时前
什么样的人适合从事FPGA开发的工作?
fpga开发
whik119417 小时前
Xilinx Vivado环境下载bit后自动触发ILA采集
fpga开发
JoneMaster1 天前
[读书日志]8051软核处理器设计实战(基于FPGA)第五篇:8051软核处理器主体框架搭建(verilog)
嵌入式硬件·fpga开发·硬件架构
楠了个难1 天前
以太网UDP协议栈实现(支持ARP、ICMP、UDP)--FPGA学习笔记26
学习·fpga开发·udp
AomanHao2 天前
【阅读笔记】基于FPGA的红外图像二阶牛顿插值算法的实现
图像处理·笔记·算法·fpga开发·插值·超分
简简单单做算法2 天前
基于FPGA的SNN脉冲神经网络之LIF神经元verilog实现,包含testbench
fpga开发·verilog·snn·lif神经元
乌恩大侠2 天前
【USRP】教程:在Macos M1(Apple芯片)上安装UHD驱动(最正确的安装方法)
macos·fpga开发·usrp
简简单单做算法2 天前
基于FPGA的SNN脉冲神经网络之IM神经元verilog实现,包含testbench
fpga开发·脉冲神经网络·snn·im神经元
32码奴2 天前
FPGA基本语法与使用
fpga开发·fpga