同步FIFO

文章介绍了如何使用Verilog语言设计同步FIFO,包括计数器法和高位扩展法,并提供了相应的RTL代码示例。在计数器法中,通过读写指针和计数器来管理FIFO的状态;高位扩展法则利用额外的地址位来判断满和空。此外,文章还包含了测试激励(TB)代码,用于验证FIFO的功能正确性。
摘要由CSDN通过智能技术生成

1.参考

1.SPEC

同步FIFO实现了对write/read的控制,其接口解决了接口两端数据速率不匹配的问题

同步FIFO接口

2.RTL代码

1.01 【Verilog实战】同步FIFO的设计(附源码RTL/TB)

//同步FIFO

module sync_fifo 
#(
  parameter DATA_WIDTH = 32,        //FIFO储存32位数据
  parameter DATA_DEPTH = 8 ,        //深度为8
  parameter PTR_WIDTH  = 3         //8的深度对应3位地址
//parameter PTR_WIDTH  = $clog2(DATA_DEPTH)
)
(
  input  wire                    clk_i   ,
  input  wire                    rst_n_i ,
  
  //write interface
  input  wire                    wr_en_i  ,    //写使能
  input  wire  [DATA_WIDTH-1:0]  wr_data_i,    
  
  //read interface
  input  wire                    rd_en_i  ,    //读使能
  output reg   [DATA_WIDTH-1:0]  rd_data_o,    //wdata是input;rdata是output
  
  //Flags_o
  output reg                     full_o   ,
  output reg                     empty_o  
);
  reg  [DATA_WIDTH-1:0]  regs_array  [DATA_DEPTH-1:0];    //32位寄存器,深度8
  reg  [PTR_WIDTH-1 :0]  wr_ptr                      ;    //写指针地址
  reg  [PTR_WIDTH-1 :0]  rd_ptr                      ;
  reg  [PTR_WIDTH   :0]  elem_cnt                    ;
  reg  [PTR_WIDTH   :0]  elem_cnt_nxt                ;
 //Flags
  wire                   full_comb                   ;
  wire                   empty_comb                  ;

/*---------------------------------------------------\
  --------------- write poiter addr ----------------  
         复位写指针归0,写使能同时fifo未写满 写指针+1
\---------------------------------------------------*/
always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    wr_ptr <= 3'b0;
  end
  else if (wr_en_i && !full_o) begin
    wr_ptr <= wr_ptr + 3'b1;
  end
end

/*---------------------------------------------------\
  -------------- read poiter addr ------------------
        复位写指针归0,读使能同时fifo未读空 读指针+1
\---------------------------------------------------*/
always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    rd_ptr <= 3'b0;
  end
  else if (rd_en_i && !empty_o) begin
    rd_ptr <= rd_ptr + 3'b1;
  end
end

/*---------------------------------------------------\
  --------------- element counter ------------------
    使用element counter(elem_cnt)记录FIFO RAM 中的数据个数:
    ▷ 等于0时,给出empty信号;
      等于BUF_LENGTH时,给出full信号
    elem_cnt:
    ▷ 写而未满时增加1
    ▷ 读而未空时减1
    ▷ 同时发生读写操作时,elem_cnt不变
\---------------------------------------------------*/

always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    elem_cnt <= 4'b0;
  end
  else if (wr_en_i && rd_en_i && !full_o && !empty_o) begin
    elem_cnt <= elem_cnt;
  end
  else if(wr_en_i && !full_o) begin
    elem_cnt <= elem_cnt + 1'b1;
  end
  else if(rd_en_i && !empty_o) begin
    elem_cnt <= elem_cnt - 1'b1;
  end
end

/*---------------------------------------------------\
  ------------- generate the flags -----------------
\---------------------------------------------------*/
always @(*) begin
  if(!rst_n_i) begin
    elem_cnt_nxt = 1'b0;
  end
  else if(elem_cnt != 4'd0 && rd_en_i && !empty_o) begin
    elem_cnt_nxt = elem_cnt - 1'b1; 
  end
  else if(elem_cnt != 4'd8 && wr_en_i && !full_o) begin
    elem_cnt_nxt = elem_cnt + 1'b1; 
  end
  else begin
    elem_cnt_nxt = elem_cnt;
  end
end

assign full_comb  = (elem_cnt_nxt == 4'd8);
assign empty_comb = (elem_cnt_nxt == 4'd0);

always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    full_o <= 1'b0;
  end
  else begin
    full_o <= full_comb;
  end
end

always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    empty_o <= 1'b1;
  end
  else begin
    empty_o <= empty_comb;
  end
end

/*---------------------------------------------------\
  -------------------- read data -------------------
\---------------------------------------------------*/
always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    rd_data_o <= 32'b0;
  end
  else if(rd_en_i && !empty_o) begin
    rd_data_o <= regs_array[rd_ptr];
  end
end

/*---------------------------------------------------\
  ------------------- write data -------------------
\---------------------------------------------------*/
reg [PTR_WIDTH:0] i;

always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    for(i=0;i<DATA_DEPTH;i=i+1) begin
      regs_array[i] <= 32'b0;
    end
  end
  else if(wr_en_i && !full_o) begin
    regs_array[wr_ptr] <= wr_data_i;
  end
end

endmodule

2.同步FIFO的两种Verilog设计方法(计数器法、高位扩展法)

计数器法
//计数器法实现同步FIFO
module    sync_fifo_cnt
#(
    parameter   DATA_WIDTH = 'd8  ,                            //FIFO位宽
    parameter   DATA_DEPTH = 'd16                             //FIFO深度
)
(
    input                                    clk        ,        //系统时钟
    input                                    rst_n    ,       //低电平有效的复位信号
    input    [DATA_WIDTH-1:0]                data_in    ,       //写入的数据
    input                                    rd_en    ,       //读使能信号,高电平有效
    input                                    wr_en    ,       //写使能信号,高电平有效
                                                            
    output    reg    [DATA_WIDTH-1:0]            data_out,        //输出的数据
    output                                    empty    ,        //空标志,高电平表示当前FIFO已被写满
    output                                    full    ,       //满标志,高电平表示当前FIFO已被读空
    output    reg    [$clog2(DATA_DEPTH) : 0]    fifo_cnt        //$clog2是以2为底取对数    
);
 
//reg define
reg [DATA_WIDTH - 1 : 0] fifo_buffer[DATA_DEPTH - 1 : 0];    //用二维数组实现RAM    
reg [$clog2(DATA_DEPTH) - 1 : 0]    wr_addr;                //写地址
reg [$clog2(DATA_DEPTH) - 1 : 0]    rd_addr;                //读地址
 
//读操作,更新读地址
always @ (posedge clk or negedge rst_n) begin
    if (!rst_n)
        rd_addr <= 0;
    else if (!empty && rd_en)begin                            //读使能有效且非空
        rd_addr <= rd_addr + 1'd1;                            //读指针+1
        data_out <= fifo_buffer[rd_addr];                    //读出数据
    end
end
//写操作,更新写地址
always @ (posedge clk or negedge rst_n) begin
    if (!rst_n)
        wr_addr <= 0;
    else if (!full && wr_en)begin                            //写使能有效且非满
        wr_addr <= wr_addr + 1'd1;                            //写指针+1
        fifo_buffer[wr_addr]<=data_in;                        //写数据进fifo
    end
end
//更新计数器
always @ (posedge clk or negedge rst_n) begin
    if (!rst_n)
        fifo_cnt <= 0;
    else begin
        case({wr_en,rd_en})                                    //拼接读写使能信号进行判断
            2'b00:fifo_cnt <= fifo_cnt;                        //不读不写
            2'b01:                                               //仅仅读
                if(fifo_cnt != 0)                               //fifo没有被读空
                    fifo_cnt <= fifo_cnt - 1'b1;               //fifo个数-1
            2'b10:                                             //仅仅写
                if(fifo_cnt != DATA_DEPTH)                     //fifo没有被写满
                    fifo_cnt <= fifo_cnt + 1'b1;               //fifo个数+1
            2'b11:fifo_cnt <= fifo_cnt;                           //读写同时
            default:;                                  
        endcase
    end
end
//依据计数器状态更新指示信号
//依据不同阈值还可以设计半空、半满 、几乎空、几乎满
assign full  = (fifo_cnt == DATA_DEPTH) ? 1'b1 : 1'b0;        //满信号
assign empty = (fifo_cnt == 0)? 1'b1 : 1'b0;                //空信号
 
endmodule
高位扩展法
module    sync_fifo_ptr
#(
    parameter   DATA_WIDTH = 'd8  ,                                //FIFO位宽
    parameter   DATA_DEPTH = 'd16                                 //FIFO深度
)
(
    input                                        clk        ,        //系统时钟
    input                                        rst_n    ,       //低电平有效的复位信号
    input    [DATA_WIDTH-1:0]                    data_in    ,       //写入的数据
    input                                        rd_en    ,       //读使能信号,高电平有效
    input                                        wr_en    ,       //写使能信号,高电平有效
                                                                
    output    reg    [DATA_WIDTH-1:0]                data_out,        //输出的数据
    output                                        empty    ,        //空标志,高电平表示当前FIFO已被写满
    output                                        full            //满标志,高电平表示当前FIFO已被读空
);                                                              
 
//reg define
//用二维数组实现RAM
reg [DATA_WIDTH - 1 : 0]            fifo_buffer[DATA_DEPTH - 1 : 0];    
reg [$clog2(DATA_DEPTH) : 0]        wr_ptr;                        //写地址指针,位宽多一位    
reg [$clog2(DATA_DEPTH) : 0]        rd_ptr;                        //读地址指针,位宽多一位    
 
//wire define
wire [$clog2(DATA_DEPTH) - 1 : 0]    wr_ptr_true;                //真实写地址指针
wire [$clog2(DATA_DEPTH) - 1 : 0]    rd_ptr_true;                //真实读地址指针
wire                                wr_ptr_msb;                    //写地址指针地址最高位
wire                                rd_ptr_msb;                    //读地址指针地址最高位

 //!!!
assign {wr_ptr_msb,wr_ptr_true} = wr_ptr;                        //将最高位与其他位拼接
assign {rd_ptr_msb,rd_ptr_true} = rd_ptr;                        //将最高位与其他位拼接
 
//读操作,更新读地址
always @ (posedge clk or negedge rst_n) begin
    if (rst_n == 1'b0)
        rd_ptr <= 'd0;
    else if (rd_en && !empty)begin                                //读使能有效且非空
        data_out <= fifo_buffer[rd_ptr_true];
        rd_ptr <= rd_ptr + 1'd1;
    end
end
//写操作,更新写地址
always @ (posedge clk or negedge rst_n) begin
    if (!rst_n)
        wr_ptr <= 0;
    else if (!full && wr_en)begin                                //写使能有效且非满
        wr_ptr <= wr_ptr + 1'd1;
        fifo_buffer[wr_ptr_true] <= data_in;
    end    
end
 
//!!!更新指示信号
//当所有位相等时,读指针追到到了写指针,FIFO被读空
assign    empty = ( wr_ptr == rd_ptr ) ? 1'b1 : 1'b10;
//当最高位不同但是其他位相等时,写指针超过读指针一圈,FIFO被写满
assign    full  = ( (wr_ptr_msb != rd_ptr_msb ) && ( wr_ptr_true == rd_ptr_true ) )? 1'b1 : 1'b0;
 
endmodule

验证

1.思路

  • 例化1个深度为8,位宽为8的同步FIFO

  • 先对FIFO进行写操作,直到其写满,写入的数据为随机数据

  • 然后对FIFO进行读操作,直到其读空

  • 然后对FIFO写入4个随机数据后,同时对其进行读写操作

2.TB代码

对应2.2.1的RTL

`timescale 1ns/1ns    //时间单位/精度
 
//------------<模块及端口声明>----------------------------------------
module tb_sync_fifo_cnt();
 
parameter   DATA_WIDTH = 8  ;            //FIFO位宽
parameter   DATA_DEPTH = 8 ;            //FIFO深度
 
reg                                    clk        ;
reg                                    rst_n    ;
reg        [DATA_WIDTH-1:0]            data_in    ;
reg                                    rd_en    ;
reg                                    wr_en    ;
                        
wire    [DATA_WIDTH-1:0]            data_out;    
wire                                empty    ;    
wire                                full    ;
wire    [$clog2(DATA_DEPTH) : 0]    fifo_cnt;
 
 
//------------<例化被测试模块>----------------------------------------
sync_fifo_cnt
#(
    .DATA_WIDTH    (DATA_WIDTH),            //FIFO位宽
    .DATA_DEPTH    (DATA_DEPTH)            //FIFO深度
)
sync_fifo_cnt_inst(
    .clk        (clk        ),
    .rst_n        (rst_n        ),
    .data_in    (data_in    ),
    .rd_en        (rd_en        ),
    .wr_en        (wr_en        ),
                 
    .data_out    (data_out    ),    
    .empty        (empty        ),    
    .full        (full        ),
    .fifo_cnt    (fifo_cnt    )            
);
 
//------------<设置初始测试条件>----------------------------------------
initial begin
    clk = 1'b0;                            //初始时钟为0
    rst_n <= 1'b0;                        //初始复位
    data_in <= 'd0;        
    wr_en <= 1'b0;        
    rd_en <= 1'b0;
//重复8次写操作,让FIFO写满     
    repeat(8) begin        
        @(negedge clk)begin        
            rst_n <= 1'b1;                
            wr_en <= 1'b1;        
            data_in <= $random;            //生成8位随机数
        end
    end
//重复8次读操作,让FIFO读空    
    repeat(8) begin
        @(negedge clk)begin        
            wr_en <= 1'b0;
            rd_en <= 1'd1;
        end
    end
//重复4次写操作,写入4个随机数据    
    repeat(4) begin
        @(negedge clk)begin        
            wr_en <= 1'b1;
            data_in <= $random;    //生成8位随机数
            rd_en <= 1'b0;
        end
    end
//持续同时对FIFO读写,写入数据为随机数据    
    forever begin
        @(negedge clk)begin        
            wr_en <= 1'b1;
            data_in <= $random;    //生成8位随机数
            rd_en <= 1'b1;
        end
    end
end
//------------<设置时钟>----------------------------------------------
always #10 clk = ~clk;            //系统时钟周期20ns
endmodule

1.实践

1.RTL

module sync_fifo
#(
    parameter fifo_width=16,
    parameter fifo_depth=8,
    parameter addr_width=3
  )
(
    input clk,
    input rst_n,
    input [fifo_width-1:0] data_in,
    output reg [fifo_width-1:0] data_out,
    input wr_en,
    input rd_en,
    output empty,
    output full,
    output reg [addr_width:0] fifo_cnt
  );
  reg [fifo_width-1:0]fifo_buffer [fifo_depth-1:0];
  reg [addr_width-1:0]wr_addr;
  reg [addr_width-1:0]rd_addr;

//read
always@(posedge clk or negedge rst_n)
begin
  if(!rst_n)
    rd_addr <= 'd0;
    else if (rd_en && !empty)
    begin
      rd_addr<= rd_addr +'d1;
      data_out <= fifo_buffer[rd_addr];
    end
  end
//write
always@(posedge clk or negedge rst_n)
begin
  if(!rst_n)
    wr_addr <= 'd0;
    else if (wr_en && !full)
    begin
      wr_addr<= wr_addr +'d1;
      fifo_buffer [wr_addr ]<= data_in;
    end
  end
//cnt
always@(posedge clk or negedge rst_n) 
begin
  if(! rst_n )
    fifo_cnt<='d0;
    else
    begin
      case({wr_en,rd_en})
        2'b00:fifo_cnt <=fifo_cnt;
        2'b01:
        if(fifo_cnt !='b0)
        fifo_cnt <=fifo_cnt -'b1;
        2'b10:
        if(fifo_cnt!=fifo_depth)
          fifo_cnt <=fifo_cnt +'b1;
        2'b11:fifo_cnt <=fifo_cnt ;
      endcase
      end
    end

//
assign full=(fifo_cnt==fifo_depth)?1'b1:'b0;
assign empty=(fifo_depth ==0)?1'b1:'b0;

endmodule

2.TB

`timescale 1ns/1ns

module sync_fifo_tb();

parameter fifo_width=16;
parameter fifo_depth=8;
parameter addr_width=3;

reg clk;
reg rst_n;
reg [fifo_width-1:0] data_in ;
wire [fifo_width-1:0] data_out;
reg wr_en ;
reg rd_en ;
wire empty;
wire full;
wire [addr_width:0] fifo_cnt;

//
sync_fifo
#(
    .fifo_width(fifo_width),
    .fifo_depth(fifo_depth ),
    .addr_width(addr_width)
  )
sync_fifo_u1
(
  .clk(clk),
  .rst_n(rst_n),
  .data_in(data_in),
  .data_out(data_out ),
  .wr_en (wr_en ),
  .rd_en(rd_en ),
  .empty(empty),
  .full(full ),
  .fifo_cnt(fifo_cnt )
);


//time
always #10 clk= ~clk ;

//
initial begin
  clk<=1'b0;
  rst_n<=1'b0;
  wr_en<=1'b0;
  rd_en <=1'b0;
  data_in <='d0;
  
//write
repeat(8)begin
  @(negedge clk)begin
  rst_n<=1'b1;
  wr_en<=1'b1;
  data_in <=$random;
end
end

//read
repeat(8)begin
  @(negedge clk)begin
  rst_n<=1'b1;
  rd_en <=1'b1;
  wr_en<='b0;
end
end

//write&read
repeat(4)begin
  @(negedge clk)begin
    rst_n<=1'b1;
    wr_en<=1'b1;
    rd_en<='b0;
    data_in<=$random;
  end
end
repeat(4)begin
  @(negedge clk)begin
    rst_n<=1'b1;
    wr_en<=1'b1;
    data_in<=$random;
    rd_en<=1'b1;
  end
end
end



endmodule

3.波形debug

1.问题1

发现读空后empty信号不为0

  • 解决思路

增加读操作循环repeat(10) 信号仍不变

考虑fifo dut有bug

assign empty=(fifo_depth ==0)?1'b1:'b0;

更正为

assign empty=(fifo_cnt ==0)?1'b1:'b0;
  • 正确代码

module sync_fifo
#(
    parameter fifo_width=16,
    parameter fifo_depth=8,
    parameter addr_width=3
  )
(
    input clk,
    input rst_n,
    input [fifo_width-1:0] data_in,
    output reg [fifo_width-1:0] data_out,
    input wr_en,
    input rd_en,
    output empty,
    output full,
    output reg [addr_width:0] fifo_cnt
  );
  reg [fifo_width-1:0]fifo_buffer [fifo_depth-1:0];
  reg [addr_width-1:0]wr_addr;
  reg [addr_width-1:0]rd_addr;

//read
always@(posedge clk or negedge rst_n)
begin
  if(!rst_n)
    rd_addr <= 'd0;
    else if (rd_en && !empty)
    begin
      rd_addr<= rd_addr +'d1;
      data_out <= fifo_buffer[rd_addr];
    end
  end
//write
always@(posedge clk or negedge rst_n)
begin
  if(!rst_n)
    wr_addr <= 'd0;
    else if (wr_en && !full)
    begin
      wr_addr<= wr_addr +'d1;
      fifo_buffer [wr_addr ]<= data_in;
    end
  end
//cnt
always@(posedge clk or negedge rst_n) 
begin
  if(! rst_n )
    fifo_cnt<='d0;
    else
    begin
      case({wr_en,rd_en})
        2'b00:fifo_cnt <=fifo_cnt;
        2'b01:
        if(fifo_cnt !='b0)
        fifo_cnt <=fifo_cnt -'b1;
        2'b10:
        if(fifo_cnt!=fifo_depth)
          fifo_cnt <=fifo_cnt +'b1;
        2'b11:fifo_cnt <=fifo_cnt ;
      endcase
      end
    end

//
assign full=(fifo_cnt==fifo_depth)?1'b1:'b0;
assign empty=(fifo_cnt ==0)?1'b1:'b0;

endmodule
  • 正确波形

2. 问题2

RTL

parameter fifo_width=16,

TB

 parameter fifo_width=8,
parameter fifo_width=32,

仿真波形的fifo buffer的位宽为8/32

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值