【深入浅出玩转FPGA学习2----设计技巧(基本语法)】

基本语法(可综合的Verilog语法子集)

所谓可综合的Verilog语法,是指硬件能够实现的一些语法。常用的RTL语法结构如下:
模块声明:module…endmodule.
端口声明:input,output,inout(inout的用法比较特殊,需要注意)。
信号类型:wire,reg,tri等,integer常用于for语句中(reg,wire是最常用的,一般tri和integer用在测试脚本里)。
参数定义:parameter。
运算操作符:各种逻辑操作符、移动操作符、算数操作符大多是可综合的(注:=== 与 !==是不可综合的)
比较判断:if…else,case(casex,casez)…default…endcase.
连续赋值:assign,问号表达式(?:)。
always模块:敏感表可以为电平、沿信号posedge/negedge;通常和@连用。
begin…end (通俗的说,它就是C语言里的“{}”)。
任务定义:task…endtask.
循环语句:for(用的也比较少,但是在一些特定的设计中使用它会起到事半功倍的效果)
赋值符号:=和<=(阻塞和非阻塞赋值,在具体设计中是很有讲究的)。
可综合的语法是Verilog可用语法里很小的一个子集,硬件设计的精髓就是力求用最简单的语句描述最复杂的硬件,这也正是硬件描述语言的本质。

If…else 与 case 语句分析

两者结构完全一致的情况

两段代码,EX1使用if…else语句,EX2使用case语句。

//EX1
input clk;
input rst_n;
input[3:0] data;
output[2:0] add;
reg[2:0] add;
always @(posedge clk) begin
	if(!rst_n) begin
		add<=0;
		end
	else begin
		if(data<4) add<=1;
		else if (data<8) add<=2;
		else if(data<12) add<=3;
		else add<=4;
		end
end
// EX2
input clk;
input rst_n;
input[3:0] data;
output[2:0] add;
reg[2:0] add;
always @(posedge clk) begin
	if(!rst_n) begin
		add<=0;
		end
	else begin
		case(data)
		0,1,2,3: add<=1;
		4,5,6,7: add<=2;
		8,9,10,11: add<=3;
		12,13,14,15: add<=4;
		default: ;
		endcase
		end
end

在这里插入图片描述
在这里插入图片描述
图2.1和图2.2分别是if…else 语句和case 语句综合后的RTL视图。 单从RTL视图来看,二者综合后的结果是有明显区别的。if…else趋向于有优先级的结构,而case则是并行的结构。
接下来看看它们所占用的资源情况。二者资源占用的情况基本是完全一样,连平均扇出也一致。
在这里插入图片描述在这里插入图片描述在这里插入图片描述
再看它们的Technology Map Viewer,分别如图2.3和图2.4所示。二者完全一致,所以,可以明确的说,在这个例子中,if…else和else 语句最终的实现都是并行的,而且完全一致。
在这里插入图片描述

两者结构不一样的情况

// if...else 示例代码
input clk;
input rst_n;
input close ,wr, rd;
output[2:0] db;
reg[2:0] dbr;
always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		dbr<=3'd0;
		end
	else begin
		if(close) dbr <=3'b111;
		else if(rd) dbr <=3'b101;
		else if(wr) dbr <=3'b011;
		else dbr <= 3'd0;
	end
end
assign db =dbr;
// case 示例代码
input clk;
input rst_n;
input close ,wr, rd;
output[2:0] db;
reg[2:0] dbr;
always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		dbr<=3'd0;
		end
	else begin
		case({close,rd,wr})
			3'b100:dbr <= 3'b111;
			3'b010: dbr <=3'b101;
			3'b001: dbr <=3'b011;
			default:dbr <= 3'do;
			endcase
	end
end
assign db =dbr;

对于上面两段代码,单从代码上分析,if…else是带优先级的,case是平行结构。
二者的资源消耗是存在差异的,那么二者的最终实现也不一定是不一样的。
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
从RTL视图看,二者的实现确确实实也正如早先所预期的,一个带优先级,一个并行处理。
在这里插入图片描述
从最终的布局布线后的结构看,和RTL视图很接近,这两个示例代码所使用的if…else和case最终实现的结构是有差异的。
从之前的实力分析看这并不稀奇,意外的是使用if…else实现的结构资源消耗居然比case要来的少(只是相对而言)。这样的结果似乎能够很好的反驳不少人提出的所谓“多用case语句,少用if…else语句,因为实现带优先级的结构比并行结构更耗费资源”的论断。
在这里插入图片描述
另外,该例子中使用多个if…if…语句实现的结果会和case语句的结果一致。

// if...if.... 示例代码
input clk;
input rst_n;
input close ,wr, rd;
output[2:0] db;
reg[2:0] dbr;
always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		dbr<=3'd0;
		end
	else begin
	dbr <=3'd0;
	if({close,rd,wr}==3'b100) dbr<=3'b111;
	if({close,rd,wr}==3'b010) dbr <=3'b101;
	if({close,rd,wr}==3'b001) dbr <=3'b011;
	end
end
assign db =dbr;

Verilog 代码优化之for语句

Verilog中的for语句虽然也是可综合的,但在RTL级的代码中基本不用。一方面是因为for语句的使用是很占用硬件资源的,另一方面是因为在设计中往往是采用时序逻辑设计,用到for循环的地方不多。
下面用一个实例说明for循环语句综合实现的结果。

module test_3(clk,rst_n,data,num);
input clk;
input rst_n;
input[12:0] data;   //输入13路数据
output[15:0] num;   //13路数据电平为高的路数
reg[3:0] i;
reg[15:0] num;
always @ (posedge clk) begin
	if(!rst_n) begin
		num <=0;
		end
	else begin
		for(i=0;i<13;i=i+1)     //用for循环进行计算
			if(data[i]) num<= num+1;
		end
	end
	endmodule

这段代码的用意是在一个时钟周期内计算出13路脉冲信号为高电平的个数,一般人都会感觉这个任务交给for循环来做再合适不过了,但是for循环不能够完成这个任务。
在这里插入图片描述
相信你已经发现问题了,为什么每个时钟周期for循环只执行以此num<=num+1呢?always 语句中使用非阻塞赋值“<=”时,是在always 结束后才把值赋给左边的寄存器,因此才出现了上面的情况。重新用阻塞语句写了如下代码:

module test_3(clk,rst_n,data,num);
input clk;
input rst_n;
input[12:0] data;   //输入13路数据
output[15:0] num;   //13路数据电平为高的路数
reg[3:0] i;
reg[15:0] num;
always @ (posedge clk) begin
	if(!rst_n) begin
		num =0;
		end
	else begin
		for(i=0;i<13;i=i+1)     //用for循环进行计算
			if(data[i]) num = num+1;
		end
	end
	assign numout = num;
	endmodule

重新修改后的代码进行仿真的波形如图所示:
在这里插入图片描述
此波形说明了修改后的代码达到了实验目的。看来for语句在这种情况下还是比较省事的,如果不用for语句就比较繁琐了。但是话说回来,for语句综合的效率不高,在对速度要求不高的前提下,还是宁愿用多个时钟周期去实现也不用for语句。
此外,一般在时序逻辑里多用非阻塞赋值语句"<=",像后面一个代码的风格其实是不可取的。硬件语言不能像C语言一样片面地追求代码的简洁。

inout用法

如表所示,它指明了在同等驱动强度下,两个驱动的wire型和tri型变量的真值表。
在这里插入图片描述
如果某时刻inout端口有输入,此时又正好要拿这个inout端口做输出,那么冲突是在所难免的,会出现什么样的结果可以参考真值表。
下面是一种典型的inout端口的使用方法:

inout io_data;     //inout口
reg out_data;    //需要输出的数据
reg io_link;        //inout口方向控制
assign io_data = io_link ? out_data : 1'bz;   //这个是关键

当inout端口作为输入口使用时,一定要把它置为高阻态,让例子中的io_link=0即可;当inout端口作为输出口使用时,则将实例中的io_link =1,对out_data赋值就可以了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

周猿猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值