FPGA跨时钟域处理的方法总结

  在实际应用的工程中,绝大部分使用的是多时钟系统,在多时钟系统中进行数据传输,不可避免的就会遇到跨时钟域的情况。而对于跨时钟域处理的信号,如果处理不当就会出现亚稳态,导致系统无法正确运行。以下是个人总结的几个对跨时钟域处理的同步方式总结:


1.双触发器同步器

使用双触发器来进行同步是最常用的同步方式(也称作“打两拍”),常用在单比特信号的同步系统中。

 

如图所示,触发器A和B1正在操作中的异步时钟域。在 CLK_B 时钟域中,当触发器 B1 对输入 B1-d 进行采样时,输出 B1-q 有可能进入亚稳态。但是在 CLK_B 时钟的一个时钟周期期间,输出 B1-q 可能会稳定到某个稳定值。如果 B1 在一个时钟周期内没有稳定到稳定值,则触发器 B2 的输出可能进入亚稳态,但 B2 在整个目标时钟周期内亚稳态的概率非常接近于零。

当单比特信号从快时钟到慢时钟同步时,要先使用快时钟对该信号进行信号延拓,确保在慢时钟域内能够对信号进行采样和同步。若是源时钟域(快时钟)信号变化太快,目的时钟(慢时钟)将可能无法对信号进行正确的采样。

当单比特信号同步的频率太高时,可以使用更多数量的触发器级,因为这将有助于降低同步器输出保持在亚稳态的可能性。

reg A_q;
always @ (posedge CLK_A)
    A_q <= Sig_1;

reg B1_d,B2_d;
always @ (posedge CLK_B)
begin
    B1_d <= A_q;
    B2_d <= B1_d;
end


2.边沿同步器

如果想要将同步的电平信号改变成边沿信号,应该在双触发器同步之后再加一级触发器,使用第二级触发器的输出和第三级触发器的输出进行运算,来构成边沿同步器。

reg A_q;
always @ (posedge CLK_A)
    A_q <= Sig_1;

reg B1_q,B2_q,B3_q;
always @ (posedge CLK_B)
begin
    B1_q <= A_q;
    B2_q <= B1_q; 
    B3_q <= B2_q;
end
//上升沿检测
assign pos_check = B2_q && !B3_q;
//下降沿检测
assign pos_check = !B2_q && B3_q;

3.脉冲同步器

脉冲同步器主要用来将源时钟域中生成的脉冲信号同步到目标时钟域。使用双触发器同步器无法直接同步脉冲。当使用双触发同步器从快速时钟域同步到慢速时钟域时,可能跳过脉冲,这可能导致脉冲检测丢失,因此依赖于它的后续电路可能无法正常工作。

如图所示,在CLK_A时钟域中,使用Sig_i单时钟脉冲信号作为输入信号来控制触发器A的输出的单比特信号的状态翻转,每当翻转电路接到一个脉冲信号,触发器A的输出发生一次电平翻转,脉冲信号结束,触发器A的输出信号Q保持上一个电平状态不变。

脉冲同步器的限制:输入脉冲之间的最小间隔必须等于两个同步器时钟周期。如果输入脉冲相互过近,则新时钟域中的输出脉冲也紧密相邻,结果是输出脉冲宽度比一个时钟周期宽。当输入脉冲时钟周期大于两个同步器时钟周期时,如果两个脉冲相邻太近,同步器就不能检测到每个脉冲。

reg A_q;
always @ (posedge CLK_A)
begin
  if(sig_1== 1'b1)
    A_q <= ~A_q;
  else
    A_q <= A_q;
end

reg B1_q,B2_q,B3_q;
always @ (posedge CLK_B)
begin
    B1_q <= A_q;
    B2_q <= B1_q; 
    B3_q <= B2_q;
end

assign Q = B2_q^B3_q;


 4.基于握手的脉冲同步器(结绳法)

由于基于双触发器的脉冲同步器在快时钟域到慢时钟域进行同步时,可能存在采样失效或亚稳态的问题,因此为了能够安全的进行跨时钟域的同步处理,引入了握手机制。基于握手机制的脉冲同步器又叫做“结绳法”,该方法适用于任何时钟域的过渡。

如图所示,在脉冲同步器的基础上添加了握手机制,sig_i脉冲在clk_a时钟域内将信号延长,使用触发器同步器同步到clk_b始终域内,当clk_b对 信号正确采样,向clk_a时钟域内同步数据应答信号(脉冲信号)。

该方法可以解决快时钟域到慢时钟域同步的问题,适用范围很广,但是实现比较复杂,在 设计要求比较高的场合应慎用。

module handshake_pulse(
  input clka,
  input clkb,
  input sig_i,
  input [16:0]idata,
  output reg sig_o,
  output reg [16:0]odata
);

  reg A_q = 0;
  reg sig_ack;
  always @ (posedge clka)
    begin
      if(sig_i == 1'b1)
        A_q <= 1'b1;
      else if(sig_ack == 1'b1)
        A_q <= 1'b0;
      else
        A_q <= A_q;
    end
  //
  reg B1_q,B2_q,B3_q;
  always @(posedge clkb)
    begin
      B1_q <= A_q;
      B2_q <= B1_q;
      B3_q <= B2_q;
    end
  //
  wire sig_o_edge;
  wire ack;
  assign ack = B2_q;
  assign sig_o_eddge = B2_q && !B3_q;
  //
  always @ (posedge clkb)
    begin
      if(sig_o_edge)
        begin
          sig_o <= 1'b1
          odata <= idatd;
        end
      else
        begin
          sig_o <= 1'b0;
          odata <= odata;
        end
    end
  
  wire sig_ack;
  reg ack_sync_f0,ack_sync_f1,ack_sync_f2;
  always @ (posedge clka)
    begin
      ack_sync_f0 <= ack;
      ack_sync_f1 <= ack_sync_f0;
      ack_sync_f2 <= ack_sync_f1;
    end
  assign sig_ack = ack_sync_f1 && !ack_sync_f2;
  
endmodule

5.握手协议

在 许多应用中,跨时钟域传送的不仅是简单的信号,数据总线,地址总线和控制总线都会同时进行传输,而握手协议就是用来解决这些情况的。

不同时钟域电路使用的握手协议有两种基本类型:全握手 和 部分握手。

每种类型的握手都要用到同步器。对于全握手信号,双方电路在声明或中止各自的握手信号前都要等待对方对的响应。

首先,在源时钟域先声明它的请求信号,然后目的时钟域对请求信号 进行检测,请求有效则声明它的响应信号。当源时钟域中检测到响应信号有效后,中止自己的请求信号。最后,当目的时钟域检测电路检测到请求无效后,随之中止自己的响应信号。除非源时钟域的电路检测到无效的响应信号后,不然不会再声明请求信号。

发送域的Verilog设计代码(This vhdl code come from Fpga Lab) 

module handshake_transmis(
	input tclk, //发送域的时钟
    input t_sclr,//复位信号
	input r_ack, //接收到的响应信号
	input data_avail, //数据有效信号
	input [31:0] transmit_data, //需要发送出去的信号
	output t_rdy, //对于发送时钟域,需要输出数据准备好了的信号,以便在接收时钟域接收此信号,用来提示接收信号
	output [31:0] t_data //需要发送出去的信号
);
	localparam IDLE = 3'b001;     //空闲状态,判断数据是否有效,如果有效就输出t_rdy有效,表示数据准备好了
	localparam ASSERT_TRDY = 3'b010; //到了这个状态,表明t_rdy已经有效了,这时我们需要判断响应r_ack_tclk是否有效,如果有效则使t_rdy无效,否则保持不变
	localparam DEASSERT_TRDY = 3'b100; //到了这个t_rdy无效状态,需要考虑下一次的数据传输了,如果data_avail有效,则下一个状态进入ASSERT_TRDY,且t_rdy有效,否则进入空闲状态

	reg current_state, next_state;
	reg t_rdy, t_rdy_d;
	wire [31:0] t_data;
	reg r_ack_d1, r_ack_tclk; //对于接收到的响应信号,需要进行时钟域同步,同步到发送时钟域

  always @ (posedge tclk)
  	begin
      if(t_sclr == 1'b1)
        begin
          t_rdy <= 0;
          //同步接收域的响应信号ack
		  r_ack_d1 <= 0;
		  r_ack_tclk <= 0;
        end
      else
        begin
          t_rdy <= t_rdy_d;
		  r_ack_d1 <= r_ack;
		  r_ack_tclk <= r_ack_d1;
        end
    end
  always @ (posedge tclk)
    begin
      	if(t_sclr == 1'b1)
          begin
			current_state <= IDLE;
          end
		else
          begin
			current_state <= next_state;
          end
    end
  
  always@(*)
    begin
      next_state = current_state;
      t_rdy_d = 0;
      t_data = 'd0;
      case(current_state)
        IDLE:
          begin
            if(data_avail)
              begin
                next_state = ASSERT_TRDY;
                t_rdy_d = 1'b1;
                t_data = transmit_data;
              end
            else;
          end
        ASSERT_TRDY:
          begin
            if(r_ack_tclk)
              begin
                t_rdy_d = 1'b0;
                next_state = DEASSERT_TRDY;
                t_data = 'd0;
              end
            else
              begin
                t_rdy_d = 1'b1;
                t_data = t_data;
              end
          end
        DEASSERT_TRDY:
          begin
            if(!r_ack_tclk)
              begin
                if(data_avail)
                  begin
                    next_state = ASSERT_TRDY;
                    t_rdy_d = 1'b1;
                    t_data = transmit_data;
                  end
                else next_state = IDLE;
              end
            else;
          end
        default: ;
      endcase
    end
  
endmodule

接受域的Verilog设计

module handshake_rclk(
	input rclk,
	input r_sclr,
	input t_rdy,
	input [31:0] t_data,
	output r_ack
	);

	localparam IDLE = 2'b01;
	localparam ASSERT_ACK = 2'b10;

	reg current_state, next_state;
	reg r_ack, r_ack_d;
    reg [31:0] t_data_rclk, t_data_rclk_d;
	reg t_rdy_d1, t_rdy_rclk;

    always@(posedge rclk) begin
      if(r_sclr == 1'b1) begin
			current_state <= IDLE;
			r_ack <= 0;
			t_data_rclk <= 0;
			t_rdy_d1 <= 0;
			t_rdy_rclk <= 0;
		end
		else begin
			current_state <= next_state;
			r_ack <= r_ack_nxt;
			t_data_rclk <= t_data_rclk_d;
			t_rdy_d1 <= t_rdy;
			t_rdy_rclk <= t_rdy_d1;
		end
	end

	always@(*) begin
		next_state = current_state;
		r_ack_nxt = 1'b0;
		t_data_rclk_d = t_data_rclk;
		case(current_state)
			IDLE: begin
				if(t_rdy_rclk) begin
					next_state = ASSERT_ACK;
					r_ack_d = 1'b1;
					t_data_rclk_d = t_data;
				end
				else;
			end

			ASSERT_ACK: begin
				if(~t_rdy_rclk) begin
					r_ack_d = 1'b0;
					next_state = IDLE;
				end
				else r_ack_d = 1'b1;
			end

		endcase

	end

6.异步FIFO

posted @ 2021-09-30 10:14  zwh搁浅  阅读(2885)  评论(1)    收藏  举报