Xilinx伪双口RAM实现同步FIFO(解决读写冲突)
一、伪双端口RAM配置
关于创建和配置IP,可以参考我的另一篇文章:Vivado 双口RAM IP核的使用,不同之处只是在于本文使用的伪双端口RAM的写端口和读端口都加了使能信号,也即没有选择始终使能。
这里需要注意的是,如果想要解决读写冲突,也就是当同时读写同一地址时,能读出之前存在该地址的旧数据,也能将要写的新数据正确写入到该地址,那写端口的操作模式一定要选择READ_FIRST
或者NO_CHANGE
。
如果选择WRITE_FIRST
,那么当读写冲突时,读出的数据是此时准备写入的数据,并不是之前存储的数据,这显然不符合逻辑。
二、读写地址变化方式
1、关于写地址的变化,只需考虑we有效的情况:
- 只写不读时:若FIFO满,则写失败,写地址不变;若FIFO不满,则写一定成功,写地址加1。
- 又写又读时:若FIFO满,读写为同一单元,因读操作只需读取旧数据,所以读写都成功,写地址加1;若FIFO不满,则进行读写操作的是两个不同单元,所以写一定成功,所以写地址也加1。
总结起来,(we有效&&((re无效&&full标志无效)||re有效))
时,写地址加1;其他情况,写地址不变。因为只有写成功(写地址加1)的情况,才能让RAM的写使能有效,所以RAM的写使能也为(we有效&&((re无效&&full标志无效)||re有效))
。
2、关于读地址的变化,只需考虑re有效的情况:
- 只读不写时:若FIFO空,则读失败,读地址不变;若FIFO不空则读一定成功,读地址加1.。
- 又读又写时:若FIFO空,则读写为同一单元,由于单元内无旧数据,读失败,只有写成功,此时读地址不变;若FIFO不空,则进行读写操作的是两个不同单元,所以读一定成功,读地址加1。
总结起来,(re有效&&empty标志无效)
时,读地址加1;其他情况,读地址不变。因为只有读成功(读地址加1)的情况,才能让RAM的读使能有效,所以RAM的读使能也为(re有效&&empty标志无效)
。
三、代码设计
需要注意的是,代码中的信号都是低电平有效,当然也可以设置成高电平有效,随意!!!
实现的是大小为16*32的fifo!!!
module syn_fifo(
input clk,
input reset,
input [31:0] data_in,//写入的命令
input wr_en,//写使能信号,低有效,
input rd_en,//读使能信号,低有效,
output reg empty,//fifo空标志,低有效
output reg full,//fifo满标志,低有效
output [31:0] data_out//读出的数据
);
reg [3:0] wr_addr ;
reg [3:0] rd_addr ;
reg [4:0] count ;
assign we = !wr_en && ((rd_en && full) || !rd_en);
assign re = !rd_en&∅
parameter max_count = 5'b10000 ;
//更新读地址
always @ (posedge clk or posedge reset) begin
if (reset == 1'b1)
rd_addr<=4'b0000;
else if (re)
rd_addr<=rd_addr+1;
else
rd_addr<=rd_addr;
end
//更新写地址
always @ (posedge clk or posedge reset) begin
if (reset == 1'b1)
wr_addr<=4'b0000;
else if (we)
wr_addr<=wr_addr+1;
else
wr_addr<=wr_addr;
end
//更新标志位
always @ (posedge clk or posedge reset) begin
if (reset == 1'b1)
count<=0;
else begin
case({we,re})
2'b00:count<=count;
2'b01:
if(count!==5'b00000)
count<=count-1;
2'b10:
if(count!== max_count)
count<=count+1;
2'b11:count<=count;
endcase
end
end
//这里是组合逻辑,所以count一旦变成0,empty立即有效!!!
always @(count) begin
if(count==5'b00000)
empty = 0;
else
empty = 1;
end
//这里是组合逻辑,所以count一旦变成16,full立即有效!!!
always @(count) begin
if (count== max_count)
full = 0;
else
full = 1;
end
ram_ip u_ram (
.clka(clk),
.ena(1'b1),
.wea(we),
.addra(wr_addr),
.dina(data_in),
.clkb(clk),
.enb(re),
.addrb(rd_addr),
.doutb(data_out)
);
endmodule
四、代码仿真
//~ `New testbench
`timescale 1ns / 1ps
module tb_syn_fifo;
// syn_fifo Parameters
parameter PERIOD = 10 ;
parameter max_count = 5'b10000;
// syn_fifo Inputs
reg clk = 0 ;
reg reset = 1 ;
reg [31:0] data_in = 0 ;
reg wr_en = 1 ;
reg rd_en = 1 ;
// syn_fifo Outputs
wire empty ;
wire full ;
wire [31:0] data_out ;
initial
begin
forever #(PERIOD/2) clk=~clk;
end
initial
begin
#(PERIOD*2) reset = 0;
end
syn_fifo #(
.max_count ( max_count ))
u_syn_fifo (
.clk ( clk ),
.reset ( reset ),
.data_in ( data_in [31:0] ),
.wr_en ( wr_en ),
.rd_en ( rd_en ),
.empty ( empty ),
.full ( full ),
.data_out ( data_out [31:0] )
);
reg [5:0]cnt;
always@(posedge clk or negedge reset)
begin
if(reset)
begin
wr_en <= 1;
rd_en <= 1;
data_in <= 0;
cnt <= 0;
end
else if(cnt < 10)
begin
//#2
wr_en <= 0;
rd_en <= 1;
data_in <= data_in + 1;
cnt <= cnt + 1;
end
else if(cnt < 12)
begin
//#2
wr_en <= 0;
rd_en <= 0;
data_in <= data_in + 1;
cnt <= cnt + 1;
end
else if(cnt < 18)
begin
//#2
wr_en <= 0;
rd_en <= 1;
data_in <= data_in + 1;
cnt <= cnt + 1;
end
else if(cnt < 23)
begin
//#2
wr_en <= 0;
rd_en <= 0;
data_in <= data_in + 1;
cnt <= cnt + 1;
end
else if(cnt < 39)
begin
//#2
wr_en <= 1;
rd_en <= 0;
data_in <= data_in + 1;
cnt <= cnt + 1;
end
else if(cnt < 50)
begin
//#2
wr_en <= 0;
rd_en <= 0;
data_in <= data_in + 1;
cnt <= cnt + 1;
end
else
begin
wr_en <= 1;
rd_en <= 1;
data_in <= 0;
cnt <= 0;
$finish;
end
end
endmodule
可以看出,一开始fifo往里写数据,此时只有写使能有效,所以写地址一直增加,读地址不变。在125ns和135ns时,开始读数据,读地址由0变成了2。
之后,读使能无效,写使能始终有效,继续往里写数据。因为前面读出了两个数据,所以这里写到了18(18-2=16),刚好把fifo写满了,195ns时full刚好拉低有效。
就在full拉低有效时,此时读使能也有效了,这就出现了fifo满并且同时读写同一个地址的情况,可以看出205ns时开始读出的数据,正是之前写进去的旧数据。而且full始终有效,因为读写都正常,那fifo中的数据个数就不会变化,始终是16个。
在245ns时,写使能拉高无效,所以下次再读出数据时,full就拉高无效了,因为此时只读不写了,fifo中的数据个数会减少。
之后一直只读不写,到405ns时,fifo中的数据读完了,empty拉低有效。并且在读有效的这段时间里,读出的数据都是之前写入的数据,也即从1-23。需要注意的是,19-23也读了出来,说明之前这几个数正确写入了,这刚好验证了前面说的满状态下同时读写同一地址时,读写都正确。
最后,在415ns时,再次往里写,此时虽然读有效,但是因为该时刻fifo为空,没有旧数据导致读失败。但是写操作是正确的,所以此时empty会拉高无效,因此在下一周期读数据又正常了,且读出的数据正是上一个周期刚写进去的数据,如40-48。
通过以上分析,可以看出该同步fifo是正确的,并且解决了读写冲突。其实你可以结合读写地址和fifo数据个数计数变量自己更加详细的分析一下,能够有一个更深的理解!!!