IC基础——FIFO

FIFO简介

先进先出的数据缓存器,与普通存储器的区别是没有外部读写地址线,使用方便,缺点是只能顺序读写,不能随机读写。其数据地址由内部读写指针自动加 1 完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。

使用场景

1、数据缓冲:也就是数据写入过快,并且间隔时间长,也就是突发写入。那么通过设置一定深度的FIFO,可以起到数据暂存的功能,且使得后续处理流程平滑。

2、时钟域的隔离:主要用异步FIFO。对于不同时钟域的数据传输,可以通过FIFO进行隔离,避免跨时钟域的数据传输带来的设计和约束上的复杂度。比如FIFO的一端是AD,另一端是PCI;AD的采集速率是16位100KSPS,每秒的数据量是1.6Mbps。而PCI总线的速度是33MHz,总线宽度是32位

3、用于不同宽度的数据接口

类别

同步FIFO

读写时钟为同一个时钟

异步FIFO

读写时钟不为同一个时钟

参数

FIFO宽度

FIFO一次读写操作的数据位

FIFO深度

指FIFO可以存储多少个N位的数据(如果宽度为N)

满指标

FIFO已满或将要满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出(overflow)

空指标

FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出(underflow)

读时钟

读操作所遵循的时钟,在每个时钟沿来临时读数据

写时钟

写操作所遵循的时钟,在每个时钟沿来临时写数据

实现

同步FIFO实现

FIFO 读写指针(读写指针就是读写地址)的工作原理:

  • 写指针:总是指向下一个将要被写入的单元,复位时,指向第 1 个单元(编号为 0)
  • 读指针:总是指向当前要被读出的数据,复位时,指向第 1 个单元(编号为 0) FIFO 的“空”/“满”检测

FIFO 设计的关键:产生可靠的 FIFO 读写指针和生成 FIFO“空”/“满”状态标志。

在这里插入图片描述

当读写指针相等时,表明 FIFO 为空,这种情况发生在复位操作时,或者当读指针读出 FIFO 中最后一 个字后,追赶上了写指针时。

当读写指针再次相等时,表明 FIFO 为满,这种情况发生在,当写指针转了一圈,折回来(wrapped around) 又追上了读指针。

1、计数器法

构建一个计数器,该计数器(fifo_cnt)用于指示当前 FIFO 中数据的个数:

  • 复位时,该计数器为0,FIFO中的数据个数为0
  • 当读写使能信号均有效时,说明又读又写,计数器不变,FIFO中的数据个数无变化
  • 当写使能有效且 full=0,则 fifo_cnt +1;表示写操作且 FIFO 未满时候,FIFO 中的数据个数增加了 1
  • 当读使能有效且 empty=0,则 fifo_cnt -1;表示读操作且 FIFO 未满时候,FIFO 中的数据个数减少了 1
  • fifo_cnt =0 的时候,表示 FIFO 空,需要设置 empty=1;fifo_cnt = fifo的深度 的时候,表示 FIFO 现在已经满,需要设置 full=1
计数器法verilog代码
//计数器法实现同步FIFO
module	sync_fifo_cnt
#(
	parameter   DATA_WIDTH = 8  ,							//FIFO位宽
    parameter   DATA_DEPTH = 16 							//FIFO深度
)
(
	input									clk		,		//系统时钟
	input									rst_n	,       //低电平有效的复位信号
	input	[DATA_WIDTH-1:0]				data_in	,       //写入的数据
	input									rd_en	,       //读使能信号,高电平有效
	input									wr_en	,       //写使能信号,高电平有效
															
	output	reg	[DATA_WIDTH-1:0]			data_out,	    //输出的数据
	output									empty	,	    //空标志,高电平表示当前FIFO已被写满
	output									full	,       //满标志,高电平表示当前FIFO已被读空
	output	reg	[$clog2(DATA_DEPTH) : 0]	fifo_cnt		//$clog2是以2为底取对数	
);
 
//reg define
reg [DATA_WIDTH - 1 : 0] fifo_buffer[DATA_DEPTH - 1 : 0];	//用二维数组实现RAM	
reg [$clog2(DATA_DEPTH) - 1 : 0]	wr_addr;				//写地址
reg [$clog2(DATA_DEPTH) - 1 : 0]	rd_addr;				//读地址
 
//读操作,更新读地址
always @ (posedge clk or negedge rst_n) begin
	if (!rst_n)
		rd_addr <= 0;
	else if (!empty && rd_en)begin							//读使能有效且非空
		rd_addr <= rd_addr + 1'd1;
		data_out <= fifo_buffer[rd_addr];
	end
end
//写操作,更新写地址
always @ (posedge clk or negedge rst_n) begin
	if (!rst_n)
		wr_addr <= 0;
	else if (!full && wr_en)begin							//写使能有效且非满
		wr_addr <= wr_addr + 1'd1;
		fifo_buffer[wr_addr]<=data_in;
	end
end
//更新计数器
always @ (posedge clk or negedge rst_n) begin
	if (!rst_n)
		fifo_cnt <= 0;
	else begin
		case({wr_en,rd_en})									//拼接读写使能信号进行判断
			2'b00:fifo_cnt <= fifo_cnt;						//不读不写
			2'b01:	                               			//仅仅读
				if(fifo_cnt != 0)				   			//fifo没有被读空
					fifo_cnt <= fifo_cnt - 1'b1;   			//fifo个数-1
			2'b10:                                 			//仅仅写
				if(fifo_cnt != DATA_DEPTH)         			//fifo没有被写满
					fifo_cnt <= fifo_cnt + 1'b1;   			//fifo个数+1
			2'b11:fifo_cnt <= fifo_cnt;	           			//读写同时
			default:;                              	
		endcase
	end
end
//依据计数器状态更新指示信号
//依据不同阈值还可以设计半空、半满 、几乎空、几乎满
assign full  = (fifo_cnt == DATA_DEPTH) ? 1'b1 : 1'b0;		//空信号
assign empty = (fifo_cnt == 0)? 1'b1 : 1'b0;				//满信号
 
endmodule

其中计数器和读写地址以为为底去对数的原因在于用多少位的二进制表示深度。

2、高位扩展法

采取扩展最高位与读写地址位共同判断是为满或为空。

  • 当最高位不同,且其他位相同,则表示读指针或者写指针多跑了一圈,而显然不会让读指针多跑一圈(多跑一圈读啥?),所以可能出现的情况只能是写指针多跑了一圈,与就意味着FIFO被写满了
  • 当最高位相同,且其他位相同,则表示读指针追到了写指针或者写指针追到了读指针,而显然不会让写指针追读指针(这种情况只能是写指针超过读指针一圈),所以可能出现的情况只能是读指针追到了写指针,也就意味着FIFO被读空了

高位扩展法示意图:

在这里插入图片描述

高位扩展法verilog代码
module	sync_fifo_ptr
#(
	parameter   DATA_WIDTH = 'd8  ,								//FIFO位宽
    parameter   DATA_DEPTH = 'd16 								//FIFO深度
)
(
	input										clk		,		//系统时钟
	input										rst_n	,       //低电平有效的复位信号
	input	[DATA_WIDTH-1:0]					data_in	,       //写入的数据
	input										rd_en	,       //读使能信号,高电平有效
	input										wr_en	,       //写使能信号,高电平有效
						                                        
	output	reg	[DATA_WIDTH-1:0]				data_out,	    //输出的数据
	output										empty	,	    //空标志,高电平表示当前FIFO已被写满
	output										full		    //满标志,高电平表示当前FIFO已被读空
);                                                              
 
//reg define
//用二维数组实现RAM
reg [DATA_WIDTH - 1 : 0]			fifo_buffer[DATA_DEPTH - 1 : 0];	
reg [$clog2(DATA_DEPTH) : 0]		wr_ptr;						//写地址指针,位宽多一位	
reg [$clog2(DATA_DEPTH) : 0]		rd_ptr;						//读地址指针,位宽多一位	
 
//wire define
wire [$clog2(DATA_DEPTH) - 1 : 0]	wr_ptr_true;				//真实写地址指针
wire [$clog2(DATA_DEPTH) - 1 : 0]	rd_ptr_true;				//真实读地址指针
wire								wr_ptr_msb;					//写地址指针地址最高位
wire								rd_ptr_msb;					//读地址指针地址最高位
 
assign {wr_ptr_msb,wr_ptr_true} = wr_ptr;						//将最高位与其他位拼接
assign {rd_ptr_msb,rd_ptr_true} = rd_ptr;						//将最高位与其他位拼接
 
//读操作,更新读地址
always @ (posedge clk or negedge rst_n) begin
	if (rst_n == 1'b0)
		rd_ptr <= 'd0;
	else if (rd_en && !empty)begin								//读使能有效且非空
		data_out <= fifo_buffer[rd_ptr_true];
		rd_ptr <= rd_ptr + 1'd1;
	end
end
//写操作,更新写地址
always @ (posedge clk or negedge rst_n) begin
	if (!rst_n)
		wr_ptr <= 0;
	else if (!full && wr_en)begin								//写使能有效且非满
		wr_ptr <= wr_ptr + 1'd1;
		fifo_buffer[wr_ptr_true] <= data_in;
	end	
end
 
//更新指示信号
//当所有位相等时,读指针追到到了写指针,FIFO被读空
assign	empty = ( wr_ptr == rd_ptr ) ? 1'b1 : 1'b10;
//当最高位不同但是其他位相等时,写指针超过读指针一圈,FIFO被写满
assign	full  = ( (wr_ptr_msb != rd_ptr_msb ) && ( wr_ptr_true == rd_ptr_true ) )? 1'b1 : 1'b0;
 
endmodule

实验仿真

TestBench代码
`timescale  1ns / 1ps

module tb_sync_fifo_ptr;

// sync_fifo_ptr Parameters
parameter PERIOD      = 5  ;
parameter DATA_WIDTH  = 'd8 ;
parameter DATA_DEPTH  = 'd16;

// sync_fifo_ptr Inputs
reg   clk                                  = 0 ;
reg   rst_n                                = 0 ;
reg   [DATA_WIDTH-1:0]  data_in            = 0 ;
reg   rd_en                                = 0 ;
reg   wr_en                                = 0 ;

// sync_fifo_ptr Outputs
wire  [DATA_WIDTH-1:0]  data_out           ;
wire  empty                                ;
wire  full                                 ;


initial
begin
    forever #(PERIOD/2)  clk=~clk;
end

initial
begin
    #(PERIOD*2) rst_n  =  1;
end

sync_fifo_ptr #(
    .DATA_WIDTH ( DATA_WIDTH ),
    .DATA_DEPTH ( DATA_DEPTH ))
 u_sync_fifo_ptr (
    .clk                     ( clk                        ),
    .rst_n                   ( rst_n                      ),
    .data_in                 ( data_in   [DATA_WIDTH-1:0] ),
    .rd_en                   ( rd_en                      ),
    .wr_en                   ( wr_en                      ),

    .data_out                ( data_out  [DATA_WIDTH-1:0] ),
    .empty                   ( empty                      ),
    .full                    ( full                       )
);
reg [7:0] tempdata = 0;
initial
begin
    $dumpfile("wave.vcd");        //生成的vcd文件名称
    $dumpvars(0, tb_sync_fifo_ptr);    //tb模块名称
    clk = 0;
    rst_n = 0;
    wr_en = 0;
    rd_en = 0;
    data_in = 0;
    #10
    rst_n = 1;
    push(1);
        fork
           push(2);
           pop(tempdata);
        join              //push and pop together   
        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);
		$finish;
end
	
	task push (input [7:0] data);
		if(full)
			$display("---Cannot push %d: Buffer Full---",data);
		else begin
			$display("Push",data);
			data_in = data;
			wr_en = 1;
			@(posedge clk);
			#5 wr_en = 0;
		end
	endtask
	
	task pop(output[7:0] data);
		if(empty)
			$display("---Cannot Pop: Buffer Empty---");
		else begin
			rd_en = 1;
			@(posedge clk);
			#3 rd_en = 0;
			data = data_out;
			$display("------Poped:",data);
		end		
	endtask
endmodule
波形示意图

在这里插入图片描述

异步FIFO实现

异步产生亚稳态问题

异步FIFO需要考虑的是亚稳态的问题,如果采用二进制数的计数值从一个时钟域到另一个时钟域的时候就会出问题,因为二进制计数器的所有位都有可能发生变化。使用格雷码的话只有一位变化,在两个时钟域间的同步多个位不会产生问题。所以需要一个二进制到gray码的转换电路,将地址值转换成相应的gray码,然后将gray码同步到另一个时钟域进行对比,作为空满状态的检测。

在这里插入图片描述

gray码判断空满问题

空判断

对于“空”的判断依然依据读写指针二者完全相等(包括MSB);

满判断

因为gray码的特性,与同步不同的是MSB最高位不同时,其他位相同,不能作为FIFO满的判断。

在gray码上判断为满必须同时满足以下3条:

  • wptr和同步过来的rptr的MSB不相等,因为wptr必须比rptr多折回一次。
  • wptr与rptr的次高位不相等,如上图位置7和位置15,转化为二进制对应的是0111和1111,MSB不同说明多折回一次,111相同代表同一位置。
  • 剩下的其余位完全相等。

设计总体实现

在这里插入图片描述

代码:

module AsyncFIFO#(
    parameter ASIZE = 4,    //地址位宽
    parameter DSIZE = 8     //数据位宽
)(
    input [DSIZE-1:0] wdata,
    input             winc,wclk,wrst_n,    //写请求信号,写时钟,写复位
    input             rinc,rclk,rrst_n,    //读请求信号,读时钟,读复位 
    output [DSIZE-1:0] rdata,
    output  wfull,
    output  rempty              
);
    wire [ASIZE-1:0] waddr, raddr;
    wire [ASIZE:0]   wptr, rptr, wq2_rptr, rq2_wptr; 
    /*在检测“满”或“空”状态之前,需要将指针同步到其它时钟域时,使用格雷码,可以降低同步过程中亚稳态出现的概率*/
    sync_r2w#(
        .ADDRSIZE ( 4 )
    )u_sync_r2w(
        .wq2_rptr ( wq2_rptr ), //out
        .rptr     ( rptr     ),
        .wclk     ( wclk     ),
        .wrst_n   ( wrst_n   )
    );

    sync_w2r#(
        .ADDRSIZE ( 4 )
    )u_sync_w2r(
        .rq2_wptr ( rq2_wptr ),
        .wptr     ( wptr     ),
        .rclk     ( rclk     ),
        .rrst_n   ( rrst_n   )
    );
    fifomem#(
        .DATASIZE ( 8 ),
        .ADDRSIZE ( 4 )
    )u_fifomem(
        .wclken   ( wclken   ),
        .wfull    ( wfull    ),
        .wclk     ( wclk     ),
        .wdata    ( wdata    ),
        .waddr    ( waddr    ),
        .raddr    ( raddr    ),
        .rdata    ( rdata    )
    );
    rptr_empty#(
        .ADDRSIZE ( 4 )
    )u_rptr_empty(
        .rempty   ( rempty   ),
        .raddr    ( raddr    ),
        .rptr     ( rptr     ),
        .rq2_wptr ( rq2_wptr ),
        .rinc     ( rinc     ),
        .rclk     ( rclk     ),
        .rrst_n   ( rrst_n   )
    );
    wptr_full#(
        .ADDRSIZE ( 4 )
    )u_wptr_full(
        .wfull    ( wfull    ),
        .waddr    ( waddr    ),
        .wptr     ( wptr     ),
        .wq2_rptr ( wq2_rptr ),
        .winc     ( winc     ),
        .wclk     ( wclk     ),
        .wrst_n   ( wrst_n   )
    );

endmodule
顶层设计

在这里插入图片描述

信号描述

信号名称位宽信号描述
Wdata数据位宽写入数据
Wfull1写满信号
Winc1写请求信号(写使能信号)
Wclk1写时钟
Wrst_n1写复位信号(低电平有效
Rdata数据位宽读出数据
Rempty1读空信号
Rinc1读请求信号(读使能信号)
Rrst_n1读复位信号(低电平有效)
RAM存储器模块

在这里插入图片描述

信号描述:

信号名称位宽信号描述
wclken1写使能信号
wclk1写时钟信号
raddr地址位宽读地址
waddr地址位宽写地址
wdata数据位宽写入的数据
rdata数据位宽读数据
wfull1写满信号

代码:

根据上面RAM的总概图,定义的一个宽度为8位,深度为8的RAM(即定义了8个寄存器,每个寄存器的宽度为8)

module fifomem
#(
    parameter  DATASIZE = 8, // Memory data word width               
    parameter  ADDRSIZE = 4  // 指针地址位宽设置为4,因为8=2^3,应该加一判断写满或读空
) // Number of mem address bits
(
    input                 wclken, wfull, wclk,
    input  [DATASIZE-1:0] wdata, //write data
    input  [ADDRSIZE-1:0] waddr, raddr, 
    output [DATASIZE-1:0] rdata //read data
);
 
    // RTL Verilog memory model
    localparam DEPTH = 1<<ADDRSIZE;   // leftshift is equal divide two
    reg [DATASIZE-1:0] mem [0:DEPTH-1];


    always @(posedge wclk) begin //当使能信号有效且还未写满的时候将数据写入实体中,与wclk时钟信号同步
        if (wclken && !wfull)
            mem[waddr] <= wdata;
    end
    assign rdata = mem[raddr];
 endmodule
FIFO写地址以及写满判断模块

主要是控制是否可以写入数据,写指针与写满的顶层模块图如图所示:

在这里插入图片描述

信号描述:

信号名称位宽信号描述
Winc1写请求信号
Wclk1写时钟信号
Wrst_n1写复位信号
Wq2_rptr地址位宽+1同步之后的读指针(格雷码形式)
Wfull1写满信号
Waddr地址位宽二进制形式的写地址
Wptr地址位宽+1格雷码形式的写指针

模块作用:当时钟信号来临时且写请求信号有效时,写一组数据,并且同时写地址向下加一位,然后讲写地址转化为格雷码,判断是否写满

写满的判断:写地址指针再次追上读地址指针

二进制转格雷码:

​ 二进制的最右一位(最低位)起,依次将每一位与左边一为进行异或操作,作为格雷码该位的值,而最左一位不变。

代码:

module wptr_full#(
    parameter ADDRSIZE = 4
) (
    output reg wfull,   
    output        [ADDRSIZE-1:0] waddr,//二进制形式的写地址
    output reg    [ADDRSIZE:0]   wptr, //格雷码形式的写指针
    input         [ADDRSIZE:0]   wq2_rptr,//同步后的读指针
    input         winc, wclk, wrst_n
);
    reg  [ADDRSIZE:0] wbin;
    wire [ADDRSIZE:0] wgraynext, wbinnext;
    wire wfull_val;
    // GRAYSTYLE2 pointer
    always @(posedge wclk or negedge wrst_n) begin 
        if (!wrst_n)
            {wbin, wptr} <= 0;   
        else         
            {wbin, wptr} <= {wbinnext, wgraynext};
    end
    // Memory write-address pointer (okay to use binary to address memory) 
    assign waddr      = wbin[ADDRSIZE-1:0]; 
    assign wbinnext   = wbin + (winc & ~wfull);//写请求且没写满时,地址加一
    assign wgraynext  = (wbinnext>>1) ^ wbinnext; //将二进制码转换为格雷码

    //最高位表示多这一次,但由于格雷码特性,次高位也需要不同,其余位需要相同
    assign wfull_val = (wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]}); 
    
    always @(posedge wclk or negedge wrst_n) begin
        if (!wrst_n)
            wfull  <= 1'b0;   
        else     
            wfull  <= wfull_val;
    end
  endmodule
FIFO读地址以及读空判断模块

读控制端口主要用于是否可以读取数据,读指针与读空的顶层模块图如下图:

在这里插入图片描述

信号描述:

信号名称位宽信号描述
Rinc1读请求信号
Rclk1读时钟信号
Rrst_n1读复位信号
Rq2_rptr地址位宽+1同步之后的写指针(格雷码形式)
Rempty1读空信号
Raddr地址位宽二进制形式的读地址
Rptr地址位宽+1格雷码形式的读指针

**作用:**当时钟信号来且读请求信号有效时,读出一组数据,并且同时读地址向下加一位。然后将读地址转换为格雷码,判断是否为读空。

代码:

1、寄存二进制地址,方便转换成格雷码

2、根据读使能以及是否为空,赋值下一地址

3、每到上升沿采样将下一拍地址传给当前地址变量

4、当同步之后的写指针与读指针(格雷码形式)相同时说明为空

module rptr_empty #(
    parameter ADDRSIZE = 4
)(
    output reg rempty,
    output      [ADDRSIZE-1:0] raddr,
    output reg  [ADDRSIZE :0]  rptr,
    input       [ADDRSIZE :0]  rq2_wptr,
    input       rinc, rclk, rrst_n
);
    reg  [ADDRSIZE:0] rbin;
wire [ADDRSIZE:0] rgraynext, rbinnext;
wire  rempty_val;
//-------------------
// GRAYSTYLE2 pointer: gray码读地址指针
//-------------------
    always @(posedge rclk or negedge rrst_n) begin
        if (!rrst_n) 
            begin 
                rbin <= 0;
                rptr <= 0;
            end
        else
            begin
                rbin <= rbinnext ; 
                rptr <= rgraynext;
            end
    end
// gray码计数逻辑
    assign raddr = rbin[ADDRSIZE-1:0];
    assign rbinnext  = rbin + (rinc & ~rempty); //不空且有读请求的时候读地址加1
    assign rgraynext = (rbinnext>>1) ^ rbinnext;      //二进制到gray码的转换
//---------------------------------------------------------------
// FIFO empty when the next rptr == synchronized wptr or on reset
//---------------------------------------------------------------
/*
*   读指针是一个n位的gray码计数器,比FIFO寻址所需的位宽大一位
*   当读指针和同步过来的写指针完全相等时(包括MSB),说明二者折回次数一致,FIFO为空
*     
*/
assign rempty_val = (rgraynext == rq2_wptr);
        always @(posedge rclk or negedge rrst_n)
            if (!rrst_n) 
                rempty <= 1'b1;
            else 
                rempty <= rempty_val;
endmodule
FIFOF写时钟同步到读时钟模块

示意图如下:

在这里插入图片描述

信号名称位宽信号描述
Rclk1读时钟信号
Rrst_n1读复位信号
Wptr地址位宽+1写指针地址
Rq2_wptr地址位宽+1同步写指针地址

代码:

module sync_w2r #(
    parameter ADDRSIZE = 4
)(
    output reg  [ADDRSIZE:0] rq2_wptr,
    input       [ADDRSIZE:0] wptr,
    input       rclk, rrst_n
);        
    reg [ADDRSIZE:0] rq1_wptr;
    always @(posedge rclk or negedge rrst_n) begin
        if (!rrst_n)
            {rq2_wptr,rq1_wptr} <= 0;
        else begin // {rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};
            rq1_wptr <= wptr;
            rq2_wptr <= rq1_wptr;
        end
           
    end

endmodule
FIFO读时钟同步到写时钟模块

示意图如下:

在这里插入图片描述

信号名称位宽信号描述
Wclk1写时钟信号
Wrst_n1写复位信号
Rptr地址位宽+1读指针地址
Wq2_rptr地址位宽+1同步读指针地址

代码:

module sync_r2w #(
    parameter ADDRSIZE = 4
)(
    output reg [ADDRSIZE:0] wq2_rptr,
    input      [ADDRSIZE:0] rptr,
    input                   wclk, wrst_n
);
    reg [ADDRSIZE:0] wq1_rptr;
    always @(posedge wclk or negedge wrst_n) begin
        if (!wrst_n) 
            {wq2_rptr,wq1_rptr} <= 0;
        else 
            {wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};
    end
endmodule

实验仿真

1、输入信号初始化

2、FIFO复位

3、写读时钟

4、读写控制(写数据、读数据)

TestBench代码:

`timescale  1ns / 1ps

module tb_AsyncFIFO;

// AsyncFIFO Parameters

parameter ASIZE  = 4;
parameter DSIZE  = 8;

// AsyncFIFO Inputs
reg   [DSIZE-1:0]  wdata                   = 0 ;
reg   winc                                 = 0 ;
reg   wclk                                 = 0 ;
reg   wrst_n                               = 0 ;
reg   rinc                                 = 0 ;
reg   rclk                                 = 0 ;
reg   rrst_n                               = 0 ;

// AsyncFIFO Outputs
wire  [DSIZE-1:0]  rdata                   ;
wire  wfull                                ;
wire  rempty                               ;

initial
begin
    //输入信号初始化
    wrst_n = 1;
    rrst_n = 1;
    wclk = 0;
    rclk = 0;
    winc = 0;
    rinc = 0;
    wdata = 0;
    //raddr = 0;
    //复位信号
    # 30 
    wrst_n = 0;
    rrst_n = 0;
    #30
    wrst_n = 1;
    rrst_n = 1;

end
always //写时钟
    #2 wclk = ~wclk;
always //读时钟
    #4 rclk = ~rclk;

always @(*) begin
    if(!wfull) begin
        winc = 1;
    end
    else begin
        winc =0;
    end
end

always @(*) begin
    if(!rempty) begin
        rinc = 1;
    end
    else begin
        rinc =0;
    end
end

always @(posedge wclk) begin
    if(!wfull) begin
        wdata <= wdata + 1;
    end 
    else begin
      wdata <= wdata;
    end
end

AsyncFIFO #(
    .ASIZE ( ASIZE ),
    .DSIZE ( DSIZE ))
 u_AsyncFIFO (
    .wdata                   ( wdata   ),
    .winc                    ( winc                ),
    .wclk                    ( wclk                ),
    .wrst_n                  ( wrst_n              ),
    .rinc                    ( rinc                ),
    .rclk                    ( rclk                ),
    .rrst_n                  ( rrst_n              ),
    .rdata                   ( rdata ),
    .wfull                   ( wfull               ),
    .rempty                  ( rempty              )
);

initial
begin
    $dumpfile("wave_test.vcd");        //生成的vcd文件名称
    $dumpvars(0, tb_AsyncFIFO);    //tb模块名称
    #1000 $stop;
    //$finish;
end

endmodule

仿真的波形图:

在这里插入图片描述

总结

异步fifo的重点,第一是对读写信号的同步,主要用两级缓存对读写信号的同步,第二是对写满信号的判断需要二进制到格雷码之间的转换

  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值