Verilog HDL 基础

一.Verilog 的基本概念

1. 硬件描述语言HDL

1.1 特点:

描述电路的连接、描述电路的功能、在不同抽象级上描述电路、

描述电路的时序、表达具有并行性

1.2 形式

Verilog 和 VHDL

2、自顶向下设计的基本概念

  • 模块(module)是Verilog的基本描述单位,用于描述某个设计的功能或结构及与其他模块通信的外部端口。

  • 模块内容是嵌在module和endmodule两个语句之间。每个模块实现特定的功能,模块可进行层次的嵌套,因此可以将大型的数字电路设计分割成大小不一的小模块来实现特定的功能,最后通过由顶层模块调用子模块来实现整体功能,这就是Top-Down的设计思想。

3.抽象级别

  1. 系统级

  2. 算法级

  3. RTL级 :描述数据在寄存器之间的流动和如何处理、控制这些数据流动的模型。


    以上三种都属于行为描述,只有RTL级才与逻辑电路有明确的对应关系。

  4. 门级 :描述逻辑门以及逻辑门之间的连接的模型。

  5. 开关级

二.Verilog 的基本格式

1.示例1

多路选择器

module  mux (out ,int 0,int 1,sel);parametet N=8;output  [N:1]   out;input [N:1]    in0,in1;input   sel;assign out=sel?in1:in0; //描述组合电路
​
endmodule

2.示例2

module count4(out,reset,clk);output[3:0] out;input reset,clk;  //默认为wire型,描述组合逻辑reg[3:0] out;  //数据类型定义:寄存器型(有保持功能)
//描述时序逻辑:always @(posedge clk) //时钟上升沿执行下面语句块:描述时序beginif (reset) out<=0; //同步复位else out<=out+1'b1; //计数end
endmodule

三.数据类型及常量、变量

1.常用词法

  • Verilog HDL区分大小写

  • Verilog HDL的关键字(如:always、and、input等) 都采用小写

2.常量之数字

1.整数:

  • 语法:<位宽> '<进制> <数值>

  • 形式:

    1. 二进制整数(b或B)

    1. 十进制整数(d或D)

    2. 十六进制整数(h或H)

    3. 八进制整数(o或O)

  • 表达方式有以下三种:

  1. <位宽><进制><数字>这是一种全面的描述方式。

  2. <进制><数字>在这种描述方式中,数字的位宽采用缺省位宽(这由具体的机器系统决定,但至少32位)。

  3. <数字>在这种描述方式中,采用缺省进制十进制。

在表达式中,位宽指明了数字的精确位数。例如:一个4位二进制数的数字的位宽为4,一个4位十六进制数的数字的位宽为16(因为每单个十六进制数就要用4位二进制数来表示)。见下例:

8'b10101100 //位宽为8的数的二进制表示, 'b表示二进制
8'ha2 //位宽为8的数的十六进制,'h表示十六进制。

注意:

4’h1111实际表示4’b0001

2.x和z值:

在数字电路中,x代表不定值,z代表高阻值。

一个x可以用来定义十六进制数的四位二进制数的状态,八进制数的三位,二进制数的一位。

z的表示方式同x类似。z还有一种表达方式是可以写作?。在使用case表达式时建议使用这种写法,以提高程序的可读性。见下例:

4'b10x0 //位宽为4的二进制数从低位数起第二位为不定值
4'b101z //位宽为4的二进制数从低位数起第一位为高阻值
12'dz //位宽为12的十进制数其值为高阻值(第一种表达方式)
12'd? //位宽为12的十进制数其值为高阻值(第二种表达方式)
8'h4x //位宽为8的十六进制数其低四位值为不定值

3.负数:

一个数字可以被定义为负数,只需在位宽表达式前加一个减号,减号必须写在数字定义表达式的最前面。注意减号不可以放在位宽和进制之间也不可以放在进制和具体的数之间。见下例:

-8'd5 //这个表达式代表5的补数(用八位二进制数表示)
8'd-5 //非法格式

4.下划线(underscore_):

下划线可以用来分隔开数的表达以提高程序可读性。但不可以用在位宽和进制处,只能用在具体的数字之间。见下例:

16'b1010_1011_1111_1010  //合法格式
8'b_0011_1010  //非法格式

当常量不说明位数时,默认值是32位,每个字母用8位的ASCII值表示。

例:

10=32'd10=32'b1010
1=32'd1=32'b1
-1=-32'd1=32'hFFFFFFFF
‘BX=32'BX=32'BXXXXXXX…X
“AB”=16'B01000001_01000010

3.常量之参数

在Verilog HDL中用parameter来定义常量,即用parameter来定义一个标识符代表一个常量,称为符号常量,即标识符形式的常量,采用标识符代表一个常量可提高程序的可读性和可维护性。parameter型数据是一种常数型的数据,其说明格式如下:

parameter 参数名1=表达式,参数名2=表达式, …, 参数名n=表达式;

parameter是参数型数据的确认符,确认符后跟着一个用逗号分隔开的赋值语句表。在每一个赋值语句的右边必须是一个常数表达式。

也就是说,该表达式只能包含数字或先前已定义过的参数。见下列:

parameter msb=7;  //定义参数msb为常量7
parameter e=25, f=29; //定义二个常数参数
parameter r=5.7;  //声明r为一个实型参数
parameter byte_size=8, byte_msb=byte_size-1; //用常数表达式赋值
parameter average_delay = (r+f)/2;  //用常数表达式赋值

4.变量

一. 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型数据

二. 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 [7:0] mymem[1023:0] //定义1k字节的存储器

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

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

reg型数据的缺省初始值是不定值x。reg型数据可以赋正值,也可以赋负值。但当一个reg型数据是一个表达式中的操作数时,它的值被当作是无符号值,即正值。例如:当一个四位的寄存器用作表达式中的操作数时,如果开始寄存器被赋以值-1,则在表达式中进行运算时,其值被认为是+15。

注意:

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

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

wire用法总结

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

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

3.wire不能存储值(无状态),并且不能在always @块内赋值(=或<=)左侧使用。

4.wire是assign语句左侧唯一的合法类型

5.wire只能用于组合逻辑

reg用法总结

  1. 类似于电线,但可以存储信息(有内存,有状态)允许连接到模块的输入端口,但不能连接到实例化的输出

  2. 在模块声明中,reg可以用作输出,但不能用作输入

  3. 在always@(......)语句块内,= 或者 <= 赋值语句的左边必须是是reg变量

    在initial语句块内,= 赋值语句的左边必须是是reg变量

  4. Reg不能用于assign赋值语句的左侧

  5. 当与@(posedge clock)块一起使用时,reg可用于创建寄存器

  6. reg可用于组合逻辑和时序逻辑

构建一个模块module时

input必须是wire

output可以是wire也可以是reg

inout必须是wire

例化模块时

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

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

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

四.运算符

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

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

  2. 赋值运算符(=,<=)

  3. 关系运算符(>,<,>=,<=)

  4. 逻辑运算符(&&,||,!)

  5. 条件运算符(?:)

  6. 位运算符(,|,^,&,^)

  7. 移位运算符(<<,>>)

  8. 拼接运算符({ })

  9. 其它

在Verilog HDL语言中运算符所带的操作数是不同的,按其所带操作数的个数运算符可分为三种:

  1. 单目运算符(unary operator):可以带一个操作数,操作数放在运算符的右边。

  2. 二目运算符(binary operator):可以带二个操作数,操作数放在运算符的两边。

  3. 三目运算符(ternary operator):可以带三个操作,这三个操作数用三目运算符分隔开。

见下例:

clock = ~clock;  // ~是一个单目取反运算符, clock是操作数。
c = a | b;  // 是一个二目按位或运算符, a 和 b是操作数。
r = s ? t : u; // ?: 是一个三目条件运算符, s,t,u是操作数。

下面对常用的几种运算符进行介绍。

1.算术运算符

在Verilog HDL语言中,算术运算符又称为二进制运算符,共有下面几种:

  1. +(加法运算符,或正值运算符,如 rega+regb,+3)

  2. - (减法运算符,或负值运算符,如 rega-3,-3)

  3. × (乘法运算符,如rega*3)

  4. / (除法运算符,如5/3)

  5. % (模运算符,或称为求余运算符,要求%两侧均为整型数据。如7%3的值为1)

在进行整数除法运算时,结果值要略去小数部分,只取整数部分。而进行取模运算时,结果值的符号位采用模运算式里第一个操作数的符号位。见下例。

模运算表达式       结果     说明
10%3               1       余数为1
11%3               2       余数为2
12%3               0       余数为0即无余数
-10%3             -1       结果取第一个操作数的符号位,所以余数为-1
11%3               2       结果取第一个操作数的符号位,所以余数为2.

注意: 在进行算术运算操作时,如果某一个操作数有不确定的值x,则整个结果也为不定值x。

2.位运算符

Verilog HDL作为一种硬件描述语言,是针对硬件电路而言的。在硬件电路中信号有四种状态值1,0,x,z.在电路中信号进行与或非时,反映在Verilog HDL中则是相应的操作数的位运算。Verilog HDL提供了以下五种位运算符:

  1. ~ //取反

  2. & //按位与

  3. | //按位或

  4. ^ //按位异或

  5. ^~ //按位同或(异或非)

说明:

  • 位运算符中除了~是单目运算符以外,均为二目运算符,即要求运算符两侧各有一个操作数.

  • 位运算符中的二目运算符要求对两个操作数的相应位进行运算操作。

下面对各运算符分别进行介绍:

1) "取反"运算符~

~是一个单目运算符,用来对一个操作数进行按位取反运算。

其运算规则见下表:

举例说明:

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

2) "按位与"运算符&

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

其运算规则见下表:

3) "按位或"运算符|

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

其运算规则见下表:

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

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

其运算规则见下表:

5) "按位同或"运算符^~

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

其运算规则见下表:

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

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

3 逻辑运算符

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

  1. && 逻辑与

  2. || 逻辑或

  3. ! 逻辑非

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

"!"是单目运算符,只要求一个操作数,如!(a>b)。

下表为逻辑运算的真值表。它表示当a和b的值为不同的组合时,各种逻辑运算所得到的值。

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

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

  • (ab)||(xy) 可写成:ab || xy

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

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

4.关系运算符

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

a < b a小于b

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<a)进行运算时,关系表达式先被运算,然后返回结果值0或1被size减去。而当表达式 size-1<a 进行运算时,size先被减去1,然后再同a相比。

5.等式运算符

在Verilog HDL语言中存在四种等式运算符:

  1. == (等于)

  2. != (不等于)

  3. === (等于)

  4. !== (不等于)

这四个运算符都是二目运算符,它要求有两个操作数。"=="和"!="又称为逻辑等式运算符。其结果由两个操作数的值决定。由于操作数中某些位可能是不定值x和高阻值z,结果可能为不定值x。

而"="和"!"运算符则不同,它在对操作数进行比较时对某些位的不定值x和高阻值z也进行比较,两个操作数必需完全一致,其结果才是1,否则为0*。*"="和"!"运算符常用于case表达式的判别,所以又称为"case等式运算符"。这四个等式运算符的优先级别是相同的。下面画出==与===的真值表,帮助理解两者间的区别。

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

例:

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

6.移位运算符

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

<< (左移位运算符) 和 >>(右移位运算符)。

其使用方法如下:

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

a代表要进行移位的操作数,n代表要移几位。这两种移位运算都用0来填补移出的空位。下面举例说明:

module shift;reg [3:0] start, result;initialbeginstart = 1; //start在初始时刻设为值0001result = (start<<2);//移位后,start的值0100,然后赋给result。end
endmodule

从上面的例子可以看出,start在移过两位以后,用0来填补空出的位。

进行移位运算时应注意移位前后变量的位数,下面将给出一例。

例:

4’b1001<<1 = 5’b10010;
4’b1001<<2 = 6’b100100;
1<<6 = 32’b1000000;
4’b1001>>1 = 4’b0100;
4’b1001>>4 = 4’b0000; 

7.位拼接运算符(Concatation)

在Verilog HDL语言有一个特殊的运算符:位拼接运算符{}。用这个运算符可以把两个或多个信号的某些位拼接起来进行运算操作。其使用方法如下:

{信号1的某几位,信号2的某几位,..,..,信号n的某几位}

即把某些信号的某些位详细地列出来,中间用逗号分开,最后用大括号括起来表示一个整体信号。见下例:

{a,b[3:0],w,3’b101}

也可以写成为

{a,b[3],b[2],b[1],b[0],w,1’b1,1’b0,1’b1}

在位拼接表达式中不允许存在没有指明位数的信号。这是因为在计算拼接信号的位宽的大小时必需知道其中每个信号的位宽。

位拼接还可以用重复法来简化表达式。见下例:

{4{w}} //这等同于{w,w,w,w}

位拼接还可以用嵌套的方式来表达。见下例:

{b,{3{a,b}}} //这等同于{b,a,b,a,b,a,b}

用于表示重复的表达式如上例中的4和3,必须是常数表达式。

补:

1.截取数据的不同位数进行拼接:

例:data1 = 0100_1101

data2 = 1011_0011

data = {data1[3:0],data2[7:4]}

则可得:data = 1101_1011

2.作为移位运算符使用,即在一个数据中对不同的位进行拼接,可以实现移位的作用:

这里需要补充一下移位运算符的用法:

左移:<< 右移:>>

在使用移位运算符时,无论是有符号还是无符号类型,移位出现的空缺一律用0填补,但是当进行的操作数和结果数位数不一致时,二者有明显的差异:

无符号类型:当操作数和结果数不一致时,需要在高位填0后,再进行移位;

有符号类型:当操作数和结果数不一致时,需要在高位填补符号位后,再进行移位。

对于移位运算符,要根据具体的电路去截取不同的位数,这里举一个例子:

data = 0011_0101

data1 = {1’b0,data[7:1]},则data1 = 0001_1010

data2 = (data >> 1),则data2 = 0001_1010

二者实现的效果是一致的。

8.缩减运算符(reduction operator)

缩减运算符是单目运算符,也有与或非运算

其与或非运算规则类似于位运算符的与或非运算规则,但其运算过程不同。位运算是对操作数的相应位进行与或非运算,操作数是几位数则运算结果也是几位数。

而缩减运算则不同,缩减运算是对单个操作数进行或与非递推运算,最后的运算结果是一位的二进制数。

缩减运算的具体运算过程是这样的:第一步先将操作数的第一位与第二位进行或与非运算,第二步将运算结果与第三位进行或与非运算,依次类推,直至最后一位。

例如:

reg [3:0] B;
reg C;
C = &B;

相当于:

C =( (B[0]&B[1]) & B[2] ) & B[3];

由于缩减运算的与、或、非运算规则类似于位运算符与、或、非运算规则,这里不再详细讲述,请参照位运算符的运算规则介绍。

五.语句

赋值语句 连续赋值语句
过程赋值语句
条件语句 if-else 语句
case 语句
循环语句 forever 语句
repeat 语句
while 语句
for 语句
结构说明语句 initial 语句
always 语句
task 语句
function 语句
编译预处理语句 define 语句
include 语句
timescale 语句

1.赋值语句

1.1 连续赋值语句(assign) :常用于对wire型变量赋值。

 input a,b;output c;assign c=a&b;

1.2 过程赋值语句:常用于对reg型变量进行赋值

(1).非阻塞(Non_Blocking)赋值方式( 如 b <= a;)

  • 块结束后才完成赋值操作。

  • b的值并不是立刻就改变的。

  • 这是一种比较常用的赋值方法。(特别在编写可综合模块时)

  • 一条非阻塞赋值语句的执行是不会阻塞下一条语句的执行,并行执行。

    module non_block(c,a,b,clk);output c,b;input a,clk;reg c,b;always @(posedge clk)begin b<=a;   //b的值变为ac<=b;   //c的值为b的旧值end
    endmodule

(2).阻塞(Blocking)赋值方式( 如 b = a; )

  • 赋值语句执行完后,块才结束。

  • b的值在赋值语句执行完后立刻就改变的。

  • 可能会产生意想不到的结果。

  • 该语句结束时就完成赋值操作,前面的语句没有完成前,后面的语句不能执行,多个阻塞赋值语句是顺序执行的。

module block(c,a,b,clk);output c,b;input a,clk;reg c,b;always @(posedge clk)begin b=a;   //b的值变为ac=b;   //c的值变为bend
endmodule

2.条件语句

1.1 if_else语句

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

(1). if(表达式)语句

例如:

 if ( a > b ) out1 <= int1;

(2).if(表达式) 语句1

else 语句2

例如:

 if(a>b) out1<=int1;else  out1<=int2;

(3).if(表达式1) 语句1;

else if(表达式2) 语句2;

else if(表达式3) 语句3;

........

else if(表达式m) 语句m;

else 语句n;

例如:

if(a>b) out1<=int1;
else if(a==b) out1<=int2;
else out1<=int3;

六点说明:

(1).三种形式的if语句中在if后面都有“表达式”,一般为逻辑表达式或关系表达式。系统对表达式的值进行判断,若为0,x,z,按“假”处理,若为1,按“真”处理,执行指定的语句。

(2) .第二、第三种形式的if语句中,在每个else前面有一分号,整个语句结束处有一分号。

例如:

这是由于分号是Verilog HDL语句中不可缺少的部分,这个分号是if语句中的内嵌套语句所要求的。如果无此分号,则出现语法错误。但应注意,不要误认为上面是两个语句(if语句和else语句)。它们都属于同一个if语句。else子句不能作为语句单独使用,它必须是if语句的一部分,与if配对使用。

(3).在if和else后面可以包含一个内嵌的操作语句(如上例),也可以有多个操作语句,此时用begin和end这两个关键词将几个语句包含起来成为一个复合块语句。如:

if(a>b)beginout1<=int1;out2<=int2;end
​
elsebeginout1<=int2;out2<=int1;end

注意在end后不需要再加分号。因为begin_end内是一个完整的复合语句,不需再附加分号。

(4).允许一定形式的表达式简写方式。如下面的例子:

if(expression) 等同与 if( expression == 1 )
if(!expression) 等同与 if( expression != 1 )

(5).if语句的嵌套

在if语句中又包含一个或多个if语句称为if语句的嵌套。一般形式如下:

if(expression1)if(expression2) 语句1 (内嵌if)else 语句2
elseif(expression3) 语句3 (内嵌if)else 语句4

应当注意if与else的配对关系,else总是与它上面的最近的if配对。如果if与else的数目不一样,为了实现程序设计者的企图,可以用begin_end块语句来确定配对关系。例如:

if( )begin if( ) 语句1 (内嵌if)end
else语句2

这时begin_end块语句限定了内嵌if语句的范围,因此else与第一个if配对。注意begin_end块语句在if_else语句中的使用。因为有时begin_end块语句的不慎使用会改变逻辑行为。见下例:

if(index>0)for(scani=0;scani<index;scani=scani+1)if(memory[scani]>0)begin$display("...");memory[scani]=0;end
else /*WRONG*/
$display("error-indexiszero");

尽管程序设计者把else写在与第一个if(外层if)同一列上,希望与第一个if对应,但实际上else是与第二个if对应,因为它们相距最近。正确的写法应当是这样的:

if(index>0)beginfor(scani=0;scani<index;scani=scani+1)if(memory[scani]>0)begin$display("...");memory[scani]=0;endendelse /*WRONG*/$display("error-indexiszero");

(6).if_else例子

下面的例子是取自某程序中的一部分。这部分程序用if_else语句来检测变量index以决定三个寄存器modify_segn中哪一个的值应当与index相加作为memory的寻址地址。并且将相加值存入寄存器index以备下次检测使用。程序的前十行定义寄存器和参数。

 //定义寄存器和参数。
reg [31:0] instruction, segment_area[255:0];
reg [7:0] index;
reg [5:0] modify_seg1, modify_seg2, modify_seg3;
parametersegment1=0, inc_seg1=1,segment2=20, inc_seg2=2,segment3=64, inc_seg3=4,data=128;
//检测寄存器index的值
if(index<segment2)begininstruction = segment_area[index + modify_seg1];index = index + inc_seg1;end
else if(index<segment3)begininstruction = segment_area[index + modify_seg2];index = index + inc_seg2;end
else if (index<data)begininstruction = segment_area[index + modify_seg3]; index = index + inc_seg3;end
else
instruction = segment_area[index];

1.2 case语句

case语句是一种多分支选择语句,if语句只有两个分支可供选择,而实际问题中常常需要用到多分支选择,Verilog语言提供的case语句直接处理多分支选择。它的一般形式如下:

  1. case(表达式) <case分支项> endcase

  2. casez(表达式) <case分支项> endcase

  3. casex(表达式) <case分支项> endcase

case分支项的一般格式如下:

分支表达式:         语句
缺省项(default项):  语句

说明:

a) case括弧内的表达式称为控制表达式,case分支项中的表达式称为分支表达式。控制表达式通常表示为控制信号的某些位,分支表达式则用这些控制信号的具体状态值来表示,因此分支表达式又可以称为常量表达式。

b) 当控制表达式的值与分支表达式的值相等时,就执行分支表达式后面的语句。如果所有的分支表达式的值都没有与控制表达式的值相匹配的,就执行default后面的语句。

c) default项可有可无,一个case语句里只准有一个default项。

下面是一个简单的使用case语句的例子。该例子中对寄存器rega译码以确定result的值。

reg [15:0] rega;
reg [9:0] result;
case(rega)
16 'd0: result = 10 'b0111111111;
16 'd1: result = 10 'b1011111111;
16 'd2: result = 10 'b1101111111;
16 'd3: result = 10 'b1110111111;
16 'd4: result = 10 'b1111011111;
16 'd5: result = 10 'b1111101111;
16 'd6: result = 10 'b1111110111;
16 'd7: result = 10 'b1111111011;
16 'd8: result = 10 'b1111111101;
16 'd9: result = 10 'b1111111110;
default: result = 'bx;
endcase

d) 每一个case分项的分支表达式的值必须互不相同,否则就会出现矛盾现象(对表达式的同一个值,有多种执行方案)。

e) 执行完case分项后的语句,则跳出该case语句结构,终止case语句的执行。

f) 在用case语句表达式进行比较的过程中,只有当信号的对应位的值能明确进行比较时,比较才能成功。因此要注意详细说明case分项的分支表达式的值。

g) case语句的所有表达式的值的位宽必须相等,只有这样控制表达式和分支表达式才能进行对应位的比较。*一个经常犯的错误是用'bx, 'bz 来替代 n'bx, n'bz,这样写是不对的,因为信号x, z的缺省宽度是机器的字节宽度,通常是32位(此处 n 是case控制表达式的位宽)。*

下面将给出 case, casez, casex 的真值表:

case语句与if_else_if语句的区别主要有两点:

  1. 与case语句中的控制表达式和多分支表达式这种比较结构相比,if_else_if结构中的条件表达式更为直观一些。

  2. 对于那些分支表达式中存在不定值x和高阻值z位时,case语句提供了处理这种情况的手段。下面的两个例子介绍了处理x,z值位的case语句。

[例1]:

case ( select[1:2] )
2 'b00: result = 0;
2 'b01: result = flaga;
2 'b0x,
2 'b0z: result = flaga? 'bx : 0;
2 'b10: result = flagb;
2 'bx0,
2 'bz0: result = flagb? 'bx : 0;
default: result = 'bx;
endcase

[例2]:

case(sig)
1 'bz: $display("signal is floating");
1 'bx: $display("signal is unknown");
default:  $display("signal is %b", sig);
endcase

Verilog HDL针对电路的特性提供了case语句的其它两种形式用来处理case语句比较过程中的不必考虑的情况( don't care condition )。其中casez语句用来处理不考虑高阻值z的比较过程,casex语句则将高阻值z和不定值都视为不必关心的情况。所谓不必关心的情况,即在表达式进行比较时,不将该位的状态考虑在内。这样在case语句表达式进行比较时,就可以灵活地设置以对信号的某些位进行比较。见下面的两个例子:

[例3]:

reg[7:0] ir;
casez(ir)8 'b1???????: instruction1(ir);8 'b01??????: instruction2(ir);8 'b00010???: instruction3(ir);8 'b000001??: instruction4(ir);
endcase

[例4]:

reg[7:0] r, mask;
mask = 8'bx0x0x0x0;
casex(r^mask)8 'b001100xx: stat1;8 'b1100xx00: stat2;8 'b00xx0011: stat3;8 'bxx001100: stat4;
endcase

注:由于使用条件语句不当在设计中生成了原本没想到有的锁存器

Verilog HDL设计中容易犯的一个通病是由于不正确使用语言,生成了并不想要的锁存器。下面我们给出了一个在“always"块中不正确使用if语句,造成这种错误的例子。

检查一下左边的"always"块,if语句保证了只有当al=1时,q才取d的值。这段程序没有写出 al = 0 时的结果, 那么当al=0时会怎么样呢?

在"always"块内,如果在给定的条件下变量没有赋值,这个变量将保持原值,也就是说会生成一个锁存器!

如果设计人员希望当 al = 0 时q的值为0,else项就必不可少了,请注意看右边的"always"块,整个Verilog程序模块综合出来后,"always"块对应的部分不会生成锁存器。

Verilog HDL程序另一种偶然生成锁存器是在使用case语句时缺少default项的情况下发生的。

case语句的功能是:在某个信号(本例中的sel)取不同的值时,给另一个信号(本例中的q)赋不同的值。

注意看下图左边的例子,如果sel=00,q取a值,而sel=11,q取b的值。这个例子中不清楚的是:如果sel取00和11以外的值时q将被赋予什么值?在下面左边的这个例子中,程序是用Verilog HDL写的,即默认为q保持原值,这就会自动生成锁存器。

右边的例子很明确,程序中的case语句有default项,指明了如果sel不取00或11时,编译器或仿真器应赋给q的值。程序所示情况下,q赋为0,因此不需要锁存器。

以上就是怎样来避免偶然生成锁存器的错误。

  • 如果用到if语句,最好写上else项。

  • 如果用case语句,最好写上default项。

遵循上面两条原则,就可以避免发生这种错误,使设计者更加明确设计目标,同时也增强了Verilog程序的可读性。

3.循环语句

在Verilog HDL中存在着四种类型的循环语句,用来控制执行语句的执行次数。

  1. forever 连续的执行语句。

  2. repeat 连续执行一条语句 n 次。

  3. while 执行一条语句直到某个条件不满足。如果一开始条件即不满足(为假),则语句一次也不能被执行。

  4. for通过以下三个步骤来决定语句的循环执行。

  • a) 先给控制循环次数的变量赋初值。

  • b) 判定控制循环的表达式的值,如为假则跳出循环语句,如为真则执行指定的语句后,转到第三步。

  • c) 执行一条赋值语句来修正控制循环变量次数的变量的值,然后返回第二步。

下面对各种循环语句详细的进行介绍。

1.1forever语句

forever语句的格式如下:

 forever 语句;
​
或
​
forever begin 多条语句 end

forever循环语句常用于产生周期性的波形,用来作为仿真测试信号。它与always语句不同处在于不能独立写在程序中,而必须写在initial块中。

1.2 repeat语句

repeat语句的格式如下:

repeat(表达式) 语句;或
​
repeat(表达式) begin 多条语句 end

在repeat语句中,其表达式通常为常量表达式。

下面的例子中使用repeat循环语句及加法和移位操作来实现一个乘法器。

parameter size=8,longsize=16;
reg [size:1] opa, opb;
reg [longsize:1] result;begin: mult
reg [longsize:1] shift_opa, shift_opb;
shift_opa = opa;
shift_opb = opb;
result = 0;
​
repeat(size)beginif(shift_opb[1])result = result + shift_opa; shift_opa = shift_opa <<1;shift_opb = shift_opb >>1;end
end

1.3 while语句

while语句的格式如下:

 while(表达式) 语句

或用如下格式:

while(表达式) begin 多条语句 end

下面举一个while语句的例子,该例子用while循环语句对rega这个八位二进制数中值为1的位进行计数。

begin: count1s
reg[7:0] tempreg;
count=0;
tempreg = rega;
​
while(tempreg)beginif(tempreg[0]) count = count + 1;tempreg = tempreg>>1;end
end

1.4 for语句

for语句的一般形式为:

 for(表达式1;表达式2;表达式3)  语句

它的执行过程如下:

  1. 先求解表达式1;

  2. 求解表达式2,若其值为真(非0),则执行for语句中指定的内嵌语句,然后执行下面的第3步。若为假(0),则结束循环,转到第5步。

  3. 若表达式为真,在执行指定的语句后,求解表达式3。

  4. 转回上面的第2步骤继续执行。

  5. 执行for语句下面的语句。

for语句最简单的应用形式是很易理解的,其形式如下:

for(循环变量赋初值;循环结束条件;循环变量增值)执行语句

for循环语句实际上相当于采用while循环语句建立以下的循环结构:

begin循环变量赋初值;while(循环结束条件)begin执行语句循环变量增值;end
end

这样对于需要8条语句才能完成的一个循环控制,for循环语句只需两条即可。

下面分别举两个使用for循环语句的例子。例1用for语句来初始化memory。例2则用for循环语句来实现前面用repeat语句实现的乘法器。

[例1]:

begin: init_mem
reg[7:0] tempi;
​
for(tempi=0;tempi<memsize;tempi=tempi+1)
memory[tempi]=0;
end

[例2]:

parameter size = 8, longsize = 16;
reg[size:1] opa, opb;
reg[longsize:1] result;begin:mult
integer bindex;
result=0;
for( bindex=1; bindex<=size; bindex=bindex+1 )if(opb[bindex])result = result + (opa<<(bindex-1));
end

在for语句中,循环变量增值表达式可以不必是一般的常规加法或减法表达式。下面是对rega这个八位二进制数中值为1的位进行计数的另一种方法。见下例:

begin: count1sreg[7:0] tempreg;count=0;for( tempreg=rega; tempreg; tempreg=tempreg>>1 )if(tempreg[0]) count=count+1;
end

4.结构说明语句

过程块是行为模型的基础。

过程块有两种:

  • initial块,只能执行一次

  • always块,循环执行

过程块中有下列部件:

  • 过程赋值语句:在描述过程块中的数据流

  • 高级结构(循环,条件语句):描述块的功能

  • 时序控制:控制块的执行及块中的语句。

initial语句与always语句和begin_endfork_join是一种高频搭配:

1.1 initial语句

initial语句的格式如下:

initialbegin语句1;语句2;......语句n;
end

举例说明:

[例1]:

initialbeginareg=0; //初始化寄存器aregfor(index=0;index<size;index=index+1)memory[index]=0; //初始化一个memory
end

在这个例子中用initial语句在仿真开始时对各变量进行初始化。

[例2]:

initialbegininputs = 'b000000; //初始时刻为0#10 inputs = 'b011001; #10 inputs = 'b011011; #10 inputs = 'b011000; #10 inputs = 'b001000;
end

从这个例子中,我们可以看到initial语句的另一用途,即用initial语句来生成激励波形作为电路的测试仿真信号。一个模块中可以有多个initial块,它们都是并行运行的。

initial块常用于测试文件和虚拟模块的编写,用来产生仿真测试信号和设置信号记录等仿真环境。

1.2 always语句

always语句在仿真过程中是不断重复执行的。

其声明格式如下:

always <时序控制>  <语句>

always语句由于其不断重复执行的特性,只有和一定的时序控制结合在一起才有用。如果一个always语句没有时序控制,则这个always语句将会发成一个仿真死锁。见下例:

[例1]:

always areg = ~areg;

这个always语句将会生成一个0延迟的无限循环跳变过程,这时会发生仿真死锁。如果加上时序控制,则这个always语句将变为一条非常有用的描述语句。见下例:

[例2]:

always #10  areg = ~areg;

这个例子生成了一个周期为20 的无限延续的信号波形,常用这种方法来描述时钟信号,作为激励信号来测试所设计的电路。

[例3]:

reg[7:0] counter;
reg tick;
​
always @(posedge areg) begintick = ~tick;counter = counter + 1;end

这个例子中,每当areg信号的上升沿出现时把tick信号反相,并且把counter增加1。这种时间控制是always语句最常用的。

always 的时间控制可以是沿触发也可以是电平触发的,可以单个信号也可以多个信号,中间需要用关键字 or 连接,如:

 always @(posedge clock or posedge reset) //由两个沿触发的always块begin……endalways @( a or b or c ) //由多个电平触发的always块begin……end

沿触发的always块常常描述时序逻辑,如果符合可综合风格要求可用综合工具自动转换为表示时序逻辑的寄存器组和门级逻辑,而电平触发的always块常常用来描述组合逻辑和带锁存器的组合逻辑,如果符合可综合风格要求可转换为表示组合逻辑的门级逻辑或带锁存器的组合逻辑。一个模块中可以有多个always块,它们都是并行运行的

always是一个极高频的语法,always@()用法总结如下

① always@(信号名)

• 信号名有变化就触发事件

例:
always@( clock)
a=b;

② always@( posedge信号名)

• 信号名有上升沿就触发事件

例:
always@( posedge clock)
a=b;

③ always@(negedge信号名)

• 信号名有下降沿就触发事件

例:
always@( negedge clock)
a=b;

④ always@(敏感事件1or敏感事件2or…)

• 敏感事件之一触发事件

• 没有其它组合触发

例:
always@(posedge reset or posedge clear)
reg_out=0;

⑤ always@(*)

• 无敏感列表,描述组合逻辑,和assign语句是有区别的

例:
always@(*)
b= 1'b0;

assign赋值语句和always@(*)语句。两者之间的差别有:

1.被assign赋值的信号定义为wire型,被always@(*)结构块下的信号定义为reg型,值得注意的是,这里的reg并不是一个真正的触发器,只有敏感列表为上升沿触发的写法才会综合为触发器,在仿真时才具有触发器的特性。

2.另外一个区别则是更细微的差别:举个例子,

wire a;
reg b;
​
assign a = 1'b0;
​
always@(*)
b= 1'b0;

在这种情况下,做仿真时a将会正常为0,但是b却是不定态。这是为什么?verilog规定,always@()中的是指该always块内的所有输入信号的变化为敏感列表,也就是仿真时只有当always@()块内的输入信号产生变化,该块内描述的信号才会产生变化,而像always@() b = 1'b0,这种写法由于1'b0一直没有变化,所以b的信号状态一直没有改变,由于b是组合逻辑输出,所以复位时没有明确的值(不定态),而又因为always@(*)块内没有敏感信号变化,因此b的信号状态一直保持为不定态。事实上该语句的综合结果有可能跟assign一样但是在功能仿真时就差之千里了。

5.编译预处理语句

Verilog HDL语言和C语言一样也提供了编译预处理的功能。“编译预处理”是Verilog HDL编译系统的一个组成部分。

Verilog HDL语言允许在程序中使用几种特殊的命令(它们不是一般的语句)。Verilog HDL编译系统通常先对这些特殊的命令进行“预处理”,然后将预处理的结果和源程序一起在进行通常的编译处理。

在Verilog HDL语言中,为了和一般的语句相区别,这些预处理命令以符号“ `”开头(注意这个符号是不同于单引号“ '”的)。这些预处理命令的有效作用范围为定义命令之后到本文件结束或到其它命令定义替代该命令之处。Verilog HDL提供了以下预编译命令:

accelerate,autoexpand_vectornets,celldefine,default_nettype,define,else,endcelldefine,endif,endprotect,endprotected,expand_vectornets,ifdef,include,noaccelerate,noexpand_vectornets,noremove_gatenames,noremove_netnames,nounconnected_drive,protect,protecte,remove_gatenames,remove_netnames,reset,timescale,`unconnected_drive

在这一小节里只对常用的define、include、`timescale进行介绍,其余的请查阅参考书。

1.1 宏定义 `define

用一个指定的标识符(即名字)来代表一个字符串,它的一般形式为:

 `define 标识符(宏名) 字符串(宏内容)

如:

`define signal string

它的作用是指定用标识符signal来代替string这个字符串,在编译预处理时,把程序中在该命令以后所有的signal都替换成string。

这种方法使用户能以一个简单的名字代替一个长的字符串,也可以用一个有含义的名字来代替没有含义的数字和符号,因此把这个标识符(名字)称为“宏名”,在编译预处理时将宏名替换成字符串的过程称为“宏展开”。`define是宏定义命令。

[例1]:

`define WORDSIZE 8
module
reg[1:`WORDSIZE] data; //这相当于定义 reg[1:8] data; 

关于宏定义的八点说明:

  1. 宏名可以用大写字母表示,也可以用小写字母表示。建议使用大写字母,以与变量名相区别。

  2. define命令可以出现在模块定义里面,也可以出现在模块定义外面。宏名的有效范围为定义命令之后到原文件结束。通常,define命令写在模块定义的外面,作为程序的一部分,在此程序内有效。

    3. *在引用已定义的宏名时,必须在宏名的前面加上符号“`”,表示该名字是一个经过宏定义的名字。**

  3. 使用宏名代替一个字符串,可以减少程序中重复书写某些字符串的工作量。而且记住一个宏名要比记住一个无规律的字符串容易,这样在读程序时能立即知道它的含义,当需要改变某一个变量时,可以只改变 define命令行,一改全改。如例1中,先定义WORDSIZE代表常量8,这时寄存器data是一个8位的寄存器。如果需要改变寄存器的大小,只需把该命令行改为:define WORDSIZE 16。这样寄存器data则变为一个16位的寄存器。由此可见使用宏定义,可以提高程序的可移植性和可读性。

  4. 宏定义是用宏名代替一个字符串,也就是作简单的置换,不作语法检查。预处理时照样代入,不管含义是否正确。只有在编译已被宏展开后的源程序时才报错。

  5. 宏定义不是Verilog HDL语句,不必在行末加分号。如果加了分号会连分号一起进行置换。如:

[例2]:

module test;
reg a, b, c, d, e, out;
`define expression a+b+c+d;
assign out = `expression + e;...endmodule 

经过宏展开以后,该语句为:

assign  out = a+b+c+d;+e;

显然出现语法错误。

\7) 在进行宏定义时,可以引用已定义的宏名,可以层层置换。如:

[例3]:

module test;
reg a, b, c;
wire out;
`define aa a + b
`define cc c + `aa
assign out = `cc;
endmodule

这样经过宏展开以后,assign语句为

assign out = c + a + b;
  1. 宏名和宏内容必须在同一行中进行声明。如果在宏内容中包含有注释行,注释行不会作为被置换的内容。如:

[例4]:

module`define typ_nand nand #5 //define a nand with typical delay`typ_nand g121(q21,n10,n11);………endmodule

经过宏展开以后,该语句为:

 nand #5 g121(q21,n10,n11);

宏内容可以是空格,在这种情况下,宏内容被定义为空的。当引用这个宏名时,不会有内容被置换。

注意:组成宏内容的字符串不能够被以下的语句记号分隔开的。

  • · 注释行

  • · 数字

  • · 字符串

  • · 确认符

  • · 关键词

  • · 双目和三目字符运算符

如下面的宏定义声明和引用是非法的。

`define first_half "start of string
$display(`first_half end of string");

注意在使用宏定义时要注意以下情况:

  1. 对于某些 EDA软件,在编写源程序时,如使用和预处理命令名相同的宏名会发生冲突,因此建议不要使用和预处理命令名相同的宏名。

  1. 宏名可以是普通的标识符(变量名)。例如signal_name 和 'signal_name的意义是不同的。但是这样容易引起混淆,建议不要这样使用。

1.2 “文件包含”处理`include

所谓“文件包含”处理是一个源文件可以将另外一个源文件的全部内容包含进来,即将另外的文件包含到本文件之中。Verilog HDL语言提供了`include命令用来实现“文件包含”的操作。其一般形式为:

`include “文件名”

上图表示“文件包含”的含意。图(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;
endmodulemodule 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
  1. 如果文件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"了。

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

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

1.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工具记录的时间信息。

补充:在verilog中#的用法

#是延迟的意思,井号后面数字是延迟的数量,延迟的单位由`timescale控制

比如有:`timescale 1ns/1ps 意思就是时间单位为1ns,精度是1ps ​ 那么,#10.5 就是延迟10.5ns的意思 ​ 在同步时序数字逻辑电路的verilog代码中,不能加入“#”进行延迟,这不是代码编写阶段能决定的

1.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工具,选择不同的激励。

六. Verilog测试(仿真)文件TestBench如何编写

一、哪些步骤需要进行仿真

下图是FPGA开发的整个流程,先看一下仿真都出现在哪里。 流程图中绿色的步骤是要进行测试仿真的,即有三个步骤是要进行仿真操作的(有时会省略“综合后仿真”这一步)

1.RTL仿真 ​ 也称为综合前仿真、前仿真和功能仿真。 这一步只验证在顶层模块和功能子模块的设计输入完成后,其电路的逻辑功能是否符合设计要求,不考虑门延时和线延时。

2.综合后仿真 ​ 综合后仿真加入了门延时。

3.时序仿真 ​ 也称为后仿真,在门延时的基础上又加入了线延时。

二、如何编写仿真测试文件

下面以功能仿真为例子,说明测试仿真文件如何编写。

1.首先准备好需要被测模块的Verilog代码

module led_twinkle( input        sys_clk ,  //系统时钟input        sys_rst_n, //系统复位,低电平有效
​output [1:0] led //LED 灯
);
​......          //省略功能部分endmodule
​

2.编写TestBench测试仿真文件

在Vivado软件中,在左侧的 Flow Navigator 窗口点击 Add Source, 选择 Add or create simulation sources,点击 Next,点击 Create File, 给测试文件命名,通常格式为 “tb被测试模块名”,点击OK,点击Finish。

*在 Source 栏中的 Simulation Sources 中双击生成的 “tb_被测试模块名” 文件,进行编写。*
~timescale 1ns / 1ps  //测试时间基本单位为1ns,精度为1psmodule tb_led_twinkle(); //通常起名格式为 tb_被测试模块名
​
//输入reg sys_clk; reg sys_rst_n;
//输出wire [1:0] led;
​
//信号初始化,必须有这一步,容易被忽略
initial beginsys_clk = 1'b0; sys_rst_n = 1'b0;#200  //延时200nssys_rst_n = 1'b1;
end
​
//生成时钟,模拟晶振实际的周期时序
always #10 sys_clk = ~sys_clk; //每10ns,sys_clk进行翻转,达到模拟晶振周期为20ns//例化待测模块(模块例化可点击文章最后附上的网址)
led_twinkle u_led_twinkle(   .sys_clk (sys_clk),      //注意语句后面为逗号.sys_rst_n (sys_rst_n),.led (led)              //最后一步无逗号
);
​
endmodule
​

Verilog HDL 基础相关推荐

  1. Verilog HDL基础知识

    Verilog HDL基础知识 Verilog HDL的语言要素 空白符 注释符 标识符和转义标识符 关键字 数值 1. 整数及其表示 2. 实数及其表示 3. 字符串及其表示 数据类型 Verilo ...

  2. Verilog HDL基础知识---之数据类型

    Verilog HDL基础知识之数据类型 数据类型 物理数据类型 连线型 寄存器型 连线型和寄存器型数据类型的声明 存储器型 抽象数据类型 整型 时间型 实型 参数型 kkkk我来啦 , 接上回写 数 ...

  3. 数字集成电路设计(二、Verilog HDL基础知识)

    文章目录 1. 语言要素 1.1 空白符 1.2 注释符 1.3 标识符 1.3.1 转义标识符 1.4 关键字 1.5 数值 1.5.1 整数及其表示方式 1.5.2 实数及其表示方式 1.5.3 ...

  4. Verilog HDL 硬件描述语言基础

    HDL 硬件描述语言(Hardware Description Lagnuage,HDL)通过描述硬件的实现方法,来 产生与之对应的真实的硬件电路,最终实现所设计的预期功能. Verilog HDL ...

  5. 【连载】【FPGA黑金开发板】Verilog HDL那些事儿--低级建模的基础实例(七)

    声明:本文为原创作品,版权归akuei2及黑金动力社区(http://www.heijin.org)共同所有,如需转载,请注明出处http://www.cnblogs.com/kingst/ 第三章 ...

  6. 三、6【Verilog HDL】基础知识之门级建模

    参考书籍:<Verilog HDL 数字设计与综合>第二版,本文档为第5章的学习笔记.由于本章也讲述的建模方式.该建模方式是通常设计师常用的底层抽象层次.更为低层的为开关级建模. 想了解更 ...

  7. 爆肝4万字❤️零基础掌握Verilog HDL

    文章目录 0.前言 1.Verilog HDL简介 1.1 什么是Verilog HDL 1.2 verilog发展历史ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ 1.3 为什么要使用verilog ...

  8. Verilog HDL 语言基础语法

    模块的结构 (1)Verilog的基本设计单元是"模块"(block). (2)一个模块由两部分组成,一部分描述接口,另一部分描述逻辑功能. (3)每个Verilog程序包括4个主 ...

  9. Verilog HDL 语言基础

    目录 前言 一.Verilog HDL模块基本结构 1.模块声明&端口定义&数据类型声明 二.数据类型 1.信号状态 2.整数 2.1.标准形式 2.2.补充 3.实数 4.字符串 三 ...

最新文章

  1. Java2021中级面试题
  2. spark大批量读取Hbase时出现java.lang.OutOfMemoryError: unable to create new native thread
  3. 中端存储趋势:x86、SSD缓存和虚拟化
  4. 常用英文搜索引擎及特点
  5. matlab模拟钢琴演奏
  6. linux命令全集,Linux命令全集
  7. java 蓝牙4.0_《蓝牙4.0 BLE开发完全手册---物联网开发技术实战
  8. 密立根油滴实验的计算机仿真实验报告,H-D光谱实验的计算机仿真
  9. 【配置属性】—Entity Framework 对应表字段的类型的设定配置方法
  10. Android-JNI开发系列《三》-异常处理
  11. cdr怎么做文字路径_CorelDRAW如何制作环绕圆形的路径文字
  12. 玫瑰线轨迹如何规划?(desmos+ROS2+turtlesim+……)
  13. iperf使用与交叉编译
  14. 【硬见小百科】一些硬件电路技术经验整理
  15. Linux 系统下各种包的安装方法
  16. php鲜花销售理论意义,鲜花销售系统(论文+源代码)论文有用
  17. Python中利用openpyxl对Excel的各种相关详细操作(二十一种常用操作<代码+示例>)
  18. 利用java格里高利公式求圆周率_C语言用下列公式求pi的近似值,直到最后一项的绝对值小于1e-4为止:...
  19. 使用阿里云OSS实现图片上传案例
  20. python程序打包为exe,并压缩体积最小!

热门文章

  1. 部署Kubernetes集群(二进制 v1.18.8版)
  2. android http 网关,安卓平台上实现基于HTTP协议数据网关的方法
  3. 搭建K8S集群之node节点部署
  4. 拼图软件那个好用?好用的拼图软件分享
  5. 等面积圆柱投影的证明
  6. Quia maxime eius ipsa.
  7. 数字SOC设计之低功耗之线性稳压器(LDO)
  8. 计算机网络第4章(网际层)
  9. 工科赛树莓派OpenCV寻迹小车
  10. N1盒子丢失bootloader救砖方法