赞
踩
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
《Verilog数字系统设计教程》学习笔记:
-Verilog模块的结构、数据类型、变量
Verilog的基本设计单元是“模块”(block)。一个模块是由两部分组成的,一部分描述接口,另一部分描述逻辑功能,即定义输入是如何影响输出的。
module block(a,b,c,d);
input a,b;
output c,d;
assign c=a|b;
assign d=a&b;
endmodule
上面的Verilog设计中,模块中的第二、第三行说明接口的信号流向,第四、第五行说明了模块的逻辑功能。
模块的端口声明了模块的输入输出口。其格式如下:
module 模块名(口1,口2,口3,口4,······)
模块的端口表示的是模块的输入和输出名,也就是说,它与别的模块联系端口的标识在模块被引用时,在引用的模块中,有些信号要输入到被引用的模块中,有的信号需要从被引用的模块中取出来。在引用模块时其端口可以用以下两种方法连接:
模块名(连接端口1信号名,连接端口2信号名,·······)
模块名(.端口1名(连接信号1名),端口2名(连接信号2名),······)
这样表示的好处在于可以用端口名与被引用模块的端口相对应,而不必严格按端口顺序对应,提高了程序的可读性和可移植性。
例如:
···
MyDesignMK M1(.sin(SerialIn),.pout(ParallelOut),······);
···
其中,.sin和.pout都是M1的端口名,而M1则是与MyDesignMK完全一致的模块。MyDesignMK已经在另一个模块中定义过,它有两个端口,即sin和pout。与sin口连接的信号名为SerialIn,与pout端口连接的信号名为ParallelOut。
模块的内容包括I/O说明。内部信号声明和功能定义。
输入口: input [信号位宽-1:0] 端口名1;
input [信号位宽-1:0] 端口名2;
···
input [信号位宽-1:0] 端口名i; //共有i个输入口
输出口: output [信号位宽-1:0] 端口名1;
output [信号位宽-1:0] 端口名2;
···
output [信号位宽-1:0] 端口名j; //共有j个输出口
输入输出口:inout [信号位宽-1:0] 端口名1;
inout [信号位宽-1:0] 端口名2;
···
inout [信号位宽-1:0] 端口名k; //共有k个双向总线端口
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...;
模块中最重要的部分是逻辑功能定义部分。以下三种方法可在模块中产生逻辑。
assign a = b & c; //描述了一个两输入的与门。
and #2 ul(q,a,b);
上述表示设计一个输出延迟2个单位时间的与门,名称为ul,输入端为a、b,输出为q。
每个实例元件的名字必须是唯一的,以避免与其他调用与门(and)的实例混淆。
always @(posedge clk or posedge clr);
begin
if(clr)<=0;
else if(en) q<=d;
end
生成了一个带有异步清除端的D触发器。
如果用Verilog模块实现一定的功能,首先应该清楚哪些是同时发生,哪些是顺序发生的。2.3节的例子描述的逻辑功能是同时执行的。也就是说,如果把这3项写到一个Verilog模块文件中取,它们的顺序不会影响实现的功能。
然而,在“always”模块内,逻辑是按照指定的顺序执行的。“always”块中的语句称为“顺序语句”,因为他们是顺序执行的。所以,“always”块也称“过程块”。不同的“always”块是同时执行的。
注:
1)在Verilog模块中所有过程块(如initial块、always块)、连续赋值语句、实例引用都是并行的;
2)他们表示的是一种通过变量名互相连接的关系;
3)在同一模块中这三者出现的先后秩序没有关系;
4)只有连续赋值语句assign和实例引用语句可以独立于过程块而存在于模块的功能定义部分。
以上4点与C语言有很大不同。许多与C语言类似的语句只能出现在过程块中,而不能随意出现在模块功能定义的范围内。
Verilog HDL中总共有19种数据类型,常见的有reg、wire、integer、parameter,本文主要讲这四种。
此外,还有large、medium、scalared、time、small、tri、trio、tril、triand、trior、trireg、vectored、wand、wor。数据类型是用来表示数字电路硬件中的数据储存和传送元素的。
现按常量和变量划分。
在程序运行过程中,其值不能改变的量称为常量。
4'b10x0 //位宽为4的二进制数从低位数起第2位为不定值
4'b101z //位宽为4的二进制数从低位数起第1位为高阻值
12'dz //位宽为12的十进制数,其值为高阻值(第1种表达方式)
12'd? //位宽为12的十进制数,其值为高阻值(第2种表达方式)
8'h4x //位宽为8的十六进制数,其低4位值为不定值
-8'd5 //5的补数(用八位二进制数表示)
8'd-5 //非法格式
16'b1010_1011_1111_1010 //合法格式
8'b_0011_1110 //非法格式
当常量不说明位数时,默认值是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 //字符串AB,为十六进制数16'h4142
在Verilog HDL中用parameter定义常量,即用parameter来定义一个标识符代表一个常量,称为符号常量,类似C语言中的给参数定值。例子如下:
parameter msb=7; //定义参数msb为常量7
parameter e=1,b=2; //定义两个参数
parameter r=2.1; //声明r为一个实型参数
parameter a=8,b=a-1; //用常数表达式赋值
参数型常数经常用于定义延迟时间和变量宽度。在模块或实例引用时,可通过参数传递改变在被引用模块或实例中已定义的参数。下面通过两个例子进一步说明在层次调用的电路中改变参数常用的一些用法。
【例1】
module Decode(A,F);
parameter Width=1,Polarity=1;
```
endmodule //定义模块Decode,Width和Polarity默认为1
module Top;
wire[3:0] A4;
wire[4:0] A5;
wire[15:0] F16;
wire[31:0] F32;
Decode #(4,0) D1(A4,F16); //实例D1引用Width=4,Polarity=0的Decode模块;
Decode #(5) D2(A5,F32); ///实例D2引用Width=5,Polarity=1的Decode模块;
endmodule
【例2】
'include “Top.v” 'include “Block.v” 'include “Annotate” module Test; wire W; Top T(); endmodule module Top; wire W; Block B1(); Block B2(); endmodule module Block; Parameter P=0; endmodule module Annotate; defparam //在一个模块中改变另一个模块的参数时,需要使用defparam命令 Test.T.B1.P=2, Test.T.B1.P=3; endmodule
以上例子见图1,在模块Annotate中定义的参数值2和3可以通过模块Test中,经实例T对模块Top的引用,而在实例Top中,实例B1和B2对模块Block的引用,分别将参数值2和3传递到模块Block中用参数定义的地方(即原来在模块Block中定义P=0,在实例B1和B2中分别被P=2和P=3代替)。
变量是一种在程序运行过程中其值可以改变的量。
网络数据类型表示结构实体(例如门)之间的物理连接。网络类型的变量不能储存值,而且它必须受到驱动器(例如门或者连续赋值语句,assign)的驱动。如果没有驱动器连接到网络类型的变量上,则该变量就是高阻的,即其值为z。常见的网络数据类型包括wire型和tri型。这两种变量都是用于连接器件单元。wire型变量通常是用来表示单个门驱动或连续赋值语句驱动的网络型数据,tri型变量则用来表示多驱动器驱动的网络型数据。
wire型数据常用来表示用以assign关键字指定的组合逻辑信号。Verilog程序模块中输入、输出信号类型默认时自动定义为wire型。wire型信号可以用做任何方程式的输入,也可以用做“assign”语句或实例元件的输出。
wire型信号的格式同reg型信号的格式很类似。其格式如下:
wire [n-1:0]数据名1,数据名2,...数据名i; //共有i条总线,每条总线内有n条线路
wire是wire型数据的确认符;[n-1:0]代表数据的位宽,即该数据有几位;最后跟着的是数据的名字。如果一次定义多个数据,数据名之间用逗号隔开。声明语句的最后要用分号表示语句结束。
wire a; //定义了一个1位的wire型数据
wire [7:0]b; //定义了一个8位的wire型数据
wire [4:1]c,d; //定义了二个4位的wire型数据
寄存器是数据储存单元的抽象,寄存器数据类型的关键字是reg。通过赋值语句可以改变寄存器储存的值,其作用与改变触发器储存的值相当。reg型数据的默认初始值为不定值x。
reg型数据常用来表示“always”模块内的指定信号,常代表触发器。通常,在设计中要由“always”模块通过使用行为描述语句来表达逻辑关系。在“always”模块内被赋值的每一个信号都必须定义成reg型。
reg型数据的格式如下:
reg [n-1:0] 数据名1,数据名2,...,数据名i;
reg是reg型数据的确认标识符;[n-1:0]代表该数据的位宽,即该数据有几位(bit);最后跟着的是数据的名字。如果一次定义多个数据,数据名之间用逗号隔开。声明语句的最后要用分号表示语句结束。
reg rega; //定义了一个1位的名为rega的reg型数据
reg [3:0] regb; //定义了一个4位的名为regb的reg型数据
reg [4:1] regc,regd; //定义了二个4位的名为regc和regd的reg型数据
对于reg型数据,其赋值语句的作用就如同改变一组触发器的存储单元的值。在Verilog中有许多构造(construct)用来控制何时或是否执行这些赋值语句。这些控制构造可用来描述硬件触发器的各种具体情况,如触发条件时用时钟的上升沿,或用来描述判断逻辑的细节,如各种多路选择器。
reg型数据的默认初始值是不定值。reg型数据可以赋正值,也可以赋负值。但当一个reg型数据是一个表达式中的操作数时,它的值被当做是无符号值,即正值。例如,当一个4位的寄存器用做表达式中的操作数时,如果开始寄存器被赋以值-1,则在表达式中进行运算时,其值被认为是+15。
Verilog HDL通过对reg型变量建立数组来对存储器建模,可以描述RAM型存储器ROM存储器和reg文件。数组中的每一个单元通过一个数组索引进行寻址。在Verilog语言中没有多维数组存在。 memory型数据是通过扩展reg型数据的地址范围来生成的。
如:
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型数据的定义格式很相似,但要注意其不同之处。
reg [n-1:0] rega; //一个n位的寄存器
reg mema [n-1:0]; //一个由n个1位寄存器构成的存储器组
一个n位的寄存器可以在一条赋值语句里进行赋值,而一个完整的存储器则不行。如:
rega=0; //合法赋值语句
mema=0; //非法赋值语句
如果想对memory中的存储单元进行读写操作,必须指定该单元在存储器中的地址。
mema[3]=0; //给memory中的第三个存储单元赋值0
以上是Verilog基础语法的一部分。总结为以下几点:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。