參考文檔
https://blog.csdn.net/u011412586/article/details/10009761
前言
對於信號需要跨時鍾域處理而言,最重要的就是確保數據能穩定的傳送到采樣時鍾域。
普通的cdc處理方法需要關注時鍾域速度的異同,即分慢時鍾域到快時鍾域、快時鍾域到慢時鍾域、相位關系等問題,會讓人瞬間爆炸。
那么,是否有一種相對穩定,又無需關注傳送時鍾域和接收時鍾域兩者時鍾速度、相位關系的數據傳遞方式呢?
這里仿真驗證握手結繩法,自己想的,不知道跟結繩法是不是一致的。。。。。。
思考方式為:要絕對的保證數據被接收,自然是握手應答機制。
流程
首先考慮亞穩態說明:對於異步信號的采集,不可避免的會有亞穩態,但亞穩態的恢復時間一般為1個時鍾或者兩個時鍾,同樣其不會是0、1的中間值,而是一個開發者無法確定的定值,同時你的時鍾頻率越高,亞穩態發生的概率就越大。
網上感覺都說的不明就里,對於簡單的使用多級觸發器,然后使用最高的2bit進行邊沿檢測,目測是不行的。
可以理解為仿真中的x值,但數字電路的本質上是有值的,要么0,要么1。
所以如果是檢測一個信號的上升沿,即使采到亞穩態,那就分兩種情況:
(1)邊沿比較陡峭,時鍾只采到1次亞穩態,那么邊沿寄存器的值就可能為:00、01。檢測到00,下一次就會采到01。如果是檢測到01,就是想要的沿。
(2)邊沿一點都不陡峭,導致時鍾采到兩次甚至更多次的亞問題,那這就沒得玩了。因為你不知道當前是00\01\10\11,這就可能導致你的后續邏輯完蛋了。
當然這里第二種情況不予考慮,FPGA內部不太可能出現這么糟糕的上升沿,除非你的時鍾過快,在時鍾沿到來前,亞穩態還沒有恢復過來。
極端的處理方式是(本質上是濾波):用更多位的寄存器,比如8bit,當檢測到為8'b0000_1111,那就是上升沿啦,雖然這闊以絕對可靠的檢測到邊沿,但檢測出沿的所用的時間更長,同時需要保證前級信號的高低電平長度,否則就被濾波了。。。。。。
所以,信號的邊沿陡峭性是很重要的,專業術語叫做:上升時間、下降時間。
言歸正傳,那么握手結繩法是咋樣的操作呢?
如下圖所示:有點復雜
如圖所示,則操作流程為:上述方法應該是支持多bit數據跨時鍾域的,這里的多bit說的是FPGA內部數據,pulse_clk1表示發送數據使能,clk1檢測到使能則拉高pulse_delay_clk1開始結繩。在clk2中,檢測到clk1中的結繩信號的上升沿則產生pulse_clk2,clk2檢測到pulse_clk2則啟動clk2中的結繩pulse_delay_clk2,在clk1中檢測到clk2中的結繩信號為高則解掉clk1的結繩。在clk2中檢測到clk1的結繩拉低,則解繩clk2中的結繩信號pulse_delay_clk2,完成傳輸。
待傳送數據在clk2的結繩上升沿被采樣。
busy信號為clk1中的信號,在clk1的發送數據使能到來則拉高,在pulse_delay_clk2的下降沿則拉低。
可以看到,這種方式只適合少量的慢速的數據傳輸。
源代碼
`timescale 1ns/1ps module cdc_test ( input i_clk1 , input i_clk2 , // input i_rst_n , input i_en_clk1 , input [15:0] i_data_clk1 , output o_busy_clk1 , output o_valid_clk2 , output [15:0] o_data_clk2 ); ////clk1 ////clk1的發送使能邊沿檢測 reg [1:0] r_en_clk1_edge = 2'b00; always @(posedge i_clk1) begin r_en_clk1_edge <= {r_en_clk1_edge[0],i_en_clk1}; end reg r_en_clk2 = 1'b0;////clk2的使能邊沿檢測 reg [1:0] r_en_clk2_edge = 2'b00; always @(posedge i_clk1) begin r_en_clk2_edge <= {r_en_clk2_edge[0],r_en_clk2}; end reg r_en_expand = 1'b0; ////clk1的使能延長 always @(posedge i_clk1) begin if (r_en_clk1_edge == 2'b01) r_en_expand <= 1'b1; else if (r_en_clk2_edge == 2'b01) r_en_expand <= 1'b0; end reg r_busy_clk1 = 1'b0; ////傳輸忙碌指示,忙碌的時候clk1的數據不可發生改變 always @(posedge i_clk1) begin if (r_en_clk1_edge == 2'b01) r_busy_clk1 <= 1'b1; else if (r_en_clk2_edge == 2'b10) r_busy_clk1 <= 1'b0; end ////clk2 reg [1:0] r_en_expand_edge = 2'b00; ////clk1的擴展后的使能邊沿檢測 always @(posedge i_clk2) begin r_en_expand_edge <= {r_en_expand_edge[0],r_en_expand}; end always @(posedge i_clk2) ////clk2中的使能 begin if (r_en_expand_edge == 2'b01) r_en_clk2 <= 1'b1; else if (r_en_expand_edge == 2'b10) r_en_clk2 <= 1'b0; end reg r_valid_clk2 = 1'b0; always @(posedge i_clk2) begin if (r_en_expand_edge == 2'b01) ////上升沿表示數據有效 r_valid_clk2 <= 1'b1; else r_valid_clk2 <= 1'b0; end reg [15:0] r_data_clk2 = 16'd0; always @(posedge i_clk2) begin if (r_en_expand_edge == 2'b01) ////上升沿刷新數據 r_data_clk2 <= i_data_clk1; end ////信號輸出 assign o_busy_clk1 = r_busy_clk1; assign o_valid_clk2 = r_valid_clk2; assign o_data_clk2 = r_data_clk2; endmodule // end the cdc_test model
仿真看看。
可以看到數據正確傳輸,不管是快到慢還是慢到快。
以后再板級測試啦。
以上。