跨时钟域方法(同步器、异步FIFO、边沿检测器、脉冲同步器、同步FIFO)

发布于:2022-11-09 ⋅ 阅读:(11) ⋅ 点赞:(0) ⋅ 评论:(0)

1、跨时钟域方法的原因

  异步时钟信号直接传输在信号跳变时违背本地时钟域的时序要求(建立时间约束,保持时间约束),容易产生亚稳态,无法确定亚稳态何时结束以及结束时保持在何种状态上。

2、跨时钟处理的两种思路

  • 对代跨时钟的信息先缓存,然后由另一时钟域接口取出 ————异步FIFO
  • 速度缓慢、可控的信号(配置信号,使能en、指示flag)我们对其进行一些处理。例如拉长持续时间,然后直接传输过去,亚稳态的问题通过同步器解决。适用于低速信号传输。

3、跨时钟域分类——单比特信号跨时钟

3.1.1慢时钟———快时钟。(满足三边沿准则,有效事件可以被安全采样)

           接收时钟的频率是发送时钟的1.5倍以上,跨时钟信号的最小持续时间必然跨越接受时钟说的三个相邻时钟边沿,经过同步器该有效事件可以被安全传输。

3.1.2慢时钟———快时钟。(不满足三边沿准则,有效事件可以被安全采样)

        接收时钟的频率小于发送时钟的1.5倍,将有效事件的持续时间拉长满足三边沿准则,然后再经过同步器传输。

always@(posedge clk_a or negedge arst)//(D触发器)(打拍可拉长持续时间)
    if(!arst)
        data_reg <=0;
    else
        data_reg <=data_in;


always@(posedge clk_b or negedge brst)//同步器(时钟域为将要同步的时钟域)(两个连续的D触发器)
    if(!brst)
        begin
        data_breg <=0;
        data_breg2<=0;
        end
    else
       begin
        data_breg <=data_reg;
        data_breg2<=data_breg2;
        end

3.2.1有效事件传输背景下确保有效事件的数量定义一致。(如何确保跨时钟前后单电平对应单事件?)

  • 脉冲同步电路
  • 边沿检测电路

 3.2.1.1边沿检测电路

       对一个持续电平的上升沿或者下降沿进行检测,并将检测后的电平作为有效事件,就可以做到无论跨时钟输出电平持续多少个cycle,有效检测电平只持续一个cycle,边沿检测常适用于慢时钟到快时钟。

   要实现边沿检测,最直接的想法是用两级寄存器,第二级寄存器锁存住某个时钟上升沿到来时的输入电平,第一级寄存器锁存住下一个时钟沿到来时的输入电平,如果这两个寄存器锁存住的电平信号不同,就说明检测到了边沿,具体是上升沿还是下降沿可以通过组合逻辑来实现。如下图所示:(思想:延迟一个寄存器

//边沿检测电路
//2014/12/10
module edge_cap
(
    input clk, rst_n,
    input pulse,
    
    output pos_edge,
    output neg_edge
    
);
reg pulse_r1, pulse_r2;
 
always @ (posedge clk or negedge rst_n)
if(!rst_n) 
    begin
    pulse_r1 <= 1'b0;
    pulse_r2 <= 1'b0;
    end
else 
    begin
    pulse_r1 <= pulse;
    pulse_r2 <= pulse_r1;
    end
    
assign pos_edge = (pulse_r1 && ~pulse_r2) ?1:0;
assign neg_edge = (~pulse_r1 && pulse_r2) ?1:0; 
 
endmodule

 异步信号同步化,一般采用多加一级寄存器的方法来减小亚稳态的发生概率,如下图所示:

//异步信号边沿检测电路,三级寄存器实现
//2014/12/08
 
module edge_cap
(
    input clk, rst_n,
    input pulse,
    
    output pos_edge,
    output neg_edge
    
);
reg pulse_r1, pulse_r2, pulse_r3;
 
always @ (posedge clk or negedge rst_n)
if(!rst_n) 
    begin
    pulse_r1 <= 1'b0;
    pulse_r2 <= 1'b0;
    pulse_r3 <= 1'b0;
    end
else 
    begin
    pulse_r1 <= pulse;
    pulse_r2 <= pulse_r1;
    pulse_r3 <= pulse_r2;
    end
    
assign pos_edge = (pulse_r2 && ~pulse_r3) ?1:0;
assign neg_edge = (~pulse_r2 && pulse_r3) ?1:0; 
 
 
endmodule

3.2.2.2脉冲同步器(快时钟--慢时钟)

        对于快时钟域单电平脉冲信号跨时钟到慢时钟域常使用脉冲同步器电路,脉冲同步器在异步时钟域时钟频率彼此差距较大的场景下能节省触发器资源。快时钟域脉冲持续时间无法满足三边沿准则,需要通过翻转电路拉长脉冲电平以保证有效事件被采样,在接收时钟通过边沿检测回复原单电平脉冲。


 

 

`timescale 1ns/1ns

module pulse_detect(
	input 				clk_fast	, 
	input 				clk_slow	,   
	input 				rst_n		,
	input				data_in		,

	output  		 	dataout
);

reg data_level,data_level1,data_level2,data_level3;//翻转电路
always @(posedge clk_fast or negedge rst_n)
	if(!rst_n)
		data_level<=0;
	else
		data_level<= (data_in)? ~data_level : data_level;

always @(posedge clk_slow or negedge rst_n)//同步器
	if(!rst_n)
	begin
		data_level1<=0;
		data_level2<=0;
	end
	else
	begin
		data_level1<=data_level;
		data_level2<=data_level1;
	end

always @(posedge clk_slow or negedge rst_n)// D触发器
	if(!rst_n)
		data_level3<=0;
	else
		data_level3<=data_level2;

	assign dataout= data_level2^data_level3;


endmodule

3.3多有效可控事件背景下使用反馈机制

        现有多个连续的有效事件需要进行跨时钟,单个事件的发起时刻是可控的,这时建议使用反馈机制保证各有效事件跨时钟传输的安全性。

3.4单bit信号跨时钟方法总结

  1.  1.在单有效事件传输背景下,首先要确认是否需要用单周期脉冲表示单有效事件若没有要求,传输信号满足三边沿准则后可经同步器直接传输
  2.     2.在单有效事件传输背景下,对于满足三边沿准则的慢时钟域到快时钟域的跨时钟信号可以优先考虑边沿检测电路,其他其他情况下可使用脉冲同步器(快时钟到慢时钟)
  3.     3.边沿检测电路与脉冲同步器的思路类似,先保证跨时钟信号满足三边沿准则以传递有效状态,然后通过边沿检测获取单周期脉冲,二者的区别在于边沿检测电路的信号事先满足三边沿准则,脉冲同步器的信号需要翻转电路产生长电平,因此也可以考虑将不满足三边沿准则的短脉冲本地打拍取逻辑或输出得到满足三边沿准则的长电平信号再打拍来跨时钟,并在异步时钟域取上升沿检测信号,这种方式代码写起来简单但可能导致资源消耗过多 。(寄存器延迟一个时钟比较。
  4.     4.在多有效可控事件传输的背景下,可考虑对以上电路加入反馈控制
  5.     5.多有效不可控事件传输的背景下,如发生两有效事件无时钟间隔连发,则上述跨时钟方法无法保证有效状态的传递与有效事件数量的识别,此时只能引入缓存机制,采用如DPRAM或者异步FIFO的方式保证数据的安全传输

  1. 4、跨时钟域信号的分类——多比特数据信号。

  2. `timescale 1ns/1ns
    /**********************************RAM************************************/
    module dual_port_RAM #(parameter DEPTH = 16,
                           parameter WIDTH = 8)(
         input wclk
        ,input wenc
        ,input [$clog2(DEPTH)-1:0] waddr  //深度对2取对数,得到地址的位宽。
        ,input [WIDTH-1:0] wdata        //数据写入
        ,input rclk
        ,input renc
        ,input [$clog2(DEPTH)-1:0] raddr  //深度对2取对数,得到地址的位宽。
        ,output reg [WIDTH-1:0] rdata       //数据输出
    );
     
    reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
     
    always @(posedge wclk) begin//写入数据
        if(wenc)
            RAM_MEM[waddr] <= wdata;
    end
     
    always @(posedge rclk) begin//读出数据
        if(renc)
            rdata <= RAM_MEM[raddr];
    end
     
    endmodule 
    
    /**********************************SFIFO************************************/
    module sfifo#(
    	parameter	WIDTH = 8,
    	parameter 	DEPTH = 16
    )(
    	input 					clk		, 
    	input 					rst_n	,
    	input 					winc	,
    	input 			 		rinc	,
    	input 		[WIDTH-1:0]	wdata	,
    
    	output reg				wfull	,
    	output reg				rempty	,
    	output wire [WIDTH-1:0]	rdata
    );
    
    
    reg [$clog2(DEPTH):0] waddr,raddr;
    
     dual_port_RAM #(.DEPTH (DEPTH),
                        .WIDTH (WIDTH))
        dual_port_RAM (
            .wclk  (clk  ),
            .wenc  (wenc ),
            .waddr (waddr),
            .wdata (wdata),
            .rclk  (clk  ),
            .renc  (renc ),
            .raddr (raddr),
            .rdata (rdata)
        );
    
    
    
    always @(posedge clk or negedge rst_n)//读数据计数加一
    	if(!rst_n)
    		raddr<=0;
    	else if(renc)
    		raddr<=raddr+1'd1;
    	else
    		raddr<=raddr;
    
    
    
    always @(posedge clk or negedge rst_n)//写数据计数加一
    	if(!rst_n)
    		waddr<=0;
    	else if(wenc)
    		waddr<=waddr+1'd1;
    	else
    		waddr<=waddr;
    
    
    always @(posedge clk or negedge rst_n) //判断空满状态
    	if(!rst_n)begin
    		wfull<=0;
    		rempty<=0;
    	end
    	
    	else  begin
    		wfull<=(waddr==raddr+DEPTH);
    		rempty<=(waddr==raddr);
    	end
    
    	assign wenc = winc && !wfull;
        assign renc = rinc && !rempty;
     
    endmodule
    

    4.6异步FIFO

    1. 4.1FIFO定义

    2.         FIFO是英文First In First Out的缩写,是一种先进先出的数据缓存器,他与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。

      1. 4.2、FIFO功能
        FIFO存储器是系统的缓冲环节,如果没有FIFO存储器,整个系统就不可能正常工作,它主要有几方面的功能:

        1)对连续的数据流]进行缓存,防止在进机和存储操作时丢失数据;

        2)数据集中起来进行进栈和存储,可避免频繁的总线操作,减轻CPU的负担;

        3)允许系统进行DMA操作,提高数据的传输速度。这是至关重要的一点,如果不采用DMA操作,数据传输将达不到传输要求,而且大大增加CPU的负担,无法同时完成数据的存储工作。

        • 4.3、FIFO分类
                  FIFO的分类根据FIFO工作的时钟域,可以将FIFO分为同步FIFO和异步FIFO。同步FIFO是指读时钟和写时钟为同一个时钟。在时钟沿来临时同时发生读写操作。异步FIFO是指读写时钟不一致,读写时钟是互相独立的。同步FIFO和异步FIFO如图所示,从图中可以看到,同步FIFO 具有一个独立的时钟端口 clock,因此所有的输入输出信号都同步于 clock 信号。而在异步FIFO 结构中,写端口和读端口分别有独立的时钟,所有与写相关的信号都是同步于写时钟 wrclk,所有与读相关的信号都是同步于读时钟 rdclk。

                  同步 FIFO 常用于同步时钟的数据缓存,异步 FIFO 常用于跨时钟域的数据信号的传递,例如时钟域 A 下的数据 data1 传递给异步时钟域 B,当 data1 为连续变化信号时,如果直接传递给时钟域 B 则可能会导致收非所送的情况,即在采集过程中会出现包括亚稳态问题在内的一系列问题,使用异步 FIFO 能够将不同时钟域中的数据同步到所需的时钟域中。

        • 4.4FIFO的几个重要参数

          FIFO 的宽度:FIFO 一次读写操作的数据位 N;

          FIFO 的深度:FIFO 可以存储多少个宽度为 N 位的数据。

          空标志:对于双时钟 FIFO 又分为读空标志 rdempty 和写空标志 wrempty。FIFO 已空或将要空时由 FIFO

          的状态电路送出的一个信号,以阻止 FIFO 的读操作继续从 FIFO 中读出数据而造成无效数据的读出。

          满标志:对于双时钟 FIFO 又分为读满标志 rdfull 和写满标志 wrfull。FIFO 已满或将要写满时由 FIFO

          的状态电路送出的一个信号,以阻止 FIFO 的写操作继续向 FIFO 中写数据而造成溢出。

          读时钟:读 FIFO 时所遵循的时钟,在每个时钟的上升沿触发。

          写时钟:写 FIFO 时所遵循的时钟,在每个时钟的上升沿触发。


      2. 4.5 同步FIFO

        •    模块主要分为读/写指针、读写指针的比较逻辑和RAM存储。
        • `timescale 1ns/1ns
          /**********************************RAM************************************/
          module dual_port_RAM #(parameter DEPTH = 16,
                                 parameter WIDTH = 8)(
               input wclk
              ,input wenc
              ,input [$clog2(DEPTH)-1:0] waddr  //深度对2取对数,得到地址的位宽。
              ,input [WIDTH-1:0] wdata        //数据写入
              ,input rclk
              ,input renc
              ,input [$clog2(DEPTH)-1:0] raddr  //深度对2取对数,得到地址的位宽。
              ,output reg [WIDTH-1:0] rdata       //数据输出
          );
           
          reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
           
          always @(posedge wclk) begin
              if(wenc)
                  RAM_MEM[waddr] <= wdata;
          end
           
          always @(posedge rclk) begin
              if(renc)
                  rdata <= RAM_MEM[raddr];
          end
           
          endmodule 
          
          /**********************************SFIFO************************************/
          module sfifo#(
          	parameter	WIDTH = 8,
          	parameter 	DEPTH = 16
          )(
          	input 					clk		, 
          	input 					rst_n	,
          	input 					winc	,
          	input 			 		rinc	,
          	input 		[WIDTH-1:0]	wdata	,
          
          	output reg				wfull	,
          	output reg				rempty	,
          	output wire [WIDTH-1:0]	rdata
          );
          
          
          reg [$clog2(DEPTH):0] waddr,raddr;
          
           dual_port_RAM #(.DEPTH (DEPTH),
                              .WIDTH (WIDTH))
              dual_port_RAM (
                  .wclk  (clk  ),
                  .wenc  (wenc ),
                  .waddr (waddr),
                  .wdata (wdata),
                  .rclk  (clk  ),
                  .renc  (renc ),
                  .raddr (raddr),
                  .rdata (rdata)
              );
          
          
          
          always @(posedge clk or negedge rst_n)//读数据计数加一
          	if(!rst_n)
          		raddr<=0;
          	else if(renc)
          		raddr<=raddr+1'd1;
          	else
          		raddr<=raddr;
          
          
          
          always @(posedge clk or negedge rst_n)//写数据计数加一
          	if(!rst_n)
          		waddr<=0;
          	else if(wenc)
          		waddr<=waddr+1'd1;
          	else
          		waddr<=waddr;
          
          
          always @(posedge clk or negedge rst_n) //判断空满状态
          	if(!rst_n)begin
          		wfull<=0;
          		rempty<=0;
          	end
          	
          	else  begin
          		wfull<=(waddr==raddr+DEPTH);
          		rempty<=(waddr==raddr);
          	end
          
          	assign wenc = winc && !wfull;
              assign renc = rinc && !rempty;
           
          endmodule
          
          
          

          5.6异步FIFO

          •         异步FIFO主要是由双端口存储器、写指针产生逻辑、读指针产生逻辑及空满标志产生逻辑4部分组成。读写操作是由两个完全不同时钟域的时钟所控制。在写时钟域部分,由写指针所产生逻辑生成写端口所需要的写地址和写控制信号;在读时钟域部分,由读指针产生逻辑生成读断口所需要的读地址和读控制信号;在空满标志产生部分,通常是把写指针与读指针相互比较产生空满标志。

             

            1. 双口RAM,用于数据的存储。(RAM中的指针的地址取FIFO中的地址的除地址第一位外的地址,因为FIFO中的第一位是标志位)

            2. 数据写入控制器,在wenc信号的使能下,数据写入控制器使RAM读入数据,同时数据写地址指针加一。

            3. 数据读取控制器,在renc信号的使能下,数据读出控制器使RAM读出数据,同时数据读地址指针加一。

            4. 读指针同步器:使用写时钟的两级触发器采集读指针,输出到数据写入控制器。

            5. 写指针同步器: 使用读时钟的两级触发器采集写指针,输出到数据读取控制器。

                    本题解采用的空满判断的方式是用格雷码的比较来产生空满信号,同时产生的空满信号,会与输入的winc,rinc输入的是能信号,共同控制数据是否写入和读出。

            • ​​​​​​​​​​​​​​        
        • `timescale 1ns/1ns
          
          /***************************************RAM*****************************************/
          module dual_port_RAM #(parameter DEPTH = 16,
          					   parameter WIDTH = 8)(
          	 input wclk
          	,input wenc
          	,input [$clog2(DEPTH)-1:0] waddr  //深度对2取对数,得到地址的位宽。
          	,input [WIDTH-1:0] wdata      	//数据写入
          	,input rclk
          	,input renc
          	,input [$clog2(DEPTH)-1:0] raddr  //深度对2取对数,得到地址的位宽。
          	,output reg [WIDTH-1:0] rdata 		//数据输出
          );
          
          reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
          
          always @(posedge wclk) begin
          	if(wenc)
          		RAM_MEM[waddr] <= wdata;
          end 
          
          always @(posedge rclk) begin
          	if(renc)
          		rdata <= RAM_MEM[raddr];
          end 
          
          endmodule  
          
          /***************************************AFIFO*****************************************/
          module asyn_fifo#(
          	parameter	WIDTH = 8,
          	parameter 	DEPTH = 16
          )(
          	input 					wclk	, 
          	input 					rclk	,   
          	input 					wrstn	,
          	input					rrstn	,
          	input 					winc	,
          	input 			 		rinc	,
          	input 		[WIDTH-1:0]	wdata	,
          
          	output wire				wfull	,
          	output wire				rempty	,
          	output wire [WIDTH-1:0]	rdata
          );
          
          
          /**********************addr bin gen*************************/
          //RAM读写地址的变化
          reg [ADDR_WIDTH:0]  waddr_bin;
          reg [ADDR_WIDTH:0]  raddr_bin;
          
          parameter ADDR_WIDTH = $clog2(DEPTH);
           
          always@(posedge rclk or negedge rrstn)//counter read
          	if(!rrstn)
          		raddr_bin<=0;
          	else if(renc)
          		raddr_bin<=raddr_bin+1'd1;
          	else
          		raddr_bin<=raddr_bin;
          
          always@(posedge wclk or negedge wrstn)//counter write
          	if(!wrstn)
          		waddr_bin<=0;
          	else if(wenc)
          		waddr_bin<=waddr_bin+1'd1;
          	else
          		waddr_bin<=waddr_bin;
          
          assign wenc = winc && !wfull;
          assign renc = rinc && !rempty;
          
          /**********************addr gray gen*************************/
          //二进制转换为格雷码
          wire    [ADDR_WIDTH:0]  waddr_gray;
          wire    [ADDR_WIDTH:0]  raddr_gray;
          reg    [ADDR_WIDTH:0]  wptr;
          reg    [ADDR_WIDTH:0]  rptr;
          
          assign waddr_gray = waddr_bin ^ (waddr_bin>>1);//二进制转换为格雷码
          assign raddr_gray = raddr_bin ^ (raddr_bin>>1);//二进制转换为格雷码
          
          
          always @(posedge rclk or negedge rrstn)//打一拍避免冲突与竞争
          	if(!rrstn)
          		rptr<=0;
          	else
          		rptr<=raddr_gray;
          
          always @(posedge wclk or negedge wrstn)//打一拍避免冲突与竞争
          	if(!wrstn)
          		wptr<=0;
          	else
          		wptr<=waddr_gray;
          	
          
          /**********************syn addr gray*************************/
          //同步器打两拍
          reg     [ADDR_WIDTH:0]  wptr_buff;
          reg     [ADDR_WIDTH:0]  wptr_syn;
          reg     [ADDR_WIDTH:0]  rptr_buff;
          reg     [ADDR_WIDTH:0]  rptr_syn;
          
          
          always@(posedge rclk or negedge rrstn)
          //同步器:格雷码写地址同步至读时钟域
          	if(!rrstn)
          	begin
          		wptr_buff<=0;
          		wptr_syn<=0;
          	end
          	else
          		begin
          		wptr_buff<=wptr;
          		wptr_syn<=wptr_buff;
          		end
          
          always@(posedge wclk or negedge wrstn)// 格雷码读地址同步至写时钟域
          	if(!wrstn)
          	begin
          		rptr_buff<=0;
          		rptr_syn<=0;
          	end
          	else
          		begin
          		rptr_buff<=rptr;
          		rptr_syn<=rptr_buff;
          		end
          
          /**********************full empty gen*************************/
          //空满状态判断
          assign wfull = (wptr == {~rptr_syn[ADDR_WIDTH:ADDR_WIDTH-1],rptr_syn[ADDR_WIDTH-2:0]});
          assign rempty = (rptr == wptr_syn);
          
          
          /**********************RAM*************************/
          
          
          wire [ADDR_WIDTH-1:0]  waddr;
          wire [ADDR_WIDTH-1:0]  raddr;
          
          assign waddr = waddr_bin[ADDR_WIDTH-1:0];
          assign raddr = raddr_bin[ADDR_WIDTH-1:0];
           
          dual_port_RAM #(.DEPTH(DEPTH),
                          .WIDTH(WIDTH)
          )dual_port_RAM(
              .wclk (wclk),  
              .wenc (wenc),  
              .waddr(waddr[ADDR_WIDTH-1:0]),  //深度对2取对数,得到地址的位宽。
              .wdata(wdata),           //数据写入
              .rclk (rclk), 
              .renc (renc), 
              .raddr(raddr[ADDR_WIDTH-1:0]),   //深度对2取对数,得到地址的位宽。
              .rdata(rdata)         //数据输出
          );
              
          endmodule

          5.6.1格雷码

          •         

            采用格雷码的原因:单bit翻转

                 每两个相邻编码之间只有一位是不同的,并且对于N位格雷码,当从最高位编码(对应二进制2^N -1)跳转到最低位编码(对应0)时也只会发生1bit跳转。单bit跳转意味着格雷码在通过二级同步器跨时钟时,输出不会出现不可控的中间状态,只能是正确的更新状态或者保持原来的状态。

          • 二进制码与gray码的转换关系

            • ​​​​​​​​​​​​​​二进制转换成格雷码:  assign    gray  =  (bin >>1^bin;

            • 格雷码转换成二进制码:

                   bin[N-1]  = gray[N-1];

                  for(i=0;i<(N-1);i+1)begin

                         bin[i] = ^(gray[N-1:0]>>i);  

                  end

          • ​​​​​​​