简析
输入:winc, rcin, wdata
输出:rdata, wfull, rempty
读写使能:rcin\wcin,读写数据:rdata\wdata,空满信号:wfull\rempty。
异步FIFO是各大公司面试笔试的重点。难点仍然是空满信号。异步FIFO的与同步FIFO的核心区别是它的读时钟和写时钟是不同步的。所以用对比读写地址的方法产生空满信号时,要进行跨时钟域处理。为了降低亚稳态可能性,异步FIFO还引入了格雷码。同时,格雷码也更方便产生空满信号。
上图是本异步FIFO的结构示意图。蓝色区域是读时钟域,黄色部分是写时钟域。异步FIFO主要包含四部分:读写地址发生器、格雷码的产生与打拍、空满信号发生器以及RAM。本题已经给出了RAM部分。
读写地址发生器
产生FIFO的自然二进制读写地址。当读使能rinc==1
且FIFO非空rempty==0
时,读地址在读时钟rclk
下自增;当写使能winc==1
且FIFO非满wfull==0
时,写地址在写时钟wclk
下自增。这两种地址可以直接传入RAM模块。
wire wenc, renc;
wire [$clog2(DEPTH)-1:0] waddr, raddr;
assign wenc = winc&!wfull;
assign renc = rinc&!rempty;
always@(posedge wclk or negedge wrstn) begin
if(~wrstn)
waddr_bin <= 0;
else
waddr_bin <= wenc? waddr_bin+1: waddr_bin;
end
always@(posedge rclk or negedge rrstn) begin
if(~rrstn)
raddr_bin <= 0;
else
raddr_bin <= renc? raddr_bin+1: raddr_bin;
end
格雷码打拍
为了产生空满信号,需要比较读写地址的大小。但两个地址是由不同的时钟控制的,需要先做跨时钟域处理才能比较。所以使用了打两拍。又因为FIFO的读写地址是连续变化的,采用格雷码可以有效减少相邻地址的bit变化,进一步降低打拍过程产生亚稳态的可能性。本题的参考答案在格雷码产生后在本地时钟域打了一拍才进行跨时钟域传输。但其实并不用打这一拍。最后,自然二进制地址和格雷码地址都是$clog2(DEPTH)+1
bit,比waddr
和raddr
多了1bit。该bit是用来辅助产生满信号的。
格雷码与自然二进制码的转换参考:格雷码计数器。
reg [$clog2(DEPTH):0] waddr_bin, raddr_bin;
wire [$clog2(DEPTH):0] waddr_gray, raddr_gray;
reg [$clog2(DEPTH):0] waddr_gray1, raddr_gray1;
reg [$clog2(DEPTH):0] waddr_gray2, raddr_gray2;
reg [$clog2(DEPTH):0] waddr_gray3, raddr_gray3;
assign waddr_gray = waddr_bin^(waddr_bin>>1);
assign raddr_gray = raddr_bin^(raddr_bin>>1);
assign waddr = waddr_bin[$clog2(DEPTH)-1:0];
assign raddr = raddr_bin[$clog2(DEPTH)-1:0];
always@(posedge rclk or negedge rrstn) begin
if(~rrstn)
raddr_gray1 <= 0;
else
raddr_gray1 <= raddr_gray;
end
always@(posedge rclk or negedge rrstn) begin
if(~rrstn) begin
waddr_gray2 <= 0;
waddr_gray3 <= 0;
end
else begin
waddr_gray2 <= waddr_gray1;
waddr_gray3 <= waddr_gray2;
end
end
always@(posedge wclk or negedge wrstn) begin
if(~wrstn) begin
raddr_gray2 <= 0;
raddr_gray3 <= 0;
end
else begin
raddr_gray2 <= raddr_gray1;
raddr_gray3 <= raddr_gray2;
end
end
空满信号发生器
当读写地址的格雷码仅有最高的2bit不同时,FIFO满;当读写地址完全相同时,FIFO空。
assign wfull = (waddr_gray1=={~raddr_gray3[$clog2(DEPTH):$clog2(DEPTH)-1], raddr_gray3[$clog2(DEPTH)-2:0]});
assign rempty = (raddr_gray1==waddr_gray3);
向FIFO写数据时,可能会出现写地址超过读地址一圈的情况,所以写满信号wfull
的判断复杂一些。
代码
`timescale 1ns/1ns
/***************************************RAM*****************************************/
module dual_port_RAM #(parameter DEPTH = 16,
parameter WIDTH = 8)(
input wclk
,input wenc
,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。
,input [WIDTH-1:0] wdata //数据写入
,input rclk
,input renc
,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。
,output reg [WIDTH-1:0] rdata //数据输出
);
reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
always @(posedge wclk) begin
if(wenc)
RAM_MEM[waddr] <= wdata;
end
always @(posedge rclk) begin
if(renc)
rdata <= RAM_MEM[raddr];
end
endmodule
/***************************************AFIFO*****************************************/
module asyn_fifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input wclk ,
input rclk ,
input wrstn ,
input rrstn ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,
output wire wfull ,
output wire rempty ,
output wire [WIDTH-1:0] rdata
);
wire [$clog2(DEPTH)-1:0] waddr, raddr;
reg [$clog2(DEPTH) :0] waddr_bin, raddr_bin;
wire [$clog2(DEPTH) :0] waddr_gray, raddr_gray;
reg [$clog2(DEPTH) :0] waddr_gray1, raddr_gray1;
reg [$clog2(DEPTH) :0] waddr_gray2, raddr_gray2;
reg [$clog2(DEPTH) :0] waddr_gray3, raddr_gray3;
wire wenc, renc;
always@(posedge wclk or negedge wrstn) begin
if(~wrstn)
waddr_bin <= 0;
else
waddr_bin <= wenc? waddr_bin+1: waddr_bin;
end
always@(posedge rclk or negedge rrstn) begin
if(~rrstn)
raddr_bin <= 0;
else
raddr_bin <= renc? raddr_bin+1: raddr_bin;
end
always@(posedge wclk or negedge wrstn) begin
if(~wrstn)
waddr_gray1 <= 0;
else
waddr_gray1 <= waddr_gray;
end
always@(posedge rclk or negedge rrstn) begin
if(~rrstn)
raddr_gray1 <= 0;
else
raddr_gray1 <= raddr_gray;
end
always@(posedge rclk or negedge rrstn) begin
if(~rrstn) begin
waddr_gray2 <= 0;
waddr_gray3 <= 0;
end
else begin
waddr_gray2 <= waddr_gray1;
waddr_gray3 <= waddr_gray2;
end
end
always@(posedge wclk or negedge wrstn) begin
if(~wrstn) begin
raddr_gray2 <= 0;
raddr_gray3 <= 0;
end
else begin
raddr_gray2 <= raddr_gray1;
raddr_gray3 <= raddr_gray2;
end
end
assign waddr_gray = waddr_bin^(waddr_bin>>1);
assign raddr_gray = raddr_bin^(raddr_bin>>1);
assign waddr = waddr_bin[$clog2(DEPTH)-1:0];
assign raddr = raddr_bin[$clog2(DEPTH)-1:0];
assign wenc = winc&!wfull;
assign renc = rinc&!rempty;
assign wfull = (waddr_gray1=={~raddr_gray3[$clog2(DEPTH):$clog2(DEPTH)-1], raddr_gray3[$clog2(DEPTH)-2:0]});
assign rempty = (raddr_gray1==waddr_gray3);
dual_port_RAM #(
.DEPTH(DEPTH),
.WIDTH(WIDTH)
)
myRAM(
.wclk (wclk ),
.wenc (wenc ),
.waddr(waddr),
.wdata(wdata),
.rclk (rclk ),
.renc (renc ),
.raddr(raddr),
.rdata(rdata)
);
endmodule