尽量不要使用"大于""小于"这样的判断语句, 这样会明显增加使用的逻辑单元数量 .看一下报告,资源使用差别很大.
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语句里一定要加default;if一定要加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的算法转写过来,那么需要注意:
- 在不影响正确性或容量等前提下,把数字改小一点。比如你用了u_int32_t存储了一个永远不会超过128的数,请把它改成[6:0]
- 你需要进一步地分离逻辑,把对不同寄存器赋值的情形分割到不同的always block中去
- 对于一些没必要初始化的值,如本例中的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_ff、always_latch和always_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
本文讨论了如何通过优化Verilog代码结构,如使用Case语句替代if-else,避免冗余逻辑,以及合理利用BRAM,以减少在FPGA综合时对逻辑单元(LUT)的占用。作者强调了理解FPGA结构和设计工具行为的重要性。
626

被折叠的 条评论
为什么被折叠?



