题解 | #异步FIFO#
异步FIFO
http://www.nowcoder.com/practice/40246577a1a04c08b3b7f529f9a268cf
一、异步FIFO读写指针表示:
二进制的计数值跨时钟域出现亚稳态,二进制计数时所有位都可能同时变化。使用格雷码减少亚稳态。将格雷码同步到另一个时钟域用来空满判断检测。 格雷码特点:
◼ 相邻格雷码之间只有一位变化,其他位相同, 降低亚稳态出现的概率。
◼ 格雷码是循环码, 0 和 2^n-1 之间只有一位不同。 当 FIFO 深度是 2 次幂时,可以满足用格雷码消除亚稳态。
二进制转格雷码代码:
gray_raddr <= (raddr>>1) ^ raddr;
gray_waddr <= (waddr>>1) ^ waddr;
二、读空、写满判断
在读时钟域进行空状态判断,在写时钟域进行满状态判断。
⚫ 读空: 将写时钟域的二进制写指针转化为格雷码经过两级触发器同步到读时钟域,被同步的写指针与读时钟域的读指针每一位完全相同。
⚫ 写满:将读时钟域的读指针同步到写时钟域,被同步的读指针与写时钟域的写指针高两位不一致,其余完全相同。
异步 FIFO 读指针是属于读时钟域,写指针属于写时钟域,读写时钟域不同。读写指针同步时采用两级触发器同步+格雷码,目的是消除亚稳态。
区分读空和写满这两种不同的状态,即FIFO在被写满后不能再写入,以免覆盖原有数据;再被读空后不能再进行读操作,防止读取无效数据。ps写满:当两个二进制地址差值为DEPTH时,转换到格雷码其差值为DEPTH+(DEPTH>>1)。
判断满/空条件代码:
assign wfull = gray_waddr==(gray_raddr2+DEPTH+(DEPTH>>1))? 1: 0;//gray_raddr2为同步到write域的read信号
assign rempty = gray_raddr==gray_waddr2? 1: 0;//gray_waddr2为同步到read域的write信号
三、使能判断
异步FIFO内的RAM使能端wenc/renc判断,wenc和renc(不是winc和rinc)有效时waddr和raddr才自增,以免错误增加地址。
assign wenc = winc && (!wfull);
assign renc = rinc && (!rempty);
完整代码:
`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
);
reg[$clog2(DEPTH):0] waddr, raddr;
reg[$clog2(DEPTH):0] gray_waddr, gray_raddr;
/***************二进制地址自增,格雷码转换*****************************************/
always@(posedge wclk or negedge wrstn)begin
if(~wrstn)begin
waddr <= 0;
gray_waddr <= 0;
end
else begin
waddr <= wenc? waddr+1: waddr;
gray_waddr <= (waddr>>1) ^ waddr;
end
end
always@(posedge rclk or negedge rrstn)begin
if(~rrstn) begin
raddr <= 0;
gray_raddr <= 0;
end
else begin
raddr <= renc? raddr+1: raddr;
gray_raddr <= (raddr>>1) ^ raddr;
end
end
/******************将格雷码地址打两拍同步时钟域*****************************************/
wire wenc, renc;
reg[$clog2(DEPTH):0] gray_waddr1, gray_waddr2;
always@(posedge rclk or negedge rrstn)begin
if(~rrstn)begin
gray_waddr1 <= 0;
gray_waddr2 <= 0;
end
else begin
gray_waddr1 <= gray_waddr;
gray_waddr2 <= gray_waddr1;
end
end
assign rempty = gray_raddr==gray_waddr2? 1: 0;
assign renc = rinc && (!rempty);
reg[$clog2(DEPTH):0] gray_raddr1, gray_raddr2;
always@(posedge wclk or negedge wrstn)begin
if(~wrstn)begin
gray_raddr1 <= 0;
gray_raddr2 <= 0;
end
else begin
gray_raddr1 <= gray_raddr;
gray_raddr2 <= gray_raddr1;
end
end
assign wfull = gray_waddr==(gray_raddr2+DEPTH+(DEPTH>>1))? 1: 0;
assign wenc = winc && (!wfull);
dual_port_RAM #(DEPTH, WIDTH) dut(
.wclk(wclk)
,.wenc(wenc)
,.waddr(waddr[$clog2(DEPTH)-1 : 0]) //深度对2取对数,得到地址的位宽。
,.wdata(wdata) //数据写入
,.rclk(rclk)
,.renc(renc)
,.raddr(raddr[$clog2(DEPTH)-1 : 0]) //深度对2取对数,得到地址的位宽。
,.rdata(rdata));
endmodule