1、位宽太小

        在FPGA设计中,我们经常需要用寄存器来寄存某些“数量类”的变量,比如FIFO的深度啦、或者计数器的最大值啦;又或者输入输出信号也需要将位宽用parameter参数化以便更好的调用等。

        举个简单的小例子:系统频率100M(周期10ns),假设需要要求设计一个计时器计时100ns,那么需要计数次数为:100ns/10ns - 1 = 9,9这个数需要用多大位宽的寄存器表示呢?很简单,以2为底取对数就行,答案是最少4位宽。为了方便地复用这个模块,我们把计时时间参数化并放到模块外,如下:

module	counter #(
	parameter	TIME = 'd10			//计时时间,单位10ns
)(
	input		clk_100M	,
	input		rst			
);
reg	[3:0]	cnt;					//计数器

//计时器
always@(posedge clk_100M)begin
	if(rst)
		cnt <= 0;
	else if(cnt == TIME - 1)
		cnt <= 0;
	else
		cnt <= cnt + 1;
end

endmodule

        假设我们下次设计需要一个计时器的话,直接调用上面的counter模块并把TIME这个参数改成自己需要的参数就可以,这样做理论上是可以的,只是会有一个致命的隐患。不妨再假设:我现在调用了counter模块,并将TIME设置为20,以实现计时200ns的功能。当TIME  = 20这个参数传递到被例化模块后,可以发现由于cnt寄存器的位宽仅为4位,其能表示的最大值为4'b1111(即十进制下的数字15),每次其到达15后就溢出为0重新开始了,也就是说这个200ns的计时器实际上根本就计数不到200ns。

        这个隐患发生的原因就是在设计寄存器cnt时的位宽只有4位,无法满足“大量时间的计时任务”。

2、自己写一个Function

        现在来想一下如何解决上述的位宽不匹配的问题。将寄存器的位宽设计为一个较大的数值(如固定为32bit)不失为一个不错的方法,但是如果将这条规则适用到每一个寄存器,则势必造成大量的资源浪费(你资源多你随便玩)。而且该方法指标不治本,我们需要做的是,这个寄存器应该有多大就设计多大的位宽(有多大的脚就穿多大的鞋,鞋子太大一定能穿,但你脚不一定舒服)。

        前面说过寄存器的位宽的计算方法:以2为底取对数。所以我们只需要设计一个Function(可综合),来实现此项功能即可。刚好在Xilinx的许多源码都出现了这个简单的Function,我们直接拿过来用就是的:

//	function 实现 
	function integer clogb2 (input integer bit_depth);              
		begin                                                           
			for(clogb2=0; bit_depth>0; clogb2=clogb2+1)                   
				bit_depth = bit_depth >> 1;                                 
		end                                                           
	endfunction                                                     

// 使用案例
	localparam integer C_TRANSACTIONS_NUM = clogb2(C_M_AXI_BURST_LEN-1);	 
	reg [C_TRANSACTIONS_NUM : 0] 	write_index;
	reg [C_TRANSACTIONS_NUM : 0] 	read_index;

        上面的代码就是定义了一个求位宽的function,用其求得某类寄存器的位宽,然后再对寄存器赋值时就直接使用求得的位宽来赋值,这样复用起来就比较方便了。

        我们将这个代码放到上面的计数器模块中后,不管需要计数多大时间,都能计算出相匹配的寄存器位宽了。

3、无法在输入输出端口使用

        自己写Function实现对2取对数的功能也有一定的局限性:无法对输入输出端口信号使用该Function。Function是定义在模块内部,所以若输入输出端口也需要根据输入的parameter参数来以2为底取对数的话此种方法就无能为力了。比如:设计一个同步FIFO,输出信号fifo_cnt(计数器)是对写入FIFO的数据进行计数的寄存器,其最大值即为FIFO的深度DATA_DEPTH ,所以fifo_cnt的位宽就需要在定义模块输入输出端口时确定,显然这无法使用自己构造的 cblogb2 Function。那该当如何?

//计数器法实现同步FIFO
module	sync_fifo_cnt
#(
	parameter   DATA_WIDTH = 'd8  ,							//FIFO位宽
    parameter   DATA_DEPTH = 'd16 							//FIFO深度
)
(
	input									clk		,		//系统时钟
	input									rst_n	,       //低电平有效的复位信号
	input	[DATA_WIDTH-1:0]				data_in	,       //写入的数据
	input									rd_en	,       //读使能信号,高电平有效
	input									wr_en	,       //写使能信号,高电平有效
															
	output	reg	[DATA_WIDTH-1:0]			data_out,	    //输出的数据
	output									empty	,	    //空标志,高电平表示当前FIFO已被写满
	output									full	,       //满标志,高电平表示当前FIFO已被读空
	output	reg	[$clog2(DATA_DEPTH) : 0]	fifo_cnt		//$clog2是以2为底取对数	
);
 
//省略部分代码
 
endmodule

4、$clog2系统函数

        其实办法也有,在上面的代码中也展示出来了,就是使用 $clog2 这个Verilog的系统函数。$clog2是Verilog--2005标准新增的一个系统函数,功能就是对输入整数实现以2为底取对数,其结果向上取整(如5.5取6)。有一点需要说明的是,目前Vivado2017以上的版本都是支持这个系统函数的(Quartus II不清楚 )。但是百度搜索这条结果的时候有两条结论是错误的:

        1、Vivado不支持$clog2系统函数

        2、$clog2系统函数在Vivado实现的是以e为底取对数,而不是2

        接下来写个简单的模块验证下Vivado对$clog2系统函数的支持如何

`timescale 1ns / 1ps

module clog2_test#(
	parameter integer	num = 325
)
(
	input 							clk,
	input							rst,
	output	reg	[$clog2(num) - 1:0]	result
);

always @(posedge clk)begin
	if(rst)
		result <= 0;
	else
		result <= result + 1;
end

endmodule

        我们直接看reg result的位宽综合出来到底是多少。如果以e为底向上取整,则位宽应是6;如果以2为底向上取整,则位宽应是9。Vivado综合的原理图局部如下:

        可以看到最后编译出的结果是9位的,也就说明Vivado是支持这个系统函数的(版本:2019.2)。

        其他变量的位宽设计同理。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐