Synchronous  FIFO
💡

Synchronous FIFO

标签&分类
项目
发表时间
Jul 11, 2022 03:10 PM
描述
同步fifo的设计与验证
属性
属性 1
属性 2
日期
Property

1.设计思想:

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

2.基本概念

  1. 同步FIFO的“同步”是什么意思?
  1. FIFO是什么,有什么用?
  1. 接口都有什么
    1. notion image
  • 同步:时钟间有确定的倍数关系或确定的相位关系
  • FIFO:Frist-in-first-out,先进先出,是一种数据缓存器,实现速率匹配。
  • 既然是数据缓冲器,那么缓冲器的大小,存储深度,读写地址和存储器空满状态都需要确定。
一般FIFO使用循环指针(计数溢出自动归零)。一般可以称写指针为头head,读指针为尾tail。初始化时,读写指针指向同一数据地址。
·FIFO:先进先出(First-in-first-out) ·FIFO的深度:同一块数据内存的犬小 ·FIFO的宽度 ·写指针:Write-pointer ·读指针:Read-pointer ·写数据端口 ·读数据端口 ·FIFO空、FIFO满
·同步:数据写入FIFO的时钟和数据读出FIFO的时钟是同步的(synchronous) ·异步:数据写入FIFO的时钟和数据读出FIFO的时钟是异步的(asynchronous)
 
一般FIFO使用循环指针(计数溢出自动归零)。一般可以称写指针为头head,读指针为尾tail。 初始化时,读写指针指向同一数据地址。 下图可见,FIFO初始化时,WP和RP指针指向同一数据单元。
WP指向下一个将要写入的数据单元,RP指向将要读出的数据单元。
notion image
初始化时,WP和RP指针指向同一数据单元。WP指向下一个将要写入的数据单元,RP指向将要读出的数据单元,两者是一个追赶过程。可以设置一个计数器,
只写,来一个数据,写一个,写地址,+1,计数器+1,写满为止;
只读,来一个,读出一个数据,读地址+1,计数器-1;
同时读写,计数器值不变,读写地址均+1。+

使用fifo_counter记录FIFORAM中的数据个数:

1.等于0时,给出empty信号
2.等于BUF_LENGTH时,给出full信号

fifo_counter:

1.写而未满时增加1
2.读而未空时减1
3.同时发生读写操作时,fifo_counter不变
读写指针宽度 1.与地址宽度相当 2.地址增加而溢出后,自动变成0
3.循环指针(此处地址变化:0-7-0-7-0)

3.SPEC

(1) Function description
同步FIFO实现了对write/read的控制,其接口解决了接口两端数据速率不匹配的问题。
(2) Feature list
支持存储宽度、深度可配置
时钟工作频率为1MHz
(3) Block diagram
notion image
模块主要分为读/写接口、读/写指针、读写指针的比较逻辑和array存储阵列四部分。
  1. 读/写接口:为模块提供读写数据和读写使能信号;
  1. 读写指针:主要标志读写指针当前array的地址
  1. 比较逻辑:
 
  • 使用element counter(elem_cnt)记录FIFO RAM 中的数据个数:▷ 等于0时,给出empty信号;等于BUF_LENGTH时,给出full信号
  • elem _ cnt :
  • ▷ 写而未满时增加1
  • ▷ 读而未空时减1
  • ▷ 同时发生读写操作时,elem_cnt不变
(4) Interface description
notion image
(5) Timing
notion image
分为三部分,写操作,读操作,读写操作。
 
DUT代码1:
`define BUF_WIDTH 4//地址宽度为3+1,
`define BUF_SIZE 8//数据个数,FIFO深度

module fifo_counter(clk,rst n,buf_in,buf_out,wr_en,rd_en,buf empty,buf_full,fifo_cnt);
		input clk,rst n;
		input wr_en,rden;
		input[70]bufin;//data input to be pushed to buffer 
		output reg [70]buf out;//port to output the data using pop.
		output wire buf empty,buf_full;//buffer empty and full indication 
		output reg [`BUF_WIDTH-1:0] fifo_cnt;//number of data pushed in to buffer
											//写入数据等于8时,满
		reg [`BUF_WIDTH-20] rd_ptr,wr_ptr;
		//这个很重要,数据指针3位宽度,0-7索引,8个数据深度,循环指针0-7-0-7

		reg [70] buf_mem [0BUF_SIZE-1];
		//判断空满
		assign buf_empty=(fifo_cnt==0; //buf_empty若是reg类型则错,不能使用assign持续赋值
		assign buf_full =(fifo_cnt ==BUF_SIZE;

		always@(posedge clk or negedge rst_n)begin 
				if!rst_n)
						fifo cnt<=0else if!buf_full&&wr_en)&&!buf_empty&&rd_en))//同时读写,数量不变
						fifo cnt<=fifo cnt;
				else if!buf_full&&wr_en)//写数据
						fifo_cnt<=fifo_cnt+1;
				else if!buf_empty&&rd_en);//读数据
						fifo_cnt<=fifo_cnt-1else 
						fifo_cnt<=fifo_cnt;
		end
		always@(posedge clk or negedge rst_n)begin//读数据
				if(!rst_n)
						buf_out<=0if(rd_en&&Ibuf_empty)
						buf_out<=buf_mem[rd_ptr];
		end 
		always@(posedge clk)begin 
				if(wr_en&&!buf full)
						buf_memlwr_ptr]<=buf_in;
		end 
		always@(posedge clk or negedge rst_n)begin
			 if(!rst n)begin 
					 wr_ptr<=0;
					 rd_ptr<=0;
			 end 
			 else begin 
		 	 if(!buf_full&&wr_en)
					 wr_ptr<=wr_ptr+1;
			 if(!buf_empty&&rd_en)
					 rd_ptr<=rd_ptr+1;
			 end
		end 
endmodule
		
TB1:
`define BUF WIDTH 4//地址宽度为3+1,
`define BUF_SIZE8//数据个数,FIFO深度
module tb_fifo_counter;
		reg clk,rst_n;
		reg wr_en,rden;
		reg[70]buf_in;//data input to be pushed to buffer 
		wire[70]buf_out;//port to output the data using pop.
		wire buf empty,buf full;//buffer empty and full indication 
		wire['BUF_WIDTH-10]fifo cnt;//number of data pushed in to buffer 
		
		fifo_counter dut(clk,rst_n,buf in,buf_out,wr_en,rd_en,buf empty,buf_full,fifo_cnt);
		always #10 clk=~clk;
		
		reg[70] tempdata=0;
		initial begin 
				clk=0; 
				rst_n=0;
				wr_en=0;
				rd_en=0; 
				buf_in=0;
				#15;
			  rstn=1;
			 push(1);
				 fork 
							push(2); 
							pop(tempdata);
				 join
				push(10);push(20);push(30);push(40);push(50);push(60);push(70);push(80);push(90); push(100);push(110);push(120);push(130); 
				pop(tempdata); push(tempdata); pop(tempdata);pop(tempdata);pop(tempdata);pop(tempdata); push(140);pop(tempdata); push(tempdata);//
				pop(tempdata);pop(tempdata);pop(tempdata);pop(tempdata);pop(tempdata);pop(tempdata);pop(tempdata);pop(tempdata);pop(tempdata);pop(tempdata);pop(tempdata); push(5); pop(tempdata);
		 end
		task push(input [7:0] data); 
				if(buf full)
						$display("--Cannot push %d:Buffer Full---",data); 
				else begin
						$display("Push",,data);
					 buf in=data; 
					wr_en=1;
					@(posedge clk);
					#5 wr_en=0;
			 end 
		endtask 

		task pop(output[7:0]data); 
				if(buf_empty)
						Sdisplay("---Cannot Pop:Buffer Empty---"); 
				else begin 
						rd_en=1;
						@(posedge clk);
						#3rden=0; data=buf out;
						$display ("-----Poped:",data); 
				end 

		endtask 
endmodule
 
RTL1
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
TB2:
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
 

5.波形分析

写阶段
notion image
  • 复位之后,进行写操作,直至写满,产生满标志后,不再写入新数据。
  • 读阶段 
notion image
  • 进行读操作,直至读空,产生空标志后,不再读出新数据。
  • 同时读写阶段
notion image
  • 先进行写操作,写入三个新数据之后,同时进行读写操作,期间写入新数据和读出数据,但是elem_cnt计数器不再变化,动态平衡。

(2)小结

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

参考资料