FPGA Verilog开发实战指南:基于Intel Cyclone IV(基础篇)
上QQ阅读APP看书,第一时间看更新

7.2 Verilog HDL语言的基础语法

Verilog的语法有很多,我们不能面面俱到,这里只简单介绍一些常用的语法,因为有很多语法是几乎用不到的,所以我们要避免因讲太多语法而使初学者陷入语法的泥潭,进而丧失对FPGA的学习兴趣。一些高级的、不常用的语法大家不需要记,用到时我们会在具体实例中特别介绍,读者也可以查阅前面推荐的参考书,或直接看IEEE官方提供的《IEEE Standard Verilog Hardware Description Language》手册。

所有Verilog代码都以模块(module)的方式存在,一个简单的逻辑可以由一个module组成,复杂的逻辑可以包含多个module,每个module都有独立的功能,并可通过输入、输出端口被其他module调用(实例化)。通过module的方式可以将一些比较独立的、可以复用的功能进行模块化,这样可以缩短开发时间,代码阅读起来也比较直观。

Verilog语法有很多,而且分为可综合(综合后可以生成对应的硬件电路)的语法和不可综合(综合后不可以生成对应的硬件电路)的语法,可综合的语法是非常少的,大多数是不可综合的,但是可以在仿真中用于验证逻辑的正确性,十分方便,这些我们都会在后面的应用中进行详细介绍。

7.2.1 标识符

标识符用于定义常数、变量、信号、端口、子模块或参数名称。Verilog语言是区分大小写的,这一点与VHDL不同,因此书写时要格外注意。

在Verilog语言中,所有关键字(又叫作保留字)都为小写。完整的Verilog关键字在编辑器中会以高亮的形式突出显示。Verilog的内部信号名(又称标识符)使用大写和小写都可以。标识符可以是字母、数字、$(美元符号)和下划线的任意组合,只要第一个字符是字母或者下划线即可。

7.2.2 逻辑值

在二进制计数中,单比特逻辑值只有“0”和“1”两种状态,而在Verilog语言中,为了对电路进行精确地建模,又增加了两种逻辑状态,即“X”和“Z”。

当“X”用作信号状态时表示未知,用作条件判断(casex或casez)时表示不关心;“Z”表示高阻状态,也就是没有任何驱动,通常用来对三态总线进行建模。在综合工具中,或者说在实际实现的电路中,并没有什么X值,只存在0、1和Z这3种状态。在实际电路中还可能出现亚稳态,它既不是0,也不是1,而是一种不稳定的状态。

Verilog语言中的所有数据都是由以上描述的4种基本逻辑值0、1、X和Z构成的,同时,X和Z是不区分大小写的,例如0z1x和0Z1X表示的是同一个数据。

7.2.3 常量

常量是Verilog中不变的数值,Verilog中的常量有3种类型:整数型、实数型和字符串型。

用户可以使用简单的十进制表示一个整数型常量,例如:

▪ 直接写“16”来表示位宽为32bit的十进制数16。

▪ -15表示十进制的-15,用二进制补码表示至少需要5bit,即1_0001,最高一位为符号位;如果用6bit表示,则为11_0001,同样地,最高一位为符号位。

整数型常量也可以采用基数表示法表示,这种写法清晰明了,所以更推荐使用这种表示方法,例如:

▪ 8'hab表示8bit的十六进制数,换算成二进制是1010_1011。

▪ 8'd171表示8bit的十进制数,换算成二进制是1010_1011。

▪ 8'o253表示8bit的八进制数,换算成二进制是1010_1011。

▪ 8'b1010_1011表示8bit的二进制数1010_1011。

虽然上面的表示方式不同,但表示的是相同的值,数值经过运算后的结果也都相同。

基数表示法的基本格式如下:


[换算为二进制后位宽的总长度]['][数值进制符号][与数值进制符号对应的数值]

其中,[位宽的总长度]可有可无,[数值进制符号]中如果是[h]则表示十六进制,如果是[o]则表示八进制,如果是[b]则表示二进制,如果是[d]则表示十进制。当[换算为二进制后位宽的总长度]比[与数值进制符号对应的数值]的实际位数多时,则自动在[与数值进制符号对应的数值]的左边补足0,如果位数少,则自动截断[与数值进制符号对应的数值]左边超出的位数。

如果将数字写成“’haa”,那么这个十六进制数的[换算为二进制后位宽的总长度]就取决于[与数值进制符号对应的数值]的长度。

在基数表示法中,如果遇到x,则在十六进制数中表示4个x,在八进制中表示3个x。另外,数字中的下划线没有任何意义,但是可以很好地增强可读性,推荐每4bit后加一个下划线,例如8'b11011011和8'b1101_1011表示的是一样的值,但是后面的看上去更容易识别。

Verilog语言中的实数型变量可以采用十进制,也可以采用科学记数法,例如13_2.18e2表示13218。

字符串是指双引号中的字符序列,是8位ASCII码值的序列,例如“Hello World”,该字符串包含11个ASCII符号(两个单词共10个符号,单词之间的空格为一个符号,共11个ASCII符号),一个ASCII符号需要用一个字节存储,所以共需要11个字节存储。

7.2.4 变量

Verilog语言中主要的两种变量类型如下:

▪ 线网型:表示电路间的物理连接。

▪ 寄存器型:Verilog中一个抽象的数据存储单元。

线网型和寄存器类型又包含很多变量,线网型最常用的变量就是wire,而寄存器型最常用的变量是reg。wire可以看成直接的连接,在可综合的逻辑中会被映射成一根真实的物理连线;reg具有对某一个时间点状态进行保持的功能,如果在可综合的时序逻辑中表达,会被映射成一个真实的物理寄存器,而在Verilog仿真器中,寄存器类型的变量通常要占据一个仿真内存空间。

因此在设计逻辑的时候要明确定义每个信号是wire属性还是reg属性。凡是在always或initial语句中被赋值的变量(赋值号左边的变量),不论表达的是组合逻辑还是时序逻辑,都一定是reg型变量;凡是在assign语句中被赋值的变量,一定是wire型变量。

7.2.5 参数

参数是一种常量,通常出现在module内部,常用于定义状态机的状态、数据位宽和计数器计数个数等,例如:


parameter IDLE = 3'b001;
parameter CNT_1S_WIDTH = 4'd15;
Parameter CNT_MAX = 25'd24_999_999;

可以在编译时修改参数的值,因此它又常用于一些参数可调的模块中,使用户在实例化模块时,可以根据需要配置参数,例如:


counter
#(
    .CNT_MAX     (25’d24    )    //实例化时参数可修改
) 
counter_inst
(
    .sys_clk     (sys_clk   ),    //input     sys_clk
    .sys_rst_n   (sys_rst_n ),    //input     sys_rst_n
 
    .led_out     (led_out   )     //output    led_out
);

parameter是出现在模块内部的局部定义,只作用于声明的那个文件,可以被灵活改变,这是parameter的一个重要特征。

7.2.6 赋值语句

赋值语句的赋值方式有两种,分别为“<=”(非阻塞赋值)和“=”(阻塞赋值)。

1. 非阻塞型过程赋值(Nonblocking Assignment)

以赋值操作符“<=”来标识的赋值操作称为“非阻塞型过程赋值”。该赋值语句的特点如下:

▪ 在begin...end串行语句块中,一条非阻塞过程语句的执行不会阻塞下一语句的执行,也就是说在本条非阻塞型过程赋值语句对应的赋值操作执行完之前,下一条语句也可以开始执行。

▪ 仿真过程在遇到非阻塞型过程赋值语句后首先计算其右端赋值表达式的值,然后等到仿真时间结束时再将该计算结果赋值变量。也就是说,这种情况下的赋值操作是在同一仿真时刻上的其他普通操作结束后才得以执行。

2. 阻塞型过程赋值(Blocking Assignment)

以赋值操作符“=”来标识的赋值操作称为“阻塞型过程赋值”。该赋值语句的特点如下:

▪ 在begin...end串行语句块中的各条阻塞型过程赋值语句将以它们在顺序块后的排列次序依次执行。

▪ 阻塞型过程赋值语句的执行过程是:首先计算右端赋值表达式的值,然后立即将计算结果赋值给“=”左端的被赋值变量。

阻塞型过程赋值语句的这两个特点表明:仿真进程在遇到阻塞型过程赋值语句时将计算表达式的值,并立即将其结果赋给等式左边的被赋值变量;在串行语句块中,下一条语句的执行会被本条阻塞型过程赋值语句所阻塞,只有在当前这条阻塞型过程赋值语句所对应的赋值操作执行完后,下一条语句才开始执行。

后面的章节中我们会通过编写代码和仿真来详细地介绍其中的不同。

7.2.7 注释

Verilog中双反斜线“//”可以实现对一行的注释,除此之外,“/*......*/”也是一种注释,进行注释时,“/*......*/”之间的语句都将被注释掉,所以“/*......*/”不仅可以实现对一行的注释,还可以实现对多行的注释。注释对整个代码的功能没有任何影响,只是设计者为了增强代码的可读性而增加的内容。

7.2.8 关系运算符

关系运算符种类:

▪ a < b:a小于b。

▪ a > b:a大于b。

▪ a <= b:a小于或者等于b。

▪ a >= b:a大于或者等于b。

在进行关系运算时,如果声明的关系是假的(false),则返回值是0;如果声明的关系是真的(true),则返回值是1;如果某个操作数的值不定,则关系是模糊的,返回值是x。所有的关系运算符都有相同的优先级别,但关系运算符的优先级要比算数运算符的低。例如:


//表达的意义相同
a < size - 1
a < (size - 1)
//表达的意义不同
size - (1 < a)
size - 1 < a

当表达式size-(1<a)进行运算时,关系表达式先被运算,然后返回值0或1被size减去;而表达式size-1<a进行运算时,size先被减去1,然后再同a相比。

7.2.9 归约运算符、按位运算符和逻辑运算符

1. 归约运算符和按位运算符

“&”操作符有两种用途,既可以作为一元运算符(仅有一个参与运算的量),也可以作为二元运算符(有两个参与运算的量)。

当“&”作为一元运算符时表示归约与。&m是将m中所有位相与,最后的结果为1bit。例如:


&4'b1111 = 1&1&1&1 = 1'b1
&4'b1101 = 1&1&0&1 = 1'b0

当“&”作为二元运算符时表示按位与。m&n是将m的每个位与n的相应位相与,在运算时要保证m和n的位数相等,最后的结果和m(n)的位数相同。例如:


4'b1010&4'b0101 = 4'b0000
4'b1101&4'b1111 = 4'b1101

同理,“~&”“^”“~^”“|”“~|”也是如此。

2. 逻辑运算符

我们在写Verilog代码时,常常当if的条件有多个同时满足时,就使用逻辑与操作符“&&”。m&&n表示判断m和n是否都为真,最后的结果只有1bit,如果都为真则输出1'b1,如果不都为真则输出1'b0。要注意和“&”的功能加以区分。

同理,“||”、“= =”(逻辑相等)、“!=”(逻辑不等)也是如此。

7.2.10 移位运算符

移位运算符是二元运算符,左移符号为“<<”,右移符号为“>>”,将运算符左边的操作数左移或右移指定的位数,用0来补充空闲位。如果右边操作数的值为x或z,则移位结果为未知数x。在应用移位运算符时一定要注意将空闲位用0来填充,也就是说,对于一个二进制数,不管原数值是多少,只要一直移位,最终全部会变为0。例如,执行4'b1000 >> 3后的结果为4'b0001,执行4'b1000 >> 4的结果为4'b0000。

在使用移位运算符时,左移一位可以看成乘以2,右移一位可以看成除以2。所以移位运算符用在计算中,代替乘法和除法。尤其是除法,使用移位的方式可以节省资源,但前提是要拓展数据位宽,否则就会出现移位后全为0的情况。

7.2.11 条件运算符

如果在条件语句中只执行单个的赋值语句,用条件表达式会更方便。条件运算符为“? :”,它是一个三元运算符,即有三个参与运算的量。

由条件运算符组成的条件表达式的一般形式如下:


表达式1 ? 表达式2 : 表达式3

执行过程是:当表达式1为真,则表达式2作为条件表达式的值,否则以表达式3作为条件表达式的值。例如,当a = 6,b = 7时,条件表达式(a > b) ? a : b的结果为7。

注意:

1)使用条件表达式时,“?”和“:”是成对出现的,不可以只使用一个。

2)条件运算符从右向左结合,例如:


a > b ? a : c > d ? c : d;
//等价于
a > b ? a : (c > d ? c : d);

虽然后面要讲到的if-else也可以实现这种功能,但是if-else只能在always块中使用,不能在assign中使用,如果想在assign中使用,就需要用到条件运算符。

7.2.12 优先级

运算符总的优先级关系为:归约运算符>算术运算符>移位运算符>关系运算符>“= =”和“!=”>按位运算符>“&&”和“||”>条件运算符,简单地说,就是一元运算符>二元运算符>三元运算符。

如果在编写代码时对这些关系容易混淆,那么最好的方式就是使用“()”增加优先级。

7.2.13 位拼接运算符

位拼接运算符由一对花括号加逗号组成,即“{ , }”,拼接的不同数据之间用“,”隔开。位拼接运算符的作用主要有两种:一种是将位宽较短的数据拼接成一个位宽长的数据;另一种是通过位拼接实现移位的效果。

1. 实现增长位宽的作用

如果需要将8bit的a、3bit的b、5bit的c按顺序拼接成一个16bit的d,则表示方法如下:


wire [15:0] d;
d = {a, b, c};

2. 实现移位的作用

din是1bit的串行数据,假如刚开始传来的数据是1,后面的数据都是0,则第一个时钟时4bit dout的值为4'b1000,第二个时钟时dout的高三位被放到最后,新来的0放到dout的最高位,变为4'b0100,从而实现了数据的右移:


always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        dout <= 4'b0;
    else
        dout <= {din, dout[3:1]};    //右移

实现左移采用的也是类似的原理,din是1bit的串行数据,假如刚开始传来的数据是1,后面的数据都是0,则第一个时钟时4bit dout的值为4'b0001,第二个时钟时dout的低三位放到最前面,新来的0放到dout的最低位,变为4'b0010,从而实现了数据的左移:


always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        dout <= 4'b0;
    else
        dout <= {dout[2:0], din};    //左移

7.2.14 if-else与case

Verilog HDL语言中存在两种分支语言:

▪ if-else条件分支语句

▪ case分支控制语句

很多初学者会问编写代码的时候,到底是用if语句好还是用case语句好。同样的逻辑,可能我们用if-else语句可以实现,用case语句也可以实现。但是在很多场合,我们会发现case语句和if-else语句总是同时出现,互相嵌套,密切配合。

1. if-else条件分支语句

if-else条件分支语句的作用是根据指定的判断条件是否满足来确定下一步要执行的操作。使用时可以采用如下三种形式。

形式1


if(<条件表达式>)
     语句或语句块;

在if-else条件语句的这种使用形式中没有出现else项,这种情况下条件分支语句的执行过程是:如果指定的<条件表达式>成立(也就是这个条件表达式的逻辑值为“1”),则执行条件分支语句内给出的语句或语句块,然后退出条件分支语句的执行;如果<条件表达式>不成立(也就是条件表达式的逻辑值为“0”“x”“z”),则不执行条件分支语句内给出的语句或语句块,而是直接退出条件语句的执行。这种写法在always块中表达组合逻辑时会产生latch,所以不推荐这种写法。

形式2


if(<条件表达式1>)
    语句或语句块1;
else    if(<条件表达式2>)
    语句或语句块2;
    ……
else
    语句或语句块3;

在执行这种形式的if-else条件分支语句时,将按照各分支项的排列顺序对各个条件表达式是否成立做出判断,当某一项的条件表达式成立时,就执行这一项所指定的语句或语句块;如果所有条件表达式都不成立,则执行最后的else项。这种形式的if-else条件分支语句实现了一种多路分支选择控制。这种写法是我们在使用根据波形写代码的方法中最常用的一种写法。

形式3


if(<条件表达式1>)           //外层if语句
    if(<条件表达式2>)       //内层if语句1
        语句或语句块1;
    else                   //内层else语句2
        语句或语句块2;
else                       //外层else语句1
    语句或语句块3;

Verilog HDL允许嵌套使用if-else条件分支语句,但是不要嵌套太多层,也不推荐这种写法,因为嵌套会有优先级的问题,最后导致逻辑混乱,if和else的结合混乱,代码也不清晰,如果写代码时遇到这种情况,往往是可以将其合并的,最终写成形式2。

2. case分支控制语句

case分支控制语句是另一种用来实现多路分支控制的分支语句。与使用if-else条件分支语句相比,采用case分支语句来实现多路控制将更为方便、直观。case分支语句通常用于对微处理器指令译码功能的描述以及对有限状态机的描述。case分支语句有“case”“casez”“casex”三种形式。


case(<控制表达式>)
    <分支语句1> : 语句块1;
    <分支语句2> : 语句块2;
    <分支语句3> : 语句块3;
    ……
    <分支语句n> : 语句块n;
    default    : 语句块n+1;
endcase

<控制表达式>代表着对程序流向进行控制的控制信号,各个<分支表达式>则控制表达式的某些具体状态取值,在实际使用中,这些分支项表达式通常是一些常量表达式③各个“语句”则指定了在各个分支下所要执行的操作,它们也可以由单条语句构成,处于最后的、以关键词default开头的那个分支项称为“default”分支项,它是可以省略的。

case语句的执行过程:

1)当“控制表达式”的取值等于“分支语句1”时,执行第1个分支项所包含的语句块1。

2)当“控制表达式”的取值等于“分支语句2”时,执行第2个分支项所包含的语句块2。

3)当“控制表达式”的取值等于“分支语句n”时,执行第N个分支项所包含的语句块n

4)在执行了某一分支项内的语句后,跳出case语句结构,终止case语句的执行。case语句中各个“分支语句”的取值必须是互不相同的,否则会出现矛盾现象。

7.2.15 inout双向端口

在定义端口列表时,我们知道输入用input,输出用output,其实还有一种双向端口,我们定义时使用inout,在后面的实例中会用到,例如I2C和SDRAM的数据线都采用了双向端口。定义为inout的端口表示该端口是双向口,既可以作为数据的输入端口,也可以作为数据的输出端口,在Verilog中的使用方式如代码清单7-1所示。

代码清单7-1 inout在Verilog中的用法


 1 module  test
 2 (
 3 
 4     input   wire    sel      , /*输入输出控制信号,sel为1时双向数据总线向外输出数据,
 5                                  sel为0时,双向数据总线为高阻态,可以向内输入数据*/
 6 
 7     input   wire    data_out , /*由内部模块传来要发送给双向数据总线
 8                                  向外输出的数据*/
 9     inout   wire    data_bus , //双向数据总线
10     output  wire    data_in    /*接收双向数据总线从外部输入的数据后
11                                   输出到其他内部模块*/
12 
13 );
14 
15 //data_in:接收双向数据总线从外部输入的数据
16 assign  data_in =   data_bus;
17 
18 /*data_bus:sel为1时,双向数据总线向外输出数据
19   sel为0时,双向数据总线为高阻态,可以向内输入数据*/
20 assign  data_bus    =   (sel == 1'b1) ? data_out : 1'bz;
21 
22 endmodule

7.2.16 Verilog语言中的系统任务和系统函数

Verilog语言中预先定义了一些任务和函数,用于完成一些特殊的功能,它们称为系统任务和系统函数,这些函数大多数都只能在Testbench仿真中使用,使我们更方便地进行验证。


`timescale 1ns/1ns  // `timescale表示时间尺度预编译指令,lns/lns表示时间单位/时间精度

时间单位和时间精度由值1、10和100以及单位s、ms、μs、ns、ps和fs组成。

▪ 时间单位:定义仿真过程所有与时间相关量的单位。

▪ 仿真中使用“#数字”表示延时相应时间单位的时间,例如#10表示延时10个单位的时间,即10ns。

▪ 时间精度:决定时间相关量的精度及仿真显示的最小刻度。


`timescale 1ns/10ps  精度0.01,#10.11 表示延时10110ps。

下面这种写法就是错误的,因为时间单位不能比时间精度小。


`timescale 100ps/1ns

主要函数如下,这些函数在支持Verilog语法的编辑器中都会显示为高亮关键字:


$display          //打印信息,自动换行 
$write            //打印信息
$strobe           //打印信息,自动换行,最后执行 
$monitor          //监测变量 
$stop             //暂停仿真 
$finish           //结束仿真 
$time             //时间函数 
$random           //随机函数 
$readmemb         //读二进制文件
$readmemh         //读十六进制文件

下面我们单独介绍它们的功能,并在ModelSim的Transcript界面中打印这些信息。

1. $display用于输出信息

使用格式:


$display("%b+%b=%d",a, b, c);//格式“%b+%b=%d”为格式控制,未指定时默认为十进制
%h或%H //以十六进制的形式输出 
%d或%D //以十进制的形式输出 
%o或%O //以八进制的形式输出 
%b或%B //以二进制的形式输出

示例代码如代码清单7-2所示。

代码清单7-2 $display函数


 1 //a,b,c输出列表,需要输出信息的变量
 2 //每次打印信息后自动换行
 3 `timescale 1ns/1ns 
 4 
 5 module tb_test(); 
 6 
 7 reg [3:0] a;
 8 reg [3:0] b;
 9 reg [3:0] c;
10 
11 initial begin 
12    $display("Hello"); 
13    $display("EmbedFire"); 
14    a = 4'd5; 
15    b = 4'd6; 
16    c = a + b; 
17    #100; 
18    $display("%b+%b=%d", a, b, c); 
19 end 
20 
21 endmodule

打印的信息如图7-1所示。

图7-1 $display函数打印信息图

2. $write用于输出信息。

使用格式:


$write("%b+%b=%d\n",a, b, c); //"%b+%b=%d\n" 为格式控制,未指定时默认为十进制
%h或%H //以十六进制的形式输出 
%d或%D //以十进制的形式输出 
%o或%O //以八进制的形式输出 
%b或%B //以二进制的形式输出
\n     //换行

示例代码如代码清单7-3所示。

代码清单7-3 $write函数


 1 //a,b,c为输出列表,需要输出信息的变量
 2 `timescale 1ns/1ns 
 3 module tb_test(); 
 4 
 5 reg [3:0]   a;
 6 reg [3:0]   b;
 7 reg [3:0]   c;
 8 
 9 initial begin 
10    $write("Hello ");
11     $write("EmbedFire\n");
12     a = 4'd5; 
13     b = 4'd6; 
14     c = a + b; 
15    #100; 
16    $write("%b+%b=%d\n", a, b, c);
17 end 
18 
19 endmodule

打印的信息如图7-2所示。

图7-2 $write函数打印信息图

3. $strobe用于输出信息

$strobe的使用格式:


$strobe("%b+%b=%d", a, b, c); // "%b+%b=%d"为格式控制,未指定时默认为十进制
%h或%H //以十六进制的形式输出 
%d或%D //以十进制的形式输出 
%o或%O //以八进制的形式输出 
%b或%B //以二进制的形式输出

示例代码如代码清单7-4所示。

代码清单7-4 $strobe函数


 1 //a,b,c输出列表,需要输出信息的变量 
 2 //打印信息后自动换行,触发操作完成后执行
 3 `timescale 1ns/1ns
 4 module tb_test ();
 5 
 6 reg  [3:0] a;
 7 reg  [3:0] b;
 8 reg  [3:0] c;
 9  
10 initial begin
11   $strobe("strobe:%b+%b=%d", a, b, c);
12   a = 4'd5;
13   $display("display:%b+%b=%d", a, b, c);
14   b = 4'd6;
15   c = a + b;
16 end
17 
18 endmodule

打印的信息如图7-3所示。

图7-3 $strobe函数打印信息图

4. $monitor用于持续监测变量

使用格式:


$monitor("%b+%b=%d", a, b, c); // “%b+%b=%d”为格式控制,未指定时默认为十进制
%h或%H //以十六进制的形式输出 
%d或%D //以十进制的形式输出 
%o或%O //以八进制的形式输出 
%b或%B //以二进制的形式输出

示例代码如代码清单7-5所示。

代码清单7-5 $monitor函数


 1 //a,b,c输出列表,需要输出信息的变量 
 2 //被测变量变化触发打印操作,自动换行
 3 `timescale 1ns/1ns 
 4 module tb_test (); 
 5 
 6 reg  [3:0] a;
 7 reg  [3:0] b;
 8 reg  [3:0] c;
 9 
10 initial begin 
11    a = 4'd5; 
12    #100; 
13    b = 4'd6; 
14    #100; 
15    c = a + b; 
16 end 
17 
18 initial $monitor("%b+%b=%d", a, b, c);
19 
20 endmodule

打印的信息如图7-4所示。

图7-4 $monitor函数打印信息图

5. $stop用于暂停仿真,$finish用于结束仿真

示例代码如代码清单7-6所示。

代码清单7-6 $stop和$finish函数


 1 `timescale 1ns/1ns 
 2 
 3 module tb_test(); 
 4 
 5 initial begin 
 6    $display("Hello"); 
 7    $display("EmbedFire"); 
 8    #100; 
 9    $display("Stop Simulation"); 
10    $stop;   //暂停仿真 
11    $display("Continue Simulation"); 
12    #100; 
13    $display("Finish Simulation"); 
14    $finish; //结束仿真 
15 end 
16 
17 endmodule

打印的信息如图7-5所示。

图7-5 $stop和$finish函数打印信息图

6. $time为时间函数,返回64位当前仿真时间;$random用于产生随机函数,返回随机数

示例代码如代码清单7-7所示。

代码清单7-7 $time和$random函数


 1 `timescale 1ns/1ns
 2 module tb_test ();
 3  
 4 reg [3:0]   a; 
 5 
 6 always #10 a = $random;
 7 
 8 initial $monitor("a=%d @time %d",a,$time); 
 9 
10 endmodule

打印的信息如图7-6所示。

图7-6 $time和$random函数打印信息图

7. $readmemb用于读二进制文件函数,$readmemh用于读十六进制文件函数

使用格式:


$readmemb("<数据文件名>", <存储器名>); 
$readmemh("<数据文件名>", <存储器名>);

其中,$readmemb函数的示例代码如代码清单7-8所示。

代码清单7-8 $readmemb函数


 1 `timescale 1ns/1ns
 2 module tb_test (); 
 3 
 4 integer i; 
 5 
 6 reg [7:0] a [20:0]; 
 7 
 8 initial begin 
 9     $readmemb("EmbedFire.txt", a); 
10     for(i=0; i<=20; i=i+1) begin 
11         #10; 
12         $write("%s", a[i]); 
13     end
14 end 
15 
16 endmodule

读取的txt文件如下:


01010111 // W 
01100101 // e 
01101100 // l 
01100011 // c 
01101111 // o 
01101101 // m 
01100101 // e 
00100000 //空格
01110100 // t 
01101111 // o 
00100000 //空格
01000101 // E 
01101101 // m 
01100010 // b 
01100101 // e 
01100100 // d
01000110 // F
01101001 // i
01110010 // r
01100101 // e 
00100001 // !

打印的信息如图7-7所示。

图7-7 $readmemb函数打印信息图