一、BRAM相关概念、作用

1.概念

①RAM(Random Access Memory)与ROM(Read-Only Memory)

        RAM可存取,数据断电丢失,作为“数据存储器”,用于内存(SDRAM、DDR等);ROM只可读,数据断电不丢失,作为“程序存储器”,有PROM和用于BIOS的EEPROM等。

②BRAM(Block Random Access Memory)与DRAM(Distributed RAM)

        a.BRAM为FPGA的定制专用的RAM,块RAM,是整块的RAM资源,位置固定,使用时将占用整个块;DRAM由FPGA中的查找表LUT组成,由逻辑单元拼凑而成,可以用于小的零星的RAM;

        b.BRAM输出需要时钟;DRAM可以在给出地址后就输出数据,也可以加上register变为有时钟的;

        c.要产生大的FIFO、存储较大的数据或者timing要求高,要求延时小,用BRAM(高速访问、大量存储);零星小RAM,timing要求低就用DRAM(灵活性、低成本);

2.常见应用

        高速访问、大量数据存储:流水线处理、高速缓存、大规模数据存储等

(1)ROM

        可构成单、双端口ROM,略

(2)RAM(IP核基础形式)

①单端口RAM(Single-port RAM):A读写         

 

②简单双端口RAM(Simple Dual-port RAM):A写B读

③真双端口RAM(Simple Dual-port RAM):A、B均可独立读写

(3)FIFO

二、BRAM IP核(Block Memory Generator)

         这里只对比较主要的可定制选项进行说明。

        1.Memory Type

        调用BRAM IP核时可在Basic中选择下面5种Memory Type,各Memory Type见 一、2

        2.Interface Type

        Basic中可以选择两种Interface Type,这里只对Native作讨论

        3.ECC

        只可在简单双端口RAM中选择,可选择软件ECC(Soft ECC)和RAM内建ECC(BuiltIn ECC)

ECC(Error Correcting Code)是指错误纠正编码技术,它可以作为一种附加功能选项集成到RAM模块中。当启用ECC时,RAM会在每个数据字上附加额外的校验位,这些校验位能够在数据读写过程中实时计算并用于检测潜在的位错误。

Error Injection Pins可选择出现错误后在校验位上附加的比特位为单bit或双bit。

        4.Port 的 Operating Mode

        选择好Memory Type 和Interface Type后,在Port Option中可对每个port进行模式的选择,port有三种Operating Mode:write first;read first;no change

        下面以单端口ram为例,勾选输出寄存器(输出打一拍),说明Port在三种Operating Mode下的时序区别

blk_mem_gen your_instance_name (
  .clka(clka),    // input wire clka                             时钟信号
  .wea(wea),      // input wire [0 : 0] wea                写使能信号
  .addra(addra),  // input wire [3 : 0] addra            输入的地址
  .dina(dina),    // input wire [15 : 0] dina                写入地址的数据
  .douta(douta)  // output wire [15 : 0] douta          从地址输出的数据
);

(1)write first

⭐在当下地址完成先写后读(,读的输出滞后一时钟周期)

        85.000ns时,wea=1,写数据使能,此时将din=1‘d9写入addr=1'h1,并在本时钟沿(85.000ns)下个时钟上升沿(90.000ns)输出addr=1'h1的数据,即1'd9;本次时钟上升沿输出的是本时钟沿的上个时钟上升沿所对应的地址1’hf的数据。

        165.000ns时,wea=0,此时未将din=2'd19写入addr=1'h1,该地址上数据依旧为上次写入的1‘d9,且于下个时钟上升沿输出。本次时钟上升沿输出的是上个时钟上升沿写入地址1’hf的数据2'd18。

(2)read first

⭐在当下地址完成先读(,读出的输出滞后一时钟周期),后写入新数据

        35.000ns时,wea=1,写数据使能,此时将din=1‘d3写入addr=1'h7,并在下次出现该addr=1'h7的时钟沿(110.000ns)下个时钟上升沿(125.00ns)输出addr=1'h7的数据,即1‘d3;本次时钟上升沿输出的是上个时钟上升沿对应地址的数据,该数据是之前写入的

(3)no change

  wea=1时只可写,输出寄存器内的读出数据不再变化,(,读出的输出滞后一时钟周期

        5.输出Port 的Output Register

        Port Option中可以选择输出寄存器Output Registers,选择后数据将打一拍后进行输出,即滞后一个时钟周期,建议勾选。

输出寄存器的作用:

a.优化时序:提供额外的时钟周期延迟,用来适应系统设计中的特定时序要求,例如满足建立时间和保持时间约束,或者为接收逻辑提供更多的时间窗口来处理读取的数据。

b.稳定数据:额外的时钟周期可以使输出数据更加稳定,在高速系统或需要高精度数据传输的情况中,需要确保在时钟边沿变化之后数据已经不再变化。

c.提升性能:使用寄存器输出可以使得最终读取操作不在RAM上进行,不影响输出数据,因此有时可以提高整体系统的吞吐率。

三、BRAM IP核的调用和仿真

1.单端口RAM:

顶层文件:

`timescale 1ns/1ns
module bram_test(
    input wire wea,
    input wire clk,
    input wire [15:0]din,
    output wire [15:0] dout,
    output wire [3:0] addr 
);

addr_ctrl addr_ctrl_inst(.clk(clk), .addr0(addr));

/*
blk_mem_gen_0 blk_mem_gen_0_inst0 (        //read first型 单端口ram,注意dout在下一时钟周期读出
  .clka(clk),    // input wire clka
  .wea(wea),      // input wire [0 : 0] wea
  .addra(addr),  // input wire [3 : 0] addra
  .dina(din),    // input wire [15 : 0] dina
  .douta(dout)  // output wire [15 : 0] douta
);
*/
blk_mem_gen_1 blk_mem_gen_1_inst1 (         //write first型 单端口ram,注意dout在下一时钟周期读出 
  .clka(clk),    // input wire clka
  .wea(wea),      // input wire [0 : 0] wea
  .addra(addr),  // input wire [3 : 0] addra
  .dina(din),    // input wire [15 : 0] dina
  .douta(dout)  // output wire [15 : 0] douta
);
   /*
blk_mem_gen_2 blk_mem_gen_2_inst2 (         //no change型 单端口ram  
  .clka(clk),    // input wire clka
  .wea(we),      // input wire [0 : 0] wea
  .addra(addr),  // input wire [3 : 0] addra
  .dina(din),    // input wire [15 : 0] dina
  .douta(dout)  // output wire [15 : 0] douta
);
  */ 
endmodule


module addr_ctrl(        //clk触发,实现地址由0000到1111循环
    input clk,
    output reg [9:0] addr0 = 0
);

always @(clk) begin
    //#1;
    if (addr0==4'b1111) addr0<=0;
    else addr0 <= addr0+1;
end
    
endmodule

testbench:

`timescale 1ns/1ns
module bram_test_tb();     
reg clk;        
reg [15:0] din;  
reg wea;        
wire [15:0] dout;
wire [3:0] addr; 
integer i;

always #5 clk <= ~clk;   //时钟信号

bram_test test1(.clk(clk), .din(din), .dout(dout), .wea(wea), .addr(addr));

initial begin 
    clk = 0;
    wea = 1'b0;
    din = 0;
    for(i=0;i<126;i=i+1)begin   //din与wea的变化
        #7.5 din = i;
        if(i%3 == 0) wea = 1'b1;else wea = 0;
    end
    #5 wea = 0;
    #50 $finish;
end

endmodule

仿真结果见二、3.(1)write first (2)read first (3)no change

2.双端口RAM:

顶层文件

`timescale 1ns / 1ps


module dual_port_bram_test(    
    input clka,
    input ena,
    input wea,
    input [3:0] addra,
    input [15:0] dina,
    output [15:0] douta,
    
    input clkb, 
    input enb, 
    input web,  
    input [3:0] addrb,
    input [15:0] dinb,
    output [15:0] doutb
);

blk_mem_gen_0 blk_mem_sd (                      //simple dual port ram,a为write first输入,b为write first输出
  .clka(clka),    // input wire clka
  .ena(ena),      // input wire ena
  .wea(wea),      // input wire [0 : 0] wea
  .addra(addra),  // input wire [3 : 0] addra
  .dina(dina),    // input wire [15 : 0] dina
  .clkb(clkb),    // input wire clkb
  .enb(enb),      // input wire enb
  .addrb(addrb),  // input wire [3 : 0] addrb
  .doutb(doutb)  // output wire [15 : 0] doutb
);   
 /*   
blk_mem_gen_1 blk_mem_td (                     //true dual port ram,ab均为write first
  .clka(clka),    // input wire clka
  .ena(ena),      // input wire ena
  .wea(wea),      // input wire [0 : 0] wea
  .addra(addra),  // input wire [3 : 0] addra
  .dina(dina),    // input wire [15 : 0] dina
  .douta(douta),  // output wire [15 : 0] douta
  .clkb(clkb),    // input wire clkb
  .enb(enb),      // input wire enb
  .web(web),      // input wire [0 : 0] web
  .addrb(addrb),  // input wire [3 : 0] addrb
  .dinb(dinb),    // input wire [15 : 0] dinb
  .doutb(doutb)  // output wire [15 : 0] doutb
);    
  */  
endmodule

testbench:

`timescale 1ns / 1ps
module dual_port_bram_test_tb();
reg clka;     
reg ena;         
reg wea;
reg [3:0] addra;
reg [15:0] dina;
wire [15:0] douta;

reg clkb;         
reg enb;          
reg web;          
reg [3:0] addrb;  
reg [15:0] dinb;  
wire [15:0] doutb;
integer i;

dual_port_bram_test inst_1(.clka(clka), .clkb(clkb), .wea(wea), .web(web), .ena(ena), .enb(enb), .addra(addra), .addrb(addrb), .dina(dina), .dinb(dinb), .douta(douta), .doutb(doutb));

always #5 begin   //产生时钟,ab时钟同步翻转
    clka <= ~clka;
    clkb <= ~clkb;
end
/*
always begin      //产生时钟,ab时钟不同步
    #5 clka <= ~clka;
    #12 clkb <= ~clkb;
end
*/
initial begin        //产生dina和dinb、wea和eab、输入addra和addrb
    clka <= 0;
    clkb <= 1;
    dina <= 0;
    dinb <= 1;
    ena  <= 1;
    enb  <= 1;
    addra<= 0;
    addrb<= 1;
    wea  <= 0;
    web  <= 1;
    for(i=0;i<126;i=i+1) begin
        #7.5
        dina <= i;
        dinb <= i+2;
        addra <= addra+1;
        addrb <= addrb+1;
        case(i%5)
            1'b0:wea=1;
            1'b1:web=1;
            default:begin 
                wea<=0;web<=0;
            end
        endcase 
    end
end

endmodule

仿真结果

①简单双端口(simple dual port ram)

A端口输入,B端口输出,且两端口模式固定,为write first,其他Operating Mode选项无效

②真双端口(true dual port ram)

A、B端口独立各自输入输出,且两端口模式固定,为write first,其他Operating Mode选项无效

四、BRAM的应用

待补充

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐