01 【verilog实战】同步FIFO的设计与功能验证(附源码)

虚拟机:VMware -14.0.0.24051
环 境:ubuntu 18.04.1
脚 本:makefile(点击查看)
应用工具:vcs 和 verdi


文章目录

  • 一、学习内容
  • 二、基本概念
  • 三、Spec
  • (1) Function description
  • (2) Feature list
  • (3) Block diagram
  • (4) Interface description
  • (5) Timing
  • 四、RTL design
  • 五、分析和小结
  • (1)分析
  • (2)小结


一、学习内容

  1. 同步FIFO的写时钟和读时钟为同一个时钟,FIFO内部所有逻辑都是同步逻辑,常常用于交互数据缓冲。
  2. 典型同步FIFO有三部分组成: (1) FIFO写控制逻辑; (2)FIFO读控制逻辑;(3)FIFO 存储实体(如Memory、Reg)。
  3. FIFO写控制逻辑主要功能:产生FIFO写地址、写有效信号,同时产生FIFO写满、写错等状态信号;
  4. FIFO读控制逻辑主要功能:产生FIFO读地址、读有效信号,同时产生FIFO读空、读错等状态信号

二、基本概念

  1. 同步FIFO的“同步”是什么意思?
  2. FIFO是什么,有什么用?
  3. 接口都有什么
    01 【verilog实战】同步FIFO的设计与功能验证(附源码)_第1张图片
  • 同步:时钟间有确定的倍数关系或确定的相位关系
  • FIFO:Frist-in-first-out,先进先出,是一种数据缓存器,实现速率匹配。
    01 【verilog实战】同步FIFO的设计与功能验证(附源码)_第2张图片
      既然是数据缓冲器,那么缓冲器的大小,存储深度,读写地址和存储器空满状态都需要确定。
      一般FIFO使用循环指针(计数溢出自动归零)。一般可以称写指针为头head,读指针为尾tail。初始化时,读写指针指向同一数据地址。
    01 【verilog实战】同步FIFO的设计与功能验证(附源码)_第3张图片

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

三、Spec

(1) Function description

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

(2) Feature list

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

(3) Block diagram

01 【verilog实战】同步FIFO的设计与功能验证(附源码)_第4张图片  模块主要分为读/写接口、读/写指针、读写指针的比较逻辑和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

01 【verilog实战】同步FIFO的设计与功能验证(附源码)_第5张图片

(5) Timing

01 【verilog实战】同步FIFO的设计与功能验证(附源码)_第6张图片  分为三部分,写操作,读操作,读写操作。


四、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)分析

  • 写阶段
    01 【verilog实战】同步FIFO的设计与功能验证(附源码)_第7张图片
      复位之后,进行写操作,直至写满,产生满标志后,不再写入新数据。
  • 读阶段
    01 【verilog实战】同步FIFO的设计与功能验证(附源码)_第8张图片
      进行读操作,直至读空,产生空标志后,不再读出新数据。
  • 同时读写阶段
    01 【verilog实战】同步FIFO的设计与功能验证(附源码)_第9张图片  先进行写操作,写入三个新数据之后,同时进行读写操作,期间写入新数据和读出数据,但是elem_cnt计数器不再变化,动态平衡。

(2)小结

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


作者:xlinxdu
版权:本文版权归作者所有
转载:未经作者允许,禁止转载,转载必须保留此段声明,必须在文章中给出原文连接。

你可能感兴趣的:(Verilog实战应用,verilog,fifo,同步fifo)