• [文档].艾米电子 FIFO缓存,Verilog


    对读者的假设

    已经掌握:

    内容

    FIFO缓存是介于两个子系统之间的弹性存储器,其概念图如图1所示。它有两个控制信号,wr和rd,用于读操作和写操作。当wr被插入时,输入的数据被写入缓存,此时读操作被忽视。FIFO缓存的head一般情况下总是有效的,因此可在任意时间被读取。rd信号实际上就像“remove”信号;当其被插入的时候,FIFO缓存的第一个项(即head)被移除,下一个项变为可用项。

     图1 FIFO缓存的概念框图

    图1 FIFO缓存的概念框图

    在许多应用中,FIFO缓存是一种临界组件,其实现的优化相当复杂。在本节中,我们介绍一种简单的、真实的基于循环序列设计的FIFO缓存。更有效的、基于指定器件实现的FIFO缓存可在Altera或Xilinx的相关手册中找到。

    基于循环队列的实现

    一种实现FIFO缓存的方法是给寄存器文件添加一个控制电路。寄存器文件通过两个指针像循环队列一样来排列寄存器。写指针(write poniter)指向队列的头(head);读指针(read poniter)指向队列的尾(tail)。每次读操作或写操作,指针都会前进一个位置。8-字循环队列的操作如图2所示。

    图2 基于循环队列的FIFO缓存

    图2 基于循环队列的FIFO缓存

    FIFO缓存通常包括两个标志信号,full和empty,相应地来指示FIFO满(即不可写)或FIFO空(即不可读)。这两种情况发生在读指针和写指针相等的时候,如图2(a)、(f)和(i)所示的情况。控制器最难的设计任务是获取一种分辨这两种情形的机制。一种方案是使用触发器来跟踪empty和full标志。当系统被初始化时,触发器被设置为1和0;然后在每一个时钟周期根据wr和rd的值来修改。

    代码 FIFO缓存

    module fifo
    #(
      parameter B=8, // number of bits in a word
                W=3  // number of address bits
    )
    (
      // global clock and aysn reset
      input clk, 
      input rst_n,
      // fifo interface
      //  fifo control signnal
      input rd, 
      input wr,
      //  fifo status signal
      output empty, 
      output full,
      // fifo data bus
      input [B-1:0] w_data,
      output [B-1:0] r_data
    );
    
    // signal declaration
    reg [B-1:0] array_reg [2**W-1:0];  // register array
    reg [W-1:0] w_ptr_reg, w_ptr_next, w_ptr_succ;
    reg [W-1:0] r_ptr_reg, r_ptr_next, r_ptr_succ;
    reg full_reg, empty_reg, full_next, empty_next;
    wire wr_en;
    
    // body
    // register file write operation
    always @(posedge clk)
      if (wr_en)
        array_reg[w_ptr_reg] <= w_data;
    // register file read operation
    assign r_data = array_reg[r_ptr_reg];
    // write enabled only when FIFO is not full
    assign wr_en = wr & ~full_reg;
    
    // fifo control logic
    // register for read and write pointers
    always @(posedge clk, negedge rst_n)
      if (!rst_n)
      begin
        w_ptr_reg <= 0;
        r_ptr_reg <= 0;
        full_reg <= 1'b0;
        empty_reg <= 1'b1;
      end
      else
      begin
        w_ptr_reg <= w_ptr_next;
        r_ptr_reg <= r_ptr_next;
        full_reg <= full_next;
        empty_reg <= empty_next;
      end
    
    // next-state logic for read and write pointers
    always @*
    begin
      // successive pointer values
      w_ptr_succ = w_ptr_reg + 1;
      r_ptr_succ = r_ptr_reg + 1;
      // default: keep old values
      w_ptr_next = w_ptr_reg;
      r_ptr_next = r_ptr_reg;
      full_next = full_reg;
      empty_next = empty_reg;
      case ({wr, rd})
        // 2'b00:  no op
        2'b01: // read
          if (~empty_reg) // not empty
          begin
            r_ptr_next = r_ptr_succ;
            full_next = 1'b0;
            if (r_ptr_succ==w_ptr_reg)
              empty_next = 1'b1;
          end
        2'b10: // write
        if (~full_reg) // not full
        begin
          w_ptr_next = w_ptr_succ;
          empty_next = 1'b0;
          if (w_ptr_succ==r_ptr_reg)
            full_next = 1'b1;
        end
        2'b11: // write and read
        begin
          w_ptr_next = w_ptr_succ;
          r_ptr_next = r_ptr_succ;
        end
      endcase
    end
    
    // output
    assign full = full_reg;
    assign empty = empty_reg;
    
    endmodule

    代码被分为寄存器文件和FIFO控制器两部分。控制器由两个指针和两个标志触发器组成,它们的次态逻辑会检测wr和rd信号,以采取相应的动作。举例说,在“10”条件下,即暗示只发生写操作。先检查标志触发器,以确保缓存不为满。如果满足条件,我们将写指针前进一位,并清除空标志。再多存储一个字(偏移地址为1所对应的数据)可能使得FIFO缓存满,即新的写指针赶上了读指针,我们使用w_ptr_succ==r_ptr_reg表达式来描述这一情况。

    根据图2,我写了下面的testbench,其RTL仿真结果与图2一致。

    代码 FIFO缓存的testbench

    `timescale 1ns/1ns
    
    module fifo_tb;
    localparam T=20; // clock period
    // global clock and asyn reset
    reg clk, rst_n;
    // fifo interface
    reg rd, wr;
    wire empty, full;
    reg [7:0] w_data;
    wire [7:0] r_data;
    
    // fifo instantiation
    fifo #(.B(8), .W(3)) fifo_inst
    (
      .clk(clk), .rst_n(rst_n),
      .rd(rd), .wr(wr),
      .empty(empty), .full(full),
      .w_data(w_data), .r_data(r_data)
    );
    
    
    // clcok
    always
    begin
      clk = 1'b0; 
      #(T/2);
      clk = 1'b1;
      #(T/2);
    end
    
    
    // reset
    initial
    begin
      rst_n = 1'b0;
      #(T/2)
      rst_n = 1'b1;
    end
    
    
    // stimulus body
    initial 
    begin
      // initial input; empty
      rd=0; wr=0; w_data=8'h00;
      @(posedge rst_n); // wait to deassert rst_n
      @(negedge clk); // wait for a clock
      // 1 write
      wr=1; w_data=8'h11;
      @(negedge clk); // wait to assert wr
      wr=0;
      @(negedge clk); // wait to deassert wr
      // 3 writes
      wr=1;
      repeat(3)
      begin
        w_data=w_data+8'h11;
        @(negedge clk); 
      end
      wr=0;
      @(negedge clk);
      // 1 read
      rd=1;
      @(negedge clk); // wait to assert rd
      rd=0;
      @(negedge clk) // wait to deassert rd
      // 4 writes
      wr=1;
      repeat(4)
      begin
        w_data=w_data+8'h11;
        @(negedge clk);
      end
      wr=0;
      @(negedge clk); 
      // 1 write; full
      wr=1; w_data=8'hAA;
      @(negedge clk); 
      wr=0;
      @(negedge clk);
      // 2 reads
      rd=1;
      repeat(2) @(negedge clk);
      rd=0; 
      @(negedge clk);
      // 5 reads
      rd=1;
      repeat(5) @(negedge clk);
      rd=0;
      @(negedge clk);
      // 1 read; empty
      rd=1;
      @(negedge clk);
      rd=0;
      @(negedge clk);
      $stop;
    end
    
    endmodule

    图3 RTL级仿真波形

    图3 RTL级仿真波形

    参考

    1 Pong P. Chu.FPGA Prototyping By Verilog Examples: Xilinx Spartan-3 Version.Wiley

    另见

    [与艾米一起学FPGA/SOPC].[逻辑实验文档连载计划]

  • 相关阅读:
    Windows 2008R2 安装PostgreSQL 11.6
    Redis-基础介绍
    SQL Server中的GAM页和SGAM页
    linux读写相关
    String 和 Stringbuild
    JVM(六)如何执行方法调用
    dubbo学习(三)实现细节
    dubbo学习(二)SPI
    spring boot
    MySQL学习(二十一)锁
  • 原文地址:https://www.cnblogs.com/yuphone/p/1913500.html
Copyright © 2020-2023  润新知