异步fifo设计

363 阅读6分钟

异步fifo:

1 使用扩展地址位的方式来判断空满。

2 跨时钟域处理 读写信号时钟不同。

单位宽信号clk1 - clk2 。慢时钟域到快时钟域,两级触发器级联,慢到快。

快到慢,快时钟域传到慢时钟域不一定会被采集到的,通过握手信号,慢时钟域采集到的信号后反馈给快时钟域,然后结束信号本次传输。(通过握手信号)

或者通过将脉冲转化为跳变的电平,将电平信号传递到慢时钟域。

跨时钟域处理方法: 寄存器打两拍和格雷码。 格雷码只有一位不同,减少同步过程中的同步错误。

格雷码的作用就是即使在亚稳态进行读写指针 抽样, 也能进行正确的空满状态指示。

‘’‘’ 将写时钟域的写指针同步到读时钟域,将同步后的写指针和读时钟域的读指针比较 产生读空信号;

将读时钟域的读指针同步到写时钟域,将同步后的读指针与写时钟域的写指针进行比较产生写满信号。

异步fifo的写指针和读指针分属于不同的时钟域,这样指针在进行同步的过程中容易出错,比如0111 到1000 跳变时需要4位同时改变。 采用格雷码,相邻每位只有一位发生变化,这样在进行指针同步时候,就只会产生同步正确。 即使同步出错,出错的结果也是写指针的跳变保持不变。000 - 001. 对于读空判断,最多导致让空标志在fifo不是真正为空的时候产生,而不会出现读空的情形。 grey码保证的是在同步后的读写指针即使出错,也不影响fifo功能的正确性。 同步过程中的亚稳态不可能消除,只是不受其影响。

二进制转格雷码: 右移一位 和自己 做异或 a[1]>>1 ^ a[1] 其实就是最高位不变 其余位与前一位做异或。

格雷码转二进制;

3 多位宽数据 需要异步fifo来处理 单位宽 两级寄存器同步

异步fifo核心 :

1 双口ram

2 控制地址自增

3 二进制地址转化为格雷码地址

4 格雷码地址跨时钟域

5 对同步后的格雷码进行解码

格雷码转二进制码,格雷码最高位为二进制码最高位,二进制码次高位为 二进制码的高位和 格雷码的次高位异或。

6 full 与empty信号的产生

扩展一位地址位 高位相等则为空 ,不相等则为满。

// gray 转化模块 以四位地址数据为例(仅供参考)

   module gray(b_in,g_out);
       input [4:0]b_in;
       input [4:0]g_out;
       wire [4:0] g_out;
       
       // 将地址位扩展了一位   
       assign  g_out[4] = b_in[4];
       assign  g_out[3] = b_in[3];
       assign  g_out[2] = b_in[2]^b_in[3];
       assign  g_out[1] = b_in[1]^b_in[2];
       assign  g_out[0] = b_in[0]^b_in[1];
       
   endmodule

1 双口ram模块


module DPRAM #
	(
		parameter WIDTH = 16 ,	  //数据位宽			
		parameter DEPTH = 16 ,	  //数据深度				
		parameter ADDR  = 4 	  //地址宽度4位
 
	)
	(
		input		wrclk	,		// 写时钟
		input		rdclk	,		// 读时钟
		input		rd_rst_n,		// 读复位
                
		input		wr_en	,		// 写使能
		input		rd_en	,		// 读使能
                
		input	[WIDTH-1:0]	wr_data	,	// 写数据
		output	reg [WIDTH-1:0] rd_data	,	// 读数据
                
		input	[ADDR-1:0]  wr_addr	,	// 写地址
		input	[ADDR-1:0]  rd_addr		// 读地址
    );
 
 
	reg [WIDTH-1:0] DPRAM [DEPTH-1:0];            // reg 类型数组
 
 
	// RAM写数据
	always @(posedge wrclk) begin
            if (wr_en)
                DPRAM[wr_addr] <= wr_data;
	end
 
 
	// RAM读数据
	always @(posedge rdclk or negedge rd_rst_n) begin
            if(!rd_rst_n)
		rd_data <= 'b0;
            else if (rd_en)
		rd_data <= DPRAM[rd_addr];
	end
endmodule

2 控制地址自增

	// 二进制写地址递增
	always @(posedge wrclk or negedge wr_rst_n) begin
		if (!wr_rst_n) begin
			wr_bin <= 'b0;
		end
                
		else if ( wr_en == 1'b1 && wr_full == 1'b0 ) begin
			wr_bin <= wr_bin + 1'b1;
		end
                
		else begin
			wr_bin <= wr_bin;
		end
	end
 
 
        // 二进制读地址递增
	always @(posedge rdclk or negedge rd_rst_n) begin
		if (!rd_rst_n) begin
			rd_bin <= 'b0;
		end
                
		else if ( rd_en == 1'b1 && rd_empty == 1'b0 ) begin
			rd_bin <= rd_bin + 1'b1;
		end
                
		else begin
			rd_bin <= rd_bin;
		end
	end

3 二进制地址转化为格雷码地址 最高位不变 其余位与前一位做异或,最后拼接。

        // 写地址时二进制转格雷码
	always @(posedge wrclk or negedge wr_rst_n) begin
		if (!wr_rst_n) begin
			wr_gray <= 'b0;
		end
		else begin
			wr_gray <= { wr_bin[PTR], wr_bin[PTR:1] ^ wr_bin[PTR-1:0] };
		end
	end
 
 
        // 读地址时二进制转格雷码
	always @(posedge rdclk or negedge rd_rst_n) begin
		if (!rd_rst_n) begin
			rd_gray <= 'b0;
		end
		else begin
			rd_gray <= { rd_bin[PTR], rd_bin[PTR:1] ^ rd_bin[PTR-1:0] };
		end
	end

4 格雷码地址跨时钟域

`` // 读地址同步到写时钟域 打两拍 rd_gray_ff1 延迟rd_gray一拍,rd_gray_ff2延迟rd_gray两拍。 always @(posedge wrclk or posedge wr_rst_n) begin if(!wr_rst_n) begin rd_gray_ff1 <= 'b0; rd_gray_ff2 <= 'b0; end else begin rd_gray_ff1 <= rd_gray; rd_gray_ff2 <= rd_gray_ff1; end end

 // 写地址同步到读时钟域 打两拍,同理。
always @(posedge rdclk or posedge rd_rst_n) begin
	if(!rd_rst_n) begin
		wr_gray_ff1 <= 'b0;
		wr_gray_ff2 <= 'b0;
	end
	else begin
		wr_gray_ff1 <= wr_gray;
		wr_gray_ff2 <= wr_gray_ff1;
	end
end



5 对同步后的格雷码进行解码


   //解格雷码的循环变量
   
   integer i ;
   integer j ;

    // 写地址,解码
always @(*) begin
	wr_bin_rd[PTR] = wr_gray_ff2[PTR];    //延迟两拍  即同步后的写地址。
	for ( j=PTR-1; j>=0; j=j-1 )
                wr_bin_rd[j] = wr_bin_rd[j+1] ^ wr_gray_ff2[j]; // 二进制码次高位为 高位二进制码和格雷码的异或。
end



// 读地址, 解码
always @(*) begin
	rd_bin_wr[PTR] = rd_gray_ff2[PTR];
	for ( i=PTR-1; i>=0; i=i-1 )
		rd_bin_wr[i] = rd_bin_wr[i+1] ^ rd_gray_ff2[i];
end

6 full 与empty信号的产生

        reg [PTR:0] rd_bin_wr;		// 写时钟域 读地址
        reg [PTR:0] wr_bin_rd;		// 读时钟域 写地址
        reg [PTR:0] rd_bin;		// 二进制读地址
        reg [PTR:0] wr_bin;		// 二进制写地址


        //写满信号
	always @(*) begin
            // 判断写地址 和 写时钟域的的读地址。 最高位不同,且地址相同为满。
            // 写满为写指针超过读指针一个存储深度 并处于同一位置。
            if( (wr_bin[PTR] != rd_bin_wr[PTR]) && (wr_bin[PTR-1:0] == rd_bin_wr[PTR-1:0]) ) 
                begin
                    wr_full = 1'b1;
		end
            else 
                begin
                    wr_full = 1'b0;
		end
	end
 
 
        // 读空信号    
	always @(*) begin
            //  最高位相同,为空。   读指针和写指针一致,说明读空了。
            if( wr_bin_rd == rd_bin )
                rd_empty = 1'b1;
            else
		rd_empty = 1'b0;
	end

fifo模块:


`timescale 1ns / 1ns
module FIFO#
	(
	parameter WIDTH = 16,	// 数据位宽
	parameter PTR   = 4,  //数据深度  4位
	)
	(
		// 写信号
		input	wrclk,		         // 写时钟
		input	wr_rst_n,		// 写指针复位
		input	[WIDTH-1:0]wr_data,	// 写数据总线
		input	wr_en,		// 写使能
		output  reg	wr_full	,	// 写满标志
 
		//读信号
		input	rdclk,		// 读时钟
		input	rd_rst_n,	// 读指针复位
		input	rd_en,		// 读使能
		output	[WIDTH-1:0]	rd_data	,// 读数据输出
		output	reg	rd_empty        // 读空标志
    );
 
 
	// 写时钟域信号定义
	reg [PTR:0] wr_bin;		// 二进制写地址
	reg [PTR:0] wr_gray	;	// 格雷码写地址
	reg [PTR:0] rd_gray_ff1 ;	// 格雷码读地址同步寄存器1
	reg [PTR:0] rd_gray_ff2 ;	// 格雷码读地址同步寄存器2
        
	reg [PTR:0] rd_bin_wr	;	// 同步到写时钟域的二进制读地址
 
 
	// 读时钟域信号定义
	reg [PTR:0] rd_bin;		// 二进制读地址
	reg [PTR:0] rd_gray;		// 格雷码读地址
	reg [PTR:0] wr_gray_ff1;	// 格雷码写地址同步寄存器1
	reg [PTR:0] wr_gray_ff2;	// 格雷码写地址同步寄存器2
        
	reg [PTR:0] wr_bin_rd;		// 同步到读时钟域的二进制写地址
 
 
	// 解格雷码电路循环变量
	integer i ;
	integer j ;
 
 
	// DPRAM控制信号
	wire  		         dpram_wr_en;	        // DPRAM写使能
	wire [PTR-1:0]		dpram_wr_addr;		// DPRAM写地址
	wire [WIDTH-1:0] 	dpram_wr_data;		// DPRAM写数据
        
	wire  			dpram_rd_en;		// DPRAM读使能
	wire [PTR-1:0]		dpram_rd_addr;		// DPRAM读地址
	wire [WIDTH-1:0] 	dpram_rd_data;		// DPRAM读数据
 
 
 
	
		// 二进制写地址递增
	always @(posedge wrclk or negedge wr_rst_n) begin
		if (!wr_rst_n) begin
			wr_bin <= 'b0;
		end
                
		else if ( wr_en == 1'b1 && wr_full == 1'b0 ) begin
			wr_bin <= wr_bin + 1'b1;
		end
                
		else begin
			wr_bin <= wr_bin;
		end
	end
 
 
        // 二进制读地址递增
	always @(posedge rdclk or negedge rd_rst_n) begin
		if (!rd_rst_n) begin
			rd_bin <= 'b0;
		end
                
		else if ( rd_en == 1'b1 && rd_empty == 1'b0 ) begin
			rd_bin <= rd_bin + 1'b1;
		end
                
		else begin
			rd_bin <= rd_bin;
		end
	end
 
        // 写地址时二进制转格雷码
	always @(posedge wrclk or negedge wr_rst_n) begin
		if (!wr_rst_n) begin
			wr_gray <= 'b0;
		end
		else begin
			wr_gray <= { wr_bin[PTR], wr_bin[PTR:1] ^ wr_bin[PTR-1:0] };
		end
	end
 
 
        // 读地址时二进制转格雷码
	always @(posedge rdclk or negedge rd_rst_n) begin
		if (!rd_rst_n) begin
			rd_gray <= 'b0;
		end
		else begin
			rd_gray <= { rd_bin[PTR], rd_bin[PTR:1] ^ rd_bin[PTR-1:0] };
		end
	end
        
        
        
        
        
      // 读地址同步到写时钟域  打两拍  rd_gray_ff1 延迟rd_gray一拍,rd_gray_ff2延迟rd_gray两拍。
	always @(posedge wrclk or posedge wr_rst_n) begin
		if(!wr_rst_n) begin
			rd_gray_ff1 <= 'b0;
			rd_gray_ff2 <= 'b0;
		end
		else begin
			rd_gray_ff1 <= rd_gray;
			rd_gray_ff2 <= rd_gray_ff1;
		end
	end
 
 
     // 写地址同步到读时钟域 打两拍,同理。
	always @(posedge rdclk or posedge rd_rst_n) begin
		if(!rd_rst_n) begin
			wr_gray_ff1 <= 'b0;
			wr_gray_ff2 <= 'b0;
		end
		else begin
			wr_gray_ff1 <= wr_gray;
			wr_gray_ff2 <= wr_gray_ff1;
		end
	end
        
        
        
        
        // 写地址,解码
	always @(*) begin
		wr_bin_rd[PTR] = wr_gray_ff2[PTR];    //延迟两拍  即同步后的写地址。
		for ( j=PTR-1; j>=0; j=j-1 )
                    wr_bin_rd[j] = wr_bin_rd[j+1] ^ wr_gray_ff2[j]; // 二进制码次高位为 高位二进制码和格雷码的异或。
	end
 
 
 
	// 读地址, 解码
	always @(*) begin
		rd_bin_wr[PTR] = rd_gray_ff2[PTR];
		for ( i=PTR-1; i>=0; i=i-1 )
			rd_bin_wr[i] = rd_bin_wr[i+1] ^ rd_gray_ff2[i];
	end
 
 
 
 
 

        //写满信号
	always @(*) begin
            // 判断写地址 和 写时钟域的的读地址。 最高位不同,且地址相同为满。
            // 写满为写指针超过读指针一个存储深度 并处于同一位置。
            if( (wr_bin[PTR] != rd_bin_wr[PTR]) && (wr_bin[PTR-1:0] == rd_bin_wr[PTR-1:0]) ) 
                begin
                    wr_full = 1'b1;
		end
            else 
                begin
                    wr_full = 1'b0;
		end
	end
 
 
        // 读空信号    
	always @(*) begin
            //  最高位相同,为空。   读指针和写指针一致,说明读空了。
            if( wr_bin_rd == rd_bin )
                rd_empty = 1'b1;
            else
		rd_empty = 1'b0;
	end
 
 
	// 双口RAM    例化
	
	
	  DPRAM   U_DPRAM #
                (
		.WIDTH (16) ,	  //数据位宽			
		.DEPTH (16) ,	  //数据深度				
		.ADDR (4) 	  //地址宽度4位
 
                )
		(
			.wrclk		(wrclk),
			.rdclk		(rdclk),
			.rd_rst_n      (rd_rst_n),
			.wr_en 		(dpram_wr_en),
			.rd_en 		(dpram_rd_en),
			.wr_data 	(dpram_wr_data),
			.rd_data 	(dpram_rd_data),
			.wr_addr 	(dpram_wr_addr),
			.rd_addr 	(dpram_rd_addr)
		);
 
 
	// DPRAM读写控制信号
	assign dpram_wr_en   = ( wr_en == 1'b1 && wr_full == 1'b0 )? 1'b1 : 1'b0;
	assign dpram_wr_data = wr_data;
        
	assign dpram_wr_addr = wr_bin[PTR-1:0];
 
	assign dpram_rd_en   = ( rd_en == 1'b1 && rd_empty == 1'b0 )? 1'b1 : 1'b0;
	assign rd_data = dpram_rd_data;
        
	assign dpram_rd_addr = rd_bin[PTR-1:0];
 
 
endmodule