RTL层面减少FPGA综合中逻辑单元的使用数量 / Reducing the number of LUT utilization in your FPGA synthesis in your RTL

本文讨论了如何通过优化Verilog代码结构,如使用Case语句替代if-else,避免冗余逻辑,以及合理利用BRAM,以减少在FPGA综合时对逻辑单元(LUT)的占用。作者强调了理解FPGA结构和设计工具行为的重要性。

尽量不要使用"大于""小于"这样的判断语句, 这样会明显增加使用的逻辑单元数量 .看一下报告,资源使用差别很大.

always@(posedge clk)
   begin
    count1=count1+1;
   if(count1==10000000) // 如果把这句话if(count1==10000000)改成大于小于,报告中用了135个逻辑单元
     feng=1;    //no_ring
    else if(count1==90000000)
     begin
      feng=0;   //ring
      count1=0;
    end
end            //这么写会用107个逻辑单元

case语句里一定要加defaultif一定要加else

如果是组合逻辑的设计,不加default或else的话,不能保证所有的情况都有赋值,就会在内部形成一个锁存器,而不再是一个纯粹的组合逻辑了。例如:

case({a,b})
	2'b11 e=b;//不加default,虽然只关心a=1时的结果,但是a=0的时候,e就会保存原来的值,直到a变为1
	2'b10 e=a;//那么e要保存原来的值,就要在内部生成锁存器了.
endcase

尽量使用Case语句 而不是if–else语句

复杂的if–else语句通常会生成优先级译码逻辑,这将会增加这些路径上的组合时延。举个例子,

always @(posedge i_clk)
if (RESET)
begin
	valid <= 1'b0;
	vector <= 0;
end else if (CE) begin
	valid <= (input is valid);
	vector <= someOper(someValue);
end

它的麻烦在于,在第二个begin-end块中的所有逻辑,都是依赖RESET和CE两个值的。由于RESET及其反相信号不能接入同一个LUT,这样就用掉两个了。此外还有一个对习惯软件的程序员很反直觉的地方,就是如果本例中的B是个很长的向量的话,那么每个bit都会用掉两个LUT。它可不是你想象中的~RESET && CE同时fan-out连出好多根线接到vector的每个位上的。

所以,如果你的Verilog是从一个C的算法转写过来,那么需要注意:

  1. 在不影响正确性或容量等前提下,把数字改小一点。比如你用了u_int32_t存储了一个永远不会超过128的数,请把它改成[6:0]
  2. 你需要进一步地分离逻辑,把对不同寄存器赋值的情形分割到不同的always block中去
  3. 对于一些没必要初始化的值,如本例中的vector,如果写入总是先于读取,就完全可以省略这部分逻辑,又能节约很多个LUT。

考虑用case语句的话,可以采用[3]中提出的方法,对组合逻辑进行编码。比如
第一版

always @(*)
        ov = (dbgv | memv) || (!clear && wb) && 
        	((aluv && !aluerr) || (memv && !memerr)
			||(divv && !diverr) || (fpuv && !fpuerr));
			
always @(*)
        if (memv)
                odata = memdata;
        else if (divv | fpuv)
                odata = (divv ? divdata : fpudata);
        else if (dbgv)
                odata = dbgdata;
        else // if (aluv)
                odata = aludata;

其中表达式中的变量均为module输入。修长的逻辑表达式和多级if-else,很像C代码。而对应的综合结果资源使用统计如下

   Number of wires:                117
   Number of wire bits:            303
   Number of public wires:          19
   Number of public wire bits:     205
   Number of memories:               0
   Number of memory bits:            0
   Number of processes:              0
   Number of cells:                131
     SB_LUT4                       131

如果我们引入一些编码机制,如

initial index = 0;
always @(posedge i_clk)
if (pred)			index <= 3'h0;
else if (prealu)	index <= 3'h1;
else if (premem)	index <= 3'h2;
else if (prediv)	index <= 3'h3;
else // if (fpuv)	index <= 3'h4;

always @(*) case(index)
	3'h0: ov = (dbgv);
	3'h1: ov = (clear && !wb) && (aluv && !aluerr);
	3'h2: ov = (memv);
	3'h3: ov = (clear && !wb) && (divv && !diverr);
	3'h4: ov = (clear && !wb) && (fpuv && !fpuerr);
	default: ov = 0;
endcase

always @(*)
	case(index)
	3'h0: odata = dbgdata;
	3'h1: odata = aludata;
	3'h2: odata = memdata;
	3'h3: odata = divdata;
	default: odata = fpudata;
endcase

多了三个DFFSR(D Flip-Flop & SR Latch),少了21个LUT4,可以说相当划算了。如果data再长一点节约的数量会更多。

   Number of wires:                103
   Number of wire bits:            291
   Number of public wires:          26
   Number of public wire bits:     214
   Number of memories:               0
   Number of memory bits:            0
   Number of processes:              0
   Number of cells:                113
     SB_DFFSR                        3
     SB_LUT4                       110

组合逻辑的always块中,输入全部放入敏感列表里

always@(a or b) begin
	out=(a&b&c);
end

此时生成的不是纯的组合逻辑,因为当C变化时, out不会立刻发生变化(需要等到a或b变化,c的变化才会显现), 所以需要生成一个寄存器来保存C的值。确切地说,这是Verilog在综合过程中较为迷惑之处,在加入敏感列表后,它严格来说就不再是组合逻辑,而是由锁存器(Latch)驱动的电路了,也就是说它是由本例中a和b的电平变化来驱动的电路。D触发器是由两个SR锁存器构成的,所以它可以使用更少的电路,但是在实际的数字电路中,它会直接导致双稳态。

在SystemVerilog中,应该使用always_ffalways_latchalways_comb加以区分。

尽量使用Block RAM(BRAM)

通常,如果你声明了一个向量构成的数组,如

logic [31:0] mem [1024];

你会期望它使用FPGA中的BRAM,而不是把大量的LUT当成寄存器来用。但是当你使用一个10位的寄存器引用mem某个地址上的数据时,倘若在不同的逻辑分支中使用了不同的寄存器,那么它就不会被综合成BRAM。比如

always @(posedge clk)
	if (A)
		mem[addrA] <= valA;
	else if (B)
		mem[addrB] <= valB;
	else if (C)
		mem[addrC] <= valC;

如果能换一种逻辑,比如

//分离write enable信号
always @(posedge i_clk)
	wr <= ((A)||(B)||(C));

// 分离读取mem的地址(这里需要引入新的寄存器wAddr)
always @(posedge i_clk)
	if (A)
	begin
		wAddr <= addrA;
		wData <= valA;
	end else if (B)
	begin
		wAddr <= addrB;
		wData <= valB;
	end else if (C)
	begin
		wAddr <= addrC;
		wData <= valC;
	end

// 只有形成在单一使能信号下通过单一的地址/数据寄存器对mem进行读写,才能被综合成mem
always @(posedge i_clk)
	if (wr)
		mem[wAddr] <= wData;

通过多引入寄存器节约LUT还是很划算的,但是这意味着你需要对代码结构进行大量更改。如果读写数据是在状态机下控制,那么需要引入更多寄存器来调节时序(retiming)。

除此之外,Xilinx Vivado支持你通过ram_style标记来修饰mem。然而它仍然取决于你的逻辑。

结论

介绍了若干种减少RTL代码在综合时对FPGA资源的占用的方法,其中以if语句的一系列使用为主,此外是推导BRAM及其它算符的使用。同时,开发者需要熟悉FPGA的结构以及设计/综合工具的行为。

目前Vivado等套件并不能在(System)Verilog代码之上理解你的意图,所以将一个近似于计算机编程语言的代码翻译为可以通过仿真的RTL代码,到贴合FPGA甚至ASIC硬件结构的过程,仍然是一门艺术。需要人类在头脑中完成映射工作。

References:

[1] https://blog.csdn.net/qq_33929689/article/details/52067290
[2] https://zipcpu.com/blog/2017/06/12/minimizing-luts.html
[3] http://zipcpu.com/zipcpu/2019/03/28/return-decoding.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值