Xilinx FPGA怎么使用LUTRAM
经常遇到Block RAM使用率几乎100%,LUTRAM使用率也才百分之几十。盲目去试,并不能带来满意的结果。现在我们来详细讲解一下在 Xilinx FPGA 中如何使用 LUTRAM。
一、什么是 LUTRAM?
首先,要理解 LUTRAM,我们需要知道 FPGA 中的基本单元——查找表。
-
LUT: 是 FPGA 中实现组合逻辑的基本单元。一个 6 输入的 LUT 可以看作是一个有 64 个地址的 1 位 ROM。你输入一个 6 位地址,它就输出预先配置好的那 1 位数据。
-
LUTRAM: 是 LUT 的一种特殊工作模式。在这种模式下,LUT 被配置为一个小的、同步的 随机存取存储器。你可以对它进行读和写操作,就像一个真正的 RAM 一样。
二、关键特性
-
容量小: 一个 6 输入 LUT 可以配置为 64x1 位 或 32x2 位的 RAM。通常用于小容量的存储,如 FIFO、小缓冲区、寄存器堆等。
-
分布式 RAM: 因为 LUT 是分布在 FPGA 的整个可编程逻辑阵列中的,所以由 LUT 构成的 RAM 被称为 分布式 RAM。与之相对的是大型的、集中的 Block RAM。
-
同步写,异步/同步读: 大多数 LUTRAM 的写操作是同步的(需要时钟)。读操作可以是异步的(地址变化,数据立即输出)或同步的(在时钟边沿输出数据),具体取决于配置模式。
三、为什么使用 LUTRAM?
-
小容量存储的理想选择: 当你只需要几十到几百个字节的存储空间时,使用一个完整的 Block RAM 是浪费的。LUTRAM 可以更精细地利用资源,避免 Block RAM 的浪费。
-
分布式特性: 由于 LUTRAM 遍布整个芯片,你可以将存储单元放在离使用它的逻辑电路非常近的地方,这有助于减少布线延迟,提高性能。
-
解决时序瓶颈: 对于需要极低延迟访问的小型存储器,LUTRAM 通常是性能最好的选择。
四、 如何使用 LUTRAM?
主要有三种方法,推荐程度从高到低。
方法一:通过 HDL 代码推断(最常用、最推荐)
这是最通用、可移植性最好的方法。综合工具(如 Vivado 的 Vivado Synthesis)能够识别出特定的代码模式,并自动将其映射到 LUTRAM。
下面是一些常见的可推断模式的 Verilog 示例:
示例 1:单端口 RAM(同步写,异步读)
module lutram_single_port (input clk,input we, // 写使能input [5:0] addr, // 6位地址,深度64input [7:0] din, // 8位写入数据output [7:0] dout // 8位读出数据 );// 声明一个 64x8 位的寄存器数组 reg [7:0] ram [0:63];// 同步写操作 always @(posedge clk) beginif (we)ram[addr] <= din; end// 异步读操作 assign dout = ram[addr];endmodule
-
注意: 异步读在 FPGA 中可能会引入布线延迟不一致的问题,在高性能设计中,更推荐使用同步读。
示例 2:单端口 RAM(同步写,同步读)
module lutram_single_port_sync (input clk,input we,input [4:0] addr, // 5位地址,深度32input [7:0] din,output reg [7:0] dout // 输出定义为寄存器 );// 声明一个 32x8 位的寄存器数组 reg [7:0] ram [0:31];// 同步写操作 always @(posedge clk) beginif (we)ram[addr] <= din; end// 同步读操作 always @(posedge clk) begindout <= ram[addr]; endendmodule
示例 3:简单的双端口 RAM(一个端口写,另一个端口读)
module lutram_simple_dual_port (input clk,input we,input [4:0] waddr, // 写地址input [4:0] raddr, // 读地址input [7:0] din,output reg [7:0] dout );// 声明一个 32x8 位的寄存器数组 reg [7:0] ram [0:31];// 同步写操作 always @(posedge clk) beginif (we)ram[waddr] <= din; end// 同步读操作 always @(posedge clk) begindout <= ram[raddr]; endendmodule
关键点:
-
使用
reg [data_width-1:0] ram [0:depth-1];这样的数组声明。 -
写操作必须在时钟控制的
always块中。 -
读操作可以在组合逻辑中(异步读)或在时钟控制的
always块中(同步读)。 -
确保你的代码风格简洁,符合工具推荐的推断模板。
方法二:使用 Vivado 的原语(Primitive)进行实例化
这种方法直接调用 Xilinx 器件专用的底层硬件单元,不具可移植性,但可以精确控制。
常见的 LUTRAM 原语包括:
-
RAM64X1S: 64x1 单端口分布式 RAM -
RAM32X1S: 32x1 单端口... -
RAM64X1D: 64x1 双端口(一个写端口,两个异步读端口) -
RAM32M: 一个 LUT6 实现 32x6 的 RAM(4个写端口,4个读端口,结构特殊)
示例:使用 RAM64X1S
// 直接实例化一个 64x1 的 LUTRAM RAM64X1S #(.INIT(64'h0000000000000000) // 可选的初始值 ) RAM64X1S_inst (.O(O), // 1位数据输出.A0(A0), // 地址位[0].A1(A1), // 地址位[1].A2(A2), // 地址位[2].A3(A3), // 地址位[3].A4(A4), // 地址位[4].A5(A5), // 地址位[5].D(D), // 1位数据输入.WCLK(WCLK), // 写时钟.WE(WE) // 写使能 );
要构建一个更宽的 RAM,你需要将多个这样的原语并排实例化。
这种方法通常只在高级优化或特殊需求时使用,对于一般应用,推断方法更简单高效。
方法三:使用 IP 核生成器
在 Vivado 的 IP Catalog 中,你可以使用 Distributed Memory Generator 这个 IP 核。
-
打开 Vivado,在 IP Catalog 中搜索 "Distributed Memory"。
-
双击打开配置界面。
-
你可以选择内存类型:
-
单端口 RAM
-
简单的双端口 RAM
-
真双端口 RAM(注意:LUTRAM 对真双端口的支持有限,通常需要多个 LUT 实现,可能不如 Block RAM 高效)
-
ROM
-
-
设置宽度、深度等参数。
-
生成 IP 核,然后在你的代码中实例化生成的模块。
这种方法适用于希望快速生成并集成一个经过验证的 LUTRAM 模块的情况。
五、如何确认你的设计确实使用了 LUTRAM?
在 Vivado 中完成综合后,可以通过以下方式验证:
-
打开综合后的设计。
-
在 "Netlist" 窗口中,搜索你的 RAM 模块名。 你应该能看到它被实现为了
RAM64X1S、RAM32X1D等原语,或者被封装在一个更大的DRAM*模块中。 -
查看资源利用率报告。 在报告中,你会看到 "Distributed RAM" 资源被使用,而 "Block RAM" 没有被使用(除非你的设计其他地方用了)。
六、总结
| 特性 | LUTRAM | Block RAM |
|---|---|---|
| 容量 | 小(几Kb) | 大(几十到几百Kb) |
| 位置 | 分布式 | 集中式,专用列 |
| 端口 | 通常单端口或简单双端口 | 真双端口 |
| 功耗 | 通常较低(根据用量) | 较高(即使不用也耗电) |
| 使用场景 | 小缓冲区、FIFO、寄存器堆 | 大型缓冲区、帧缓冲区、大型数据存储 |
七、最佳建议
-
首选代码推断法,因为它灵活、可读性强、工具支持好。
-
明确你的存储需求,小容量用 LUTRAM,大容量用 Block RAM。
-
在代码中,尽量使用同步读,以获得更好的时序性能。
-
综合后,务必查看报告和原理图,确认工具是否按照你的预期实现了设计。
通过以上方法,你就可以高效地在 Xilinx FPGA 设计中利用 LUTRAM 这一灵活的资源了。
