CDC处理——异步FIFO

文章详细介绍了异步FIFO的工作原理,包括使用格雷码确保读写指针跨时钟域传输时只改变一位以避免错误,以及如何处理偶数和奇数深度的FIFO。此外,还提供了Verilog代码示例,展示了FIFO的读写指针同步和存储器访问的实现方法。
摘要由CSDN通过智能技术生成

1. 异步FIFO原理

        请看《硬件架构的艺术》笔记(三)3.8节 异步FIFO 

2. 格雷码传递FIFO读写指针(回环特性)

        通常情况下,设计的异步FIFO的深度是2的N次方,但事实上,选择这个2^N的原因也是因为格雷码这么取的时候,最大值+1回到最小值时,跳变还是只有1bit。

        事实上格雷码可以取2N个进行环回,例如取格雷码个数为12,在2^4=16的基础上,掐头去尾,取中间的12个格雷码,这样从0011到最大值1011中间任意两个相邻数之间只有1bit位的跳变。

        任意偶数个数格雷码,都可以实现相邻数字跳变仅有1bit发生变化。 

 3. 任意深度异步FIFO

        异步FIFO的关键就在于读写指针跨时钟域传输的问题,保证每次跳变仅 有1bit变化,这样无论是变化前还是变化后的读写指针被同步到另一个时钟域,都不会发生“FIFO空时继续读”、“FIFO满时继续写”这样的逻辑错误。

3.1 偶数深度的异步FIFO

        对于偶数深度的FIFO,如FIFO深度为4。那么无论FIFO深度是多少,我们都会对地址进行扩展1bit,来作为标志位,用于产生空满信号。因此FIFO深度为4时,我们的读写指针取值范围为:

        {0_110,0_111,0_101,0_100,1_100,1_101,1_111,1_110}

        可以看到,任意两个相邻的数字仅有1bit发生跳变,取格雷码回环就按照:在2^4=16的基础上,掐头去尾,取中间的8个格雷码。

3.2 奇数深度的异步FIFO

        对于奇数深度的FIFO,如FIFO深度为3。那么无论FIFO深度是多少,我们都会对地址进行扩展1bit,来作为标志位,用于产生空满信号。因此FIFO深度为3时,我们的读写指针取值范围为:

        {0_111,0_101,0_100,1_100,1_101,1_111}

        可以看到,任意两个相邻的数字仅有1bit发生跳变,取格雷码回环就按照:在2^4=16的基础上,掐头去尾,取中间的6个格雷码。

        也就是说,无论FIFO深度是奇数还是偶数,我们都会对读写指针扩1bit标志位,使得扩充后他们的深度一定是偶数。这样我们就可以用第二节提到的偶数格雷码环回特性:任意偶数个数格雷码,都可以实现相邻数字跳变仅有1bit发生变化。让读写指针跨时钟域传输时,相邻数字仅有1bit发生变化,就不会出现FIFO读写错误,所以FIFO深度可以是任意值。

4. Verilog 实现

4.1 设计代码

module async_fifo
#(
    parameter   DATA_WIDTH  =   16, //FIFO 位宽(数据位宽)
                FIFO_DEPTH  =   8,  //FIFO 深度
                ADDR_DEPTH  =   $clog2(FIFO_DEPTH) //根据FIFO深度计算地址位宽,此处为3。
)
(
    //reset signals
    input   wire                        wr_rst_n_i  ,
    input   wire                        rd_rst_n_i  ,
    
    //write interface
    input   wire                        wr_clk_i    ,
    input   wire                        wr_en_i     ,
    input   wire    [DATA_WIDTH-1:0]    wr_data_i   ,
    
    //read interface
    input   wire                        rd_clk_i    ,
    input   wire                        rd_en_i     ,
    output  reg     [DATA_WIDTH-1:0]    rd_data_o   ,
    
    //flag signals
    output  wire                        fifo_full_o ,
    output  wire                        fifo_empty_o
);

//memary
reg     [DATA_WIDTH-1:0]    fifo_buffer [FIFO_DEPTH-1:0];

//memary addr
wire    [ADDR_DEPTH-1:0]    wr_addr;    //真实写地址,作为写ram地址
wire    [ADDR_DEPTH-1:0]    rd_addr;    //真实读地址,作为读ram地址

//write poiter and write poiter of gray and sync
reg     [ADDR_DEPTH:0]  wr_ptr          ;   //写指针,二进制
wire    [ADDR_DEPTH:0]  gray_wr_ptr     ;   //写指针,格雷码
reg     [ADDR_DEPTH:0]  gray_wr_ptr_d0  ;   //写指针在读时钟域下同步一拍
reg     [ADDR_DEPTH:0]  gray_wr_ptr_d1  ;   //写指针在读时钟域下同步二拍

//read poiter and read poiter of gray and sync
reg     [ADDR_DEPTH:0]  rd_ptr          ;   //读指针,二进制
wire    [ADDR_DEPTH:0]  gray_rd_ptr     ;   //读指针,格雷码
reg     [ADDR_DEPTH:0]  gray_rd_ptr_d0  ;   //读指针在写时钟域下同步一拍
reg     [ADDR_DEPTH:0]  gray_rd_ptr_d1  ;   //读指针在写时钟域下同步二拍

//---write poiter and bin2gray---//
always@(posedge wr_clk_i or negedge wr_rst_n_i)begin 
    if(~wr_rst_n_i)begin 
        wr_ptr <= 'b0;
    end else if(wr_en_i && !fifo_full_o)begin   //写使能有效,且非满
        wr_ptr <= wr_ptr + 1'b1;
    end 
end 

assign gray_wr_ptr = wr_ptr ^ (wr_ptr>>1);

//---gray_wr_ptr sync to read clk domain---//
always@(posedge rd_clk_i or negedge rd_rst_n_i)begin 
    if(~rd_rst_n_i)begin 
        gray_wr_ptr_d0 <= 'b0;              //寄存1拍
        gray_wr_ptr_d1 <= 'b0;              //寄存2拍
    end else begin 
        gray_wr_ptr_d0 <= gray_wr_ptr   ;   //寄存1拍
        gray_wr_ptr_d1 <= gray_wr_ptr_d0;   //寄存2拍
    end 
end 

//---read poiter and bin2gray---//
always@(posedge rd_clk_i or negedge rd_rst_n_i)begin 
    if(~rd_rst_n_i)begin 
        rd_ptr <= 'b0;
    end else if(rd_en_i && !fifo_empty_o)begin  //读使能有效,且非空
        rd_ptr <= rd_ptr + 1'b1;
    end 
end 

assign gray_rd_ptr = rd_ptr ^ (rd_ptr>>1);

//---gray_rd_ptr sync to write clk domain---//
always@(posedge wr_clk_i or negedge wr_rst_n_i)begin 
    if(~wr_rst_n_i)begin 
        gray_rd_ptr_d0 <= 'b0;              //寄存1拍
        gray_rd_ptr_d1 <= 'b0;              //寄存2拍
    end else begin                          
        gray_rd_ptr_d0 <= gray_rd_ptr   ;   //寄存1拍
        gray_rd_ptr_d1 <= gray_rd_ptr_d0;   //寄存2拍
    end 
end 

//---full flag and empty flag---//
//当高位相反且其他位相等时,写指针超过读指针一圈,FIFO被写满
//同步后的读指针格雷码高两位取反,再拼接上余下位
assign fifo_full_o = (gray_wr_ptr == {~gray_rd_ptr_d1[ADDR_DEPTH], ~gray_rd_ptr_d1[ADDR_DEPTH-1], gray_rd_ptr_d1[ADDR_DEPTH-2:0]})
                       ? 1'b1 : 1'b0;
                      
//当所有位相等时,读指针追到到了写指针,FIFO被读空                     
assign fifo_empty_o= (gray_rd_ptr == gray_wr_ptr_d1) ? 1'b1 : 1'b0;

//---write addr and read addr---//
assign wr_addr = wr_ptr[ADDR_DEPTH-1:0];    //写RAM地址等于写指针的低 ADDR_DEPTH 位(去除最高位)
assign rd_addr = rd_ptr[ADDR_DEPTH-1:0];    //读RAM地址等于读指针的低 ADDR_DEPTH 位(去除最高位)

//---write operation---//
integer  i;

always@(posedge wr_clk_i or negedge wr_rst_n_i)begin 
    if(~wr_rst_n_i)begin 
        for(i=0; i<FIFO_DEPTH; i=i+1)begin 
            fifo_buffer[i] <= 'd0;
        end 
    end else if(wr_en_i && !fifo_full_o)begin   //写使能有效,且非满
        fifo_buffer[wr_addr] <= wr_data_i;
    end 
end 

//---read operation---//
always@(posedge rd_clk_i or negedge rd_rst_n_i)begin 
    if(~rd_rst_n_i)begin 
        rd_data_o <= {(DATA_WIDTH){1'b0}};
    end else if(rd_en_i && !fifo_empty_o)begin  //读使能有效,且非空
        rd_data_o <= fifo_buffer[rd_addr];
    end 
end      

endmodule


 

4.2 测试代码(testbench)

`timescale 1ns/1ns	//时间单位/精度
 
//----------<模块及端口声明>-------//
module tb_async_fifo();
 
parameter   DATA_WIDTH = 8  ;		//FIFO位宽
parameter   FIFO_DEPTH = 8 ;		//FIFO深度

parameter   TEST_TIME = 300;        //随机测试最大次数
 
 
reg                     wr_rst_n_i  ;
reg                     rd_rst_n_i  ;
                                    
reg                     wr_clk_i    ;
reg                     wr_en_i     ;
reg  [DATA_WIDTH-1:0]   wr_data_i   ;
                                    
reg                     rd_clk_i    ;
reg                     rd_en_i     ;
wire  [DATA_WIDTH-1:0]  rd_data_o   ;
                                                                       
wire                    fifo_full_o ;
wire                    fifo_empty_o;
 
integer i;

 
//------------<设置时钟>-------------------//
always #10 rd_clk_i = ~rd_clk_i;			//读时钟周期20ns
always #20 wr_clk_i = ~wr_clk_i;			//写时钟周期40ns
 
//------------<设置初始测试条件>----------//
initial begin
	rd_clk_i = 1'b0;			    //初始时钟为0
	wr_clk_i = 1'b0;				//初始时钟为0
	wr_rst_n_i <= 1'b0;				//初始复位
	rd_rst_n_i <= 1'b0;				//初始复位
	wr_en_i <= 1'b0;
	rd_en_i <= 1'b0;	
	wr_data_i <= 'd0;
	#5
	wr_rst_n_i <= 1'b1;				
	rd_rst_n_i <= 1'b1;	
    
//-----------固定测试-------------//    
//重复8次写操作,让FIFO写满 	
	repeat(8) begin
		@(negedge wr_clk_i)begin		
			wr_en_i <= 1'b1;
			wr_data_i <= $random;	//生成8位随机数
		end
	end
//拉低写使能	
	@(negedge wr_clk_i)	wr_en_i <= 1'b0;
	
//重复8次读操作,让FIFO读空 	
	repeat(8) begin
		@(negedge rd_clk_i)
        rd_en_i <= 1'd1;		
	end
//拉低读使能
	@(negedge rd_clk_i)rd_en_i <= 1'd0;	   
	
//重复4次写操作,写入4个随机数据	
	repeat(4) begin
		@(negedge wr_clk_i)begin		
			wr_en_i <= 1'b1;
			wr_data_i <= $random;	//生成8位随机数
		end
	end
//持续同时对FIFO读
	@(negedge rd_clk_i)rd_en_i <= 1'b1;
//持续同时对FIFO写,写入数据为随机数据	
	repeat(100) begin
		@(negedge wr_clk_i)begin		
			wr_en_i <= 1'b1;
			wr_data_i <= $random;	//生成8位随机数
		end
	end	
// 拉低读写使能   
    @(negedge wr_clk_i)wr_en_i <= 1'b0;
    @(negedge rd_clk_i)rd_en_i <= 1'b0;
    
//等待5个写时钟周期
    repeat(5) @(posedge wr_clk_i);
    
//--------------随机测试---------------//   
    for(i=0; i<TEST_TIME; i=i+1)begin
        if(i<100) begin 
            @(negedge wr_clk_i) ;
                wr_en_i <= $random() % 2;
                wr_data_i <= $random();
            @(negedge rd_clk_i);
                rd_en_i <= 1;         
        end 
        if(i<200) begin 
            @(negedge wr_clk_i) ;
                wr_en_i <= 1;
                wr_data_i <= $random();
            @(negedge rd_clk_i);
                rd_en_i <= $random() % 2;
            
        end 
        else if(i<TEST_TIME)begin
            @(negedge wr_clk_i) ;
                wr_en_i <= $random() % 2;
                wr_data_i <= $random();
            @(negedge rd_clk_i);
                rd_en_i <= $random() % 2;         
        end      
    end 

    repeat(5) @(posedge wr_clk_i);
    $finish();
end

//------------<例化被测试模块>-----------//
async_fifo
#(
	.DATA_WIDTH	(DATA_WIDTH),			//FIFO位宽
    .FIFO_DEPTH	(FIFO_DEPTH)			//FIFO深度
)
async_fifo_inst(
	.wr_rst_n_i     (wr_rst_n_i  ),  
	.rd_rst_n_i     (rd_rst_n_i  ),
                     
	.wr_clk_i       (wr_clk_i    ),          
	.wr_en_i        (wr_en_i     ),
	.wr_data_i      (wr_data_i   ),
	                 
	.rd_clk_i    	(rd_clk_i    ),
	.rd_en_i        (rd_en_i     ),
    .rd_data_o      (rd_data_o   ),
                     
    .fifo_full_o    (fifo_full_o ),
    .fifo_empty_o   (fifo_empty_o)
);
 
endmodule

SV小项目-异步FIFO是一个简单的项目,旨在让学生理解FIFO缓存的原理和实现方式。 FIFO缓存是一种常用的数据存储方式,可以用于解决数据传输时的不匹配问题。在异步FIFO中,数据的写入和读取是异步的,意味着数据可以在任何时候写入或读取。这种异步的方式可以增加FIFO的灵活性,并且允许数据的写入和读取在不同的时钟域上应用。 这个小项目的主要目标是实现一个基于Verilog的异步FIFO模块,包括以下功能: 1. 内部缓存的储存和检索 2. 空和满指示器的生成 3. 数据的写入和读取 对于写入操作,当FIFO未满时,它将数据存储在内部缓存中,并更新其指针以指向下一个空位置。当缓存已满时,写入操作将被忽略,并且FIFO满指示器将被设置为高电平。同样,读取操作从内部缓存中检索数据,并将其指针更新以指向下一个位置。当缓存为空时,读操作将被忽略,并且FIFO空指示器将被设置为高电平。 在设计过程中,需要考虑解决的问题包括时序和异步信号的同步。时序问题可以通过FIFO指针和状态机解决,而异步信号可以通过信号同步器进行同步。此外,还需要考虑FIFO的读写顺序和存储器的尺寸,并确保FIFO模块的有效性和可靠性。 总之,通过实现SV异步FIFO项目,学生可以加深对FIFO缓存的理解,并学习基于Verilog的设计和实现。此外,学生还可以通过在项目中遇到的挑战来提高他们的编程和设计技能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值