知识屋:更实用的电脑技术知识网站
所在位置:首页 > 科技

在学习verilog HDL之前有哪些基础课程需要学习呢?如何自学verilog?

发表时间:2022-03-25来源:网络

2020-11-23

之前在一个博主那里看到有很详细的写了如何从0到1的来学习Verilog HDL觉得很适合初学者,我收藏整理了下,希望可以帮到你

呼~~~终于完了......有需要PDF的可以无尝分享→lemonn093

还有一些适合零基础小白练习的代码

Verilog HDL简介

Verilog HDL的语法与C语言的语法有许多类似的地方,但也有许多不同的地方。我们学习Verilog HDL语法要善于找到不同点,着重理解如:

*阻塞〔Blocking〕和非阻塞〔Non-Blocking〕赋值的不同;

*顺序块和并行块的不同;

*块与块之间的并行执行的概念;

*task和function的概念。

Verilog HDL还有许多系统函数和任务也是C语言中没有的如:$monitor、$readmemb、$stop等等,而这些系统任务在调试模块的设计中是非常有用的,我们只有通过阅读大量的Verilog调试模块实例,经过长期的实践,经常查阅的Verilog语言参考手册才能逐步掌握。

Verilog HDL是一种用于数字逻辑电路设计的语言。用Verilog HDL描述的电路设计就是该电路的Verilog HDL模型。

Verilog HDL既是一种行为描述的语言也是一种结构描述的语言。这也就是说,既可以用电路的功能描述也可以用元器件和它们之间的连接来建立所设计电路的Verilog HDL模型。

Verilog模型可以是实际电路的不同级别的抽象。这些抽象的级别和它们对应的模型类型共有以下五种:

*系统级(system):用高级语言结构实现设计模块的外部性能的模型。

*算法级(algorithm):用高级语言结构实现设计算法的模型。

*RTL级(Register Transfer Level):描述数据在寄存器之间流动和如何处理这些数据的模型。

*门级(gate-level):描述逻辑门以及逻辑门之间的连接的模型。

*开关级(switch-level):描述器件中三极管和储存节点以及它们之间连接的模型。

一个复杂电路系统的完整Verilog HDL模型是由若干个Verilog HDL模块构成的,每一个模块又可以由若干个子模块构成。其中有些模块需要综合成具体电路,而有些模块只是与用户所设计的模块交互的现存电路或激励信号源。

利用Verilog HDL语言结构所提供的这种功能就可以构造一个模块间的清晰层次结构来描述极其复杂的大型设计,并对所作设计的逻辑电路进行严格的验证。

Verilog HDL行为描述语言作为一种结构化和过程性的语言,其语法结构非常适合于算法级和RTL级的模型设计。这种行为描述语言具有以下功能:

*可描述顺序执行或并行执行的程序结构。

*用延迟表达式或事件表达式来明确地控制过程的启动时间。

*通过命名的事件来触发其它过程里的激活行为或停止行为。

*提供了条件、if-else、case、循环程序结构。

*提供了可带参数且非零延续时间的任务(task)程序结构。

*提供了可定义新的操作符的函数结构(function)。

*提供了用于建立表达式的算术运算符、逻辑运算符、位运算符。

Verilog HDL语言作为一种结构化的语言也非常适合于门级和开关级的模型设计。因其结构化的特点又使它具有以下功能:

*提供了完整的一套组合型原语(primitive);

*提供了双向通路和电阻器件的原语;

*可建立MOS器件的电荷分享和电荷衰减动态模型。

Verilog HDL的构造性语句可以精确地建立信号的模型。这是因为在Verilog HDL中,提供了延迟和输出强度的原语来建立精确程度很高的信号模型。

信号值可以有不同的的强度,可以通过设定宽范围的模糊值来降低不确定条件的影响。

Verilog HDL作为一种高级的硬件描述编程语言,有着类似C语言的风格。其中有许多语句如:if语句、case语句等和C语言中的对应语句十分相似。如果读者已经掌握C语言编程的基础,那么学习Verilog HDL并不困难,我们只要对Verilog HDL某些语句的特殊方面着重理解,并加强上机练习就能很好地掌握它,利用它的强大功能来设计复杂的数字逻辑电路。

Verilog语法之一:简单的Verilog HDL模块

1.简单的Verilog HDL程序介绍

下面先介绍几个简单的Verilog HDL程序,然后从中分析Verilog HDL程序的特性。

例[1]:module...endmodule

module adder ( count,sum,a,b,cin );

input [2:0] a,b;

input cin; output count;

output [2:0] sum;

assign {count,sum} = a + b + cin;

Endmodule

这个例子通过连续赋值语句描述了一个名为adder的三位加法器可以根据两个三比特数a、b和进位(cin)计算出和(sum)和进位(count)。从例子中可以看出整个Verilog HDL程序是嵌套在module和 endmodule 声明语句里的。

例[2]:注释

module compare ( equal,a,b );

output equal; //声明输出信号equal

input [1:0] a,b; //声明输入信号a,b

assign equal=(a==b)?1:0;

/*如果a、b 两个输入信号相等,输出为1。否则为0*/

Endmodule

这个程序通过连续赋值语句描述了一个名为compare的比较器。对两比特数 a、b 进行比较,如a与b相等,则输出equal为高电平,否则为低电平。在这个程序中,/*........*/和//.........表示注释部分,注释只是为了方便程序员理解程序,对编译是不起作用的。

例[3]:基本元件

module trist2(out,in,enable);

output out;

input in, enable;

bufif1 mybuf(out,in,enable);

Endmodule

这个程序描述了一个名为trist2的三态驱动器。程序通过调用一个在Verilog语言库中现存的三态驱动器实例元件bufif1来实现其功能。

例[4]:模块调用(例化)

module trist1(out,in,enable);

output out;

input in, enable;

mytri tri_inst(out,in,enable);

//调用由mytri模块定义的实例元件tri_inst

endmodule

module mytri(out,in,enable);

output out;

input in, enable;

assign out = enable? in : 'bz;

endmodule

这个程序例子通过另一种方法描述了一个三态门。在这个例子中存在着两个模块。模块trist1调用由模块mytri定义的实例元件tri_inst。模块trist1是顶层模块。模块mytri则被称为子模块。

通过上面的例子可以看到:

*Verilog HDL程序是由模块构成的。每个模块的内容都是嵌在module和endmodule两个语句之间。每个模块实现特定的功能。模块是可以进行层次嵌套的。正因为如此,才可以将大型的数字电路设计分割成不同的小模块来实现特定的功能,最后通过顶层模块调用子模块来实现整体功能。

*每个模块要进行端口定义,并说明输入输出口,然后对模块的功能进行行为逻辑描述。

*Verilog HDL程序的书写格式自由,一行可以写几个语句,一个语句也可以分写多行。

*除了endmodule语句外,每个语句和数据定义的最后必须有分号。

*可以用/*.....*/和//.......对Verilog HDL程序的任何部分作注释。一个好的,有使用价值的源程序都应当加上必要的注释,以增强程序的可读性和可维护性。

2.模块的结构

Verilog的基本设计单元是“模块”(block)。一个模块是由两部分组成的,一部分描述接口,另一部分描述逻辑功能,即定义输入是如何影响输出的。下面举例说明:

module block (a,b,c,d);

input a,b;

output c,d;

assign c= a | b ;

assign d= a & b;

endmodule

1.wire类型

wire型数据常用来表示用于以assign关键字指定的组合逻辑信号。Verilog程序模块中输入输出信号类型缺省时自动定义为wire型。wire型信号可以用作任何方程式的输入,也可以用作“assign”语句或实例元件的输出。

wire型信号的格式同reg型信号的很类似。其格式如下:

wire [n-1:0] 数据名1,数据名2,…数据名i; //共有i条总线,每条总线内有n条线路

wire [n:1] 数据名1,数据名2,…数据名i;

*wire是wire型数据的确认符,[n-1:0]和[n:1]代表该数据的位宽,即该数据有几位。

*最后跟着的是数据的名字。

*如果一次定义多个数据,数据名之间用逗号隔开。声明语句的最后要用分号表示语句结束。

看下面的几个例子。

wire a; //定义了一个一位的wire型数据

wire [7:0] b; //定义了一个八位的wire型数据

wire [4:1] c, d; //定义了二个四位的wire型数据

2.reg类型

寄存器是数据储存单元的抽象。寄存器数据类型的关键字是reg。通过赋值语句可以改变寄存器储存的值,其作用与改变触发器储存的值相当。

Verilog HDL语言提供了功能强大的结构语句使设计者能有效地控制是否执行这些赋值语句。这些控制结构用来描述硬件触发条件,例如时钟的上升沿和多路器的选通信号。reg类型数据的缺省初始值为不定值,x。

reg型数据常用来表示用于“always”模块内的指定信号,常代表触发器。通常,在设计中要由“always”块通过使用行为描述语句来表达逻辑关系。在“always”块内被赋值的每一个信号都必须定义成reg型。

reg型数据的格式如下:

reg [n-1:0] 数据名1,数据名2,… 数据名i;

reg [n:1] 数据名1,数据名2,… 数据名i;

*reg是reg型数据的确认标识符,[n-1:0]和[n:1]代表该数据的位宽,即该数据有几位(bit)。

*最后跟着的是数据的名字。

*如果一次定义多个数据,数据名之间用逗号隔开。声明语句的最后要用分号表示语句结束。

看下面的几个例子:

reg rega; //定义了一个一位的名为rega的reg型数据reg [3:0]

regb; //定义了一个四位的名为regb的reg型数据reg [4:1]

regc, regd; //定义了两个四位的名为regc和regd的reg型数据

对于reg型数据,其赋值语句的作用就象改变一组触发器的存储单元的值。

在Verilog中有许多构造(construct)用来控制何时或是否执行这些赋值语句。这些控制构造可用来描述硬件触发器的各种具体情况,如触发条件用时钟的上升沿等,或用来描述具体判断逻辑的细节,如各种多路选择器。

reg型数据的缺省初始值是不定值x。reg型数据可以赋正值,也可以赋负值。但当一个reg型数据是一个表达式中的操作数时,它的值被当作是无符号值,即正值。

例如:当一个四位的寄存器用作表达式中的操作数时,如果开始寄存器被赋以值-1,则在表达式中进行运算时,其值被认为是+15。

注意:

reg型只表示被定义的信号将用在“always”块内,理解这一点很重要。并不是说reg型信号一定是寄存器或触发器的输出。虽然reg型信号常常是寄存器或触发器的输出,但并不一定总是这样。

初学者往往会对wire和reg的用法混淆,下面是对wire和reg用法的总结:

wire用法总结

*wire可以在Verilog中表示任意宽度的单线/总线

*wire可以用于模块的输入和输出端口以及一些其他元素并在实际模块声明中

*wire不能存储值(无状态),并且不能在always @块内赋值(=或

构建一个模块module时,

*input必须是wire

*output可以是wire也可以是reg

*inout必须是wire

例化模块时,

*外部连接input端口的可以是wire也可以是reg

*外部连接output端口的必须是wire

*外部连接inout端口的必须是wire

3.memory类型

Verilog HDL通过对reg型变量建立数组来对存储器建模,可以描述RAM型存储器,ROM存储器和reg文件。数组中的每一个单元通过一个数组索引进行寻址。在Verilog语言中没有多维数组存在。memory型数据是通过扩展reg型数据的地址范围来生成的。其格式如下:

reg [n-1:0] 存储器名[m-1:0];

在这里,reg[n-1:0]定义了存储器中每一个存储单元的大小,即该存储单元是一个n位的寄存器。存储器名后的[m-1:0]或[m:1]则定义了该存储器中有多少个这样的寄存器。最后用分号结束定义语句。下面举例说明:

reg [7:0] mema[255:0];

这个例子定义了一个名为mema的存储器,该存储器有256个8位的存储器。该存储器的地址范围是0到255。注意:对存储器进行地址索引的表达式必须是常数表达式。

另外,在同一个数据类型声明语句里,可以同时定义存储器型数据和reg型数据。见下例:

parameter wordsize=16, //定义二个参数。memsize=256;

reg [wordsize-1:0] mem[memsize-1:0],writereg, readreg;

尽管memory型数据和reg型数据的定义格式很相似,但要注意其不同之处。如一个由n个1位寄存器构成的存储器组是不同于一个n位的寄存器的。见下例:

reg [n-1:0] rega; //一个n位的寄存器

reg mema [n-1:0]; //一个由n个1位寄存器构成的存储器组

一个n位的寄存器可以在一条赋值语句里进行赋值,而一个完整的存储器则不行。

见下例:

rega =0; //合法赋值语句

mema =0; //非法赋值语句

如果想对memory中的存储单元进行读写操作,必须指定该单元在存储器中的地址。下面的写法是正确的。

mema[3]=0; //给memory中的第3个存储单元赋值为0。

进行寻址的地址索引可以是表达式,这样就可以对存储器中的不同单元进行操作。表达式的值可以取决于电路中其它的寄存器的值。例如可以用一个加法计数器来做RAM的地址索引。

Verilog语法之四:运算符

Verilog HDL语言的运算符范围很广,其运算符按其功能可分为以下几类:

*算术运算符(+,-,×,/,%)

*赋值运算符(=,,=,

举例说明:

rega='b1010;//rega的初值为'b1010

rega=~rega;//rega的值进行取反运算后变为'b0101

"按位与"运算符&

按位与运算就是将两个操作数的相应位进行与运算,

其运算规则见下表:


"按位或"运算符|

按位或运算就是将两个操作数的相应位进行或运算。

其运算规则见下表:


"按位异或"运算符^(也称之为XOR运算符)

按位异或运算就是将两个操作数的相应位进行异或运算。

其运算规则见下表:

"按位同或"运算符^~

按位同或运算就是将两个操作数的相应位先进行异或运算再进行非运算.

其运算规则见下表:


不同长度的数据进行位运算

两个长度不同的数据进行位运算时,系统会自动的将两者按右端对齐.位数少的操作数会在相应的高位用0填满,以使两个操作数按位进行操作.

3.逻辑运算符

在Verilog HDL语言中存在三种逻辑运算符:

&& 逻辑与

|| 逻辑或

! 逻辑非

"&&"和"||"是二目运算符,它要求有两个操作数,如(a>b)&&(b>c),(a


逻辑运算符中"&&"和"||"的优先级别低于关系运算符,"!" 高于算术运算符。见下例:

*(a>b)&&(x>y) 可写成: a>b && x>y

*(a==b)||(x==y) 可写成:a==b || x==y

*(!a)||(a>b) 可写成: !a || a>b

为了提高程序的可读性,明确表达各运算符间的优先关系,建议使用括号.

4. 关系运算符

关系运算符共有以下四种:

a < b a小于b

a > b a大于b

a = b a大于或等于b

在进行关系运算时,如果声明的关系是假的(flase),则返回值是0,如果声明的关系是真的(true),则返回值是1,如果某个操作数的值不定,则关系是模糊的,返回值是不定值。

所有的关系运算符有着相同的优先级别。关系运算符的优先级别低于算术运算符的优先级别。见下例:

a < size-1 //这种表达方式等同于下面

a < (size-1) //这种表达方式。

size - ( 1 < a ) //这种表达方式不等同于下面

size - 1 < a //这种表达方式。

从上面的例子可以看出这两种不同运算符的优先级别。当表达式size-(1

下面举一个例子说明“==”和“===”的区别。

例:

if(A==1’bx) $display(“AisX”); (当A等于X时,这个语句不执行)

if(A===1’bx) $display(“AisX”); (当A等于X时,这个语句执行)

6.移位运算符

在Verilog HDL中有两种移位运算符:

>(右移位运算符)。

其使用方法如下:

a >> n;//a右移n位
a

关键词无需特意无记忆,下面是Verilog HDL中使用的关键词:

always, and, assign,begin,buf,bufif0,bufif1,case,casex,casez,cmos,deassign, default,defparam,disable,edge,else,end,endcase,endmodule,endfunction,endprimitive, endspecify, endtable, endtask, event, for, force, forever, fork, function,highz0, highz1, if,initial, inout, input,integer,join,large,macromodule,medium,module, nand,negedge,nmos,nor,not,notif0,notifl, or, output, parameter, pmos, posedge, primitive, pull0, pull1, pullup, pulldown, rcmos, reg, releses, repeat, mmos, rpmos, rtran, rtranif0,rtranif1,scalared,small,specify,specparam,strength,strong0, strong1, supply0, supply1, table, task, time, tran, tranif0, tranif1, tri, tri0, tri1, triand, trior, trireg,vectored,wait,wand,weak0,weak1,while, wire,wor, xnor, xor

注意在编写Verilog HDL程序时,变量的定义不要与这些关键词冲突。

Verilog语法之六:阻塞赋值与非阻塞赋值

1.初步理解阻塞赋值与非阻塞赋值

在Verilog HDL语言中,信号有两种赋值方式:

(1).非阻塞(Non_Blocking)赋值方式( 如 b

2.深入理解阻塞和非阻塞赋值

1.在描述组合逻辑的always 块中用阻塞赋值,则综合成组合逻辑的电路结构。

2.在描述时序逻辑的always 块中用非阻塞赋值,则综合成时序逻辑的电路结构。

为什么一定要这样做呢?这是因为要使综合前仿真和综合后仿真一致的缘故。

首先了解两个定义:

*RHS – 方程式右手方向的表达式或变量可分别缩写为:RHS 表达式或 RHS 变量。

*LHS – 方程式左手方向的表达式或变量可分别缩写为:LHS 表达式或 LHS 变量。

阻塞赋值的执行可以认为是只有一个步骤的操作:

*计算RHS 并更新LHS,此时不能允许有来自任何其他Verilog 语句的干扰。

所谓阻塞的概念是指在同一个always 块中,其后面的赋值语句从概念上(即使不设定延迟)是在前一句赋值语句结束后再开始赋值的。

[例1]用阻塞赋值的反馈振荡器(不好的例子)

module fbosc1 (y1, y2, clk, rst);
output y1, y2;
input clk, rst;
reg y1, y2;

always @(posedge clk or posedge rst)
begin
if (rst) y1 = 0; // reset
else y1 = y2;
end


always @(posedge clk or posedge rst)
begin
if (rst) y2 = 1; // preset
else y2 = y1;
end

endmodule

例1中,如果前一个always块的复位信号先到0 时刻,则y1 和y2 都会取1,而如果后一个always 块的复位信号先到0 时刻,则y1 和y2 都会取0。这清楚地说明这个Verilog 模块是不稳定的会产生冒险和竞争的情况。

如果在一个过程块中阻塞赋值的RHS 变量正好是另一个过程块中阻塞赋值的LHS 变量,这两个过程块又用同一个时钟沿触发,如果阻塞赋值的次序安排不好,就会出现竞争。若这两个阻塞赋值操作用同一个时钟沿触发,则执行的次序是无法确定的。

非阻塞赋值的操作可以看作为两个步骤的过程:

*在赋值时刻开始时,计算非阻塞赋值RHS 表达式。

*在赋值时刻结束时,更新非阻塞赋值LHS 表达式。

[例2] 用非阻塞赋值的反馈振荡器(正确示范)

module fbosc2 (y1, y2, clk, rst);
output y1, y2;
input clk, rst;
reg y1, y2;

always @(posedge clk or posedge rst)
begin
if (rst) y1


上面的两个例子在功能上是等价的。fork-join例子里的赋值故意打乱顺序是为了强调顺序是没有关系的。

注意fork-join块是典型的不可综合语句,并且在一些仿真器时效率较差。

Verilog语法之八:条件语句

一、if_else语句

if语句是用来判定所给定的条件是否满足,根据判定的结果(真或假)决定执行给出的两种操作之一。Verilog HDL语言提供了三种形式的if语句。

(1) if(表达式) 语句

例如:

if ( a > b )

out1 b)

out1


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中,其输出格式控制是用双引号括起来的字符串,它包括两种信息:

1. 格式说明,由"%"和格式字符组成。它的作用是将输出的数据转换成指定的格式输出。格式说明总是由“%”字符开始的。对于不同类型的数据用不同的格式输出。下表中给出了常用的几种输出格式。

2. 普通字符,即需要原样输出的字符。其中一些特殊的字符可以通过下表中的转换序列来输出。下面表中的字符形式用于格式字符串参数中,用来显示特殊的字符。


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

[例1]:

module disp;initial
begin
$display("\\\t%%\n\"\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", 1'bx);

输出结果为:x


$display("%h", 14'bx0_1010);

输出结果为:xxXa


$display("%h %o",12'b001x_xx10_1x01,12'b001_xxx_101_x01);

输出结果为:XXX 1x5X

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

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/1nsmodule 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/1nsmodule 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/1nsmodule 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

上图表示“文件包含”的含意。

图(a)为文件File1.v,它有一个`include "File2.v"命令,然后还有其它的内容(以A表示)。

图(b)为另一个文件File2.v,文件的内容以B表示。

在编译预处理时,要对`include命令进行“文件包含”预处理:将File2.v的全部内容复制插入到 `include "File2.v"命令出现的地方,即File2.v 被包含到File1.v中,得到图(c)所示的结果。

在接着往下进行的编译中,将“包含”以后的File1.v作为一个源文件单位进行编译。

“文件包含”命令是很有用的,它可以节省程序设计人员的重复劳动。可以将一些常用的宏定义命令或任务(task)组成一个文件,然后用`include命令将这些宏定义包含到自己所写的源文件中,相当于工业上的标准元件拿来使用。另外在编写Verilog HDL源文件时,一个源文件可能经常要用到另外几个源文件中的模块,遇到这种情况即可用`include命令将所需模块的源文件包含进来。

[例1]:

(1)文件aaa.v

module aaa(a,b,out);

input a, b;

output out;

wire out;

assign out = a^b;

endmodule

(2)文件 bbb.v

`include "aaa.v"

module bbb(c,d,e,out);

input c,d,e;

output out;

wire out_a;

wire out;
aaa aaa(.a(c),.b(d),.out(out_a));

assign out=e&out_a;

endmodule

在上面的例子中,文件bbb.v用到了文件aaa.v中的模块aaa的实例器件,通过“文件包含”处理来调用。模块aaa实际上是作为模块bbb的子模块来被调用的。在经过编译预处理后,文件bbb.v实际相当于下面的程序文件bbb.v:

module aaa(a,b,out);
input a, b;
output out;
wire out;
assign out = a ^ b;

endmodule


module bbb( c, d, e, out);
input c, d, e;
output out;
wire out_a;
wire out;
aaa aaa(.a(c),.b(d),.out(out_a));
assign out= e & out_a;

endmodule

关于“文件包含”处理的四点说明:

1) 一个`include命令只能指定一个被包含的文件,如果要包含n个文件,要用n个`include命令。

2) `include命令可以出现在Verilog HDL源程序的任何地方,被包含文件名可以是相对路径名,也可以是绝对路径名。例如:'include"parts/count.v"

3) 可以将多个`include命令写在一行,在`include命令行,只可以出空格和注释行。例如下面的写法是合法的。

'include "fileB" 'include "fileC" //including fileB and fileC

4) 如果文件1包含文件2,而文件2要用到文件3的内容,则可以在文件1用两个`include命令分别包含文件2和文件3,而且文件3应出现在文件2之前。例如在下面的例子中,即在file1.v中定义:

`include"file3.v"

`include"file2.v"

module test(a,b,out);

input[1:`size2] a, b;

output[1:`size2] out;

wire[1:`size2] out;

assign out= a+b;

endmodule

file2.v的内容为:

`define size2 `size1+1...

file3.v的内容为:

`define size1 4...

这样,file1.v和file2.v都可以用到file3.v的内容。在file2.v中不必再用 `include "file3.v"了。

5) 在一个被包含文件中又可以包含另一个被包含文件,即文件包含是可以嵌套的。例如上面的问题也可以这样处理,见下图,

它的作用和下图的作用是相同的。

3.时间尺度 `timescale

`timescale命令用来说明跟在该命令后的模块的时间单位和时间精度。

使用`timescale命令可以在同一个设计里包含采用了不同的时间单位的模块。

例如,一个设计中包含了两个模块,其中一个模块的时间延迟单位为ns,另一个模块的时间延迟单位为ps。EDA工具仍然可以对这个设计进行仿真测试。

`timescale 命令的格式如下:

`timescale/

在这条命令中,时间单位参量是用来定义模块中仿真时间和延迟时间的基准单位的。时间精度参量是用来声明该模块的仿真时间的精确程度的,该参量被用来对延迟时间值进行取整操作(仿真前),因此该参量又可以被称为取整精度。

如果在同一个程序设计里,存在多个`timescale命令,则用最小的时间精度值来决定仿真的时间单位。另外时间精度至少要和时间单位一样精确,时间精度值不能大于时间单位值。

在`timescale命令中,用于说明时间单位和时间精度参量值的数字必须是整数,其有效数字为1、10、100,单位为秒(s)、毫秒(ms)、微秒(us)、纳秒(ns)、皮秒(ps)、毫皮秒(fs)。这几种单位的意义说明见下表。

下面举例说明`timescale命令的用法。

[例1]:

`timescale 1ns/1ps

在这个命令之后,模块中所有的时间值都表示是1ns的整数倍。这是因为在`timescale命令中,定义了时间单位是1ns。模块中的延迟时间可表达为带三位小数的实型数,因为 `timescale命令定义时间精度为1ps.

[例2]:

`timescale 10us/100ns

在这个例子中,`timescale命令定义后,模块中时间值均为10us的整数倍。因为`timesacle 命令定义的时间单位是10us。延迟时间的最小分辨度为十分之一微秒(100ns),即延迟时间可表达为带一位小数的实型数。

例3:

`timescale 10ns/1ns

module test;

reg set;

parameter d=1.55;

initial

begin

#d set=0;

#d set=1;

end

endmodule

在这个例子中,`timescale命令定义了模块test的时间单位为10ns、时间精度为1ns。因此在模块test中,所有的时间值应为10ns的整数倍,且以1ns为时间精度。这样经过取整操作,存在参数d中的延迟时间实际是16ns(即1.6×10ns),这意味着在仿真时刻为16ns时寄存器set被赋值0,在仿真时刻为32ns时寄存器set被赋值1。

仿真时刻值是按照以下的步骤来计算的。

1) 根据时间精度,参数d值被从1.55取整为1.6。

2) 因为时间单位是10ns,时间精度是1ns,所以延迟时间#d作为时间单位的整数倍为16ns。

3) EDA工具预定在仿真时刻为16ns的时候给寄存器set赋值0(即语句 #d set=0;执行时刻),在仿真时刻为32ns的时候给寄存器set赋值1(即语句 #d set=1;执行时刻),

注意:如果在同一个设计里,多个模块中用到的时间单位不同,需要用到以下的时间结构。

1) 用`timescale命令来声明本模块中所用到的时间单位和时间精度。

2) 用系统任务$printtimescale来输出显示一个模块的时间单位和时间精度。

3) 用系统函数$time和$realtime及%t格式声明来输出显示EDA工具记录的时间信息。

4.条件编译`ifdef、`else、`endif

一般情况下,Verilog HDL源程序中所有的行都将参加编译。但是有时希望对其中的一部分内容只有在满足条件才进行编译,也就是对一部分内容指定编译的条件,这就是“条件编译”。有时,希望当满足条件时对一组语句进行编译,而当条件不满足是则编译另一部分。

条件编译命令有以下几种形式:

1)

`ifdef 宏名 (标识符)

程序段1

`else

程序段2

`endif

它的作用是当宏名已经被定义过(用`define命令定义),则对程序段1进行编译,程序段2将被忽略;否则编译程序段2,程序段1被忽略。其中`else部分可以没有,即:

2)

`ifdef 宏名 (标识符)

程序段1

`endif

这里的 “宏名” 是一个Verilog HDL的标识符,“程序段”可以是Verilog HDL语句组,也可以是命令行。这些命令可以出现在源程序的任何地方。

注意:被忽略掉不进行编译的程序段部分也要符合Verilog HDL程序的语法规则。

通常在Verilog HDL程序中用到`ifdef、`else、`endif编译命令的情况有以下几种:

*选择一个模块的不同代表部分。

*选择不同的时序或结构信息。

*对不同的EDA工具,选择不同的激励。

收藏
  • 人气文章
  • 最新文章
  • 下载排行榜
  • 热门排行榜