目录
在FPGA系统中,如果数据传输中不满足触发器的Tsu和Th,或者复位过程中复位信号的释放相对于有效时钟沿的恢复时间(recovery time)不满足,就可能产生亚稳态,此时触发器输出端Q在有效时钟沿之后比较长的一段时间处于不确定的状态,在这段时间里Q端在0和1之间处于振荡状态,而不是等于数据输入端D的值。这段时间称为决断时间(resolution time)。经过resolution time之后Q端将稳定到0或1上,但是稳定到0或者1,是随机的,与输入没有必然的关系。
1.触发器的Tsu,Th
在数字电路中,触发器(Flip-Flop)是一种用于存储和稳定数据的元件。Tsu和Th是触发器的输入信号的时序参数,用于描述输入信号在时钟边沿前后的稳定要求。
1. Tsu(Setup Time):Tsu是指在时钟边沿到来之前,输入信号必须保持稳定的最小时间。在时钟边沿之前,输入信号必须达到并保持其稳定的逻辑电平,以确保正确的数据采样。如果输入信号在Tsu之内变化,可能会导致触发器的输出变得不确定。
2. Th(Hold Time):Th是指在时钟边沿到来之后,输入信号必须保持稳定的最小时间。在时钟边沿之后的一段时间内,输入信号必须保持在其稳定的逻辑电平,以确保正确的数据保持。如果输入信号在Th之内变化,也可能会导致触发器的输出变得不确定。
这两个时序参数的目的是确保输入信号在时钟边沿时被稳定采样,以避免亚稳态(Metastability)和不确定性。设计和时序约束的目标是满足Tsu和Th的要求,以确保正确的数据传输和存储操作。如果输入信号的变化在Tsu和Th之间,会增加触发器出现错误的风险。
Tsu和Th的值通常由触发器的制造商提供,并且会根据具体的触发器类型和工作条件而有所不同。在设计数字电路时,需要根据所选用的触发器和设计要求来选择合适的时序参数值,并确保输入信号满足这些要求,以确保可靠的操作。
2.消除亚稳态的方法
有亚稳态产生,我们就要对亚稳态进行消除,常用对亚稳态消除有三种方式:
(1) 对异步信号进行同步处理;
(2) 采用FIFO对跨时钟域数据通信进行缓冲设计;
(3) 对复位电路采用异步复位、同步释放方式处理。
2.1对异步信号进行同步提取边沿
input signal;
reg signal_r1;
reg signal_r2;
wire signal_rising;
wire signal_down;
always@(posedge clk or negedge rst)
if(!rst)
signal_r1<= 1'b0;
else
signal_r1<= signal;
always@(posedge clk or negedge rst)
if(!rst)
signal_r2<= 1'b0;
else
signal_r2<= signal_r1;
assign signal_rising = ((signal_r1 == 1'b1)&&(signal_r2 == 1'b0))? 1:0; //检测上升延
assign signal_down = ((signal_r1 == 1'b0)&&(signal_r2 == 1'b1))? 1:0; //检测下降延
//-----------便捷方法如下----------
input signal;
reg [3:0] sig_nsyn_r; //信号异步移位
wire signal_rising;
wire signal_down;
always@(posedge clk or negedge rst)
begin
if(!rst)
sig_nsyn_r <= 2’d0;
else
sig_nsyn_r <= { sig_nsyn_r [0], sig_nsyn };
end
assign signal_rising = ~sig_nsyn_r[3] & sig_nsyn_r[2];
assign signal_down = sig_nsyn_r[3] & ~sig_nsyn_r[2];
这种边沿提取方式对于一个稳定的系统是不合适的,例如:当第一级寄存器采集到亚稳态,那势必造成sig_nsyn_p输出亚稳态,这样就会对采用sig_nsyn_p的信号进行判断的电路造成影响,甚至判断出错误的值。根据亚稳态产生概率,如果在100M时种下那第一级寄存器产生亚稳态的概率约为10%,随着系统采集频率升高,那产生亚稳态的概率也会随之上升。因此,在进行异步信号跨频提取边沿时候,一般采用多进行一级寄存器消除亚稳态,可能在系统稳定性要求高的情况下,采用更多级寄存器来消除亚稳态,如程序清单 4.2所示,即为采用4级寄存器消除亚稳态,相应的边沿信号产生的时间就晚了两个时钟周期。
2.2FIFO进行异步跨频数据处理
高位扩展法
- 当最高位不同,且其他位相同,则表示读指针或者写指针多跑了一圈,而显然不会让读指针多跑一圈(多跑一圈读啥?),所以可能出现的情况只能是写指针多跑了一圈,与就意味着FIFO被写满了
- 当最高位相同,且其他位相同,则表示读指针追到了写指针或者写指针追到了读指针,而显然不会让写指针追读指针(这种情况只能是写指针超过读指针一圈),所以可能出现的情况只能是读指针追到了写指针,也就意味着FIFO被读空了
- “写满”的判断:需要将读指针同步到写时钟域,再与写指针判断
- “读空”的判断:需要将写指针同步到读时钟域,再与读指针判断
假读空
假写满
跨时钟域进行同步之前,可以先讲二进制转换为格雷码表示,相对于二进制,格雷码在进行跨时钟域转换时,产生亚稳态的概率更小,格雷码是一种非权重码,每次变化位数只有一位,这就有效的避免了在跨时钟域情况下亚稳态问题发生的概率。举个例子,二进制的7(0111)跳转到8(1000),4位都会发生变化,所以发生亚稳态的概率就比较大。而格雷码的跳转就只有一位(从0100--1100,仅第四位发生变化)会发生变化,有效地减小亚稳态发生的可能性。
- 分别构造读、写时钟域下的读、写指针,指针位数需拓展一位。举例,设计的FIFO深度为16,16个地址需要4位二进制数表示,同时扩宽一位作为指示位,所以指针的位宽共需要5位。
- 分别将读、写指针从二进制码转换成格雷码
- 将格雷码形式的读指针同步到写时钟域;将格雷码形式的写指针同步到读时钟域
- 在写时钟域判断“写满”:格雷码形式的读写指针高2位相反,其余位相等
- 在读时钟域判断“读空”:格雷码形式的读写指针高2位相等,其余位也相等--即全部相等
//异步FIFO
module async_fifo
#(
parameter DATA_WIDTH = 'd8 , //FIFO位宽
parameter DATA_DEPTH = 'd16 //FIFO深度
)
(
//写数据
input wr_clk , //写时钟
input wr_rst_n , //低电平有效的写复位信号
input wr_en , //写使能信号,高电平有效
input [DATA_WIDTH-1:0] data_in , //写入的数据
//读数据
input rd_clk , //读时钟
input rd_rst_n , //低电平有效的读复位信号
input rd_en , //读使能信号,高电平有效
output reg [DATA_WIDTH-1:0] data_out , //输出的数据
//状态标志
output empty , //空标志,高电平表示当前FIFO已被写满
output full //满标志,高电平表示当前FIFO已被读空
);
//reg define
//用二维数组实现RAM
reg [DATA_WIDTH - 1 : 0] fifo_buffer[DATA_DEPTH - 1 : 0];
reg [$clog2(DATA_DEPTH) : 0] wr_ptr; //写地址指针,二进制
reg [$clog2(DATA_DEPTH) : 0] rd_ptr; //读地址指针,二进制
reg [$clog2(DATA_DEPTH) : 0] rd_ptr_g_d1; //读指针格雷码在写时钟域下同步1拍
reg [$clog2(DATA_DEPTH) : 0] rd_ptr_g_d2; //读指针格雷码在写时钟域下同步2拍
reg [$clog2(DATA_DEPTH) : 0] wr_ptr_g_d1; //写指针格雷码在读时钟域下同步1拍
reg [$clog2(DATA_DEPTH) : 0] wr_ptr_g_d2; //写指针格雷码在读时钟域下同步2拍
//概率问题。2拍已经基本能防止出现亚稳态了。3、4拍提升不大
//wire define
wire [$clog2(DATA_DEPTH) : 0] wr_ptr_g; //写地址指针,格雷码
wire [$clog2(DATA_DEPTH) : 0] rd_ptr_g; //读地址指针,格雷码
wire [$clog2(DATA_DEPTH) - 1 : 0] wr_ptr_true; //真实写地址指针,作为写ram的地址
wire [$clog2(DATA_DEPTH) - 1 : 0] rd_ptr_true; //真实读地址指针,作为读ram的地址
//地址指针从二进制转换成格雷码
assign wr_ptr_g = wr_ptr ^ (wr_ptr >> 1);
assign rd_ptr_g = rd_ptr ^ (rd_ptr >> 1);
//读写RAM地址赋值
assign wr_ptr_true = wr_ptr [$clog2(DATA_DEPTH) - 1 : 0]; //写RAM地址等于写指针的低DATA_DEPTH位(去除最高位)
assign rd_ptr_true = rd_ptr [$clog2(DATA_DEPTH) - 1 : 0]; //读RAM地址等于读指针的低DATA_DEPTH位(去除最高位)
//写操作,更新写地址
always @ (posedge wr_clk or negedge wr_rst_n) begin
if (!wr_rst_n)
wr_ptr <= 0;
else if (!full && wr_en)begin //写使能有效且非满
wr_ptr <= wr_ptr + 1'd1;
fifo_buffer[wr_ptr_true] <= data_in;
end
end
//将读指针的格雷码同步到写时钟域,来判断是否写满
always @ (posedge wr_clk or negedge wr_rst_n) begin
if (!wr_rst_n)begin
rd_ptr_g_d1 <= 0; //寄存1拍
rd_ptr_g_d2 <= 0; //寄存2拍
end
else begin
rd_ptr_g_d1 <= rd_ptr_g; //寄存1拍
rd_ptr_g_d2 <= rd_ptr_g_d1; //寄存2拍
end
end
//读操作,更新读地址
always @ (posedge rd_clk or negedge rd_rst_n) begin
if (!rd_rst_n)
rd_ptr <= 'd0;
else if (rd_en && !empty)begin //读使能有效且非空
data_out <= fifo_buffer[rd_ptr_true];
rd_ptr <= rd_ptr + 1'd1;
end
end
//将写指针的格雷码同步到读时钟域,来判断是否读空
always @ (posedge rd_clk or negedge rd_rst_n) begin
if (!rd_rst_n)begin
wr_ptr_g_d1 <= 0; //寄存1拍
wr_ptr_g_d2 <= 0; //寄存2拍
end
else begin
wr_ptr_g_d1 <= wr_ptr_g; //寄存1拍
wr_ptr_g_d2 <= wr_ptr_g_d1; //寄存2拍
end
end
//更新指示信号
//当所有位相等时,读指针追到到了写指针,FIFO被读空
assign empty = ( wr_ptr_g_d2 == rd_ptr_g ) ? 1'b1 : 1'b0;
//当高位相反且其他位相等时,写指针超过读指针一圈,FIFO被写满
//同步后的读指针格雷码高两位取反,再拼接上余下位
assign full = ( wr_ptr_g == { ~(rd_ptr_g_d2[$clog2(DATA_DEPTH) : $clog2(DATA_DEPTH) - 1])
,rd_ptr_g_d2[$clog2(DATA_DEPTH) - 2 : 0]})? 1'b1 : 1'b0;
endmodule
2.3对复位电路进行异步复位,同步释放
//*******************同步复位模块**************************
//-----------端口定义-------------------------------
module rst_test(
input clk , //工作时钟
input rst_n , //复位,低电平有效
input in , //输入信号
output reg out //输出信号
);
//-----------reg定义-------------------------------
reg arst_n_r;
reg arst_n;
//-----------复位信号同步模块-------------------------------
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
arst_n_r <= 1'b0 ; //复位将输出置零
arst_n <= 1'b0 ; //复位将输出置零
end
else begin
arst_n_r <= 1'b1 ; //跟接rst_n是一样的,都是逻辑1
arst_n <= arst_n_r ;
end
end
//-----------输出模块-------------------------------
always@(posedge clk or negedge arst_n)begin
if(!arst_n)
out <= 1'b0; //复位将输出置零
else
out <= in; //其他时候将输入赋值给输出
end
endmodule