(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。

如果有更好的方法方案可以留言一起谈论,谢谢大家,欢迎大家点赞收藏留言讨论交流。


分享到:


相關文章: