基于Verilog实现的异步FIFO
本文大部分内容参考《FPGA之道》、《硬件架构的艺术》,代码部分自己完成。
为什么要用到异步FIFO
在FPGA芯片开发中中难免会遇到跨时钟域的问题,解决跨时钟域的问题主要有如下方法:
1、两级采样法;(采样一次已经完成了非本时钟域信号的同步操作,采用两级采用给数字电路充分的时间来充放电,使得输出信号更健壮)
2、握手法;(暂时还没看嘿嘿)
3、异步FIFO;(针对大量数据的跨时钟域传输)
4、异步RAM法;(主体思路和异步FIFO法一致,不同的是RAM是一个较为开放的存储结构,随机存取,操作方式灵活)
本文主要介绍异步FIFO法:
异步FIFO就是用来在两个异步时钟域见传输数据;
注意事项
要避免用二进制计数器实现指针。以写指针为例,写指针在写时钟的作用下递增,同时,读指针在读指针的作用下递增。在产生FIFO满信号时,需要将写指针和读指针进行同步,但写指针和读指针彼此异步,如果使用二进制计数器实现指针就会导致用于比较的指针值取样错误。由于FIFO满和FIFO空标记的产生会使用这些指针的值,所以错误的指针值会误产生标记。导致数据丢时或读出垃圾数据。
格雷码取代二进制指针
格雷码的优势在于格雷码是单位间距码,每下一个值与前一个值的区别只有一位距离,所以在转换中最多只会出现一位错误。
同步指针的影响
在FIFO满时会组织进一步访问FIFO。在FIFO满时,按照各自时钟递增的读、写指针会进行比较。需要将读指针(格雷码)同步到写指针上。如下图,最初在t0阶段,读、写指针都为0。随着后续发生在FIFO上的写操作,写指针增加。当到达某个阶段时,读、写指针相等,这是FIFO满。
如果在t6发送读操作,由于典型同步电路至少包含两个触发器,将读指针同步到写指针上将会导致同步后的读指针在两个写指针后出现,虽然增加了数据写入周期,但对数据来说是有益的。
产生FIFO满和FIFO空的方法
创建两个格雷码计数器,一个n位,另一个n-1位。可先创建一个n位格雷码计数器,再通过修改其第二个MSB来产生一个n-1位的格雷码计数器,两个计数器的LSB相同。
双口FIFO设计
FIFO空条件的产生
当读指针与同步后的写指针相匹配时,FIFO为空,这是该再FIFO的读指针时钟域内马上产生FIFO空标记。空条件产生逻辑的Verilog代码如下:
always@(posedge rd_clk or negedge rstn)
begin
if(!rstn)
empty_r <= 1'b0;
else
empty_r <= (rd_ptr_gray == wr_ptr_sync);
end
FIFO空条件的产生
满标志置起需要三个条件:
1、同步后的读指针(rd_ptr_sync)的MSB要与写指针的下一个格雷码的MSB不同(wr_ptr_gray_next);
2、写时钟域中的下一个格雷码计数值(wr_ptr_gray_next)的第二个MSB,应该与同步到写时钟域内读指针(rd_ptr_sync)的第二个MSB相同;
3、两个指针中的所有省略掉的LSB都应该匹配;
空条件产生逻辑的Verilog代码如下:
always@(posedge wr_clk or negedge rstn)
begin
if(!rstn)
full_r <= 1'b0;
else
full_r <= ((wr_ptr_gray_next[adder_width]!=rd_ptr_sync[adder_width])&&(rd_2nd_msb==wr_2nd_msb)&&(wr_ptr_gray_next[adder_width-2:0] == rd_ptr_sync[adder_width-2:0]));
end
整体逻辑Verilog代码如下:
module fifo_nsync#(
parameter data_width = 4,
parameter data_depth = 8,
parameter adder_width = 4)
(
input wr_clk,
input rd_clk,
input rstn,
//write
input[data_width-1:0] wr_data,
input wr_inr,
//read
output [data_width-1:0] rd_data,
input rd_inr,
//full&empty
output full,
output empty
);
reg full_r;
reg empty_r;
//write read adder
reg [adder_width-1:0] wr_addr;
reg [adder_width-1:0] wr_addr_next;
reg [adder_width-1:0] rd_addr;
reg [adder_width-1:0] rd_addr_next;
//write read ptr gray
reg[adder_width-1:0] wr_ptr_gray;
reg[adder_width-1:0] rd_ptr_gray;
//write read sync
reg[adder_width-1:0] wr_ptr_sync1;
reg[adder_width-1:0] wr_ptr_sync;
reg[adder_width-1:0] rd_ptr_sync1;
reg[adder_width-1:0] rd_ptr_sync;
//得到写地址
always@(posedge wr_clk or negedge rstn)
begin
if(!rstn)
begin
wr_addr <= 0;
end
else if(wr_addr[adder_width-1]) //注意优先级
wr_addr <= 0;
else if(wr_inr & (!full))
begin
wr_addr <= wr_addr + 1'b1;
end
end
//得到格雷码写指针
always@(wr_inr,wr_addr,wr_addr_next)
begin
wr_addr_next <= wr_addr + 1'd1;
wr_ptr_gray <= (wr_addr_next>>1)^wr_addr_next;
end
//得到读地址
always@(posedge rd_clk or negedge rstn)
begin
if(!rstn)
begin
rd_addr <= 0;
end
else if(rd_addr[adder_width-1])
rd_addr <= 0;
else if(rd_inr & (!empty))
begin
rd_addr <= rd_addr + 1'b1;
end
end
//得到格雷码读指针
always@(rd_inr,rd_addr,rd_addr_next)begin
begin
rd_addr_next <= rd_addr + 1'b1;
rd_ptr_gray <= (rd_addr_next>>1)^rd_addr_next;
end
end
//写指针同步读时钟
always@(posedge rd_clk)
begin
wr_ptr_sync1 <= wr_ptr_gray;
wr_ptr_sync <= wr_ptr_sync1;
end
//读指针同步写时钟
always@(posedge wr_clk)
begin
rd_ptr_sync1 <= rd_ptr_gray;
rd_ptr_sync <= rd_ptr_sync1;
end
//产生FIFO空
always@(posedge rd_clk or negedge rstn)
begin
if(!rstn)
empty_r <= 1'b0;
else if(wr_inr) //只要写使能有效,空标志就一直为0;如果不写这个会导致部分时间空标志无值
empty_r <= 1'b0;
else if(!full&(rd_ptr_gray == wr_ptr_sync))
empty_r <= 1'b1;
end
//产生FIFO满
wire rd_2nd_msb = rd_ptr_sync[adder_width-1]^rd_ptr_sync[adder_width-2];
wire wr_2nd_msb = wr_ptr_gray[adder_width-1]^wr_ptr_gray[adder_width-2];
always@(posedge wr_clk or negedge rstn)
begin
if(!rstn)
full_r <= 1'b0;
else if(rd_inr) ////只要读使能有效,满标志就一直为0;如果不写这个会导致部分时间读标志无值
full_r <= 1'b0;
else if(!empty&(wr_ptr_gray[adder_width-1]!=rd_ptr_sync[adder_width-1])&&(rd_2nd_msb==wr_2nd_msb)&&(wr_ptr_gray[adder_width-3:0] == rd_ptr_sync[adder_width-3:0]))
full_r <= 1'b1;
end
assign full = full_r;
assign empty = empty_r;
Dpram256_8 #(
.WIDTH (data_width),
.ADD_WIDTH (adder_width),
.DEEP (data_depth)
)
ram_fifo(
.wr_clk (wr_clk),
.rd_clk (rd_clk),
.wr_data (wr_data),
.wr_addr (wr_addr),
.rd_addr (rd_addr),
.wr_en (wr_inr&(!full)), //如果不加(!full),即使满了,如果写使能有效,仍然会修改RAM中的值
.rd_en (rd_inr&(!empty)),
.rd_data (rd_data)
);
endmodule
仿真波形如下:
以上为异步FIFO全部内容,有不足之处还请大家批评指正!!!