亚稳态介绍:
亚稳态是指在设计的正常运行过程中,信号在一定时间内不能达到稳定的0或者1的现象。由于同步系统中,数据与时钟存在固定的关系,其输入信号满足触发器的时序要求,即建立时间和保持时间,所以不会发送亚稳态现象;而对于异步系统,数据与时钟关系不固定,当出现不满足建立时间和保持时间的现象时,就会输出亚稳态,输出介于0和1之间的不定态。
举例:输入信号(src_data_out)在源时钟域(src_clk)的亚稳态窗内发生了变化,在另一个时钟域(dest_clk)对该输入信号采样时,就会导致输出出现亚稳态(dest_data_out)。
所谓亚稳态窗,就是建立时间+保持时间
另:在异步复位中,如果复位信号不满足removal time和recovery time也会出现亚稳态。
而对于容易出现亚稳态现象的电路则需要进行跨时钟域处理。
跨时钟域介绍:
跨时钟域主要目的是保证数据从A时钟到B时钟的正确传递,其发生于多时钟域下,包含以下关系:
1. 时钟频率不同;
2. 时钟频率相同,但相位不同;
跨时钟域信号的传输分为两类:
1. 控制信号的传输(单bit信号)
2. 数据信号的传输(多bit信号)
其中,控制信号的传输分为从快时钟域到慢时钟域的传输,慢时钟域到快时钟域的传输。
慢时钟域到快时钟域的传输——延迟打拍:
此类传输可直接使用两级触发器进行缓存即可,因为理论上,快时钟域总会采集到慢时钟域的信号;在快时钟域下,对慢时钟域的信号进行连续缓存两次,可有效降低因建立时间和保持时间的不满足而导致的亚稳态问题。
RTL code:
module pulse_syn_slow2f(
input clk_f,clk_s,rst_n,signal_slow,
output signal_fast
);
reg signal_fast_r, signal_fast_rr;
always @(posedge clk_f or negedge rst_n)begin
if(!rst_n)begin
signal_fast_r <= 1'b0;
signal_fast_rr <= 1'b0;
end
else begin
signal_fast_r <= signal_slow;
signal_fast_rr <= signal_fast_r;
end
end
//上升沿检测
assign signal_fast = !signal_fast_rr && signal_fast_r;
/*
下降沿检测
assign signal_fast = signal_fast_rr && !signal_fast_r;
*/
endmodule
快时钟域到慢时钟域的传输——握手传输:
此类情况,容易出现在慢时钟域中无法采集到快时钟域的信号,可采用握手传输的方式进行跨时钟域处理。
原理:
1. 在快时钟域下,对输入的脉冲信号进行检测,检测到输入的脉冲信号后,输出一个高电平信号,不急于将信号拉低,保持高电平状态;
2. 在慢时钟域下,对快时钟域下输出的高电平信号进行延迟打拍采样;
3. 在慢时钟域下,确认得到高电平信号后,输出反馈信号给快时钟域;
4. 快时钟域对反馈信号进行延迟打拍采样,确认接收到反馈信号,拉低输出的高电平信号;
RTL code:
module pulse_syn_fast2s
#(parameter pulse_init = 1'b0)(
input rst_n,clk_fast,pulse_fast,clk_slow,
output pulse_slow);
wire clear_n;
reg pulse_fast_r;
//(1)快时钟域对脉冲信号检测,并不急于将信号拉低,保持输出信号为高电平状态
always @(posedge clk_fast or negedge rst_n)begin
if(!rst_n)
pulse_fast_r <= pulse_init;
else if(!clear_n)
pulse_fast_r <= 1'b0;
else if(pulse_fast)
pulse_fast_r <= 1'b1;
end
//(2)慢时钟域下对信号进行延迟打拍采样
reg [1:0] pulse_fast2s_r;
always @(posedge clk_slow or negedge rst_n)begin
if(!rst_n)
pulse_fast2s_r <= 2'b0;
else
pulse_fast2s_r <= {pulse_fast2s_r[0],pulse_fast_r};
end
assign pulse_slow = pulse_fast2s_r[1];
//(3)快时钟域下对慢时钟域的反馈信号延迟打拍采样
reg [1:0] pulse_slow2f_r;
always @(posedge clk_fast or negedge rst_n)begin
if(!rst_n)
pulse_slow2f_r <= 2'b0;
else
pulse_slow2f_r <= {pulse_slow2f_r[0],pulse_slow};
end
//(4)拉低快时钟域脉冲信号
assign clear_n = ~(!pulse_fast && pulse_slow2f_r[1]);
endmodule
数据信号的传输(多bit信号)——异步FIFO:
多位宽数据的异步传输问题,无论是从快时钟到慢时钟,还是从慢时钟到快时钟,
都可以用 FIFO 处理。
异步FIFO系统框图:
FIFO设计的几个关键点:
1. FIFO空满的判断:
增加1位标志位,因为FIFO是一种回卷式的读写,将标志位和地址位结合,可进行FIFO空满的判断;假设:读地址和写地址都指向同样的位置,即最高位相同,rdaddr==wraddr,可判断出FIFO此时的状态为读空;若读写地址的最高位不同,其余位相同,即rdaddr[N]==wraddr[N]; rdaddr[N-1:0]==wraddr[N-1:0]; 此时可判断出FIFO状态为写满;写地址比读地址大出一个FIFO深度。在读写吞吐量相同情况下,FIFO深度=写入数据个数-读出数据个数。
FIFO满条件:
FIFO空条件:
2. 格雷码和二进制码的相互转换:
因为格雷码本身的性质,即格雷码相邻两个状态之间只有单比特信号发生跳变,其余信号不变。因此,在FIFO中,地址总线可以先转换成格雷码,再进行两级触发器对格雷码地址进行同步,其误码概率与单比特信号的跨时钟域转换是一致的。
格雷码转二进制:
bin[3]=gray[3]
bin[2]=gray[3]^gray[2]
bin[1]=gray[3]^gray[2]^gray[1]
bin[0]=gray[3]^gray[2]^gray[1]^gray[0]
RTL code:
integer i
always @(*)
for(i=0;i<=size;i=i+1)
bin[i] = ^(gray>>i);
二进制转格雷码:
gray[3]=bin[3]
gray[2]=bin[2]^bin[3]
gray[1]=bin[1]^bin[2]
gray[0]=bin[0]^bin[1]
RTL code:
assign gray = (bin>>1)^bin;
注意:先将a时钟的地址格雷码编码,再用b时钟采样格雷码地址进行两级触发器缓存,最后用b时钟对暂存的格雷码地址进行译码。
RTL code:
module async_fifo_16x16(
//fifo_write
wr_clk,wr_en,almost_full,full,wr_data,wr_reset,
//fifo_read
rd_clk,rd_en,almost_empty,empty,rd_data,rd_reset
);
/*************** parameter ************************/
parameter ADDR_WIDTH = 4;
parameter DATA_WIDTH = 16;
parameter ALMOST_FULL_GAP = 3;//离满还有 ALMOST_FULL_GAP时,almost_full有效
parameter ALMOST_EMPTY_GAP = 3;//离空还有ALMOST_EMPTY_GAP时,almost_empty有效
parameter FIFO_DEEP = 16;
input wr_reset;//wr_clk 下的复位
input wr_clk;
input wr_en;
input [DATA_WIDTH-1:0] wr_data;
output almost_full;
output full;
input rd_reset;
input rd_clk;
input rd_en;
output almost_empty;
output empty;
output [DATA_WIDTH-1:0] rd_data;
/*********************** wire && reg ********************/
wire [DATA_WIDTH-1:0] wr_addr;
wire [DATA_WIDTH-1:0] rd_addr;
wire [DATA_WIDTH-1:0] data_out_temp;//FIFO读出数据
reg [ADDR_WIDTH:0] wr_gap;//写指针与读指针之间的间隔
reg [ADDR_WIDTH:0] rd_gap;//读指针与写指针之间的间隔
wire [DATA_WIDTH-1:0] rd_data;//fifo data output
reg almost_full;//fifo almost full
reg full;
reg almost_empty;
reg empty;
reg [ADDR_WIDTH:0] waddr;//写地址,最高位为指针循环指示
reg [ADDR_WIDTH:0] waddr_gray;//写地址格雷码
reg [ADDR_WIDTH:0] waddr_gray_sync_dl;//写地址格雷码,同步到读时钟域
reg [ADDR_WIDTH:0] waddr_gray_sync;
reg [ADDR_WIDTH:0] raddr;//读地址,最高位为指针循环指示位
reg [ADDR_WIDTH:0] raddr_gray;//读地址格雷码
reg [ADDR_WIDTH:0] raddr_gray_sync_dl;//同步到写时钟域的读地址格雷码
reg [ADDR_WIDTH:0] raddr_gray_sync;
reg [ADDR_WIDTH:0] raddr_gray2bin;//读地址的二进制码
reg [ADDR_WIDTH:0] waddr_gray2bin;//写地址的二进制码
reg wen;//RAM写使能
reg ren;//RAM读使能
/************** 异步FIFO读/写地址的格雷码编码 ****************/
//写控制逻辑
//RAM写使能与地址
assign wen = wr_en && (!full);
//fifo write address generated
always @(posedge wr_clk or posedge wr_reset)
if(wr_reset)
waddr <= {(ADDR_WIDTH+1){1'b0}};
else if(wen)
waddr <= waddr + 1'b1;
assign wr_addr = waddr[ADDR_WIDTH-1:0];//写地址连接到RAM存储器
//fifo write address : bin2gray
always @(posedge wr_clk or posedge wr_reset)
if(wr_reset)
waddr_gray <= {(ADDR_WIDTH+1){1'b0}};
else
waddr_gray <= waddr ^ {1'b0,waddr[ADDR_WIDTH:1]};//原二进制码左移一位,高位补零,再与原二进制码进行异或
//fifo read address gray sync to wr_clk
always @(posedge wr_clk or posedge wr_reset)
if(wr_reset)begin
raddr_gray_sync <= {(ADDR_WIDTH+1){1'b0}};
raddr_gray_sync_dl <= {(ADDR_WIDTH+1){1'b0}};
end
else begin
raddr_gray_sync <= raddr_gray;
raddr_gray_sync_dl <= raddr_gray_sync;
end
//读地址格雷码转变为二进制码
always @(*)begin
raddr_gray2bin = {
raddr_gray_sync_dl[4],
raddr_gray_sync_dl[4]^raddr_gray_sync_dl[3],
raddr_gray_sync_dl[4]^raddr_gray_sync_dl[3]^raddr_gray_sync_dl[2],
raddr_gray_sync_dl[4]^raddr_gray_sync_dl[3]^raddr_gray_sync_dl[2]^raddr_gray_sync_dl[1],
raddr_gray_sync_dl[4]^raddr_gray_sync_dl[3]^raddr_gray_sync_dl[2]^raddr_gray_sync_dl[1]^raddr_gray_sync_dl[0]
};
end
//写指针与读指针间隔计算
/*wraddr和rdaddr最高位不等时,rdaddr[3:0]>wraddr[3:0],低4位直接相减即可*/
/*wraddr和rdaddr最高位相等时,rdaddr[3:0]
参考资料:
《FPGA深度解析》
《硬件架构的艺术》
《菜鸟教程——跨时钟域处理》