打印

跟我一起学习Verilog HDL语言

[复制链接]
7479|37
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 山东电子小菜鸟 于 2017-9-27 18:51 编辑

            先说下Verilog hal 能干啥?
          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级的模型设计。

相关帖子

沙发
gujiamao12345| | 2017-9-30 23:20 | 只看该作者
未完待续

使用特权

评论回复
板凳
山东电子小菜鸟|  楼主 | 2017-10-1 17:45 | 只看该作者

哪个板块的

使用特权

评论回复
地板
山东电子小菜鸟|  楼主 | 2017-10-1 17:58 | 只看该作者
本帖最后由 山东电子小菜鸟 于 2017-10-1 18:00 编辑

下面先介绍几个简单的Verilog HDL程序,然后从中分析Verilog HDL程序的特性。
例[1.1.1]:
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 声明语句里的。

例[1.1.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为高电平,否则为低电平。
在这个程序中,/*........*/和//.........表示注释部分,
注释只是为了方便程序员理解程序,对编译是不起作用的。

例[1.1.3]:
module  trist2(out,in,enable);
output  out;
input   in, enable;
bufif1  mybuf(out,in,enable);
endmodule

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

例[1.1.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程序的任何部分作注释。一个好的,
            有使用价值的源程序都应当加上必要的注释,以增强程序的可读性和可维护性。

使用特权

评论回复
5
gujiamao12345| | 2017-10-1 22:06 | 只看该作者
山东电子小菜鸟 发表于 2017-10-1 17:45
哪个板块的

新手园地

使用特权

评论回复
6
山东电子小菜鸟|  楼主 | 2017-10-10 10:45 | 只看该作者
模块的结构

Verilog的基本设计单元是“模块(block)。一个模块是由两部分组成的,一部分描述接口,另一部分描述逻辑功能,即定义输入是如何影响输出的。下面举例说明:
请看上面的例子,程序模块旁边有一个电路图的符号。在许多方面,程序模块和电路图符号是一致的
,这是因为电路图符号的引脚也就是程序模块的接口。而程序模块描述了电路图符号所实现的逻辑功能。
上面的Verilog设计中,模块中的第二、第三行说明接口的信号流向,第四、第五行说明了模块的逻辑功能。
以上就是设计一个简单的Verilog程序模块所需的全部内容。

从上面的例子可以看出,Verilog结构完全嵌在module和endmodule声明语句之间,
每个Verilog程序包括四个主要部分:
端口定义、I/O说明、内部信号声明、功能定义。

使用特权

评论回复
7
山东电子小菜鸟|  楼主 | 2017-10-11 20:40 | 只看该作者
3.1.3.模块的端口定义
模块的端口声明了模块的输入输出口。其格式如下:
module    模块名(口1,口2,口3,口4, ………);
3.1.4.模块内容
模块的内容包括I/O说明、内部信号声明、功能定义。
Ÿ        I/O说明的格式如下:
输入口:    input 端口名1,端口名2,………,端口名i;   //(共有i个输入口)
输出口:    output 端口名1,端口名2,………,端口名j;   //(共有j个输出口)
I/O说明也可以写在端口声明语句里。其格式如下:
module  module_name(input port1,input port2,
output port1,output port2 );
Ÿ        内部信号说明:在模块内用到的和与端口有关的wire 和 reg 变量的声明。
如: reg [width-1 : 0] R变量1,R变量2 。。。。;
    wire [width-1 : 0] W变量1,W变量2 。。。。;
    ………..
Ÿ        功能定义: 模块中最重要的部分是逻辑功能定义部分。有三种方法可在模块中产生逻辑。
1).用“assign”声明语句
如: assign  a = b & c;
这种方法的句法很简单,只需写一个“assign”,后面再加一个方程式即可。例子中的方程式描述了一个有两个输入的与门
2).用实例元件
如: and  and_inst( q, a, b );
采用实例元件的方法象在电路图输入方式下,调入库元件一样。键入元件的名字和相连的引脚即可,表示在设计中用到一个跟与门(and)一样的名为and_inst的与门,其输入端为a, b,输出为q。要求每个实例元件的名字必须是唯一的,以避免与其他调用与门(and)的实例混淆。
3).用“always”块
如:always @(posedge clk or posedge clr)
begin
if(clr)  q <= 0;
else  if(en) q <= d;
end
采用“assign”语句是描述组合逻辑最常用的方法之一。而“always”块既可用于描述组合逻辑也可描述时序逻辑。上面的例子用“always”块生成了一个带有异步清除端的D触发器。“always”块可用很多种描述手段来表达逻辑,例如上例中就用了if...else语句来表达逻辑关系。如按一定的风格来编写“always”块,可以通过综合工具把源代码自动综合成用门级结构表示的组合或时序逻辑电路。
注意:
如果用Verilog模块实现一定的功能,首先应该清楚哪些是同时发生的,哪些是顺序发生的。上面三个例子分别采用了“assign”语句、实例元件和“always”块。这三个例子描述的逻辑功能是同时执行的。也就是说,如果把这三项写到一个VeriIog 模块文件中去,它们的次序不会影响逻辑实现的功能。这三项是同时执行的,也就是并发的。
然而,在“always”模块内,逻辑是按照指定的顺序执行的。“always”块中的语句称为“顺序语句”,因为它们是顺序执行的。请注意,两个或更多的“always”模块也是同时执行的,但是模块内部的语句是顺序执行的。 看一下“always”内的语句,你就会明白它是如何实现功能的。  if..else… if必须顺序执行,否则其功能就没有任何意义。如果else语句在if语句之前执行,功能就会不符合要求!为了能实现上述描述的功能,“always”模块内部的语句将按照书写的顺序执行。
3.2.数据类型及其常量、变量
Verilog HDL中总共有十九种数据类型,数据类型是用来表示数字电路硬件中的数据储存和传送元素的。在本教材中我们先只介绍四个最基本的数据类型,它们是:
reg型、wire型、integer型、parameter型
其它数据类型在后面的章节里逐步介绍,同学们也可以查阅附录中Verilog HDL语法参考书的有关章节逐步掌握。其它的类型如下:
large型、medium型、scalared型、time型、small型、tri型、trio型、tri1型、triand型、trior型、trireg型、vectored型、wand型、wor型。这些数据类型除time型外都与基本逻辑单元建库有关,与系统设计没有很大的关系。在一般电路设计自动化的环境下,仿真用的基本部件库是由半导体厂家和EDA工具厂家共同提供的。系统设计工程师不必过多地关心门级和开关级的VerilogHDL语法现象。
Verilog HDL语言中也有常量和变量之分。它们分别属于以上这些类型。下面就最常用的几种进行介绍。

使用特权

评论回复
8
山东电子小菜鸟|  楼主 | 2017-10-15 12:46 | 只看该作者
3.2.1.常量
在程序运行过程中,其值不能被改变的量称为常量。下面首先对在Verilog HDL语言中使用的数字及其表示方式进行介绍。
一.数字
Ÿ        整数:
在Verilog HDL中,整型常量即整常数有以下四种进制表示形式:
1)      二进制整数(b或B)
2)      十进制整数(d或D)
3)      十六进制整数(h或H)
4)      八进制整数(o或O)
数字表达方式有以下三种:
1)      <位宽><进制><数字>这是一种全面的描述方式。
2)      <进制><数字>在这种描述方式中,数字的位宽采用缺省位宽(这由具体的机器系统决定,但至少32位)。
3)      <数字>在这种描述方式中,采用缺省进制十进制。
在表达式中,位宽指明了数字的精确位数。例如:一个4位二进制数的数字的位宽为4,一个4位十六进制数的数字的位宽为16
(因为每单个十六进制数就要用4位二进制数来表示)。见下例:
8'b10101100  //位宽为8的数的二进制表示, 'b表示二进制
8'ha2        //位宽为8的数的十六进制,'h表示十六进制。
Ÿ        x和z值:
在数字电路中,x代表不定值,z代表高阻值。一个x可以用来定义十六进制数的四位二进制数的状态,八进制数的三位,二进制数的一位。
z的表示方式同x类似。z还有一种表达方式是可以写作?。在使用case表达式时建议使用这种写法,以提高程序的可读性。见下例:
4'b10x0  //位宽为4的二进制数从低位数起第二位为不定值
4'b101z  //位宽为4的二进制数从低位数起第一位为高阻值
12'dz    //位宽为12的十进制数其值为高阻值(第一种表达方式)
12'd?    //位宽为12的十进制数其值为高阻值(第二种表达方式)
8'h4x    //位宽为8的十六进制数其低四位值为不定值
Ÿ        负数:
一个数字可以被定义为负数,只需在位宽表达式前加一个减号,减号必须写在数字定义表达式的最前面。
注意减号不可以放在位宽和进制之间也不可以放在进制和具体的数之间。见下例:
-8'd5   //这个表达式代表5的补数(用八位二进制数表示)
8'd-5   //非法格式
Ÿ        下划线(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

使用特权

评论回复
9
山东电子小菜鸟|  楼主 | 2017-10-15 12:48 | 只看该作者
二.参数(Parameter)型
在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;           //用常数表达式赋值
参数型常数经常用于定义延迟时间和变量宽度。在模块或实例引用时可通过参数传递改变在被引用模块或实例
中已定义的参数。下面将通过两个例子进一步说明在层次调用的电路中改变参数常用的一些用法。
[例1]:在引用Decode实例时,D1,D2的Width将采用不同的值4和5,且D1的Polarity将为0。可用
例子中所用的方法来改变参数,即用 #(4,0)向D1中传递 Width=4,Polarity=0;用#(5)向D2中传递Width=5,Polarity仍为1。
module Decode(A,F);
parameter  Width=1, Polarity=1;
……………
endmodule
module  Top;
wire[3:0] A4;
wire[4:0] A5;
wire[15:0] F16;
wire[31:0] F32;
Decode  #(4,0)  D1(A4,F16);
Decode  #(5)    D2(A5,F32);
Endmodule
[例2]:下面是一个多层次模块构成的电路,在一个模块中改变另一个模块的参数时,需要使用defparam命令
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image002.gif





使用特权

评论回复
10
山东电子小菜鸟|  楼主 | 2017-10-15 12:51 | 只看该作者
3.2.2 变量
变量即在程序运行过程中其值可以改变的量,在Verilog HDL中变量的数据类型有很多种,
这里只对常用的几种进行介绍。
网络数据类型表示结构实体(例如门)之间的物理连接。网络类型的变量不能储存值,而且
它必需受到驱动器(例如门或连续赋值语句,assign)的驱动。如果没有驱动器连接到网络类
型的变量上,则该变量就是高阻的,即其值为z。常用的网络数据类型包括wire型和tri型。
这两种变量都是用于连接器件单元,它们具有相同的语法格式和功能。之所以提供这两种名
字来表达相同的概念是为了与模型中所使用的变量的实际情况相一致。wire型变量通常是用
来表示单个门驱动或连续赋值语句驱动的网络型数据,tri型变量则用来表示多驱动器驱动
的网络型数据。如果wire型或tri型变量没有定义逻辑强度(logicstrength),在多驱动源
的情况下,逻辑值会发生冲突从而产生不确定值。下表为wire型和tri型变量的真值表(注意
:这里假设两个驱动源的强度是一致的,关于逻辑强度建模请参阅附录:Verilog语言参考书)。
  
wire/tri
  
0
1
x
z
0
0
x
x
0
1
x
1
x
1
x
x
x
x
x
z
0
1
x
z
一. 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型数据,其赋值语句的作用就象改变一组触发器的存储单元的值。在Verilog中有许多构造(construct)
用来控制何时或是否执行这些赋值语句。这些控制构造可用来描述硬件触发器的各种具体情况,如触发条件用
时钟的上升沿等,或用来描述具体判断逻辑的细节,如各种多路选择器。reg型数据的缺省初始值是不定值。
reg型数据可以赋正值,也可以赋负值。但当一个reg型数据是一个表达式中的操作数时,它的值被当作是无符
号值,即正值。例如:当一个四位的寄存器用作表达式中的操作数时,如果开始寄存器被赋以值-1,则在表达式
中进行运算时,其值被认为是+15。
注意:
reg型只表示被定义的信号将用在always块内,理解这一点很重要。并不是说reg型信号一定是寄存器或触发器的输出。虽然reg型信号常常是寄存器或触发器的输出,但并不一定总是这样。


使用特权

评论回复
11
山东电子小菜鸟|  楼主 | 2017-10-15 12:52 | 只看该作者
三. memory型
Verilog HDL通过对reg型变量建立数组来对存储器建模,可以描述RAM型存储器,
ROM存储器和reg文件。数组中的每一个单元通过一个数组索引进行寻址。
在Verilog语言中没有多维数组存在。 memory型数据是通过扩展reg型数据
的地址范围来生成的。其格式如下:
reg [n-1:0] 存储器名[m-1:0];
或  reg [n-1:0] 存储器名[m:1];
在这里,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的地址索引。本小节里只对以上几
种常用的数据类型和常数进行了介绍,其余的在以后的章节的示例中用到之处再逐一介绍。有兴趣的
同学可以参阅附录:

使用特权

评论回复
12
山东电子小菜鸟|  楼主 | 2017-10-15 12:56 | 只看该作者
3.3. 运算符及表达式
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是操作数。
下面对常用的几种运算符进行介绍。
3.3.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。
3.3.2.位运算符
Verilog HDL作为一种硬件描述语言,是针对硬件电路而言的。在硬件电路中信号有四种状态值1,0,x,z.
在电路中信号进行与或非时,反映在Verilog HDL中则是相应的操作数的位运算。Verilog HDL提供了以下五种位运算符:
1)      ~           //取反
2)      &           //按位与
3)      |           //按位或
4)      ^           //按位异或
5)      ^~          //按位同或(异或非)
说明:
·        位运算符中除了~是单目运算符以外,均为二目运算符,即要求运算符两侧各有一个操作数.
·        位运算符中的二目运算符要求对两个操作数的相应位进行运算操作。
下面对各运算符分别进行介绍:
1)      "取反"运算符~
~是一个单目运算符,用来对一个操作数进行按位取反运算。
其运算规则见下表:
  
~
  
1
0
0
1
x
x
举例说明:
rega='b1010;//rega的初值为'b1010
rega=~rega;//rega的值进行取反运算后变为'b0101
2)      "按位与"运算符&
按位与运算就是将两个操作数的相应位进行与运算,
其运算规则见下表:
  
&
  
0
1
x
0
0
0
0
1
0
1
x
x
0
x
x
3)      "按位或"运算符|
按位或运算就是将两个操作数的相应位进行或运算。
其运算规则见下表:
  
|
  
0
1
x
0
0
1
x
1
1
1
1
x
x
1
x
4)      "按位异或"运算符^(也称之为XOR运算符)
按位异或运算就是将两个操作数的相应位进行异或运算。
其运算规则见下表:
  
^
  
0
1
x
0
0
1
x
1
1
0
x
x
x
x
x
5)      "按位同或"运算符^~
按位同或运算就是将两个操作数的相应位先进行异或运算再进行非运算.
其运算规则见下表:
  
^~
  
0
1
x
0
1
0
x
1
0
1
x
x
x
x
x
6)      不同长度的数据进行位运算
两个长度不同的数据进行位运算时,系统会自动的将两者按右端对齐.位数少的操作数
会在相应的高位用0填满,以使两个操作数按位进行操作.
3.3.3 逻辑运算符
在Verilog HDL语言中存在三种逻辑运算符:
1)      && 逻辑与
2)      || 逻辑或
3)      !  逻辑非
"&&"和"||"是二目运算符,它要求有两个操作数,如(a>b)&&(b>c),(a<b)||(b<c)。"!"是单目运算符,
只要求一个操作数,如!(a>b)。下表为逻辑运算的真值表。它表示当a和b的值为不同的组合时,各种逻辑运算所得到的值。
  
a
  
b
!a
!b
a&&b
a||b
逻辑运算符中"&&"和"||"的优先级别低于关系运算符,"!" 高于算术运算符。见下例:
(a>b)&&(x>y)    可写成: a>b && x>y
(a==b)||(x==y)  可写成:a==b || x==y
(!a)||(a>b) 可写成: !a || a>b
为了提高程序的可读性,明确表达各运算符间的优先关系,建议使用括号.


使用特权

评论回复
13
山东电子小菜鸟|  楼主 | 2017-10-15 12:56 | 只看该作者
3.3.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相比。
3.3.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时,这个语句执行)
3.3.6.移位运算符
在Verilog HDL中有两种移位运算符:
<< (左移位运算符) 和  >>(右移位运算符)。
其使用方法如下:
a >> n  或  a << n
a代表要进行移位的操作数,n代表要移几位。这两种移位运算都用0来填补移出的空位。下面举例说明:
module  shift;
reg [3:0]  start, result;
initial
begin
start  = 1;   //start在初始时刻设为值0001
result = (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;
3.3.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,必须是常数表达式。
3.3.8.缩减运算符(reduction operator)
缩减运算符是单目运算符,也有与或非运算。其与或非运算规则类似于位运算符的与或非运算规则,
但其运算过程不同。位运算是对操作数的相应位进行与或非运算,操作数是几位数则运算结果也是
几位数。而缩减运算则不同,缩减运算是对单个操作数进行或与非递推运算,最后的运算结果是一位
的二进制数。缩减运算的具体运算过程是这样的:第一步先将操作数的第一位与第二位进行或与非
运算,第二步将运算结果与第三位进行或与非运算,依次类推,直至最后一位。
例如:reg [3:0] B;
reg C;
C = &B;
相当于:
C =( (B[0]&B[1]) & B[2] ) & B[3];
由于缩减运算的与、或、非运算规则类似于位运算符与、或、非运算规则,这里不再详细讲述,请参照位
运算符的运算规则介绍。
3.3.9.优先级别
下面对各种运算符的优先级别关系作一总结。见下表:


3.3.10.关键词
在Verilog HDL中,所有的关键词是事先定义好的确认符,用来组织语言结构。
关键词是用小写字母定义的,因此在编写原程序时要注意关键词的书写,以避免出错。
下面是Verilog HDL中使用的关键词(请参阅附录:Verilog语言参考手册):
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 functionhighz0 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程序时,变量的定义不要与这些关键词冲突.

使用特权

评论回复
14
山东电子小菜鸟|  楼主 | 2017-10-15 12:57 | 只看该作者
3.4 赋值语句和块语句
3.4.1 赋值语句
在Verilog HDL语言中,信号有两种赋值方式:
(1).非阻塞(Non_Blocking)赋值方式( 如 b <= a; )
1)      块结束后才完成赋值操作。
2)      b的值并不是立刻就改变的。
3)      这是一种比较常用的赋值方法。(特别在编写可综合模块时)
(2).阻塞(Blocking)赋值方式( 如 b = a; )
1)      赋值语句执行完后,块才结束。
2)      b的值在赋值语句执行完后立刻就改变的。
3)      可能会产生意想不到的结果。
非阻塞赋值方式和阻塞赋值方式的区别常给设计人员带来问题。问题主要是给"always"
块内的reg型信号的赋值方式不易把握。到目前为止,前面所举的例子中的"always"模块
内的reg型信号都是采用下面的这种赋值方式:
b <= a;
这种方式的赋值并不是马上执行的,也就是说"always"块内的下一条语句执行后,b并不等于a,
而是保持原来的值。"always"块结束后,才进行赋值。而另一种赋值方式阻塞赋值方式,如下所示:
b = a;
这种赋值方式是马上执行的。也就是说执行下一条语句时,b已等于a。尽管这种方式看起来很直观,
但是可能引起麻烦。下面举例说明:
[例1]:always @( posedge clk )
begin
b<=a;
c<=b;
end
[例1] 中的"always"块中用了非阻塞赋值方式,定义了两个reg型信号b和c,clk信号的上升
沿到来时,b就等于a,c就等于b,这里应该用到了两个触发器。请注意:赋值是在"always"块
结束后执行的,c应为原来b的值。这个"always"块实际描述的电路功能如下图所示:
[例2]: always @(posedge  clk)
begin
b=a;
c=b;
end
[例2]中的 "always"块用了阻塞赋值方式。clk信号的上升沿到来时,将发生如下的变化:b马上取a的值,c马上取b的值(即等于a),生成的电路图如下所示只用了一个触发器来寄存器a的值,又输出给b和c。这大概不是设计者的初衷,如果采用[例1]所示的非阻塞赋值方式就可以避免这种错误。


关于赋值语句更详细的说明请参阅第七章中深入理解阻塞和非阻塞赋值小节。

使用特权

评论回复
15
山东电子小菜鸟|  楼主 | 2017-10-15 13:11 | 只看该作者
3.4.2 块语句
块语句通常用来将两条或多条语句组合在一起,使其在格式上看更象一条语句。
块语句有两种,一种是begin_end语句,通常用来标识顺序执行的语句,用它来标识的块称为顺序块。
一种是fork_join语句,通常用来标识并行执行的语句,用它来标识的块称为并行块。下面进行详细的介绍。
一.顺序块
顺序块有以下特点:
1)      块内的语句是按顺序执行的,即只有上面一条语句执行完后下面的语句才能执行。
2)      每条语句的延迟时间是相对于前一条语句的仿真时间而言的。
3)      直到最后一条语句执行完,程序流程控制才跳出该语句块。
顺序块的格式如下:
begin
语句1;
语句2;
......
语句n;
end
begin:块名
块内声明语句  
语句1;
语句2;
......
语句n;
end
其中:
Ÿ        块名即该块的名字,一个标识名。其作用后面再详细介绍。
Ÿ        块内声明语句可以是参数声明语句、reg型变量声明语句、integer型变量声明语句、real型变量声明语句。
下面举例说明:
[例1]:begin
areg = breg;
creg = areg;   //creg的值为breg的值。
end
从该例可以看出,第一条赋值语句先执行,areg的值更新为breg的值,然后程序流程控制转到第二条赋值语句,creg的
值更新为areg的值。因为这两条赋值语句之间没有任何延迟时间,creg的值实为breg的值。当然可以在顺序块里延迟控
制时间来分开两个赋值语句的执行时间,见[例2]:
[例2]: begin
areg = breg;
#10 creg = areg;
//在两条赋值语句间延迟10个时间单位。
end
[例3]:parameter d=50;  //声明d是一个参数
reg [7:0]  r;     //声明r是一个8位的寄存器变量
begin             //由一系列延迟产生的波形
#d  r = 'h35;
#d  r = 'hE2;
#d  r = 'h00;
#d  r = 'hF7;
#d  -> end_wave;  //触发事件end_wave
end
这个例子中用顺序块和延迟控制组合来产生一个时序波形。

使用特权

评论回复
16
山东电子小菜鸟|  楼主 | 2017-10-15 13:12 | 只看该作者
二. 并行块
并行块有以下四个特点:
1)      块内语句是同时执行的,即程序流程控制一进入到该并行块,块内语句则开始同时并行地执行。
2)      块内每条语句的延迟时间是相对于程序流程控制进入到块内时的仿真时间的。
3)      延迟时间是用来给赋值语句提供执行时序的。
4)      当按时间时序排序在最后的语句执行完后或一个disable语句执行时,程序流程控制跳出该程序块。
并行块的格式如下:
fork
语句1;
语句2;
.......
语句n;
join
fork:块名
块内声明语句   
语句1;
语句2;
......
语句n;
join
其中:
·        块名即标识该块的一个名字,相当于一个标识符。
·        块内说明语句可以是参数说明语句、reg型变量声明语句、integer型变量声明语句、
real型变量声明语句、time型变量声明语句、事件(event)说明语句。
下面举例说明:
[例4]:fork
#50   r = 'h35;
#100  r = 'hE2;
#150  r = 'h00;
#200  r = 'hF7;
#250    -> end_wave;    //触发事件end_wave.
join
在这个例子中用并行块来替代了前面例子中的顺序块来产生波形,用这两种方法生成的波形是一样的。
三. 块名
在VerilgHDL语言中,可以给每个块取一个名字,只需将名字加在关键词begin或fork后面即可。
这样做的原因有以下几点。
1)      这样可以在块内定义局部变量,即只在块内使用的变量。
2)      这样可以允许块被其它语句调用,如被disable语句。
3)      在Verilog语言里,所有的变量都是静态的,即所有的变量都只有一个唯一的存储地址,
因此进入或跳出块并不影响存储在变量内的值。
基于以上原因,块名就提供了一个在任何仿真时刻确认变量值的方法。
四. 起始时间和结束时间
在并行块和顺序块中都有一个起始时间和结束时间的概念。对于顺序块,起始时间就是第一条语句开始
被执行的时间,结束时间就是最后一条语句执行完的时间。而对于并行块来说,起始时间对于块内所有
的语句是相同的,即程序流程控制进入该块的时间,其结束时间是按时间排序在最后的语句执行完的时间。
当一个块嵌入另一个块时,块的起始时间和结束时间是很重要的。至于跟在块后面的语句只有在该块的结
束时间到了才能开始执行,也就是说,只有该块完全执行完后,后面的语句才可以执行。
在fork_join块内,各条语句不必按顺序给出,因此在并行块里,各条语句在前还是在后是无关紧要的。见下例:
[例5]:fork
#250  -> end_wave;
#200  r = 'hF7;
#150  r = 'h00;
#100  r = 'hE2;
#50   r = 'h35;
join
在这个例子中,各条语句并不是按被执行的先后顺序给出的,但同样可以生成前面例子中的波形。

使用特权

评论回复
17
山东电子小菜鸟|  楼主 | 2017-10-15 13:13 | 只看该作者
3.5.条件语句
3.5.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)
begin
  out1<=int1;
  out2<=int2;
end
else
begin
  out1<=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
else
if(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)
begin
for(scani=0;scani<index;scani=scani+1)
if(memory[scani]>0)
begin
$display("...");
memory[scani]=0;
end
end
else   /*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;
parameter
segment1=0,  inc_seg1=1,
segment2=20, inc_seg2=2,
segment3=64, inc_seg3=4,
data=128;
//检测寄存器index的值
if(index<segment2)
begin
instruction = segment_area[index + modify_seg1];
index = index + inc_seg1;
end
else  if(index<segment3)
begin
instruction = segment_area[index + modify_seg2];
index = index + inc_seg2;
end
else  if (index<data)
begin
instruction = segment_area[index + modify_seg3];
index = index + inc_seg3;
end
else
instruction = segment_area[index];

使用特权

评论回复
18
山东电子小菜鸟|  楼主 | 2017-10-15 13:16 | 只看该作者
3.5.2. case语句
case语句是一种多分支选择语句,if语句只有两个分支可供选择,而实际问题中常常需要用到多
分支选择,Verilog语言提供的case语句直接处理多分支选择。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 isfloating");
1 'bx:    $display("signal isunknown");
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
3.5.3.由于使用条件语句不当在设计中生成了原本没想到有的锁存器
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=0,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程序的可读性。

使用特权

评论回复
19
山东电子小菜鸟|  楼主 | 2017-10-15 13:17 | 只看该作者
3.6.循环语句
在Verilog HDL中存在着四种类型的循环语句,用来控制执行语句的执行次数。
1)    forever     连续的执行语句。
2)    repeat  连续执行一条语句 n 次。
3)    while   执行一条语句直到某个条件不满足。如果一开始条件即不满足(为假),
              则语句一次也不能被执行。
4)    for通过以下三个步骤来决定语句的循环执行。
a)      先给控制循环次数的变量赋初值。
b)      判定控制循环的表达式的值,如为假则跳出循环语句,如为真则执行指定的语句后,转到第三步。
c)      执行一条赋值语句来修正控制循环变量次数的变量的值,然后返回第二步。
下面对各种循环语句详细的进行介绍。
3.6.1.forever语句
forever语句的格式如下:
forever  语句;  或
forever  begin   多条语句 end
forever循环语句常用于产生周期性的波形,用来作为仿真测试信号。它与always语句不同处在于不能
独立写在程序中,而必须写在initial块中。其具体使用方法将在"事件控制"这一小节里详细地加以说明。
3.6.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)
begin
if(shift_opb[1])
result = result + shift_opa;
shift_opa = shift_opa <<1;
shift_opb = shift_opb >>1;
end
end
3.6.3.while语句
while语句的格式如下:
while(表达式) 语句
或用如下格式:
while(表达式) begin  多条语句   end
下面举一个while语句的例子,该例子用while循环语句对rega这个八位二进制数中值为1的位进行计数。
begin:  count1s
reg[7:0] tempreg;
count=0;
tempreg = rega;
while(tempreg)
begin
if(tempreg[0])  count = count + 1;
tempreg = tempreg>>1;
end
end
3.6.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: count1s
reg[7:0] tempreg;
count=0;
for( tempreg=rega; tempreg; tempreg=tempreg>>1 )
if(tempreg[0])  
count=count+1;
end

使用特权

评论回复
20
山东电子小菜鸟|  楼主 | 2017-10-17 14:04 | 只看该作者
占座 更新。。。。。。。。。。。。。。。。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

140

主题

3082

帖子

23

粉丝