在大规模ASIC或FPGA设计中,多时钟系统往往是不可避免的,这样就产生了不同时钟域数据传输的问题,其中一个比较好的解决方案就是使用异步FIFO来作不同时钟域数据传输的缓冲区,这样既可以使相异时钟域数据传输的时序要求变得宽松,也提高了它们之间的传输效率。
FIFO在硬件上是一种地址依次自增的Simple Dual Port RAM,按读数据和写数据工作的时钟域是否相同分为同步FIFO和异步FIFO,其中同步FIFO是指读时钟和写时钟为同步时钟,常用于数据缓存和数据位宽转换;异步FIFO通常情况下是指读时钟和写时钟频率有差异,即由两个异步时钟驱动的FIFO,由于读写操作是独立的,故常用于多比特数据跨时钟域处理。
异步FIFO是通过比较读指针和写指针的位置来判断FIFO是否写满或读空,但是不可以直接比较两个指针,因为他们属于不同时钟域,直接相比可能会产生亚稳态从而引起误判,这就需要将两个指针分别进行跨时钟域处理,然后再判断。但是存在一个问题,自然二进制编码的地址在状态翻转的时候是多位变化,这就可能会产生竞争现象并有可能被另一个时钟域的触发器采样到,从而引发误判。最容易的解决方法就是将自然二进制编码的地址转为格雷码编码的地址。
从上图可以看出,格雷码如果每2^n个数一循环,首尾两个格雷码仍然是只有一位变化,如果不是2^n个数,那么首尾数据就不是仅有一位变化,那就不是真正的格雷码,所以这也是异步FIFO的存储深度只能是2^n的原因。
自然二进制码转换成二进制格雷码,其法则是保留自然二进制码的最高位作为格雷码的最高位,而次高位格雷码为二进制码的高位与次高位相异或,而格雷码其余各位与次高位的求法相类似。:
module ASFIFO#
(
parameter WIDTH = 16, // FIFO数据总线位宽
parameter PTR = 4 // FIFO存储深度(bit数,深度只能是2^n个)
)
(
// write interface
input wrclk , // 写时钟
input wr_rst_n, // 写指针复位
input [WIDTH-1:0] wr_data , // 写数据总线
input wr_en , // 写使能
output reg wr_full , // 写满标志
//read interface
input rdclk , // 读时钟
input rd_rst_n, // 读指针复位
input rd_en , // 读使能
output [WIDTH-1:0] rd_data , // 读数据输出
output reg rd_empty // 读空标志
);
// 写时钟域信号定义
reg [PTR:0] wr_bin ; // 二进制写地址
reg [PTR:0] wr_gray ; // 格雷码写地址
reg [PTR:0] rd_gray_ff1 ; // 格雷码读地址同步寄存器1
reg [PTR:0] rd_gray_ff2 ; // 格雷码读地址同步寄存器2
reg [PTR:0] rd_bin_wr ; // 同步到写时钟域的二进制读地址
// 读时钟域信号定义
reg [PTR:0] rd_bin ; // 二进制读地址
reg [PTR:0] rd_gray ; // 格雷码读地址
reg [PTR:0] wr_gray_ff1 ; // 格雷码写地址同步寄存器1
reg [PTR:0] wr_gray_ff2 ; // 格雷码写地址同步寄存器2
reg [PTR:0] wr_bin_rd ; // 同步到读时钟域的二进制写地址
// 解格雷码电路循环变量
integer i ;
integer j ;
// DPRAM控制信号
wire dpram_wr_en ; // DPRAM写使能
wire [PTR-1:0] dpram_wr_addr ; // DPRAM写地址
wire [WIDTH-1:0] dpram_wr_data ; // DPRAM写数据
wire dpram_rd_en ; // DPRAM读使能
wire [PTR-1:0] dpram_rd_addr ; // DPRAM读地址
wire [WIDTH-1:0] dpram_rd_data ; // DPRAM读数据
// ******************************** 写时钟域 ******************************** //
// 二进制写地址递增
always @(posedge wrclk or posedge wr_rst_n) begin
if (!wr_rst_n) begin
wr_bin <= 'b0;
end
else if ( wr_en == 1'b1 && wr_full == 1'b0 ) begin
wr_bin <= wr_bin + 1'b1;
end
else begin
wr_bin <= wr_bin;
end
end
// 写地址:二进制转格雷码
always @(posedge wrclk or posedge wr_rst_n) begin
if (!wr_rst_n) begin
wr_gray <= 'b0;
end
else begin
wr_gray <= { wr_bin[PTR], wr_bin[PTR:1] ^ wr_bin[PTR-1:0] };
end
end
// 格雷码读地址同步至写时钟域
always @(posedge wrclk or posedge wr_rst_n) begin
if(!wr_rst_n) begin
rd_gray_ff1 <= 'b0;
rd_gray_ff2 <= 'b0;
end
else begin
rd_gray_ff1 <= rd_gray;
rd_gray_ff2 <= rd_gray_ff1;
end
end
// 同步后的读地址解格雷
always @(*) begin
rd_bin_wr[PTR] = rd_gray_ff2[PTR];
for ( i=PTR-1; i>=0; i=i-1 )
rd_bin_wr[i] = rd_bin_wr[i+1] ^ rd_gray_ff2[i];
end
// 写时钟域产生写满标志
always @(*) begin
if( (wr_bin[PTR] != rd_bin_wr[PTR]) && (wr_bin[PTR-1:0] == rd_bin_wr[PTR-1:0]) ) begin
wr_full = 1'b1;
end
else begin
wr_full = 1'b0;
end
end
// ******************************** 读时钟域 ******************************** //
always @(posedge rdclk or posedge rd_rst_n) begin
if (!rd_rst_n) begin
rd_bin <= 'b0;
end
else if ( rd_en == 1'b1 && rd_empty == 1'b0 ) begin
rd_bin <= rd_bin + 1'b1;
end
else begin
rd_bin <= rd_bin;
end
end
// 读地址:二进制转格雷码
always @(posedge rdclk or posedge rd_rst_n) begin
if (!rd_rst_n) begin
rd_gray <= 'b0;
end
else begin
rd_gray <= { rd_bin[PTR], rd_bin[PTR:1] ^ rd_bin[PTR-1:0] };
end
end
// 格雷码写地址同步至读时钟域
always @(posedge rdclk or posedge rd_rst_n) begin
if(!rd_rst_n) begin
wr_gray_ff1 <= 'b0;
wr_gray_ff2 <= 'b0;
end
else begin
wr_gray_ff1 <= wr_gray;
wr_gray_ff2 <= wr_gray_ff1;
end
end
// 同步后的写地址解格雷
always @(*) begin
wr_bin_rd[PTR] = wr_gray_ff2[PTR];
for ( j=PTR-1; j>=0; j=j-1 )
wr_bin_rd[j] = wr_bin_rd[j+1] ^ wr_gray_ff2[j];
end
// 读时钟域产生读空标志
always @(*) begin
if( wr_bin_rd == rd_bin )
rd_empty = 1'b1;
else
rd_empty = 1'b0;
end
// RTL双口RAM例化
DPRAM
# ( .WIDTH(16), .DEPTH(16), .ADDR(4) )
U_DPRAM
(
.wrclk (wrclk ),
.rdclk (rdclk ),
.rd_rst_n (rd_rst_n ),
.wr_en (dpram_wr_en ),
.rd_en (dpram_rd_en ),
.wr_data (dpram_wr_data ),
.rd_data (dpram_rd_data ),
.wr_addr (dpram_wr_addr ),
.rd_addr (dpram_rd_addr )
);
// 产生DPRAM读写控制信号
assign dpram_wr_en = ( wr_en == 1'b1 && wr_full == 1'b0 )? 1'b1 : 1'b0;
assign dpram_wr_data = wr_data;
assign dpram_wr_addr = wr_bin[PTR-1:0];
assign dpram_rd_en = ( rd_en == 1'b1 && rd_empty == 1'b0 )? 1'b1 : 1'b0;
assign rd_data = dpram_rd_data;
assign dpram_rd_addr = rd_bin[PTR-1:0];
endmodule