异步fifo:
1 使用扩展地址位的方式来判断空满。
2 跨时钟域处理 读写信号时钟不同。
单位宽信号clk1 - clk2 。慢时钟域到快时钟域,两级触发器级联,慢到快。
快到慢,快时钟域传到慢时钟域不一定会被采集到的,通过握手信号,慢时钟域采集到的信号后反馈给快时钟域,然后结束信号本次传输。(通过握手信号)
或者通过将脉冲转化为跳变的电平,将电平信号传递到慢时钟域。
跨时钟域处理方法: 寄存器打两拍和格雷码。 格雷码只有一位不同,减少同步过程中的同步错误。
格雷码的作用就是即使在亚稳态进行读写指针 抽样, 也能进行正确的空满状态指示。
‘’‘’ 将写时钟域的写指针同步到读时钟域,将同步后的写指针和读时钟域的读指针比较 产生读空信号;
将读时钟域的读指针同步到写时钟域,将同步后的读指针与写时钟域的写指针进行比较产生写满信号。
异步fifo的写指针和读指针分属于不同的时钟域,这样指针在进行同步的过程中容易出错,比如0111 到1000 跳变时需要4位同时改变。 采用格雷码,相邻每位只有一位发生变化,这样在进行指针同步时候,就只会产生同步正确。 即使同步出错,出错的结果也是写指针的跳变保持不变。000 - 001. 对于读空判断,最多导致让空标志在fifo不是真正为空的时候产生,而不会出现读空的情形。 grey码保证的是在同步后的读写指针即使出错,也不影响fifo功能的正确性。 同步过程中的亚稳态不可能消除,只是不受其影响。
二进制转格雷码: 右移一位 和自己 做异或 a[1]>>1 ^ a[1] 其实就是最高位不变 其余位与前一位做异或。
格雷码转二进制;
3 多位宽数据 需要异步fifo来处理 单位宽 两级寄存器同步
异步fifo核心 :
1 双口ram
2 控制地址自增
3 二进制地址转化为格雷码地址
4 格雷码地址跨时钟域
5 对同步后的格雷码进行解码
格雷码转二进制码,格雷码最高位为二进制码最高位,二进制码次高位为 二进制码的高位和 格雷码的次高位异或。
6 full 与empty信号的产生
扩展一位地址位 高位相等则为空 ,不相等则为满。
// gray 转化模块 以四位地址数据为例(仅供参考)
module gray(b_in,g_out);
input [4:0]b_in;
input [4:0]g_out;
wire [4:0] g_out;
// 将地址位扩展了一位
assign g_out[4] = b_in[4];
assign g_out[3] = b_in[3];
assign g_out[2] = b_in[2]^b_in[3];
assign g_out[1] = b_in[1]^b_in[2];
assign g_out[0] = b_in[0]^b_in[1];
endmodule
1 双口ram模块
module DPRAM #
(
parameter WIDTH = 16 , //数据位宽
parameter DEPTH = 16 , //数据深度
parameter ADDR = 4 //地址宽度4位
)
(
input wrclk , // 写时钟
input rdclk , // 读时钟
input rd_rst_n, // 读复位
input wr_en , // 写使能
input rd_en , // 读使能
input [WIDTH-1:0] wr_data , // 写数据
output reg [WIDTH-1:0] rd_data , // 读数据
input [ADDR-1:0] wr_addr , // 写地址
input [ADDR-1:0] rd_addr // 读地址
);
reg [WIDTH-1:0] DPRAM [DEPTH-1:0]; // reg 类型数组
// RAM写数据
always @(posedge wrclk) begin
if (wr_en)
DPRAM[wr_addr] <= wr_data;
end
// RAM读数据
always @(posedge rdclk or negedge rd_rst_n) begin
if(!rd_rst_n)
rd_data <= 'b0;
else if (rd_en)
rd_data <= DPRAM[rd_addr];
end
endmodule
2 控制地址自增
// 二进制写地址递增
always @(posedge wrclk or negedge 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 rdclk or negedge 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
3 二进制地址转化为格雷码地址 最高位不变 其余位与前一位做异或,最后拼接。
// 写地址时二进制转格雷码
always @(posedge wrclk or negedge 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 rdclk or negedge 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
4 格雷码地址跨时钟域
`` // 读地址同步到写时钟域 打两拍 rd_gray_ff1 延迟rd_gray一拍,rd_gray_ff2延迟rd_gray两拍。 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 @(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
5 对同步后的格雷码进行解码
//解格雷码的循环变量
integer i ;
integer j ;
// 写地址,解码
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
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
6 full 与empty信号的产生
reg [PTR:0] rd_bin_wr; // 写时钟域 读地址
reg [PTR:0] wr_bin_rd; // 读时钟域 写地址
reg [PTR:0] rd_bin; // 二进制读地址
reg [PTR:0] wr_bin; // 二进制写地址
//写满信号
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 @(*) begin
// 最高位相同,为空。 读指针和写指针一致,说明读空了。
if( wr_bin_rd == rd_bin )
rd_empty = 1'b1;
else
rd_empty = 1'b0;
end
fifo模块:
`timescale 1ns / 1ns
module FIFO#
(
parameter WIDTH = 16, // 数据位宽
parameter PTR = 4, //数据深度 4位
)
(
// 写信号
input wrclk, // 写时钟
input wr_rst_n, // 写指针复位
input [WIDTH-1:0]wr_data, // 写数据总线
input wr_en, // 写使能
output reg wr_full , // 写满标志
//读信号
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 negedge 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 rdclk or negedge 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 wrclk or negedge 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 rdclk or negedge 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
// 读地址同步到写时钟域 打两拍 rd_gray_ff1 延迟rd_gray一拍,rd_gray_ff2延迟rd_gray两拍。
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 @(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
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 @(*) begin
// 最高位相同,为空。 读指针和写指针一致,说明读空了。
if( wr_bin_rd == rd_bin )
rd_empty = 1'b1;
else
rd_empty = 1'b0;
end
// 双口RAM 例化
DPRAM U_DPRAM #
(
.WIDTH (16) , //数据位宽
.DEPTH (16) , //数据深度
.ADDR (4) //地址宽度4位
)
(
.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