FIFO工作流程与代码流程

FIFO介绍1

1. 什么是FIFO?

FIFO是一种先入先出的数据缓存器。优点是没有外部读写地址线,使用简单,缺点是只能顺序写入数据与读出数据。数据地址由内部指针自动加1完成

2.FIFO应用场景

1. FIFO一般用于不同时钟域之间的数据传输:设FIFO一端的AD数据采集的速率为16位 100K,每秒速度为100K×16bit=1.6Mbps,另一端计算机的PCI总线速度为33MHz,总线宽度为32bit,其最大传输速率为1056Mbps,两个不同的时钟域可以采用FIFO来做数据缓冲。
2. FIFO还可用于不同位宽的数据接口:单片机8位数据输出,DSP16位数据输入,在单片机与DSP连接时可以使用FIFO来进行数据匹配。

3.典型异步FIFO结构图2

下图为典型异步FIFO结构图,可分为几个部分:

  1. 双端RAM:存储输入端的数据,输出端取出数据。输入数据和输出数据位宽可以不一致,但要保证写数据、写地址位宽与读数据、读地址位宽的一致性。例如写数据位宽 8bit,写地址位宽为 6bit(64 个8bit数据)时,若输出数据位宽为 32bit,则输出地址位宽应该为 4bit(2^4 ×32)。 memory 数组定义时,以长位宽地址、短位宽数据的参数为参考,方便数组变量进行选择访问。
  2. 写地址与写指针:(计数器)写指针从0开始,每写一次数据写地址指针加一,指向下一个存储单元。当 FIFO 写满后,数据将不能再写入,否则数据会因覆盖而丢失。
  3. 读地址与读指针:(计数器)读指针指向下一个读出地址,读完自动加 1。 读写指针其实就是读写的地址,只不过这个地址不能任意选择,而是连续的。
  4. 写数据指针同步到读时钟域
  5. 读数据指针同步到写时钟域
  6. 写满判断:通过两级寄存器,将读指针同步到写时钟域与写指针对比,判断是否写满,实际读指针会大一些,写满非真满,会影响一定性能,但不影响功能
  7. 读空判断:通过两级寄存器,将写指针同步到读时钟域与读指针对比是否读空,实际写指针会大一些,读空非真空,会影响一定性能,但不影响功能
  8. 满空标志:在用到触发器的设计中,会遇到亚稳态的问题(亚稳态是在时钟跳变的时,寄存器采样到一个逻辑0和逻辑1参考电压的中间值。而亚稳态经过一段时间逐渐恢复成逻辑0或1,而具体会成为0还是1这件事是无法预测的。出现亚稳态的原因根源是被采样信号在时钟沿发生了跳变。一般情况下,同步时钟在保证setup和hold的情况下不会出现亚稳态(这也同步时钟不需要转格雷码的原因),而异步时钟相位关系无法设定,有可能同步前的信号正好在目标时钟沿跳变,有概率出现亚稳态3)。亚稳态的发生会使得 FIFO出现错误,读/写时钟采样的地址指针会与真实的值之间不同,这就导致写入或读出的地址错误
    a. 使用格雷码降低亚稳态概率:格雷码在相邻的两个码元之间只有一位变换(二进制码在很多情况下是很多码元在同时变化),这就会减少计数器与时钟同步的时候发生亚稳态现象。
    b. 使用冗余的触发器:假设一个触发器发生亚稳态的概率为 P,那么两个及联的触发器发生亚稳态的概率就为 P的平方,但这会导致延时的增加,考虑延时的作用,空/满标志的产生并不一定出现在 FIFO真的空/满时才出现,可能FIFO还未空/满时就出现了空/满标志。
    在这里插入图片描述

FIFO工作流程4

格雷码形式表示指针,二进制表示地址

1. 双端RAM:
	   输入:输入数据,输入地址,输出地址,写时钟,写时钟使能,写满标志
	   输出:输出数据
module fifomem 
#(	parameter DATASIZE = 8, // 数据位宽
    parameter ADDRSIZE = 4) // 地址位宽,2^4 × 8bit
	(output wire [DATASIZE - 1 : 0] rdata,
	 input  wire [DATASIZE - 1 : 0] wdata,
	 input  wire [ADDRSIZE - 1 : 0] waddr, raddr,
	 input  wire                    wclken, wfull, wclk);
	 
	 `ifdef VENDORRAM
		//实例化一个双端RAM
		vendor_ram mem(
			.dout(rdata),
			.din(wdata),
			.waddr(waddr),
			.raddr(raddr),
			.wclken(wclken),
			.clk(wclk));
     `else
        //RTL Verilog memory model
        localparam DEPTH = 1 << ADDRSIZE;
		reg[DATASIZE - 1 : 0] mem [DEPTH - 1 : 0];//根据位宽与地址定义memory
		assign rdata = mem[raddr];//读出读地址的数据	
		always @ (posedge wclk)
			if(wclken == 1'b1 && wfull == 1'b0) //未写满,写时钟使能时,将数据写入
				mem[waddr] <= wdata;
	 
	 `endif
	 
endmodule
2.两级寄存机将读指针同步到写域
	输入:写时钟,写复位,读指针
	输出:写域的读指针
module sync_r2w 
   #(parameter ADDRSIZE = 4)
	(output reg[ADDRSIZE : 0] wq2_rptr,
	 input  wire[ADDRSIZE :0] rptr,
	 input  wire              wclk, wrst_n);
	 
	 reg[ADDRSIZE :0] wq1_rptr;//2级寄存器
	 
	 always @ (posedge wclk or negedge wrst_n)
		if(wrst_n == 1'b0)
			{wq2_rptr, wq1_rptr} <= 0;
		else
			{wq2_rptr, wq1_rptr} <= {wq1_rptr, rptr};
	
endmodule
3.两级寄存机将写指针同步到读域
	输入:读时钟,读复位,写指针
	输出:读域的写指针
module sync_w2r 
   #(parameter ADDRSIZE = 4)
	(output reg[ADDRSIZE : 0] rq2_wptr,
	 input  wire[ADDRSIZE :0] wptr,
	 input  wire              rclk, rrst_n);
	 
	 reg[ADDRSIZE :0] rq1_wptr;	 
	 always @ (posedge rclk or negedge rrst_n)
		if(rrst_n == 1'b0)
			{rq2_wptr, rq1_wptr} <= 0;
		else
			{rq2_wptr, rq1_wptr} <= {rq1_wptr, wptr};

endmodule
4. 读空判断与读指针的移动
	输入:读域的写指针,读使能,读时钟,读复位
	输出:读空标志,读指针,读地址
module rptr_empty 
   #(parameter ADDSIZE = 4)
	(output reg[ADDSIZE :0]      rptr,//格雷码形式的读指针
	 output reg[ADDSIZE - 1 : 0] raddr,//二进制形式的读地址
	 
	 output reg                  rempty,
	 input  wire[ADDSIZE : 0]    rq2_wptr,//同步到读域的写指针
	 input  wire                 rinc, rclk, rrst_n);
	 
	 //=============临时变量==================//
	 reg  [ADDSIZE : 0] rbin;//输出的读地址
	 wire [ADDSIZE : 0] rgraynext, rbinnext;//移动后的读指针与读地址
	 wire               rempty_val;//读空标志
	
	//读指针移动:读时钟到,读指针更新(+1)
	 always @ (posedge rclk or negedge rrst_n)
		if(rrst_n == 1'b0)
			{rbin, rptr} <= 0;
		else
			{rbin, rptr} <= {rbinnext, rgraynext};
			
	assign raddr = rbin[ADDSIZE - 1 : 0];
	
	assign rbinnext = rbin + (rinc & ~rempty);//不空且有读请求的时候读指针+1
	assign rgraynext = (rbinnext >> 1) ^ rbinnext;//读地址转换为格雷码->读指针
	
	//判断是否读空:当前读指针与同步到读域的写指针完全相同
	assign rempty_val = (rgraynext == rq2_wptr);
	
	//读空赋值
	always @ (posedge rclk or negedge rrst_n)
		if(rrst_n == 1'b0)
			rempty <= 1'b1;
		else
			rempty <= rempty_val;
	
endmodule
5. 写满判断与写指针的移动
	输入:写域的读指针,写使能,写时钟,写复位
	输出:写满标志,写指针,写地址
module wptr_full 
   #(parameter ADDRSIZE = 4)
	(output reg[ADDRSIZE : 0]      wptr,//格雷码形式的写指针
	 output wire[ADDRSIZE - 1 : 0] waddr,//二进制形式的写地址
	 output reg                    wfull,
	 
	 input  wire[ADDRSIZE :0]      wq2_rptr,
	 input  wire                   wclk, winc, wrst_n);
	 
	 //=============临时变量==================//
	 reg [ADDRSIZE : 0] wbin;//输出的写地址
	 wire [ ADDRSIZE : 0] wbinnext, wgraynext};//移动后的写地址与写指针
	 wire full_val;//写满标志
	 
	 //写指针移动:写时钟到,写指针更新(+1)
	 always @ (posedge wclk or negedge wrst_n)
		if(wrst_n == 1'b0)
			{wbin, wprt} <= 0;
		else
			{wbin, wprt} <= {wbinnext, wgraynext};
	 
	 assign wgraynext = (wbinnext >> 1) ^ wbinnext; //写地址转换为格雷码->写指针
	 assign wbinnext = wbin + (winc + ~wfull); //不满且有写请求时,写地址+1;溢出后重新归零
	 
	 assign waddr = wbin[ADDRSIZE - 1 : 0];//更新写地址
	 
	 assign full_val = (wgraynext == {~wq2_rptr[ADDRSIZE : ADDRSIZE - 1], wq2_rptr[ADDRSIZE - 2] : 0});//最高位和次高位不同,其余位相同时,为写满
     //写满赋值
     always @ (posedge wclk or negedge wrst_n)
		if(wrst_n == 1'b0);
			wfull <= 1'b0;
		else
			wfull <= full_val;
			
endmodule
6.top层
	输入:写数据;写使能,写时钟,写复位;读使能,读时钟,读复位
	输出:读数据;写满标志,读空标志

module fifo 
   #(parameter DATASIZE = 8,            //数据位宽
     parameter ADDRSIZE = 4)            //地址位宽
	(output wire[DATASIZE - 1 : 0] rdata,
	 output wire                   wfull,
	 output wire                   rempty,
	 input  wire[DATASIZE - 1 : 0] wdata,
	 input  wire                   winc, wclk, wrst_n,     //写请求、写时钟、写复位
	 input  wire                   rinc, rclk, rrst_n);    //读请求、读时钟、读复位
	
	 //====================临时变量=====================//
	 wire [ADDRSIZE - 1 : 0] waddr, raddr;//读地址,写地址
	 wire [ADDRSIZE : 0]     wptr, rptr, wq2_rptr, rq2_wptr;//读指针,写指针,读域的写指针,写域的读指针
	 
	 //=============读域与写域的时钟同步==================//
	 sync_r2w U_sync_r2w (.wq2_rptr(wq2_rptr), .rptr(rtpr),
	                      .wclk(wclk), .wrst_n(wrst_n));
	 
	 sync_w2r U_sync_w2r (.rq2_wptr(rq2_wptr), .wptr(wptr),
	                      .rclk(rclk), .rrst_n(rrst_n));
	                      
	//===================mem实例化====================//					  
	 fifomem #(DSIZE, ASIZE) fifomem
                         (.rdata(rdata), .wdata(wdata),
                          .waddr(waddr), .raddr(raddr),
                          .wclken(winc), .wfull(wfull),
                          .wclk(wclk));
	//===================读空实例化====================//	
	 rptr_empty #(ASIZE) rptr_empty
                         (.rempty(rempty), .raddr(raddr),
                          .rptr(rptr), .rq2_wptr(rq2_wptr),
                          .rinc(rinc), .rclk(rclk),
                          .rrst_n(rrst_n));
	//===================写满实例化====================//						  
     wptr_full #(ASIZE) wptr_full
                         (.wfull(wfull), .waddr(waddr),
                          .wptr(wptr), .wq2_rptr(wq2_rptr),
                          .winc(winc), .wclk(wclk),
                          .wrst_n(wrst_n));
						  
endmodule
7. testbentch
`timescale 1ns/1ns
module fifo_tb;
   reg          wrstn, winc, wclk;
   reg          rrstn, rinc, rclk;
   reg [31:0]   wdata ;
   
   wire [31:0]  rdata ;
   wire         wfull;  
   wire         rempty;  

   fifo	u_fifo (
               .rdata(rdata),  
            //    .wfull(wfull),  
            //    .rempty(rempty),  
               .wdata (wdata),  
               .winc  (winc), 
               .wclk  (wclk), 
               .wrst_n(wrst_n), 
               .rinc(rinc), 
               .rclk(rclk), 
               .rrst_n(rrst_n)
 	);
 	localparam CYCLE = 20;
	localparam CYCLE1 = 40;
	//生成本地时钟50M
    initial begin
        wclk = 0;
        forever
        #(CYCLE/2)
        wclk=~wclk;
    end
    initial begin
        rclk = 0;
        forever
        #(CYCLE1/2)
        rclk=~rclk;
    end
   //reset
    initial begin
        wrst_n = 1;
        #2;
        wrst_n = 0;
        #(CYCLE*3);
        wrst_n = 1;
    end
    initial begin
       rrst_n = 1;
       #2;
       rrst_n = 0;
       #(CYCLE*3);
       rrst_n = 1;
    end
    
    initial begin
	    winc=0;
	    #5 winc=1;
	end 
	
	initial begin
	   rinc=0;
	    #5 rinc=1;
	end
    //data generate
    always #30 wdata= $random ;
	
	//stop sim
   initial begin
      forever begin
         #100;
         if ($time >= 5000)  $finish ;
      end
   end

endmodule

未完待续。。。。。。
仿真结果。。。。
画个图可以清晰一点
专栏里面关于读写数据位宽与读写地址位宽不同的转换


  1. 一个PDF上看见的,不知道原文出处了。。 ↩︎

  2. https://www.runoob.com/w3cnote/verilog2-fifo.html ↩︎

  3. 视频的第一个评论:https://www.bilibili.com/video/BV1DF411u7DA ↩︎

  4. 小姐姐讲的很赞,和小姐姐代码差不多https://www.bilibili.com/video/BV14A4y197Fq ↩︎

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,以下是一个简单的FIFO流程的Verilog代码: ``` module fifo #( parameter WIDTH = 8, // 数据位宽 parameter DEPTH = 16 // FIFO 深度 ) ( input clk, // 时钟 input rst, // 复位 input wr_en, // 使能 input rd_en, // 读使能 input [WIDTH-1:0] data_in, // 数据 output [WIDTH-1:0] data_out // 读数据 ); reg [WIDTH-1:0] fifo_mem [0:DEPTH-1]; // FIFO 存储器 reg [WIDTH-1:0] rd_ptr = 0; // 读指针 reg [WIDTH-1:0] wr_ptr = 0; // 指针 reg [WIDTH-1:0] count = 0; // FIFO 数据的个数 // 操作 always @(posedge clk, posedge rst) begin if (rst) begin wr_ptr <= 0; count <= 0; end else if (wr_en && count < DEPTH) begin fifo_mem[wr_ptr] <= data_in; wr_ptr <= wr_ptr + 1; count <= count + 1; end end // 读操作 always @(posedge clk, posedge rst) begin if (rst) begin rd_ptr <= 0; end else if (rd_en && count > 0) begin data_out <= fifo_mem[rd_ptr]; rd_ptr <= rd_ptr + 1; count <= count - 1; end end endmodule ``` 该代码定义了一个FIFO模块,具有指定的数据位宽和深度。它包含一个存储器数组,一个读指针,一个指针和一个计数器,用于跟踪FIFO数据。在时钟上升沿时,如果使能被置位且FIFO未满,则将数据入存储器,并将指针和计数器递增。在时钟上升沿时,如果读使能被置位且FIFO非空,则从存储器读出数据,并将读指针和计数器递减。 这个FIFO模块可以在其他模块实例化,并连接到系统的其他信号和模块。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小鱼爱学习,每天好心情

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值