天天看点

同步FIFO和异步FIFO的实现

同步FIFO的实现

首先是同步的实现,只需要一个时钟用来控制读写。

同步 FIFO 实现较为直接,如下图所示,一个 FIFO 内部实现了 RAM 和一个控制读写的控制端,和普通的随机存储器不一样的是,FIFO 内部需要自动产生读写地址。

控制端产生空、满信号。因此如何实现控制端去产生空满信号是主要需要考虑的地方。通常空满的判断是通过读、写地址进行的。

同步FIFO和异步FIFO的实现

第一种思路,利用读写地址的大小来判断空、满

方法很简单,设置一个信号量 factor 来表识空满,每次读一个数据则令 factor - 1,写一个数据则令其+1。实现代码如下:

module fifo #(
	parameter DATA_WIDTH=16,
	parameter ADDR_WIDTH=8
)(
	input							clk			,
	input							rst_n		,
	input							write_en	,
	input		[DATA_WIDTH-1:0]	write_data	,
	input							read_en		,
	output	reg	[DATA_WIDTH-1:0]	read_data	,
	output	reg						empty		,
	output	reg						full		,
	output		[ADDR_WIDTH-1:0]	counter		
);

	
	reg [ADDR_WIDTH-1:0] read_addr;
	reg [ADDR_WIDTH-1:0] write_addr;
	reg [ADDR_WIDTH-1:0] factor;
	assign counter		= factor;

	wire write_allow	= (write_en && !full);
	wire read_allow		= (read_en	&& !empty);
	// 实例化 RAM
	dual_ram ram(clk, clk, rst_n, read_allow, write_allow, read_addr, write_addr, write_data, read_data);

	[email protected](posedge clk or negedge rst_n) begin
		if (!rst_n) begin
			factor		<= 'h0;
			read_addr	<= 'h0;
			write_addr	<= 'h0;
		end
		else if (write_allow) begin
			factor 		<= factor + 1;
			write_addr	<= write_addr + 1;
		end
		else if (read_allow) begin
			factor		<= factor - 1;
			read_addr	<= read_addr + 1;
		end
	end

	[email protected](posedge clk or negedge rst_n) begin
		if (!rst_n) begin
			empty	<= 1;
		end
		else begin
			empty	<= ( !write_en && (factor[7:1] == 7'b0) && (factor[0] == 0 || read_en) );
			//empty	<= factor == 8'b0;
		end
	end

	[email protected](posedge clk or negedge rst_n) begin
		if (!rst_n) begin
			full	<= 0;
		end
		else begin
			full 	<= ( !read_en && (factor[7:1] == 7'b1111111) && (factor[0] == 1 || write_en) );
			//full	<= (factor == 8'hFF);
		end
			
	end

endmodule

// 定义一个双端口 RAM
module dual_ram#(
	parameter DATA_WIDTH=16,
	parameter ADDR_WIDTH=8
)(
	input	read_clk,
	input	write_clk,
	input	rst_n,
	input	read_en,
	input	write_en,
	input	[ADDR_WIDTH-1:0]	read_addr,
	input	[ADDR_WIDTH-1:0]	write_addr,
	input	[DATA_WIDTH-1:0]	write_data,
	output	reg	[DATA_WIDTH-1:0]	read_data

);

	reg [DATA_WIDTH-1:0] mem [255:0];
	integer i;
	[email protected](posedge write_clk or negedge rst_n) begin: write_ram
		if (!rst_n) begin
			for (i = 0; i < 256; i = i + 1)
				mem[i] <= {DATA_WIDTH{0}};
		end
		else if (write_en) begin
			mem[write_addr] <= write_data;
		end
	end

	[email protected](posedge read_clk or negedge rst_n) begin: read_ram
		if (!rst_n) begin
			read_data <= {DATA_WIDTH{0}};
		end
		else if (read_en) begin
			read_data <= mem[read_addr];
		end
	end

endmodule

           

值得注意的是,上面的代码对空满的判断并不简简单单是判断 factor 的大小为 0 还是等于 FIFO 大小。例如对空信号的判断:

这句代码的意思就是,要想为空,那么写信号一定无效,同时如果如果 factor==0,那么肯定是空;然而还有一种极端情况,即在FIFO 内部只有一个数据时,读信号还有效,那么下个 cycle,FIFO 一定为空。

这样的判断有些繁琐,还需要两个 full、empty 寄存器。

第二种思路,采用读写地址位进行空、满判断

假设,实例化的 RAM 地址位为 4,那么我们可以定义 5 bit 的读写地址寄存器(read_addr_e、write_addr_e),高位用来当作标志位,用来标识读写的次数关系。但是在访问 RAM 时还是需要输入 4 bit 的地址。判断规则如下:

  • i f w r i t e _ a d d r _ e = = r e a d _ a d d r _ e t h e n e m p t y = 1 if \quad write\_addr\_e == read\_addr\_e \quad then \quad empty=1 ifwrite_addr_e==read_addr_ethenempty=1(因为读写地址相同,意味着读指针、写指针重合)
  • i f ( r e a d _ a d d r _ e [ 4 ]  ⁣ = w r i t e a d d r e [ 4 ] ) & & ( r e a d _ a d d r _ e [ 3 : 0 ] = = w r i t e _ a d d r _ e [ 3 : 0 ] ) t h e n f u l l = 1 if \quad (read\_addr\_e[4] \!= write_addr_e[4]) \&\& (read\_addr\_e[3:0] == write\_addr\_e[3:0]) \quad then \quad full=1 if(read_addr_e[4]=writea​ddre​[4])&&(read_addr_e[3:0]==write_addr_e[3:0])thenfull=1(因为标志位不同意味着写指针可能已经领先一圈,例如 00000,10000 表示已经写了 8 个数了,而还没有读出一个数,则满了)

实现代码如下:

module fifo #(
	parameter DATA_WIDTH=16,
	parameter ADDR_WIDTH=4
)(
	input							clk			,
	input							rst_n		,
	input							write_en	,
	input		[DATA_WIDTH-1:0]	write_data	,
	input							read_en		,
	
	output	reg	[DATA_WIDTH-1:0]	read_data	,
	output							empty		,
	output							full		
);

	wire	[ADDR_WIDTH-1:0] read_addr;
	wire	[ADDR_WIDTH-1:0] write_addr;

	reg		[ADDR_WIDTH  :0] read_addr_e;
	reg		[ADDR_WIDTH  :0] write_addr_e;
	
	assign read_addr	= read_addr_e[ADDR_WIDTH-1:0];
	assign write_addr	= write_addr_e[ADDR_WIDTH-1:0];

	wire write_allow	= (write_en && !full);
	wire read_allow		= (read_en	&& !empty);
	// 为了更加简洁,直接定义了一个 memory
	reg [DATA_WIDTH-1:0] mem [15:0];
	integer i;
	[email protected](posedge clk or negedge rst_n) begin
		if (!rst_n) begin
			for (i = 0; i < 8; i = i + 1)
				mem[i]		<= 'b0;
		end
		else if (write_allow)
			mem[write_addr] <= write_data;
	end
	

	[email protected](posedge clk or negedge rst_n) begin
		if (!rst_n) begin
			read_addr_e 	<= 'h0;
			write_addr_e	<= 'h0;
			read_data		<= 'h0;
		end
		else if (write_allow) begin
			write_addr_e	<= write_addr_e + 1;
		end
		else if (read_allow) begin
			read_addr_e		<= read_addr_e + 1;
			read_data		<= mem[read_addr];
		end
	end
	// 空、满判断
	assign empty	= (write_addr_e == read_addr_e) ? 1 : 0;
	assign full		= ( (read_addr_e[4] != write_addr_e[4]) && (read_addr_e[3:0] == write_addr_e[3:0]) ) ? 1 : 0;

endmodule
           

异步 FIFO 的实现

在异步 FIFO 中,读写时钟不同,需要跨时钟域处理,还需要采用格雷码来打一拍,进行读写地址的采样(空满判断)。格雷码如下所示:

同步FIFO和异步FIFO的实现

为什么采用格雷码?

事实上二进制读指针在增减时,经常发生多为突变,比如六位地址111111会在下一个 时刻变成000000,在实际电路中,这个变化过程要持续很长一段时间,会由 111111 经历 6 个状态转移到 00000,比如111111->101111->10111->100110->100100->000100->000000。由于写时钟与读时钟不同步,异步的写时钟很可能会在状态不稳定的中间某个状态采样,这样就会得到错误的读指针,进而做出错误的状态判断,导致系统异常(亚稳态问题)。而且由于多位同时突变,凭借概率论常识可知发生错误的可能性很大。

那么怎样才能避免这个问题的发生呢? 1000->1000(可能会错采样到它)->0000

显然,在中间状态采样,这个是不可能避免的,这是异步系统天生的缺陷。我们的目标是:即使在中间状态采样,也不能影响空满状态的判断。符合这个要求的编码方式是:每次只能有1个bit发生改变。为什么这么说呢?因为当只有一个bit发生改变时,即使在中间状态采样,其结果也不外乎两种:递增前原指针和递增后新指针。显然递增后新指针是最新情况的反映,如果采样到这个指针,那么和我们的设计预期是一致的,如果采样到递增前的原指针,会有什么结果呢?假设现在采样读指针,那么最坏的情况就是把“不满”判断成了 “满”,使得本来被允许的写操作被禁正了,但是这并不会对逻辑产生影响,只是带来了写操作的延迟。同样的,如果现在采样写指针,那么最坏的情况就是把“不空”判断成“空”,产生“虚空”, 使得本来被允许的读操作被禁止了,但是这也不会对逻辑产生影响,只是带来了读操作的延迟。

综上,使用格雷码进行采样是一种较好的选择。

硬件架构设计

同步FIFO和异步FIFO的实现

异步FIFO的整体结构大致如下:

  • Write_control:控制写操作与满信号(w_full)的判断与产生。
  • Read_control:控制读操作与空信号(r_empty)的判断与产生。
  • RAM:双端口数据存取RAM。
  • Bin_to_gray:二进制码转格雷码模块。用于将读写地址二进制码转成格雷码。
  • SYN:跨时钟同步模块,即将读地址的格雷码(r_g_addr)向w_clk同步;将写地址的格雷码(w_g_addr)向r_clk同步。主要操作就是通过寄存器打两拍。
module asyn_fifo #(
	parameter	WIDTH_D = 16,
	parameter	WIDTH_A	= 8	,
	parameter	DEPTH	= 256
)
(
	input					r_clk,
	input					w_clk,
	input					rst_n,
	input					r_en,
	input					w_en,
	input	[WIDTH_D-1:0]	data_in,
	output	[WIDTH_D-1:0]	data_out
);

wire	[WIDTH_A-1:0] 	w_addr_to_ram;
wire	[WIDTH_A-1:0] 	r_addr_to_ram;
wire					r_allow;
wire					w_allow;
wire					empty;
wire					full;

RAM #(
	.DEPTH(DEPTH),
	.WIDTH_D(WIDTH_D),
	.WIDTH_A(WIDTH_A)
)ram (
	.clk_r		(r_clk),
	.clk_w		(w_clk),
	.rst_n		(rst_n),
	.write_en	(w_allow),
	.read_en	(r_allow),
	.write_data	(data_in),
	.write_addr	(w_addr_to_ram),
	.read_addr	(r_addr_to_ram),
	.read_data	(data_out)
);

wire	[WIDTH_A:0]	w_gray1;
wire	[WIDTH_A:0]	w_gray2;
wire	[WIDTH_A:0]	r_gray1;
wire	[WIDTH_A:0]	r_gray2;

read_part #(
	.WIDTH_D(WIDTH_D),
	.WIDTH_A(WIDTH_A)
)read (
	.r_clk			(r_clk),
	.rst_n			(rst_n),
	.r_en			(r_en),
	.w_gray			(w_gray2),
	.empty			(empty),
	.r_addr_to_ram	(r_addr_to_ram),
	.r_gray			(r_gray1),
	.r_allow		(r_allow)
);

write_part #(
	.WIDTH_D(WIDTH_D),
	.WIDTH_A(WIDTH_A)
)write (
	.w_clk			(w_clk),
	.rst_n			(rst_n),
	.w_en			(w_en),
	.r_gray			(r_gray2),
	.full			(full),
	.w_addr_to_ram	(w_addr_to_ram),
	.w_gray			(w_gray1),
	.w_allow		(w_allow)
);

syn #(
	.WIDTH_A(WIDTH_A)
)syn_1 (
	.clk		(r_clk),
	.rst_n		(rst_n),
	.data_in	(w_gray1),
	.syn_data	(w_gray2)
);

syn	#(
	.WIDTH_A(WIDTH_A)
)syn_2 (
	.clk		(w_clk),
	.rst_n		(rst_n),
	.data_in	(r_gray1),
	.syn_data	(r_gray2)
);
endmodule

module bin_to_gray#(
	parameter WIDTH_A = 8
)
(
	input	[WIDTH_A:0]	bin_c	,
	output	[WIDTH_A:0]	gray_c
);

wire h_b = bin_c[WIDTH_A];

reg [WIDTH_A-1:0] gray_c_r;
integer i;
[email protected](*) begin
	for (i = 0; i < WIDTH_A; i = i + 1)
		gray_c_r[i] = bin_c[i] ^ bin_c[i + 1];
end

assign	gray_c = {h_b, gray_c_r};

endmodule

module read_part#(
	parameter	WIDTH_D = 16,
	parameter	WIDTH_A = 8
)(

	input					r_clk			,
	input					rst_n			,
	input					r_en			,
	input	[WIDTH_A  :0]	w_gray			,

	output					empty			,
	output	[WIDTH_A-1:0]	r_addr_to_ram	,
	output	reg	[WIDTH_A  :0]	r_gray			,
	output	reg				r_allow			
);

reg [WIDTH_A:0] r_addr_r;
assign	r_addr_ro_ram	= r_addr_r[WIDTH_A-1:0]; // to RAM

[email protected](posedge r_clk or negedge rst_n) begin
	if (!rst_n) begin 
		r_addr_r <= 'b0;
		r_allow <= 'b0;
	end
	else if (r_en && !empty) begin
		r_addr_r	<= r_addr_r + 1;
		r_allow		<= 1;
	end
end

assign	empty = (w_gray[WIDTH_A:0] == r_gray[WIDTH_A:0]) ? 1 : 0;

wire [WIDTH_A:0] r_gray_w;
// 要通过寄存器打一拍之后再输出
[email protected](posedge r_clk or negedge rst_n) begin
	if (!rst_n)
		r_gray <= 'b0;
	else
		r_gray <= r_gray_w;
end

bin_to_gray bin1 (r_addr_r, r_gray_w);

endmodule

module write_part#(
	parameter	WIDTH_A = 8,
	parameter	WIDTH_D = 16
)
(
	input					w_clk		,
	input					rst_n		,
	input					w_en		,
	input	[WIDTH_A  :0]	r_gray		,

	output	[WIDTH_A-1:0]	w_addr_to_ram,
	output	reg	[WIDTH_A  :0]	w_gray		,
	output					full		,
	output	reg				w_allow		
);

reg [WIDTH_A:0] w_addr_r;
assign	w_addr_to_ram = w_addr_r[WIDTH_A-1:0];

[email protected](posedge w_clk or negedge rst_n) begin
	if (!rst_n) begin
		w_addr_r <= 'b0;
		w_allow  <= 'b0;
	end
	else if (w_en && !full) begin
		w_addr_r <= w_addr_r + 1;
		w_allow  <= 1;
	end
end

assign full = ( {~w_gray[WIDTH_A], ~w_gray[WIDTH_A-1], w_gray[WIDTH_A-2:0]} == r_gray ) ? 1 : 0;
wire [WIDTH_A:0] w_gray_w;
[email protected](posedge w_clk or negedge rst_n) begin
	if (!rst_n) 
		w_gray <= 'b0;
	else
		w_gray <= w_gray_w;
end
bin_to_gray bin2 (w_addr_r, w_gray_w);

endmodule

module syn #(
	parameter	WIDTH_A = 8
)
(
	input					clk		,
	input					rst_n	,
	input	[WIDTH_A:0]	data_in	,
	output	[WIDTH_A:0]	syn_data
);

reg [WIDTH_A:0] reg0, reg1;

[email protected](posedge clk or negedge rst_n) begin
	if (!rst_n) begin
		reg0 <= 'b0;
		reg1 <= 'b0;
	end
	else begin
		reg0 <= data_in;
		reg1 <= reg0;
	end
end

assign	syn_data = reg1;

endmodule


module RAM #(
	parameter DEPTH = 256,
	parameter WIDTH_A = 8,
	parameter WIDTH_D = 16
)
(
	input					clk_r		,
	input					clk_w		,
	input					rst_n		,
	input					write_en	,
	input					read_en		,
	input	[WIDTH_D-1:0]	write_data	,
	input	[WIDTH_A-1:0]	write_addr	,
	input	[WIDTH_A-1:0]	read_addr	,
	output reg	[WIDTH_D-1:0]	read_data
);

reg [WIDTH_D-1:0] memory [DEPTH-1:0];

integer i;
[email protected](posedge clk_w or negedge rst_n) begin: writing
	if(!rst_n) begin
		for (i = 0; i < DEPTH; i = i + 1) 
			memory[i] <= 'b0;
	end
	else if (write_en) 
		memory[write_addr] <= write_data;
end

[email protected](posedge clk_r or negedge rst_n) begin: reading
	if(!rst_n) begin
		read_data <= 'b0;
	end
	else if (read_en) 
		read_data <= memory[read_addr];

end

endmodule

           

继续阅读