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!

相关推荐
芯门21 小时前
基于 Xilinx K7 FPGA 的全套万兆 10G GigE Vision 商业级传输方案
计算机视觉·fpga开发·万兆gige
ehiway21 小时前
FPGA在未来产业中的应用潜力与商业机会分析
fpga开发
GateWorld1 天前
FPGA内部模块详解之第1篇 FPGA内部结构总览
fpga开发·fpga内部模块
爱吃汽的小橘1 天前
驱动GPIO使用GPIO中断模式
fpga开发
普密斯科技1 天前
精准把控每一处细节——FPGA焊点高度精准检测实施方案
人工智能·深度学习·数码相机·计算机视觉·fpga开发·测量
FPGA_小田老师1 天前
Xilinx AXI UART Lite IP核:IP核深度解析
fpga开发·uart·串口通讯·axi转uart
GateWorld1 天前
FPGA内部模块详解之二 FPGA的逻辑“心脏”——可编程逻辑块(PFU/CLB)深度解析
fpga开发·fpga内部结构
Saniffer_SH1 天前
【高清视频】如何针对电动汽车进行通信可靠性测试、故障注入与功率分析?
服务器·驱动开发·测试工具·fpga开发·计算机外设·硬件架构·压力测试
博览鸿蒙1 天前
基于FPGA技术的数字存储示波器设计探讨
fpga开发
Saniffer_SH1 天前
【高清视频】企业级NVMe SSD (E3.S, U.2)和消费类M.2 SSD拆解分析
服务器·网络·数据库·驱动开发·测试工具·fpga开发·压力测试