从零开始设计riscv cpu九之降低LUT使用量语法技巧

📅 2026/7/25 1:44:07 👁️ 阅读次数 📝 编程学习
从零开始设计riscv cpu九之降低LUT使用量语法技巧

一、首先知道LUT是什么

LUT(Look-Up Table,查找表)是 FPGA 中最核心的可编程逻辑资源。

你可以把它想象成一个真值表:输入信号作为地址,输出就是预先存储在表中的结果。

输入 A输入 B输出(AND)
000
010
100
111

二、LUT的使用方式,有两种

模式本质用途
LUT(逻辑模式)查找表实现组合逻辑(与、或、异或、多路选择器等)
LUTRAM(存储模式)同一个物理 LUT,配置为小型 RAM存储数据,作为分布式 RAM 使用

不要看LUTRAM带着RAM,其实就是LUT,是LUT实现的RAM。

关键认知:LUTRAM 本质上就是 LUT,是 LUT 实现的 RAM。1 LUTRAM = 1 LUT(物理同一资源),通过配置位实现不同的用途。

然后可以看下面的这个真实的综合出来的资源使用结果,一目了然。


看第一行top这一行就足够:使用logic LUT 3659个 + LUTRAM 212个 = 总共的3871个

至此应该对LUT的使用清楚了。

三、现在看ram的实现方式

除了 Block RAM(BRAM),还有LUTRAM / 分布式 RAM

通过ram_style属性控制实现方式:

(* ram_style = “block”)
(
ram_style = “distributed” *)

实现方式适用场景
"block"Block RAM(BRAM)大容量存储(>1KB),需要独立时钟端口
"distributed"LUTRAM / 分布式 RAM小容量存储,需要异步读,或 BRAM 资源紧张
"auto"工具自动选择默认行为,由综合工具决定

四、现在来看怎么减少LUT使用。

五、先看代码

`timescale1ns/1ps `include"defines.v"// Tournament direction predictor with bimodal, gshare, and a PC-indexed chooser.module bht #(parameter ENTRY_NUM=256,parameter INDEX_W=8,parameter GHR_W=4)(input wire clk,input wire rst,input wire[`AW-1:0]lookup_pc_i,output wire predict_taken_o,output wire[INDEX_W-1:0]lookup_index_o,output wire bimodal_taken_o,output wire gshare_taken_o,input wire update_valid_i,input wire[`AW-1:0]update_pc_i,input wire[INDEX_W-1:0]update_index_i,input wire update_taken_i,input wire update_bimodal_taken_i,input wire update_gshare_taken_i);reg[1:0]bimodal_counter_r[0:ENTRY_NUM-1];reg[1:0]gshare_counter_r[0:ENTRY_NUM-1];reg[1:0]chooser_counter_r[0:ENTRY_NUM-1];reg[GHR_W-1:0]ghr_r;wire[INDEX_W-1:0]lookup_pc_index;wire[INDEX_W-1:0]update_pc_index;wire[INDEX_W-1:0]ghr_extended;wire choose_gshare;assign lookup_pc_index=lookup_pc_i[INDEX_W+1:2];assign update_pc_index=update_pc_i[INDEX_W+1:2];assign ghr_extended={{(INDEX_W-GHR_W){1'b0}},ghr_r};assign lookup_index_o=lookup_pc_index^ghr_extended;//assign lookup_index_o = lookup_pc_index ^ ghr_r;assign bimodal_taken_o=bimodal_counter_r[lookup_pc_index][1];assign gshare_taken_o=gshare_counter_r[lookup_index_o][1];assign choose_gshare=chooser_counter_r[lookup_pc_index][1];assign predict_taken_o=choose_gshare?gshare_taken_o:bimodal_taken_o;integer i;initial beginif(ENTRY_NUM!=(1<<INDEX_W))begin $error("BHT: ENTRY_NUM must equal 2^INDEX_W");$finish;endif((GHR_W<2)||(GHR_W>INDEX_W))begin $error("BHT: GHR_W must be in the range 2..INDEX_W");$finish;end end always @(posedge clk)beginif(rst==`RstEnable)begin ghr_r<={GHR_W{1'b0}};for(i=0;i<ENTRY_NUM;i=i+1)begin bimodal_counter_r[i]<=2'b01;gshare_counter_r[i]<=2'b01;chooser_counter_r[i]<=2'b01;end endelseif(update_valid_i)beginif(update_taken_i)beginif(bimodal_counter_r[update_pc_index]!=2'b11)bimodal_counter_r[update_pc_index]<=bimodal_counter_r[update_pc_index]+2'b01;if(gshare_counter_r[update_index_i]!=2'b11)gshare_counter_r[update_index_i]<=gshare_counter_r[update_index_i]+2'b01;endelsebeginif(bimodal_counter_r[update_pc_index]!=2'b00)bimodal_counter_r[update_pc_index]<=bimodal_counter_r[update_pc_index]-2'b01;if(gshare_counter_r[update_index_i]!=2'b00)gshare_counter_r[update_index_i]<=gshare_counter_r[update_index_i]-2'b01;endif(update_bimodal_taken_i!=update_gshare_taken_i)beginif(update_gshare_taken_i==update_taken_i)beginif(chooser_counter_r[update_pc_index]!=2'b11)chooser_counter_r[update_pc_index]<=chooser_counter_r[update_pc_index]+2'b01;endelsebeginif(chooser_counter_r[update_pc_index]!=2'b00)chooser_counter_r[update_pc_index]<=chooser_counter_r[update_pc_index]-2'b01;end end ghr_r<={ghr_r[GHR_W-2:0],update_taken_i};end end endmodule

可以看到采用的是同步复位。
此时综合的结果是:

可以看到bht使用了1794个LUT和1544个FF触发器

六、为什么消耗了 1794 个 LUT?

6.1 致命误解:for循环不是"循环 256 拍"

always块里的for循环在编译期(elaboration)就被完全展开,语义等价于:

if (rst) begin bimodal_counter_r[0] &lt;= 2'b01; bimodal_counter_r[1] &lt;= 2'b01; // ... 共 768 条,全部在同一拍生效 end

也就是说你要求的是:rst 有效的那一拍之后,1536 个 bit 同时变成 2’b01。

复位分支的隐藏代价:为什么 1536 bit 表变成了 1794 个 LUT

2. 这个语义只有触发器能实现

对比一下硬件原语的能力:

原语清零能力实现代价
LUTRAM / BRAM写端口一拍只能写一个地址,没有任何"全体清零"端口要清空 256 项需要256 拍 + 一个状态机——综合器不会替你发明这个,因为它必须保持逐拍等价的语义
触发器 (FDRE/FDSE)每个都带 SR 引脚,复位信号扇出到 1536 个 FF一拍全部归位。复位本身走专用引脚,不花 LUT

所以综合器没有选择权:只要这个复位分支存在,表就必须是 1536 个 FF。


3. LUT 是从 FF 的"配套电路"里长出来的

表变成 FF 之后,访问它的电路全部失去原语可以吸收,只能摊在逻辑 LUT 上:

粗估老代码的 1794 个 LUT

功能估算 LUT
读出 mux:3 表 × 2bit × 256:1~400–500
写译码 + 每使能:256 项 × 3 表~500–600
每项"保持/更新"的 D 端 mux 和饱和比较~几百
chooser 判断、共享加减法、XOR~几十

LUTRAM 方案:同样的功能,不同的归属

LUTRAM 之后同样是这些功能,但:

功能LUTRAM 中的归属
译码器就是 LUT 的6 根地址线
读出 muxLUT 内部结构 + Slice 里专用的F7/F8 选择器(独立资源,不计入 LUT 数

所以:1536 bit 的表 + 全部访问电路 =48 个 LUTRAM + 17 个逻辑 LUT


一句话总结

复位分支的真正代价不是它自己生成的电路,而是它剥夺了综合器把表映射成 RAM 的权利。

删掉它、用initial赋初值(FPGA 比特流配置时免费写入),选择权就回来了。

七、改为使用LUTRAM+INITIAL赋初值

`timescale1ns/1ps `include"defines.v"// Tournament direction predictor with bimodal, gshare, and a PC-indexed chooser.module bht #(parameter ENTRY_NUM=256,parameter INDEX_W=8,parameter GHR_W=4)(input wire clk,input wire rst,input wire[`AW-1:0]lookup_pc_i,output wire predict_taken_o,output wire[INDEX_W-1:0]lookup_index_o,output wire bimodal_taken_o,output wire gshare_taken_o,input wire update_valid_i,input wire[`AW-1:0]update_pc_i,input wire[INDEX_W-1:0]update_index_i,input wire update_taken_i,input wire update_bimodal_taken_i,input wire update_gshare_taken_i);(*ram_style="distributed"*)reg[1:0]bimodal_counter_r[0:ENTRY_NUM-1];(*ram_style="distributed"*)reg[1:0]gshare_counter_r[0:ENTRY_NUM-1];(*ram_style="distributed"*)reg[1:0]chooser_counter_r[0:ENTRY_NUM-1];reg[GHR_W-1:0]ghr_r;wire[INDEX_W-1:0]lookup_pc_index;wire[INDEX_W-1:0]update_pc_index;wire[INDEX_W-1:0]ghr_extended;wire choose_gshare;assign lookup_pc_index=lookup_pc_i[INDEX_W+1:2];assign update_pc_index=update_pc_i[INDEX_W+1:2];assign ghr_extended={{(INDEX_W-GHR_W){1'b0}},ghr_r};assign lookup_index_o=lookup_pc_index^ghr_extended;//assign lookup_index_o = lookup_pc_index ^ ghr_r;assign bimodal_taken_o=bimodal_counter_r[lookup_pc_index][1];assign gshare_taken_o=gshare_counter_r[lookup_index_o][1];assign choose_gshare=chooser_counter_r[lookup_pc_index][1];assign predict_taken_o=choose_gshare?gshare_taken_o:bimodal_taken_o;integer i;initial beginif(ENTRY_NUM!=(1<<INDEX_W))begin $error("BHT: ENTRY_NUM must equal 2^INDEX_W");$finish;endif((GHR_W<2)||(GHR_W>INDEX_W))begin $error("BHT: GHR_W must be in the range 2..INDEX_W");$finish;end end initial beginfor(i=0;i<ENTRY_NUM;i=i+1)begin bimodal_counter_r[i]=2'b01;gshare_counter_r[i]=2'b01;chooser_counter_r[i]=2'b01;end ghr_r={GHR_W{1'b0}};end always @(posedge clk)beginif(update_valid_i)beginif(update_taken_i)beginif(bimodal_counter_r[update_pc_index]!=2'b11)bimodal_counter_r[update_pc_index]<=bimodal_counter_r[update_pc_index]+2'b01;if(gshare_counter_r[update_index_i]!=2'b11)gshare_counter_r[update_index_i]<=gshare_counter_r[update_index_i]+2'b01;endelsebeginif(bimodal_counter_r[update_pc_index]!=2'b00)bimodal_counter_r[update_pc_index]<=bimodal_counter_r[update_pc_index]-2'b01;if(gshare_counter_r[update_index_i]!=2'b00)gshare_counter_r[update_index_i]<=gshare_counter_r[update_index_i]-2'b01;endif(update_bimodal_taken_i!=update_gshare_taken_i)beginif(update_gshare_taken_i==update_taken_i)beginif(chooser_counter_r[update_pc_index]!=2'b11)chooser_counter_r[update_pc_index]<=chooser_counter_r[update_pc_index]+2'b01;endelsebeginif(chooser_counter_r[update_pc_index]!=2'b00)chooser_counter_r[update_pc_index]<=chooser_counter_r[update_pc_index]-2'b01;end end ghr_r<={ghr_r[GHR_W-2:0],update_taken_i};end end endmodule

综合后结果:

可以看到使用量锐减至100以内,效果显著。