首页 > 技术文章 > Xilinx伪双口RAM实现同步FIFO(解决读写冲突)

wzx19970918 2021-06-06 23:05 原文

一、伪双端口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数据个数计数变量自己更加详细的分析一下,能够有一个更深的理解!!!

推荐阅读