当前位置:   article > 正文

Verilog模块的结构、数据类型、变量_verilog模块结构

verilog模块结构

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

上面的Verilog设计中,模块中的第二、第三行说明接口的信号流向,第四、第五行说明了模块的逻辑功能。

1 模块的端口定义

模块的端口声明了模块的输入输出口。其格式如下:

module 模块名(口1,口2,口3,口4,······)
  • 1

模块的端口表示的是模块的输入和输出名,也就是说,它与别的模块联系端口的标识在模块被引用时,在引用的模块中,有些信号要输入到被引用的模块中,有的信号需要从被引用的模块中取出来。在引用模块时其端口可以用以下两种方法连接:

  1. 在引用时,严格按照模块定义的端口顺序来连接,不用标明原模块定义时规定的端口名,例如:
 模块名(连接端口1信号名,连接端口2信号名,·······)
  • 1
  1. 在引用时用“.”符号,标明原模板是定义时规定的端口名,例如:
模块名(.端口1名(连接信号1名),端口2名(连接信号2名),······)
  • 1

这样表示的好处在于可以用端口名与被引用模块的端口相对应,而不必严格按端口顺序对应,提高了程序的可读性和可移植性。
例如:

···
MyDesignMK M1(.sin(SerialIn),.pout(ParallelOut),······);
···
  • 1
  • 2
  • 3

其中,.sin和.pout都是M1的端口名,而M1则是与MyDesignMK完全一致的模块。MyDesignMK已经在另一个模块中定义过,它有两个端口,即sin和pout。与sin口连接的信号名为SerialIn,与pout端口连接的信号名为ParallelOut。

2 模块内容

模块的内容包括I/O说明。内部信号声明和功能定义。

2.1 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个双向总线端口
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

I/O说明也可以写在端口声明语句里。格式如下:

module module_name(input port1,input port2,...
  					output port1,output port2,...);
  • 1
  • 2

2.2 内部信号说明

在模块内用到与端口有关的wire和reg类型变量的声明。

reg [width-1:0] R变量1,R变量2...;
wire [width-1:0] W变量1,W变量2...;
  • 1
  • 2

2.3 功能定义

模块中最重要的部分是逻辑功能定义部分。以下三种方法可在模块中产生逻辑。

  1. assign
    如:
assign a = b & c;  //描述了一个两输入的与门。
  • 1
  1. 用实例元件
    如:
and #2 ul(q,a,b);
  • 1

上述表示设计一个输出延迟2个单位时间的与门,名称为ul,输入端为a、b,输出为q。
每个实例元件的名字必须是唯一的,以避免与其他调用与门(and)的实例混淆。

  1. “always”块
always @(posedge clk or posedge clr);
begin 
	if(clr)<=0;
		else if(en) q<=d;
end
  • 1
  • 2
  • 3
  • 4
  • 5

生成了一个带有异步清除端的D触发器。

3.总结

如果用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。数据类型是用来表示数字电路硬件中的数据储存和传送元素的。
现按常量和变量划分。

1.常量

在程序运行过程中,其值不能改变的量称为常量。

1.1.数字

  1. 整数
    整型常量有4种进制表示形式:
    1)二进制整数(b或者B);
    2)十进制整数(d或者D);
    3)十六进制整数(h或者H);
    4)八进制整数(o或者O)。
    数字表达方式有以下3种:
    1)<位宽><进制><数字>,这是一种全面的描述方式。
    2)在<进制><数字>这种描述方式中,数字的位宽采用默认位宽(这由具体的机器系统决定,但至少32位)。
    3)在<数字>这种描述方式中,采用默认进制(十进制)。
  2. x值和z值
    在数字电路中,x代表不定值,z代表高阻值。一个x可以用来定义十六进制数的4位二进制数的状态,八进制数的3位,二进制数的1位。z的表示方式同x类似。z还有一种表达方式是可以写作“?”,在使用case表达式时建议使用这种写法,以提高程序的可读性。见下例:
4'b10x0    //位宽为4的二进制数从低位数起第2位为不定值
4'b101z    //位宽为4的二进制数从低位数起第1位为高阻值
12'dz        //位宽为12的十进制数,其值为高阻值(第1种表达方式)
12'd?      //位宽为12的十进制数,其值为高阻值(第2种表达方式)
8'h4x       //位宽为8的十六进制数,其低4位值为不定值
  • 1
  • 2
  • 3
  • 4
  • 5
  1. 负数
    一个数字可以定义为负数,只需在位宽表达式前加一个减号,减号必须写在数字定义表达式的最前面。
    注: 减号不可以放在位宽和进制之间,也不能放在进制和具体的数之间。
-8'd5       //5的补数(用八位二进制数表示)
8'd-5       //非法格式
  • 1
  • 2
  1. 下画线(underscore_)
    下画线可以用来分隔开数的表达以提高程序可读性。它不可以用在位宽和进制处,只能用在具体的数字之间。
16'b1010_1011_1111_1010     //合法格式
8'b_0011_1110               //非法格式
  • 1
  • 2

当常量不说明位数时,默认值是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
  • 1
  • 2
  • 3
  • 4
  • 5

1.2参数型

在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
  • 2
  • 3
  • 4

参数型常数经常用于定义延迟时间和变量宽度。在模块或实例引用时,可通过参数传递改变在被引用模块或实例中已定义的参数。下面通过两个例子进一步说明在层次调用的电路中改变参数常用的一些用法。
【例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	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

【例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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

以上例子见图1,在模块Annotate中定义的参数值2和3可以通过模块Test中,经实例T对模块Top的引用,而在实例Top中,实例B1和B2对模块Block的引用,分别将参数值2和3传递到模块Block中用参数定义的地方(即原来在模块Block中定义P=0,在实例B1和B2中分别被P=2和P=3代替)。在这里插入图片描述


2.变量

变量是一种在程序运行过程中其值可以改变的量。
网络数据类型表示结构实体(例如门)之间的物理连接。网络类型的变量不能储存值,而且它必须受到驱动器(例如门或者连续赋值语句,assign)的驱动。如果没有驱动器连接到网络类型的变量上,则该变量就是高阻的,即其值为z。常见的网络数据类型包括wire型和tri型。这两种变量都是用于连接器件单元。wire型变量通常是用来表示单个门驱动或连续赋值语句驱动的网络型数据,tri型变量则用来表示多驱动器驱动的网络型数据。

2.1 wire型

wire型数据常用来表示用以assign关键字指定的组合逻辑信号。Verilog程序模块中输入、输出信号类型默认时自动定义为wire型。wire型信号可以用做任何方程式的输入,也可以用做“assign”语句或实例元件的输出。
wire型信号的格式同reg型信号的格式很类似。其格式如下:

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

wire是wire型数据的确认符;[n-1:0]代表数据的位宽,即该数据有几位;最后跟着的是数据的名字。如果一次定义多个数据,数据名之间用逗号隔开。声明语句的最后要用分号表示语句结束。

wire a;         //定义了一个1位的wire型数据
wire [7:0]b;    //定义了一个8位的wire型数据
wire [4:1]c,d;  //定义了二个4位的wire型数据
  • 1
  • 2
  • 3

2.2 reg型

寄存器是数据储存单元的抽象,寄存器数据类型的关键字是reg。通过赋值语句可以改变寄存器储存的值,其作用与改变触发器储存的值相当。reg型数据的默认初始值为不定值x。
reg型数据常用来表示“always”模块内的指定信号,常代表触发器。通常,在设计中要由“always”模块通过使用行为描述语句来表达逻辑关系。在“always”模块内被赋值的每一个信号都必须定义成reg型。
reg型数据的格式如下:

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

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型数据
  • 1
  • 2
  • 3

对于reg型数据,其赋值语句的作用就如同改变一组触发器的存储单元的值。在Verilog中有许多构造(construct)用来控制何时或是否执行这些赋值语句。这些控制构造可用来描述硬件触发器的各种具体情况,如触发条件时用时钟的上升沿,或用来描述判断逻辑的细节,如各种多路选择器。
reg型数据的默认初始值是不定值。reg型数据可以赋正值,也可以赋负值。但当一个reg型数据是一个表达式中的操作数时,它的值被当做是无符号值,即正值。例如,当一个4位的寄存器用做表达式中的操作数时,如果开始寄存器被赋以值-1,则在表达式中进行运算时,其值被认为是+15。

2.3 memory型

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

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

这个例子定义了一个名为mema的存储器,该存储器有256个8位的存储器。该存储器的地址范围是0到255.
注: 对存储器进行地址索引的表达式必须是常数表达式。
另外,在用一个数据类型声明语句里,可以同时定义存储器型数据和reg型数据。见下例:

parameter wordsize=16,
          memsize=256;      //定义两个参数
reg [wordsize-1:0] 	mem[memsize-1:0],writereg,readreg;
  • 1
  • 2
  • 3

尽管memory型数据和reg型数据的定义格式很相似,但要注意其不同之处。

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

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

rega=0;	//合法赋值语句
mema=0;	//非法赋值语句
  • 1
  • 2

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

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

总结

以上是Verilog基础语法的一部分。总结为以下几点:

  1. 在Verilog模块中所有过程块(如:initial块、always块)、连续赋值语句、实例引用都是并行的;
  2. 他们表示的是一种通过变量名互相连接的关系;
  3. 在同一模块中各个过程块、各条连续赋值语句和各条实例引用语句这三者出现的先后秩序没有关系;
  4. 只有连续赋值语句(即用关键词assign引出的语句)和实例引用语句(即用已定义的模块名引出的语句),可以独立于过程块而存在于模块的功能定义部分;
  5. 被实例引用的模块,其端口可以通过不同名的连线或寄存器类型变量连接到别的模块相应的输出、输入信号端;
  6. 在“always”模块内被赋值的每一个信号都必须定义为reg型。
    以上6点与C语言有很大不同。许多与C语言类似的语句只能出现在过程块中,而不能随意出现在模块功能定义的范围内。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/462699
推荐阅读
相关标签
  

闽ICP备14008679号