文章:Simulation and Synthesis Techniques for Asynchronous

FIFO Design

跨时钟域之异步FIFO_verilog

异步FIFO的读写指针


写指针
写指针指向当前将要写入数据的位置,复位之后,读写指针被置零。
执行写操作的时候,向写指针指向的存储区写入数据,之后写指针加1,指向接下来要被写入数据的位置。
On a FIFO-write operation, the memory location that is pointed to by the write pointer is written, and then the write pointer is incremented to point to the next location to be written.
读指针:
读指针指向当前要被读取数据的位置,复位时,读写指针被置零,FIFO为空读指针指向一个无效的数据(FIFO为空,empty信号有效——拉高)。当第一个有效数据被写入FIFO之后,写指针增加,empty flag信号被拉低,且读指针一直指向FIFO第一个数据的存储区域。接收逻辑没必要使用两个时钟周期读取数据,这样会使得效率很低。
FIFO空标志:
当读写指针是相等的时候:分两种情况
1.当读写指针执行复位操作的时候。
2.当读指针赶上写指针的时候,最后一笔数据从FIFO读出后FIFO为空
FIFO满标志:
读写指针相等,当FIFO里面的写指针写满一圈之后又转回到和读指针同样的位置。有个问题,读写指针相等的时候怎么判断FIFO是empty还是full?
设计的时候增加一位bit去辅助判断FIFO是空还是满。当写指针超过FIFO的最大寻址范围时,写指针将使辅助位zhi高,其余位为0.
FIFO满的时候:读写指针的低位(n-1位bit)相等,高位(第n位bit)不同。
FIFO空的时候,读写指针的低位和高位都相等。(针对二进制)
但是二进制FIFO指针综合电路复杂,一般采用**格雷码**,文章中采用二进制转换格雷码的方法,判断FIFO的空满标志4位二进制格雷码,有效地址位为三位。
二进制转换为格雷码的算法:rgraynext = (rbinnext>>1) ^ rbinnext;


1.顶层模块fifo:例化各个子模块


//顶层模块 实例化各个子模块
module fifo
#(
parameter DSIZE = 8, //读写数据位宽均设置为8位
parameter ASIZE = 4 // 存储地址位宽设置
)
(
output [DSIZE-1:0] rdata,
output wfull,
output rempty,
input [DSIZE-1:0] wdata,
input winc, wclk, wrst_n,
input rinc, rclk, rrst_n
);

wire [ASIZE-1:0] waddr, raddr;
wire [ASIZE:0] wptr, rptr, wq2_rptr, rq2_wptr;// 内部线网

// synchronize the read pointer into the write-clock domain
sync_r2w sync_r2w
(
.wq2_rptr (wq2_rptr),
.rptr (rptr ),
.wclk (wclk ),
.wrst_n (wrst_n )
);

// synchronize the write pointer into the read-clock domain
sync_w2r sync_w2r
(
.rq2_wptr(rq2_wptr),
.wptr(wptr),
.rclk(rclk),
.rrst_n(rrst_n)
);

//this is the FIFO memory buffer that is accessed by both the write and read clock domains.
//This buffer is most likely an instantiated, synchronous dual-port RAM.
//Other memory styles can be adapted to function as the FIFO buffer.
fifomem
#(DSIZE, ASIZE)
fifomem
(
.rdata(rdata),
.wdata(wdata),
.waddr(waddr),
.raddr(raddr),
.wclken(winc),
.wfull(wfull),
.wclk(wclk)
);

//this module is completely synchronous to the read-clock domain and contains the FIFO read pointer and empty-flag logic.
rptr_empty
#(ASIZE)
rptr_empty
(
.rempty(rempty),
.raddr(raddr),
.rptr(rptr),
.rq2_wptr(rq2_wptr),
.rinc(rinc),
.rclk(rclk),
.rrst_n(rrst_n)
);

//this module is completely synchronous to the write-clock domain and contains the FIFO write pointer and full-flag logic
wptr_full
#(ASIZE)
wptr_full
(
.wfull(wfull),
.waddr(waddr),
.wptr(wptr),
.wq2_rptr(wq2_rptr),
.winc(winc),
.wclk(wclk),
.wrst_n(wrst_n)
);
endmodule


2.时钟域同步模块sync_r2w:读指针同步到写时钟域wclk


// 采用两级寄存器同步读指针到写时钟域
module sync_r2w
#(
parameter ADDRSIZE = 4
)
(
output reg [ADDRSIZE:0] wq2_rptr, //读指针同步到写时钟域
input [ADDRSIZE:0] rptr, // 格雷码形式的读指针,格雷码的好处后面会细说
input wclk, wrst_n
);

reg [ADDRSIZE:0] wq1_rptr;

always @(posedge wclk or negedge wrst_n)
if (!wrst_n) begin
wq1_rptr <= 0;
wq2_rptr <= 0;
end
else begin
wq1_rptr<= rptr;
wq2_rptr<=wq1_rptr;
end
endmodule


3.时钟域同步模块sync_w2r:写指针同步到读时钟域rclk

//采用两级寄存器同步写指针到读时钟域
module sync_w2r
#(parameter ADDRSIZE = 4)
(
output reg [ADDRSIZE:0] rq2_wptr, //写指针同步到读时钟域
input [ADDRSIZE:0] wptr, //格雷码形式的写指针
input rclk, rrst_n
);

reg [ADDRSIZE:0] rq1_wptr;

always @(posedge rclk or negedge rrst_n)
if (!rrst_n)begin
rq1_wptr <= 0;
rq2_wptr <= 0;
end
else begin
rq1_wptr <= wptr;
rq2_wptr <= rq1_wptr;
end

endmodule


4.存储模块

//存储模块
module fifomem
#(
parameter DATASIZE = 8, // Memory data word width
parameter ADDRSIZE = 4 // 深度为8即地址为3位即可,这里多定义一位的原因是用来判断是空还是满,详细在后文讲到
) // Number of mem address bits
(
output [DATASIZE-1:0] rdata,
input [DATASIZE-1:0] wdata,
input [ADDRSIZE-1:0] waddr, raddr,
input wclken, wfull, wclk
);

////////////////////////////////这部分没用到,可以单独写一个模块来调用//////////////
`ifdef RAM //可以调用一个RAM IP核
// instantiation of a vendor's dual-port RAM
my_ram mem
(
.dout(rdata),
.din(wdata),
.waddr(waddr),
.raddr(raddr),
.wclken(wclken),
.wclken_n(wfull),
.clk(wclk)
);
//////////////////////////这部分没用到,可以单独写一个模块来调用//////////////////

`else //用数组生成存储体
// RTL Verilog memory model
localparam DEPTH = 1<<ADDRSIZE; // 左移相当于乘法,2^4 将1左移4位
reg [DATASIZE-1:0] mem [0:DEPTH-1]; //生成2^4个位宽位8的数组
assign rdata = mem[raddr];
always @(posedge wclk) //当写使能有效且还未写满的时候将数据写入存储实体中,注意这里是与wclk同步的
if (wclken && !wfull)
mem[waddr] <= wdata;
`endif
endmodule


5. rptr_empty模块:产生rempty和raddr信号

//产生empty信号和raddar信号的模块
module rptr_empty
#(
parameter ADDRSIZE = 4
)
(
output reg rempty,
output [ADDRSIZE-1:0] raddr, //二进制形式的读指针
output reg [ADDRSIZE :0] rptr, //格雷码形式的读指针
input [ADDRSIZE :0] rq2_wptr, //同步后的写指针 同步到读时钟域
input rinc, rclk, rrst_n
);
reg [ADDRSIZE:0] rbin;
wire [ADDRSIZE:0] rgraynext, rbinnext;
// GRAYSTYLE2 pointer
//将二进制的读指针与格雷码进制的读指针同步
always @(posedge rclk or negedge rrst_n)
if (!rrst_n) begin
rbin <= 0;
rptr <= 0;
end
else begin
rbin<=rbinnext; //直接作为存储实体的地址
rptr<=rgraynext;//输出到 sync_r2w.v模块,被同步到 wrclk 时钟域
end
// Memory read-address pointer (okay to use binary to address memory)
assign raddr = rbin[ADDRSIZE-1:0]; //直接作为存储实体的地址,比如连接到RAM存储实体的读地址端。
assign rbinnext = rbin + (rinc & ~rempty); //不空且有读请求的时候读指针加1,//否则输出原先地址的数据waq
assign rgraynext = (rbinnext>>1) ^ rbinnext; //将二进制的读指针转为格雷码 先右移一位然后与原二进制数异或
// FIFO empty when the next rptr == synchronized wptr or on reset
assign rempty_val = (rgraynext == rq2_wptr); //当读指针等于同步后的写指针,则为空。
always @(posedge rclk or negedge rrst_n)
if (!rrst_n)
rempty <= 1'b1;
else
rempty <= rempty_val;

endmodule


6.wfull和waddr信号产生的模块


//产生写满信号(wptr_full)以及写地址的逻辑部分
module wptr_full
#(
parameter ADDRSIZE = 4
)
(
output reg wfull,
output [ADDRSIZE-1:0] waddr,
output reg [ADDRSIZE :0] wptr,
input [ADDRSIZE :0] wq2_rptr,//同步后的读指针,注意是多了一个位,用作判断是否fifo满
input winc, wclk, wrst_n //
);
reg [ADDRSIZE:0] wbin;
wire [ADDRSIZE:0] wgraynext, wbinnext;
// GRAYSTYLE2 pointer
always @(posedge wclk or negedge wrst_n)
if (!wrst_n)
{wbin, wptr} <= 0;
else
{wbin, wptr} <= {wbinnext, wgraynext};// wptr 被同步到sync_w2r.v读时钟域rclk
// Memory write-address pointer (okay to use binary to address memory)
assign waddr = wbin[ADDRSIZE-1:0];
assign wbinnext = wbin + (winc & ~wfull);// winc为1的时候数据才能写入对应的地址中,否则即使wdata=0,数据是没有被写入地址的!
assign wgraynext = (wbinnext>>1) ^ wbinnext; //二进制转为格雷码
//-----------------------------------------------------------------
assign wfull_val = (wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]}); //当最高位和次高位不同其余位相同时则写指针超前于读指针一圈,即写满。
// assign wfull_val = ( (wgraynext[ADDRSIZE] !=wq2_rptr[ADDRSIZE])&&
// (wgraynext[ADDRSIZE-1] !=wq2_rptr[ADDRSIZE-1])&&
// (wgraynext[ADDRSIZE-2] ==wq2_rptr[ADDRSIZE-2]) );
always @(posedge wclk or negedge wrst_n)
if (!wrst_n)
wfull <= 1'b0;
else
wfull <= wfull_val;

endmodule


testbench文件


`timescale 1ns /1ns

module test();
reg [7:0] wdata;
reg winc, wclk, wrst_n;
reg rinc, rclk, rrst_n;
wire [7:0] rdata;
wire wfull;
wire rempty;

fifo

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;



//时钟周期,单位为ns,可在此修改时钟周期。

//生成本地时钟50M (写时钟50M)
initial begin
wclk = 0;
forever
#(CYCLE/2)
wclk=~wclk;
end
// 读时钟25M
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
winc <= 0;
// rinc <= 0;
end
else begin
winc <= $random;
$display ("winc=%h", winc);
// rinc <= $random;
// $display ("winc=%h", winc);
end
end

always @(posedge rclk or negedge rrst_n)begin
if(rrst_n==1'b0)begin
rinc <= 0;
end
else begin
rinc <= $random;
$display ("rinc=%h", rinc);
end
end
always@(*)begin
if(winc == 1)
wdata= $random ;
else
wdata = 0;
end
endmodule


仿真图:

跨时钟域之异步FIFO_读取数据_02

原理图:

跨时钟域之异步FIFO_数据_03