异步FIFO介绍 : https://www.cnblogs.com/IClearner/p/6579754.html
//==============================================================================
// Author : chauncey_wu
// Email : chauncey_wu@163.com
//==============================================================================
// File : Asychronous_fifo.v
// Create :
// Last edit :
// Editor :
// Revision : V1.0
// Description :
//==============================================================================
`timescale 1ns/1ns
module asyc_fifo #(
//默认生成位宽为4,深度为4的fifo
parameter WDATA_WIDTH = 4, //读数据位宽
RDATA_WIDTH = 4, //写数据位宽
DEEP_WIDTH = 4, //FIFO深度,存疑:fifo深度和地址位宽是什么关系
ALMOST_DEEP = 1, //读写地址相差为1则认为,写满/读空
TCQ = 2
)
(
//写时钟域
input wire wclk,
input wire wrst_n,
input wire winc, //外部winc, //外部输入的写使能信号
input wire [WDATA_WIDTH-1:0] wdata,
output reg wfull,
output reg wfull_almost,
//读时钟域
input wire rclk,
input wire rrst_n,
input wire rinc,
output wire [RDATA_WIDTH-1:0] rdata,
output reg rempty,
output reg rempty_almost
);
//中间变量
reg [DEEP_WIDTH:0] waddr;//写时钟域地址,最高位位回卷标志位,0表示没有回卷,1表示有回卷
reg [DEEP_WIDTH:0] raddr;
wire [DEEP_WIDTH:0] wptr; //要同步到读时钟域的写指针,最高位为回卷标志位
wire [DEEP_WIDTH:0] rptr; //要同步到写时钟域的读指针,格雷码形式
reg [DEEP_WIDTH:0] wq1_rptr; //
reg [DEEP_WIDTH:0] wq2_rptr; //格雷码读地址同步到写时钟域
reg [DEEP_WIDTH:0] rq1_wptr; //
reg [DEEP_WIDTH:0] rq2_wptr;
reg [DEEP_WIDTH:0] wq2_rptr_b; //格雷码读地址同步到写时钟域,再转化为二进制码
reg [DEEP_WIDTH:0] rq2_wptr_b;
reg [DEEP_WIDTH:0] i; //格雷码转转为二进制码时的位数计数
reg [DEEP_WIDTH:0] j;
wire wclken; //写RAM使能信号
wire rclkwn;
//------------------读时钟域---------------------------//
//二进制读地址转化为格雷码,并同步到写时钟域,在转换为二进制码与实际写地址比较,产生满信号
assign rptr = (raddr>>1)^raddr; //格雷码的值只需要在原来的二进制的基础上右移一位再与原来的二进制值异或即可得到
always @(posedge wclk)
begin
if (wrst_n)
begin
wq1_rptr <= #TCQ {DEEP_WIDTH{1'b0}};
wq2_rptr <= #TCQ {DEEP_WIDTH{1'b0}};
end
else
begin
wq1_rptr <= #TCQ rptr;
wq2_rptr <= #TCQ wq1_rptr;
end
end
always @(wq2_rptr) //格雷码转二进制码
begin
wq2_rptr_b[4] = #TCQ wq2_rptr[4];
wq2_rptr_b[3] = #TCQ wq2_rptr[3]^wq2_rptr[4];
wq2_rptr_b[2] = #TCQ wq2_rptr[2]^wq2_rptr[3]^wq2_rptr[4];
wq2_rptr_b[1] = #TCQ wq2_rptr[1]^wq2_rptr[2]^wq2_rptr[3]^wq2_rptr[4];
wq2_rptr_b[0] = #TCQ wq2_rptr[0]^wq2_rptr[1]^wq2_rptr[2]^wq2_rptr[3]^wq2_rptr[4];
end
//--------------------写满信、将满号的产生------------------//
always @(posedge wclk)
begin
if (wrst_n)
begin
wfull <= #TCQ 1'b0;
wfull_almost <= #TCQ 1'b0;
waddr <= #TCQ {DEEP_WIDTH{1'b0}};
end
else if (winc)
begin //标志位不同且地址为相同则为写满,
if ((waddr[DEEP_WIDTH-1:0] == wq2_rptr_b[DEEP_WIDTH-1:0]) && (waddr[DEEP_WIDTH] != wq2_rptr_b[DEEP_WIDTH]))
begin
wfull <= #TCQ 1'b1;
wfull_almost <= #TCQ 1'b1;
end //标志位相同且地址为相差为1则为将满
else if ((waddr[DEEP_WIDTH-1:0]-wq2_rptr_b[DEEP_WIDTH-1:0] == ALMOST_DEEP) && (waddr[DEEP_WIDTH] != wq2_rptr_b[DEEP_WIDTH]))
begin
wfull <= #TCQ 1'b0;
wfull_almost <= #TCQ 1'b1;
waddr <= #TCQ waddr + 1'b1;
end
else
begin
waddr <= #TCQ waddr + 1'b1;
end
end
end
//写满信号与写使能信号生成对DPRAM的写使能信号
assign wclken = winc & !wfull;
//***************************************************************************************************//
//------------------写时钟域---------------------------//
assign wptr = (waddr>>1)^waddr;
always @(posedge rclk)
begin
if (wrst_n)
begin
rq1_wptr <= #TCQ {DEEP_WIDTH{1'b0}};
rq2_wptr <= #TCQ {DEEP_WIDTH{1'b0}};
end
else
begin
rq1_wptr <= #TCQ wptr;
rq2_wptr <= #TCQ rq1_wptr;
end
end
always @(rq2_wptr) // 格雷码转二进制码
begin
rq2_wptr_b[4] = #TCQ rq2_wptr[4];
rq2_wptr_b[3] = #TCQ rq2_wptr[3]^rq2_wptr[4];
rq2_wptr_b[2] = #TCQ rq2_wptr[2]^rq2_wptr[3]^rq2_wptr[4];
rq2_wptr_b[1] = #TCQ rq2_wptr[1]^rq2_wptr[2]^rq2_wptr[3]^rq2_wptr[4];
rq2_wptr_b[0] = #TCQ rq2_wptr[0]^rq2_wptr[1]^rq2_wptr[2]^rq2_wptr[3]^rq2_wptr[4];
end
//--------------------读空、将空号的产生------------------//
always @(posedge rclk)
begin
if (rrst_n)
begin
rempty <= #TCQ 1'b0;
rempty_almost <= #TCQ 1'b0;
raddr <= #TCQ {DEEP_WIDTH{1'b0}};
end
//else if (rinc)
//begin //标志位xiang同且地址为相同则为读空,
else if (raddr[DEEP_WIDTH:0] == rq2_wptr_b[DEEP_WIDTH:0])
begin
rempty <= #TCQ 1'b1;
rempty_almost <= #TCQ 1'b1;
end //标志位相同且地址为相差为1则为将读空
else if ((raddr[DEEP_WIDTH:0] - rq2_wptr_b[DEEP_WIDTH:0]) == ALMOST_DEEP)
begin
rempty <= #TCQ 1'b0;
rempty_almost <= #TCQ 1'b1;
raddr <= #TCQ raddr + 1'b1;
end
else
begin
rempty <= #TCQ 1'b0;
raddr <= #TCQ raddr + 1'b1;
end
//end
end
//写满信号与写使能信号生成对DPRAM的写使能信号
assign rclken = rinc & !rempty;
//------------------------------------------------------//
//----------- Begin Cut here for INSTANTIATION Template ---// INST_TAG
blk_mem_gen_0 simple_DPRAM (
.clka(wclk), // input wire clka
.ena(wclken), // input wire ena
.wea(wclken), // input wire [0 : 0] wea
.addra(waddr), // input wire [2 : 0] addra
.dina(wdata), // input wire [7 : 0] dina
.clkb(rclk), // input wire clkb
.enb(rclken), // input wire enb
.addrb(raddr), // input wire [2 : 0] addrb
.doutb(rdata) // output wire [7 : 0] doutb
);
endmodule
简单的仿真 TESTBENCH :
//timescale
`timescale 1ns/1ns
module tb_module #(
parameter DATA_WIDTH = 4 )();
//the Internal motivation variable(register) and output wire
wire wfull ;
wire wfull_almost ;
reg [DATA_WIDTH-1:0] wdata ;
reg winc ;
reg wclk ;
reg wrst_n ;
//读时钟域信号
wire rempty ;
wire rempty_almost ;
wire [DATA_WIDTH-1:0] rdata ;
reg rinc ;
reg rclk ;
reg rrst_n ;
//the External motivation storage variable
//Sub module signal,example: wire [1:0] xxx == xxx_inst.xxx_inst.xxx;
// Global variable initialization ,such as 'clk'、'rst_n'
initial begin
#0 rrst_n = 1;
wrst_n = 1 ;
wclk = 0;
rclk = 0;
// rinc = 0 ;
// winc = 0 ;
//wdata = 0 ;
#20 rrst_n = 0 ;
#25 wrst_n = 0 ;
end
//cloclk signal generation
always #15 rclk = ~rclk ;
always #10 wclk = ~wclk ;
//Internal motivation variable initialization
//initial begin
//end
// winc generate
always @(posedge wclk or wrst_n)begin
if( wrst_n == 1'b1 )begin
winc = 1'b0;
end
else if( wfull_almost )
winc = 1'b0;
else
winc = 1'b1 ;
end
// rinc generate
always @(posedge rclk or rrst_n)begin
if( rrst_n == 1'b1 )begin
rinc = 1'b0 ;
end
else if( rempty )
rinc = 1'b0;
else
rinc = 1'b1 ;
end
// wdata
always @(posedge wclk or negedge wrst_n)begin
if( wrst_n == 1'b1 )begin
wdata = 4'd0 ;
end
else if( winc )begin
wdata = wdata + 1'b1;
end
end
//Cases of sub module xxxx xxxx_inst(.(),.(), ... ,.());
asyc_fifo Async_FIFO_inst(
//写时钟域信号
.wfull ( wfull ),
.wfull_almost ( wfull_almost ),
.wdata ( wdata ),
.winc ( winc ),
.wclk ( wclk ),
.wrst_n ( wrst_n ),
//读时钟域信号
.rempty ( rempty ),
.rempty_almost ( rempty_almost ),
.rdata ( rdata ),
.rinc ( rinc ),
.rclk ( rclk ),
.rrst_n ( rrst_n )
);
// Internal motivation variable assignment using task or random
/* example
task data_assign(xx); | task rand_bit();
integer xx,xx,...; | integer i;
begin | begin
for( ; ; )begin | for(i=0; i<255; i=i+1)begin
@(posedge clock) | @(posedge sclk);
Internal motivation variable <= xxxxx; | Internal motivation variable <={$random} %2;
end | end
end | end
endtask | endtask
*/
endmodule