一、前言
异步FIFO结构如上图所示
1. 第1部分是双口RAM,用于数据的存储。
2. 第2部分是数据写入控制器
3. 第3部分是数据读取控制器
4. 读指针同步器
使用写时钟的两级触发器采集读指针,输出到数据写入控制器。
5. 写指针同步器
使用读时钟的两级触发器采集写指针,输出到数据读取控制器。
拟采用的空满判断的方式是用格雷码的比较来产生空满信号。
如上图所示,使用4位格雷码作为深度为8的FIFO的读写指针。
将格雷码转换成四位二进制数,使用二进制数低三位作为访问RAM的地址。
与同步FIFO类似,当读写指针相等时,得出FIFO为空。
当写指针比读指针多循环RAM一周时,此时读写指针的最高位和次高位都相反,其余位相同,FIFO为满。
二、格雷码计数
异步FIFO是通过比较读指针和写指针的位置来判断FIFO是否写满或读空,但是不可以直接比较两个指针,因为他们属于不同时钟域,直接相比可能会产生亚稳态从而引起误判,这就需要将两个指针分别进行跨时钟域处理,然后再判断。但是存在一个问题,自然二进制编码的地址在状态翻转的时候是多位变化,这就可能会产生竞争现象并有可能被另一个时钟域的触发器采样到,从而引发误判。最容易的解决方法就是将自然二进制编码的地址转为格雷码编码的地址。
格雷码如果每2^n个数一循环,首尾两个格雷码仍然是只有一位变化,如果不是2^n个数,那么首尾数据就不是仅有一位变化,那就不是真正的格雷码,所以这也是异步FIFO的存储深度只能是2^n的原因。
自然二进制码转换成二进制格雷码,其法则是保留自然二进制码的最高位作为格雷码的最高位,而次高位格雷码为二进制码的高位与次高位相异或,而格雷码其余各位与次高位的求法相类似。 如下图:
三、代码设计
3.1 二进制转格雷码
//生成格雷码
wire [ADDR_WIDTH:0] waddr_gray;
wire [ADDR_WIDTH:0] raddr_gray;
assign waddr_gray=waddr_bin^(waddr_bin>>1);
assign raddr_gray=raddr_bin^(raddr_bin>>1);
3.2 格雷码打拍
reg [ADDR_WIDTH:0] waddr_gray1;
reg [ADDR_WIDTH:0] waddr_gray2;
reg [ADDR_WIDTH:0] waddr_gray3;
reg [ADDR_WIDTH:0] raddr_gray1;
reg [ADDR_WIDTH:0] raddr_gray2;
reg [ADDR_WIDTH:0] raddr_gray3;
always@(posedge wclk or negedge wrstn)begin
if(!wrstn)
waddr_gray1<='d0;
else
waddr_gray1<=waddr_gray;
end
always@(posedge rclk or negedge rrstn)begin //写信号在读时钟下打两拍
if(!rrstn)begin
waddr_gray2<='b0;
waddr_gray3<='b0;
end
else begin
waddr_gray2<=waddr_gray1;
waddr_gray3<=waddr_gray2;
end
end
always@(posedge rclk or negedge rrstn)begin
if(!rrstn)
raddr_gray1<='d0;
else
raddr_gray1<=raddr_gray;
end
always@(posedge wclk or negedge wrstn)begin //读信号在写时钟下打两拍
if(!wrstn)begin
raddr_gray2<='b0;
raddr_gray3<='b0;
end
else begin
raddr_gray2<=raddr_gray1;
raddr_gray3<=raddr_gray2;
end
end
相对应时钟域赋值后,异时钟域打两拍消除亚稳态。
3.3 空满信号判断
assign wfull=(!waddr_gray3[ADDR_WIDTH:ADDR_WIDTH-1]==raddr_gray3[ADDR_WIDTH:ADDR_WIDTH-1] && waddr_gray3[ADDR_WIDTH-2:0]==raddr_gray3[ADDR_WIDTH-2:0])?1'b0:1'b0;
assign rempty=(waddr_gray3[ADDR_WIDTH:0]==raddr_gray3[ADDR_WIDTH:0])?1'b0:1'b0;
空信号时,读地址与写地址相同(差为0)
满信号格雷码判断方式为:前两位相反,其余位置相同 。当读地址和写地址在不同圈时,格雷码的最高位也会不同。再根据格雷码的对称性,此时如果次高位不同而其他位相同,说明读写地址的自然二进制码是相通的。
3.4 整体设计
`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
);
//定义读写指针
parameter ADDR_WIDTH=$clog2(DEPTH);
reg [ADDR_WIDTH:0] waddr_bin;
reg [ADDR_WIDTH:0] raddr_bin;
always@(posedge wclk or negedge wrstn)begin
if(!wrstn)
waddr_bin<=0;
else if(winc && !wfull)
waddr_bin<=waddr_bin+1;
else
waddr_bin<=waddr_bin;
end
always@(posedge rclk or negedge rrstn)begin
if(!rrstn)
raddr_bin<=0;
else if(rinc && !rempty)
raddr_bin<=raddr_bin+1;
else
raddr_bin<=raddr_bin;
end
//生成格雷码
wire [ADDR_WIDTH:0] waddr_gray;
wire [ADDR_WIDTH:0] raddr_gray;
assign waddr_gray=waddr_bin^(waddr_bin>>1);
assign raddr_gray=raddr_bin^(raddr_bin>>1);
//跨时钟格雷码打拍
reg [ADDR_WIDTH:0] waddr_gray1;
reg [ADDR_WIDTH:0] waddr_gray2;
reg [ADDR_WIDTH:0] waddr_gray3;
reg [ADDR_WIDTH:0] raddr_gray1;
reg [ADDR_WIDTH:0] raddr_gray2;
reg [ADDR_WIDTH:0] raddr_gray3;
always@(posedge wclk or negedge wrstn)begin
if(!wrstn)
waddr_gray1<='d0;
else
waddr_gray1<=waddr_gray;
end
always@(posedge rclk or negedge rrstn)begin //写信号在读时钟下打两拍
if(!rrstn)begin
waddr_gray2<='b0;
waddr_gray3<='b0;
end
else begin
waddr_gray2<=waddr_gray1;
waddr_gray3<=waddr_gray2;
end
end
always@(posedge rclk or negedge rrstn)begin
if(!rrstn)
raddr_gray1<='d0;
else
raddr_gray1<=raddr_gray;
end
always@(posedge wclk or negedge wrstn)begin //读信号在写时钟下打两拍
if(!wrstn)begin
raddr_gray2<='b0;
raddr_gray3<='b0;
end
else begin
raddr_gray2<=raddr_gray1;
raddr_gray3<=raddr_gray2;
end
end
assign wfull=(!waddr_gray3[ADDR_WIDTH:ADDR_WIDTH-1]==raddr_gray3[ADDR_WIDTH:ADDR_WIDTH-1] && waddr_gray3[ADDR_WIDTH-2:0]==raddr_gray3[ADDR_WIDTH-2:0])?1'b0:1'b0;
assign rempty=(waddr_gray3[ADDR_WIDTH:0]==raddr_gray3[ADDR_WIDTH:0])?1'b0:1'b0;
wire wen;
wire ren;
assign wen= winc && !wfull;
assign ren= rinc && !rempty;
dual_port_RAM #(
.DEPTH(DEPTH),
.WIDTH(WIDTH)
)
myRAM(
.wclk (wclk ),
.wenc (wen ),
.waddr(waddr),
.wdata(wdata),
.rclk (rclk ),
.renc (ren ),
.raddr(raddr),
.rdata(rdata)
);
endmodule
参考:https://blog.csdn.net/qq_40807206/article/details/109555162