(1)单端口RAM 模式
单端口RAM的模型如图所示,只有一个时钟源CLK,WE为写使能信号,EN为单口RAM使能信号,SSR为清零信号,ADDR为地址信号,DI和DO分别为写入和读出数据信号。
单端口RAM模式支持非同时的读写操作。同时每个块RAM可以被分为两部分,分别实现两个独立的单端口RAM。需要注意的是,当要实现两个独立的单端口RAM模块时,首先要保证每个模块所占用的存储空间小于块RAM存储空间的1/2。在单端口RAM配置中,输出只在read-during-write模式有效,即只有在写操作有效时,写入到RAM的数据才能被读出。当输出寄存器被旁路时,新数据在其被写入时的时钟上升沿有效。
(2)简单的双端口RAM
简单双端口RAM 模型如下图所示,图中上边的端口只写,下边的端口只读,因此这种RAM 也被称为伪双端口RAM(Pseudo Dual Port RAM)。这种简单双端口RAM 模式也支持同时的读写操作。
块RAM 支持不同的端口宽度设置,允许读端口宽度与写端口宽度不同。这一特性有着广泛地应用,例如:不同总线宽度的并串转换器等。在简单双端口RAM 模式中,块RAM具有一个写使能信号wren 和一个读使能信号rden,当rden 为高电平时,读操作有效。当读使能信号无效时,当前数据被保存在输出端口。当读操作和写操作同时对同一个地址单元时,简单双口RAM 的输出或者是不确定值,或者是存储在此地址单元的原来的数据。
(3)真正双端口RAM 模式
真正双端口RAM模型如下图所示,图中上边的端口A和下边的端口B都支持读写操作,WEA、WEB信号为高时进行写操作,低为读操作。同时它支持两个端口读写操作的任何组合:两个同时读操作、两个端口同时写操作或者在两个不同的时钟下一个端口执行写操作,另一个端口执行读操作。
真正双端口RAM模式在很多应用中可以增加存储带宽。例如,在包含嵌入式处理器iroBlaze和DMA控制器系统中,采用真正双端口RAM模式会很方便;相反,如果在这样的一个系统中,采用简单双端口RAM模式,当处理器和DMA控制器同时访问RAM时,就会出现问题。真正双端口RAM模式支持处理器和DMA控制器同时访问,这个特性避免了采用仲裁的麻烦,同时极大地提高了系统的带宽。
一般来讲,在单个块RAM实现的真正双端口RAM模式中,能达到的最宽数据位为36比特*512,但可以采用级联多个块RAM的方式实现更宽数据位的双端口RAM。当两个端口同时向同一个地址单元写入数据时,写冲突将会发生,这样存入该地址单元的信息将是未知的。要实现有效地向同一个地址单元写入数据,A端口和B端口时钟上升沿的到来之间必须满足一个最小写周期时间间隔。因为在写时钟的下降沿,数据被写入块RAM中,所以A端口时钟的上升沿要比B端口时钟的上升沿晚到来1/2个最小写时钟周期,如果不满足这个时间要求,则存入此地址单元的数据无效。
(4)ROM 模式
块RAM还可以配置成ROM,可以使用存储器初始化文件(.coe)对ROM进行初始化,在上电后使其内部的内容保持不变,即实现了ROM功能。
(5)FIFO 模式
FIFO即先入先出,其模型如下图所示。在FIFO具体实现时,数据存储的部分是采用简单双端口模式操作的,一个端口只写数据而另一个端口只读数据,另外在RAM(块RAM和分布式RAM)周围加一些控制电路来输出指示信息。FIFO最重要的特征是具备“满(FULL)”和“空(EMPTY)”的指示信号,当FULL信号有效时(一般为高电平),就不能再往FIFO中写入数据,否则会造成数据丢失;当EMPTY信号有效时(一般为高电平),就不能再从FIFO中读取数据,此时输出端口处于高阻态。
例子:利用块RAM实现a、b两路数据的延迟,其中a、b两路数据的位宽都为32比特,速率都为61.44Mbps,要求a路延迟16个数据周期,b路延迟8个时钟周期。
module bram_delay(
clk_122p88MHz, a, b, a_delay, b_delay
);
input clk_122p88MHz;
input [31:0]a,b;
output [31:0]a_delay,b_delay;
reg [31:0] a_delay;
reg [31:0] b_delay;
wire [5:0] addra, addrb;
wire [31:0] douta, doutb;
reg [5:0] addra1 = 0;
reg [5:0] addra2 = 0;
reg [5:0] addrb1 = 32;
reg [5:0] addrb2 = 32;
reg wea = 0;
reg web = 0;
reg flag = 0;
bram_16 bram_16(
.clka(clk_122p88MHz),
.dina(a),
.addra(addra),
.wea(wea),
.douta(douta),
.clkb(clk_122p88MHz),
.dinb(b),
.addrb(addrb),
.web(wea),
.doutb(doutb)
);
assign addra = !flag ? addra1 : addra2;
assign addrb = !flag ? addrb1 : addrb2;
always @ (posedge clk_122p88MHz)
begin
flag<=!flag;
if(flag==1'b1)//write
begin
a_delay<=a_delay;
b_delay<=b_delay;
wea<=1'b1;
web<=1'b1;
addra2<=addra2;//the read address
addrb2<=addrb2;
if(addra1==31)
addra1<=0;
else
addra1<=addra1+1;
if(addrb1==63)
addrb1<=32;
else
addrb1<=addrb1+1;
end
else
begin
wea<=1'b0;
web<=1'b0;
a_delay<=douta;
b_delay<=doutb;
addra1<=addra1;
addrb1<=addrb1;
if(addra1<=15)
addra2<=addra1+16;
else
addra2<=addra1-16;//a_delay 16 clock period
if(addrb1<=39)
addrb2<=addrb1+24;
else
addrb2<=addrb1-8;//b_delay 8 clock period
end
end
endmodule