同步和异步FIFO的实现

整个博客写到最后就是个改错本,代码都是没问题的,可以正常仿真

1. FIFO介绍

FIFO-IP调用.

2. 同步FIFO

读写用的是一个是时钟

模块实现

模块框图
在这里插入图片描述
端口介绍:

  • sys_clk:50MHz系统时钟
  • sys_rst_n:系统复位,低有效
  • wr_en:写使能信号,高有效
  • rd_en:读使能信号,高有效
  • data_in:写入数据
  • data_out:读出数据
  • full:FIFO满信号,高有效
  • empty:FIFO空信号,高有效
  • count:FIFO数据计数器,当读写指针相等时,可能是写满,也可能是读空,根据计数器的值可以判断空满

功能描述:

  • 以4*8大小的FIFO为例,数据位宽4bit,地址位宽3bit,[4-1 : 0] mem [{3{1’b1}} : 0] // 寻址范围为0~3’b111,即0 ~7
  • 复位,将mem初始化为0
  • 写数据,写使能并且未满,写入数据,计数器加1
  • 读数据,读使能并且未空,读出数据,计数器减1
  • 读写同时进行,只进行数据更新,计数器不变

代码:

// fifo_4x8,地址位宽为3bit
// 计数器工作范围0~7
// 当计数器为8,证明已经满了,为了计数器能到8,需要4bit
module fifo_sync
#(
	parameter	data_width	=	4,
				addr_width	=	3
)
(
	input	wire						sys_clk		,
	input	wire						sys_rst_n	,
	input	wire						wr_en		,
	input	wire						rd_en		,
	input	wire	[data_width-1 : 0]	data_in		,
	
	output	reg		[data_width-1 : 0]	data_out	,
	output	wire						full		,
	output	wire						empty		,
	output	reg		[addr_width   : 0]	count
);
	reg		[addr_width-1 : 0]	wr_addr	;	// 写地址,指向下一个要写的地址
	reg		[addr_width-1 : 0]	rd_addr	;	// 读地址,指向下一个要读的地址
	reg		[data_width-1 : 0]	mem	[{addr_width{1'b1}} : 0];	// 声明存储器
	integer	i;		// 用于初始化存储器
// mem初始化
	always @ (posedge sys_clk or negedge sys_rst_n)
		if (!sys_rst_n)	begin
			for (i = 0;	i <= {addr_width{1'b1}}; i = i + 1)	// 寻址范围0~3'b111,即0~7
				mem[i]	<=	{data_width{1'b0}};				// 数据初始化为4'd0000,即0x0
		end

// 写操作
	always @ (posedge sys_clk or negedge sys_rst_n)
		if (!sys_rst_n)
			wr_addr	<=	0;
		else	if (wr_en && (~full))		// 写使能 & 存储器未满
			begin
				mem[wr_addr]	<=	data_in;
				wr_addr			<=	wr_addr + 1'b1;
			end
		else
			wr_addr	<=	wr_addr;
			
// 读操作
	always @ (posedge sys_clk or negedge sys_rst_n)
		if (!sys_rst_n)
			begin
				data_out	<=	{data_width-1{1'b0}};		// 输出对应数据位宽个0
				rd_addr		<=	0;
			end
		else	if (rd_en && (~empty))		// 读使能 & 存储器未空
			begin
				data_out	<=	mem[rd_addr];
				rd_addr		<=	rd_addr + 1'b1;
			end
		else
			begin
				data_out	<=	data_out;
				rd_addr		<=	rd_addr;
			end
			
// count计数器,产生空满标志
	always @ (posedge sys_clk or negedge sys_rst_n)
		if (!sys_rst_n)
			count	<=	0;
		else	if (wr_en && (~full))	// 只进行写操作
			count	<=	count + 1'b1;
		else	if (rd_en && (~empty))	// 只进行读操作
			count	<=	count - 1'b1;
		else
			count	<=	count;
	
	assign	empty	=	(count == 0);
	assign	full	=	(count == {addr_width{1'b1}} + 1'b1);
	
endmodule
// fifo_4x8
// 数据位宽4bit,地址位宽为3bit
module sim_fifo_sync #(
	parameter	data_width	=	4,
				addr_width	=	3
)();
	reg				       		sys_clk		;
	reg			     			sys_rst_n	;
	reg		  				    wr_en		;
	reg			 			     rd_en		;
	reg    	[data_width-1 : 0]	data_in		;
	
	wire	[data_width-1 : 0]	data_out	;
	wire						full		;
	wire						empty		;
	wire	[addr_width   : 0]	count		;
	
	integer    i;
	
	// 实例化fifo_sync
	fifo_sync
	#(
		.data_width   (data_width)	,
		.addr_width   (addr_width)  
	)
	fifo_sync_inst 
	(
		.sys_clk	(sys_clk)	,
		.sys_rst_n	(sys_rst_n)	,
		.wr_en		(wr_en)		,
		.rd_en		(rd_en)		,
		.data_in	(data_in)	,
		
		.data_out	(data_out)	,
		.full		(full)		,
		.empty		(empty)		,
		.count		(count)
	);
	
	
	// 50MHz
	localparam	clk_period_50M	=	20;
	always # (clk_period_50M /2 )	sys_clk = ~sys_clk;
	
	initial	begin
		sys_clk		=	0;
		sys_rst_n	=	0;
		wr_en		=	0;
		rd_en		=	0;
		data_in		=	0;
		#20
		sys_rst_n	=	1;	// 复位结束
		
		wr_en	=	1;		// 写5个数据
		#100
		wr_en	=	0;		// 不写了
		rd_en	=	1;		// 读5个数据
		#100
		rd_en	=	0;		// 不读了
		
		#100
		wr_en	=	1;		// 开始写
		#100
		rd_en	=	1;		// 一边写一边读
		#100
		wr_en	=	0;
		rd_en	=	0;
		
		#100
		wr_en	=	1;
		#200
		rd_en	=	1;
		
	end
	
	initial    begin
	   for (i = 0; i < 50; i=i+1)
	       #20 data_in = {$random} % 16;
	end
	
endmodule

在这里插入图片描述

读写分开进行的时候没有问题,当边读边写的时候就有问题了
在这里插入图片描述
在这里插入图片描述
边读边写的时候期待计数器不变,只进行FIFO数据的更新,而对比波形,可以看到边读边写的时候优先进行了写操作,count模块里面用了if-else结构,本身就有优先级,会先判断是否进行写操作
修改一下count计数器代码

	always @ (posedge sys_clk or negedge sys_rst_n)
		if (!sys_rst_n)
			count	<=	0;
		else	if ((wr_en && (~full)) && (rd_en && (~empty)))	// 读写操作同时进行
			count	<=	count;
		else	if (wr_en && (~full))	// 只进行写操作
			count	<=	count + 1'b1;
		else	if (rd_en && (~empty))	// 只进行读操作
			count	<=	count - 1'b1;
		else
			count	<=	count;

在这里插入图片描述

下板验证

  • 验证思路:
    在这里插入图片描述

所有模块的工作时钟都是50MHz,EGO1系统时钟是100MHz,在顶层模块进行二分频。
对读写控制按键先进行消抖处理,按键有效时仅输出一个时钟周期的高电平flag信号;
data_in要写入的4bit数据由开关确定;
将读出的数据data_out、写数据指针(地址下标)、读数据指针(地址下标)、数据个数计数器count用数码管进行动态显示

  • 下板遇到的问题:

    • 初始化操作和写操作分成两个always块进行:复位无效后,写数据写不进去,但其实data_in已经送过去了,读出来的数据永远都是初始化的值。(下板验证后的结果,不太懂)。或者直接注释掉初始化的always块,直接写,这样就能正常写入数据,跟下面的合并是一个结果
    • 初始化操作和写操作合并在一个always块进行:数据可以正常由开关写入FIFO,读出来的数据也是后来写入的
    • 复位后,默认会写入一个数据,这个数据就是当前开关控制的值,不太知道为什么会默认写,但确实发生了
      除了第一个数据有一点点不受控制,其他情况都是正常写入、正常判断空满
      在这里插入图片描述
  • key_filter消抖模块代码

    module key_filter
    #(
        parameter CNT_MAX = 20'd999_999 //计数器计数最大值
    )
    (
        input   wire    sys_clk     ,   //系统时钟50Mhz
        input   wire    sys_rst_n   ,   //全局复位
        input   wire    key_in      ,   //按键输入信号
    
        output  reg     key_flag        //key_flag为1时表示消抖后检测到按键被按下
                                        //key_flag为0时表示没有检测到按键被按下
    );
    
    //reg   define
    reg     [19:0]  cnt_20ms    ;   //计数器
    
    //cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            cnt_20ms <= 20'b0;
        else    if(key_in == 1'b1)
            cnt_20ms <= 20'b0;
        else    if(cnt_20ms == CNT_MAX && key_in == 1'b0)
            cnt_20ms <= cnt_20ms;
        else
            cnt_20ms <= cnt_20ms + 1'b1;
    
    //key_flag:当计数满20ms后产生按键有效标志位
    //且key_flag在999_999时拉高,维持一个时钟的高电平
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            key_flag <= 1'b0;
        else    if(cnt_20ms == CNT_MAX - 1'b1)
            key_flag <= 1'b1;
        else
            key_flag <= 1'b0;
    
    endmodule
    
    
  • fifo_sync,在上面代码的基础上修改一下初始化

    module fifo_sync
    #(
    	parameter	data_width	=	4,
    				addr_width	=	3
    )
    (
    	input	wire						sys_clk		,
    	input	wire						sys_rst_n	,
    	input	wire						wr_en		,
    	input	wire						rd_en		,
    	input	wire	[data_width-1 : 0]	data_in		,
    	
    	output	reg		[data_width-1 : 0]	data_out	,
    	output	wire						full		,
    	output	wire						empty		,
    	output	reg		[addr_width   : 0]	count		,
    
    	output	reg     [addr_width-1 : 0]	wr_addr		,
    	output	reg     [addr_width-1 : 0]	rd_addr	
    );
    //	reg		[addr_width-1 : 0]	wr_addr	;	// 写地址
    //	reg		[addr_width-1 : 0]	rd_addr	;	// 读地址
    	reg		[data_width-1 : 0]	mem	[{addr_width{1'b1}} : 0];	// 声明存储器
    	integer	i;		// 用于初始化存储器
    // mem初始化
    //	always @ (posedge sys_clk or negedge sys_rst_n)
    //		if (!sys_rst_n)	begin
    //			for (i = 0;	i <= {addr_width{1'b1}}; i = i + 1)	// 寻址范围0~3'b111,即0~7
    //				//mem[i]	<=	{data_width{1'b1}};				// 数据初始化为4'd0000,即0x0
    //				mem[i]	<=	4'd8;	
    //		end
    
    // 写操作
    //	always @ (posedge sys_clk or negedge sys_rst_n)
    //		if (!sys_rst_n)
    //			wr_addr	<=	0;
    //		else	if (wr_en && (~full))		// 写使能 & 存储器未满
    //			begin
    //				mem[wr_addr]	<=	data_in;
    //				wr_addr			<=	wr_addr + 1'b1;
    //			end
    //		else
    //			wr_addr	<=	wr_addr;
    
    // 初始化和写操作
    	always @ (posedge sys_clk or negedge sys_rst_n)
    		if (!sys_rst_n)
    			begin
    			    wr_addr	<=	0;
    				for (i = 0;	i <= {addr_width{1'b1}}; i = i + 1)	// 寻址范围0~3'b111,即0~7
    					mem[i]	<=	{data_width{1'b0}};				// 数据初始化为4'd0000,即0x0
    			end
    		else	if (wr_en && (~full))		// 写使能 & 存储器未满
    			begin
    				mem[wr_addr]	<=	data_in;
    				wr_addr			<=	wr_addr + 1'b1;
    			end
    		else
    			wr_addr	<=	wr_addr;	
    
    // 读操作
    	always @ (posedge sys_clk or negedge sys_rst_n)
    		if (!sys_rst_n)
    			begin
    				data_out	<=	{data_width-1{1'b0}};		// 输出对应数据位宽个0
    				rd_addr		<=	0;
    			end
    		else	if (rd_en && (~empty))		// 读使能 & 存储器未空
    			begin
    				data_out	<=	mem[rd_addr];
    				rd_addr		<=	rd_addr + 1'b1;
    			end
    		else
    			begin
    				data_out	<=	data_out;
    				rd_addr		<=	rd_addr;
    			end
    			
    // count计数器,产生空满标志
    	always @ (posedge sys_clk or negedge sys_rst_n)
    		if (!sys_rst_n)
    			count	<=	0;
    		else	if ((wr_en && (~full)) && (rd_en && (~empty)))	// 读写操作同时进行
    			count	<=	count;
    		else	if (wr_en && (~full))	// 只进行写操作
    			count	<=	count + 1'b1;
    		else	if (rd_en && (~empty))	// 只进行读操作
    			count	<=	count - 1'b1;
    		else
    			count	<=	count;
    //	always @ (posedge sys_clk or negedge sys_rst_n)      // 读写同时进行就有小问题
    //		if (!sys_rst_n)
    //			count	<=	0;
    //		else	if (wr_en && (~full))	// 只进行写操作
    //			count	<=	count + 1'b1;
    //		else	if (rd_en && (~empty))	// 只进行读操作
    //			count	<=	count - 1'b1;
    //		else
    //			count	<=	count;
    	
    	assign	empty	=	(count == 0) ? 1'b1 : 1'b0;
    	assign	full	=	(count == {addr_width{1'b1}} + 1'b1) ? 1'b1 : 1'b0;
    	
    endmodule
    
  • 数码管动态显示
    链接: 数码管动态显示.

    module led_dynamic(
        output  reg    [7:0]    seg,
        output  reg    [3:0]    an,
        
        input   wire            sys_clk,
        input   wire            sys_rst_n,
        input   wire    [3:0]   in3, in2, in1, in0
        );
        
    	   parameter   _0 = ~8'hc0;
    	   parameter   _1 = ~8'hf9;
    	   parameter   _2 = ~8'ha4;
    	   parameter   _3 = ~8'hb0;
    	   parameter   _4 = ~8'h99;
    	   parameter   _5 = ~8'h92;
    	   parameter   _6 = ~8'h82;
    	   parameter   _7 = ~8'hf8;
    	   parameter   _8 = ~8'h80;
    	   parameter   _9 = ~8'h90;
    	   parameter   _a = ~8'h88;
    	   parameter   _b = ~8'h83;
    	   parameter   _c = ~8'hc6;
    	   parameter   _d = ~8'ha1;
    	   parameter   _e = ~8'h86;
    	   parameter   _f = ~8'h8e;
    	   parameter   _err = ~8'hcf;
    	   
    	   parameter   N = 18;
        
           
        reg     [N-1 : 0]  regN; 
        reg     [3:0]       hex_in;
        
        always @ (posedge sys_clk or posedge sys_rst_n)   begin
            if (sys_rst_n == 1'b0)    begin
                regN    <=  0;
            end else    begin
                regN    <=  regN + 1;
            end
        end
        
        always @ (*)    begin
            case (regN[N-1: N-2])
                2'b00:  begin
                    an  <=  4'b0001;
                    hex_in  <=  in0;
                end
                2'b01:  begin
                    an  <=  4'b0010;
                    hex_in  <=  in1;
                end
                2'b10:  begin
                    an  <=  4'b0100;
                    hex_in  <=  in2;
                end
                2'b11:  begin
                    an  <=  4'b1000;
                    hex_in  <=  in3;
                end
                default:    begin
                    an  <=  4'b1111;
                    hex_in  <=  in3;
                end
            endcase
        end
        
        always @ (*)    begin
            case (hex_in)
                4'h0:   seg <=  _0;
                4'h1:   seg <=  _1;
                4'h2:   seg <=  _2;
                4'h3:   seg <=  _3;
                4'h4:   seg <=  _4;
                4'h5:   seg <=  _5;
                4'h6:   seg <=  _6;
                4'h7:   seg <=  _7;
                4'h8:   seg <=  _8;
                4'h9:   seg <=  _9;
                4'ha:   seg <=  _a;
                4'hb:   seg <=  _b;
                4'hc:   seg <=  _c;
                4'hd:   seg <=  _d;
                4'he:   seg <=  _e;
                4'hf:   seg <=  _f;
                default:seg <=  _err;
            endcase
        end
                
    endmodule
    
  • 顶层模块

    // 4X8,数据位宽4bit,地址位宽3bit
    // 使用EGO1板子下板,系统时钟是100MHz,调用模块需要注意工作时钟的选择
    module top_fifo_sync
    #(
    	parameter	data_width	=	4,
    				addr_width	=	3
    )(
    	input	wire						sys_clk	,	// 100MHz
    	input	wire						sys_rst_n,
    	input	wire						key_wr,
    	input	wire						key_rd,
    	input	wire	[data_width-1 : 0]	data_in,
    	
    	output	wire	[7:0]				seg,
    	output	wire	[3:0]				an,
    	output	wire						full,
    	output	wire						empty
    );
    	
    	wire						wr_en	;
    	wire						rd_en	;
    	wire	[data_width-1 : 0]	data_out;
    	wire	[addr_width   : 0]	count	;
    	
    	wire	[addr_width-1 : 0]	wr_addr	;
    	wire	[addr_width-1 : 0]	rd_addr	;
    	reg		sys_clk_50M;
        always @ (posedge sys_clk or negedge sys_rst_n)
        	if (!sys_rst_n)
        		sys_clk_50M	=	0;
        	else	if (sys_clk == 1)
        		sys_clk_50M	=	~sys_clk_50M;
        	
        key_filter	key_filter_wr_inst (
        	.sys_clk	(sys_clk_50M),
        	.sys_rst_n	(sys_rst_n),
        	.key_in		(key_wr),
        	
        	.key_flag	(wr_en)
        );
        key_filter	key_filter_rd_inst (
        	.sys_clk	(sys_clk_50M),
        	.sys_rst_n	(sys_rst_n),
        	.key_in		(key_rd),
        	
        	.key_flag	(rd_en)
        );
        
        fifo_sync
        #(
    		.data_width   (data_width)	,
    		.addr_width   (addr_width)  
    	)
        fifo_sync_inst (
        	.sys_clk	(sys_clk_50M),
        	.sys_rst_n	(sys_rst_n),
        	.wr_en		(wr_en),
        	.rd_en		(rd_en),
        	.data_in	(data_in),
        	
        	.data_out	(data_out),
        	.full		(full),
        	.empty		(empty),
        	.count		(count),
        	.wr_addr	(wr_addr),
        	.rd_addr	(rd_addr)
        );
        
        led_dynamic	led_dynamic_inst (
        	.sys_clk	(sys_clk_50M),
        	.sys_rst_n	(sys_rst_n),
        	.in3		(data_out),
        	.in2		(wr_addr),
        	.in1		(rd_addr),
        	.in0		(count),
        	
        	.seg		(seg),
        	.an			(an)
        );
        
    endmodule
    
  • 约束文件

    set_property -dict {PACKAGE_PIN P17 IOSTANDARD LVCMOS33} [get_ports sys_clk ]
    set_property -dict {PACKAGE_PIN P15 IOSTANDARD LVCMOS33} [get_ports sys_rst_n  ]
    
    set_property -dict {PACKAGE_PIN V1  IOSTANDARD LVCMOS33} [get_ports {key_wr}]
    set_property -dict {PACKAGE_PIN R11 IOSTANDARD LVCMOS33} [get_ports {key_rd}]
    
    set_property -dict {PACKAGE_PIN R2 IOSTANDARD LVCMOS33} [get_ports {data_in[3]}]
    set_property -dict {PACKAGE_PIN M4 IOSTANDARD LVCMOS33} [get_ports {data_in[2]}]
    set_property -dict {PACKAGE_PIN N4 IOSTANDARD LVCMOS33} [get_ports {data_in[1]}]
    set_property -dict {PACKAGE_PIN R1 IOSTANDARD LVCMOS33} [get_ports {data_in[0]}]
    
    set_property -dict {PACKAGE_PIN G2 IOSTANDARD LVCMOS33} [get_ports {an[3]}]
    set_property -dict {PACKAGE_PIN C2 IOSTANDARD LVCMOS33} [get_ports {an[2]}]
    set_property -dict {PACKAGE_PIN C1 IOSTANDARD LVCMOS33} [get_ports {an[1]}]
    set_property -dict {PACKAGE_PIN H1 IOSTANDARD LVCMOS33} [get_ports {an[0]}]
    
    set_property -dict {PACKAGE_PIN B4 IOSTANDARD LVCMOS33} [get_ports {seg[0]}]
    set_property -dict {PACKAGE_PIN A4 IOSTANDARD LVCMOS33} [get_ports {seg[1]}]
    set_property -dict {PACKAGE_PIN A3 IOSTANDARD LVCMOS33} [get_ports {seg[2]}]
    set_property -dict {PACKAGE_PIN B1 IOSTANDARD LVCMOS33} [get_ports {seg[3]}]
    set_property -dict {PACKAGE_PIN A1 IOSTANDARD LVCMOS33} [get_ports {seg[4]}]
    set_property -dict {PACKAGE_PIN B3 IOSTANDARD LVCMOS33} [get_ports {seg[5]}]
    set_property -dict {PACKAGE_PIN B2 IOSTANDARD LVCMOS33} [get_ports {seg[6]}]
    set_property -dict {PACKAGE_PIN D5 IOSTANDARD LVCMOS33} [get_ports {seg[7]}]
    
    set_property -dict {PACKAGE_PIN F6 IOSTANDARD LVCMOS33} [get_ports {full}]
    set_property -dict {PACKAGE_PIN G4 IOSTANDARD LVCMOS33} [get_ports {empty}]
    

3. 异步FIFO

读写用的是不同的时钟

思路补充

补充:$clog2()系统函数使用

// 这里依旧以4X8大小的FIFO为例

// 在同步FIFO的例子中,给定数据位宽4bit,地址位宽3bit,然后寻址范围根据地址位宽来确定:[{addr_width{1'b1}} : 0]
parameter	data_width	=	4,
parameter	addr_width	=	3
reg		[data_width-1 : 0]	mem	[{addr_width{1'b1}} : 0];
reg		[addr_width-1 : 0]	wr_addr	;	// 写地址
reg		[addr_width-1 : 0]	rd_addr	;	// 读地址

// 如果使用$clog2()系统函数
// 给定数据位宽4bit,给定数据深度为8,2^3 = 8
parameter	data_width	=	4;
parameter	data_depth	=	8;
reg		[data_width-1 : 0]	mem	[data_depth : 0];
reg		[$clog(data_depth)-1 : 0]	wr_addr	;	// 写地址	[3-1 : 0]
reg		[$clog(data_depth)-1 : 0]	rd_addr	;	// 读地址	[3-1 : 0]

这里是补充跨时钟域的问题
参考这博客:
谈谈跨时钟域传输问题(CDC)——李锐博恩.
FPGA基础知识极简教程(4)从FIFO设计讲起之异步FIFO篇——李锐博恩.

补充:

  1. fast to slow: 快时钟信号保持时间短,慢时钟可能采样不到,这就需要对快时钟下的输入信号进行展宽,让它保持时间久一点,然后采样完了得到一个反馈信号,用于拉低展宽信号
  2. slow to fast: 快时钟一定能采样到慢时钟,慢时钟的信号至少会在快时钟下保持一个时钟周期
  3. 同步FIFO可以用计数器的方法判断空满,但是异步不可以,因为读写指针不在一个时钟域,计数器不能在这样的情况下计数,需要将读写指针同步一下

读写指针同步到一个时钟域:

  1. 判满:读指针同步到写时钟域
    • 读数据完成后读指针+1,将结果值转换为格雷码形式,寄存一拍,然后用写时钟两级同步,将同步过来的信号转换为二进制编码,寄存一拍与写指针比较
    • 当读指针转换为格雷码形式以及同步到写时钟域的过程中,读写数据可能都在进行,读写指针也可能都还在递增,等同步后的读写指针相等时,实际的读指针可能已经变了,这样的话还有空间没有写满(被读出)
  2. 判空:写指针同步到读时钟域
    • 写数据完成后写指针+1,将结果值转换为格雷码性质,寄存一拍,然后用读时钟两级同步,将同步过来的信号转换为二进制编码,寄存一拍与读指针比较
    • 当写指针转换为格雷码形式以及同步到写时钟域的过程中,读写数据可能都在进行,读写指针也可能都还在递增,等同步后的读写指针相等时,实际的写市镇可能已经变了,这样的话相当于多写了在这里插入图片描述

格雷码转换的应用:

  • 格雷码是一种安全码,因为相邻的格雷码只有一位不同,和二进制不同,二进制一般相邻的都有多位不同。格雷码在传输中,因为相邻只有一位不同,所以其误码率比二进制低得多。
    在同步时,出现亚稳态的概率也比二进制低。

    // 二进制转格雷码
    gray = binary ^ (binary >>> 1)
    
  • 格雷码在任意两个相邻的数之间转换时,只有1个bit发生了变化,所以它有效的避免了寄存器由一个数值到下一个数值时的不稳定态。并且由于格雷码中最大数与最小数之间也仅1个bit不同,因此通常又被称作循环二进制码或者反射二进制码。

  • 这里以深度为8的FIFO为例,实际地址宽度为[2:0],3bit就足够了,但是应用中宽度多加1位,定义位[3:0]。

    • 观察二进制数,宽度多一位对指针是没有影响的,当地址为3bit的时候,全1之后再加1,就变成全0;当地址为4bit的时候,低三位也是全1之后再加1,就变成全0。都是在3b’111~3’b000循环,不会出现溢出越界。
    • 观察格雷码,横着看一行,大小相差8的一对码值,高两位相反,其余位都相等。比如5和6,对应的格雷码为4’d0111和4’d1011。
    • 多出来的这一位,可以将0 ~ 7、8 ~ 15分成两个组(本来也只用0 ~ 7的地址位置),可以用格雷码的特性进行空满判断。
    1. 格雷码全部位相等,即为空。读写指针相等,并且在一个组,证明读指针追上了写指针,读空了
    2. 格雷码高两位相反,其余位相等,即为满。读写指针相等,分别在两个组,证明写指针追上了读指针,写满了
      在这里插入图片描述

草率的代码实现,以及好不容易出结果的仿真测试

full和empty信号分别用了时序逻辑和组合逻辑实现,最后的问题分析提到了

// fifo_8x32
module fifo_async
#(
	parameter	data_width	=	8,
				data_depth	=	32
)
(
	input	wire						wr_clk	,
	input	wire						wr_rst_n,
	input	wire						wr_en	,
	input	wire	[data_width-1 : 0]	data_in	,
	output	reg 						full	,
	
	input	wire						rd_clk	,
	input	wire						rd_rst_n,
	input	wire						rd_en	,
	output	reg 	[data_width-1 : 0]	data_out,
	output	wire						empty
);

// 定义FIFO	8x32
	reg		[data_width-1 : 0]	mem	[0 : data_depth-1];

// 定义寄存器
	reg		[$clog2(data_depth) : 0]	wr_addr	=	0	;	// 写指针	[5:0]
	reg		[$clog2(data_depth) : 0]	rd_addr	=	0	;	// 读指针	[5:0]

// 对比mem[{wr_addr[$clog2(data_depth)-1 : 0]}]和mem[wr_addr],后面的问题分析讲到这两种寻址方式的差异
//	wire	[data_width-1 : 0]	s1;
//	wire	[data_width-1 : 0]	s2;
//	assign	s1	=	mem[{wr_addr[$clog2(data_depth)-1 : 0]}];	// 可以正确读出FIFO的数据
//	assign	s2	=	mem[wr_addr];								// 读出的都是高阻态,证明该位置没有写入数据
	
// 写操作
	always @ (posedge wr_clk or negedge wr_rst_n)
		if (!wr_rst_n)
			wr_addr	<=	0;
//		else	if (wr_en && (~full))
		else	if (wr_en && ~((~rd_b2g_rr[$clog2(data_depth) : $clog2(data_depth)-1] == wr_b2g[$clog2(data_depth) : $clog2(data_depth)-1]) 
					&& (rd_b2g_rr[$clog2(data_depth)-2 : 0] == wr_b2g[$clog2(data_depth)-2 : 0])))
			begin
				wr_addr										<=	wr_addr + 1'b1;
				// 这里因为wr_addr比实际位宽多定义了一位,这里摘取除了最高位的地址
				// 以深度为16举例,mem[0:15][7:0],比如第一组数据写完了,回过头要给第一个地址写新的数据,此时wr_addr =5'b 1_0000,直接mem[wr_addr] <= data_in,数据其实没有写到想要写的位置
				mem[{wr_addr[$clog2(data_depth)-1 : 0]}]	<=	data_in;
//				mem[wr_addr]	<=	data_in;
			end
		else
			wr_addr	<=	wr_addr;

// 读操作
	always @ (posedge rd_clk or negedge rd_rst_n)
		if (!rd_rst_n)
			begin
				rd_addr		<=	0;
				data_out	<=	0;
			end
		else	if (rd_en && (~empty))
			begin
				rd_addr		<=	rd_addr + 1'b1;
				data_out	<=	mem[rd_addr];
			end
		else
			begin
				rd_addr		<=	rd_addr;
				data_out	<=	data_out;
			end

	
	wire	[$clog2(data_depth) : 0]	wr_b2g;
	wire	[$clog2(data_depth) : 0]	rd_b2g;
// 读写指针二进制转格雷码	采用组合逻辑,减少打拍次数
	assign	wr_b2g	=	wr_addr ^ (wr_addr >> 1);
	assign	rd_b2g	=	rd_addr ^ (rd_addr >> 1);

// 两级寄存器进行打拍												这里直接用两个寄存器进行同步,需要注意CDC问题
	reg		[$clog2(data_depth) : 0]	wr_b2g_r	;
	reg		[$clog2(data_depth) : 0]	wr_b2g_rr	;
	reg		[$clog2(data_depth) : 0]	rd_b2g_r	;
	reg		[$clog2(data_depth) : 0]	rd_b2g_rr	;

// 写指针同步到读时钟域,用于判空
//	always @ (posedge rd_clk or negedge rd_rst_n)
	always @ (rd_clk)
		if (!rd_rst_n)
			begin
				wr_b2g_r	<=	0;
				wr_b2g_rr	<=	0;
			end
		else
			begin
				wr_b2g_r	<=	wr_b2g;
				wr_b2g_rr	<=	wr_b2g_r;
			end
			
//	always @ (posedge rd_clk or negedge rd_rst_n)		// 时序逻辑
//		if (!rd_rst_n)
//			empty	<=	0;
//		// 所有位都相等,判空
//		else	if (wr_b2g_rr == rd_b2g)
//			empty	<=	1;
//		else
//			empty	<=	0;
	assign	empty	=	(wr_b2g_rr == rd_b2g) ? 1 : 0;	// 组合逻辑

// 读指针同步到写时钟域,用于判满
//	always @ (posedge wr_clk or negedge wr_rst_n)
	always @ (wr_clk)
		if (!wr_rst_n)
			begin
				rd_b2g_r	<=	0;
				rd_b2g_rr	<=	0;
			end
		else
			begin
				rd_b2g_r	<=	rd_b2g;
				rd_b2g_rr	<=	rd_b2g_r;
			end
	
	always @ (posedge wr_clk or negedge wr_rst_n)
		if (!wr_rst_n)
			full	<=	0;
		// 高两位相反,其余位相等,判满
		else	if ( (~rd_b2g_rr[$clog2(data_depth) : $clog2(data_depth)-1] == wr_b2g[$clog2(data_depth) : $clog2(data_depth)-1]) 
					&& (rd_b2g_rr[$clog2(data_depth)-2 : 0] == wr_b2g[$clog2(data_depth)-2 : 0]) )
			full	<=	1;
		else
			full	<=	0;
			
endmodule
`define	 clk_period_wr 50
`define	 clk_period_rd 20

module sim_fifo_async ();
	parameter		data_width = 8;
	parameter		data_depth = 16;
    
    reg							wr_clk	;
    reg							wr_rst_n;
    reg							wr_en	;
    reg		[data_width-1 : 0]	data_in	;
    wire						full	;
    
    reg							rd_clk	;
    reg							rd_rst_n;
    reg							rd_en	;
    wire	[data_width-1 : 0]	data_out;
    wire						empty	;
    
    fifo_async #(
		.data_width	(data_width),
		.data_depth	(data_depth)
		)
	fifo_async_inst (	
		.wr_clk			(wr_clk),
		.wr_rst_n		(wr_rst_n),
		.wr_en			(wr_en),
		.data_in		(data_in),
		.full			(full),
		
		.rd_clk			(rd_clk),
		.rd_rst_n		(rd_rst_n),
		.rd_en			(rd_en),
		.data_out		(data_out),
		.empty			(empty)
	);

	//always #(`clk_period_wr/2) wr_clk = ~wr_clk;
	//always #(`clk_period_rd/2) rd_clk = ~rd_clk;
	always #(4/2) wr_clk = ~wr_clk;
	always #(10/2) rd_clk = ~rd_clk;

	initial	begin
		wr_clk	=	0;
		rd_clk	=	0;
		wr_rst_n	=	0;
		rd_rst_n	=	0;
		wr_en	=	0;
		rd_en	=	0;
		#10
		wr_rst_n	=	1;
		rd_rst_n	=	1;
		
		// 开始写,6个数据
		#10
		wr_en = #(0.2) 1'b1;
		data_in	=	#(0.2) $random;
		repeat(5) begin
            @(posedge wr_clk);
                data_in = #(0.2) $random;  
        end
        
        @(posedge wr_clk); 
        wr_en = #(0.2) 1'b0;
        data_in = #(0.2) $random;
        
        // 开始读,读空
        #10
        rd_en = #(0.2) 1'b1;
        repeat(5) begin
            @(posedge rd_clk);  
        end

        @(posedge rd_clk);
        rd_en = #(0.2) 1'b0;
        
        
        // 将FIFO写满,会看到full标志信号
        #50
        wr_en = #(0.2) 1'b1;
        data_in = #(0.2) $random; 
        repeat(19) begin
            @(posedge wr_clk);  
                data_in = #(0.2) $random;
        end
        @(posedge wr_clk); 
        wr_en = #(0.2) 1'b0;
        data_in = #(0.2) $random;      
		
	end
	
endmodule

在这里插入图片描述

遇到的问题,以及片面的分析:

  1. 有关empty信号是否可以正常工作,仿真给出的激励信号必须有讲究,同样的写入一组数据,但因为信号变化的时间不太一样,就影响到了相关寄存器的状态,也就影响了空满信号。

  2. 有关wr_addr多定义一位的影响,上面说了没有影响,但是对mem 的寻址还是有一点影响的
    在这里插入图片描述
    在这里插入图片描述

  3. 有关读写标志的跨时钟域问题,这里也没有很好的解决,这里能跨时钟域后能正常得出结果,有一点勉强的感觉。测试的时候,修改了很多组周期,从T(wr_clk) = 4, T(rd_clk) = 10,到T(wr_clk) = 300, T(rd_clk) = 20,都能正确读写,并且产生正确的空满标志

    这个博客完了又有新的目标了,重新整理一下跨时钟域

    单比特数据为例

    • slow2fast,快时钟一定能采样到慢时钟,慢时钟的信号至少会在快时钟下保持一个时钟周期
    • fast2slow,快时钟下的信号保持时间短,慢时钟可能会采样不到,代码中用的寄存器打两拍的方法,就不太适用,需要其他方法解决,比如对输入信号进行展宽呀啥的
  4. 有关读写操作的请求控制,以写数据为例,

    // 写操作
    	always @ (posedge wr_clk or negedge wr_rst_n)
    		if (!wr_rst_n)
    			wr_addr	<=	0;
    		else	if (wr_en && (~full))
    			begin
    				wr_addr			<=	wr_addr + 1'b1;
    				mem[{wr_addr[$clog2(data_depth)-1 : 0]}]	<=	data_in;
    			end
    		else
    			wr_addr	<=	wr_addr;
    // 判满
    	always @ (posedge wr_clk or negedge wr_rst_n)
    	if (!wr_rst_n)
    		full	<=	0;
    	// 高两位相反,其余位相等,判满
    	else	if ( (~rd_b2g_rr[$clog2(data_depth) : $clog2(data_depth)-1] == wr_b2g[$clog2(data_depth) : $clog2(data_depth)-1]) 
    				&& (rd_b2g_rr[$clog2(data_depth)-2 : 0] == wr_b2g[$clog2(data_depth)-2 : 0]) )
    		full	<=	1;
    	else
    		full	<=	0;
    

    仿真结果:这一大段我一直在写数据,确实full信号也输出了高电平,但也只是短暂的输出了一个周期 ,大无语。
    在这里插入图片描述
    full信号是使用时序逻辑求得的,如果用(wr_en && (~full))作为控制条件,wr_en是实时激励的,而full相对于晚一拍,她们俩短暂的相匹配(红色箭头的位置),然后又错过了
    修改了一下控制条件如下,

    else	if (wr_en && ~((~rd_b2g_rr[$clog2(data_depth) : $clog2(data_depth)-1] == wr_b2g[$clog2(data_depth) : $clog2(data_depth)-1]) 
    					&& (rd_b2g_rr[$clog2(data_depth)-2 : 0] == wr_b2g[$clog2(data_depth)-2 : 0])))
    			begin
    				wr_addr			<=	wr_addr + 1'b1;
    				mem[{wr_addr[$clog2(data_depth)-1 : 0]}]	<=	data_in;
    			end
    

    在这里插入图片描述
    猜想:如果full信号是wire型,用的assign语句赋值,那么它就是实时的,可以if((wr_en && (~full))),下面我试着这样用一下empty信号
    试过了,也是正确的
    回顾问题1,说到的empty信号没有正常工作,和问题4是一个原因,修改了就都好了。

总结

  1. 组合逻辑还是为了辅助时序逻辑,要注意节拍的同步问题
  2. 别人的博客都是技术分享,我的博客像是一个改错本,遇到各种问题,然后各种打补丁修改

终于写完了
4.29到5.18
烦死人了

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值