从零开始设计riscv cpu九之降低LUT使用量语法技巧
一、首先知道LUT是什么
LUT(Look-Up Table,查找表)是 FPGA 中最核心的可编程逻辑资源。
你可以把它想象成一个真值表:输入信号作为地址,输出就是预先存储在表中的结果。
| 输入 A | 输入 B | 输出(AND) |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
二、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] <= 2'b01; bimodal_counter_r[1] <= 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 根地址线 |
| 读出 mux | LUT 内部结构 + 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以内,效果显著。
