文章目录
一、BRAM简介
BRAM是“Block RAM”的缩写,指的是在FPGA中常见的存储器类型,用于存储数据和指令。BRAM通常由一系列存储单元组成,可用于在FPGA中实现存储器功能。
二、BMG简介和特点
Xilinx提供 BMG(Block Memory Generator )IP核,方便用户调用BRAM资源来构成用户想要的存储器类型。例如:单端口 RAM、简单双端口 RAM、真正的双端口 RAM、单端口 ROM 和双端口 ROM 。对于双端口存储器,每个端口独立运行。每个端口均可选择工作模式、时钟频率、可选输出寄存器和可选引脚(简单双端口不能选工作模式)。
- BMG 可选存储器算法内核配置Block RAM 原语并使用以下算法之一将它们连接在一起:
-
最小面积算法:使用最小数量的Block RAM 原语生成存储器。
-
低功耗算法:生成存储器时,在读或写操作期间启用最少数量的块RAM 原语;
-
固定原语算法:仅使用一种类型的Block RAM 原语生成内存;
-
每个端口可选择的操作模式:WRITE FIRST、READ FIRST 和 NO CHANGE,每个端口都可以分配其自己的操作模式。
-
可选端口宽高比:A 端口宽度可能与 B 端口宽度相差 1、2、4、8、16 或 32 倍。
-
可选的字节写入使能:提供字节写入支持,内存宽度是八位(无奇偶校验)或九位(有奇偶校验)的倍数。
-
可选流水线级:内核在 MUX 内提供可选流水线级,仅当存储器内核输出处的寄存器启用且仅适用于特定配置时才可用。
三、操作模式
- Write First 模式:在 WRITE_FIRST 模式下,输入数据同时写入存储器并驱动数据输出,如下图所示:
写操作:当 WEA 为 1 写入当前地址的数据,在下一个时钟 DOUTA 会输出这个地址新写入的数据。
读操作:当 WEA 为 0 读出当前地址的数据,在下一个时钟 DOUTA 会输出这个地址的数据。
- Read First 模式:在 READ_FIRST 模式下,先前存储在写地址的数据出现在数据输出上,而输入数据则存储在内存中,如下图所示:
写操作:当 WEA 为 1 写入当前地址的数据,而且在下一个时钟 DOUTA 会输出这个地址的原先的数据。
读操作:当 WEA 为 0 读出当前地址的数据,在下一个时钟 DOUTA 会输出这个地址的数据。
- No Change模式:在 NO CHANGE 模式下,输出寄存器在写操作期间保持不变。输出的数据仍然是之前的Read数据,不受同端口Write操作的影响,如下图所示:
写操作:当 WEA 为 1 写入当前地址的数据,和前面两种方式不一样,DOUT 保存不变。
读操作:当 WEA 为 0 读出当前地址的数据,在下一个时钟 DOUTA 会输出这个地址的数据。
四、端口位宽不一致
BMG IP核支持的端口纵横比为 1:32、1:16、1:8、1:4、1:2、1:1、2:1、4:1、8:1、16 :1 和 32:1。端口 A 数据宽度最多可以比端口 B 数据宽度大 32 倍,反之亦然。
以下是AB端口位宽不一致的情况, 以下为32x2048 的真正双端口 RAM,即 A 端口宽度和深度。从8位B端口的角度来看,深度为8192。addra总线为11位,而addrb总线为13位。An 是相对于 A 端口的地址 n 处的数据字; Bn 是相对于 B 端口的地址 n 处的数据字;A0 由 B3、B2、B1 和 B0 组成。
对于B端口,第B0地址为A0地址所在32位数据的低8位,以此类推。
五、字节写
BMG IP核提供字节写入支持。字节写入可使用 8 位或 9 位字节大小。当使用 8 位字节大小时,不使用奇偶校验位,并且存储器宽度限制为 8 位的倍数。当使用 9 位字节大小时,每个字节都包含一个奇偶校验位,并且存储器宽度被限制为 9 位的倍数。
从上面时序图可以看出,只要控制 WEA 就可以控制对具体哪一个 BYTE 进行写控制,例如当WEA[2:0]=011时候,就选取DINA[23:0]低两位字节写入到当前地址中。
六、输出寄存器
BMG IP核可选的输出寄存器,这可能会提高内核的性能;可以选择在两个位置包含寄存器:BLOCK RAM 原语的输出处和内核的输出处。Block RAM 原语输出处的寄存器减少了原语的时钟到输出延迟的影响。内核输出端的寄存器通过输出多路复用器隔离延迟,从而改善块内存生成器内核的时钟到输出延迟。两个可选寄存器级中的每一个都可以分别为端口 A 和端口 B 选择。
使用的每个可选寄存器级都会为读取操作增加一个额外的时钟周期延迟。
下图显示了没有使用输出寄存器时的读数据 (LATCH) 和读使能 (en) 延迟, LATCH 信号是原语输出处的数据:
下图显示了使用原语输出寄存器读取数据和读取使能延迟:
下图显示了使用两级流水线寄存器的读取数据和读取使能延迟:
七、调用BRAM并仿真测试
7.1 单端口ROM的仿真测试
单端口 ROM 允许通过单个端口对存储空间进行读访问
首先生成好coe文件,从1累加到256的数据。
控制代码如下:
`timescale 1ns / 1ps
module bram_test(
input rd_clk ,
input wr_clk ,
input rst_n
);
reg [7:0] addra ;
wire [8:0] douta ;
always @(posedge rd_clk or negedge rst_n) begin
if(rst_n == 1'b0)
addra <= 'd0;
else
addra <= addra + 1'b1;
end
u_bram u_bram (
.clka(rd_clk ),
.addra(addra),
.douta(douta)
);
endmodule
仿真结果如下:
时钟上升沿时刻输入地址信号,在下一时钟上升沿输出数据。
7.2 双端口ROM的仿真测试
双端口 ROM 允许通过两个端口对内存空间进行读访问。还是以同样的配置方式,稍微修改以下控制程序,打开仿真:
`timescale 1ns / 1ps
module bram_test(
input rd_clk1 ,
input rd_clk2 ,
input rst_n
);
reg [7:0] addra ;
wire [8:0] douta ;
reg [7:0] addrb ;
wire [8:0] doutb ;
always @(posedge rd_clk1 or negedge rst_n) begin
if(rst_n == 1'b0)
addra <= 'd0;
else
addra <= addra + 1'b1;
end
always @(posedge rd_clk2 or negedge rst_n) begin
if(rst_n == 1'b0)
addrb <= 'd0;
else
addrb <= addrb + 1'b1;
end
u_bram u_bram (
.clka(rd_clk1),
.addra(addra),
.douta(douta),
.clkb(rd_clk2),
.addrb(addrb),
.doutb(doutb)
);
endmodule
仿真结果依然和单口ROM一致;时钟上升沿时刻输入地址信号,在下一时钟上升沿输出数据。
7.3 单口RAM的仿真测试
单端口 RAM 允许通过单个端口对存储器进行读写访问,wea = 1为写,wea=0为读。设置步骤如下:
控制程序如下:
`timescale 1ns / 1ps
module bram_test(
input clk1 ,
input rst_n
);
reg [7:0] addra ;
reg [8:0] dina ;
wire [8:0] douta ;
reg wea ;
reg [7:0] addrb ;
reg [8:0] dinb ;
wire [8:0] doutb ;
always @(posedge clk1 or negedge rst_n) begin
if(rst_n == 1'b0)begin
addra <= 'd0;
wea <= 1'b1;
dina <= 'd0;
end
else if(addra == 'd200)begin
addra <= 'd0;
wea <= 1'b0;
end
else begin
addra <= addra + 1'b1;
dina <= dina + 1'b1;
end
end
u_bram u_bram (
.clka(clk1),
.wea(wea),
.addra(addra),
.dina(dina),
.douta(douta)
);
endmodule
当wea=1时,执行写操作,从地址0到200写入0到200的数据。
当wea=0时,执行读操作,从地址0到200延迟一拍后读出数据。
7.4 简单双端口RAM的仿真测试
简单双端口 RAM 提供两个端口 A 和 B,允许通过端口 A 对存储器进行写访问,并允许通过端口 B 进行读访问。
还是按照上面的配置步骤,选择简单双端口RAM,宽度9,深度256。稍微修改一下控制程序,然后打开仿真:
`timescale 1ns / 1ps
module bram_test(
input clka ,
input clkb ,
input rst_n
);
reg [7:0] addra ;
reg [8:0] dina ;
wire [8:0] douta ;
reg wea ;
reg [7:0] addrb ;
reg [8:0] dinb ;
wire [8:0] doutb ;
always @(posedge clka or negedge rst_n) begin
if(rst_n == 1'b0)begin
addra <= 'd0;
wea <= 1'b1;
dina <= 'd0;
addrb <= 'd0;
end
else if(addra == 'd200)begin
addra <= 'd0;
addrb <= 'd0;
wea <= 1'b0;
end
else begin
addra <= addra + 1'b1;
addrb <= addrb + 1'b1;
dina <= dina + 1'b1;
end
end
u_bram u_bram (
.clka(clka),
.wea(wea),
.addra(addra),
.dina(dina),
.clkb(clkb),
.addrb(addrb),
.doutb(doutb)
);
endmodule
开始端口A先进行写操作,从地址0写到200。数据从0开始累加到200。
当端口A写满到200地址后拉低wea信号,然后开始端口B读操作,数据延迟端口B地址一拍出来。
7.5 真双端口RAM的仿真测试
真双端口 RAM 提供两个端口 A 和 B任一端口都允许对存储器进行读和写访问。
还是按照上面的配置步骤,选择真双端口RAM,宽度9,深度256。稍微修改一下控制程序,然后打开仿真:
`timescale 1ns / 1ps
module bram_test(
input clka ,
input clkb ,
input rst_n
);
reg [7:0] addra ;
reg [8:0] dina ;
wire [8:0] douta ;
reg wea ;
reg [7:0] addrb ;
reg [8:0] dinb ;
wire [8:0] doutb ;
reg web ;
always @(posedge clka or negedge rst_n) begin
if(rst_n == 1'b0)begin
addra <= 'd0;
wea <= 1'b1;
dina <= 'd0;
end
else if(addra == 'd200)begin
addra <= 'd0;
wea <= 1'b0;
end
else begin
addra <= addra + 1'b1;
dina <= dina + 1'b1;
end
end
always @(posedge clkb or negedge rst_n) begin
if(rst_n == 1'b0)begin
addrb <= 'd0;
web <= 1'b1;
dinb <= 'd0;
end
else if(addrb == 'd200)begin
addrb <= 'd0;
web <= 1'b0;
end
else begin
addrb <= addrb + 1'b1;
dinb <= dinb + 1'b1;
end
end
u_bram u_bram (
.clka(clka),
.wea(wea),
.addra(addra),
.dina(dina),
.douta(douta),
.clkb(clkb),
.web(web),
.addrb(addrb),
.dinb(dinb),
.doutb(doutb)
);
endmodule
上面可以看出,端口A和B操作都和单端口一致,端口A和端口B的时钟可以异步,但是不能同时对一个地址进行操作,否者会出错。
参考
《PG058》