FPGA开发技巧–BRAM资源优化

FPGA开发技巧--BRAM资源优化

前言

最近在优化之前一个项目的资源,总体来说是BRAM资源占用比较多,导致时序不太好收敛,那么我们就萌生了去优化BRAM资源的想法。

优化思路

在我们的项目里面有着大量位宽为128,深度为640的单端口BRAM。并且我们知道xilinx的BRAM可以配置为两个18kbit或一个36kbit。

那么对于我们项目中这种规格的BRAM理论上需要占用的BRAM个数为

128∗64036∗1024=2.2 \frac{128*640}{36*1024}=2.2 36∗1024128∗640=2.2

但是实际占用是4个36k的BRAM。

所以我们可以想办法去优化这种规格的BRAM。

我们可以想到一个xilinx的BRAM位宽是36bit,深度是1024,那么就可以使用这个BRAM的双端口模式来拼接完成一个128*640的单端口BRAM。

比如使用0号BRAM的0-639深度,完成一个36*640规格的RAM,使用1号BRAM的0-639深度,完成第二个36*640规格的RAM,这样我们就有了一个72*640规格的RAM。此时注意到0号和1号BRAM的640-1023深度其实并没有被使用到,那么我们就能利用这部分地址来完成剩余的拼接,但是这个时候剩余的深度只有384不足以完成640深度的RAM,所以我们引入2号BRAM。这样在0号BRAM的640-1024,2号BRAM的0-255,这么多地址空间就能完成一个36*640的RAM,这样我们就完成了108*640的RAM拼接,同理我们可以利用1号RAM的剩余地址和2号RAM的剩余地址完成剩下的20*640的RAM。

这样我们就使用3个RAM完成了128*640的BRAM,相比于之前的方案减少了一个BRAM的占用。虽然这里个数比较少,但是我整个工程中用到这种规格BRAM的地方非常多,所以整体下来BRAM减少了很多。

整体的结构图如下,就是三个BRAM来进行拼接

实际代码

verilog 复制代码
module bram_w128_d640 (
    input                  clk  ,
    input        [127 : 0] wdata,
    input                  wen  ,
    input        [9 : 0]   addr ,
    input                  en   ,
    output       [127 : 0] rdata
);

wire [9:0] ram_addr0;
wire [9:0] ram_addr1;

assign ram_addr0 = addr + 10'd640;
assign ram_addr1 = addr;

wire ram_en0;
wire ram_en1;

reg ram_en0_ff;

assign ram_en0 = en & (addr[9:8] == 2'b00);
assign ram_en1 = en & (addr[9:8] != 2'b00);

always @(posedge clk ) begin
    ram_en0_ff <= ram_en0;
end

wire [35:0] rdata0;
wire [35:0] rdata2;
wire [35:0] rdata1;
wire [35:0] rdata3;

assign rdata[107:72]  = ram_en0_ff ? rdata0 : rdata2; 
assign rdata[127:108] = ram_en0_ff ? rdata1[19:0] : rdata3[19:0]; 

tpram #(
    .DP   (1024),
    .DW   (36),
    .PIPE (1)
) tpram_inst_0 (    
    .clk      (clk        ),
    .wdata_0  (wdata[35:0]),
    .wen_0    (wen        ),
    .addr_0   (addr       ),
    .en_0     (en         ),
    .rdata_0  (rdata[35:0]),
    .wdata_1  (wdata[107:72]),
    .wen_1    (wen        ),
    .addr_1   (ram_addr0  ),
    .en_1     (ram_en0    ),
    .rdata_1  (rdata0     )
);

tpram #(
    .DP   (1024),
    .DW   (36),
    .PIPE (1)
) tpram_inst_1 (    
    .clk      (clk         ),
    .wdata_0  (wdata[71:36]),
    .wen_0    (wen         ),
    .addr_0   (addr        ),
    .en_0     (en          ),
    .rdata_0  (rdata[71:36]),
    .wdata_1  ({16'd0, wdata[127:108]}),
    .wen_1    (wen        ),
    .addr_1   (ram_addr0  ),
    .en_1     (ram_en0    ),
    .rdata_1  (rdata1     )
);

tpram #(
    .DP   (1024),
    .DW   (36),
    .PIPE (1)
) tpram_inst_2 (    
    .clk      (clk          ),
    .wdata_0  (wdata[107:72]),
    .wen_0    (wen          ),
    .addr_0   (ram_addr1    ),
    .en_0     (ram_en1      ),
    .rdata_0  (rdata2       ),
    .wdata_1  ({16'd0, wdata[127:108]}),
    .wen_1    (wen           ),
    .addr_1   (ram_addr0     ),
    .en_1     (ram_en1       ),
    .rdata_1  (rdata3        )
);
    
endmodule

注意到这里,我并不是在0号和1号BRAM里面利用了384深度,而是利用了256深度。在2号RAM里面利用了更多的地址空间。

测试代码如下

verilog 复制代码
module tb_top (
    
);

reg clk;

initial begin
    clk = 1'b0;
    forever begin
        clk = #5 !clk;
    end
end

reg [127:0] wdata;  
reg         wen  ;  
reg [9:0]   addr ;  
reg         en   ;  

reg rd_valid;

wire [127:0] rdata_0; 
wire [127:0] rdata_1; 

initial begin
    wdata <= 128'd0;
    wen   <=   1'd0;
    addr  <=  10'd0;
    en    <=   1'd0;
    repeat(20) @(posedge clk);
    wdata <= {$random, $random, $random, $random};
    wen   <=   1'd1;
    addr  <=  10'd0;
    en    <=   1'd1;   
    repeat(639)begin
        @(posedge clk);
        wdata <= {$random, $random, $random, $random};
        wen   <=   1'd1;
        addr  <=  addr + 10'd1;
        en    <=   1'd1;   
    end
    @(posedge clk);
    wdata <= 128'd0;
    wen   <=   1'd0;
    addr  <=  10'd0;
    en    <=   1'd0;
    @(posedge clk);
    addr  <=  10'd0;
    en    <=   1'd1;
    repeat(639)begin
        @(posedge clk);
        addr  <=  addr + 10'd1;
        en    <=   1'd1;       
    end
    @(posedge clk);
    addr  <=  10'd0;
    en    <=   1'd0;
    repeat(20) @(posedge clk);
    $display("sim success");
    $finish;
end

always @(posedge clk ) begin
    rd_valid <= en & (!wen);
end

initial begin
    forever begin
        @(posedge clk)begin
            if(rd_valid)begin
                if(rdata_0 != rdata_1)begin
                    $display("sim error rdata_0 : %h, rdata_1 : %h\n", rdata_0, rdata_1);
                    $finish;
                end
                else begin
                    $display("sim success rdata_0 : %h, rdata_1 : %h\n", rdata_0, rdata_1);
                end              
            end
        end
    end
end

top top_inst(
    .clk     (clk    ),
    .wdata   (wdata  ),
    .wen     (wen    ),
    .addr    (addr   ),
    .en      (en     ),
    .rdata_0 (rdata_0),
    .rdata_1 (rdata_1)
);
endmodule

仿真结果如下

资源占用如下

可以看到第一种方案占用了32个LUT,一个FF,3个BRAM

第二种直接使用Xilinx IP核的方案占用了4个BRAM

相关推荐
泛联新安9 小时前
InfinitPro多FPGA原型验证系统|提升芯片设计验证效率10-100倍
fpga开发
hahaha60161 天前
HLS高层次综合设计技巧--数组
开发语言·嵌入式硬件·算法·fpga开发
NamoAmitabha1281 天前
直接上手 UVM 例子
fpga开发
cmc10281 天前
301.modelsim自动仿真
fpga开发
Riwuarua2 天前
从近似0基础开始FPGA开发 -- part.10 verilog-ethernet开源UDP协议栈工程学习
fpga开发·udp·开源
my_daling2 天前
紫光FPGA远程升级注意事项(关于GTP_CFGCLK原语使用问题)
笔记·学习·fpga开发
Riwuarua3 天前
从近似0基础开始FPGA开发 -- part.11 AXI总线协议与工程应用
fpga开发
自小吃多3 天前
高云 FPGA 添加时序约束笔记
笔记·fpga开发
学习FPGA的电气小兴兴3 天前
基于FPGA的CORDIC旋转模式实现sin和cos运算
fpga开发·信号处理·数字信号处理·verilog·fpga·verilog hdl·cordic