本文首发于微信公众号「花蚂蚁」,想要学习FPGA及Verilog的同学可以关注一下。

Verilog HDL语言中共有以下一些系统函数和任务:

$bitstoreal, $rtoi, $display, $setup, $finish, $skew, $hold,

$setuphold, $itor, $strobe, $period, $time, $printtimescale,

$timefoemat, $realtime, $width, $real tobits, $write, $recovery,

在Verilog HDL语言中每个系统函数和任务前面都用一个标识符$来加以确认。这些系统函数和任务提供了非常强大的功能。下面对一些常用的系统函数和任务逐一加以介绍。

注意:对于初学者来说任务函数和任务不是首要必须掌握的,可以以后慢慢理解。

1.$display和$write任务

格式:

$display(p1,p2,....pn);
$write(p1,p2,....pn);

这两个函数和系统任务的作用是用来输出信息,即将参数p2到pn按参数p1给定的格式输出。参数p1通常称为「格式控制」,参数p2至pn通常称为「输出表列」。

这两个任务的作用基本相同。$display自动地在输出后进行换行,$write则不是这样。如果想在一行里输出多个信息,可以使用$write。在$display和$write中,其输出格式控制是用双引号括起来的字元串,它包括两种信息:

  • 格式说明,由"%"和格式字元组成。它的作用是将输出的数据转换成指定的格式输出。格式说明总是由「%」字元开始的。对于不同类型的数据用不同的格式输出。下表中给出了常用的几种输出格式。
  • 普通字元,即需要原样输出的字元。其中一些特殊的字元可以通过下表中的转换序列来输出。下面表中的字元形式用于格式字元串参数中,用来显示特殊的字元。

在$display和$write的参数列表中,其「输出表列」是需要输出的一些数据,可以是表达式。下面举几个例子说明一下。

[例1]:

module disp;
initial
begin
$display("\ %%
"123
");
end
endmodule

输出结果为

\%
"S

从上面的这个例子中可以看到一些特殊字元的输出形式(八进位数123就是字元S)。

[例2]:

module disp;
reg[31:0] rval;
pulldown(pd);
initial
begin
rval=101;
$display("rval=%h hex %d decimal", rval, rval);
$display("rval=%o otal %b binary", rval, rval);
$display("rval has %c ascii character value",rval);
$display("pd strength value is %v",pd);
$display("current scope is %m");
$display("%s is ascii value for 101",101);
$display("simulation time is %t",$time);
end
endmodule

其输出结果为:

rval=00000065 hex 101 decimal
rval=00000000145 octal 00000000000000000000000001100101 binary
rval has e ascii character value
pd strength value is StX
current scope is disp
e is ascii value for 101
simulation time is 0

输出数据的显示宽度

在$display中,输出列表中数据的显示宽度是自动按照输出格式进行调整的。这样在显示输出数据时,在经过格式转换以后,总是用表达式的最大可能值所占的位数来显示表达式的当前值。

在用十进位数格式输出时,输出结果前面的0值用空格来代替。对于其它进位,输出结果前面的0仍然显示出来。例如对于一个值的位宽为12位的表达式,如按照十六进位数输出,则输出结果占3个字元的位置,如按照十进位数输出,则输出结果占4个字元的位置。

这是因为这个表达式的最大可能值为FFF(十六进位)、4095(十进位)。可以通过在%和表示进位的字元中间插入一个0自动调整显示输出数据宽度的方式。见下例:

$display("d=%0h a=%0h",data,addr);

这样在显示输出数据时,在经过格式转换以后,总是用最少的位数来显示表达式的当前值。下面举例说明:

[例3]:

module printval;
reg[11:0]r1;
initial
begin
r1=10;
$display("Printing with maximum size=%d=%h",r1,r1);
$display("Printing with minimum size=%0d=%0h",r1,r1);
end
endmodule

输出结果为:

printing with maximum size=10=00a:
printing with minimum size=10=a;

如果输出列表中表达式的值包含有不确定的值或高阻值,其结果输出遵循以下规则:

(1).在输出格式为十进位的情况下:

  • · 如果表达式值的所有位均为不定值,则输出结果为小写的x。
  • · 如果表达式值的所有位均为高阻值,则输出结果为小写的z。
  • · 如果表达式值的部分位为不定值,则输出结果为大写的X。
  • · 如果表达式值的部分位为高阻值,则输出结果为大写的Z。

(2).在输出格式为十六进位和八进位的情况下:

  • · 每4位二进位数为一组代表一位十六进位数,每3位二进位数为一组代表一位八进位数。
  • · 如果表达式值相对应的某进位数的所有位均为不定值,则该位进位数的输出的结果为小写的x。
  • · 如果表达式值相对应的某进位数的所有位均为高阻值,则该位进位数的输出结果为小写的z。
  • · 如果表达式值相对应的某进位数的部分位为不定值,则该位进位数输出结果为大写的X。
  • · 如果表达式值相对应的某进位数的部分位为高阻值,则该位进位数输出结果为大写的Z。

对于二进位输出格式,表达式值的每一位的输出结果为0、1、x、z。下面举例说明:

语句输出结果:

$display("%d", 1bx); 输出结果为:x
$display("%h", 14bx0_1010); 输出结果为:xxXa
$display("%h %o",12b001x_xx10_1x01,12b001_xxx_101_x01); 输出结果为:XXX 1x5X

注意:因为$write在输出时不换行,要注意它的使用。可以在$write中加入换行符
,以确保明确的输出显示格式。

2.系统任务$monitor

格式:

$monitor(p1,p2,.....pn);
$monitor;
$monitoron;
$monitoroff;

任务$monitor提供了监控和输出参数列表中的表达式或变数值的功能。其参数列表中输出控制格式字元串和输出表列的规则和$display中的一样。

当启动一个带有一个或多个参数的$monitor任务时,模拟器则建立一个处理机制,使得每当参数列表中变数或表达式的值发生变化时,整个参数列表中变数或表达式的值都将输出显示。

如果同一时刻,两个或多个参数的值发生变化,则在该时刻只输出显示一次。但在$monitor中,参数可以是$time系统函数。这样参数列表中变数或表达式的值同时发生变化的时刻可以通过标明同一时刻的多行输出来显示。如:

$monitor($time,,"rxd=%b txd=%b",rxd,txd);

在$display中也可以这样使用。注意在上面的语句中,「,,"代表一个空参数。空参数在输出时显示为空格。

$monitoron和$monitoroff任务的作用是通过打开和关闭监控标志来控制监控任务monitor的启动和停止,这样使得程序员可以很容易的控制$monitor何时发生。

其中$monitoroff任务用于关闭监控标志,停止监控任务$monitor,$monitoron则用于打开监控标志,启动监控任务$monitor。通常在通过调用$monitoron启动$monitor时,不管$monitor参数列表中的值是否发生变化,总是立刻输出显示当前时刻参数列表中的值,这用于在监控的初始时刻设定初始比较值。

在预设情况下,控制标志在模拟的起始时刻就已经打开了。在多模块调试的情况下,许多模块中都调用了$monitor,因为任何时刻只能有一个$monitor起作用,因此需配合$monitoron与$monitoroff使用,把需要监视的模块用$monitoron打开,在监视完毕后及时用$monitoroff关闭,以便把$monitor 让给其他模块使用。

$monitor与$display的不同处还在于$monitor往往在initial块中调用,只要不调用$monitoroff,$monitor便不间断地对所设定的信号进行监视。

3.时间度量系统函数$time

在Verilog HDL中有两种类型的时间系统函数:$time和$realtime。用这两个时间系统函数可以得到当前的模拟时刻。

系统函数$time

$time可以返回一个64比特的整数来表示的当前模拟时刻值。该时刻是以模块的模拟时间尺度为基准的。下面举例说明。

[例1]:

`timescale 10ns/1ns
module test;
reg set;
parameter p=1.6;
initial
begin
$monitor($time,,"set=",set);
#p set=0;
#p set=1;
end
endmodule

输出结果为:

0 set=x
2 set=0
3 set=1

在这个例子中,模块test想在时刻为16ns时设置寄存器set为0,在时刻为32ns时设置寄存器set为1。但是由$time记录的set变化时刻却和预想的不一样。这是由下面两个原因引起的:

1) $time显示时刻受时间尺度比例的影响。在上面的例子中,时间尺度是10ns,因为$time输出的时刻总是时间尺度的倍数,这样将16ns和32ns输出为1.6和3.2。

2) 因为$time总是输出整数,所以在将经过尺度比例变换的数字输出时,要先进行取整。在上面的例子中,1.6和3.2经取整后为2和3输出。注意:时间的精确度并不影响数字的取整。

$realtime系统函数

$realtime和$time的作用是一样的,只是$realtime返回的时间数字是一个实型数,该数字也是以时间尺度为基准的。下面举例说明:

[例2]:

`timescale10ns/1ns
module test;
reg set;
parameter p=1.55;
initial
begin
$monitor($realtime,,"set=",set);
#p set=0;
#p set=1;
end
endmodule

输出结果为:

0 set=x
1.6 set=0
3.2 set=1

从上面的例子可以看出,$realtime将模拟时刻经过尺度变换以后即输出,不需进行取整操作。所以$realtime返回的时刻是实型数。

4.系统任务$finish

格式:

$finish;
$finish(n);

系统任务$finish的作用是退出模拟器,返回主操作系统,也就是结束模拟过程。任务$finish可以带参数,根据参数的值输出不同的特征信息。如果不带参数,默认$finish的参数值为1。下面给出了对于不同的参数值,系统输出的特征信息:

  • 不输出任何信息
  • 输出当前模拟时刻和位置
  • 输出当前模拟时刻,位置和在模拟过程中所用memory及CPU时间的统计

5.系统任务$stop

格式:

$stop;
$stop(n);

$stop任务的作用是把EDA工具(例如模拟器)置成暂停模式,在模拟环境下给出一个互动式的命令提示符,将控制权交给用户。这个任务可以带有参数表达式。根据参数值(0,1或2)的不同,输出不同的信息。参数值越大,输出的信息越多。

6.系统任务$readmemb和$readmemh

在Verilog HDL程序中有两个系统任务$readmemb和$readmemh用来从文件中读取数据到存贮器中。这两个系统任务可以在模拟的任何时刻被执行使用,其使用格式共有以下六种:

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

2) $readmemb("<数据文件名>",<存贮器名>,<起始地址>);

3) $readmemb("<数据文件名>",<存贮器名>,<起始地址>,<结束地址>);

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

5) $readmemh("<数据文件名>",<存贮器名>,<起始地址>);

6) $readmemh("<数据文件名>",<存贮器名>,<起始地址>,<结束地址>);

在这两个系统任务中,被读取的数据文件的内容只能包含:空白位置(空格,换行,制表格(tab)和form-feeds),注释行(//形式的和/*...*/形式的都允许),二进位或十六进位的数字。

数字中不能包含位宽说明和格式说明,对于$readmemb系统任务,每个数字必须是二进位数字,对于$readmemh系统任务,每个数字必须是十六进位数字。

数字中不定值x或X,高阻值z或Z,和下划线(_)的使用方法及代表的意义与一般Verilog HDL程序中的用法及意义是一样的。另外数字必须用空白位置或注释行来分隔开。

在下面的讨论中,地址一词指对存贮器(memory)建模的数组的定址指针。当数据文件被读取时,每一个被读取的数字都被存放到地址连续的存贮器单元中去。存贮器单元的存放地址范围由系统任务声明语句中的起始地址和结束地址来说明,每个数据的存放地址在数据文件中进行说明。当地址出现在数据文件中,其格式为字元「@」后跟上十六进位数。如:

@hh...h

对于这个十六进位的地址数中,允许大写和小写的数字。在字元「@」和数字之间不允许存在空白位置。可以在数据文件里出现多个地址。当系统任务遇到一个地址说明时,系统任务将该地址后的数据存放到存贮器中相应的地址单元中去。

对于上面六种系统任务格式,需补充说明以下五点:

1) 如果系统任务声明语句中和数据文件里都没有进行地址说明,则预设的存放起始地址为该存贮器定义语句中的起始地址。数据文件里的数据被连续存放到该存贮器中,直到该存贮器单元存满为止或数据文件里的数据存完。

2) 如果系统任务中说明了存放的起始地址,没有说明存放的结束地址,则数据从起始地址开始存放,存放到该存贮器定义语句中的结束地址为止。

3) 如果在系统任务声明语句中,起始地址和结束地址都进行了说明,则数据文件里的数据按该起始地址开始存放到存贮器单元中,直到该结束地址,而不考虑该存贮器的定义语句中的起始地址和结束地址。

4) 如果地址信息在系统任务和数据文件里都进行了说明,那么数据文件里的地址必须在系统任务中地址参数声明的范围之内。否则将提示错误信息,并且装载数据到存贮器中的操作被中断。

5) 如果数据文件里的数据个数和系统任务中起始地址及结束地址暗示的数据个数不同的话,也要提示错误信息。

下面举例说明:

先定义一个有256个地址的位元组存贮器 mem:

reg[7:0] mem[1:256];

下面给出的系统任务以各自不同的方式装载数据到存贮器mem中。

initial $readmemh("mem.data",mem);
initial $readmemh("mem.data",mem,16);
initial $readmemh("mem.data",mem,128,1);

第一条语句在模拟时刻为0时,将装载数据到以地址是1的存贮器单元为起始存放单元的存贮器中去。

第二条语句将装载数据到以单元地址是16的存贮器单元为起始存放单元的存贮器中去,一直到地址是256的单元为止。

第三条语句将从地址是128的单元开始装载数据,一直到地址为1的单元。在第三种情况中,当装载完毕,系统要检查在数据文件里是否有128个数据,如果没有,系统将提示错误信息。

7.系统任务 $random

这个系统函数提供了一个产生随机数的手段。当函数被调用时返回一个32bit的随机数。它是一个带符号的整形数。

$random一般的用法是:$ramdom % b ,其中 b>0.它给出了一个范围在(-b+1):(b-1)中的随机数。下面给出一个产生随机数的例子:

reg[23:0] rand;
rand = $random % 60;

上面的例子给出了一个范围在-59到59之间的随机数,

下面的例子通过位并接操作产生一个值在0到59之间的数。

reg[23:0] rand;
rand = {$random} % 60;

利用这个系统函数可以产生随机脉冲序列或宽度随机的脉冲序列,以用于电路的测试。

下面例子中的Verilog HDL模块可以产生宽度随机的随机脉冲序列的测试信号源,在电路模块的设计模拟时非常有用。同学们可以根据测试的需要,模仿下例,灵活使用$random系统函数编制出与实际情况类似的随机脉冲序列。

[例]

`timescale 1ns/1ns
module random_pulse( dout );
output [9:0] dout;
reg dout;
integer delay1,delay2,k;

initial
begin
#10 dout=0;
for (k=0; k< 100; k=k+1)
begin
delay1 = 20 * ( {$random} % 6);
// delay1 在0到100ns间变化
delay2 = 20 * ( 1 + {$random} % 3);
// delay2 在20到60ns间变化
#delay1 dout = 1 << ({$random} %10);
//dout的0--9位中随机出现1,并出现的时间在0-100ns间变化
#delay2 dout = 0;
//脉冲的宽度在在20到60ns间变化
end
end
endmodule

本文首发于微信公众号「花蚂蚁」,想要学习FPGA及Verilog的同学可以关注一下。


推荐阅读:
相关文章