12-Verilog-同步FIFO设计

同步FIFO和异步FIFO

FIFO分为一个同步FIFO,一个异步FIFO,FIFO有读口和写口
读写时钟是一个,就是同步FIFO;读写时钟不是一个,异步FIFO

  • IP核设计中,一般使用同步FIFO设计
  • SOC设计或者跨时钟域的内容使用异步FIFO

RAM

FIFO中的数据,存储在寄存器中或者是SRAM中

  • 数据少--存储在寄存器中
  • 数据多--使用SRAM
  • 一般而言fab会提供RAM仿真的模型,仿真库和物理库
    设计实现一个16*8的双端口RAM


RAM在时钟的驱动下,可以通过一个端口写数据,从另外一个端口读取数据,并且读写可以同时进行,如果要写入数据,首先要有写入的数据,并且要知道写到什么地方,要知道写的地址,RAM不可能一直在写,所以要有写使能;读的时候也要知道从什么地方读,有读的地址,有读使能决定什么时候读,读取出来得到数据

  • RAM的位宽,写成参数化的形式,能参数化的部分,全部进行参数化parameter
  • 定义完接口数据之后,定义内部;数据是存放在一个存储器中的,RTL中是不能直接生成存储器的,需要使用代码进行模拟,实际综合的时候,将module代码替换为SRAM,SRAM是一个已经综合好的硬件,可以直接进行使用
  • 首先定义存储体,使用reg或者logic类型
reg/logic [RAM_WIDTH-1:0] memory[RAM_DEPTH-1:0]; //memory存储体
  • 写操作,在写使能的驱动下,根据写地址,将数据赋值到写地址中,得到的是寄存器组
  • 读操作,从entrance中,根据读地址,将memory中的数据读取出来,read_data需要尽心输出寄存的
  • 实际上生成的是一个总容量为128bit的寄存器,数据比较多的时候,使用SRAM存储数据,面积会更小;数据量比较少的时候,使用寄存器,面积也不会大
always @ (posedge clk) begin
  if(read_en)
    read_data <= memory[read_addr]
end

// SRAM实现

wire [DATA_WIDTH-1:0] read_data_nxt;
assign read_data_nxt = memeory[read_addr];

always @ (posedge clk) begin
  if(read_en) 
    read_data <= read_data_nxt;
end

// 寄存器实现
wire [DATA_WIDTH-1:0] read_data_nxt;
assign read_data_nxt = memeory[read_addr];
  • SRAM的时序是在读使能的下一个cycle,得到读数据;用register实现fifo,可以使用组合逻辑,在当前的cycle得到数据

FIFO设计

  • 先写存储体,可以使用register或者sram搭建
  • FIFO control,FIFO除了时钟和复位,还有写端口和读端口
  • FIFO 是先进先出的队列,先写进去的数据,读的时候先读出来;FIFO中的数据存储在存储体中,类似于SRAM,有写数据,写使能,读使能,读数据,但是没有读地址和写地址,读地址和写地址是由FIFO内部的功能模块,FIFO control功能模块进行控制
  • 端口:读写使能\读写数据,没有读地址和写地址
  • FIFO原则:满不能写,空不能读,取决于full和empty信号,上游模块看到full信号,不向里面写,下游模块看到empty之后不读,FIFO自己也需要进行保护

    FIFO 端口
  • Fcounter,当前FIFO中有效的数据量,写入一个数据,加一,读出一个数据为0,在标准的FIFO中是没有的
module SYNCFIFO(
  Fifo_rst,    //async reset
  Clock,       // write and read clock
  Read_enable,
  write_enable,
  Read_data,
  write_data,
  Full,       // full flag
  Empty,      // empty flag
  Fcounter    // count the number of data in FIFO
  
);
endmodule

FIFO control

  • FIFO control 最主要的就是维护两个地址,读地址和写地址的产生;FIFO control需要有读使能和写使能,读地址和写地址是直接连到SRAM中的,不用经过FIFO control,空满信号也是FIFO control产生的
  • FIFO control 需要有读写使能,空满信号,读指针rp和写指针wp
  • 空满状态使用计数器,计算存储器的容量
  • counter = 3时,来一个写使能,就是再写入一个数据,counter = 4,full从0跳变为1
  • counter = 1时,来一个读使能,再读取一个数据,counter = 0,empty从0跳变为1
    FIFO实现的两种方法
  1. 通过计数器产生空满信号,执行一次写操作,计数器加1;执行一次读操作,计数器减1;
  2. 通过扩展地址位,用最高位判断空满状态
// 方法1
paramter DATA_WIDTH = 8;
parameter ADDR_WIDTH = 9;  // 2^9 512 这里设计的是512*8的RAM

input Fifo_rst;
input Clock;
input Read_enable;
input Write_enable;
input [DATA_WIDTH-1:0] Write_data;
output [DATA_WIDTH-1:0] Read_data;
output Full;
output Empty;
output [ADDR_WIDTH-1:0] Fcounter;

reg [DATA_WIDTH-1:0] Read_data;

reg Full;
reg Empty;

// reg [ADDR_WIDTH-1:0] Fcounter;
reg [ADDR_WIDTH] Fcounter;

reg [ADDR_WIDTH-1:0] Reader_addr;  // read_address,FIFO control传递给memory的
reg [ADDR_WIDTH-1:0] write_addr;   // write_address,FIFO control传递给memory的

wire Read_allow = (Read_enable && !Empty);  // 读使能来的时候并且非空,才能读
wire Write_allow = (Write_enable && !FUll); // 写使能来的时候并且非满,才能写

RTL Coding

// 例化 RAM 并连接端口
DUALRAM U_RAM (
  .Read_clock(Clock),
  .Write_clock(Clock), // 同步复位时钟,使用同一个clock
  .Read_allow(Read_allow),
  .Write_allow(Write_allow),
  .Read_addr(Read_addr),
  .Write_addr(Write_addr),
  .Write_data(Write_data),
  .Read_data(Read_data)
);
// 空信号产生
always @(posedge Clock or posedge Fifo_rst) begin
  if(Fifo_rst)
    Empty <= 'b1;   // 时钟复位的时候 empty=1s
  else
    Empty <= (!Write_allow && (Fcounter[8:1] == 8'h0)&&(Fcounter[0] ==0||Read_allow));
end
  • 只要有写使能,Empty就为0,!Write_allow表示写使能无效的时候
  • Fcounter[8:1] = 0,表示第1位到第9位都为0
  • Fcounter[0] = 0,因为与的关系,所以Fcounter = 0000_0000_0,FIFO无数据,empty = 1;
  • Fcounter[0] = 1,因为与的关系,所以Fcounter = 0000_0000_1,FIFO有一个数据,此时如果Read_allow来临,FIFO也无数据,empty = 1
// 最后一句的写法
empty <= Write_allow ? 1'b0 :
         Read_allow ? (Fcounter == 9'd1):(Fcounter == 9'd0);
// Write_allow 为 1 empty = 0
// Write_allow 为0 empty = Read_allow ? (Fcounter == 9'd1):(Fcounter == 9'd0);
//                         Read_allow = 1    empty = Fcounter == 9'd1 
//                         读使能来之后,判断Fcounter是不是等于1,是empty=1,不是empty=0
//                         Read_allow = 0    empty = Fcounter == 9'd0
//                         读使能为0,判断Fcounter是不是等于0,是empty=1,不是empty=0

// 这种方法使用了两个9bit比较器,面积大,不推荐使用
// Full 信号产生
always @(posedge clock or posedge Fifo_rst) begin
  if(Fifo_rst)
    Full <= 'b0;
  else 
    Full <= (!Read_allow && (Fcounter[8:] == 8'hFF) &&((Fcounter[0]==1 || Write_allow))); 
end
  • 分析方法与empty信号产生相同
  • 这里Full信号取值范围为0-511(1111_1111_1),9位的二进制数,取不到512,所以定义Full的时候要用10位
reg [ADDR_WIDTH] Fcounter; // 直接是10bit的位宽

Full <= Read_allow ? 1'b0 :
        Write_allow ? (Fcounter == 10'd511) : (Fcounter == 10'd512);
// 地址产生
always @(posedge Clock or posedge Fifo_rst) begin
  if(Fifo_rst)
    Read_addr <= 'h0
  else if(Read_allow)
    Read_addr <= Read_addr + 'b1;
end

always @(posedge Clock or posedge Fifo_rst) begin
  if(Fifo_rst)
    Write_addr <= 'h0;
  else
    write_addr <= Write_addr + 'b1;
end
// Fcounter
always @(posedge Clock or posedge Fifo_rst) begin
    if(Fifo_rst)
      Fcounter <= 'h0;
    else if((!Read_allow && !Write_allow) || (Read_allow && Write_allow))
    begin
      if(Write_allow) 
        Fcounter <= Fcounter + 'b1;
      else
        Fcounter <= Fcounter - 'b1;
    end
end

FIFO的第二种实现方法

  • 扩展地址位,使用最高位表示空和满的情况


  • 使用低位进行寻址
module sync_fifo(clk,rst,wr_en,rd_en,data_in,data_out,empty,full)
  input clk,rst,wr_en,rd_en
  input [7:0] data_in;
  output empty,full;
  output [7:0] data_out
  
  reg [7:0] mem[15:0]; //16*8 RAM
  reg [7:0] data_out;
  wire [3:0] w_addr,r_addr; // 表示寻址
  reg [4:0] r_addr_a,w_addr_a;  // 表示产生地址
  
  assign r_addr = r_addr_a[3:0];
  assign w_addr = w_addr_a[3:0];
  
  always@(posedge clk or negedge rst)
  begin
    if(!rst)
      begin
        r_addr_a = 5'b0;
      end 
    else
      begin
        if (rd_en == 1 && empty == 0)
          begin
            data_out <=mem[r_addr];
            r_addr_a <= r_addr_a+1;
          end
      end
  end

  always@(posedge clk or negedge rst)
  begin
    if(!rst)
      begin
        w_addr_a = 5'b0;
      end 
    else
      begin
        if (wr_en == 1 && full == 0)
          begin
            mem[w_addr] <=data_in;
            w_addr_a <= w_addr_a+1;
          end
      end
  end

  assign empty = (r_addr_a == w_addr_a) ? 0 : 1;
  assign full = (r_addr_a[4] != w_addr_a[4] && r_addr_a[3:0] == w_addr_a[3:0])?1:0;
endmodule
posted @ 2023-02-14 00:51  Icer_Newer  阅读(671)  评论(0编辑  收藏  举报