(LVDS差分信號簡單處理)2. DDR信號的處理

注意,這裡的DDR指的是Double Data Rate,雙倍數據速率。這篇文章並不是講DDR存儲器系列的東西。

不同於SDR,也就是單上升沿或下降沿,傳輸數據。DDR說我不想選擇是上升沿還是下降沿傳輸數據,小孩子才做選擇,大人只會說我全都要。

(LVDS差分信號簡單處理)2. DDR信號的處理

上升沿和下降沿全部都要傳數據



通過一組圖片就可以看到SDR和DDR的區別:

(LVDS差分信號簡單處理)2. DDR信號的處理

SDR


(LVDS差分信號簡單處理)2. DDR信號的處理

DDR

可以看到經過DDR處理的數據在數據時鐘上升沿和下降沿都有數據更新,對於如何完整的取出數據,我仔細思考了許久,經歷了否定之否定的過程,最終才找到了通用的解決方案。現在寫出解決方案的心路歷程:

  1. 剛開始覺得,既然它上升沿和下降沿都有,不如用一個always檢測時鐘跳變,有跳變就開始取值。代碼示例如下:
<code>always @ (fb_clk)beginif (fb_clk == 1'b1) //上升沿跳變   i_data <= tx_frame ? {tx_d,6'd0} : {i_data[11:6], tx_d};// tx_frame為高代表高6位, 低為低8位else   q_data <= tx_frame ? {tx_d,6'd0} : {q_data[11:6], tx_d};end/<code>

但是這麼做肯定是有問題的,我們本來是要描述一個時序電路,最後always的敏感列表裡面是一個信號,這麼做就成了組合邏輯了,這麼做不穩定不可取。


2. 第二種方法是使用鎖相環輸出一個與原數據時鐘同頻但相位延後180度的時鐘fb_clk_180, fb_clk負責採樣data_I, fb_clk_180負責data_Q。這種方法可以,但感覺麻煩,因為後面還要使用DDR輸出信號,時鐘轉來轉去有點麻煩。


3. 第三種方法還是使用鎖相環,輸出一個同相但頻率為原來頻率2倍的時鐘信號fb_clk_mul2。fb_clk_mul2的每次上升沿,對應著原時鐘fb_clk的上升沿和下降沿,使用fb_clk_mul2就可以分離data_I和data_Q。但這種方法也有侷限性,不僅增加時鐘數量,當原時鐘速率過高,這種方法的穩定性也將有待商榷。


最後,我們在Vivado裡面找到了一種原語,完美解決這個問題。這就是IDDR和ODDR。

對於輸入信號,我們使用IDDR解出原始數據,在Language Template找到IDDR原語示例,例子如下:

<code>IDDR #(         .DDR_CLK_EDGE("SAME_EDGE"), // "OPPOSITE_EDGE", "SAME_EDGE"                                          //    or "SAME_EDGE_PIPELINED"          .INIT_Q1(1'b0), // Initial value of Q1: 1'b0 or 1'b1         .INIT_Q2(1'b0), // Initial value of Q2: 1'b0 or 1'b1         .SRTYPE("SYNC") // Set/Reset type: "SYNC" or "ASYNC"       ) IDDR_inst (         .Q1(rx_data_pos[i]), // 1-bit output for positive edge of clock          .Q2(rx_data_neg[i]), // 1-bit output for negative edge of clock         .C(data_clk),   // 1-bit clock input         .CE(1'b1), // 1-bit clock enable input         .D(rx_data_dly[i]),   // 1-bit DDR data input//         .D(rx_data[i]),   // 1-bit DDR data input         .R(1'b0),   // 1-bit reset         .S(1'b0)    // 1-bit set      );/<code>

設置好IDDR的4個常量參數之後,將數據時鐘接入C端口,時鐘使能CE端口拉高,待轉數據信號接入D端口,Q1端口將會輸出時鐘上升沿採樣的數據,Q2端口將會輸出時鐘下降沿採樣的數據。注意設置好復位R和置位S端口。

設置好之後就可以在rx_data_pos,rx_data_neg看到數據。這裡我使用了generate for生成塊,所以出現了genvar變量i;

同樣,對於DDR輸出信號,使用ODDR原語解決:

<code> ODDR #(     .DDR_CLK_EDGE("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE"      .INIT(1'b0),    // Initial value of Q: 1'b0 or 1'b1     .SRTYPE("SYNC") // Set/Reset type: "SYNC" or "ASYNC"   ) ODDR_inst (     .Q(p0_data[i]),   // 1-bit DDR output     .C(data_clk),   // 1-bit clock input     .CE(1'b1), // 1-bit clock enable input     .D1(idata[i]), // 1-bit data input (positive edge)     .D2(qdata[i]), // 1-bit data input (negative edge)     .R(1'b0),   // 1-bit reset     .S(1'b0)    // 1-bit set  );/<code>

設置好ODDR的3個常量參數之後,將數據時鐘接入C端口,時鐘使能CE端口拉高,Q端口輸出DDR處理後的數據,數據時鐘上升沿更新的數據接入D1端口,數據時鐘下降沿更新的數據接入D2端口。注意設置好復位R和置位S端口。

ODDR還可以巧妙地輸出時鐘,在D1輸入1'b1, D2輸入1'b0,其他不變,則在數據時鐘上升沿輸出高電平,下降沿輸出低電平。巧妙地輸出了數據時鐘。

注意,ODDR輸出的數據只能經過IOBUF或者輸出,曾經有人想使用ILA抓取ODDR的Q端口輸出的數據,無奈Implemention總會報錯。


總結:

  • 對於DDR信號,不能直接用always @ (data_clk)的方法採樣信號,詳細見上述(1)內容
  • 上述(2)和(3)的方法在一定範圍內都有其可行性,但也有一些弊端,詳細見上述(2)和(3)內容
  • 使用IDDR和ODDR最為妥當,IDDR和ODDR的數據端口都是1bit,多bit可以使用generate for生成塊
  • 可以使用ODDR在普通IO上輸出數據時鐘
  • ODDR輸出的數據只能經過IOBUF或者輸出

如果是LVDS信號,需要先轉單端再進IDDR;或者ODDR後再轉差分輸出;差分信號的處理方法可以看上一篇文章。

信號處理好之後,如果出現了時鐘與數據對不上該怎麼辦,這個時候可以使用Idelay調整時序。我們下一篇文章可以談論一下Idelay。

如果有更好的方法方案可以留言一起談論,謝謝大家,歡迎大家點贊收藏留言討論交流。


分享到:


相關文章: