【DNN Weaver FPGA实现】Vivado BRAM资源使用

1、BRAM配置测试

我们知道Vivado中BRAM大小分为18K和36K两种,这两种BRAM在何种配置下会如何分配资源,需要进行一定的考量。由于Vivado可以配置生成任意bit数的IO位宽,所以我对BRAM配置进行了简单的实验,结果如下所示。


18bit位宽,1K深度,共18Kb

可以看到,18bit位宽,1K深度可以正常使用18Kb的小BRAM。


16bit位宽,1152深度,共18Kb

如果使用16bit访存位宽,1152深度,依然是18Kb大小来生成BRAM,会导致资源无法映射到18Kb的BRAM中,而是使用了36Kb大小的BRAM,这导致了一半的BRAM被浪费。
8bit位宽,2304深度,共18Kb

8bit位宽,2048深度,共16Kb

上面两张图可以看到,如果使用较低的8bit位宽实现18Kb大小的RAM,也无法调用18K BRAM。但是如果将深度缩减为2K(经过测试2049深度也是调用了36K BRAM,所以深度必须是2K以下),即RAM大小减小为16Kb,则可以调用18K BRAM,减少资源浪费,这就很有意思了。

根据上述思路,我又对16bit位宽进行测试,设定深度为1K,则调用了18K BRAM,并且深度也是必须小于等于1K才会调用,否则就是36K BRAM,即RAM大小必须小于等于16Kb。依此类推,4bit位宽如果想使用18K BRAM,则必须深度小于等于4K,即RAM小于等于16Kb。

官方文档给出的BRAM原型分配

从官方文档中找到了对上述内容的解释,其实BRAM有16K×1、8K×2、4K×4、2K×9、1K×18、512×36等6种原型方案,所有的BRAM配置方案均在这些原型的基础上进行叠加拼接得到。所以说如果使用了1152×16的配置方案,则需要至少2块1K×18串联来满足深度要求,或者两块2K×9并联(4个4K×4并联等其他方案也可,但太浪费BRAM)来满足位宽要求,所以必须要占用36K BRAM。并且如果有更大的深度或者位宽出现时,可能会有很多种不同的解决方案,Vivado中也提供了相应的三种方案,Minimum Area Algorithm、Low Power Algorithm、Fixed Primitive Algorithm,帮助生成最适合项目需求的RAM形式。详见文档pg058-blk-mem-gen,42~45页。

Examples of the Minimum Area Algorithm

Examples of the Low Power Algorithm

Examples of the Fixed Primitive Algorithm

2、BRAM读写时序

写优先

在本文中主要使用了写优先模式,以保证读出数据为最新。可以从时序图中看到,输入数据、数据地址、Enable信号等需要在时钟上升沿之前就到达接口位置。

3、ARM Memory Compiler SRAM时序

Memory Compiler SRAM时序

与ASIC实现中使用Memory Compiler生成的SRAM时序进行对比,可以发现该SRAM中控制信号和数据写入基本与Xilinx的BRAM是一致的,均需要在时钟上升沿之前到来地址和数据。但是MC SRAM数据读出速度要更快,在一定的时间延迟后即可得到有效输出数据吗,而BRAM则至少有1~3个周期的延迟才能获得读出数据。

4、读写冲突

转自Xilinx之RAM使用指南

BRAM读写时对端口位宽大小的单元进行操作,例如dout位宽为32bit,则每次读写均为32bit,BRAM深度大小就是有几个32bit的数据。

因此读写冲突的可能仅存在于同时读写同一个32bit数据,即同时读写一个地址的数据。所以我们在进行OBUF实现时,应该不会有读写冲突的出现。下图中所示为三种端口模式下的读写冲突情况,其中写优先模式的读写冲突,是由于前一周期B端口在读,本周期A端口在写,导致本周期B端口应当读到的旧数据被覆盖。从数据读逻辑上来说,只要有一个端口在本周期写数据,本周期读到的数据必定是亚稳态,如果是字节写模式(有写Mask),则写入部分地址的数据输出为亚稳态。如果是本周期A端口写入、B端口读出,则可以在下周期读出最新写入的数据。(本周期读的数据在下周期获得是在BRAM配置时设定的输出寄存器个数决定的,延迟周期数可以为1~3)


BRAM读写冲突

各种RAM(双端RAM、DRAM)HDL写法详见Vivado使用技巧(27):RAM编写技巧

5、双端RAM Ping-Pong Buffer读写控制

转自FPGA基础设计(7)双口RAM乒乓操作
这里的双端RAM两个口接的时钟频率不一样,写端口CLKA为20MHz,读端口CLKB为100MHz,也就是说读速度为写速度的5倍。

`timescale 1ns / 1ps

module DualRAM
(
    input clk_wr,      //写时钟速率20Mhz
    input clk_rd,      //读时钟速率100Mhz
    input rst_n,
    input [7:0] din,   
    output reg out_valid,
    output reg [7:0] dout
);

reg [9:0] addr_wr, addr_rd;
reg en_wr1, en_wr2, we_wr1, we_wr2, en_rd1, en_rd2;
wire [7:0] dout1, dout2;

dual_port_ram u1 (
  .clka(clk_wr),      //写端口
  .ena(en_wr1),    
  .wea(we_wr1),      
  .addra(addr_wr),  
  .dina(din),   
  .douta(), 
  .clkb(clk_rd),      //读端口
  .enb(en_rd1),      
  .web(1'b0),      
  .addrb(addr_rd),  
  .dinb(8'd0),    
  .doutb(dout1) 
);

dual_port_ram u2 (
  .clka(clk_wr),      //写端口
  .ena(en_wr2),    
  .wea(we_wr2),      
  .addra(addr_wr),  
  .dina(din),   
  .douta(), 
  .clkb(clk_rd),      //读端口
  .enb(en_rd2),      
  .web(1'b0),      
  .addrb(addr_rd),  
  .dinb(8'd0),    
  .doutb(dout2) 
);

//写端口乒乓操作
always @ (posedge clk_wr)     //写地址信号控制0~1023
    if (!rst_n) addr_wr <= 1023;
    else addr_wr <= addr_wr + 1'b1;

always @ (posedge clk_wr)     //轮流写RAM1与RAM2
    if (!rst_n) begin we_wr1 <= 1'b1; we_wr2 <= 1'b0;
        en_wr1 <= 1'b1; en_wr2 <= 1'b0;  end
    else if (addr_wr == 1023) begin
        we_wr1 <= ~we_wr1; we_wr2 <= ~we_wr2;
        en_wr1 <= ~en_wr1; en_wr2 <= ~en_wr2;
    end 
 
//读端口乒乓操作
always @ (posedge clk_rd)    //读地址信号控制0~1023
    if (!rst_n) addr_rd <= 1021;  //匹配延迟
    else addr_rd <= addr_rd + 1'b1; 
    
reg [15:0] cnt;
always @ (posedge clk_rd)    //读时钟为写时钟的5倍
    if (!rst_n) cnt <= 16'hFFFE;  //匹配延迟
    else if (cnt == 5119) cnt <= 0;
    else cnt <= cnt + 1'b1;
    
reg flag1, flag2;
always @ (posedge clk_rd)    //读RAM标志,RAM1或RAM2
    if (!rst_n) begin flag1 <= 1'b1; flag2 <= 1'b0; end
    else if (cnt == 5119) begin flag1 = ~flag1; flag2 = ~flag2; end    
    else begin flag1 <= flag1; flag2 <= flag2; end
    
always @ (posedge clk_rd)    //读RAM使能,选择cnt的前1/5时间读取
    if (!rst_n) begin en_rd1 <= 1'b1; en_rd2 <= 1'b0; end 
    else if (cnt < 1024) begin en_rd1 <= flag1; en_rd2 <= flag2; end
    else begin en_rd1 <= 1'b0; en_rd2 <= 1'b0; end

reg en_rd1_reg, en_rd2_reg;
always @ (posedge clk_rd)    //延迟一级,匹配时序
    if (!rst_n) begin en_rd1_reg <= 0; en_rd1_reg <= en_rd1_reg; end
    else begin en_rd1_reg <= en_rd1; en_rd2_reg <= en_rd2; end

always @ (posedge clk_rd)    //输出选择,RAM1或RAM2;控制输出使能信号
    if (!rst_n) begin dout <= 0; out_valid <= 0; end
    else if (en_rd1_reg) begin dout <= dout1; out_valid <= 1; end
    else if (en_rd2_reg) begin dout <= dout2; out_valid <= 1; end
    else begin dout <= 0; out_valid <= 0; end
    
endmodule

上面的代码中有例化BRAM模块,不过这些端口不一定全部需要,根据本项目特点,使用Simple dual-port BRAM就可以,因此A口写,B口读,A口没有douta信号,B口没有dinb和web信号,例化的时候要注意,如果不清楚可以到Vivado中打开diagram看一下。

仿真结果

6、对比DNN Weaver RAM与BRAM数据接口与时序差别

下面是DNN Weaver RAM模块代码,该RAM使用在IBUF和BBUF中。与BRAM模块接口对比,该RAM的读写使能信号分开,并且读通道与写通道分开,可以同时读写,但没有解决读写冲突问题,说明IBUF和BBUF不会出现该问题,并且输出均有1个寄存器的延迟(例化模块时设定OUTPUT_REG=1)。总之使用BRAM对该模块可以进行很好的代替,因为功能上来说该RAM是BRAM的子集。

除了IBUF和BBUF,DNN Weaver中还有个OBUF。由于OBUF需要大量的读写,OBUF设计比IBUF等逻辑复杂很多,并且有两套读写接口,模块名称为banked_ram。学姐当时建议使用DRAM(Distributed RAM)实现OBUF,不知道会不会在综合的时候RAM逻辑过大,导致片上资源不足,或者导致综合时间过长的问题。

`timescale 1ns/1ps
module ram
#(
  parameter integer DATA_WIDTH    = 10,
  parameter integer ADDR_WIDTH    = 12,
  parameter integer OUTPUT_REG    = 0
)
(
  input  wire                         clk,
  input  wire                         reset,

  input  wire                         s_read_req,
  input  wire [ ADDR_WIDTH  -1 : 0 ]  s_read_addr,
  output wire [ DATA_WIDTH  -1 : 0 ]  s_read_data,

  input  wire                         s_write_req,
  input  wire [ ADDR_WIDTH  -1 : 0 ]  s_write_addr,
  input  wire [ DATA_WIDTH  -1 : 0 ]  s_write_data
);

  reg  [ DATA_WIDTH -1 : 0 ] mem [ 0 : 1<<ADDR_WIDTH ];

  always @(posedge clk)
  begin: RAM_WRITE
    if (s_write_req)
      mem[s_write_addr] <= s_write_data;
  end

  generate
    if (OUTPUT_REG == 0)
      assign s_read_data = mem[s_read_addr];
    else begin
      reg [DATA_WIDTH-1:0] _s_read_data;
      always @(posedge clk)
      begin
        if (reset)
          _s_read_data <= 0;
        else if (s_read_req)
          _s_read_data <= mem[s_read_addr];
      end
      assign s_read_data = _s_read_data;
    end
  endgenerate
endmodule
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 226,728评论 6 526
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 97,623评论 3 411
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 174,371评论 0 372
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,174评论 1 306
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,014评论 6 405
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,532评论 1 319
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,641评论 3 433
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 41,793评论 0 283
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,294评论 1 329
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,276评论 3 352
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,423评论 1 364
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,980评论 5 354
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,678评论 3 342
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,087评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,307评论 1 279
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,997评论 3 385
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,404评论 2 370