当前位置:   article > 正文

Multiport RAM,多读多写寄存器-——基于FPGA BRAM的多端口地址查找表与FPGA BRAM的资源分析...

Multiport RAM,多读多写寄存器-——基于FPGA BRAM的多端口地址查找表与FPGA BRAM的资源分析...

本项目开源,如需要完整源代码移步到此链接:

https://blog.csdn.net/qq_45634652/article/details/138034081?spm=1001.2014.3001.5502

一、背景

      在多端口交换机的设计中,交换机的每个端口都会各自维护一张查找表,数据帧进入到交换机后,需要进行查表和转发。但随着端口数量和表项需求的增加,每个端口都单独维护一张表使得FPGA的资源变得非常紧张。因此,需要一张查找表(本质是可读可写的RAM),能够满足多读多写的功能。但在Xilinx FPGA上,Xilinx提供的BRAM IP最高只能实现真双端口RAM。不能满足多读多写的需求。

      补充:这里不使用其他RAM类型如URAM的原因是,BRAM拥有更好的时序,更适合在高速交换中用于查找表

二、手写Multiport Ram

      Multiport Ram,即多读多写存储器,本工程实现的是1个口写,同时满足11个口读的BRAM

      为了让vivado在综合的时候把手写ram例化为BRAM,我们需要按照官方手册的要求编写multiport ram。这时需要通过(*ram_style="block"*)array进行修饰。

    查看Vivado的官方手册ug901可知,对于Distributed RAM(LUTRAM)和Dedicated Block RAM(BRAM),二者都是写同步的。主要区别在于读数据,前者为异步,后者为同步的。

049d3671371179940aaf6119f77b8eba.png

      下面给出一种手写多端口bram的方案并给出一种优化FPGA bram资源利用的方法。

Multiport RAM 代码方案

      实现多端口bram最简单的方法就是把读数据部分的逻辑复制11份,写数据部分的逻辑保留1份。部分代码如下,实现位宽73bit,深度为16K的multiport ram:

  1. (*ram_style="block"*)reg [DATA_WIDTH-1:0] bram [0:DEPTH-1];
  2. /*-------------复制读端口11份---------------*/
  3. always @(posedge clk)
  4. begin
  5. if(re1)
  6. rd_data1 <= bram[rd_addr1];
  7. else
  8. rd_data1 <= rd_data1;
  9.     end
  10. /*-----------------------------------------*/
  11. //write
  12. always @(posedge clk)
  13. begin
  14. if(we)
  15. bram[wr_addr]<=wr_data;
  16. end
  17. endmodule

资源评估

        利用vivado综合实现后,消耗的资源如下

fe1423db9b097444bb29d66b9afaf30f.png

MultiportRAM16K深度,73位宽的单口写,11口读的RAM消耗的BRAM数为192个。

普通真双口RAM:利用vivado IP核生成的16K深度,73bit位宽的真双口RAM消耗的BRAM数为32个。即如果11个端口各自维护一张地址查找表共使用352个RAM。

对比发现,在满足11个端口同时读地址查找表的条件下,多端口RAM比普通RAM节约了45%左右的BRAM资源

三、Multiport RAM 资源利用的优化

      可能有的同学说,在某些大工程里面,192个BRAM还是有点多。下面我给出了一种降低BRAM资源消耗的方法。

      首先我们把例化的ram array的位宽翻倍

  1. //原本
  2. (*ram_style="block"*)reg [DATA_WIDTH-1:0] bram [0:DEPTH-1];
  3. //现在
  4. (*ram_style="block"*)reg [DATA_WIDTH+DATA_WIDTH-1:0] bram [0:DEPTH-1];

      (有同学会问了,这样资源消耗不是翻倍了吗?···别急!)

      我们把需要写入RAM的数据,73位写data复制成两份,同时写进bram的高73位和低73位,地址不变,其中multi_wdata是我们要写进表中的73位表项,代码如下:

  1. //bram例化模块的写使能、地址和数据
  2. .we ( multi_wr),
  3. .wr_addr (multi_waddr),
  4.     .wr_data  ({multi_wdata,multi_wdata})

         在bram输出中,每两个端口共用一个143位的bram行,并根据使能情况赋值:

  1. //read1
  2. assign rd_data1_wire = rd_data1[72:0] ;
  3. assign rd_data2_wire = rd_data2[145:73];
  4. always @(posedge clk)
  5. begin
  6. if (re1 & re2) begin
  7. rd_data1 <= bram[rd_addr1];
  8. rd_data2 <= bram[rd_addr2];
  9. end
  10. else
  11. if(re1) begin
  12. rd_data1 <= bram [rd_addr1];
  13. end
  14. else if (re2) begin
  15. rd_data2 <= bram [rd_addr2];
  16. end
  17. end

***补充:具体代码在文章开头链接

资源评估

        利用vivado综合实现后,消耗的资源如下

4d79d41e71cd08490c0b7ed463ddf497.png

MultiportRAM:16K深度,146位宽的单口写,11口读的RAM消耗的BRAM数为112个。

普通真双口RAM:利用vivado IP核生成的16K深度,73bit位宽的真双口RAM消耗的BRAM数为32个。即如果11个端口各自维护一张表共使用352个RAM

对比发现,在满足11个端口同时读地址查找表的条件下,多端口RAM比普通RAM节约了68%左右的BRAM资源

四、防止读写冲突的组合逻辑设计(写优先)

      代码原理,利用组合逻辑时序,当写入地址和读地址相同时,写入地址、数据正常进行但读端口不对RAM进行读取,而是将写入端的数据直接赋值给读出端的数据。

      下一拍,即读写冲突结束后的下一拍,再读一拍RAM中的数据,使得读端口数据保持这一次读的结果(因为组合逻辑在读写冲突时没有真正读RAM,所以RAM输出data会保持上一次输出的data),但这一步不是必要的,纯粹为了好看。

部分代码如下:

  1. //防止读写冲突,且为写优先逻辑
  2. assign multi_rdata0 =(multi_raddr0_f ==multi_waddr_f && multi_raddr0_f !='b0 )?multi_wdata_f:multi_rdata0_ram ;
  3. assign multi_rdata1 =(multi_raddr1_f ==multi_waddr_f && multi_raddr1_f !='b0 )?multi_wdata_f:multi_rdata1_ram ;
  4. assign multi_rdata2 =(multi_raddr2_f ==multi_waddr_f && multi_raddr2_f !='b0 )?multi_wdata_f:multi_rdata2_ram ;
  5. assign multi_rdata3 =(multi_raddr3_f ==multi_waddr_f && multi_raddr3_f !='b0 )?multi_wdata_f:multi_rdata3_ram ;
  6. assign multi_rdata4 =(multi_raddr4_f ==multi_waddr_f && multi_raddr4_f !='b0 )?multi_wdata_f:multi_rdata4_ram ;
  7. assign multi_rdata5 =(multi_raddr5_f ==multi_waddr_f && multi_raddr5_f !='b0 )?multi_wdata_f:multi_rdata5_ram ;
  8. assign multi_rdata6 =(multi_raddr6_f ==multi_waddr_f && multi_raddr6_f !='b0 )?multi_wdata_f:multi_rdata6_ram ;
  9. assign multi_rdata7 =(multi_raddr7_f ==multi_waddr_f && multi_raddr7_f !='b0 )?multi_wdata_f:multi_rdata7_ram ;
  10. assign multi_rdata8 =(multi_raddr8_f ==multi_waddr_f && multi_raddr8_f !='b0 )?multi_wdata_f:multi_rdata8_ram ;
  11. assign multi_rdata9 =(multi_raddr9_f ==multi_waddr_f && multi_raddr9_f !='b0 )?multi_wdata_f:multi_rdata9_ram ;
  12. assign multi_rdata10=(multi_raddr10_f==multi_waddr_f && multi_raddr10_f!='b0 )?multi_wdata_f:multi_rdata10_ram;
  13. assign multi_raddr0_ram =(multi_raddr0_f ==multi_waddr_f && multi_raddr0_f !='b0 )?multi_waddr_f: multi_raddr0;
  14. assign multi_raddr1_ram =(multi_raddr1_f ==multi_waddr_f && multi_raddr1_f !='b0 )?multi_waddr_f: multi_raddr1;
  15. assign multi_raddr2_ram =(multi_raddr2_f ==multi_waddr_f && multi_raddr2_f !='b0 )?multi_waddr_f: multi_raddr2;
  16. assign multi_raddr3_ram =(multi_raddr3_f ==multi_waddr_f && multi_raddr3_f !='b0 )?multi_waddr_f: multi_raddr3;
  17. assign multi_raddr4_ram =(multi_raddr4_f ==multi_waddr_f && multi_raddr4_f !='b0 )?multi_waddr_f: multi_raddr4;
  18. assign multi_raddr5_ram =(multi_raddr5_f ==multi_waddr_f && multi_raddr5_f !='b0 )?multi_waddr_f: multi_raddr5;
  19. assign multi_raddr6_ram =(multi_raddr6_f ==multi_waddr_f && multi_raddr6_f !='b0 )?multi_waddr_f: multi_raddr6;
  20. assign multi_raddr7_ram =(multi_raddr7_f ==multi_waddr_f && multi_raddr7_f !='b0 )?multi_waddr_f: multi_raddr7;
  21. assign multi_raddr8_ram =(multi_raddr8_f ==multi_waddr_f && multi_raddr8_f !='b0 )?multi_waddr_f: multi_raddr8;
  22. assign multi_raddr9_ram =(multi_raddr9_f ==multi_waddr_f && multi_raddr9_f !='b0 )?multi_waddr_f: multi_raddr9;
  23. assign multi_raddr10_ram=(multi_raddr10_f==multi_waddr_f && multi_raddr10_f!='b0 )?multi_waddr_f: multi_raddr10;
  24. assign multi_rd0_ram =(multi_raddr0 ==multi_waddr && multi_raddr0!='b0 )? 1'b0:((multi_raddr0_f ==multi_waddr_f && multi_raddr0_f !='b0 )?multi_rd0_f :multi_rd0 );
  25. assign multi_rd1_ram =(multi_raddr1 ==multi_waddr && multi_raddr1!='b0 )? 1'b0:((multi_raddr1_f ==multi_waddr_f && multi_raddr1_f !='b0 )?multi_rd1_f :multi_rd1 );
  26. assign multi_rd2_ram =(multi_raddr2 ==multi_waddr && multi_raddr2!='b0 )? 1'b0:((multi_raddr2_f ==multi_waddr_f && multi_raddr2_f !='b0 )?multi_rd2_f :multi_rd2 );
  27. assign multi_rd3_ram =(multi_raddr3 ==multi_waddr && multi_raddr3!='b0 )? 1'b0:((multi_raddr3_f ==multi_waddr_f && multi_raddr3_f !='b0 )?multi_rd3_f :multi_rd3 );
  28. assign multi_rd4_ram =(multi_raddr4 ==multi_waddr && multi_raddr4!='b0 )? 1'b0:((multi_raddr4_f ==multi_waddr_f && multi_raddr4_f !='b0 )?multi_rd4_f :multi_rd4 );
  29. assign multi_rd5_ram =(multi_raddr5 ==multi_waddr && multi_raddr5!='b0 )? 1'b0:((multi_raddr5_f ==multi_waddr_f && multi_raddr5_f !='b0 )?multi_rd5_f :multi_rd5 );
  30. assign multi_rd6_ram =(multi_raddr6 ==multi_waddr && multi_raddr6!='b0 )? 1'b0:((multi_raddr6_f ==multi_waddr_f && multi_raddr6_f !='b0 )?multi_rd6_f :multi_rd6 );
  31. assign multi_rd7_ram =(multi_raddr7 ==multi_waddr && multi_raddr7!='b0 )? 1'b0:((multi_raddr7_f ==multi_waddr_f && multi_raddr7_f !='b0 )?multi_rd7_f :multi_rd7 );
  32. assign multi_rd8_ram =(multi_raddr8 ==multi_waddr && multi_raddr8!='b0 )? 1'b0:((multi_raddr8_f ==multi_waddr_f && multi_raddr8_f !='b0 )?multi_rd8_f :multi_rd8 );
  33. assign multi_rd9_ram =(multi_raddr9 ==multi_waddr && multi_raddr9!='b0 )? 1'b0:((multi_raddr9_f ==multi_waddr_f && multi_raddr9_f !='b0 )?multi_rd9_f :multi_rd9 );
  34. assign multi_rd10_ram=(multi_raddr10==multi_waddr && multi_raddr1!='b0 )? 1'b0:((multi_raddr10_f==multi_waddr_f && multi_raddr10_f!='b0 )?multi_rd10_f:multi_rd10);

***补充:具体代码在文章开头链接

读写冲突的仿真结果如下:

bf3ad2b05c60154ff62d5913fdbcf19e.png

五、Multiport RAM仿真和时序

      所有写端口都是一拍写入。读端口是第一拍读使能,读地址,第二拍读出数据。

1.单口写数据

c7678c8ff5b6d0e3365a5b0eb213e228.png

2.单端口读数据

584d49fa39e67afd12274814e1903c30.png

3.多口读相同数据

ff9cdd12e2ac828e55cc8035be440fb2.png

4.多口同时读不同数据

ecd917ee8fb70a98b7f5d64a25332847.png

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号