数字IC设计系列----同步FIFO

学习路线:

  • 同步FIFO设计                (附源码RTL/TB)
  • 异步FIFO设计                (附源码RTL/TB)
  • uart通信协议                  (附源码RTL/TB)
  • SPI协议接口设计            (附源码RTL/TB)
  • AMBA 3 APB 接口设计   (附源码RTL/TB)
  • AMBA  AHB 接口设计     (附源码RTL/TB)
  • AMBA AXI 接口设计        (附源码RTL/TB)
  • UART 2 APB bridge设计 (附源码RTL/TB)
  • APB 2 UART bridge设计 (附源码RTL/TB)

    1、基本概念  

  1. FIFO是什么,有什么用?
  2. 同步FIFO的“同步”是什么意思?
  3. 接口都有什么:

    在这里插入图片描述

        FIFO 是一种先进先出数据缓存器。FIFO和RAM可以说是一对孪生兄弟,先进先出,顾名思义,他不可以像RAM一样根据地址读写,只能顺序的写入和读出数据,其数据地址由内部读写指针自动加 1 完成,这是她和RAM最大的区别,也因此决定了他们的使用场景不同。从逻辑上讲,FIFO是有RAM和控制逻辑共同组成的。从接口时序上讲,少了读地址和写地址信号,多了空信号和满信号。其他配置,比如大小配置、工作频率等特性都是和RAM一致的。

        同步FIFO,读和写应用同一个时钟。它的作用一般是做交互数据的一个缓冲,也就是说它的主要作用就是一个buffer。而buffer来实现速率匹配。
        异步FIFO,读写应用不同的时钟,它有两个主要的作用,一个是实现数据在不同时钟域进行传递,另一个作用就是实现不同数据宽度的数据接口 

 既然是数据缓冲器,那么缓冲器的大小,存储深度,读写地址和存储器空满状态都需要确定。
  一般FIFO使用循环指针(计数溢出自动归零)。一般可以称写指针为头head,读指针为尾tail。初始化时,读写指针指向同一数据地址。

  上图可见,FIFO初始化时,WP和RP指针指向同一数据单元。WP指向下一个将要写入的数据单元,RP指向将要读出的数据单元,两者是一个追赶过程。可以设置一个计数器,只写,来一个数据,写一个,写地址,+1,计数器+1,写满为止;只读,来一个,读出一个数据,读地址+1,计数器-1;同时读写,计数器值不变,读写地址均+1。
 

三、Spec

(1) Function description

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

(2) Feature list

  • 支持存储宽度、深度可配置
  • 时钟工作频率为1MHz

(3) Block diagram

在这里插入图片描述  模块主要分为读/写接口、读/写指针、读写指针的比较逻辑和array存储阵列四部分。

  1. 读/写接口:为模块提供读写数据和读写使能信号;
  2. 读写指针:主要标志读写指针当前array的地址
  3. 比较逻辑:
  •  使用element counter(elem_cnt)记录FIFO RAM 中的数据个数:
    ▷ 等于0时,给出empty信号;等于BUF_LENGTH时,给出full信号
  • elem_cnt:
    ▷ 写而未满时增加1
    ▷ 读而未空时减1
    ▷ 同时发生读写操作时,elem_cnt不变

(4) Interface description

(5) Timing

在这里插入图片描述  分为三部分,写操作,读操作,读写操作。

四、RTL design

  • DUT模块
module sync_fifo 
#(
  parameter DATA_WIDTH = 32,
  parameter DATA_DEPTH = 8 ,
  parameter PTR_WIDTH  = 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,
  
  //Flags_o
  output reg                     full_o   ,
  output reg                     empty_o  
);
  reg  [DATA_WIDTH-1:0]  regs_array  [DATA_DEPTH-1:0];
  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 ----------------
\---------------------------------------------------*/
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 ------------------
\---------------------------------------------------*/
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 ------------------
\---------------------------------------------------*/

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
  • tb
module tb_sync_fifo;
  reg          clk_i    ;
  reg          rst_n_i  ;

  reg          wr_en_i  ;
  reg  [31:0]  wr_data_i;
  
  reg          rd_en_i  ;
  reg  [31:0]  rd_data_o;

  wire         full_o   ;
  reg          empty_o  ;

initial begin
  rst_n_i   = 1  ;
  clk_i     = 0  ;
  
  rd_en_i   = 0  ;

  wr_en_i   = 0  ;
  wr_data_i = 32'b0;

  #2 rst_n_i = 0 ;
  #5 rst_n_i = 1 ;
end

initial begin
  #10 wr_en_i = 1;
      rd_en_i = 0;

  #10 wr_en_i = 0;
      rd_en_i = 1;

  #10 wr_en_i = 1;
      rd_en_i = 0;

  #3  rd_en_i = 1;
  #10
  repeat(100) begin
    #5 wr_en_i = {$random}%2;
       rd_en_i = {$random}%2;
  end
end

initial #2000 $finish;

always #0.5 clk_i     = ~clk_i       ;
always #1   wr_data_i = {$random}%10;
sync_fifo u_sync_fifo
(
  .clk_i    (clk_i    ),
  .rst_n_i  (rst_n_i  ),
  .wr_en_i  (wr_en_i  ),
  .wr_data_i(wr_data_i),
  .rd_en_i  (rd_en_i  ),
  .rd_data_o(rd_data_o),
  .full_o   (full_o   ),
  .empty_o  (empty_o  )
);

initial begin
  $fsdbDumpfile("sync_fifo.fsdb");
  $fsdbDumpvars                  ;
  $fsdbDumpMDA                   ;
end
endmodule

五、分析和小结

(1)分析

  • 写阶段
    在这里插入图片描述

  复位之后,进行写操作,直至写满,产生满标志后,不再写入新数据。


  • 读阶段
    在这里插入图片描述

  进行读操作,直至读空,产生空标志后,不再读出新数据。


  • 同时读写阶段
    在这里插入图片描述

  先进行写操作,写入三个新数据之后,同时进行读写操作,期间写入新数据和读出数据,但是elem_cnt计数器不再变化,动态平衡。


(2)小结
  设计思路:先分析需求,定义接口,画出具体的实现框图;按照协议和理解,画出相应时序图;看图写程序,验证仿真波形是否与时序图对应。
  同步FIFO设计要点是什么时候产生空满标志位,即怎么衡量array被写满或者被读空。在这里,我使用了4bit的elem_cnt表示,通过elem_cnt的值表示当前array存储阵列的资源使用情况。0表示没有数据,即空状态;8表示写满,因为array的存储深度就是8。在spec中提到实现FIFO可配置,在这里只实现了宽度为32bit,深度为8的同步fifo设计,初步验证仿真波形与时序图相对应。


————————————————
版权声明:本文为CSDN博主「xlinxdu」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_43244515/article/details/124163224

猜你喜欢

转载自blog.csdn.net/Arvin_ing/article/details/132761271