学习板:ZYNQ7020
Verilog的模块相当与面向对象语言中的类,它定义了具有公有和私有属性的逻辑集合,在设计中可以多次实例化。
Verilog中可以将模块将模块看作项目的子组件,并且该组件定义了与其它模块或IC的连接关系。
上图中A为与门、B为与非门、C为或非门,输入a、b、c、d,输出f,则这个逻辑电路可以当做一个模块,模块代码:
module aoi(a,b,c,d,f); //模块名aoi、端口列表 a、b、c、d、f
input a,b,c,d; //输入端口为a、b、c、d
output f; //输出端口为f
wire a,b,c,d,f; //定义信号的数据类型
assign f=~((a&b)|(~(c&d))); //逻辑功能描述
endmodule
从上面代码中可以看到,模块由两部分组成,一部分是接口描述,描述输入输出端口、信号的数据类型等;另一部分就是描述逻辑功能。
从书写形式上来看,具有以下特点:
① Verilog程序由模块组成,每个模块有关键字module开始、endmodule结束。
②每个模块首先要进行端口定义,并说明输入、输出口(input、output、inout),之后再进行逻辑功能描述。
③endmodule 后面没有分号“;”
每个verilog程序包括:模块声明、端口定义、信号类型声明、逻辑功能描述
整体框架:
moudle 端口名(端口列表);
input 输入端口列表;
output 输出端口列表;
inout 双向端口列表;
wire 线型数据类型列表;
reg 寄存器型数据类型列表;
parameter 参数型数据类型列表;
assign …(逻辑功能描述)…;
always …(逻辑功能描述)…;
function…(逻辑功能描述)…;
task…(逻辑功能描述)…;
… …(逻辑功能描述)…;
endmoudle
可以看到与我们之前画的原理图是一致的。
当然我们可以把端口列表、定义合在一起:
module aoi(
input wire a,
input wire b,
input wire c,
input wire d,
output wire f
);
assign f=~((a&b)|(~(c&d)));
endmodule
模式声明格式:
module 模块名(端口1、端口2…端口n);
模块结束时用关键字:endmodule
结合起来,一个模块:
module 模块名(端口1、端口2...端口n);
...
...
endmodule
端口定义格式:
input 端口1,端口2…端口n;
output 端口1,端口2…端口n;
inout 端口1、端口2…端口n;
定义端口的注意事项:
①输入、双向端口不能定义为寄存器型
②测试模块中不需要定义端口
对模块中所用的的所有信号,如端口信号(输入、输出)、节点信号的数据类型进行定义。
如:
reg aout; //定义信号aout为reg型
reg[7:0] bout; //信号bout为8位reg型
wire a,b,c,d; //信号a、b、c、d为wire型
如果信号的类型没有被定义,综合时,综合器默认该信号为wire型。
可以将端口声明和信号类型声明放在同一条语句中:
output wire a; //定义a为输出端口、数据类型为wire型
output reg[7:0] out; //定义out为输出端口、数据类型为reg型
input wire b; //定义b为输入信号,数据类型为wire型
可以将端口声明和信号类型声明放在模块端口列表中:
如最开始的那个例子:
module aoi(a,b,c,d,f); //模块名aoi、端口列表 a、b、c、d、f
input a,b,c,d; //输入端口为a、b、c、d
output f; //输出端口为f
wire a,b,c,d,f; //定义信号的数据类型
assign f=~((a&b)|(~(c&d))); //逻辑功能描述
endmodule
可以写为:
module aoi(input wire a,b,c,d,
output wire f);
assign f=~((a&b)|(~(c&d)));
endmodule
可以用assign语句持续赋值来描述逻辑功能
如上面的例子:
assign f=~((a&b)|(~(c&d)));
还是最开始的那个例子,用always语句描述:
module aoi(input wire a,b,c,d,
output reg f);
//always语句中赋值的变量应该定义为reg型
always @(a or b or c or d) //敏感信号列表
//或写成always @(*)
begin
f=~((a&b)|(~(c&d)));
end
endmodule
always语句后面会详细总结。
内置门元件有:
类别 | 关键字 | 名称 |
---|---|---|
多输入门 | and | 与门 |
多输入门 | nand | 非门 |
多输入门 | or | 或门 |
多输入门 | nor | 或非门 |
多输入门 | xor | 异或门 |
多输入门 | xnor | 异或非门 |
多输出门 | buf | 缓冲器 |
多输出门 | not | 非门 |
多输出门 | bufif1 | 高电平使能三态缓冲器 |
多输出门 | bufif0 | 低电平使能三态缓冲器 |
多输出门 | notif1 | 高电平使能三态非门 |
多输出门 | notif0 | 低电平使能三态非门 |
还有一些上拉、下拉电阻、mos等,这里就不再列举了。
①多输入门内置元件用法:
格式:
内置门元件名 例化门名称(输出,输入1、输入2…);
或者:
内置门元件名 (输出,输入1、输入2…);
and myand(out, ina,inb,inc); //与门
nand mynand(out, ina,inb); //与非门
②三态门内置元件用法:
格式:
内置三态门名称 例化名称 (输出,输入,使能)
或
内置三态门名称 (输出,输入,使能)
buffif1 mybuff(out,in,enable);
还是最开始的那个例子:
module aoi(input wire a,b,c,d,
output reg f);
//always语句中赋值的变量应该定义为reg型
wire f1,f2;
always @(a or b or c or d) //敏感信号列表
//或写成always @(*)
begin
// f=~((a&b)|(~(c&d)));
and(f1,a,b);
nand(f2,c,d);
nor(f,f1,f2);
end
endmodule
verilog的语句:
类别 | 语句 | 可综合性 |
---|---|---|
过程语句 | initial | 不可综合 |
过程语句 | always | 可综合 |
块语句 | 串行块:begin-end | 不可综合 |
块语句 | 并行块:fork-join | 不可综合 |
赋值语句 | 持续赋值:assign | 可综合 |
赋值语句 | 过程赋值=、<= | 可综合 |
条件语句 | if-else | 可综合 |
条件语句 | case | 可综合 |
循环语句 | for | 可综合 |
循环语句 | while | 不可综合 |
循环语句 | repeat | 不可综合 |
循环语句 | forever | 不可综合 |
编译指示语句 | ‘define | 可综合 |
编译指示语句 | ‘include | 不可综合 |
编译指示语句 | ‘ifdef、‘else、‘endif | 可综合 |
在一个模块中,使用initial和always语句的次数是不受限制的,而initial语句只执行一次,常用于仿真中的初始化;always语句可以不断执行。
always语句的格式:
always @(敏感信号列表)
begin
...
end
always语句的执行时需要触发的,触发条件在敏感列表中。
当敏感信号列表中的变量的值改变时,就会触发always语句。敏感列表中的多个变量用“or”隔开。
always @(a) //信号a发生变化时触发
always @(a or b) //信号a或b发生变化时触发
always @(posedge clk) //clk上升沿触发
always @(posedge clk or negedge reset) //clk上升沿触发或reset下降沿触发
敏感信号分为:边沿敏感信号和电平敏感信号。每个always语句最好用一类敏感信号来触发,避免将边沿敏感信号和电平敏感信号写在同一个always敏感列表中,如果既有边沿敏感信号又有电平敏感信号,则可用两个always语句来实现。
上面代码的前两行就是电平敏感信号,第三行、第四行为边沿敏感信号
这两个关键字一般用于时序电路,通常由时钟边沿触发。
格式:
always @(posedge ... or negedge ...)
需要注意的是always块内的逻辑描述一定要与敏感列表中的电平一致
如:
always @(negedge clk)
begin
if(clk)//这是错的
...
上面触发always语句的条件是时钟信号clk的下降沿,但是块语句中当clk高电平时执行某些语句,这是错误的。因为下降沿触发后,clk变为低电平,if语句是不能执行的。
如果always语句的敏感列表变量太多,可以用通配符“ * ”来表示。需要注意的是。使用通配符后,always块内的所有变量都变为敏感,即always块语句内的所有变量,发生改变时都会触发always语句。
通配符使用的两种格式:
always @ *
begin
//....
end
always @(*)
begin
//....
end
initial语句的格式:
initial
begin
语句1;
语句2;
...
end
initial语句只执行一次,通常用于仿真模块对激励信号的描述、给寄存器赋初值,通常不能被逻辑综合器综合。
如用initial语句对储存器赋初值:
initial
begin
for(addr=0;addr<size;addr=addr+1)
memory[addr]=0;
end
begin-end语句按照串行方式顺序执行,一条语句执行完毕后,才会执行下一条语句,例:
begin
regb=rega;
regc=regb;
end
程序先执行regb=rega;
再执行regc=regb;
,所以块语句结束后rega、regb、regc的值是一样的。
fork-join块内的语句是并行执行的,比如赋值语句,先全部计算块内的等号右边的等式,再在块语句执行完毕后更新等号左侧的数据。
例:
fork
regb=rega;
regc=regb;
join
开始执行时,先计算所有等号右边的值,块语句结束后,将刚刚计算的右边的值赋值给左边。所有上面这个代码结束后,regb和rega的初值相等,regc和regb的初值相等。
用assign持续赋值,主要用于对wire变量的赋值。
例如:
module myand(input wire a,input wire b,output wire c);
assign c=a&b;
endmodule
上面程序中的模块,持续输出a&b,表现在c的电平一直随输入a、b的变化而变化。
过程赋值语句分为阻塞赋值和非阻塞赋值。
符号为“=”,与c语言的赋值一致。在该语句结束时就立即完成赋值操作。
例:
...
b=a;
c=d;
...
上面代码 第一句结束时,就完成a对b的赋值。
符号为“<=”,在整个块结束时才完成赋值操作。
如
...
b=a;
c=d;
...
在块语句没有结束之前,先计算等号右边的值,即先计算a、d的值,在整个块语句结束后。在完成赋值操作,此时b、c的值才改变。
三种情况:
if(表达式) 语句1:
if(表达式) 语句1:
else 语句2:
if(表达式) 语句1:
else if(表达式) 语句2:
else if(表达式) 语句3:
....
else 语句n:
注意case语句没有switch !
case语句的格式:
case(敏感表达式)
值1: 语句1;
值2: 语句2;
....
....
值n: 语句n;
default 语句n+1;
endcase
case语句中重要的两个东西:casex与casez。
在case中,敏感表达式要与值1~n全等比较,必须保证两者的对应的每一位都相同。
casez语句中,如果分支表达式有些位为高阻态,则这些位不与考虑。
如:
casez(5'b10110)
5'b11111: ...
5'b10zz0:....
....
则执行5'b10zz0:
分支的代码。
casex语句与casez类似,casex忽略分支表达式的不定态的位。
如:
casex(5'b10110)
5'b11111: ...
5'b10xx0:....
....
则执行5'b10xx0:
分支。
Verilog有4种循环语句:
①for: 符合条件的循环
②repeat:连续执行一条语句n次
③while:执行语句直到条件不成立
④forever:连续的执行语句,一般用作initial块中,生成时钟等。
for语句与c语言一致,其格式:
for(循环变量赋初值;循环结束条件;循环变量增值 )
语句:
repeat:连续执行一条语句n次,其格式:
repeat (循环次数表达式)
begin
语句、语句块
end
while语句格式:
while(循环执行条件表达式)
begin
语句、语句块
end
forever连续的执行语句,会一直持续下去。其格式:
forever
begin
语句、语句块
end
如果想终止forever语句,可以用disable关键字,该关键字可以终止任意循环。
在模块中,如果多次使用重复的代码,可以将代码定义为一个函数。综合时,每调用一次函数,则复制或平铺一次该函数对于的电路。为了不对复制或平铺电路造成负担,调用的函数应该不能过于复杂。
function 返回值位宽或类型说明 函数名;
端口声明;
局部变量定义;
其他语句;
endfunction
如果返回值位宽或类型没有说明,默认返回位宽为1的寄存器类型的数据。
例:获取输入数据各个位中0的个数:
function[7:0] get0;
input [7:0] a;
reg[7:0] sum;
integer i;
begin
sum=0;
for(i=0;i<8;i=i+1)
if(a[i]==1'b0)
sum=sum+1;
get0=sum;
end
endfunction
注意上面代码,返回值就是函数名。
函数的定义包含了与函数同名、函数内部的寄存器。在函数定义时,将函数返回值所使用的寄存器名称设为与函数同名的内部变量,所以函数名被赋予的值就是函数的返回值。
所以在function里直接对函数名赋值即可。调用function也比较简单,如上面的程序:
assign out=get0(8'b10101010);
注意,上面的function里定义了input类型的input [7:0] a;
所以调用函数get0时必须将输入数据写进调用函数的括号