网络知识 娱乐 FIFO专题之单口RAM实现FIFO(同步)

FIFO专题之单口RAM实现FIFO(同步)

使用单口RAM实现FIFO,其实很简单,其中的重点就是区分出读写,

读写如果同时启动,你肯定会思考单口RAM肯定会出问题,毕竟单口RAM只有一个口,肯定不能实现同时读写,那么怎么解决这个问题呢。

有两种办法:

第一种办法就是采用两个单口RAM,这样就可以了,两个单口RAM分开奇偶,相当于乒乓的意思,然后再加一个REG,这就相当于把读写分开了

那么就可能分为以下几种情况:

①同时读写:读写同时为奇,这种情况就是在当前一拍,将写数据存入REG中,并将REG_VALID拉高告诉FIFO我下一拍要写数据,并在当前拍从奇数的FIFO中读取数据,那么下一拍如果再此发生同时读写,那么此时的同时读写就为偶,这一拍发生的情况就是将前一拍REG中的数据写入FIFO,然后将REG中数据更新新数据,然后将REG_VALID再拉高,告诉偶数FIFO下一拍要写数据了,并同时从偶数FIFO中取出要读的数据。

其实核心观点就是用两个单口RAM一个REG,用来区分最难的读写同时发生的情况,通过将RAM分为奇偶再加一个REG寄存器用来缓存,这就使不能同时读写的情况给解决了。

如果同时读写,且奇偶不同,那这种情况就更容易解决了,当前拍写数据的模块将数据写入REG,读模块的读出数据,然后下一拍将REG再写入单口RAM,和之前同时读写同时为奇偶的情况很相似。

②不同时读写:这种情况就是你只要写就先将数据写入REG,然后拉高VALID下一拍将REG中的数据写入单口RAM,如果读就直接读出数据。

通过这种方式就完美的解决了单口RAM没办法同时操作RAM的情况。

代码如下

`timescale 1ns / 1ps
//
// Company: 
// Engineer: Brad
// 
// Create Date: 2022/05/11 19:38:41
// Design Name: 
// Module Name: FIFO_single_ram
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module FIFO_single_ram
#( parameter WIDTH = 8,
   parameter DEEPTH= 256
)
(
    input clk ,
    input rstn ,
    input [WIDTH-1:0] data,
    input wr,
    input rd,
    output reg [WIDTH-1:0] data_out,
    output full,
    output empty
    );
    //-------------下面是FIFO的操作------------------//
    reg [8:0] rd_cnt;  //多一位用来回卷判断空满
    reg [8:0] wr_cnt;
    reg [8:0] wr_cnt_r;
    wire [7:0] rd_addr;
    wire [7:0] wr_addr;
    wire [7:0] addr_odd;
    wire [7:0] addr_even;
    reg [WIDTH-1:0] data_reg;
    reg       reg_valid;
    reg       wr_flag; //用来判断读写的奇偶 0代表奇 1代表偶数
    reg       rd_flag;
    wire      wr_ram;  //用来判断是否可以写
    wire      rd_ram;  //用来判断是否可以读
    wire      wr_en_odd; //奇数写
    wire      wr_en_even;//偶数写
    wire      rd_en_odd; //奇数写
    wire      rd_en_even;//偶数写
    reg      rd_en_odd_r; //奇数写
    reg      rd_en_even_r;//偶数写
    wire [7:0] datao_odd;
    wire [7:0] datao_even;
    assign rd_addr = rd_cnt [7:0];
    assign wr_addr = wr_cnt_r [7:0];
    assign full = (wr_cnt[8]^rd_cnt[8])&&(wr_cnt[7:0]==rd_cnt[7:0]); //最高位不同 低位相同;
    assign empty= (wr_cnt == rd_cnt); //全等
    assign wr_ram = (~full)&(wr);
    assign rd_ram = (~empty)&(rd);
//    assign data_out = (rd_en_odd_r)? datao_odd:datao_even;
    always@(*) begin
    if(rd_en_odd_r)
        data_out = datao_odd;
    else if(rd_en_even_r)
        data_out = datao_even;
    else
        data_out = data_out;
    end
    always@(posedge clk or negedge rstn)begin
    if(~rstn) begin
        wr_cnt <= 9'b0;
        data_reg <= {WIDTH{1'b0}};
        reg_valid <= 1'b0;
        wr_cnt_r <= 9'b0;
        end
    else if(wr_ram)
        begin
        wr_cnt <= wr_cnt + 1'b1;
        data_reg <= data;
        reg_valid <= 1'b1;
        wr_cnt_r <= wr_cnt;
        end
    else
        begin
        reg_valid <= 1'b0;
        end
    end
    always@(posedge clk or negedge rstn)begin
    if(~rstn)
        rd_cnt <= 9'b0;
    else if(rd_ram)
        rd_cnt <= rd_cnt + 1'b1;
    end

    //------------------------读写奇偶判断----------------------
    always@(posedge clk or negedge rstn) begin
    if(~rstn)
        wr_flag <= 1'b0;
    else if(reg_valid)
        wr_flag <=wr_flag + 1'b1; //往里面写数据的同时奇偶改变
    else 
        wr_flag <= wr_flag ; //往里面读数据的同时奇偶改变
    end
    always@(posedge clk or negedge rstn) begin
    if(~rstn)
        rd_flag <= 1'b0;
    else if(rd_ram)
        rd_flag <= rd_flag + 1'b1; //往里面读数据的同时奇偶改变
    else
        rd_flag <= rd_flag;
    end
    //--------------------RAM读写使能控制 分为奇偶两种类型
    always@(posedge clk or negedge rstn) begin
    if(~rstn)begin
        rd_en_odd_r <= 1'b0;
        rd_en_even_r <= 1'b0;
        end
    else
    begin
        rd_en_odd_r <= rd_en_odd;
        rd_en_even_r<=rd_en_even;
    end
    end

    assign wr_en_odd =(rd_en_odd)? 1'b0:( reg_valid & (~wr_flag));
    assign wr_en_even =(rd_en_even)? 1'b0:( reg_valid & (wr_flag));
    assign rd_en_odd = rd_ram & (~rd_flag);
    assign rd_en_even = rd_ram & (rd_flag);
    wire ena_odd = wr_en_odd | rd_en_odd;
    wire ena_even = wr_en_even | rd_en_even;
    assign addr_odd= (wr_en_odd)? wr_addr:rd_addr;
    assign addr_even= (wr_en_even)?wr_addr:rd_addr;
    SINGLE_RAM odd_ram (
      .clka(clk),    // input wire clka
      .ena(ena_odd),      // input wire ena
      .wea(wr_en_odd),      // input wire [0 : 0] wea
      .addra(addr_odd),  // input wire [7 : 0] addra
      .dina(data_reg),    // input wire [7 : 0] dina
      .douta(datao_odd)  // output wire [7 : 0] douta
    );
    SINGLE_RAM even_ram (
      .clka(clk),    // input wire clka
      .ena(ena_even),      // input wire ena
      .wea(wr_en_even),      // input wire [0 : 0] wea
      .addra(addr_even),  // input wire [7 : 0] addra
      .dina(data_reg),    // input wire [7 : 0] dina
      .douta(datao_even)  // output wire [7 : 0] douta
    );
endmodule

我这个代码没有做仔细的仿真,经过各位网友的提醒,改了几处,首先针对读写的时候对奇偶不同时操作,通过加了一个直通路解决,以防止下一拍写的时候漏掉数据。

并对这个代码进行了详细仿真,仿真代码如下:

第二种情况就是针对单口RAM的位宽进行变化,比如FIFO的读写数据位宽为8Bit,那么就针对RAM的位宽设置为FIFO位宽的两倍,外部加两个同等位宽的读写寄存器,通过将读写寄存器与外界读写和读写寄存器与RAM的交互实现FIFO的功能,比如对于同时读写的情况,在时钟上按2拍完成一次寄存器与RAM数据的交互,比如第一拍完成读,第二拍完成写,对于外界的接口而言数据是源源不断的从寄存器出来的,从而实现了单口RAM实现FIFO的功能,这种情况我仔细想了想只适用于 对FIFO连续读写的情况下才能使用,不连续读写总有一次会把其置成同时读写的情况。