CDC跨时钟域同步设计
参考博文:https://blog.csdn.net/maxwell2ic/article/details/81051545, https://blog.csdn.net/dongdongnihao_/article/details/79873555 和 https://www.cnblogs.com/digital-wei/p/6014450.html
亚稳态
锁存器出现亚稳态
(1)在其中一个输入端输入的脉冲太短。
(2)两个端口输入同时有效,或两输入有效相差足够短。
(3)在使能输入的边缘处,输入信号不稳定。
触发器出现亚稳态
(1)建立/保持时间内输入信号不稳定。
(2)时钟脉冲太窄。
(3)异步信号对时钟有效沿是随机的,易产生亚稳态。异步信号包括:不被时钟控制的信号;或被不同时钟域的时钟同步的信号。
触发器进入亚稳态的时间可以用参数MTBF(Mean Time Between Failures)来描述,MTBF即触发器采样失败的时间间隔,表示为:
其中fclock表示系统时钟频率,fdata代表异步输入信号的频率,tmet代表不会引起故障的最长亚稳态时间,C1和C2分别为与器件特性相关的常数。如果MTBF很大,就认为这个设计在实际工作中是能够正常运行的,不会因为亚稳态导致整个系统的失效。
亚稳态的处理方法
(1)亚稳态不能避免。
(2)尽可能降低亚稳态的影响。
(3)高速数字电路依赖于同步器产生的从亚稳态事件中恢复的缓冲时间。
常见的同步电路解决方案
(1)两级DFF(传单信号)
(2)FIFO(传有一定位宽的信号bus)
(3)握手信号(对于FIFO的深度要求比较大,需要引入握手信号)
总结一下 :
1、有关系的时钟之间传单bit数据,理论上只需要源数据保持足够长的时间(clk2的两个周期)即可;
2、无关系的时钟之间传单bit数据,必须要使用同步器;
3、不管有无关系的时钟进行单bit传输,脉冲同步器都可以解决这个问题;
4、多bit传输只能使用握手机制或者异步fifo;
5、低频采高频,为防止数据不丢失,应当让源数据变慢,多保持一些周期;高频采低频则不需要,但是高频采低频得到的结果可能带有很多冗余
同步器设计
信号同步的要求
为了使同步工作能正常进行,从某个时钟域传来的信号应先通过原时钟域上的一个触发器,然后不经过两个时钟域间的任何组合逻辑直接进入同步器的第一个触发器中。这一要求非常重要,因为同步器的第一级触发器对组合逻辑所产生的毛刺非常敏感。如果一个足够长的毛刺正好满足建立-保持时间的要求则同步器的第一级触发器会将其放行,给新时钟域的后续逻辑送出一个虚假的信号。
跨时钟域传输信号主要引起的问题就是亚稳态。处理跨时钟域的数据有单bit和多bit之分,而打两拍的方式常见于处理单bit数据的跨时钟域问题。打两拍本质就是定义两级寄存器对数据进行延拍。流程如好图所示: 两级寄存器的原理:两级寄存是一级寄存的平方,两级并不能完全消除亚稳态危害,但是提高了可靠性减少其发生概率。总的来讲,就是一级概率很大,三级改善不大。
电平同步器
电平同步器适用情况为:异步输入信号的脉冲宽度大于时钟周期时的同步电路(低速时钟域→高速时钟域)。
在电平同步器中,跨时钟域的信号在新时钟域中要保持高电平或低电平两个时钟周期以上。同步之后的信号是电平的形式,而该电平所维持的时钟周期个数是其在跨时钟域期间被上升沿检测到的次数。这种同步器是所有同步器电路的核心。
module sync(
input async_in, bclk, rst,
output sync_out
);
reg bdat1, bdat2;
always @(posedge bclk or posedge rst) begin
if (rst) begin
bdat1 <= 1'b0;
bdat2 <= 1'b0;
end
else begin
bdat1 <= async_in;
bdat2 <= bdat1;
end
end
assign sync_out = bdat2;
endmodule
边沿检测同步器
边沿检测同步器适用情况为:异步输入信号的脉冲宽度小于时钟周期时的同步电路(高速时钟域→低速时钟域)。
原理图:
不产生亚稳态时的时序图
会产生亚稳态时的时序图
module sync_h2lck(
input async_in, bclk, rst,
output sync_out
);
reg q1, q2, q3;
wire async_rst;
assign async_rst = !async_in && sync_out;
always @(posedge async_in or posedge async_rst) begin
if (async_rst) begin
q1 <= 1'b0;
end
else begin
q1 <= 1'b1;
end
end
always @(posedge bclk or posedge rst) begin
if (rst) begin
q2 <= 1'b0;
q3 <= 1'b0;
end
else begin
q2 <= q1;
q3 <= q2;
end
end
assign sync_out = q3;
endmodule
脉冲检测同步器
简单的脉冲同步器:1)源时钟域脉冲转换为源时钟域电平信号;2)对单bit电平信号进行打拍的异步处理;3)在目的时钟域中进行脉冲还原。
它可以实现简单应用场景下的同步功能,同时也存在不少应用限制或缺陷:1) 对src_clk域dst_clk关系较为敏感,当src_clk与dst_clk时钟频率差别很大时可能不适应;2) 由于没有完整的握手机制,当多个src_pulse之间间隔较短时,可能存在脉冲同步丢失情况。3) 当dst_clk时钟域出现无时钟或复位时,src_clk时钟域将丢失。
从以上设计原理中,我们可以发现该同步器的控制传递是单向的,即仅从源时钟域到目的时钟域,目的时钟域并没有状态反馈。假设存在如下应用:
(1) 源时钟域中的第一个脉冲和第二个脉冲间隔过短,第一个脉冲未完成同步,第二脉冲又将状态清空,导致最终脉冲同步丢失。
要解决以上同步问题,需要引入异步握手机制,保证每个脉冲都同步成功,同步成功后再进行下一个脉冲同步。握手原理如下:sync_req: 源时钟域同步请求信号,高电平表示当前脉冲需要同步;sync_ack: 目的时钟域应答信号,高电平表示当前已收到同步请求。
完整同步过程分为以下4个步骤:
(1) 同步请求产生;当同步器处于空闲(即上一次已同步完成)时,源同步脉冲到达时产生同步请求信号sync_req;
(2) 同步请求信号sync_req同步到目的时钟域,目的时钟域产生脉冲信号并将产生应答信号sync_ack;
(3) 同步应答信号sync_ack同步到源时钟域,源时钟域检测到同步应答信号sync_ack后,清除同步请求信号;
(4) 目的时钟域检测到sync_req撤销后,清除sync_ack应答;源时钟域将到sync_ack清除后,认为一次同步完成,可以同步下一个脉冲。
异步FIFO
FIFO结构
异步FIFO处理多bit数据信号的CDC跨时钟域问题。端口定义:
u w:写时钟域一方的信号;r:读时钟域一方的信号
u wclk:写时钟
u wrst_n:写复位,低有效
u rclk:读时钟
u rrst_n:读复位,低有效
u winc:外部输入的写使能信号
u rinc:外部输入的读使能信号
u wdata :要写进数据,要写进FIFO里面存储的数据。
u rdata:读数据,从FIFO里面读取出来的数据。
u wdata:要读出的数据,要读出FIFO里面存储的数据
u wfull:写满的状态信号
u rempty:读空的状态信号
u wclken:RAM的允许写信号,在这个信号有效的情况下,RAM才能写得进数据。
u rclken:RAM的允许读信号,在这个信号有效的情况下,RAM才能读得出数据。
u waddr:RAM的写地址。
u raddr:RAM的读地址
u wptr:要同步到写时钟域的读指针(读地址)。
u rptr:要同步到读时钟域的写指针(写时钟)
u wq2_rptr:读地址rptr同步到写时钟域的读地址(格雷码,后面会说为什么用格雷码)
u rq2_wptr:写地址rptr同步到读时钟域的读地址(格雷码,后面会说为什么用格雷码)
u syn_r2w:读同步到写触发器链中间信号。
u syn_w2r:写同步到读触发器链中间信号。
异步FIFO读写过程
l 写过程
在复位的时候,FIFO(双口RAM)里面的数据被清零(也就是不存在数据)。
复位之后,只能进行写操作,因为什么都没有,读数据会读出错误的值。
当外部给FIFO写使能信号了,在时钟的驱动下,数据就会被写入FIFO里面的RAM存储单元(存储单元的地址由写指针寄存器的内容确定,写指针寄存器中的内容称为写地址,复位的时候为0),写完数据之后(或者在允许写数据之后),这个写指针寄存器就会自动加一,指向下一个存储单元。
当写到一定程度的时候(写指针寄存器到达一定的数值),旧数据还没有被读出的时候,再写入新数据就会把旧数据给覆盖,这个时候称为写满,需要产生写满的状态信号(full,简称满)。在写满的时候,需要禁止继续写数据。
l 读过程:
在复位的时候,FIFO里面没有数据,因此这个时候是禁止读数据的。
当里面有数据之后,外部读信号到来后,在时钟信号到来的时候,FIFO就会根据读地址(由读指针寄存器的内容确定,读指针寄存器里面的内容称为读地址,复位的时候为0)读出相应的数据,读出数据之后(或者说允许RAM读之后),读指针寄存器自动加一。指向下一个存储单元。
当读到一定的程度的时候,也就是FIFO里面没有数据了,这个时候称为读空,需要产生读空的状态信号(empty,简称空)。在读空的时候,需要禁止继续读数据。
FIFO的空满信号产生
- 空满信号产生过程: 将指针同步到另一边时钟中,使用两级同步器,会导致延迟两个时钟周期,然后出现在未满时通知写一边fifo已满,或在未空时通知读一边fifo已空,都是可以的,即使指针同步后的值(写时同步读指针或读时同步写指针)会持续一小段时间亚稳态,阻止写、读的影响使fifo挂起一段时间,但是不会导致任何错误。进行设计地址寄存器的时候,增多一位当做状态。然后读写地址全相等的时候,表示是读空;除了标志位外,剩余的地址为全部相等,那么就表示是写满。
- 空标志: 本地的读指针( rp tr_nexttr_nexttr_next tr_nexttr_next )与异步传输过来的写指针 异步传输过来的写指针 异步传输过来的写指针 异步传输过来的写指针 异步传输过来的写指针 rq _wptr_wptr _wptr2各位都相等。
- 满标志 :本地的写指针( wp tr_nexttr_nexttr_next tr_nexttr_next )与异步传输过来的读指针 异步传输过来的读指针 异步传输过来的读指针 异步传输过来的读指针 异步传输过来的读指针异步传输过来的读指针wq_rptr2wq_rptr2 wq_rptr2wq_rptr2 wq_rptr2wq_rptr2高两位相反,其余相等。
- 格雷码:在异步时钟域中传递数据时使用异步fifo:产生空信号时需要同步写指针到读时钟域,产生满信号时需要同步读指针到写时钟域;(指针)地址信号是多位,故采用gray码编码实现,同步gray码计数器使取样计数器值很少会出现亚稳态。
FIFO深度选择
FIFO是有宽度和深度的。FIFO的宽度就是RAM的位宽,也是要存入/取出数据的位宽;然后深度就RAM的地址深度,也就是最多可以存多少个数据。
FIFO的深度选小了,在写的时候就很有可能写溢出;深度选大了,就会浪费存储面积。选择一个合适的深度,最主要的就是防止写溢出;由于FIFO要读也要写,那么FIFO的(地址)深度该选多少合适呢?
根据读写速度来选择FIFO深度,此外需要注意的是,在使用FIFO的时候,写的平均吞吐量要和读的平均吞吐量相等。
现在举例来说明:设你的写时钟频率为100M,读时钟为200M;写速度为:100个时钟写如60个数据,读速度为:100个时钟读出30个数据 。
①首先验证你的数据吞吐量是否相等:
写的平均吞吐量=100M*60/100=60M个数据/S
读的平均吞吐量=200M*30/100=60M个数据/S
因此这两个是相等的,不会发生写溢出;如果写大于读,那么FIFO早晚会很快写满溢出;如果读大于写,那么FIFO迟早会读空。因此需要读写吞吐量相同。
②求最低深度
我们知道读写速度之后,就可以判断FIFO要多少深度才合适了,由于FIFO的深度考虑是出于我们要防止写溢出(写满),因此我们考虑写的情况:
写的时候是100个时钟写60个数据,我们不知道它是怎么样子写的,我们从悲观的角度出发,也就是从写得最密集的角度出发:前100个写时钟的最后60个时钟写60个数据,然后后100个写时钟的最前60个时钟写入60个数据,也就是在120个写时钟内写入了120数据。
这120个写时钟的时间是:
在这段时间内,根据读写的速率要求,肯定是要读数据的,读出的数据为:
因此我们FIFO需要的深读就等于没有读出的数据的个数就是:120-72 =48
然而由于上面读的方式是一个平均的方式,此外FIFO的深度一般是2的整数次幂,要符合格雷码的编码转换规则,因此我们深度一般不选择48,而是选择比它大的2的整数次幂的数,比如64或者128。
异步FIFO代码
// ********************************************************
// Copyright(c) 2018
// Author: gujiangtao
//-----------------------------------------------------------
// Asynchronization FIFO
// Asynchronism clock domain: data and control signals transaction
// Verilog-standard: Verilog 2001
//-----------------------------------------------------------
// Structure:
// 1r1w-async FIFO
//-----------------------------------------------------------
module asys_fifo #(
parameter DATA_WIDTH = 32,
parameter ADDR_WIDTH = 32)
(
input wclk,
input wreset_n,
input wden,
input [DATA_WIDTH-1:0] wdata,
input rclk,
input rreset_n,
input rden,
output [DATA_WIDTH-1:0] rdata,
output wfull,
output rempty
);
reg reg wfull, rempty;
reg [ADDR_WIDTH:0] wptr, wq_rptr1, wq_rptr2; //read point for being synchronized to write-point side by two D-flops
reg [ADDR_WIDTH-0:0] rptr, rq_wptr1, rq_wptr2; //write point for being synchronized to read-point side by two D-flops
reg [ADDR_WIDTH:0] rbin, wbin;
reg [DATA_WIDTH-1:0] mem [0: (1<< ADDR_WIDTH)-1];
wire[ADDR_WIDTH-1:0] raddr, waddr;
wire[ADDR_WIDTH:0] rgray_next, rbin_next;
wire[ADDR_WIDTH:0] wgray_next, wbin_next;
wire rempty_temp, wfull_temp;
/////////////////////////////////////////////////////////////////////////////
// 1r1w two-end RAM data transaction
assign rdata = mem[raddr]; //output directly without buffer
always @(posedge wclk) begin
if(wden && !wfull) mem[waddr] <= wdata;
end
//////////////////////////////////////////////////////////////////////////////
//read point for being synchronized to write-point side by two
always @(posedge wclk or negedge wreset_n) begin
if(!wreset_n)
{wq_rptr2, wq_rptr1} <= 0;
else
{wq_rptr2, wq_rptr1} <= {wq_rptr1, rptr};
end
//write point for being synchronized to read-point side by two D-flops
always @(posedge rclk or negedge rreset_n) begin
if(!rreset_n)
{rq_wptr2, rq_wptr1} <= 0;
else
{rq_wptr2, rq_wptr1} <= {rq_wptr1, wptr};
end
/////////////////////////////////////////////////////////////////////////////
// raddr produce for reading the mem RAM
always @(posedge rclk or negedge rreset_n) begin
if(!rreset_n)
{rbin, rptr} <= 0;
else
{rbin, rptr} <= {rbin_next, rgray_next};
end
assign rbin_next = rbin + (rden && ~rempty); //reading point increase
assign raddr = rbin[ADDR_WIDTH-1:0];
// bin to gray
assign rgray_next = (rbin_next>>1) ^ rbin_next;
// FIFO empty singal produce
assign rempty_temp = (rgray_next == rq_wptr2);
always @(posedge rclk or negedge rreset_n) begin
if(!rreset_n)
rempty <= 1'b1;
else
rempty <= rempty_temp;
end
//////////////////////////////////////////////////////////////////////////////
//waddr produce for writing the mem RAM
always @(posedge wclk or negedge wreset_n) begin
if(!wreset_n)
{wbin, wptr} <= 0;
else
{wbin, wptr} <= {wbin_next, wgray_next};
end
assign wbin_next = wbin + (wden && ~wfull); //writing point increase
assign waddr = wbin[ADDR_WIDTH-1:0];
// bin to gray
assign wgray_next= (wbin_next>>1) ^ wbin_next;
// FIFO Full signal produce
assign wfull_temp = (wgray_next == {~wq_rptr2[ADDR_WIDTH:ADDR_WIDTH-1], wq_rptr2[ADDR_WIDTH-2:0]});
always @(posedge wclk or negedge wreset_n) begin
if(!wreset_n)
wfull <= 1'b0;
else
wfull <= wfull_temp;
end
///////////////////////////////////////////////////////////////////////////////
endmodule// end of module asys_fifo
异步FIFO测试平台:
测试平台设计
// ********************************************************
// Copyright(c) 2018
// Author: gujiangtao
//-----------------------------------------------------------------------------
// Asynchronization FIFO
// Asynchronism clock domain: data and control signals transaction
// Verilog-standard: Verilog 2001
//--------------------------------------------------------------------------
// Structure:
// 1r1w-async FIFO --- testbench
//--------------------------------------------------------------------------
`define read_enable_time 5
`define write_enable_time 1
`define data_width 8
`define addr_width 6
module testbench;
localparam DATA_WIDTH = `data_width;
localparam ADDR_WIDTH = `addr_width;
reg wclk_i;
reg wreset_n_i;
reg wden_i;
reg [DATA_WIDTH-1:0] wdata_i;
reg rclk_i;
reg rreset_n_i;
reg rden_i;
wire [DATA_WIDTH-1:0] rdata_o;
wire rempty_o;
wire wfull_o;
reg [DATA_WIDTH-1:0] value_m;
asys_fifo #(
.DATA_WIDTH(DATA_WIDTH),
.ADDR_WIDTH(ADDR_WIDTH)
) asys_fifo
(. wclk(wclk_i)
,. wreset_n(wreset_n_i)
,. wden(wden_i)
,. wdata(wdata_i)
,. rclk(rclk_i)
,. rreset_n(rreset_n_i)
,. rden(rden_i)
,. rdata(rdata_o)
,. rempty(rempty_o)
,. wfull(wfull_o)
);
task read_word;
begin
@(negedge rclk_i);
rden_i = 1;
@(posedge rclk_i);
#`read_enable_time;
rden_i = 0;
end
endtask
task write_word
input [DATA_WIDTH-1:0] value;
begin
@(negedge wclk_i);
wdata_i = value;
wden_i = 1;
@(posedge wclk_i);
#`write_enable_time;
wdata_i = `data_width'bz;
wden_i = 0;
end
endtask
initial begin
wclk_i = 0;
forever begin
#2 wclk_i = 1;
#2 wclk_i = 0;
end
end
initial begin
rclk_i = 0;
forever begin
#10 rclk_i = 1;
#10 rclk_i = 0;
end
end
`ifdef DUMP_FSDB
initial begin
$fsdbDumpfile("testbench.fsdb");
$fsdbDumpvars;
end
`endif
initial begin
test1;
//test2;
#10000;
$finish();
end
task test1;
begin
wdata_i = `data_width'bz;
wden_i = 0;
rden_i = 0;
wreset_n_i = 0;
rreset_n_i = 0;
#50;
wreset_n_i = 1;
rreset_n_i = 1;
#50;
write_word(`data_width'h01);
write_word(`data_width'h02);
write_word(`data_width'h03);
read_word;
read_word;
write_word(`data_width'h04);
repeat(1)begin
read_word;
end
write_word(`data_width'h05);
write_word(`data_width'h06);
write_word(`data_width'h07);
write_word(`data_width'h08);
write_word(`data_width'h09);
write_word(`data_width'h10);
write_word(`data_width'h11);
write_word(`data_width'h12);
repeat(6)begin
read_word;
end
end
endtask
endmodule