一、前言
异步,是指读写时钟频率不同,因此可以用来做跨时钟域处理。
跨时钟域处理,单比特的数据一般采用两级寄存器缓存的方式(适用于由慢到快),多比特则采用异步FIFO、异步双口RAM处理。
二、原理解析
1.空满信号的产生
空信号:读地址赶上写地址时产生空信号,因此同步写地址可以小于等于真实写地址,于是将写地址同步到读时钟域进行比较,产生真空或者虚空信号;
满信号:写地址赶上读地址时产生满信号,因此同步读地址可以小于等于真实读地址,于是将读地址同步到写时钟域进行比较,产生真满或者虚满信号;
2.格雷码的引入
在异步FIFO 中,最核心的功能是产生空满信号。以满信号为例,需要将读地址同步到写时钟内,然后将真实写地址与同步读地址做比较,如果写地址≥读地址,则产生满信号。
但在实际电路中会出现一个问题,即读地址并非单比特位宽的,在地址累加过程中可能产生竞态,即仿真图中看到不确定的中间态。以2位读地址为例,画出时序图如下:
由于器件的延迟不同,本应该顺序累加的00-01-10-11,在01-10翻转过程中可能产生竞态“11”,我们称为address bus skew。同步时地址出错如果恰好被同步写时钟采样到,则会认为此时读地址已经到“11”,因此写操作可以到写地址“10”,从而导致上一轮处于地址“10”的地址还没被读出已经被覆盖,导致读出数据错误。
因此引入格雷码,使得每次只翻转一位。
B_i=G_i^B_(i+1)
B表示二进制码,G表示格雷码。
如Read_gray={Read_addr[2]), (Read_addr[2] ^ Read_addr[1]), (Read_addr[1] ^ Read_addr[0])};
3.格雷码如何做地址比较
如果地址作顺序递增,则直接比较大小即可,但转换为格雷码后不能直接比大小。可以这么理解:真实的读写的地址本身是顺序的,格雷码地址作为真实地址的映射,也可以产生一个带顺序的数列。将读写格雷地址作一一对应, 格雷写地址与同步格雷读地址作比较产生满信号,格雷读地址与同步格雷写地址比较产生空信号。
4.同步化操作
以满信号为例,写时钟频率大于读时钟频率。需要将读地址同步到写时钟域,相当于增加采样点。
注意:提前产生满信号,即虚满,逻辑上是正确的,因为FIFO可以不写满。
三、时序分析
为了直观理解,假设地址为1、2、3,对应的格雷地址为I、II、III
该异步FIFO的时序如下:
以满信号为例,假设此刻wr_gray_next= =rd_gray_last= =I,组合逻辑产生Almost_full信号。
如以上时序分析图所示,取极限,假定写时钟频率远大于读时钟,读地址不变,则满信号被采集到需要两拍,如圆圈所示;此时存储器写地址到2,读地址到3,时序分析是正确的。
其设计精妙之处,在于对almostfull/almostempty信号的控制,
四、示例代码
`timescale 1ns / 10ps
module ASYNCFIFO
#(
parameter DATA_WIDTH = 8 ,
parameter ADDR_WIDTH = 9)(
input Fifo_rst ,
input Read_clock ,
input Write_clock ,
input Read_enable ,
input Write_enable ,
input [DATA_WIDTH-1:0] Write_data ,
output [DATA_WIDTH-1:0] Read_data ,
output Full ,
output Empty
);
//signals from RAM_CTRL
wire [ADDR_WIDTH-1:0] Read_addr;
wire [ADDR_WIDTH-1:0] Write_addr;
wire Read_allow;
wire Write_allow;
//signals from Gray
wire Emptyg;
wire Fullg;
wire Almostemptyg;
wire Almostfullg;
wire [ADDR_WIDTH-1:0] Write_addrgray;
wire [ADDR_WIDTH-1:0] Write_nextgray;
wire [ADDR_WIDTH-1:0] Read_addrgray;
wire [ADDR_WIDTH-1:0] Read_nextgray;
wire [ADDR_WIDTH-1:0] Read_lastgray;
DUALRAM U_RAM (
.Read_clock ( Read_clock ),
.Write_clock ( Write_clock ),
.Read_allow ( Read_allow ),
.Write_allow ( Write_allow ),
.Read_addr ( Read_addr ),
.Write_addr ( Write_addr ),
.Write_data ( Write_data ),
.Read_data ( Read_data )
);
RAM_CTRL ram_ctrl (
.Fifo_rst ( Fifo_rst ),
.Read_clock ( Read_clock ),
.Write_clock ( Write_clock ),
.Read_enable ( Read_enable ),
.Write_enable ( Write_enable ),
.Emptyg ( Emptyg ),
.Fullg ( Fullg ),
.Almostemptyg ( Almostemptyg ),
.Almostfullg ( Almostfullg ),
.Read_addr ( Read_addr ),
.Write_addr ( Write_addr ),
.Read_allow ( Read_allow ),
.Write_allow ( Write_allow ),
.Empty ( Empty ),
.Full ( Full )
);
Gray gray (
.Fifo_rst ( Fifo_rst ),
.Read_clock ( Read_clock ),
.Write_clock ( Write_clock ),
.Read_allow ( Read_allow ),
.Write_allow ( Write_allow ),
.Read_addr ( Read_addr ),
.Write_addr ( Write_addr ),
.Write_addrgray ( Write_addrgray ),
.Write_nextgray ( Write_nextgray ),
.Read_addrgray ( Read_addrgray ),
.Read_nextgray ( Read_nextgray ),
.Read_lastgray ( Read_lastgray ),
.Emptyg ( Emptyg ),
.Fullg ( Fullg ),
.Almostemptyg ( Almostemptyg ),
.Almostfullg ( Almostfullg )
);
endmodule
module RAM_CTRL
#(
parameter DATA_WIDTH = 8 ,
parameter ADDR_WIDTH = 9)(
input Fifo_rst,
input Read_clock,
input Write_clock,
input Read_enable,
input Write_enable,
input Emptyg,
input Fullg,
input Almostemptyg,
input Almostfullg,
output reg [ADDR_WIDTH-1:0] Read_addr,
output reg [ADDR_WIDTH-1:0] Write_addr,
output Read_allow,
output Write_allow,
output reg Empty,
output reg Full
);
assign Read_allow = (Read_enable && ! Empty);
assign Write_allow = (Write_enable && ! Full);
always @(posedge Read_clock or posedge Fifo_rst)
if (Fifo_rst)
Empty <= 1'b1;
else
Empty <= (Emptyg || (Almostemptyg && Read_enable && ! Empty));
always @(posedge Write_clock or posedge Fifo_rst)
if (Fifo_rst)
Full<= 1'b1;
else
Full <= (Fullg || (Almostfullg && Write_enable && ! Full));
always @(posedge Read_clock or posedge Fifo_rst)
if (Fifo_rst)
Read_addr <= 'b0;
else if (Read_allow)
Read_addr <= Read_addr + 'b1;
always @(posedge Write_clock or posedge Fifo_rst)
if (Fifo_rst)
Write_addr <= 'b0;
else if (Write_allow)
Write_addr <= Write_addr + 'b1;
endmodule
module Gray
#(
parameter DATA_WIDTH = 8 ,
parameter ADDR_WIDTH = 9)(
input Fifo_rst ,
input Read_clock ,
input Write_clock ,
input Read_allow ,
input Write_allow ,
input [ADDR_WIDTH-1:0] Read_addr ,
input [ADDR_WIDTH-1:0] Write_addr ,
output reg [ADDR_WIDTH-1:0] Write_addrgray ,
output reg [ADDR_WIDTH-1:0] Write_nextgray ,
output reg [ADDR_WIDTH-1:0] Read_addrgray ,
output reg [ADDR_WIDTH-1:0] Read_nextgray ,
output reg [ADDR_WIDTH-1:0] Read_lastgray ,
output reg Emptyg ,
output reg Fullg ,
output reg Almostemptyg ,
output reg Almostfullg
);
always @(posedge Read_clock or posedge Fifo_rst)
if (Fifo_rst)
Read_nextgray <= 9'b100000000;
else if (Read_allow)
Read_nextgray <= { Read_addr[8], (Read_addr[8] ^ Read_addr[7]),(Read_addr[7] ^ Read_addr[6]), (Read_addr[6] ^ Read_addr[5]),(Read_addr[5] ^ Read_addr[4]), (Read_addr[4] ^ Read_addr[3]),(Read_addr[3] ^ Read_addr[2]), (Read_addr[2] ^ Read_addr[1]), (Read_addr[1] ^ Read_addr[0]) };
always @(posedge Write_clock or posedge Fifo_rst)
if (Fifo_rst)
Write_nextgray <= 9'b100000000;
else if (Write_allow)
Write_nextgray <= { Write_addr[8], (Write_addr[8] ^ Write_addr[7]),(Write_addr[7] ^ Write_addr[6]), (Write_addr[6] ^ Write_addr[5]),(Write_addr[5] ^ Write_addr[4]), (Write_addr[4] ^ Write_addr[3]),(Write_addr[3] ^ Write_addr[2]), (Write_addr[2] ^ Write_addr[1]), (Write_addr[1] ^ Write_addr[0]) };
//提高读地址的采样频率,同步到写时钟域
always @(posedge Write_clock or posedge Fifo_rst)
if (Fifo_rst)
Read_addrgray<= 9'b100000001;
else if (Read_allow)
Read_addrgray<= Read_nextgray;
//同上,同步化
always @(posedge Write_clock or posedge Fifo_rst)
if (Fifo_rst)
Read_lastgray <= 9'b100000011;
else if (Read_allow)
Read_lastgray <= Read_addrgray;
//同上,同步化
always @(posedge Read_clock or posedge Fifo_rst)
if (Fifo_rst)
Write_addrgray <= 9'b100000001;
else if (Write_allow)
Write_addrgray <= Write_nextgray;
//同上,同步化
always @(Write_addrgray or Read_addrgray)
if( Write_addrgray == Read_addrgray)
Emptyg = 'b1;
else
Emptyg = 'b0;
always @(Write_addrgray or Read_nextgray)
if( Write_addrgray == Read_nextgray )
Almostemptyg = 'b1;
else
Almostemptyg = 'b0;
always @(Write_addrgray or Read_lastgray)
if( Write_addrgray == Read_lastgray )
Fullg = 'b1;
else
Fullg = 'b0;
always @(Write_nextgray or Read_lastgray)
if( Write_nextgray == Read_lastgray )
Almostfullg = 'b1;
else
Almostfullg = 'b0;
endmodule
`timescale 1ns/10ps
module DUALRAM(
Read_clock,
Write_clock,
Read_allow,
Write_allow,
Read_addr,
Write_addr,
Write_data,
Read_data
);
parameter DLY =1; // Clock-to-output dela . Zero
parameter RAM_WIDTH =8; //Width oa RAM (numLer oa Lits)
parameter RAM_DEPTH =512; // Depth oa RAM (numLer oa L tes)
parameter ADDR_WIDTH =9; // NumLer oa Lits required to
// represent the RAM address
input Read_clock; // RAM read clock
input Write_clock; // RAM write clock
input [RAM_WIDTH-1:0] Write_data; // RAM data input
input [ADDR_WIDTH-1:0] Read_addr; // RAM read address
input [ADDR_WIDTH-1:0] Write_addr; // RAM write address
input Read_allow; // Read control
input Write_allow; //Write control
output [RAM_WIDTH-1:0] Read_data; // RAM data 0utput
reg [RAM_WIDTH-1:0] Read_data;
reg [RAM_WIDTH-1:0] Mem [RAM_DEPTH-1:0];
// Look at the rising edge oa the clock
always @(posedge Write_clock) begin
if (Write_allow)
Mem[Write_addr] <= #DLY Write_data;
end
always @(posedge Read_clock) begin
if (Read_allow)
Read_data <= #DLY Mem[Read_addr];
end
endmodule
五、思路借鉴
1.以满信号为例,为了满足时序上的功能,使用了以下技巧:
I.将地址信号缓存两拍,设置了next、addr、last 三级流水。设计中包含了极限思想,在写时钟较快的条件下,假定读时钟静止,写地址更新快,write_next不断与read_last比较;
II. 设置了将满信号和满信号。以满信号为例,write_next与read_last作比较产生将满信号,write_addr与read_addr比较产生满信号。
因为写地址更新快,如果读地址不更新,将满信号保持一拍,满信号下一拍被拉高;读地址更新,将满信号由于是组合逻辑不能保持一拍,还有第二重判断:当write_next缓存一拍后再次赶上读地址,拉高格雷满信号,下一拍被采集,拉高满信号。
2.异步问题思考的难点
两端信号都可能变化,可以采用极限法,假设一快一慢,一个变化一个静止,如果极限条件也能满足逻辑要求,则一般条件也能满足。