题解 | #异步FIFO#
异步FIFO
https://www.nowcoder.com/practice/40246577a1a04c08b3b7f529f9a268cf
`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 [DEPTH-1:0];
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
);
// 异步FIFO的与同步FIFO的核心区别是它的读时钟和写时钟是不同步的。所以用对比读写地址的方法产生空满信号时,要进行跨时钟域处理。二进制的计数值跨时钟域出现亚稳态,二进制计数时所有位都可能同时变化。
// 为了降低亚稳态可能性,异步FIFO还引入了格雷码。同时,格雷码也更方便产生空满信号
// 在同步FIFO基础上,进行异步FIFO设计。保持类似的读写逻辑和读写地址控制,区别是增加了格雷码跨时钟,另外空满逻辑是使用格雷码判断空满。
parameter DEPTH_WK = $clog2(DEPTH); //深度对2取对数,得到地址的位宽
wire wenc, renc;
assign wenc=winc&&~wfull; // 写使能
assign renc=rinc&&~rempty; // 读使能
//******************二进制******************//
reg [DEPTH_WK:0] waddr_bin, raddr_bin;
//写地址(waddr_bin)变化
always@(posedge wclk or negedge wrstn)begin
if (!wrstn)
waddr_bin<='d0;
else if( wenc)
waddr_bin<=waddr_bin+1'b1;
end
//读地址(raddr_bin)变化
always@(posedge rclk or negedge rrstn)begin
if (!rrstn)
raddr_bin<='d0;
else if(renc)
raddr_bin<=raddr_bin+1'b1;
end
//************ 格雷码*************//
wire [DEPTH_WK:0] waddr_gray , raddr_gray; //格雷码
//二进制转格雷码 (组合逻辑)
assign waddr_gray = waddr_bin ^ (waddr_bin>>1);
assign raddr_gray = raddr_bin ^ (raddr_bin>>1);
// ***************格雷码打拍*************//
//在原时钟下进行一拍寄存,对于输入进行寄存是很常见的方法,主要是去除毛刺等,做一个本时钟域的同步
reg [DEPTH_WK:0] waddr_gray_reg ,raddr_gray_reg;
always @ (posedge wclk or negedge wrstn) begin
if(!wrstn)
waddr_gray_reg <= 'd0;
else
waddr_gray_reg <= waddr_gray;
end
always @ (posedge rclk or negedge rrstn) begin
if(!rrstn)
raddr_gray_reg <= 'd0;
else
raddr_gray_reg <= raddr_gray;
end
//打两拍 读时钟和写时钟是不同步的。用对比读写地址的方法产生空满信号时,要进行跨时钟域处理。
reg [DEPTH_WK:0] waddr_gray_reg1 , waddr_gray_reg2;
reg [DEPTH_WK:0] raddr_gray_reg1 , raddr_gray_reg2;
always @ (posedge wclk or negedge wrstn) begin
if(!wrstn) begin
raddr_gray_reg1 <= 'd0;
raddr_gray_reg2 <= 'd0;
end
else begin
raddr_gray_reg1 <= raddr_gray_reg;
raddr_gray_reg2 <= raddr_gray_reg1;
end
end
always @ (posedge rclk or negedge rrstn) begin
if(!rrstn) begin
waddr_gray_reg1 <= 'd0;
waddr_gray_reg2 <= 'd0;
end
else begin
waddr_gray_reg1 <= waddr_gray_reg;
waddr_gray_reg2 <= waddr_gray_reg1;
end
end
// *************空满信号发生器*************//
// 判断空rempty、满信号wfull
assign wfull = (waddr_gray_reg == {~raddr_gray_reg2[DEPTH_WK:DEPTH_WK-1], raddr_gray_reg2[DEPTH_WK-2:0]});
assign rempty = (raddr_gray_reg == waddr_gray_reg2);
wire[DEPTH_WK-1:0] waddr, raddr;
assign waddr= waddr_bin[DEPTH_WK-1:0] ;
assign raddr= raddr_bin[DEPTH_WK-1:0] ;
dual_port_RAM #(
.DEPTH(DEPTH),
.WIDTH(WIDTH)
)
myRAM(
.wclk (wclk ),
.wenc (wenc ),
.waddr(waddr_bin),
.wdata(wdata),
.rclk (rclk ),
.renc (renc ),
.raddr(raddr_bin),
.rdata(rdata)
);
endmodule