Verilog的基本设计单元是“模块”(block)。一个模块是由两部分组成的,一部分描述接口,另一部分描述逻辑功能,即定义输入是如何影响输出的。
//例如一个 二选一多路选择器
module muxtwo(out,a,b,sl);
input a,b,sl;
output out;
reg out;
always @(sl or a or b)
//always @(sl or a or b)表示只要sl或a或b,其中若有一个变化时就执行下面的语句。
if(!sl)
out=a;
else
out=b;
endmodule
module muxtwo (out,a,b,sl);
input a,b,sl;
output out;
wire nsl,sela,selb;//
not u1(nsl,sl);
and #1 u2(sela,a,nsl);
and #1 u3(selb,b,sl);
or #2 u4(out,sela,selb);
endmodule
//and、or和not都是Verilog语言的保留字
//由Verilog语言的原语(primitive)规定了它们的接口顺序和用法
//分别表示与门、或门和非门,#1和#2分别表示门输入到输出的延迟为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
//以上程序也可写为
module compare_2 ( equal,a,b );
output equal; //声明输出信号equal
input [1:0] a,b; //声明输入信号a,b
reg equal;//
always @(a,b)
if(a==b)
equal=1;
else
equal=0;
/*如果a、b 两个输入信号相等,输出为1。否则为0*/
endmodule
**与门**
module and(a,b,c);
input a,b;
output c;
assign c=a&b;
endmodule
**非门**
module inv(a,b,c);
input a,b;
output c;
assign c=~(a&b);
endmodule
综上可实现图示电路
module test_comp(a,b,c,d,z);
input a,b,c,d;
output z;
wire e,f;
and u1(a,b,e);
and u2(c,d,f);
inv u3(e,f,z);
//u1/u2/u3表示逻辑单元的实例名称。
endmodule
通过上面的例子可见:
• Verilog HDL程序是由模块构成的。每个模块的内容都是嵌在module和endmodule两个语句之间。每个模块实现特定的功能。模块是可以进行层次嵌套的。因此, 大型的数字电路设计可分割成不同的小模块来实现特定的功能,最后通过顶层模块调用子模块来实现整体功能。
• Verilog HDL程序的书写格式自由,一行可以写几个语句,一个语句也可以分写多行。
• 除了endmodule语句外,每个语句和数据定义的最后必须有分号。
• 可以用/…/和//…对Verilog HDL程序的任何部分作注释。一个好的、有使用价值的源程序都应当加上必要的注释,以增强程序的可读性和可维护性。
【一道关于模块的例题】:
module d1(a,b,h)
input a,b;
output h;
assign h=~(a|b);
endmodule
module d2(h,c,d)
input h,c;
output d;
assign d=h^c;
endmodule
module example(a,b,c,d)
input a,b,c;
output d;
wire h;
d1 u1(a,b,h);
d2 u2(h,c,d);
endmodule
Verilog还可以用来描述变化的测试信号。描述测试信号的变化和测试过程的模块也叫做测试平台(testbench),它可以对上面介绍的电路模块进行动态的全面测试。通过观测被测试模块的输出信号是否符合要求,可以调试和验证逻辑系统的设计和结构正确与否,并发现问题及时修改。
一个很简单的测试平台
module t_lx1;
wire b;
reg a;
initial
begin
a=0;
#23 a=1;
#96 a=0;
end
lx1 u1(.a(a),.b(b));
endmodule
Verilog结构位于module和endmodule声明语句之间,每个Verilog程序包括4个主要部分:
例:
module block1(a,b,c,d ); //端口定义
input a,b,c; // I/O说明
output d; // I/O 说明
wire x; //内部信号说明
assign d = a | x; //功能定义
assign x = ( b & ~ c );
endmodule
模块端口定义格式
module 模块名(口1,口2,口3,…)
引用模块的两种连接方法
(1)在引用时严格按模块定义的端口顺序来连接,不用标明原模块定义时规定的端口名
mytri tri_inst(sout,sin,ena);
(2)在引用时用“.”符号,标明定义时规定的端口名不必严格按端口顺序对应
mytri tri_inst(.out(sout),.in(sin),.enable(ena))
I/O说明的格式
I/O说明也可以写在端口声明里。
module module_name(input in_port1,input in_port2,output out_port1,output out_port2);
例如
module test_width(b,a);
input[6:5] a;
output[3:2] b;
assign b=a;
endmodule
内部信号说明
reg[范围] 变量1,变量2…;
wire[范围] 变量1,变量2…;
模块中实现逻辑功能的3种方法
(1)assign
assign c=a&b;
(2)用实例元件
and #2 u1(q,a,b);
(3)用always块
assign语句是描述组合逻辑最常用的方法之一。
always块既可用于描述时序逻辑,又可用于组合逻辑。
Verilog语言要点
Verilog中过程块、连续赋值语句、实例引用的关系
(1)在Verilog模块中所有过程块(如initial块、always块)、连续赋值语句、实例引用都是并行的。
(2)它们表示的是一种通过变量名互相连接的关系
(3)在同一模块中这三者出现的先后次序没有关系
(4)只有连续赋值语句assign和实例引用语句可以独立于过程块而存在于模块的功能定义部分。
D触发器
module new_dff(q,clk,d);
input clk,d;
output q;
reg q;
always @(posedge clk)
q<=d;
endmodule
D触发器(带异步清除端)
module new_dff2(q,clk,d,clr);
input clk,d,clr;
output q;
reg q;
always @(posedge clk or posedge clr)
begin
if(clr)
q<=0;
else
q<=d;
end
endmodule
D触发器(带异步清除端和使能端)
module new_dff3(q,clk,d,clr,en);
output q;
input clk,d,clr,en;
reg q;
always @(posedge clk or posedge clr)
begin
if(clr)
q<=0;
else if (en)
q<=d;
end
endmodule
4种逻辑值
0 1 z(高阻) x (不定值)
在程序运行过程中,其值不能被改变的量称为常量。
1.数字
(1)整数:在Verilog HDL中,整型常量即整常数有以下四种进制表示形式:
数字表达方式
<位宽>’<进制><数字>
- 4’b1110 //4位二进制数
- 12’habc //12位十六进制数
- 16’d255 //16位十进制数
或者有
’<进制><数字>
采用默认位宽,与仿真器和使用的计算机有关(最小为32位)
- ‘hc3 //32位16进制数
- ‘o21 //32位8进制数
以及
<数字>
默认为十进制数
采用默认位宽,与仿真器和使用的计算机有关(最小为32位)
326 //32位十进制数
(2)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’BXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
“AB”=16’B01000001_01000010 //字符串AB,//为十六进制数16’h4142
2. 参数(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 网络型(线网,net )
表示硬件单元之间的连接。
net不是关键字,代表了一组数据类型,包括wire, wand, wor, tri, triand, trior, trireg等。
线网的默认值为z(trireg例外,默认值为x)
本文主要讨论wire(线网中最常用)
• wire型数据常用来表示用于以assign关键字指定的组合逻辑信号。
• Verilog程序模块中输入输出信号类型缺省时自动定义为wire型。
• wire型信号可以用作任何方程式的输入,也可以用作“assign”语句或实例元件的输出。
格式:
wire [n-1:0] 数据名1,数据名2,…数据名i; //共有i条总线,每条总线内有n条线路
wire [n:1] 数据名1,数据名2,…数据名i;
wire a; //定义了一个一位的wire型数据
wire [7:0] b; //定义了一个八位的wire型数据
wire [4:1] c, d; //定义了二个四位的wire型数据
2. 寄存器型(reg)
通常表示一个存储数据的空间。
reg是最常用的寄存器型数据。 reg型变量并不严格对应于电路上的存储单元。
Verilog还支持integer,real和time寄存器数据类型。
reg的默认初始值是x。
reg型数据常用来表示用于**“always”模块内的指定信号**,常代表触发器。
通常,在设计中要由**“always”块通过使用行为描述语句来表达逻辑关系**。在“always”块内被赋值的每一个信号都必须定义成reg型。
格式:
reg [n-1:0] 数据名1,数据名2,… 数据名i;
reg [n:1] 数据名1,数据名2,… 数据名i;
reg rega; //定义了一个一位的名为rega的reg型数据
reg [3:0] regb; //定义了一个四位的名为regb的reg型数据
reg [4:1] regc, regd; //定义了两个四位的名为regc和regd的reg型数据
3. memory型
Verilog HDL通过对reg型变量建立数组来对存储器建模,可以描述RAM型存储器,ROM存储器和reg文件。数组中的每一个单元通过一个数组索引进行寻址。memory型数据是通过扩展reg型数据的地址范围来生成的。
格式:
reg [n-1:0] 存储器名[m-1:0];
或
reg [n-1:0] 存储器名[m:1];
reg [7:0] mema[255:0];
在同一个数据类型声明语句里,可以同时定义存储器型数据和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。
进行寻址的地址索引可以是表达式,这样就可以对存储器中的不同单元进行操作。表达式的值可以取决于电路中其它的寄存器的值。
运算符按其功能可分为以下几类
1) 算术运算符(+,-,×,/,%)
2) 赋值运算符(=,<=)
3) 关系运算符(>,<,>=,<=)
4) 逻辑运算符(&&,||,!)
5) 条件运算符(?: )
6) 位运算符(,|,^,&,^)
7) 移位运算符(<<,>>)
8) 拼接运算符({ })
9) 其它
按其所带操作数的个数运算符可分为三种:
1) 单目运算符(unary operator):可以带一个操作数,操作数放在运算符的右边。
2) 双目运算符(binary operator):可以带二个操作数,操作数放在运算符的两边。
3) 三目运算符(ternary operator):可以带三个操作,这三个操作数用三目运算符分隔开。
clock = ~clock; //~是一个单目取反运算符, clock是操作数。
c=a|b; //|是一个双目按位或运算符, a 和 b是操作数。
r=s ? t : u; // ?: 是一个三目条件运算符, s,t,u是操作数。
在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. |
(-11)%(-3) | -2 | |
11%(-3) | 2 |
注意: 在进行算术运算操作时,如果某一个操作数有不确定的值x,则整个结果也为不定值x。
1) ~ //取反
2) & //按位与
3) | //按位或
4) ^ //按位异或
5) ^~ //按位同或(异或非)
按位同或运算就是将两个操作数的相应位先进行异或运算再进行非运算
不同长度的数据进行位运算
两个长度不同的数据进行位运算时,系统会自动的将两者按右端对齐。位数少的操作数会在相应的高位用0填满,以使两个操作数按位进行操作。
例如以下程序片段执行后
reg[4:0] temp1,temp2,a,b;
reg[2:0] temp3;
initial
begin
temp2=5'b10010;
temp3=3'b110;
temp1=temp2&temp3;
a=temp2|temp3;
b=temp2^temp3;
end
temp1 为 00010
a 为 10110
b 为 10100
在Verilog HDL语言中存在三种逻辑运算符:
1) && 逻辑与
2) || 逻辑或
3) ! 逻辑非
逻辑运算符中
"&&“和”||"的优先级别低于关系运算符
“!” 高于算术运算符。
见下例:
(a>b)&&(x>y) 可写成: a>b && x>y
(a==b)||(x==y) 可写成:a==b || x==y
(!a)||(a>b) 可写成: !a || a>b
为了提高程序的可读性,明确表达各运算符间的优先关系,建议使用括号。
关系运算符共有以下四种:
在进行关系运算时,如果声明的关系是假的(flase),则返回值是0,如果声明的关系是真的(true),则返回值是1,如果某个操作数的值不定,则关系是模糊的,返回值是不定值。
在Verilog HDL语言中存在四种等式运算符:
1) == (等于)
2) != (不等于)
3) === (等于)
4) !== (不等于)
这四个运算符都是二目运算符,它要求有两个操作数。
“==”和“!=”又称为逻辑等式运算符。其结果由两个操作数的值决定。
由于操作数中某些位可能是不定值x和高阻值z,结果可能为不定值x。
而“ = = = ”和“!==”运算符则不同,它在对操作数进行比较时对某些位的不定值x和高阻值z也进行比较,两个操作数必需完全一致,其结果才是1,否则为0。“ = = = ”和“!= =”运算符常用于case表达式的判别,所以又称为“case等式运算符”。这四个等式运算符的优先级别是相同的。
module test_equal;
reg A;
initial
begin
A=1'bx;
if(A==1'bx)
$display("== two. A is x") ;
if(A===1'bx)
$display("=== three. A is x");
end
endmodule
在Verilog HDL中有两种移位运算符:
<< (左移位运算符) 和 >>(右移位运算符)。其使用方法如下:
a >> n 或 a << n
a代表要进行移位的操作数,n代表要移几位。
这两种移位运算都用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;
例如
module test_shift;
reg[3:0] start,result;
reg[4:0] result2;
initial
begin
start=4'b1001;
result=(start<<1);
result2=(start<<1);
start=(start<<1);
end
endmodule
执行后结果如下
在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}
在位拼接表达式中不允许存在没有指明位数的信号。这是因为在计算拼接信号的位宽的大小时必需知道其中每个信号的位宽。
变量a,b,w的声明语句如下
reg[1:0] a;
reg[4:0] b;
reg w;
已知:a的值是2'b01,b的值是5'b10010,w的值是
1'b1,
{a,b[3:0],w,3'b101}的值
10'b01_0010_1_101
位拼接可以用重复法来简化表达式
{4{w}} //等同于{w,w,w,w}
位拼接 还可以用嵌套的方法来表达
{b,{3{a,b}}} //等同于{b,a,b,a,b,a,b}
变量a,b,w的声明语句如下
reg[1:0] a;
reg b;
reg[1:0] w;
已知 a的值是2'b01,b的值是1'b0,w的值是2'b10。
{4{w}} 的值和{b,{3{a,b}}} 的值
8'b10_10_10_10 10'b0_010_010_010
变量a,b的声明语句如下
reg[7:0] a;
reg[9:0] b;
已知:a的值是8'b0100_1011,b的值是10'b01_0011_1001
{a[2:0],b[7:4]}的值
7'b011_0011
缩减运算符是单目运算符,也有与或非运算。其与或非运算规则类似于位运算符的与或非运算规则,但其运算过程不同。
缩减运算符包括:
相当于:
C =( (B[0]&B[1]) & B[2] ) & B[3];
在Verilog HDL中,所有的关键词是事先定义好的确认符,用来组织语言结构。关键词是用小写字母定义的,因此在编写源程序时要注意关键词的书写,以避免出错。
在Verilog HDL语言中,信号有两种赋值方式:
1.非阻塞(Non_Blocking)赋值方式( 如 b <= a; )
(1)在语句块中,上面语句所赋的变量值不能立即就为下面的语句所用。
(2)块结束后才能完成这次赋值操作,而所赋的变量值是上一次赋值得到的。
(3)在编写可综合的时序逻辑模块时,这是最常用的赋值方法。
非阻塞赋值语句右端表达式计算完后并不立即赋值给左端,而是同时启动下一条语句继续执行,可以将其理解为所有的右端表达式RHS1、RHS2等在进程开始时同时计算,计算完后 ,等进程结束时同时分别赋给左端变量LHS1、LHS2等。
2.阻塞(Blocking)赋值方式( 如 b = a; )
(1)赋值语句执行完后,才执行下一条语句。
(2)b的值在赋值语句执行完后立刻就改变的。
(3)在时序逻辑中使用时可能会产生意想不到的结果。
阻塞赋值语句在每个右端表达式计算完后立即赋给左端变量,即赋值语句LHS1=RHS1执行完后LHS1是立即更新的,同时只有LHS1=RHS1执行 完后才可执行语LHS1=RHS2,依次类推。前一条语句的执行结果直接影响到后面语句的执行结果。
非阻塞以及阻塞的用法:
module ex4_1(c,a,clk);
output c;
input a,clk;
reg c;
reg b;
always @(posedge clk)
begin
b<=a;
c<=b;
end
endmodule
module ex4_2(c,a,clk);
output c;
input a,clk;
reg c;
reg b;
always @(posedge clk)
begin
b=a;
c=b;
end
endmodule
块语句通常用来将两条或多条语句组合在一起,使其在格式上看更象一条语句。
块语句有两种:
begin_end语句
通常用来标识顺序执行的语句,用它来标识的块称为顺序块。
fork_join语句
通常用来标识并行执行的语句,用它来标识的块称为并行块。
顺序块
顺序块有以下特点:
(1) 块内的语句是按顺序执行的,即只有上面一条语句执行后下面的语句才能执行。
(2) 每条语句的延迟时间是相对于前一条语句的仿真时间而言的。
(3) 直到最后一条语句执行完,程序流程控制才跳出该语句块
顺序块的格式如下:
begin
语句1;
语句2;
......
语句n;
end
或
begin:块名
块内声明语句
语句1;
语句2;
......
语句n;
end
其中块名即该块的名字,一个标识名。
块内声明语句可以是参数声明语句、reg型变量声明语句、integer型变量声明语句、real型变量声明语句。
module ex4_5;
parameter d=50;
reg[7:0] r;
reg[3:0] a;
event end_wave;
always @(end_wave)
a=4'hf;
initial
begin
a=4'h2;
end
initial
begin
#d r='h35;
#d r='hE2;
#d r='h00;
#d r='hF7;
#d ->end_wave;
end
endmodule
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;
五点说明:
(1) 三种形式的if语句中在if后面都有“表达式”,一般为逻辑表达式或关系表达式。系统对表达式的值进行判断,若为0,x,z,按“假”处理,若为1,按“真”处理,执行指定的语句。
(2) 第2、3种形式的if语句中,在每个else前面有一分号,整个语句结束处有一分号。
例如:
if (a>b)
out1 <=int1; //分号
else
out1 <=int2; //分号
但应注意,不要误认为上面是两个语句(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 //begin、end等同于C语言中{}
(4)允许一定形式的表达式简写方式。
如下面的例子:
if(expression)
等同 if( expression == 1 )
【if()判定非0为真,0为假。】
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(index>0)
for(scani=0;scani<index;scani=scani+1)
if(memory[scani]>0)
begin
$display("...");
memory[scani]=0;
end
else /*WRONG*/ //此处else实际上与第二个if对应。
$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");
module counter16b(q,clk,clr);
output[3:0] q;
input clk,clr;
reg[3:0] q=4'b0000;//ModelSim仿真用,QuartusII不用初值
always @(posedge clk or posedge clr)
begin
if(clr) //置零信号高电平有效,若clr==1,q置零。
q<=4'b0000;
else
q<=q+1'b1;
end
endmodule
增加进位后
module counter10b(q,clk,clr);
output[3:0] q;
input clk,clr;
reg[3:0] q=4'b0000;//ModelSim仿真用,QuartusII不用初值
always @(posedge clk or posedge clr)
begin
if(clr) //置零信号高电平有效,若clr==1,q置零。
q<=4'b0000;
else
begin
if (q==4'b1110) //当q==4'b1110即15时,输出进位信号,置零。
q<=4'b0000;
else
q<=q+1'b1;
end
end
endmodule
编写Verilog程序描述一个电路,实现以下功能:
----具有2组输入端口 clk和clr,位宽均为1,clk输入时钟信号;clr起异步清零作用,‘1’有效。两个输出端口,c和q,q的位宽为4,输出计数值,从0到9,c的位宽为1,每当q为9时输出一个高电平脉冲。
模块名为counter10c。
module counter10c(q,c,clk,clr)
input clk,clr;
output [3:0] q;
output c;
reg [3:0] q = 4'b0000;
reg c = 1'b0;
always@(posedge clk or posedge clr)
begin
if(clr)
begin
q<=4'b0000;
c<=1b'0;
end
else
begin
if(q==4'b1001)
begin
q<=4'b0000;
c<=1'b1;
end
else
begin
q<=q+1'b1;
c<=1'b0;
end
end
end
endmodule
编写上述程序的testbench
module test_counter10c;
reg clock,clear;
wire ov;
wire[3:0] Q;
counter10c U1(.q(Q),.c(ov),.clk(clock),.clr(clear));
always
begin
clock=0;
#10;
clock=1;
#10;
end
initial
begin
clear=0;
#123 clear=1;
#50 clear=0;
end
endmodule
【一道关于条件语句的例题】:
----用Verilog设计一个带同步复位的十进制计数器
module counter10(clk,rst,q,c)
input clk,rst;
output [3:0] q;
output c;
reg q = 4'b0000;
reg c = 1'b0;
always@(posedge clk)
begin
if(rst)
begin
q<=4'b0000;
c<=1'b0;
end
else if(q == 4'b1001)
begin
q <= 4'b0000;
c <= 1'b1;
end
else
begin
q <= q + 1'b1;
c <= 1'b0;
end
end
endmodule
同步复位:
------------即复位信号只有在时钟上升信号到来时才有效
例如:
always @(posedge clk)
begin
if(!rst) count <= 4'b0;
else count <= count + 1'b1;
end
异步复位:
------------即只要复位信号有效,就立刻对系统进行复位。
例如:
always@(posedge clk or posedge rst) /复位信号高电平有效
begin
if(rst)
count <= 4'b0;
else
count <= count + 1'b1;
case语句是一种多分支选择语句,if语句只有两个分支可供选择,而实际问题中常常需要用到多分支选择,Verilog语言提供的case语句直接处理多分支选择。
它的一般形式如下:
1) case(表达式)
2) casez(表达式)
3) casex(表达式)
case分支项的一般格式如下:
分支表达式: 语句
缺省项(default项): 语句
(1)case括弧内的表达式称为控制表达式,case分支项中的表达式称为分支表达式。控制表达式通常表示为控制信号的某些位,分支表达式则用这些控制信号的具体状态值来表示,因此分支表达式又可以称为常量表达式。
(2) 当控制表达式的值与分支表达式的值相等时,就执行分支表达式后面的语句。如果所有的分支表达式的值都没有与控制表达式的值相匹配的,就执行default后面的语句。
(3)default项可有可无,一个case语句里只准有一个default项。
(4) case语句各分支表达式间未必是并列互斥关系,允许出现多个分支取值同时满足case表达式的情况。这种情况下将执行最先满足表达式的分支项,然后跳出case语句,不再检测其余分支项目。
(5)执行完case分项后的语句,则跳出该case语句结构,终止case语句的执行。
(不同于C语言需要break跳出)
(6)在用case语句表达式进行比较的过程中,只有当信号的对应位的值能明确进行比较时,比较才能成功。因此要注意详细说明case分项的分支表达式的值。
(7)case语句的所有表达式的值的位宽必须相等,只有这样控制表达式和分支表达式才能进行对应位的比较。一个经常犯的错误是用’bx, 'bz 来替代 n’bx, n’bz,这样写是不对的,因为信号x, z的缺省宽度是机器的字节宽度,通常是32位(此处 n 是case控制表达式的位宽)。
case语句与if_else_if语句的区别主要有两点:
与case语句中的控制表达式和多分支表达式这种比较结构相比,if_else_if结构中的条件表达式更为直观一些。
对于那些分支表达式中存在不定值x和高阻值z位时,case语句提供了处理这种情况的手段。
----Verilog HDL针对电路的特性提供了case语句的其它两种形式用来处理case语句比较过程中的不必考虑的情况( don’t care condition )。**其中casez语句用来处理不考虑高阻值z的比较过程,casex语句则将高阻值z和不定值x都视为不必关心的情况。**所谓不必关心的情况,即在表达式进行比较时,不将该位的状态考虑在内。这样在case语句表达式进行比较时,就可以灵活地设置以对信号的某些位进行比较。
reg[7:0] ir;
casez(ir)
8 'b1???????: instruction1(ir);
8 'b01??????: instruction2(ir);
8 'b00010???: instruction3(ir);
8 'b000001??: instruction4(ir);
endcase
例题:
编写Verilog程序描述一个电路,实现以下功能:
具有1组输入端口 a, 位宽为3,1组输出端口y,位宽为8,y的输出由a决定,满足以下真值表。
module yima(y,a);
output[7:0] y;
input[2:0] a;
reg[7:0] y;
always @(a)
begin
case (a)
3'b000: y=8'b1111_1110;
3'b001: y=8'b1111_1101;
3'b010: y=8'b1111_1011;
3'b011: y=8'b1111_0111;
3'b100: y=8'b1110_1111;
3'b101: y=8'b1101_1111;
3'b110: y=8'b1011_1111;
3'b111: y=8'b0111_1111;
endcase
end
endmodule
例题2:
编写Verilog程序描述一个电路,实现以下功能:
具有6组输入端口 i0,i1,i2,i3和s1,s0, i0,i1,i2,i3均为输入端口,位宽为2,s1,s0为输入端口,位宽为1,out为输出端口,位宽为2,当s1,s0为“00”时,i0的数据从out输出,s1,s0为“01”时,i1的数据从out输出,s1,s0为“10”时,i3的数据从out输出,s1,s0为“11”时,i3的数据从y输出。
module example2(out,i0,i1,i2,i3,s0,s1)
output [1:0] out;
input [1:0] i0,i1,i2,i3;
intput s0,s1;
reg[ 1,0] out;
always @(s1 or s0 or i0 or i1 or i2 or i3)
begin
case({s1,s0})
2'b00:out = i0;
2'b01:out = i1;
2'b10:out = i2;
2'b11:out = i3;
default:out = 2'bxx;
endcase
end
endmodule
Verilog HDL设计中容易犯的一个通病是由于不正确使用语言,生成了并不想要的锁存器。
always @ (al or d ) // 有锁存器
begin
if ( al ) q = d;
end
always @ (al or d ) // 无锁存器
begin
if ( al ) q = d;
else q = 0;
end
//有锁存器
always @(sel[1:0] or a or b)
case(sel[1:0])
2'b00:q<=a;
2'b11:q<=b;
endcase
//无锁存器
always @(sel[1:0] or a or b)
case(sel[1:0])
2'b00:q<=a;
2'b11:q<=b;
default:q<=‘b0;
endcase
在Verilog HDL中存在着四种类型的循环语句,用来控制执行语句的执行次数。
1) forever 连续的执行语句。
2) repeat 连续执行一条语句 n 次。
3) while 执行一条语句直到某个条件不满足。
如果一开始条件即不满足(为假),则语句一次也不能被执行。
4) for通过三个步骤来决定语句的循环执行。
注意: 循环语句只能在always或initial块中使用。
forever语句的格式如下:
forever 语句;
或
forever
begin
多条语句
end
forever循环语句不包含任何条件表达式,只执行无限循环,直到遇到系统任务**$finish为止**。常用于产生周期性的波形,用来作为仿真测试信号。它与always语句不同处在于不能独立写在程序中,而必须写在initial块中。
reg clock;
initial
begin
clock=1’b0;
forever #10 clock=~clock;
end
产生周期为20个单位时间的时钟信号
repeat语句的格式如下:
repeat(表达式) 语句;
或
repeat(表达式)
begin
多条语句;
end
在repeat语句中,其表达式通常为常量表达式(例如数字5)
例:
integer count;
initial
begin
count=0;
repeat(128)
begin
$display(“count=%d”,count);
count =count + 1;
end
end
实现乘法器
module test_repeat;
parameter size=8,longsize=16;
reg [size:1] opa, opb;
reg [longsize:1] result;
initial
begin
opa=8'ha2;
opb=8'h3f;
#100;
opa=8'hf5;
opb=8'hc6;
end
always @(opa or opb)
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
endmodule
while语句的格式如下:
while(表达式) 语句;
或用如下格式:
while(表达式)
begin
多条语句;
end
对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
for语句的一般形式为:
for(表达式1;表达式2;表达式3) 语句
通过以下三个步骤来决定语句的循环执行。
a) 先给确定循环次数的变量赋初值。
b) 判定控制循环的表达式的值,如为假则跳出循环语句,如为真则执行指定的语句后,转到第三步。
c) 执行一条赋值语句来修正控制循环变量次数的变量的值,然后返回第二步。
初始化memory型变量
begin: init_mem
reg[7:0] tempi;
for(tempi=0;tempi<memsize;tempi=tempi+1)
memory[tempi]=0;
end
在for语句中,循环变量增值表达式可以不必是一般的常规加法或减法表达式。对rega这个八位二进制数中值为1的位进行计数的另一种方法。
begin: count1s
reg[7:0] tempreg;
count=0;
for( tempreg=rega; tempreg; tempreg=tempreg>>1 )
if(tempreg[0]) //不断移位,判断是否为1;
count=count+1;
end
----Verilog通过关键字disable提供了一种终止命名块执行的方法。可用来从循环中退出、处理错误条件以及根据控制信号来控制某些代码段是否被执行。对块语句的禁用导致紧接在块后面的那条语句被执行。
//从矢量标志寄存器的低有效位开始查找第一个值为1的位
module ex5_13;
reg [15:0] flag;
integer i; //用于计数的整数
initial
begin
flag = 16'b 0010_0000_0000_0000;
i = 0;
begin: block1 //while循环声明中的主模块是命名块block1
while(i < 16)
begin
if (flag[i])
begin
$display("Encountered a TRUE bit at element number %d", i);
disable block1; // 在标志寄存器中找到了值为真
//(1)的位,禁用block1
end
i = i + 1;
end
end
end
endmodule
生成语句可以动态地生成Verilog代码。使用生成语句能够大大简化程序的编写过程。
Verilog中有3种创建生成语句的方法。
- 循环生成
- 条件生成
- case生成
原程序(不使用生成语句)
module bitwise_xor3_2 (out,i0,i1) ;
parameter N=4;//缺省的总线位宽为4位
output[ N-1:0] out;
input[N-1:0] i0,i1;
new_xor g1(out[0],i0[0],i1[0]) ;
new_xor g2(out[1],i0[1],i1[1]) ;
new_xor g3(out[2],i0[2],i1[2]) ;
new_xor g4(out[3],i0[3],i1[3]) ;
endmodule
module new_xor(c,a,b);
output c;
input a,b;
assign c=a^b;
endmodule
循环生成语句(只讨论对模块进行多次实例引用)
//本模块生成两条N位总线变量的按位异或
module bitwise_xor3 (out,i0,i1) ;
parameter N=4;//缺省的总线位宽为4位
output[ N-1:0] out;
input[N-1:0] i0,i1;
genvar j ;
//用一个单循环生成按位异或的异或门(new_xor)
generate
for(j=0;j<N;j=j+1)
begin : xor_loop
new_xor g1(out[j],i0[j],i1[j]) ;
end // 在生成块内部结束循环
endgenerate //结束生成块
endmodule
module new_xor(c,a,b);
output c;
input a,b;
assign c=a^b;
endmodule
循环生成语句若干特点
(1)在仿真开始之前,仿真器会对生成块中的代码进行确立(展平),将生成块转换为展开代码,然后对展开的代码进行仿真。因此,生成块的本质是使用循环内的一条语句来代替多条重复的Verilog语句,简化用户的编程。
(2)关键词genvar用于声明生成变量,生成变量只能用在生成块中,在确立后的仿真码中,生成变量是不存在的。
(3)一个生成变量的值只能由循环生成语句来改变。
(4)循环生成语句可以嵌套使用,不过使用同一个生成变量作为索引的循环生成语句不能相互嵌套。
(5)xor_loop是赋予循环生成语句的名字,目的在于通过它对循环生成语句之中的变量进行层次化引用。因此,循环生成语句中各个异或门模块的相对层次名为:xor_loop[0].g1,xor_loop[1].g1,…,xor_loop[N-1].g1
// 本模块实现一个参数化乘法器
module multiplier (product,a0,a1) ;
//参数声明,该参数可以重新定义
parameter a0_width = 8 ;
parameter a1_width = 8 ;
//本地参数声明
//本地参数不能用参数重新定义(defparam)修改 ,
//也不能在实例引用时通过传递参数语句,即 #(参数1,参数2,...)的方法修改
localparam product_width = a0_width + a1_width ;
//端口声明语句
output [ product_width - 1 : 0 ] product ;
input [ a0_width - 1 : 0 ] a0 ;
input [ a1_width - 1 : 0 ] a1 ;
//有条件地调用(实例引用)不同类型的乘法器
//根据参数a0_width 和 a1_width的值,在调用时
//引用相对应的乘法器实例。
generate
if ((a0_width<8)||(a1_width<8))
cal_multiplier #(a0_width,a1_width)
m0(product,a0,a1);
else
tree_multiplier #(a0_width,a1_width)
m0(product,a0,a1) ;
endgenerate //生成块的结束
endmodule
module cal_multiplier(product,a0,a1) ;
parameter a0_width = 7 ;
parameter a1_width = 7 ;
localparam product_width = a0_width + a1_width ;
output [ product_width - 1 : 0 ] product ;
input [ a0_width - 1 : 0 ] a0 ;
input [ a1_width - 1 : 0 ] a1 ;
reg[product_width-1:0] product ;
always @(a0 or a1)
begin
product=a0*a1;
$display("This is cal_multiplier...");
end
endmodule
module tree_multiplier ( product , a0 , a1 ) ;
parameter a0_width = 8 ;
parameter a1_width = 8 ;
localparam product_width = a0_width + a1_width ;
output [ product_width - 1 : 0 ] product ;
input [ a0_width - 1 : 0 ] a0 ;
input [ a1_width - 1 : 0 ] a1 ;
reg[product_width-1:0] product ;
always @(a0 or a1)
begin
product=a0*a1;
$display("This is tree_multiplier...");
end
endmodule
//本模块生成N位的加法器
module adder ( co , sum , a0 , a1 , ci );
//参数声明,本参数可以重新定义
parameter N = 4 ; // 缺省的总线位宽为4
//端口声明
output [ N-1 : 0 ] sum ;
output co ;
input [ N-1 : 0 ] a0 , a1 ;
input ci ;
// 根据总线的位宽,调用(实例引用)相应的加法器
//参数N在调用(实例引用)时可以重新定义,调用(实例引用)
// 不同位宽的加法器是根据不同的N来决定的。
generate
case ( N )
// 当N=1, 或2 时分别选用位宽为1位或2位的加法器
1:adder_1bit adder1(co,sum,a0,a1,ci);// 1位的加法器
2:adder_2bit adder2(co,sum,a0,a1,ci);// 2位的加法器
// 缺省的情况下选用位宽为N位的超前进位加法器
default:adder_cla #(N) adder3(co,sum,a0,a1,ci);
endcase
endgenerate //生成块的结束
endmodule
Verilog语言中的任何过程模块都从属于以下四种结构的说明语句。
(1) initial说明语句
(2) always说明语句
(3) task说明语句
(4) function说明语句
其中:
- initial和always说明语句在仿真的一开始即开始执行。
- initial语句只执行一次。
- always语句则是不断地重复执行,直到仿真过程结束。
- always语句后面跟着的过程块是否运行,则要看它的触发条件是否满足,如满足则运行过程块一次,再次满足再运行一次,直至仿真过程结束。
- 在一个模块中,使用initial和always语句的次数是不受限制的。
- task和function语句可以在程序模块中的一处或多处调用。
initial语句的格式如下:
initial
begin
语句1;
语句2;
…
语句n;
end
所有在initial语句内的语句构成了一个initial块,initial块从仿真0时刻开始执行,在整个仿真过程中只执行一次。如果模块中包含多个initial块,则这些initial块各自独立并发执行。
例6.1.1 用initial 块对存储器变量赋初始值
initial
begin
areg=0; //初始化寄存器areg
for(index=0;index<size;index=index+1)
memory[index]=0; //初始化一个memory
end
例6.1.2 用initial语句来生成激励波形
initial
begin
inputs = 'b000000; //初始时刻为0
#10 inputs = 'b011001;
#10 inputs = 'b011011;
#10 inputs = 'b011000;
#10 inputs = 'b001000;
end
initial块常用于测试文件和虚拟模块的编写,用来产生仿真测试信号和设置信号记录等仿真环境。
always语句由于其不断活动的特性,只有和一定的时序控制结合在一起才有用。
如果一个always语句没有时序控制,则这个always语句将会使仿真器产生死锁。
always areg = ~areg;
但如果加上时序控制,则这个always语句将变为一条非常有用的描述语句。
always #half_period areg = ~areg;
例6.1.3
reg[7:0] counter;
reg tick;
always @(posedge areg)
begin
tick = ~tick;
counter = counter + 1;
end
always 的时序控制可以是沿触发也可以是电平触发的,可以单个信号也可以多个信号,中间需要用关键字 or 连接。
always @(posedge clock or posedge reset)
begin
…
end
always @(a or b or c)
begin
…
end
边沿触发的always块常常描述时序行为,如有限状态机。如果符合可综合风格要求,则可通过综合工具自动地将其转换为表示寄存器组和门级组合逻辑的结构,而该结构应具有时序所要求的行为。
电平触发的always块常常用来描述组合逻辑的行为。如果符合可综合风格要求,可通过综合工具自动将其转换为表示组合逻辑的门级逻辑结构或带锁存器的组合逻辑结构,而该结构应具有所要求的行为。
一个模块中可以有多个always块,它们都是并行运行的。
always块的or事件控制
有时,多个信号或者事件中任意一个发生的变化都能触发语句或语句块的执行。在Verilog语言中,可以使用“或”表达式来表示这种情况。由关键词“or”连接的多个事件名或信号名组成的列表称为敏感列表。关键词“or”被用来表示这种关系。
Verilog 1364-2001版本的语法中,对于原来的规定作了补充:关键词"or"也可以使用","来代替。使用","来代替关键词"or"也适用于跳变沿敏感的触发器。
有异步复位的电平敏感锁存器
always @ ( reset , clock , d )
//等待复位信号reset 或 时钟信号clock 或 输入信号d的改变
begin
if ( reset ) //若 reset 信号为高,把q置零
q = 1 'b0 ;
else if ( clock ) //若clock 信号为高,锁存输入信号d
q = d ;
end
用reset异步下降沿复位,clk正跳变沿触发的D寄存器
always @ ( posedge clk , negedge reset )
//注意:使用逗号来代替关键字or
if (! reset )
q <= 0 ;
else
q <= d ;
如果组合逻辑块语句的输入变量很多,那么编写敏感列表会很繁琐并且容易出错。针对这种情况,Verilog提供另外两个特殊的符号:@* 和@(*),它们都表示对其后面语句块中所有输入变量的变化是敏感的。
用or 操作符的组合逻辑块
编写敏感列表很繁琐并且容易漏掉一个输入
always @(a or b or c or d or e or f or g or h or p or m)
begin
out1 = a ? b + c : d + e ;
out2 = f ? g + h : p + m ;
end
/不用上述方法,用符号 @(*) 来代替,
/可以把所有输入变量都自动包括进敏感列表。
always @ ( * )
begin
out1 = a ? b + c : d + e ;
out2 = f ? g + h : p + m ;
end
电平敏感时序控制 wait
always
wait (count_enable) #20 count=count+1;
仿真器连续监视count_enable的值,若其值为0,则不执行后面的语句,仿真会停顿下来;如果其值为1,则在20个时间单位之后执行这条语句。如果count_enable始终为1,那么count将每过20个时间单位加1。
也可写为
module test_block_delay;
reg[3:0] x,y;
always
begin
x<=4'ha;
y<=4'hc;
#50;
x<=y;
y<=x;
#50;
end
endmodule
每隔50ns x和y的值交换一次
----在设计中,设计者经常需要在程序的多个不同地方实现同样的功能。这表明有必要把这些公共的代码提取出来,将其组成子程序,然后在需要的地方调用这些子程序,以避免重复编码。
----Verilog语言提供的任务和函数可以将较大的设计划分为较小的代码段,允许设计者将在多个地方使用的相同代码提取出来,编写成任务和函数,以使代码简洁、易懂。
task和function说明语句的不同点
(1) 函数只能与主模块共用同一个仿真时间单位,而任务可以定义自己的仿真时间单位。
(2) 函数不能启动任务,而任务能启动其它任务和函数。
(3) 函数至少要有一个输入变量,而任务可以没有或有多个任何类型的变量。
(4) 函数返回一个值,而任务则不返回值。
任务启动与返回的过程
• 如果传给任务的变量值和任务完成后接收结果的变量已定义,就可以用一条语句启动任务。
• 任务完成以后控制就传回启动过程。如任务内部有定时控制,则启动的时间可以与控制返回的时间不同。
• 任务可以启动其它的任务,其它任务又可以启动别的任务,可以启动的任务数是没有限制的。不管有多少任务启动,只有当所有的启动任务完成以后,控制才能返回。(可嵌套)。
任务的定义
定义任务的语法如下:
task <任务名>;
<端口及数据类型声明语句>
begin
<语句1>
<语句2>
.....
<语句n>
end
endtask
任务的调用及变量的传递
启动任务并传递输入输出变量的声明语句的语法如下:
<任务名>(端口1,端口2,...,端口n);
编写Verilog程序实现一个电路,该电路有3组输入端口a,b,c,1组输出端口d,位宽均为1,d的输出是a和b的与运算,再和c异或,要求“2个变量与运算再和另外一个变量异或”这一功能用任务来实现。
module task_call(a,b,c,d);
input a,b,c;
output d;
reg d;
task bitwise; /定义任务bitwise
input in1,in2,in3;
output out;
begin
out=(in1&in2)^in3;
end
endtask
always @(a,b,c)
bitwise(a,b,c,d);
endmodule
编写Verilog程序实现一个电路,该电路有2组输入端口a和b,1组输出端口c,位宽均为4,c输出a和b中较大的数。要求“对两数进行比较找出最大值”这一功能用任务来实现。
module task_max(a,b,c);
input[3:0] a,b;
output[3:0] c;
reg[3:0] c;
task new_max;
input[3:0] a,b;
output[3:0] c;
begin
if (a>b)
c=a;
else
c=b;
end
endtask
always @(a,b)
new_max(a,b,c);
endmodule
任务定义时需注意以下事项:
(1)在第一行“task”语句中不能列出端口名列表。
(2)任务中可以有延时语句、敏感事件控制语句等事件控制语句。
(3)任务可以没有或可以有一个或多个输入、输出和双向端口。
(4)任务可以没有返回值,也可以通过输出端口或双向端口返回一个或多个返回值。
(5)任务可以调用其它的任务或函数,也可以调用该任务本身。(可嵌套)
(6)任务定义结构内不允许出现过程块(initial或always过程块)。
(7)任务定义结构内可以出现disable终止语句,这条语句的执行将中断正在执行的任务。在任务被中断后,程序流程将返回到调用任务的地方继续向下执行。
函数的目的是返回一个用于表达式的值。
定义函数的语法:
function <返回值的类型或范围> (函数名);
<端口说明语句>
<变量类型说明语句>
begin
<语句>
........
end
endfunction
从函数返回的值
函数的定义蕴含声明了与函数同名的、函数内部的寄存器。如在函数的声明语句中<返回值的类型或范围>为缺省,则这个寄存器是一位的,否则是与函数定义中<返回值的类型或范围>一致的寄存器。函数的定义把函数返回值所赋值寄存器的名称初始化为与函数同名的内部变量。
函数的调用
函数的调用是通过将函数作为表达式中的操作数来实现的。
<函数名> (<表达式>,…,<表达式>)
与任务相比较函数的使用有较多的约束,下面给出的是函数的使用规则:
(1)函数的定义不能包含有任何的时间控制语句,即任何用#、@、或wait来标识的语句。
(2)函数不能启动任务。
(3)定义函数时至少要有一个输入参量。
(4)在函数的定义中必须有一条赋值语句给函数中的一个内部变量赋以函数的结果值,该内部变量具有和函数名相同的名字
编写Verilog程序实现一个电路,该电路有1组输入端口old_word,1组输出端口new_word,位宽均为16,old_word的高8位和低8位交换后由new_word输出。要求“高8位和低8位交换”这一功能用函数来实现。
module call_function2(old_word,new_word);
input[15:0] old_word;
output[15:0] new_word;
wire[15:0] old_word;
reg[15:0] new_word;
function[15:0] switch_bytes;
input[15:0] old_w;
reg[15:0] temp;//此变量可以不要,此处是为了说明function内变量的定义
begin
temp=old_w;
switch_bytes[15:8]=temp[7:0];
switch_bytes[7:0]=temp[15:8];
end
endfunction
always @(old_word)
begin
new_word=switch_bytes(old_word);
end
//assign new_word= switch_bytes(old_word); //也可以,注意前面reg改成wire
endmodule
任务返回的新字是通过输出端口的变量,
switch_bytes(old_word,new_word);
任务switch_bytes把输入old_word的字的高、低字节互换放入new_word端口输出。
而函数返回的新字是通过函数本身的返回值,
new_word = switch_bytes(old_word);
奇偶校验位的计算
定义一个模块,其中包含能计算偶校验位的函数(calc_parity)
module parity;
reg [31:0] addr;
reg parity;
initial
begin
addr = 32'h3456_789a;
#10 addr = 32'hc4c6_78ff;
#10 addr = 32'hff56_ff9a;
#10 addr = 32'h3faa_aaaa;
end
//每当地址值发生变化,计算新的偶校验位
always @(addr)
begin
parity = calc_parity(addr);
//第一次启动校验位计算函数 calc_parity
$display("Parity calculated = %b", calc_parity(addr) );
// 第二次启动校验位计算函数 calc_parity
end
//定义偶校验计算函数
function calc_parity;
input [31:0] address;
begin
//适当地设置输出值,使用隐含的内部寄存器calc_parity
calc_parity = ^address; //返回所有地址位的异或值
end
endfunction
endmodule
// 定义一个包含移位函数的模块
module shifter;
// 左/右 移位寄存器
`define LEFT_SHIFT 1'b0
`define RIGHT_SHIFT 1'b1
reg [31:0] addr, left_addr, right_addr;
//reg control;
//每当新地址出现时就计算右移位和左移位的值
always @(addr)
begin
//调用下面定义的具有左右移位功能的函数
left_addr = shift(addr, `LEFT_SHIFT);
right_addr = shift(addr, `RIGHT_SHIFT);
end
//定义移位函数,其输出是一个32位的值
function [31:0] shift;
input [31:0] address;
input control;
begin
//根据控制信号适当地设置输出值
shift = (control == `LEFT_SHIFT) ? (address << 1) : (address >> 1);
end
endfunction
initial
begin
addr=32'h0001_0000;
#100;
addr=32'h0200_0000;
end
endmodule
在进行函数定义时需要注意以下几点:
(1)与任务一样,函数定义结构只能出现在模块中,而不能出现在过程块内。
(2)函数至少必须有一个输入端口。
(3)函数不能有任何类型的输出端口(output端口)和双向端口(inout端口)。
(4)在函数定义结构中的行为语句部分内不能出现任何类型的时间控制描述,也不允许使用disable终止语句。
(5)与任务定义一样,函数定义结构内部不能出现过程块。
(6)在一个函数内可以对其它函数进行调用,但是函数不能调用其它任务。
(7)在第一行“function”语句中不能出现端口名列表。
(8)在函数声明的时候,在Verilog HDL的内部隐含地声明了一个名为function_identifier(函数标识符)的寄存器类型变量,函数的输出结果将通过这个寄存器类型变量被传递回来。
•任务(task)
•通常用于调试,或对硬件进行行为描述
•可以包含时序控制(#延迟,@, wait)
•可以有 input,output,和inout参数
•可以调用其他任务或函数
•函数(function)
•通常用于计算,或描述组合逻辑
•不能包含任何延迟;函数仿真时间为0
•只含有input参数并由函数名返回一个结果
•可以调用其他函数,但不能调用任务
相同点:
• 任务和函数必须在module内调用
• 在任务和函数中不能声明wire,所有输入/输出都是局部寄存器
• 任务和函数只能使用行为级语句,但是不能包含always和initial块。
• 任务/函数执行完成后才返回结果。
例如,若任务/函数中有forever语句,则永远不会返回结果。
格式
$display(p1,p2,…,pn);
$write(p1,p2,…,pn);
这两个函数和系统任务的作用是用来输出信息,即将参数p2到pn按参数p1给定的格式输出。参数p1通常称为**“格式控制”,参数p2至pn通常称为“输出表列”**。
这两个任务的作用基本相同。
$ display自动地在输出后进行换行。
$ write则不是这样。可以在一行里输出多个信息。
因为$ write在输出时不换行,要注意它的使用。可以在$ write中加入换行符\n,以确保明确的输出显示格式。
格式说明,由"%"和格式字符组成。它的作用是将输出的数据转换成指定的格式输出。格式说明总是由“%”字符开始的。对于不同类型的数据用不同的格式输出。下表中给出了常用的几种输出格式。
普通字符,即需要原样输出的字符。其中一些特殊的字符可以通过下表中的转换序列来输出。下面表中的字符形式用于格式字符串参数中,用来显示特殊的字符。
在$ display和$ write的参数列表中,其“输出表列”是需要输出的一些数据,可以是表达式。下面举几个例子说明一下。
[例1]:
module disp;
initial
begin
$display("\\\t%%\n\"\o123");
end
endmodule
输出结果为
\ %
"S
从上面的这个例子中可以看到一些特殊字符的输出形式
(八进制数123就是字符S)(ASCII)
[例2]:
module disp;
reg[31:0] rval;
initial
begin
rval=101;
$display("rval=%h hex %d decimal", rval, rval);
$display("rval=%o octal %b binary", rval, rval);
end
endmodule
其输出结果为:
rval=00000065 hex 101 decimal
rval=00000000145 octal 00000000000000000000000001100101 binary
输出数据的显示宽度
在$display中,输出列表中数据的显示宽度是自动按照输出格式进行调整的。这样在显示输出数据时,在经过格式转换以后,总是用表达式的最大可能值所占的位数来显示表达式的当前值。
在用十进制数格式输出时,输出结果前面的0值用空格来代替。对于其它进制,输出结果前面的0仍然显示出来。例如对于一个值的位宽为12位的表达式,如按照十六进制数输出,则输出结果占3个字符的位置,如按照十进制数输出,则输出结果占4个字符的位置。这是因为这个表达式的最大可能值为FFF(十六进制)、4095(十进制)。可以通过在%和表示进制的字符中间插入一个0自动调整显示输出数据宽度的方式。
见下例:
$display("d=%0h a=%0h",data,addr);
这样在显示输出数据时,在经过格式转换以后,总是用最少的位数来显示表达式的当前值。
[例3]:
module printval;
reg[11:0]r1;
initial
begin
r1=10;
$display("Printing with maximum size=%d=%h",r1,r1);
$display("Printing with minimum size=%0d=%0h",r1,r1);
end
enmodule
输出结果为:
Printing with maximum size= 10=00a:
Printing with minimum size=10=a;
如果输出列表中表达式的值包含有不确定的值或高阻值,其结果输出遵循以下规则:
(1) 在输出格式为十进制的情况下:
(2)在输出格式为十六进制和八进制的情况下:
例如
对于二进制输出格式,表达式值的每一位的输出结果为0、1、x、z。
语句输出结果:
$display("%d", 1'bx);
输出结果为:x
$display("%h", 14'bx0_1010);
输出结果为:xxXa
$display("%h %o", 12'b001x_xx10_1x01,
12'b001_xxx_101_x01);
输出结果为:XXX 1x5X
打开文件 使用系统任务$ fopen
用法:$ fopen(“<文件名>”);
用法:<文件句柄>=$ fopen(“<文件名>”);
任务$ fopen返回一个被称为多通道描述符的32位值。多通道描述符中只有一位被设置成1.标准输出有一个多通道描述符,其最低位(第0位)被设置成1.标准的输出也称通道为0。标准输出一直是开放的。以后对$ fopen的每一次调用打开一个新通道,并且返回一个设置第1位、第2位等,直到32位描述的第30位。第31位是保留位。通道号与多通道描述符中被设置成1的位相对应。
多通道的优点在于可以有选择的同时写多个文件。
写文件
$ fdisplay $ fmonitor $ fwrite $ fstrobe都用于写文件
这些任务在语法上与常规系统任务$ display、$ monitor等类似
关闭文件 使用系统任务$ fclose
$ fclose(<文件描述符>)
module ex6_20;
//多通道描述符
integer handle1, handle2, handle3; //整型数为 32 位
//标准输出是打开的; descriptor = 32'h0000_0001 ( 第0位
置1)
integer desc1, desc2, desc3 ; // 三个文件的描述符
initial
begin
handle1 = $fopen("file1.out");
//handle1 = 32'h0000_0002 (bit 1 set 1)
handle2 = $fopen("file2.out");
//handle2 = 32'h0000_0004 (bit 2 set 1)
handle3 = $fopen("file3.out");
//handle3 = 32'h0000_0008 (bit 3 set 1)
desc1 = handle1 | 1; //按位或; desc1 = 32'h0000_0003
$fdisplay(desc1, "Display 1");
//写到文件file1.out和标准输出stdout
desc2 = handle2 | handle1; //desc2 = 32 'h0000_0006
$fdisplay(desc2, "Display 2");
//写到文件file1.out和file2.out
desc3 = handle3 ; //desc3 = 32'h0000_0008
$fdisplay(desc3, "Display 3 desc1=%d desc2=%d
desc3=%d",desc1,desc2,desc3); //只写到文件file3.out
$fclose(handle1|handle2|handle3);//本句不写也可以
end
endmodule
通过任何显示任务,例如$ display、$ write、$ monitor或者$ strobe任务中的%m选项的方式可以显示任何级别的层次。(%m:输出等级层次的名字)
//显示层次信息
module M;
initial
$display("Displaying in %m");
endmodule
//调用模块M
module top;
M m1( );
M m2( );
M m3( );
endmodule
仿真输出如下所示:
Displaying in top.m1
Displaying in top.m2
Displaying in top.m3
这一特征可以显示全层次路径名,包括模块实例、任务、函数和命名块。
$ strobe 和$ display任务除了一点小的差别外,其它非常相似。
如果许多其它语句和$ display任务在同一个时间单位执行,那么这些语句与$ display任务的执行顺序是不确定的。如果使用 $ strobe,该语句总是在同时刻的其他赋值语句执行完成后才执行
module ex6_22;
reg[3:0] a,b,c,d;
reg clock;
always @ (posedge clock)
$strobe("%t Displaying a = %b, c = %b",$time,a ,c);
//显示正跳变沿时刻的值
//选通显示
always @ (posedge clock)
begin
a = b ;
c = d ;
end
initial
clock=0;
always #10 clock=~clock;
initial
begin
b=4'h0;
d=4'h2;
#25;
b=4'h9;
d=4'h5;
end
endmodule
//在例6.22中,时钟上升沿的值在语句a = b和c = d执行完之后才显示。
//如果使用$display,$display可能在语句a = b和c = d之前执行,结果显示不同的值。
//run 45 74
有限状态机
----有限状态机(Finite State Machine)又称有限状态自动机或简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。
----在数字电路系统中,有限状态机是一种十分重要的时序逻辑电路模块,它对数字系统的设计具有十分重要的作用。
----有限状态机是指输出取决于过去输入部分和当前输入部分的时序逻辑电路。一般来说,除了输入部分和输出部分外,有限状态机还含有一组具有“记忆”功能的寄存器,这些寄存器的功能是记忆有限状态机的内部状态,它们常被称为状态寄存器。在有限状态机中,状态寄存器的的下一个状态不仅与输入信号有关,而且还与该寄存器的当前状态有关,因此有限状态机又可以认为是组合逻辑和寄存器逻辑的一种组合。其中,寄存器逻辑的功能是存储有限状态机的内部状态;而组合逻辑有可以分为次态逻辑和输出逻辑两部分,次态逻辑的功能是确定有限状态机的下一个状态,输出逻辑的功能是确定有限状态机的输出。
有限状态机结构模式相对简单,设计方案相对固定,有利于综合器优化。
容易构成性能良好的同步时序逻辑模块,有利于解决竞争冒险现象。
在高速运算和控制方面,状态机有其巨大优势。一个结构体可包含多个状态机,类似于并行运行的多CPU系统。
运行速度远远高于CPU。状态机状态变换周期只有一个时钟周期,而且每一状态可以完成许多并行运算和控制操作。
在可靠性方面,状态机优势明显。状态机可以使用容错技术;状态机进入非法状态并从中跳出所耗时间极短,通常只需2个时钟周期。 CPU一般数十ms。
- Melay型:输出是当前状态和所有输入信号的函数。
- Moore型:输出仅是当前状态的函数。
Melay型状态机的输出是输入变化后立即发生变化;而Moore型状态机在输入发生变化后,还需等到时钟到来,时钟使状态发生变化才导致输出变化。因此要多等待一个时钟周期。把状态机精确地分为这类或那类,其实并不重要,重要的是设计者如何把握输出的结构能满足设计的整体目标,包括定时的准确性和灵活性。
例1 Moore状态机状态s0时,输入为0维持s0,输入为1时,下一状态改为s1,不论输入是什么,输出均为0。
module moore_ex1(clk,din,op);
input clk,din;
output op;
reg op;
parameter s0=2'b00,
s1=2'b01,
s2=2'b10,
s3=2'b11;
reg[1:0] presentstate ;
reg[1:0] nextstate ;
always @(posedge clk)
presentstate <= nextstate;
always @(din,presentstate)
begin
case (presentstate)
s0: begin
if (din == 0)
nextstate = s0;
else
nextstate = s1;
op=0;
end
s1: begin
if (din == 1)
nextstate = s1;
else
nextstate = s2;
op=1;
end
s2: begin
if (din == 1)
nextstate = s2;
else
nextstate= s3;
op=0;
end
s3: begin
if (din == 1)
nextstate = s0;
else
nextstate = s1;
op=0;
end
default:begin
nextstate = s0;
op = 0;
end
endcase
end
endmodule
三特征
• 状态(state)总数是有限的
• 任意时刻只处于一种状态
• 某条件下会从一个状态转到下一个状态
四要素
• 当前状态:当前所处的状态
• 条件/事件:触发动作或转换的情况,例如输入
• 动作/转换:根据条件/事件,将一个状态转换到下一状态
• 下一状态:动作转换的下一状态,转换后这一状态就成了当前状态
注意项
• 状态不要漏掉
• 动作不要当做状态
“101” 序列检测器序列检测器
有“101”序列输入时输出为1,其他输入情况下,输出为0。画出状态转移图,并用Verilog描述。
'timescale命令用来说明跟在该命令后的模块的时间单位和时间精度。
格式如下:
'timescale<时间单位>/<时间精度>
在这条命令中,时间单位参量是用来定义模块中仿真时间和延迟时间的基准单位的。时间精度参量是用来声明该模块的仿真时间的精确程度的,该参量被用来对延迟时间值进行取整操作(仿真前),因此该参量又可以被称为取整精度。另外时间精度至少要和时间单位一样精确,时间精度值不能大于时间单位值。
在’timescale命令中,用于说明时间单位和时间精度参量值的数字必须是整数,其有效数字为1、10、100,单位为秒(s)、毫秒(ms)、微秒(us)、纳秒(ns)、皮秒(ps)、飞秒(fs)。
例
'timescale 1ns/1ps 时间单位/时间精度
在这个命令之后,模块中所有的时间值都表示是1ns的整数倍。这是因为在timescale命令中,定义了时间单位是1ns。模块中的延迟时间可表达为带三位小数的实型数。因为 'timescale命令定义时间精度为1ps。
例
‘timescale 10us/100ns
在这个例子中,’timescale 命令定义后,模块中时间值均为10us的整数倍。因为`timesacle 命令定义的时间单位是10us。延迟时间的最小分辨度为十分之一微秒(100ns),即延迟时间可表达为带2位小数的实型数。
‘timescale 10ns/1ns
module ex7_12;
reg set;
parameter d=1.55;
initial
begin
#d set=0;
#d set=1;
end
endmodule
时间单位10ns,时间精度1ns,10ns/1ns=10,因此延迟时间可带1位小数。程序中的#d就是#1.55,由于只能带1位小数,四舍五入变为1.6,1.6×10ns=16ns。所以参数d中的延迟时间实际上是16ns。
仿真时刻16ns时,set被赋值0;仿真时刻32ns时被赋值1。
’timescale 10ns/10ns
module ex7_12c;
reg set;
parameter d=1.55;
initial
begin
#d set=0;
#d set=1;
end
endmodule
时间单位10ns,时间精度10ns,10ns/10ns=1,因此延迟时间不能带小数。程序中的#d就是#1.55,由于不能带小数,四舍五入变为2,2×10ns=20ns。所以参数d中的延迟时间实际上是20ns。
仿真时刻20ns时,set被赋值0;仿真时刻40ns时被赋值1。
‘timescale 1ns/100ps
module ex7_12f;
reg set;
parameter d1=20.3,d2=23.36;
initial
begin
#d1;
set=0;
#d2;
set=1;
end
endmodule
时间单位1ns,时间精度100ps,1ns/100ps=10,因此延迟时间可带1位小数。程序中的#d1就是#20.3,由于能带1位小数,不需四舍五入,20.3×1ns=20.3ns。所以参数d1中的延迟时间实际上是20.3ns。
程序中的#d2就是#23.36,由于能只带1位小数,四舍五入成23.4。
23.4×1ns=23.4ns。所以参数d2中的延迟时间实际上是23.4ns。
仿真时刻20.3ns(20300ps)时,set被赋值0;仿真时刻20.3+23.4=43.7ns(43700ps)时被赋值1。
【一道状态机设计例题】:
module example(din,clk,pout)
input [1:0]din,clk;
output [2:0]pout;
reg din,clk;
reg pout;
reg [2:0] pstate,nexstate;
parameter zt0 = 3'b001,
zt1 = 3'b010,
zt2 = 3'b011,
zt3 = 3'b100,
zt4 = 3'b101;
assign pstage = zt0;
always@(posedge clk)
pstage<=nexstage;
always@(din)
begin
case(pstage)
zt0:
if(din == 0)
begin
nexstage <= zt0;
pout <= 101;
end
else
begin
nexstage <= zt1;
pout <= 011;
end
zt1:
if(din == 0)
begin
nexstage <= zt1;
pout <= 011;
end
else
begin
nexstage <= zt2;
pout <= 100;
end
zt2:
if(din == 0)
begin
nexstage <= zt2;
pout <= 100;
end
else
begin
nestage <= zt3;
pout <= 001;
end
zt3:
if(din == 0)
begin
nexstage <= zt3;
pout <= 001;
end
else
begin
nexstage <= zt4;
pout <= 111;
end
zt4:
if(din == 0)
begin
nexstage <= zt4;
pout <= 111;
end
else
begin
nexstage <= zt0;
pout <= 101;
end
endcase
end
endmoudle