FPGA(二)Verilog语法入门(二)

学习板:ZYNQ7020

Verilog语法入门(二)

  • 一、模块
    • 1、简述
    • 2、模块声明
    • 3、端口定义
    • 4、信号类型声明
    • 5、逻辑功能描述
    • (1)assign持续赋值描述
    • (2)always过程块描述
    • (3)调用内置门元件描述
  • 二、过程语句
  • (一)always语句
      • 1、敏感信号列表
      • 2、posedge与negedge关键字
      • 3、敏感列表的通配符
  • (二)initial语句
  • 三、块语句
  • (一)串行块begin-end
  • (二)并行块fork-join
  • 四、赋值语句
  • (一)持续初值语句assign
  • (二)过程赋值语句<=、=
    • 1、阻塞赋值
    • 2、非阻塞赋值
  • 五、条件语句
    • 1、 if-else语句
    • 2、case语句
  • 六、循环语句
    • 1、for语句
    • 2、repeat语句
    • 3、while语句
    • 4、forever
  • 七、函数(function)

一、模块

1、简述

Verilog的模块相当与面向对象语言中的类,它定义了具有公有和私有属性的逻辑集合,在设计中可以多次实例化。

Verilog中可以将模块将模块看作项目的子组件,并且该组件定义了与其它模块或IC的连接关系。

先举例说明:
FPGA(二)Verilog语法入门(二)_第1张图片

上图中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软件综合后的电路:
FPGA(二)Verilog语法入门(二)_第2张图片

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

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

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

你可能感兴趣的:(FPGA学习之路(仅自己可见),fpga开发,嵌入式硬件)