第7章-Verilog-HDL语言简介-课件_第1页
第7章-Verilog-HDL语言简介-课件_第2页
第7章-Verilog-HDL语言简介-课件_第3页
第7章-Verilog-HDL语言简介-课件_第4页
第7章-Verilog-HDL语言简介-课件_第5页
已阅读5页,还剩210页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

7.1VerilogHDL语言总体结构7.2端口声明与数据类型声明7.3数值的表示7.4连续赋值7.5模块实例化7.6验证设计7.7运算符(operator)7.8VerilogHDL行为级建模7.9任务和函数介绍7.10综合设计:交通信号灯控制器7.11VerilogHDL语言的仿真工具习题

第7章VerilogHDL语言简介

VerilogHDL语言是一种硬件描述语言,在20世纪80年代由GatewayDesignAutomation公司开发,用于电路的模拟。1995年,VerilogHDL成为IEEE(InstituteofElectricalandElectronicsEngineers,电子电气工程师协会)标准,即IEEE1364标准(Verilog-1995),2001年对该标准进行了修改(Verilog-2001)。Verilog-2001在Verilog-95上增加了一些新的特性,使VerilogHDL的表述更简洁。目前大部分EDA工具都支持两个标准。本书按Verilog-95标准对VerilogHDL进行简要的介绍。

VerilogHDL语言可以在系统级、算法级、寄存器传输级RTL(RegisteredTransfer

Level)、门级和开关级(晶体管级)等多个抽象设计层次对数字电路或者系统进行建模。VerilogHDL本质是一种复杂的描述数字系统的语言,句法上与C语

言相似,延用了C语言的多种操作符和结构,但是其语义上是基于并发硬件操作,完全不同于顺序执行的C语言。

VerilogHDL是一种仿真能力非常强大的语言,语言中包含了一部分可以用EDA工具进行逻辑综合的子集。所谓的逻辑综合就是指利用电子设计自动化EDA(ElectronicDesignAutomation)工具将VerilogHDL代码转换成电路。

VerilogHDL可以用于描述系统中的一个元件,也可以用于描述一个完整的系统。7.1VerilogHDL语言总体结构

VerilogHDL语言描述主要由三个部分构成:端口声明(PortDeclaration)、信号数据类型声明(DataTypeDeclaration)、电路模块功能描述(CircuitFunctionality),图7-1给出了一个基本VerilogHDL程序的组成结构。

VerilogHDL程序以关键词module开始,以关键字endmodule结束。module后面是一个模块名标识符,它唯一

地标识一个模块,模块名后括号中的port_list是模块的输入和输出列表。当portlist是空的时候,该模块是一个仿真中模块。图7-1VerilogHDL的模块组成结构端口声明(PortDeclarations):声明一个端口的性质,如输入(Input)、输出(Output)还是双向口(inout)以及端口的宽度([PORT_WIDTH1:0])。

信号数据类型声明(DataTypeDeclarations):声明module中用到的数据类型,如regport1,wireport2。

电路模块功能描述(Circuitfunctionality):描述电路功能。与C语言一样,在VerilogHDL语言中,除了关键字endmodule行外,每行以“;”号结束。VerilogHDL是大小写敏感语言,语言中所有的关键字都是小写,其注释方式有两种:一是单行注释,注释内容在“//”之后,直至本行末;二是多行注释,注释内容自“/*”开始,至“*/”结束。

【例7-1】

用Verilog语言描述两输入的异或门。

图7-2(a)给出了一个异或门的Verilog描述,图(b)给出了图(a)的综合结果。

图7-3是VerilogHDL语言的基本元素组成图,本章将按图7-3对VerilogHDL语言的各个部分作简单的介绍。图7-2异或门的VerilogHDL描述图7-3VerilogHDL语言的基本组成一个电路由多个不同的部件(模块)构成,这些部件之间并行地工作。一个部件可以是一个门,也可以是一个完成某些特殊功能的电路,如译码器、多路选择器和计数器等,还可以是完成复杂任务的子系统,如嵌入式微处理器等。Verilog语言中可以用三种方式来描述系统各个部件的功能:连续赋值(ContinuousAssignment)、过程模块(ProceduralBlock)和模块实例化(ModuleInstantiation)。此外,Verilog还可以用各种子程序如任务(task)、函数(function)或者系统任务(systemtask)来描述电路的行为或者仿真的行为。1.I/O端口声明

I/O端口是一个模块和其周围环境进行通信的信号接口,模块内部的信号对于环境而言是不可见的。例如,图7-4是一个两输入异或门的I/O端口。

VerilogHDL语言在模块内必须对输入、输出列表中的每个信号进行声明,同时对输入和输出信号的数据类型进行声明。7.2端口声明与数据类型声明图7-4两输入XOR门的I/O端口基本语法是:

moduleidentifier(port_name1,port_name2,…port_nameN);

[mode][port_name1];//声明端口类型

[mode][port_nameN];

[data_type][port_name1];//声明端口数据类型

[data_type][port_nameN];

endmodule

mode类型有三种:input(输入端口)、output(输出端口)和inout(双向端口)。这些都是关键字,不能用于信号名。(port_name1,…,port_nameN)是一个模块的输入和输出端口名称列表,这些名称之间用“,”进行分离,输入、输出列表的最后一项没

有“,”。[JP2][data_type][port_name1],…,[data_type][port_nameN]是对端口的数据类型进行声明。

【例7-2】

两输入异或门的I/O端口声明。

modulexor_2(in1,in2,out);

inputin1,in2;//声明输入信号

outputout;

//声明输出信号

wirein1,in2;

//声明输入端口的数据类型

wireout;

//声明输出端口的数据类型

endmodule

要特别注意的是:在模块中不能再次定义模块,即模块不能嵌套。例如,下面的写法是错误的。

moduleand_2(in1,in2,ou1);

moduledff(clk,rst,din,dout);//错误定义!

endmodule

endmodule

2.信号的数据类型声明

在VerilogHDL中使用四值逻辑,它们分别是:

(1)“0”:表示逻辑0或者判定条件假。

(2)“1”:表示逻辑或者判定条件为真。

(3)“z”:高阻状态。

(4)“x”:未知状态。

“z”表示高阻态,通常用于描述一个三态门;而“x”则是用于仿真或者建模,表示当前值不确定,当输入没有初始化或者输出有冲突的时候,会出现“x”状态。信号的取值是这四种逻辑值中的任意一种。信号数据类型的声明说明在本模块中使用的信号和参数的数据类型。

简单的语法是:

[data_type][range][signal_name];

//声明一个信号变量

或者

[data_type][range][signal_name1,signal_name2,…,

signal_nameN];

//同时声明N个类型、范围相同的信号变量

VerilogHDL有两大类数据类型:线网类型组及寄存器类型组。

(1)线网类型组(netgroup)。

在VerilogHDL语言中,线网类型组用于描述硬件元件间的物理连线。线网类型的变量是连续赋值的输出,也是不同部件之间的连接信号,它的值由驱动元件的值决定,如图7-5所示。如果没有驱动元件连接到线网,线网的缺省值为“z”。在net类型中最常用的类型是wire类型。正如其名字所表示,它表示一个连接线。图7-5线网类型组

wire数据类型表示比特信号。例如:

wirep1,p2;//声明两个1比特的信号

线网类型组(netgroup)中的其它数据类型包括:wand(线与)、supply0(电路地连接)、supply1(电源连接)等等,表示某类逻辑操作和功能,本书不涉及这些类型,有兴趣的读者可以参看本书后面列出的参考文献。

(2)寄存器类型组(registergroup)。

寄存器组中的数据类型在行为模型中表示抽象存储,是过程赋值的输出。这个组中包括四种类型:reg、integer、real和time。大多数变量用reg类型,reg类型是可以综合的。注意:综合出来的电路既可以包含也可以不包含存储元件。后三种数据类型只是用于建模和仿真。例如:

reg

d_flip;//声明一个reg类型的信号变量

在VerilogHDL中可用[rang]表示一组信号。wire和reg类型的变量可以声明成多位宽度的向量(vectors)。如果变量的宽度是一位,该变量即是标量(scalar)。我们可以用一维向量表示一组信号(总线)。例如:

wiresel;//标量信号

wire[7:0]data1,data2;//8比特数据

wire[31:0]addr;

//32比特地址

reg

[7:0]out;//8比特reg变量

信号的索引范围可以是升序[low#:high#],也可以是降序[high#:low#],但是信号数据最左边的那位就是二进制数的最高位。可以用一个二维向量表示一个存储器,例如一个32*4的存储器(表示一个存储器有32个字,每个字有4位),用VerilogHDL可以表示成

reg

[3:0]data_mem

[31:0];

在VerilogHDL语言中允许使用一个向量中的一位或者多位,如:

Data1[1];//向量data1的第2位;

Data2[3:2];//向量data2中的第4比特和第3比特;

在Verilog语言中,整数常量可以表示成各种各样的形式,一般的格式是:

[size]′[base][value]

其中,[base]说明数字的基数,当一个数用十进制表示,可省略该项。该项有4种形式:

①borB:二进制;

②oorO:八进制;

③horH:十六进制;

④dorD:十进制。

7.3数值的表示

[value]:说明在对应的基数表示方式下对应的数。为了方便阅读,一个数可以用“_”分割表示。如8′b1000_0011。

[size]:说明一个数值用几位表示,这项是可选的。如果在一个数的表示中存在该项,那么这个数被认为是有位数表示的,否则是无位数表示的数。

[size]项显式地表示了一个数值所用的位数。如果一个数所用位数小于size所规定的位数,需要对该数扩展到size规定的位数。如果一个数值的最高位是x或z,那么在该数前用x或者z填充,扩展到规定的位数;如果一个数有符号位,那么用符号位填充,扩展到规定的位数;其它情况下,在该数前用0填充,扩展至规定的位数。如果一个数前省略了[size]项,那么它实际的表示依赖于计算机的结构,但是至少扩展到32位。

例如,表7-1所示为有位数表示的常数,表7-2所示为无位数表示的常数。表7-1有位数表示的常数示例表7-2无位数表示的常数示例可以通过连续赋值描述组合电路的功能,基本语法是:assign[signalname]=[expression];

assign是VerilogHDL语言的关键词;[signalname]的数据类型必须是wire类型;[expression]的数据类型没有限制,它是由操作数和操作符变量构成的表达式或者是函数。操作数类型可以是前面定义的任意数据类型。操作数可以是常数、整数、实数,线网、寄存器、向量或者向量的一位或者多位。7.4连续赋值在连续赋值语句中,等式右边表达式中的任何一个信号变量发生变化,表达式都要计算,例如:

wire[9:0]addr;

wire[31:0]bus;

wireout;

wire[7:0]result;

assignaddr=10′b0000_0010_11;

//表达式是常数

assigndata=bus^32′h0000_ffff;

//变量、常数和运算符构成的表达式

assignout=in1|in2;

//变量、操作符和变量构成的表达式

assignresult=mult(a,b);

//表达式是函数值模块实例化是创建模块的另外一个应用实例。通过模块的实例化可以构建更复杂的设计模块或者系统。

实例化的一般语法形式:

[module_name][instance_name]

(.[port_name1]([signal_name1]),

.[port_name2]([signal_name2]),

.[port_nameN]([signal_nameN]));7.5模块实例化

【例7-3】

一个两位的比较器可以通过两个一位比较器按照图7-6所示的连接方式实现。

一位/两位的比较器verilog的描述方式有如下两种。

第1种:

moduleeq_1bit(i0,i1,eq);

inputi0,i1;

outputeq;

wirep0,p1,eq;

assignp0=~i0&~i1;

assignp1=i0&i1;

assigneq=p0|p1;

endmodule

图7-6一个两位比较器第2种:

moduleeq_2bit(a,b,a_eq_b);

input[1:0]a,b;

outputa_eq_b;

wiree0,e1,a_eq_b;

eq_1biteq_bit0_unit(.i0(a[0]),.i1(b[0]),.eq(e0));

//实例化模块eq_1bit

eq_1biteq_bit1_unit(.i0(a[1]),.i1(b[1]),.eq(e1));

//实例化模块eq_1bit

assigna_eq_b=e0&e1;

endmodule

在第2种描述方式中,句子

eq_1biteq_bit0_unit(.i0(a[0]),.i1(b[0]),.eq(e0))

表示实例化eq_1bit模块,eq_bit0_unit是eq_1bit模块的一个实例,(.i0(a[0]),.i1(b[0]),.eq(e0))表示实例eq_bit0_unit的输入/输出信号在本模块中和其它模块之间的信号连接关系。

在本例中,实例eq_bit0_unit是一个一位比较器,其输

入端i0、i1在本模块中连接到信号a[0]和b[0],其输出端连接到e0上。注意“.”不可缺少。一个设计完成后,必须要确认设计是否满足设计者所要求的功能。一种方法就是利用EDA软件,在计算机上通过仿真确认所完成电路是否正确,我们称之为电路验证。

为了验证一个电路是否正确,需要对所设计的电路提供输入激励,然后通过软件观察输出是否正确。因此,除了设计模块之外,还需要编写激励模块,我们把这种模块称为testbench模块。下面介绍如何编写一个设计的testbench模块。7.6验证设计在testbench模块中,实例化设计模块,按照模块的功能编写模块的输入激励,检查和显示输出信息。以两输入的比较器为例,图7-7给出了testbench模块tb_eq_2bit和被仿真模块eq_2bit之间的关系。从图中可以看出,仿真模块tb_eq_2bit本身没有输入和输出,包含了模块eq_2bit(设计模块)的一个实例eq_2bit_inst。tb_eq_2bit内产生eq_2bit_inst实例所需要的输入激励a、b,检查和显示其输出信号a_eq_b。图7-7测试模块和设计模块之间的关系

【例7-4】

编写eq_2bit的测试模块。

moduletb_eq_2bit;

//两位比较器电路的testbench模块名

reg[1:0]a,b;

//比较器输入信号

wirea_eq_b;

//比较器输出信号

initial

begin

//给出所有可能的输入值

a=2′b00;b=2′b00;//初始时刻,给a,b赋值

#10a=2′b00;b=2′b01;//10个时间单位后,给a,b赋不同的值

#10a=2′b00;b=2′b10;//20个时间单位后,给a,b赋不同的值

#10a=2′b00;b=2′b11;//30个时间单位后,给a,b赋不同的值

#10a=2′b01;b=2′b00;//40个时间单位后,给a,b赋不同的值

#10a=2′b01;b=2′b01;//50个时间单位后,给a,b赋不同的值

#10a=2′b01;b=2′b10;//60个时间单位后,给a,b赋不同的值

#10a=2′b01;b=2′b11;//70个时间单位后,给a,b赋不同的值

#10a=2′b10;b=2′b00;//80个时间单位后,给a,b赋不同的值

#10a=2′b10;b=2′b01;//90个时间单位后,给a,b赋不同的值

#10a=2′b10;b=2′b10;//100个时间单位后,给a,b赋不同的值

#10a=2′b10;b=2′b11;//110个时间单位后,给a,b赋不同的值

#10a=2′b11;b=2′b00;//120个时间单位后,给a,b赋不同的值

#10a=2′b11;b=2′b01;//130个时间单位后,给a,b赋不同的值

#10a=2′b11;b=2′b10;//140个时间单位后,给a,b赋不同的值

#10a=2′b11;b=2′b11;//150个时间单元后,给a,b赋不同的值

end

eq_2biteq_2bit_inst(.a(a),.b(b),.a_eq_b(a_eq_b));

//两位比较器实例化

endmodule在上面的例子中,initialbegin...end是一个过程块,下节将详细介绍。#10表示10个时间单位的延时。该过程块给出了随时间变化的输入值,完整的仿真eq_2bit设计需要输入a、b的全部组合,一共是16种,结果如图7-8所示。图7-8两位比较器完整测试波形图上面的例子介绍了如何建立一个验证程序。验证程序也是一个module,但是它没有任何的输入和输出。在验证程序中,需要实例化被验证的设计;定义设计所需要的输入和输出;为输入加载输入激励。在Verilog语言中包含了10类操作符、24种运算符。这些运算符对应着简单的器件,比如加法器和比较器。表7-3说明了VerilogHDL中的运算符,表7-4给出了运算符的优先级。7.7运算符(operator)表7-3VerilogHDL中的运算符表7-4运算符优先级

1.算术运算符

在Verilog中,算术运算符共有六个:“+”、“-”、“*”、“/”、“%”和“**”,分别代表加、减、乘、除、

取模和指数运算。“+”和“-”也可以作为一元运算符,例如:-a。

在算术运算操作中,“+”和“-”运算符可以用EDA工具直接综合成加法器和减法器。乘法是一个复杂的操作,对于乘法运算符“*”的综合取决于综合软件以及目标器件的工艺。除法运算符“/”、求模运算符“%”和指数运算符“**”在通常情况下是不可综合的。

2.移位运算符

移位运算符有四个:“<<”、“>>”、“<<<”、“>>>”,分别表示逻辑左移、逻辑右移、算术左移、算术右移。这组运算符是将一个向量向左或者向右移动给定的位数,如果是逻辑移位,则用0填充在向量的高位(左移)或者低位(右移)。算术左移高位用低位填充,最低位用0来填充,符号位移出后,则丢弃,这样,算术左移和逻辑左移的运算结果一样。而算术右移低位用高位填充,把数的最高位看成符号位,用符号位填充高位,最低位移出后丢弃。例如:a=8′b0100_1111,b=8′b1100_1111,c=8′b10100011,有

x=a>>2;//x=0001_0011

y=b>>>2;

//y=1111_0011

z=c>>>3;

//z=1111_0100

算术右移将b的最高位看成符号位,用符号位填充;z=a<<2;

//z=0011_1100;

w=b<<<2;

//w=0011_1100

3.关系运算符

“>”、“<”、“>=”、“<=”是双目比较运算,返回结果是布尔值,如果运算的结果是假的(false),则返回值是0;如果运算的结果是真的(true),则返回值是1。

“==”、“!=”、“===”、“!==”是等式运算符,返回值是真、假或x。“===”和“!==”常在case语句中表示相等或者不等,操作数可以为“x”和“z”,这两个运算符是不可综合的,“==”和“!=”是可综合的。相等关系运

算的描述见表7-5。表7-5相等关系运算

【例7-5】A=4′b0100,B=4′b0011;x=4′b1010,y=4′b1101;z=4′b1xxz,m=4′b1xxz,n=4′b1xxx。

(1)A==B,运算结果为0;

(2)x!=y,运算结果为1;

(3)z=x,运算结果是x;

(4)z===m,运算结果为1;

(5)m!==n,结果为逻辑1;

(6)z===n,结果为0。

4.位运算符、缩减运算符和逻辑运算符

位运算符、缩减运算符和逻辑运算符有几分相似,都是与、或、异或、非的运算。这些运算都可以通过基本逻辑单元实现。

1)位运算符

位运算符包括四个:“&”(与运算),“|”(或运算),“^”(异或运算),“~”(取反运算)。除了“~”是单目运算符外,其它运算都需要两个操作数。取反和异或的运算

可以组合使用,例如:“~^”和“^~”组合成异或非(同或)运算符,两者是等价的。

这些运算符进行运算时按照一位一位运算的,所以称它们为位运算符。例如,有

wire[3:0]a,b,c;

语句

assignc=~(a|b);

等价于:

assignc[3]=~(a[3]|b[3]);

assignc[2]=~(a[2]|b[2]);

assignc[1]=~(a[1]|b[1]);

assignc[0]=~(a[0]|b[0]);

在VerilogHDL语言中的位运算中,对z和x进行相同的处理,表7-6~表7-10给出了位运算的真值表。表7-6“与”运算真值表表7-7“或”运算真值表表7-8“非”运算真值表表7-9“异或”运算真值表表7-10“同或”运算真值表

2)缩减运算符

缩减运算符有三个:“&”、“|”和“^”。它们与位运算符写法相同,但是缩减运算符只有一个操作数。缩减运算符的返回值是1位的二进制数。例如,有

wire[3:0]a;

wirey;

则语句:

assigny=|a;

等价于:

assigny=a[3]|a[2]|a[1]|a[0];

3)逻辑运算符

逻辑运算符有三个:“&&”(逻辑与)、“||“(逻辑或)和”!“(逻辑非)。逻辑运算符与位运算符不同。逻辑运算的特点是:

(1)逻辑运算的结果总是一位的,有三种取值,即1、0或者x。

(2)如果一个操作数不等于0,那么这个数等价逻辑1(真条件);如果操作数等于0,那么等价于逻辑0(假条件);如果一个操作数中包含x或者z,等价为x(不确定)。

(3)逻辑运算的操作数可以是变量或者是表达式。例如:

if((state==idle)||((state==op)&&(count>10)))

【例7-6】

表7-11给出了逻辑运算和位运算的一些例子。由于VerilogHDL中用0和1来代表假和真,所以位运算和逻辑运算在某些条件下可以交换使用。表7-11逻辑运算例子

5.拼接运算符和复制运算符

1)拼接运算符

用{}表示拼接运算,使用这个运算符可以把两个或多个信号的某些位拼接形成一个向量,进行运算操作,多个信号变量用“,”隔开。

wirea1;

wire[3:0]a4;

wire[7:0]b8,c8,d8;

assignb8={a4,a4};

assignc8={a1,a1,a4,2′b00};

assignd8={b8[3:0],c8[3:0]};拼接运算的一个应用是进行信号变量移位或循环操作,举例如下:

wire[7:0]a;

wire[7:0]rot,shl,sha;

assignrot={a[2:0],a[7:3]};

//a向右循环3位

assignshl={3′b000,a[8:3]};//a逻辑右移3位

assignsha={a[8],a[8],a[8],a[8:3]};

//a算术右移3位

2)复制运算符

用{N{}}表示对{}内的数复制N次,形成新的向量。该操作可以用于化简拼接的写法。例如,

{4{2′b01}}

等价于

8′b01010101

又如,

assignsha={3{a[8]},a[8:3]};

//a算术右移3位

等价于:

assignsha={a[8],a[8],a[8],a[8:3]};

//a算术右移3位

6.条件运算符

条件运算符“?:”是三目运算符,其基本格式如下:[signal_name]=[boolean_exp]?[true_exp]:[false_exp];

其中,[boolean_exp]是指布尔表达式,返回值是真(1′b1)或假(1′b0)。如果[boolean_exp]为真,则[signal]为[true_exp]的值,否则为[false_exp]的值。例如,

wire[2:0]max;

assignmax=(a>b)?a:b;该运算用if-else语句表示如下:

if[boolean_exp]

[signal]=[true_exp];

else

[signal]=[false_exp];

条件运算符还可以嵌套,例如:

assignmax=(a>b)?((a>c)?a:c):

((b>c)?b:c);

在综合时,条件运算符会被综合成二选一选择器。

7.表达式的位宽

VerilogHDL程序中的信号,连线和变量通常都具有不同的位宽,在存储中,选择的位数与位宽一致。例如,

wire[7:0]a,b;

assigna=8′b00000000;

assignb=0;上述代码中,第一条assign语句是将8位的二进制数“00000000”赋给a,而第二条assign语句是指将整数0赋给b。在Verilog中整数是32位,即“0000_0000_0000_0000

_0000_0000_0000_0000”。由于b事先声明为8位,故正确地从32位中截取8位的“00000000”。尽管这两条assign语句的结果都是8位的“00000000”,但是我们必须清楚a和b分别是怎样获得的值。例如,

wire[7:0]a,b;

wire[7:0]sum8;

wire[8:0]sum9;

assignsum8=a+b;

assignsum9=a+b;

上述代码中,第一条assign语句中所有的操作数都是8位,等号左边的sum8也是8位的,而附加的进位位被丢弃了。在第二条assign语句中,由于sum9是9位的,所有“a”和“b”也被扩展成9位,而sum9[9]就是进位位的值。

我们同样可以利用拼接运算符来完成上述运算:

assign{c_out,sum8}=a+b;

1.过程块结构

在VerilogHDL语言中有两种结构化过程语句always和initial,它们是行为建模中最基本的两个语句,所有其它的行为语句可以出现在这两个结构化过程中。

always和initial语句不能嵌套。7.8VerilogHDL行为级建模

1)initial语句

initial语句只用于建立行为仿真,不能综合。所有出现在initial中的语句构成一个initial块(initialblock),一个initial块从时间0开始执行,在仿真过程中每个语句只执行一次。如果一个仿真程序中出现了多个initial块,这些块都是在0时刻开始执行的,每个initial块的执行是相互独立的。基本的语法是:

initialbegin

statements_sequence;

end

【例7-7】initial模块示例。

modulestimulus;//验证模块

regx,y,a,b,m;

initial

m=1′b0;//只有一个语句,可以省略begin/endinitialbegin

#5a=1′b1;

//多条语句,需要用begin/end

#25b=1′b0;

end

initialbegin

#10x=1′b0;

#25y=1′b1;

end

initial

#50$finish;

endmodule在上面的例子中有一个延时#delay,表示等待delay个时间单位。其执行的结果如下:

timestatementexecuted

0m=1′b0;

5a=1′b1;

10x=1′b0;

30b=1′b0;

35y=1′b1;

50$finish;

2)always块

always块是Verilog语言中另外一种描述电路行为的语句,它是可综合语句。

所有出现在always中的语句构成一个always块。always块使用行为语句描述电路的功能。always的一般形式为

always@(event_list)begin

statements_sequence

end执行always块的条件是事件列表(event_list)中任何信号发生变化,即只要事件列表中的任何事件发生变化,就重复执行语句序列。如果always块的语句只有一条,可以省略begin/end。statement_sequence是指过程赋值语句序列。过程赋值语句和@(event_list)将在后续小节详细介绍。例如,

regout1;

always@(in1orin2)

//in1或者in2发生变化,执行out1=in1|in2语句

out1=in1|in2;

2.过程赋值语句

过程赋值语句的基本语法:

<delayorevent>lhs=<delayorevent>rhs;

<delayorevent>lhs<=<delayorevent>rhs;

上面两个语句都是过程赋值语句,前者我们称为阻塞赋值(=),而后者称为非阻塞赋值。过程赋值语句的作用是用等式右边的表达式(rhs)更新等式左边的变量值(lhs)。等式左边的变量值的类型有reg、integer、real和time。过程赋值与连续赋值的不同之处是:在连续赋值语句中,等式右边表达式的值连续不断地赋给等式的左边,而在过程赋值语句中,一个变量存储的值保持不变,直到过程赋值语句用不同的值更新该变量。

等式右边的表达式可以是下面的几种形式:

(1)reg、integer、real和time型变量,如a,b,c;

(2)变量的比特选择,如addr[0];

(3)变量的部分选择,如addr[31:16];

(4)上述类型的连接形式,如{4′h0,addr[15:0],a}。

1)阻塞赋值语句

用“=”进行赋值的语句是阻塞赋值语句。阻塞赋值可以按照它们在过程块中的次序顺序执行,在它执行结束之前,将阻止后续语句的执行

【例7-8】

下面的表达式形式都是合法的:

rega,b,c;

reg

[15:0]data;

integercounter;

initial

begin

a=1′b0;b=1′b1;c=1′b1;

//在0时刻,将3′b011分别赋给a,b,c;counter=0;//在0时刻,counter赋初值0;

#10data[1]=a;//在第10个时间单位,将a赋给data[1];data[7:2]=6′b00_1011;

//将二进制数001011赋给data[7:2];#20counter=counter+1;//在第30个时间单位,将counter加1{a,b,c}=counter[2:0];

//在第30个时间单位,将counter的低三位分别赋给a,b,c

end

注意:如果等式右边的数据宽度大于等式左边变量的宽度,那么抛弃高位,而保留低位。如果等式右边的数据宽度小于等式左边变量的宽度,则高位用0填充。

2)非阻塞赋值语句

一条非阻塞赋值语句不阻止后续语句的执行。

【例7-9】

阻塞与非阻塞赋值语句的执行情况。如下两段代码分别为阻塞和非阻塞赋值语句。阻塞赋值:

regA,B;

initialbegin

A=#101′b1;

B=#51′b0;

A=#201′b0;

B=#151′b1;

end非阻塞赋值:

regA,B

initialbegin

A<=#101′b1;

B<=#51′b0;

A<=#201′b0;

B<=#151′b1;

end我们来考察阻塞语句与非阻塞语句的执行情况,在本例中的时间单位是ns,如图7-9所示,图(a)是阻塞赋值结果,图(b)是非阻塞赋值结果。初始,A、B值均为高阻态。对于非阻塞语句描述,10ns执行语句A=1′b1;在该句执行结束后的5s,即第15ns执行B=1′b0;而此后的20ns,即到35ns前,A一直为1;35ns时执行语句A=1′b1;在50ns的时候执行B=1′b1。可以看出阻塞语句一定是本句执行结束后,才允许运行其它语句的执行。在一个过程块中,它们是顺序执行的。图7-9阻塞赋值和非阻塞赋值的输出波形而对于非阻塞语句,它的执行并不阻塞后续语句的执行。因此,调度器在5ns时候执行B=0;在10ns的时候执行A=1;在15ns的时候执行B=1,而在20ns的时候执行A=0,如图7-9(b)所示。

为了进一步看清楚阻塞语句与非阻塞语句的区别,我们再举一个例子。【例7-10】

阻塞语句与非阻塞语句的综合。

阻塞赋值:

moduledff(clk,din,dout);

inputdin,clk;

outputdout;

rega,dout;

always@(posedge

clk)

begin

a=din;

dout=a;

end

endmodule

非阻塞赋值:

moduledff(clk,din,dout);

inputdin,clk;

outputdout;

rega,dout;

always@(posedge

clk)

begin

a<=din;

dout<=a;

end

endmodule

在阻塞赋值代码中,由于a=din在瞬间完成后,执行dout=a,然后在时钟的上升沿到达的时候,更新等式左边的值。因此,从仿真结果看,a和dout的值相同,其综合的结果就是一个D触发器。而非阻塞赋值代码的综合结果是一个两位的移位寄存器,两个等式右边的表达式按照当前期的值计算出结果,然后保存在临时变量中,在时钟上升沿到达时,同时更新表达式的左边。这样,寄存器dout的结果是上一周

期a的结果,而a则是上一个周期din的结果。图7-10给出了阻塞赋值与非阻塞赋值的综合结果。图7-10阻塞赋值和非阻塞赋值综合结果

3.时序控制

时序控制用于说明过程语句执行的仿真时间。有三种基本的时间控制语句:基于延时控制的时序控制、基于事件的时序控制和电平敏感的时序控制。

1)基于延时的时序控制

表达式中基于延时的时序控制说明了仿真器遇到语句和执行语句之间的间隔。

在Verilog

语言中用“#delay”说明延时。在过程赋值语句中,主要的两类延时控制是:

(1)正则延时(regulardelay):在过程表达式的左边说明非零延时。如

#delaylhs=rhs;

等待delay时间后将rhs的值赋给lhs。

(2)内定延时(intradelay):在赋值语句的右边说明延时。

#delay1lhs=#delay2rhs;

仿真器执行的过程如下:

●等待delay1;

●计算等式的右边rhs;

●等待delay2后,将rhs的值赋给lhs。

【例7-11】

时间赋值语句举例,其延时时序图如图7-11所示。

moduletest

parameterlatency=10,delta=2;

reg

[10:0]a,b;

regw1,w2;

integeri1,i2,delay3;

initialbegin

w1=1′b0;w2=1′b1;delay3=10;

图7-11例7-25延时时序图#delay3a=-23;

//使用标识符控制

#5i1=10;//在5时间单位后将10赋给整型变量i1

#latencyb=a+10′d15;

//用参数标识符(parameter_identifer)延时控制

i1=#(latency+delta)a&b+{w1,w2};//使用表达式控制

#10i2=#10i1-(b+a);

end

endmodule

我们将上述代码做一定的修改,加深对延时语义的理解。timescale1ns/1ps

//{{Sectionbelowthiscommentisautomaticallymaintained

//andmaybeoverwritten

//{module{test1}}

moduletest;

parameterlatency=10,delta=2;

reg

[10:0]a,b;

reg

[10:0]c,d;

regw1,w2; integeri1,i2,i3,i4,delay3;

initialbegin

w1=1′b0;

w2=1′b1;

delay3=10;

#delay3a=-23;//使用标识符控制

#5i1=10;i3=10;i4=10;

//在5时间单位后将10赋给整型变量i1

#latencyb=a+10′d15;d=a+10′d15;

//用参数标识符(parameter_identifer)延时控制i1<=#(latency+delta)a&b+{w1,w2};//使用表达式控制

i3<=#(latency+delta)c&d+{w1,w2};//使用表达式控制

#10i2=#10i1-(b+a);

end

initial

begin

#10;

#5;

#latency;

#(latency+delta)i4<=c&d+{w1,w2};

end initial

begin

#10c=-23;

#5;

#(latency+1)c=21;

#(latency+delta+9)c=20;

end

endmodule

【例7-12】

在上述代码中,i1、i3、i4在不同时刻的值分别是多少?说明i3<=#(latency+delta)c&d+{w1,w2};与#(latency+delta)i4<=c&d+{w1,w2};在赋值上的区别。

图7-12所示时序图能够说明问题,由图中可以看出,i3和i1是一样的,i4和i3的区别在于:

i3<=#(latency+delta)c&d+{w1,w2};

#(latency+delta)i4<=c&d+{w1,w2};这两种表达式产生了不同的结果,前者是先计算表达式的值,然后等待#(latency+delta)之后,将计算的结果赋予i3。而后者是等待#(latency+delta)之后,计算表达式的值并赋予i4。图7-12延时时序图

2)基于事件的时序控制

事件是指线网或者寄存器型的变量值的变化。事件可以用于触发一个语句的执行,也可以触发一个块语句的执行。用@符号说明事件控制,用posedge和negedge关键词说明一个信号的上升沿或下降沿的跳变。事件控制的一般形式如下:@(事件列表)事件列表可以为多种形式:

(1)单一事件或者是“posedge/negedge信号”:表示在信号的正沿/负沿发生时,触发事件。例如,

@(clock)q1=d;

//无论何时信号clock发生了变化,执行这个语句

@(negedgeclock)q2=d;//当clock下降沿时,执行这个语句

q3=@(posedgeclock)d+1;

//计算d+1,当时钟上升沿到来时,用d+1的值更新q3@(aorb)c=a^b;//a或者b发生变化时,用a^b更新c

(2)事件的组合:当有多个事件控制一个过程块或者语句的时候,可以写成“event1orevent2or…or

eventN”,也可以写成“event1,event2,…,eventN”。例如,

always@(aorborc)begin

sum=a+b;

c=a&b;

end

always@(a,b,c)begin

sum=a+b;

c=a&b;

end当组合电路中的输入变量过多时,敏感变量表中的变量容易遗落,造成仿真不正确。在VerilogHDL语言中,可以用@*或者@(*)代替@(event_list),@*或者@(*)包含always过程块中所有等式右边出现的变量,这种写法不容易出错。当写组合电路时,推荐用@*或者@(*)表示事件控制。例如,

always@(*)begin

sum=a+b;

c=a&b;

end

(3)有名称的事件:一个名称事件用关键词event说明,事件的触发用->表示,触发的事件可以用符号@来识别。这类事件并不保持任何数据,仅仅用于仿真。限于篇幅关系,本书对这类事件不予介绍,有兴趣读者可读参考相关文献。

(4)用关键词wait实现电平敏感的时序控制。

例如,

wait(count_able)#10count=count+1;

当count_able为高电平10s后,执行count=count+1,如果count_able为1,那么每隔10个时间单位,count计数器加1直到count_able变成0。

注意:(3)和(4)语句都不可以综合成电路。

4.条件语句

Verilog语言中包含了两种条件语句:If条件语句和Case语句。

1)If条件语句

If是一种具有优先级的条件分支结构。它有三种格式:格式1:

If(<expression>)statement_sequence;

如果expression为真,则执行语句序列(statement_sequence)。

格式2:

If(<expression>)statement_sequence1

elsestatement_sequence2

如果表达式成立,则执行序列1;不成立,则执行语句序列2。格式3:

If(<expression1>)

statement_sequence1

elseif(<expression2>)

statement_sequence2

elseif

else

statement_sequence_default

这是嵌套If条件语句格式,当expression1为真时,执行statement_sequence1;不为真时,计算expression2是否为真。如果所有的表达式都不为真,则执行statement_sequence_default。

【例7-13】

用If语句设计一个三选一的选择器。

always@(*)begin

if(sela)q=a;

elseif(selb)q=b;

elseq=c;

end

【例7-14】

用If语句设计一个两位的比较器。

modulecomparator(a,b,eq,gt,lt);

input[1:0]a,b;

outputgt,lt,eq;

reg

gt,lt,eq;

always@(*)

begin

gt=1′b0;//gt默认的值

eq=1′b0;

//eq默认的值

lt=1′b0;//lt默认的值

if(a>b)

gt=1′b1;

elseif(a==b)

eq=1′b1;

else

lt=1′b1;

end

endmodule

2)case语句

case语句的语法形式为

case(<expression>)

<condition1>:statement_sequence1

<condition2>:sequenceofstatement(s)

default:statement_sequence1

endcase

case语句是一种没有优先级的多路分支结构。<expression>按照它们在case语句中的次序匹配不同的条件<conditon1>,<condition2>,…,<conditionN>,如果条件满

足,则执行的相应的语句序列;如果条件不满足,则执行default后面的语句序列。

case语句的行为类似于一个多路选择器。

【例7-15】

用case语句实现一个四选一电路。

modulemux4_to_1(i0,i1,i2,i3,sel,out);

inputi0,i1,i2,i3;

input[1:0]sel;

outputout;regout;

always@(*)

case(sel)

2′d0:out=i0;

2′d1:out=i1;

2′d2:out=i2;

default:out=i3;

endcase

endmodule

除此之外,如果case语句中的条件(condition)包含了′z′和′x′,那么我们需要用case语句的另外两种形式:casez和casex。在casex中把所有的x和z当成无关项;而在casez中把所有的z当成无关项,′z′也可以用′?′代替。在casez和casex语句中只把表达式和条件中的非-z和非-x位置的值进行比较。

【例7-16】casex和casez的使用。

casex(encoder)

4′b1xxx:high_lvl=3;

4′b01xx:high_lvl=2;

4′b001x:high_lvl=1;

4′b0001:high_lvl=0;

default:high_lvl=0;endcase

casez(encoder)

4′b1???:high_lvl=3;

4′b01??:high_lvl=2;

4′b001?:high_lvl=1;

4′b0001:high_lvl=0;

default:high_lvl=0;

endcase

上面的代码完成一个编码器。

5.循环语句

在Verilog语言中还提供了四种循环机制来描述电路的行为:While循环、Repeat循环、For循环以及Forever循环。循环的句法形式与C语言非常相似,它们只能用于initial块和always块中。

1)While循环

While语句的语法形式为

While(<expression>)

statement_sequence当<expressio

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论