实现原理参考:异步FIFO---Verilog实现_alangaixiaoxiao的博客-CSDN博客_异步fifo
代码参考:IC基础(一):异步FIFO_MaoChuangAn的博客-CSDN博客_异步fifo
1.异步FIFO设计的关键点:
(1)二进制码到格雷码的转化:
二进制码的最高位不变,其余位分别与自己的左边一位进行异或,得到的就是格雷码。也就是将二进制码右移一位后再与自己异或,就得到了对应的格雷码。
即: assign gray = ( bin >> 1 ) ^ bin ;
(2)时钟域的同步:
二进制地址对应的格雷码从FIFO判空模块到FIFO判满模块,经过同步模块进行了打一拍的延迟完成时钟的同步。判满模块到判空模块同理。
(3)通过格雷码,FIFO判满判空的逻辑:
判满:最高位和次高位不同,其它都相同。即:
assign wfull_val = ( wgraynext == { ~wq2_rptr [ ADDR_SIZE: ADDR_SIZE - 1 ], wq2_rptr [ ADDR_SIZE-2: 0 ] } )
判满:格雷码完全相同。即:
assign rempty_val = ( rgraynext == rq2_wptr );
2.各模块作用及Verilog代码详解:
(图摘自篇头博客,有改动)
(1)双口RAM模块
作用:用作存储模块,输入的读写地址都是二进制的。最高位用于存放空/满信号。
端口说明:
wclken: 写使能
wclk: 写时钟
raddr: 读地址
waddr: 写地址
wdata: 写入RAM的数据
rdata: 从RAM读出的数据
module DualRAM #(
parameter DATA_SIZE = 8, // 数据位宽
parameter ADDR_SIZE = 4 // FIFO地址深度
)(
input wclken, // 写使能
input wclk, // 写时钟
input [ADDR_SIZE - 1: 0] raddr, // 读地址
input [ADDR_SIZE - 1: 0] waddr, // 写地址
input [DATA_SIZE - 1: 0] wdata, // 写入RAM的数据
output [DATA_SIZE - 1: 0] rdata // 从RAM读出的数据
);
localparam RAM_DEPTH = 1 << ADDR_SIZE; // RAM深度,最高位存放判定空满信号指针
reg [DATA_SIZE-1: 0] mem [0:RAM_DEPTH-1];// 开辟内存
// 写时序
always @ ( posedge wclk ) begin
if ( wclken == 1'b1 ) begin
mem[ waddr ] <= wdata; // 写使能信号为高,将数据写入
end
else begin
mem[ waddr ] <= mem[ waddr ]; // 写使能没来,保持
end
end
assign rdata = mem [ raddr ]; // 读地址来,直接给出数据
endmodule
(2)FIFO判空模块
作用:输入读使能信号,产生对应二进制地址输出到RAM模块,并将二进制地址转化为相应的格雷码输出到同步模块,同步模块将该读地址同步到写时钟域中。
注意图中该模块传至同步模块的信号为二进制地址对应的格雷码。
端口说明:
rclk : 读时钟信号
rinc : 读使能信号。每给一个读使能信号,读地址加一
rrst_n: 复位信号
rq2_wptr:同步模块传入的写指针格雷码
rempty : 输出的FIFO空信号
raddr : 输出到RAM的二进制读地址
rptr : 输出到写时钟域的格雷码
module rptr_empty # (
parameter ADDR_SIZE = 4
)
(
input rclk,
input rinc, // 读使能信号,每给一个读使能信号,读地址加一
input rrst_n,
input [ ADDR_SIZE :0 ] rq2_wptr, // 同步模块传入的写指针格雷码
output reg rempty,
output [ ADDR_SIZE - 1:0 ] raddr, // 输出到RAM的读地址
output reg [ ADDR_SIZE :0 ] rptr // 输出到写时钟域的格雷码: 比地址多一位,最高位用来判断空满状态
);
reg [ ADDR_SIZE: 0 ] rbin; // 二进制地址
wire [ ADDR_SIZE: 0 ] rgraynext, rbinnext; // 二进制和格雷码地址
wire rempty_val;
//---------- 地址逻辑 -------------//
always @ ( posedge rclk or negedge rrst_n ) begin
if ( !rrst_n ) begin
rbin <= 0;
rptr <= 0;
end
else begin
rbin <= rbinnext; // 时钟来,更新二进制地址
rptr <= rgraynext; // 更新对应地址的格雷码
end
end
// 地址产生逻辑
assign rbinnext = !rempty ? ( rbin + rinc ): rbin; // 二进制地址更新逻辑: 若FIFO非空, 地址为当前地址 + 读使能(即地址加一); FIFO空则地址不更新
assign rgraynext = ( rbinnext >> 1 ) ^ ( rbinnext ); // 格雷码地址产生:二进制右移一位后异或
assign raddr = rbin[ ADDR_SIZE - 1: 0 ]; // 读地址传入 RAM
// FIFO 判空
assign rempty_val = ( rgraynext == rq2_wptr ); // 判空逻辑:写指针的格雷码和读指针的格雷码完全一样,则空
// 判空信号rempty判断时序
always @ ( posedge rclk or negedge rrst_n ) begin
if ( !rrst_n )
rempty <= 1'b1; // 复位时,FIFO为空
else
rempty <= rempty_val; // 需要再用一个信号 rempty_val写入时序逻辑
end
endmodule
(3)读时钟域到写时钟域同步模块
作用:将FIFO判空模块生成的格雷码地址打一拍的延迟,然后将该地址输出到写时钟域。
端口说明:
rptr : 判空模块传入的格雷码地址指针
wclk : 外部传入的写时钟信号
wrst_n: 外部传入的写复位信号
wq2_rptr:输出到判满模块的格雷码地址指针
module sync_r2w # (
parameter ADDR_SIZE = 4
)
(
input [ ADDR_SIZE: 0 ] rptr, // 判空模块传入的格雷码地址指针
input wclk, // 外部传入的写时钟信号
input wrst_n, // 外部传入的写复位信号
output reg [ ADDR_SIZE: 0 ] wq2_rptr // 输出到判满模块的格雷码地址指针
);
reg [ ADDR_SIZE: 0 ] wq1_rptr; // 该寄存器用于生成打一拍的延迟
// D触发器,两级同步
always @ ( posedge wclk or negedge wrst_n ) begin
if ( !wrst_n )
{ wq2_rptr, wq1_rptr } <= 0;
else // 相当于将输入的地址延迟一个时钟周期:
{ wq2_rptr, wq1_rptr } <= { wq1_rptr, rptr }; // 第一个周期把判空模块的地址指针给到打拍寄存器,第二个周期再取出给输出信号
end
endmodule
(4)判满模块和另一个同步模块同理,直接贴代码:
判满模块:
module wptr_full# (
parameter ADDR_SIZE = 4
)
(
input wclk,
input winc,
input wrst_n,
input [ ADDR_SIZE :0 ] wq2_rptr,
output reg wfull,
output [ ADDR_SIZE - 1:0 ] waddr, // 输出到RAM的读地址
output reg [ ADDR_SIZE :0 ] wptr // 输出到写时钟域的格雷码
);
reg [ ADDR_SIZE: 0 ] wbin; // 二进制地址
wire [ ADDR_SIZE: 0 ] wgraynext, wbinnext; // 二进制和格雷码地址
wire wfull_val;
//---------- 地址逻辑 -------------//
always @ ( posedge wclk or negedge wrst_n ) begin
if ( !wrst_n ) begin
wbin <= 0;
wptr <= 0;
end
else begin
wbin <= wbinnext;
wptr <= wgraynext;
end
end
// 地址产生逻辑
assign wbinnext = !wfull ? ( wbin + winc ): wbin;
assign wgraynext = ( wbinnext >> 1 ) ^ ( wbinnext );
assign waddr = wbin[ ADDR_SIZE - 1: 0 ];
// FIFO 判满
assign wfull_val = ( wgraynext == { ~wq2_rptr [ ADDR_SIZE: ADDR_SIZE - 1 ], wq2_rptr [ ADDR_SIZE-2: 0 ] } );//最高两位取反,然后再判断
always @ ( posedge wclk or negedge wrst_n ) begin
if ( !wrst_n )
wfull <= 1'b0;
else
wfull <= wfull_val;
end
endmodule
同步模块:
// 写指针同步到读时钟模块
module sync_w2r # (
parameter ADDR_SIZE = 4
)
(
input [ ADDR_SIZE: 0 ] wptr, // 判满模块产生的写地址格雷码
input rclk,
input rrst_n,
output reg [ ADDR_SIZE: 0 ] rq2_wptr // 输出到读时钟域的写地址格雷码
);
reg [ ADDR_SIZE: 0 ] rq1_wptr; // 打一拍的延迟
// D触发器,两级同步
always @ ( posedge rclk or negedge rrst_n ) begin
if ( !rrst_n )
{ rq2_wptr, rq1_wptr } <= 0;
else
{ rq2_wptr, rq1_wptr } <= { rq1_wptr, wptr }; // 相当于将输入的地址延迟一个时钟周期
end
endmodule
(5)顶层模块进行连线:
module AsyncFIFO # (
parameter ADDR_SIZE = 4,
parameter DATA_SIZE = 8
)
(
input [ DATA_SIZE - 1: 0 ] wdata,
input winc,
input wclk,
input wrst_n,
input rinc,
input rclk,
input rrst_n,
output [ DATA_SIZE - 1: 0 ] rdata,
output wfull,
output rempty
);
wire [ ADDR_SIZE - 1: 0 ] waddr, raddr;
wire [ ADDR_SIZE : 0 ] wptr, rptr, wq2_rptr, rq2_wptr;
sync_r2w # (
.ADDR_SIZE ( ADDR_SIZE )
)
I1_sync_r2w (
.rptr ( rptr ),
.wclk ( wclk ),
.wrst_n ( wrst_n ),
.wq2_rptr ( wq2_rptr )
);
sync_w2r # (
.ADDR_SIZE ( ADDR_SIZE )
)
I2_sync_w2r (
.wptr ( wptr ),
.rclk ( rclk ),
.rrst_n ( rrst_n ),
.rq2_wptr ( rq2_wptr )
);
DualRAM # (
.DATA_SIZE ( DATA_SIZE ), // 数据位宽
.ADDR_SIZE ( ADDR_SIZE ) // FIFO地址深度
)
I3_DualRAM (
.wclken ( winc ), // 写使能
.wclk ( wclk ), // 写时钟
.raddr ( raddr ), // 读时钟
.waddr ( waddr ), // 写地址
.wdata ( wdata ), // 写入RAM的数据
.rdata ( rdata ) // 从RAM读出的数据
);
rptr_empty # (
.ADDR_SIZE ( ADDR_SIZE )
)
I4_rptr_empty (
.rclk ( rclk ),
.rinc ( rinc ),
.rrst_n ( rrst_n ),
.rq2_wptr ( rq2_wptr ),
.rempty ( rempty ),
.raddr ( raddr ), // 输出到RAM的读地址
.rptr ( rptr ) // 输出到写时钟域的格雷码
);
wptr_full# (
.ADDR_SIZE ( ADDR_SIZE )
)
I5_wptr_full (
.wclk ( wclk ),
.winc ( winc ),
.wrst_n ( wrst_n ),
.wq2_rptr ( wq2_rptr ),
.wfull ( wfull ),
.waddr ( waddr ), // 输出到RAM的读地址
.wptr ( wptr ) // 输出到写时钟域的格雷码
);
endmodule
(6)testbench
module tb();
parameter DATA_SIZE = 16;
parameter ADDR_SIZE = 16;
reg [ DATA_SIZE - 1: 0] wdata;
reg winc, wclk, wrst_n;
reg rinc, rclk, rrst_n;
wire [ DATA_SIZE - 1: 0 ] rdata;
wire wfull;
wire rempty;
integer i=0;
AsyncFIFO # (
.ADDR_SIZE ( ADDR_SIZE ),
.DATA_SIZE ( DATA_SIZE )
)
u_fifo (
.rdata ( rdata ),
.wfull ( wfull ),
.rempty ( rempty ),
.wdata ( wdata ),
.winc ( winc ),
.wclk ( wclk ),
.wrst_n ( wrst_n ),
.rinc ( rinc ),
.rclk ( rclk ),
.rrst_n ( rrst_n )
);
localparam CYCLE = 20;
localparam CYCLE1 = 40;
initial begin
wclk = 0;
forever
# ( CYCLE / 2 )
wclk =~ wclk;
end
initial begin
rclk = 0;
forever
# ( CYCLE1 / 2)
rclk =~ rclk;
end
initial begin
wrst_n = 1;
#2;
wrst_n = 0;
# ( CYCLE * 3 );
wrst_n = 1;
end
initial begin
rrst_n = 1;
#2;
rrst_n = 0;
# ( CYCLE * 3 );
rrst_n = 1;
end
always @ ( posedge wclk or negedge wrst_n ) begin
if ( wrst_n == 1'b0 ) begin
i <= 0;
end
else if ( !wfull ) begin
i = i+1;
end
else begin
i <= i;
end
end
always @ ( rempty or rrst_n ) begin
if ( rrst_n == 1'b0 ) begin
rinc = 1'b0;
end
else if ( !rempty ) begin
rinc = 1'b1;
end
else
rinc = 1'b0;
end
always @ ( wfull or wrst_n ) begin
if ( wrst_n )
winc = 1'b0;
if ( !wfull )
winc = 1'b1;
else
winc = 1'b0;
end
always @ ( * ) begin
if ( !wfull )
wdata = i;
else
wdata = 0;
end
endmodule
RTL图:
仿真图: