FPGAVerilog语法入门

Posted 独独白

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FPGAVerilog语法入门相关的知识,希望对你有一定的参考价值。

学习板:ZYNQ7020

一、模块

1、简述

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

上面代码通过vivado软件综合后的电路:

可以看到与我们之前画的原理图是一致的。

当然我们可以把端口列表、定义合在一起:

module aoi(
input wire a,
input wire b,
input wire c,
input wire d,
output wire f
);
assign f=~((a&b)|(~(c&d)));
endmodule

2、模块声明

模式声明格式:

module 模块名(端口1、端口2…端口n);

模块结束时用关键字:endmodule

结合起来,一个模块:

 module 模块名(端口1、端口2...端口n);
 ...
 ...
 endmodule

3、端口定义

端口定义格式:

input 端口1,端口2…端口n;
output 端口1,端口2…端口n;
inout 端口1、端口2…端口n;

定义端口的注意事项:

①输入、双向端口不能定义为寄存器型

②测试模块中不需要定义端口

4、信号类型声明

对模块中所用的的所有信号,如端口信号(输入、输出)、节点信号的数据类型进行定义。

如:

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

5、逻辑功能描述

(1)assign持续赋值描述

可以用assign语句持续赋值来描述逻辑功能

如上面的例子:

assign f=~((a&b)|(~(c&d))); 

(2)always过程块描述

还是最开始的那个例子,用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语句后面会详细总结。

(3)调用内置门元件描述

内置门元件有:

类别关键字名称
多输入门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可综合

(一)always语句

在一个模块中,使用initial和always语句的次数是不受限制的,而initial语句只执行一次,常用于仿真中的初始化;always语句可以不断执行。

always语句的格式:

always @(敏感信号列表)
begin
...
end

always语句的执行时需要触发的,触发条件在敏感列表中。

1、敏感信号列表

当敏感信号列表中的变量的值改变时,就会触发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语句来实现。

上面代码的前两行就是电平敏感信号,第三行、第四行为边沿敏感信号

2、posedge与negedge关键字

这两个关键字一般用于时序电路,通常由时钟边沿触发。

格式:

always @(posedge ... or  negedge ...)

需要注意的是always块内的逻辑描述一定要与敏感列表中的电平一致

如:

always @(negedge clk)
begin
if(clk)//这是错的
...

上面触发always语句的条件是时钟信号clk的下降沿,但是块语句中当clk高电平时执行某些语句,这是错误的。因为下降沿触发后,clk变为低电平,if语句是不能执行的。

3、敏感列表的通配符

如果always语句的敏感列表变量太多,可以用通配符“ * ”来表示。需要注意的是。使用通配符后,always块内的所有变量都变为敏感,即always块语句内的所有变量,发生改变时都会触发always语句。

通配符使用的两种格式:

always @ *
begin
//....
end
always @(*)
begin
//....
end

(二)initial语句

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-end语句按照串行方式顺序执行,一条语句执行完毕后,才会执行下一条语句,例:

	begin 
		regb=rega;
		regc=regb;
	end	

程序先执行regb=rega;再执行regc=regb;,所以块语句结束后rega、regb、regc的值是一样的。

(二)并行块fork-join

fork-join块内的语句是并行执行的,比如赋值语句,先全部计算块内的等号右边的等式,再在块语句执行完毕后更新等号左侧的数据。

例:

	fork
		regb=rega;
		regc=regb;
	join

开始执行时,先计算所有等号右边的值,块语句结束后,将刚刚计算的右边的值赋值给左边。所有上面这个代码结束后,regb和rega的初值相等,regc和regb的初值相等。

四、赋值语句

(一)持续初值语句assign

用assign持续赋值,主要用于对wire变量的赋值。
例如:

module myand(input wire a,input wire b,output wire c);
	assign c=a&b;
endmodule

上面程序中的模块,持续输出a&b,表现在c的电平一直随输入a、b的变化而变化。

(二)过程赋值语句<=、=

过程赋值语句分为阻塞赋值和非阻塞赋值。

1、阻塞赋值

符号为“=”,与c语言的赋值一致。在该语句结束时就立即完成赋值操作。
例:

...
b=a;
c=d;
...

上面代码 第一句结束时,就完成a对b的赋值。

2、非阻塞赋值

符号为“<=”,在整个块结束时才完成赋值操作。

...
b=a;
c=d;
...

在块语句没有结束之前,先计算等号右边的值,即先计算a、d的值,在整个块语句结束后。在完成赋值操作,此时b、c的值才改变。

五、条件语句

1、 if-else语句

三种情况:

if(表达式)				语句1if(表达式)				语句1else					语句2if(表达式)				语句1else if(表达式) 			语句2else if(表达式) 			语句3....
else					语句n:

2、case语句

注意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块中,生成时钟等。

1、for语句

for语句与c语言一致,其格式:

for(循环变量赋初值;循环结束条件;循环变量增值 )
	语句:

2、repeat语句

repeat:连续执行一条语句n次,其格式:

repeat (循环次数表达式)
	begin
	语句、语句块
	end

3、while语句

while语句格式:

while(循环执行条件表达式)
	begin
		语句、语句块
	end

4、forever

forever连续的执行语句,会一直持续下去。其格式:

forever 
	begin
		语句、语句块
	end

如果想终止forever语句,可以用disable关键字,该关键字可以终止任意循环。

七、函数(function)

在模块中,如果多次使用重复的代码,可以将代码定义为一个函数。综合时,每调用一次函数,则复制或平铺一次该函数对于的电路。为了不对复制或平铺电路造成负担,调用的函数应该不能过于复杂。

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时必须将输入数据写进调用函数的括号

以上是关于FPGAVerilog语法入门的主要内容,如果未能解决你的问题,请参考以下文章

FPGAVerilog语法入门

FPGAVerilog语法入门

FPGAVerilog语法入门

FPGAVerilog语法入门

FPGAverilog实现的i2c接口控制

FPGAVerilog:时序电路应用 | 序列发生器 | 序列检测器