单bit信号跨时钟域处理
跨时钟域在FPGA开发中是比较常见的问题,常见的跨时钟域有单bit信号及多bit信号。笔者这里讲的是单bit信号的跨时钟域处理方法,其实单bit与多bit信号的处理方式有些是通用的,只是资源消耗问题。比如都可以使用异步FIFO或者ram进行异步转换,但是对于单bit信号则显得有些浪费资源。
对于快转慢信号:可通过扩展信号时间长度来实现,其实就是要保证快时钟域的信号在慢时钟域的边沿能够检测到完整的信号,最好信号长度拓展超过慢时钟周期的一半以上,这样才能确保信号被慢时钟边沿检测到。前提是知道慢时钟大小才能确定要拓展多少的长度。
对于慢转快信号:慢信号的长度直接超过了快时钟的周期,所以可以直接打两拍进行时钟域同步。
以上都是最常见而且最简单的跨时钟域处理方式,笔者这里再提下使用原语进行跨时钟域处理的方法。可以根据需要到xilinx官网下载相关的原语资料,这里不对原语进行一一说明。以下是以UltraScale的原语为例,如图目录下红框CDC部分是跨时钟域处理的原语模块。
根据实际需要使用情况选择相应的原语模块,笔者这里演示的是单bit信号,因此只需使用xpm_cdc_single模块,文档中也有相关的Testbanch File,以下是笔者写的仿真代码及仿真结果。
1 `timescale 1ns/1ps 2 module CDC_tb; 3 4 reg clk1; //125MHz 5 reg clk2; //100MHz 6 reg rstn; 7 8 reg signal_in1; 9 reg signal_in2; 10 reg [15:0] cnt1; 11 reg [15:0] cnt2; 12 13 wire signal_out1; 14 wire signal_out2; 15 16 17 initial 18 begin 19 clk1 = 0; 20 clk2 = 0; 21 rstn = 0; 22 #200; 23 rstn = 1; 24 end 25 26 always #4 clk1 = ~clk1; 27 always #5 clk2 = ~clk2; 28 //fast to slow 29 xpm_cdc_single 30 #( 31 .DEST_SYNC_FF (2 ), // DECIMAL; range: 2-10 32 .INIT_SYNC_FF (0 ), // DECIMAL; 0=disable simulation init values, 1=enable simulation init values 33 .SIM_ASSERT_CHK (0 ), // DECIMAL; 0=disable simulation messages, 1=enable simulation messages 34 .SRC_INPUT_REG (0 ) // DECIMAL; 0=do not register input, 1=register input 35 ) 36 u_xpm_cdc_single1 37 ( 38 .dest_out (signal_out1 ), // 1-bit output: src_in synchronized to the destination clock domain. This output is 39 // registered. 40 .dest_clk (clk2 ), // 1-bit input: Clock signal for the destination clock domain. 41 .src_clk (clk1 ), // 1-bit input: optional; required when SRC_INPUT_REG = 1 42 .src_in (signal_in1 ) // 1-bit input: Input signal to be synchronized to dest_clk domain. 43 ); 44 //slow to fast 45 xpm_cdc_single 46 #( 47 .DEST_SYNC_FF (2 ), // DECIMAL; range: 2-10 48 .INIT_SYNC_FF (0 ), // DECIMAL; 0=disable simulation init values, 1=enable simulation init values 49 .SIM_ASSERT_CHK (0 ), // DECIMAL; 0=disable simulation messages, 1=enable simulation messages 50 .SRC_INPUT_REG (1 ) // DECIMAL; 0=do not register input, 1=register input 51 ) 52 u_xpm_cdc_single2 53 ( 54 .dest_out (signal_out2 ), // 1-bit output: src_in synchronized to the destination clock domain. This output is 55 // registered. 56 .dest_clk (clk1 ), // 1-bit input: Clock signal for the destination clock domain. 57 .src_clk (clk2 ), // 1-bit input: optional; required when SRC_INPUT_REG = 1 58 .src_in (signal_in2 ) // 1-bit input: Input signal to be synchronized to dest_clk domain. 59 ); 60 61 always@(posedge clk1 or negedge rstn) 62 begin 63 if(~rstn) 64 begin 65 cnt1 <= 'd0; 66 end 67 else if(cnt1[15]) 68 begin 69 cnt1 <= cnt1; 70 end 71 else 72 begin 73 cnt1 <= cnt1 + 1'b1; 74 end 75 end 76 77 always@(posedge clk1 or negedge rstn) 78 begin 79 if(~rstn) 80 begin 81 signal_in1 <= 'd0; 82 end 83 else if(&cnt1[5:0]) 84 begin 85 signal_in1 <= 1'b1; 86 end 87 else 88 begin 89 signal_in1 <= 'd0; 90 end 91 end 92 93 94 always@(posedge clk2 or negedge rstn) 95 begin 96 if(~rstn) 97 begin 98 cnt2 <= 'd0; 99 end 100 else if(cnt2[15]) 101 begin 102 cnt2 <= cnt2; 103 end 104 else 105 begin 106 cnt2 <= cnt2 + 1'b1; 107 end 108 end 109 110 always@(posedge clk2 or negedge rstn) 111 begin 112 if(~rstn) 113 begin 114 signal_in2 <= 'd0; 115 end 116 else if(&cnt2[5:0]) 117 begin 118 signal_in2 <= 1'b1; 119 end 120 else 121 begin 122 signal_in2 <= 'd0; 123 end 124 end 125 126 endmodule
也可以自己写代码实现,为了方便使用,不管快到慢还是慢到快都能兼容,这里使用结绳法也叫握手法。原理就是信号的握手,代码如下:
1 //************************************************************************** 2 // *** file name : Handshake_CDC.v 3 // *** version : 1.0 4 // *** Description : Handshake_CDC 5 // *** Blogs : https://www.cnblogs.com/WenGalois123/ 6 // *** Author : Galois_V 7 // *** Date : 2022.7.11 8 // *** Changes : Initial 9 //************************************************************************** 10 `timescale 1ns/1ps 11 module Handshake_CDC 12 ( 13 input i_clk1 , 14 input i_rstn , 15 input i_clk2 , 16 input i_clk1_signal , 17 output o_clk2_siganl , 18 output o_clk2_signal_pos 19 ); 20 reg [2:0] r_clk1_signal; 21 reg [2:0] r_clk2_signal; 22 23 always@(posedge i_clk1) 24 begin 25 if(~i_rstn) 26 begin 27 r_clk1_signal[0] <= 'd0; 28 end 29 else if(i_clk1_signal) 30 begin 31 r_clk1_signal[0] <= 1'b1; 32 end 33 else if(r_clk1_signal[2]) 34 begin 35 r_clk1_signal[0] <= 'd0; 36 end 37 end 38 39 always@(posedge i_clk1) 40 begin 41 if(~i_rstn) 42 begin 43 r_clk1_signal[2:1] <= 'd0; 44 end 45 else 46 begin 47 r_clk1_signal[2:1] <= {r_clk1_signal[1],r_clk2_signal[1]}; 48 end 49 end 50 51 always@(posedge i_clk2) 52 begin 53 if(~i_rstn) 54 begin 55 r_clk2_signal <= 'd0; 56 end 57 else 58 begin 59 r_clk2_signal <= {r_clk2_signal[1:0],r_clk1_signal[0]}; 60 end 61 end 62 assign o_clk2_siganl = r_clk2_signal[1]; 63 assign o_clk2_signal_pos = ~r_clk2_signal[2] & r_clk2_signal[1]; 64 endmodule
虽说写的代码输出信号没原语输出的那么好,但实际运用使用上下边沿同步信号即可。