本文转载自:米联客
1.1概述
对于BRAM 详细的说明在XILINX 官方文档,pg058中有说明,我们这里仅对课程涉及的内容讲解。
Xlinx系列FPGA,包含两种RAM:Block RAM和分布式RAM(Distributed RAM),他们的区别在于,Block RAM是内嵌专用的RAM,而Distributed RAM需要消耗珍贵的逻辑资源组成。前者具有更高的时序性能,而后者由于分布在不通的位置,延迟较大。
1.2 BRAM RAM的应用形式
1.2.1单口ROM (Single-Port ROM)
单口ROM,就是数据只读的,需要在IP初始化的时候,对ROM进行初始化,而且只有一个读接口。
1.2.2双口ROM(Dual-port ROM)
端口A和端口B可以同时访问ROM
1.2.3单口RAM(Single-port RAM)
单口RAM只能一个时刻写,一个时刻读
1.2.4简单双口RAM(Simple Dual-port RAM)
A端口写,B端口读
1.2.5真双口RAM(True Dual-port RAM)
A 端口和B端口都可以读或者写
1.3 BLOCK RAM的读写模式
支持3种模式,分别是Write First Mode, Read First Mode, No Change Mode
1.3.1先写模式(Write First Mode)
这种模式下:
1)写操作:设置WEA为1写入当前地址的数据,而且在下一个时钟DOUTA会输出当前写入的
2)读操作:设置WEA为0读出当前地址的数据,在下一个时钟,DOUTA有效
1.3.2先读模式(Read First Mode)
这种模式下:
1)写操作:设置WEA为1写入当前地址的数据,而且在下一个时钟DOUTA会输出之前这个地址的数据
2)读操作:设置WEA为0读出当前地址的数据,在下一个时钟,DOUTA有效
1.3.3不变模式(No Change Mode)
这种模式下:
1)写操作:设置WEA为1写入当前地址的数据,和前面两种方式不一样,DOUT保存不变
2)读操作:设置WEA为0读出当前地址的数据,在下一个时钟,DOUTA有效
1.4 支持字节写入(BYTE Write)
另外,BRAM还具备BYTE Write功能,这样可以只对某一个字节进行修改,从下图时序图可以看出,只要控制WEA就可以控制对具体哪一个BYTE进行写控制。
1.5访问冲突 (Collision Behavior)
BRAM 很好用,但是需要注意冲突的问题,就是对于同一个地址写或者读的时候需要注意。
1.5.1异步时钟处理原则
使用异步时钟,当一个端口将数据写入存储位置时,另一端口在指定的时间内不得读取或写入该位置。 器件数据手册中定义了该时钟到时钟的建立时间,以及其他Block RAM切换特性。这里说到的” 时钟到时钟的建立时间”我还没注意到哪一个文档有说明。所以异步时钟可以通过长期的稳定性测试获取到这个时间间隔。
1.5.2同步时钟处理原则
同步写冲突:如果两个端口都试图写到内存中的同一位置,则会发生写写冲突。 内存位置的结果内容未知。 请注意,Write-Write冲突会影响内存内容,而Write-Read冲突只会影响数据输出
使用字节写入:使用字节写入时,在同一数据字中写入单独的字节时,存储器内容不会损坏。 仅当两个端口都试图写入同一字节时,RAM内容才会损坏。 下图说明了这种情况。 假设addra = addrb = 0
同步读写冲突:如果一个端口尝试写入内存位置而另一个端口读取相同的位置,则可能发生同步读写冲突。 虽然在写-读冲突中存储器的内容没有损坏,但是输出数据的有效性取决于写端口的工作模式。
a: 如果写入端口处于READ_FIRST模式,则另一个端口可以可靠地读取旧的存储器内容。
b: 如果写入端口处于WRITE_FIRST或NO_CHANGE模式,则读取端口的输出上的数据无效。
c: 如果是字节写入,则只有更新的字节在读取端口输出上无效,但是RAM中的内容是对的
下图说明了读写冲突和字节写入的影响。 当端口A处于WRITE_FIRST模式和READ_FIRST模式时,显示doutb。 假定addra = addrb = 0,端口B始终处于读取状态,并且所有内存位置均初始化为0。RAM的内容在读写冲突中不会被破坏。
1.5.3简单的双端口RAM冲突
对于简单双端口RAM,无论时钟如何,都可以使用READ_FIRST,WRITE_FIRST和NO_CHANGE工作模式。
简单双端口RAM就像真正的双端口RAM,其中仅连接了A端口的Write接口和B端口的Read接口。 工作模式定义了A或B端口的读写关系,并且仅在地址冲突期间影响A和B端口之间的关系。
对于同步时钟和冲突期间,可以配置端口A的写模式,以便对端口B的读操作可以产生数据(作用类似于READ_FIRST),也可以产生未定义的数据(Xs)。 因此,始终建议在配置为简单双端口RAM时使用READ_FIRST。 对于异步时钟,Xilinx建议将端口A的写入模式设置为WRITE_FIRST以确保碰撞安全。 有关此行为的详细信息,请参阅pg058第51页的冲突行为。
对于7系列设备,当RAM_MODE设置为ture dual port时,选定的操作模式将传递到Block RAM。 对于将RAM_MODE设置为simple dual port的原语,写模式为READ_FIRST用于同步时钟,而WRITE_FIRST用于异步时钟。
对于基于UltraScale架构的设备,没有限制,并且无论时钟如何,所选的操作模式总是传递给Block RAM原语。这一段说明,这种高级模式我们暂时不涉及。
其他内存冲突限制:地址空间重叠
7系列FPGA Block RAM存储器在以下配置中具有附加的冲突限制:
•当配置为真双端口(ture dual port)
•当CLKA(端口A)和CLKB(端口B)异步时
•在同时执行读写操作的应用程序中
•使用配置为READ_FIRST的写入模式配置端口A,端口B或两个端口时
上面文字描述中很多都在讲解冲突,其实对于我们的具体应用而言,更多时候我们BRAM是做乒乓使用的,也就是读地址和写地址,都是不会同时发生,而且时钟是同步的,这样就不容易发生冲突导致的数据破坏,和不正确。
1.6输出寄存器
BRAM 可以设置有寄存器输出和无寄存器输出,下图是BRAM的框图结构
下图是有寄存器和无寄存器输出,可以达到的最高时钟频率的数据表,所以增加寄存器输出可以提高速度。我们例子中由于用到的演示时钟并没有很高所以不需要增加寄存器输出。
1.6.1读取数据和读取使能延迟,无输出寄存器
1.6.2通过原始输出寄存器读取数据并实现rEad使能延迟
1.6.3使用两个流水线阶段读取数据和读取启用延迟
1.7添加BRAM IP
设置简单双口RAM
设置BRAM的端口A的宽度和深度
设置BRAM的端口B的宽度和深度,并且没有寄存器输出
这一页默认
单击OK
1.8读写BRAM代码
本代码的设计和FIFO使用非常类似
1)、写操作:写操作不断进行,每次写入1024个数据
2)、读操作:读操作是在每次写入达到512个数据开始的,当然实际上读操作完全可以和写操作同时进行,错开512个数据是为了方便观察现象。
module Bram_test(
input rstn_i,
input sys_clk_i
);
reg [9:0]addra;
reg [7:0]wr_frame;
reg WR_S;
reg ena;
reg wea;
always @(posedge sys_clk_i)begin
if(!rstn_i)begin
wr_frame <= 8'd0;
addra <= 9'd0;
ena <= 1'b1;
wea <= 1'b0;
WR_S <= 1'd0;
end
else begin
case(WR_S)
0:begin
addra <= 10'd0;
ena <= 1'd1;
wea <= 1'b1;
WR_S <= 1'b1;
end
1:begin
if(addra != 10'd1023)begin //写数据的1024个地址
wea <= 1'b1;
ena <= 1'b1;
addra <= addra + 1'b1;
end
else begin
wea <= 1'b0;
ena <= 1'b0;
wr_frame <= wr_frame +1'b1;//帧计数器
WR_S <= 1'b0;
end
end
endcase
end
end
reg [9:0]addrb;
reg RD_S;
reg enb;
always @(posedge sys_clk_i)begin
if(!rstn_i)begin
addrb <= 9'd0;
enb <= 1'b0;
RD_S <= 1'd0;
end
else begin
case(RD_S)
0:begin
enb <= 1'b0;
addrb <= 10'd0;
if(addra == 10'd512)begin//读数据在写数据的第512个地址开始
enb <= 1'b1;
RD_S <= 1'b1;
end
end
1:begin
if(addrb != 10'd1023)begin//读出1024个数据
enb <= 1'b1;
addrb <= addrb + 1'b1;
end
else begin
enb <= 1'b0;
addrb <= 10'd0;
RD_S <= 1'b0;
end
end
endcase
end
end
wire [31:0] dina = {wr_frame,wr_frame,addra[7:0],addra[7:0]};
wire [31:0] doutb;
blk_mem_gen_0 bram_inst (
.clka(sys_clk_i), // input wire clka
.ena(ena), // input wire ena
.wea(wea), // input wire [0 : 0] wea
.addra(addra), // input wire [9 : 0] addra
.dina(dina), // input wire [31 : 0] dina
.clkb(sys_clk_i), // input wire clkb
.enb(enb), // input wire enb
.addrb(addrb), // input wire [9 : 0] addrb
.doutb(doutb) // output wire [31 : 0] doutb
);
1.9 仿真文件
module tb_bram_test;
reg sys_clk_i;
reg rstn_i;
Bram_test Bram_test_inst(
.sys_clk_i(sys_clk_i),
.rstn_i(rstn_i)
);
initial begin
sys_clk_i = 1'b0;
rstn_i = 1'b0;
#5;sys_clk_i = 1'b1;
#5;sys_clk_i = 1'b0;
#5;sys_clk_i = 1'b1;
#5;sys_clk_i = 1'b0;
#5;sys_clk_i = 1'b1;
#5;sys_clk_i = 1'b0;
#5;sys_clk_i = 1'b1;
#5;sys_clk_i = 1'b0;
#5;sys_clk_i = 1'b1;
rstn_i = 1'b1;
forever
#5 sys_clk_i = ~sys_clk_i;
end
endmodule
1.10 仿真结果
箭头1 写开始
箭头2 写完1024个数据
箭头3 当写数据达到512个后开始读
箭头4 读完1024个数据