DDR(double data rate)是指相对于时钟频率,data的吞吐率是普通IO接口的双倍,即在时钟的上升沿可以读写一笔数,在时钟的下降沿也可以读写一笔数。
标准的应用中Vivado提供向导生成DDR2,DDR3控制器,DDR控制器也将DDR接口等设置做好,因此无需单独设置DDR接口,但还有许多应用场景还是需要单独设置,单独使用DDR接口已满足与其它设备的接口,如RGMII接口等。Xilinx FPGA DDR接口有输入接口和输出接口两种,下面将分开介绍。
1. DDR接口只用在端口上
尽管在FPGA内部通过Verilog语言描述也可以实现DDR的方式,但与数据相关的IP core都是单边沿操作,因此对于DDR的使用都是用在FPGA端口上。IDDR输入使用端口寄存器,将DDR双边沿数据变成两个单边沿寄存器输出。同样如果使用ODDR,则将FPGA内部单边沿的两个时钟的数据Q1,Q2变成双边沿的一个时钟内输出,即上升沿输出Q1,下降沿输出Q2。通过DDR接口在时钟频率不变的情况下,将端口的数据吞吐率提高一倍,著名的DDR存储器系列就是通过这种方式实现的。
2. DDR输入接口(IDDR)
DDR输入接口是指数据或时钟输入是以DDR接口实现的,即输入数据是双边沿的,输出将数据还原回单边沿的方式。
-
DDR输入接口Verilog原语
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<span style="color: #0000ff;">IDDR #( .DDR_CLK_EDGE(“OPPOSITE_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(Q1), // 1-bit output for positive edge of clock .Q2(Q2), // 1-bit output for negative edge of clock .C(C), // 1-bit clock input .CE(CE), // 1-bit clock enable input .D(D), // 1-bit DDR data input .R(R), // 1-bit reset .S(S) // 1-bit set ); </span> |
这个原语可以复制到Verilog程序中直接使用,虽然这个原语是ARTIX器件的相关器件原语,但整个7系列的所有器件的IDDR原语都相同。下面就各个部分作简单介绍,IDDR分为两个部分,参数化(属性)部分以及IP核实体例化部分。
-
IDDR参数化(属性)部分
-
时钟边沿属性
-
对于IDDR时钟边沿属性分为OPPOSITE_EDGE,SAME_EDGE和SAME_EDGE_PIPELINED三种模式。
-
- OPPOSITE_EDGE模式
.DDR_CLK_EDGE(“OPPOSITE_EDGE”), // “OPPOSITE_EDGE”, “SAME_EDGE”
// or “SAME_EDGE_PIPELINED”
“OPPOSITE_EDGE”属性是指,Q1上升沿输出,Q2下降沿输出,即Q1,Q2分别同步在相反的时钟边沿上,如图1所示。

图1 IDDR OPPOSITE_EDGE模式
图1可以看出Q1,Q2交错输出,Q1与Q2相差1800,在FPGA内部可以利用时钟再次锁存,统一到同一个边沿上,如:
always@(posedge c) //统一到上升沿,此时D0A,D1A 为一对,D2A,D3A 为一对。
begin
Q3 <= Q1;
Q4 <= Q2;
end
此时Q3,Q4统一在时钟上升沿输出,锁存后Q3,Q4的数据成对并列出现,如在同一个上升沿锁存后Q3,Q4的值(D0A,D1A),(D2A,D3A),(D4A,D5A)……。但在对时钟C进行约束时应将时钟的约束频率翻倍。 “OPPOSITE_EDGE”属性适合并行数据与串行数据的转换。
或利用下降沿锁存
always@(negedge c) //统一到下降沿,第一个下降沿Q5 = D0A, 第二下降沿Q5 = D2A,Q6 = D1A。
begin
Q5 <= Q1;
Q6 <= Q2;
end
此时Q5,Q6统一在时钟下降沿输出,Q5,Q6的数据奇偶交错,Q6的数据比Q5滞后一个时钟周期。但在对时钟C进行约束时应将时钟的约束频率翻倍。 “OPPOSITE_EDGE”属性适合高速数据流与DDR数据的转换。
-
- SAME_EDGE模式
“SAME_EDGE” 属性是指,双边沿数据D经过IDDR后,输出Q1、Q2相差一个时钟周期,都同步在上升沿上,如图2.

图2 “SAME_EDGE” 属性IDDR
“SAME_EDGE” 属性适合前后相差一个时钟周期的串行数据合并成DDR数据,以降低端口时钟频率。
-
- SAME_EDGE_PIPELINED模式
“SAME_EDGE_PIPELINED” 是指双边沿数据D经过IDDR后,延时一个时钟周期,Q1,Q2输出统一合并到上升沿,如图3所示。

图3 IDDR SAME_EDGE_PIPELINED模式
属性为”SAME_EDGE_PIPELINED”的IDDR的输出Q1,Q2成对同步在时钟的上升沿上,这与 “OPPOSITE_EDGE”属性的IDDR输出后,再经过上升沿锁存的效果相同,如Q3,Q4
-
- INIT属性
INIT属性用来初始化输出Q1,Q2 的初始值,一般为1’b0或1’b1。
.INIT_Q1(1’b0), // Initial value of Q1: 1’b0 or 1’b1
描述输出端Q1的初始值,也就是输入数据没有到来,或DDR在没有完全接受控制之前的数据,一般0或1都可以,IDDR在数据分解之前可以使用R或S输入端进行复位或置位。
.INIT_Q2(1’b0), // Initial value of Q2: 1’b0 or 1’b1
描述输出端Q2的初始值,也就是输入数据没有到来,或DDR在没有完全接收控制之前的数据,一般0或1都可以,IDDR在数据分解之前可以使用R或S输入端进行复位或置位。
-
- SRTYPE属性
SRTYPE属性可以指定复位端R、置位端S是否与时钟同步。
.SRTYPE(“SYNC”) // Set/Reset type: “SYNC” or “ASYNC”
描述S,R置位/复位输入端的特性,有同步(SYNC)和异步(ASYNC)两种,同步就是置位或复位位为高电平时复位或置位并不能立即生效,而是在时钟上升沿时才会生效,而异步行为将立即生效。
-
实体例化部分端口
输入时钟 C:IDDR寄存器的输入时钟,时钟C与数据D在双边沿同步。
输入端D:DDR数据输入,在输出端口将被分离出Q1,Q2。
输出Q1 :从输入端D分离出的数据 ,D端对应时钟的上升沿的数据。
输出Q2 :从输入端D分离出的数据 ,D端对应时钟的下降沿的数据。
输入CE:时钟使能,高电平有效。
输入R: 复位端,高电平时Q1,Q2为0,R为0,IDDR正常工作。
输入S: 置位端,高电平时 Q1, Q2为1, S为0时,IDDR正常工作。
注: R,S不能同时为高电平。
3. DDR输出接口(ODDR)
ODDR的原语如下,
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<span style="color: #0000ff;">ODDR #(</span> <span style="color: #0000ff;"> .DDR_CLK_EDGE(“OPPOSITE_EDGE”), // “OPPOSITE_EDGE” or “SAME_EDGE”</span> <span style="color: #0000ff;"> .INIT(1’b0), // Initial value of Q: 1’b0 or 1’b1</span> <span style="color: #0000ff;"> .SRTYPE(“SYNC”) // Set/Reset type: “SYNC” or “ASYNC”</span> <span style="color: #0000ff;">) ODDR_inst (</span> <span style="color: #0000ff;"> .Q(Q), // 1-bit DDR output</span> <span style="color: #0000ff;"> .C(C), // 1-bit clock input</span> <span style="color: #0000ff;"> .CE(CE), // 1-bit clock enable input</span> <span style="color: #0000ff;"> .D1(D1), // 1-bit data input (positive edge)</span> <span style="color: #0000ff;"> .D2(D2), // 1-bit data input (negative edge)</span> <span style="color: #0000ff;"> .R(R), // 1-bit reset</span> <span style="color: #0000ff;"> .S(S) // 1-bit set</span> <span style="color: #0000ff;">);</span> |
-
ODDR属性部分(参数)
ODDR属性部分(参数),仅有 “OPPOSITE_EDGE” 与 “SAME_EDGE” 两种,因为ODDR是将D1,D2两个数据合并到双边沿输出,因此没有 “SAME_EDGE_PIPELINED” 选项。
-
-
OPPOSITE_EDGE模式:
-
当输入参数为OPPOSITE_EDGE模式时,要求输入数据D1,D2在时钟CLK的相位上差1800,即D1满足上升沿采样条件(如 setup time,hold time),也可与上升沿对齐。D2满足下降沿采样条件或与下降沿对齐,输出OQ的上升沿对应D1 对应的数据(D1A),下降沿对应D2的数据(D2A),如图4。

图4 ODDR 的OPPOSITE_EDGE模式
OPPOSITE_EDGE 模式一般用在串行数据流,可以降低端口的时钟频率。
-
SAME_EDGE 模式:
ODDR的SAME_EDGE模式要求输入数据D1,D2满足CLK的上升沿采样条件或与CLK的上升沿对齐。

图5 ODDR的SAME_EDGE模式
-
- SAME_EDGE模式一般用在并行数据转成DDR的方式输出。
- INIT 初始化Q的值为INIT的值,将Q初始化为0或1。
- STYPE 属性参数为“SYNC ”,“ASYNC”用来设置R,S 是否与时钟同步。
-
ODDR例化的实体部分,
D1,D2 对应的输入数据,Q为DDR输出。CE为时钟使能,当CE为低电平时ODDR进入低功耗模式。R,S分别为复位和置位端,R为1时,Q = 0,S为1时Q = 1。R,S不能同时为1.
4. DDR接口的使用与仿真
由于DDR接口在FPGA端口实现,因此实时DEBUG工具很难观测到端口上的具体行为,但仿真可以看到具体细节,因此对于DDR接口学习和使用,仿真是一个很有效的办法。后续IDDR,ODDR的部分主要以实例的方式介绍高速数据流到DDR的转换,以及并行数据交替在DDR接口串行输出,以及多IDDR,ODDR并行数据的接口实现。
4.1. 串行数据流到DDR的收发转换
-
-
ODDR 的使用与仿真
-
例1: 利用ODDR将串行数据流转成DDR的方式输出,并将端口时钟频率降低1倍。假设数据流以11010011_10111100为基本单位进行循环。
由于ODDR的D1,D2对应的是串行数据,因此选用OPPOSITE_EDGE属性比较合适。
ODDR实体程序:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
`timescale 1ns / 1ps module test_ODDR # ( parameter DDR_CLK_EDGE = "OPPOSITE_EDGE", // "OPPOSITE_EDGE" or "SAME_EDGE" parameter INIT = 1'b0, // Initial value of Q: 1'b0 or 1'b1 parameter SRTYPE = "SYNC" // Set/Reset type: "SYNC" or "ASYNC" ) ( input DDR_IN_CLK, input D1, input D2, input CE, input R, input S, output DDR_OUT_CLK, output Q ); assign DDR_OUT_CLK = DDR_IN_CLK; ODDR #( .DDR_CLK_EDGE("OPPOSITE_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(Q), // 1-bit DDR output .C(DDR_IN_CLK), // 1-bit clock input .CE(CE), // 1-bit clock enable input .D1(D1), // 1-bit data input (positive edge) .D2(D2), // 1-bit data input (negative edge) .R(R), // 1-bit reset .S(S) // 1-bit set ); endmodule |
仿真程序:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
`timescale 1ns / 1ps module sim_ODDR( ); reg inclk = 0; wire DDR_IN_CLK; wire DDR_OUT_CLK; always #2 inclk = ~inclk; reg DDR_R = 0, DDR_S = 0; reg DDR_CE = 1; reg DDR_D1 = 0; reg DDR_D2 = 0; reg shift_r = 0; reg [ 15: 0 ] shift_reg = 0; reg HL; reg DDR_VLD; reg DDR_VLD_R; reg PLL_RST; wire locked; wire rst = !locked; initial begin DDR_R = 0; DDR_S = 1; PLL_RST = 1'b1; #20 DDR_S = 0; PLL_RST = 1'b0; #2000 $stop; end always@( posedge sys_clk or posedge rst ) if ( rst ) begin shift_reg <= 16'b11010011_10111100; shift_r <= 0; DDR_D1 <= 0; DDR_D2 <= 0; HL <= 0; DDR_VLD <= 0; DDR_VLD_R <= 0; end else begin shift_reg <= { shift_reg[ 14: 0 ], shift_reg[ 15 ] }; shift_r <= shift_reg[ 15 ]; HL <= ~HL; DDR_VLD <= 1; DDR_VLD_R <= DDR_VLD; if ( HL ) DDR_D1 <= #1 shift_r; else DDR_D2 <= #1 shift_r; end test_ODDR # ( .DDR_CLK_EDGE( "OPPOSITE_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" ) test_ODDR_inst ( .DDR_IN_CLK( DDR_IN_CLK ), // 1-bit clock input .CE( DDR_CE ), // 1-bit clock enable input .D1( DDR_D1 ), // 1-bit data input (positive edge) .D2( DDR_D2 ), // 1-bit data input (negative edge) .R( DDR_R ), // 1-bit reset .S( DDR_S ), // 1-bit set .DDR_OUT_CLK( DDR_OUT_CLK ), .Q( DDR_Q ) // 1-bit DDR output ); PLL1 PLL1_inst ( // Clock out ports .clk_out1 ( sys_clk ), .clk_out2 ( DDR_IN_CLK ), // Status and control signals .reset ( PLL_RST ), .locked ( locked ), // Clock in ports .clk_in1 ( inclk ) ); endmodule |
-
-
仿真程序解释如下:
-
(1)利用PLL生成了两个时钟分别为系统时钟sys_clk(200MHz)与DDR_IN_CLK(ODDR时钟输入时钟,100MHz),sys_clk与DDR_IN_CLK在相位上相差00。
(2)利用移位寄存器shift_reg在sys_clk的控制下, 将初始化的数据16’b11010011_10111100循环左移,并将最高位赋值给寄存器shift_r。
(3)生成HL控制信号,高(1)、低(0)交替,同步于sys_clk。
(4)根据HL选择将shift_r的值赋值给D1或D2。
(5)将PLL生成的DDR_IN_CLK 作为ODDR的输入时钟。
仿真结果如图6,

图6 ODDR OPPOSITE _EDGE 模式的仿真结果
例1实现了200Mbps的串行数据流,如何利用ODDR实现100M上下边沿实现数据发送,从波形上可以看出DDR_IN_CLK在上升沿采样D1,在下降沿采样D2,数据合并从上升沿开始。注意由于DDR是同步数据传输,因此端口需要数据端Q和时钟一起传送。
也可以修改DDR_IN_CLK与sys_clk在相位上差900关系,如图6。可以预测DDR_IN_CLK的相位滞后sys_clk 900,最终的仿真结果就与图4显示的结果基本一致。

图6,设置clk_out2相对于输入时钟偏移900

图7 DDR_IN_CLK偏移900的仿真结果
例2 将IDDR输入的数据按照先后顺序统一到数据上升沿上,并检测数据包,假设线路状态如下:
(1)IDLE 状态 ,输入数据D的上升沿和下降沿都为1,
(2)Preamble 10101010
(3)SFD 10101011
(4)DATA 8位数据0-255循环, 8’h00, 8’h01, 8’h02, 8’h03, 8’h04, 8’h05, 8’h06, 8’h07…。
根据以上提供的信息解出数据包,并给出数据包的包络线(RX _DV )。
-
-
解题思路如下:
-
(1)在例1的基础上改进的ODDR能按照例2的要求实现线路状态,以提供实验环境。
(2)在输入端添加PLL2,输入时钟为IDDR数据伴随时钟IDDR_IN_CLK,输出时钟为IDDR_CLK(100M,偏移900),IDDR_SYS_CLK(200M,偏移900)以提供IDDR 对应的2倍频率的时钟,如图8所示。

图8
对于IDDR双边沿数据合并成单边沿的快速数据流,应该采用OPPOSITE_EDGE模式,如图1所示。
IDDR的实体程序IDDR_decomposition.v如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
`timescale 1ns / 1ps module IDDR_decomposition ( input PLL_RST, input IDDR_IN_CLK, input IDDR_R, input IDDR_S, input IDDR_D, input IDDR_CE, output reg RX_DV, output reg RX_envelope, output reg[ 7: 0 ] pkt_data ); wire IDDR_Q1, IDDR_Q2; wire IDDR_CLK; wire IDDR_SYS_CLK; wire locked; wire rst = ~locked; reg q; reg HL; reg [ 7: 0 ] shift_reg; always@( posedge IDDR_SYS_CLK or posedge rst ) if ( rst ) begin q <= 0; HL <= 1; end else begin HL <= ~HL; if ( HL ) q <= IDDR_Q1; else q <= IDDR_Q2; shift_reg <= { shift_reg[ 6: 0 ], q }; end reg [ 3: 0 ] shift_count; reg [ 4: 0 ] data_count; reg [ 2: 0 ] detect_st; always@( posedge IDDR_SYS_CLK or posedge rst ) if ( rst ) begin detect_st <= 0; RX_DV <= 0; RX_envelope <= 0; shift_count <= 0; data_count <= 0; pkt_data <= 0; end else case ( detect_st ) 0: begin RX_DV <= 0; shift_count <= 0; data_count <= 0; RX_envelope <= 0; if ( shift_reg == 8'hAA ) //8'b1010_1010 detect_st <= 1; end 1: begin shift_count <= shift_count + 1; if ( shift_count == 7 ) begin shift_count <= 0; if ( shift_reg == 8'hAB ) //8'b1010_1011 ,SFD detect_st <= 2; else detect_st <= 0; end end 2: begin RX_envelope <= 1; shift_count <= shift_count + 1; RX_DV <= 0; if ( shift_count == 7 ) begin data_count <= data_count + 1; pkt_data <= shift_reg; shift_count <= 0; RX_DV <= 1; end if ( data_count == 8 ) begin detect_st <= 0; end end default: detect_st <= 0; endcase IDDR #( .DDR_CLK_EDGE( "OPPOSITE_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( IDDR_Q1 ), // 1-bit output for positive edge of clock .Q2( IDDR_Q2 ), // 1-bit output for negative edge of clock .C( IDDR_CLK ), // 1-bit clock input .CE( IDDR_CE ), // 1-bit clock enable input .D( IDDR_D ), // 1-bit DDR data input .R( IDDR_R ), // 1-bit reset .S( IDDR_S ) // 1-bit set ); PLL2 PLL2_inst ( // Clock in ports // Clock out ports .clk_out1 ( IDDR_CLK ), // 100MHz, offset 90 degree from IDDR_IN_CLK .clk_out2 ( IDDR_SYS_CLK ), // Status and control signals .reset ( PLL_RST ), .locked ( locked ), .clk_in1 ( IDDR_IN_CLK ) ); endmodule |
-
仿真激励程序sim_ODDR.v,
仿真激励程序将快速时钟产生的数据流,采用ODDR的方式输出,对外时钟降低1倍, ODDR与例1一样采用OPPOSITE_EDGE 模式。程序如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
`timescale 1ns / 1ps module sim_ODDR( ); reg inclk = 0; wire DDR_IN_CLK; wire DDR_OUT_CLK; always #2 inclk = ~inclk; reg DDR_R = 0, DDR_S = 0; reg DDR_CE = 1; reg DDR_D1 = 0; reg DDR_D2 = 0; reg shift_r = 0; reg [ 7: 0 ] shift_reg = 0; reg [ 3: 0 ] shift_count; reg [ 4: 0 ] data_count; reg [ 7: 0 ] send_data; reg HL; reg DDR_VLD; reg DDR_VLD_R; reg PLL_RST; wire locked; wire rst = !locked; //==============================receive signal wire RX_envelope; wire [ 7: 0 ] pkt_data; wire RX_DV; initial begin DDR_R = 0; DDR_S = 1; PLL_RST = 1'b1; #20 DDR_S = 0; PLL_RST = 1'b0; #5000 $stop; end reg[ 2: 0 ] pkt_st; always@( posedge sys_clk or posedge rst ) if ( rst ) begin shift_reg <= 16'b1111_1111; shift_r <= 0; shift_count <= 0; data_count <= 0; send_data <= 0; DDR_D1 <= 0; DDR_D2 <= 0; HL <= 0; DDR_VLD <= 0; DDR_VLD_R <= 0; pkt_st <= 0; end else begin HL <= ~HL; shift_r <= shift_reg[ 7 ]; DDR_VLD <= 1; DDR_VLD_R <= DDR_VLD; if ( HL ) DDR_D1 <= shift_r; else DDR_D2 <= shift_r; case ( pkt_st ) 0: begin //IDLE shift_reg <= { shift_reg[ 6: 0 ], shift_reg[ 0 ] }; shift_count <= shift_count + 1; if ( shift_count == 15 ) begin shift_reg <= 8'hAA; //1010_1010 shift_count <= 0; pkt_st <= 1; end end 1: begin //IDLE shift_reg <= { shift_reg[ 6: 0 ], shift_reg[ 0 ] }; shift_count <= shift_count + 1; if ( shift_count == 7 ) begin shift_reg <= 8'hAB; //1010_1011 shift_count <= 0; pkt_st <= 2; end end 2: begin shift_reg <= { shift_reg[ 6: 0 ], 1'b0 }; if ( shift_count == 7 ) begin shift_reg <= send_data; //first data,00 data_count <= data_count + 1; send_data <= send_data + 1; shift_count <= 0; pkt_st <= 3; end else shift_count <= shift_count + 1; end 3: begin shift_reg <= { shift_reg[ 6: 0 ], 1'b0 }; shift_count <= shift_count + 1; if ( shift_count == 7 ) begin shift_reg <= send_data; //second data,01 data_count <= data_count + 1; send_data <= send_data + 1; shift_count <= 0; end else shift_count <= shift_count + 1; if ( data_count == 9 ) begin //make sure 07 is completely shifted out pkt_st <= 0; data_count <= 0; shift_count <= 0; send_data <= send_data - 1; //since the last one no chance to be shifted out shift_reg <= 8'b1111_1111; end end default: pkt_st <= 0; endcase end test_ODDR #( .DDR_CLK_EDGE( "OPPOSITE_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" ) test_ODDR_inst ( .DDR_IN_CLK( DDR_IN_CLK ), // 1-bit clock input .CE( DDR_CE ), // 1-bit clock enable input .D1( DDR_D1 ), // 1-bit data input (positive edge) .D2( DDR_D2 ), // 1-bit data input (negative edge) .R( DDR_R ), // 1-bit reset .S( DDR_S ), // 1-bit set .DDR_OUT_CLK( DDR_OUT_CLK ), .Q( DDR_Q ) // 1-bit DDR output ); IDDR_decomposition IDDR_decomposition_inst ( .PLL_RST ( PLL_RST ), .IDDR_IN_CLK ( DDR_OUT_CLK ), .IDDR_R ( DDR_R ), .IDDR_S ( DDR_S ), .IDDR_D ( DDR_Q ), .IDDR_CE ( DDR_CE ), .RX_DV ( RX_DV ), .RX_envelope ( RX_envelope ), .pkt_data ( pkt_data ) ); PLL1 PLL1_inst ( // Clock out ports .clk_out1( sys_clk ), .clk_out2( DDR_IN_CLK ), // Status and control signals .reset( PLL_RST ), .locked( locked ), // Clock in ports .clk_in1( inclk ) ); endmodule |
仿真波形如图9所示,

图9-1 IDDR数据

图9-2 采用ODDR/IDDR实现高速数据流到DDR的转换
图9显示利用ODDR/IDDR可以将线路时钟频率降低一倍,并能正确将数据包还原,在例2中双方约定收发数据长度,如果利用8b/10b编码也可以利用K28.5和K28.1作为起始和结束进行包头、包尾的判断。
例2 工程代码下载 DDR_int erface
4.2 并行数据实现DDR串行化
例3,采用ODDR与IDDR实现8位并行数据串行化,在保持时钟频率不变的情况下,将输出端口降低一半。数据包的测试数据与例2相同。
解题思路如下:
(1)数据包的构成:
-
- IDLE 11111111…
- Preamble 8’b10101010
- SFD 8’b10101011
- data 0-255 循环
(2)系统时钟采用125Mhz, DDR的时钟同样为125Mhz,端口由8-bit降低为4-bit,实现1000Mbps 的收发。
(3)收、发双方采用握手信号
-
- 发送端 tx_en, txd[3:0], tx_clk(125M ), 将tx_data[7:0],按照低半字节(上升沿),高半字节(下降沿)的顺序发出。
- 接收端 RX_DV,RXD[3:0],RX_CLK, 将收到的RXD [3:0] ,拼成一个字节的方式输出。
(4)并行数据发送与接收,ODDR采用SAME_EDGE模式, IDDR采用SAME_EDGE_PIPELINED 模式。
(5)写出收、发程序并仿真。
-
利用ODDR实现4-bit双边沿发送数据包程序test_ODDR.v:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
`timescale 1ns / 1ps module test_ODDR # ( parameter DDR_CLK_EDGE = "SAME_EDGE", // "OPPOSITE_EDGE" or "SAME_EDGE" parameter INIT = 1'b0, // Initial value of Q: 1'b0 or 1'b1 parameter SRTYPE = "SYNC" // Set/Reset type: "SYNC" or "ASYNC" ) ( input rst, input tx_clk, //125M input [ 7: 0 ] tx_data, input tx_vld, output DDR_OUT_CLK, output reg tx_en, output [ 3: 0 ] txd ); reg [ 3: 0 ] ODDR_D1; reg [ 3: 0 ] ODDR_D2; reg DDR_S; always@( posedge tx_clk or posedge rst ) if ( rst ) begin ODDR_D1 <= 0; ODDR_D2 <= 0; DDR_S <= 1'b1; tx_en <= 1'b0; end else begin DDR_S <= 1'b1; tx_en <= 1'b0; if ( tx_vld ) begin DDR_S <= 1'b0; ODDR_D1 <= tx_data[ 3: 0 ]; ODDR_D2 <= tx_data[ 7: 4 ]; tx_en <= 1'b1; end end assign #2 DDR_OUT_CLK = tx_clk; //DDR_OUT_CLK 需要延时一个固定时间,才能对上升沿和下降沿的数据正确采样 ODDR #( .DDR_CLK_EDGE( DDR_CLK_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_inst1 ( .Q( txd[ 0 ] ), // 1-bit DDR output .C( tx_clk ), // 1-bit clock input .CE( 1'b1 ), // 1-bit clock enable input .D1( ODDR_D1[ 0 ] ), // 1-bit data input (positive edge) .D2( ODDR_D2[ 0 ] ), // 1-bit data input (negative edge) .R( 1'b0 ), // 1-bit reset .S( DDR_S ) // 1-bit set ); ODDR #( .DDR_CLK_EDGE( DDR_CLK_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_inst2 ( .Q( txd[ 1 ] ), // 1-bit DDR output .C( tx_clk ), // 1-bit clock input .CE( 1'b1 ), // 1-bit clock enable input .D1( ODDR_D1[ 1 ] ), // 1-bit data input (positive edge) .D2( ODDR_D2[ 1 ] ), // 1-bit data input (negative edge) .R( 1'b0 ), // 1-bit reset .S( DDR_S ) // 1-bit set ); ODDR #( .DDR_CLK_EDGE( DDR_CLK_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_inst3 ( .Q( txd[ 2 ] ), // 1-bit DDR output .C( tx_clk ), // 1-bit clock input .CE( 1'b1 ), // 1-bit clock enable input .D1( ODDR_D1[ 2 ] ), // 1-bit data input (positive edge) .D2( ODDR_D2[ 2 ] ), // 1-bit data input (negative edge) .R( 1'b0 ), // 1-bit reset .S( DDR_S ) // 1-bit set ); ODDR #( .DDR_CLK_EDGE( DDR_CLK_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_inst4 ( .Q( txd[ 3 ] ), // 1-bit DDR output .C( tx_clk ), // 1-bit clock input .CE( 1'b1 ), // 1-bit clock enable input .D1( ODDR_D1[ 3 ] ), // 1-bit data input (positive edge) .D2( ODDR_D2[ 3 ] ), // 1-bit data input (negative edge) .R( 1'b0 ), // 1-bit reset .S( DDR_S ) // 1-bit set ); endmodule |
上面的程序基本上按照ODDR的SAME_EDGE 模式配置,将高4个bit和低4个bit的数据赋值给ODDR_D1 与ODDR_D2。
注意时钟输出应延时一个固定值,在接收端才能正确采样,这段程序与RGMII接口MAC端的应用一致。如:
assign #2 DDR_OUT_CLK=tx_clk; //DDR_OUT_CLK 需要延时一个固定时间,才能对上升沿和下降沿的数据正确采样。
当然这个延时也可以在接收端设计,目前延时的方法有如下几种:
(1)仿真时可以采用延迟赋值语句的方式实现,这种延时只能用来评估时钟对齐的效果,也就是只能在仿真时有效,综合后的结果无效,因为综合时会忽略延迟部分。
(2)采用IO延迟,可以采用IDELAY或ODELAY实现,有关IDEAL或ODELAY部分的相关内容,参照IO DELAY部分的相关内容。
(3)采用PLL或MMCM实现时钟的相位偏移。
(4)有些器件已经带有2 ns延迟,如RTL8211E物理层网络芯片,因此在使用时应注意正确设定,详细资料见RTL8211E的datasheet。
-
发送端数据包生成程序
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
`timescale 1ns / 1ps module parallel_ODDR_data ( input rst, input tx_clk, output tx_en, output [ 3: 0 ] txd, output DDR_OUT_CLK ); reg [ 7: 0 ] tx_data; reg tx_vld; reg [ 4: 0 ] data_count; reg [ 2: 0 ] pkt_st; always@( posedge tx_clk or posedge rst ) if ( rst ) begin tx_data <= 8'b1111_1111; tx_vld <= 1'b0; data_count <= 0; pkt_st <= 0; end else begin case ( pkt_st ) 0: begin //IDLE tx_data <= 8'b1111_1111; tx_vld <= 1'b0; data_count <= data_count + 1; if ( data_count == 15 ) begin tx_vld <= 1'b1; tx_data <= 8'hAA; pkt_st <= 1; end end 1: begin //preamble and sfd tx_data <= 8'hAB; //1010_1011 pkt_st <= 2; end 2: begin tx_data <= 8'h00; pkt_st <= 3; end 3: begin data_count <= 0; tx_data <= tx_data + 1; if ( tx_data == 255 ) pkt_st <= 0; end default: pkt_st <= 0; endcase end test_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" ) test_ODDR_inst ( .rst ( rst ), .tx_clk ( tx_clk ), //125M .tx_data ( tx_data ), .tx_vld ( tx_vld ), .DDR_OUT_CLK( DDR_OUT_CLK ), .tx_en ( tx_en ), .txd ( txd ) ); endmodule |
- 接收端程序IDDR_decomposition.v
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
`timescale 1ns / 1ps module IDDR_decomposition ( input rst, input IDDR_IN_CLK, input [ 3: 0 ] IDDR_D, output reg RX_DV, output reg RX_envelope, output reg[ 7: 0 ] pkt_data ); wire [ 3: 0 ] IDDR_Q1, IDDR_Q2; reg [ 7: 0 ] rx_data; always@( posedge IDDR_IN_CLK or posedge rst ) if ( rst ) begin rx_data <= 0; end else begin rx_data <= { IDDR_Q2, IDDR_Q1 }; end reg [ 7: 0 ] data_count; reg [ 2: 0 ] detect_st; always@( posedge IDDR_IN_CLK or posedge rst ) if ( rst ) begin detect_st <= 0; RX_DV <= 0; RX_envelope <= 0; data_count <= 0; pkt_data <= 0; end else case ( detect_st ) 0: begin RX_DV <= 0; data_count <= 0; RX_envelope <= 0; if ( rx_data == 8'hAA ) detect_st <= 1; end 1: begin if ( rx_data == 8'hAB ) //8'b1010_1011 ,SFD detect_st <= 2; else detect_st <= 0; end 2: begin RX_envelope <= 1; data_count <= data_count + 1; pkt_data <= rx_data; RX_DV <= 1; if ( data_count == 255 ) begin detect_st <= 0; end end default: detect_st <= 0; endcase IDDR #( .DDR_CLK_EDGE( "SAME_EDGE_PIPELINED" ), // "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_inst1 ( .Q1( IDDR_Q1[ 0 ] ),// 1-bit output for positive edge of clock .Q2( IDDR_Q2[ 0 ] ),// 1-bit output for negative edge of clock .C( IDDR_IN_CLK ), // 1-bit clock input .CE( 1'b1 ), // 1-bit clock enable input .D( IDDR_D[ 0 ] ), // 1-bit DDR data input .R( 1'b0 ), // 1-bit reset .S( 1'b0 ) // 1-bit set ); IDDR #( .DDR_CLK_EDGE( "SAME_EDGE_PIPELINED" ), // "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_inst2 ( .Q1( IDDR_Q1[ 1 ] ),// 1-bit output for positive edge of clock .Q2( IDDR_Q2[ 1 ] ),// 1-bit output for negative edge of clock .C( IDDR_IN_CLK ), // 1-bit clock input .CE( 1'b1 ), // 1-bit clock enable input .D( IDDR_D[ 1 ] ), // 1-bit DDR data input .R( 1'b0 ), // 1-bit reset .S( 1'b0 ) // 1-bit set ); IDDR #( .DDR_CLK_EDGE( "SAME_EDGE_PIPELINED" ), // "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_inst3 ( .Q1( IDDR_Q1[ 2 ] ),// 1-bit output for positive edge of clock .Q2( IDDR_Q2[ 2 ] ),// 1-bit output for negative edge of clock .C( IDDR_IN_CLK ), // 1-bit clock input .CE( 1'b1 ), // 1-bit clock enable input .D( IDDR_D[ 2 ] ), // 1-bit DDR data input .R( 1'b0 ), // 1-bit reset .S( 1'b0 ) // 1-bit set ); IDDR #( .DDR_CLK_EDGE( "SAME_EDGE_PIPELINED" ), // "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_inst4 ( .Q1( IDDR_Q1[ 3 ] ),// 1-bit output for positive edge of clock .Q2( IDDR_Q2[ 3 ] ),// 1-bit output for negative edge of clock .C( IDDR_IN_CLK ), // 1-bit clock input .CE( 1'b1 ), // 1-bit clock enable input .D( IDDR_D[ 3 ] ), // 1-bit DDR data input .R( 1'b0 ), // 1-bit reset .S( 1'b0 ) // 1-bit set ); endmodule |
-
仿真程序
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
`timescale 1ns / 1ps module sim_ODDR( ); reg DDR_IN_CLK = 0; wire DDR_OUT_CLK; reg rst; always #4 DDR_IN_CLK = ~DDR_IN_CLK; wire [ 3: 0 ] txd; wire tx_en; //==============================receive signal wire RX_envelope; wire [ 7: 0 ] pkt_data; wire RX_DV; initial begin rst = 1'b1; #20 rst = 1'b0; #2000 $stop; end parallel_ODDR_data parallel_ODDR_data_inst ( .rst ( rst ), .tx_clk ( DDR_IN_CLK ), .tx_en ( tx_en ), .txd ( txd ), .DDR_OUT_CLK( DDR_OUT_CLK ) ); IDDR_decomposition IDDR_decomposition_inst ( .rst ( rst ), .IDDR_IN_CLK( DDR_OUT_CLK ), .IDDR_D ( txd ), .RX_DV ( RX_DV ), .RX_envelope( RX_envelope ), .pkt_data ( pkt_data ) ); endmodule |
-
仿真结果:
- 时钟没有延迟的仿真波形(assign DDR_OUT_CLK=tx_clk;),如图10所示。

图10
从图10中可以看出,在IDDR_IN_CLK上升沿的采样第一次(如黄线位置)采样的值为f,下降沿采样为a,因此后面的拼接都是错位的。所以整个数据包无法正确解调出来。
-
- 时钟带有延迟赋值语句的仿真波形(assign #2 DDR_OUT_CLK=tx_clk;),如图11所示。

图11
图11显示,时钟经过延迟之后,可以正确恢复原数据包。
-
- 在接收端利用PLL将DDR_IN_CLK延时900,如图12所示。

图12 PLL例化(输入时钟125M)

图13 输出时钟125M(延迟900)
图13中输出两个时钟,分别为125M并延迟900,另一个时钟为200M,在后续的IO delay延迟中会用到。修改IDDR_decomposition.v程序如下,
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
`timescale 1ns / 1ps module IDDR_decomposition ( input rst, input IDDR_IN_CLK, input [ 3: 0 ] IDDR_D, output reg RX_DV, output reg RX_envelope, output reg[ 7: 0 ] pkt_data ); wire IDDR_CLK; wire IDDR_REF_CLK; wire locked; wire sys_rst = ~locked; wire [ 3: 0 ] IDDR_Q1, IDDR_Q2; reg [ 7: 0 ] rx_data; always@( posedge IDDR_CLK or posedge sys_rst ) if ( sys_rst ) begin rx_data <= 0; end else begin rx_data <= { IDDR_Q2, IDDR_Q1 }; end reg [ 7: 0 ] data_count; reg [ 2: 0 ] detect_st; always@( posedge IDDR_CLK or posedge sys_rst ) //主时钟切换到IDDR_CLK, 是IDDR_IN_CLK延迟90度的时钟 if ( rst ) begin detect_st <= 0; RX_DV <= 0; RX_envelope <= 0; data_count <= 0; pkt_data <= 0; end else case ( detect_st ) 0: begin RX_DV <= 0; data_count <= 0; RX_envelope <= 0; if ( rx_data == 8'hAA ) //8'b1010_1010 detect_st <= 1; end 1: begin if ( rx_data == 8'hAB ) //8'b1010_1011 ,SFD detect_st <= 2; else detect_st <= 0; end 2: begin RX_envelope <= 1; data_count <= data_count + 1; pkt_data <= rx_data; RX_DV <= 1; if ( data_count == 255 ) begin detect_st <= 0; end end default: detect_st <= 0; endcase IDDR #( .DDR_CLK_EDGE( "SAME_EDGE_PIPELINED" ), // "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_inst1 ( .Q1( IDDR_Q1[ 0 ] ), // 1-bit output for positive edge of clock .Q2( IDDR_Q2[ 0 ] ), // 1-bit output for negative edge of clock .C( IDDR_CLK ), // 1-bit clock input 主时钟切换到IDDR_CLK, 是IDDR_IN_CLK延迟90度的时钟 .CE( 1'b1 ), // 1-bit clock enable input .D( IDDR_D[ 0 ] ), // 1-bit DDR data input .R( 1'b0 ), // 1-bit reset .S( 1'b0 ) // 1-bit set ); IDDR #( .DDR_CLK_EDGE( "SAME_EDGE_PIPELINED" ), // "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_inst2 ( .Q1( IDDR_Q1[ 1 ] ), // 1-bit output for positive edge of clock .Q2( IDDR_Q2[ 1 ] ), // 1-bit output for negative edge of clock .C( IDDR_CLK ), // 1-bit clock input 主时钟切换到IDDR_CLK, 是IDDR_IN_CLK延迟90度的时钟 .CE( 1'b1 ), // 1-bit clock enable input .D( IDDR_D[ 1 ] ), // 1-bit DDR data input .R( 1'b0 ), // 1-bit reset .S( 1'b0 ) // 1-bit set ); IDDR #( .DDR_CLK_EDGE( "SAME_EDGE_PIPELINED" ), // "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_inst3 ( .Q1( IDDR_Q1[ 2 ] ), // 1-bit output for positive edge of clock .Q2( IDDR_Q2[ 2 ] ), // 1-bit output for negative edge of clock .C( IDDR_CLK ), // 1-bit clock input 主时钟切换到IDDR_CLK, 是IDDR_IN_CLK延迟90度的时钟 .CE( 1'b1 ), // 1-bit clock enable input .D( IDDR_D[ 2 ] ), // 1-bit DDR data input .R( 1'b0 ), // 1-bit reset .S( 1'b0 ) // 1-bit set ); IDDR #( .DDR_CLK_EDGE( "SAME_EDGE_PIPELINED" ), // "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_inst4 ( .Q1( IDDR_Q1[ 3 ] ), // 1-bit output for positive edge of clock .Q2( IDDR_Q2[ 3 ] ), // 1-bit output for negative edge of clock .C( IDDR_CLK ), // 1-bit clock input 主时钟切换到IDDR_CLK, 是IDDR_IN_CLK延迟90度的时钟 .CE( 1'b1 ), // 1-bit clock enable input .D( IDDR_D[ 3 ] ), // 1-bit DDR data input .R( 1'b0 ), // 1-bit reset .S( 1'b0 ) // 1-bit set ); PLL2 PLL2_inst ( // Clock in ports // Clock out ports .clk_out1 ( IDDR_CLK ), // 125MHz, offset 90 degree from IDDR_IN_CLK .clk_out2 ( IDDR_REF_CLK ), // Status and control signals .reset ( rst ), .locked ( locked ), .clk_in1 ( IDDR_IN_CLK ) ); endmodule |
更新的test_ODDR.v如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
`timescale 1ns / 1ps module test_ODDR # ( parameter DDR_CLK_EDGE = "SAME_EDGE", // "OPPOSITE_EDGE" or "SAME_EDGE" parameter INIT = 1'b0, // Initial value of Q: 1'b0 or 1'b1 parameter SRTYPE = "SYNC" // Set/Reset type: "SYNC" or "ASYNC" ) ( input rst, input tx_clk, //125M input [ 7: 0 ] tx_data, input tx_vld, output DDR_OUT_CLK, output reg tx_en, output [ 3: 0 ] txd ); reg [ 3: 0 ] ODDR_D1; reg [ 3: 0 ] ODDR_D2; reg DDR_S; always@( posedge tx_clk or posedge rst ) if ( rst ) begin ODDR_D1 <= 0; ODDR_D2 <= 0; DDR_S <= 1'b1; tx_en <= 1'b0; end else begin DDR_S <= 1'b1; tx_en <= 1'b0; if ( tx_vld ) begin DDR_S <= 1'b0; ODDR_D1 <= tx_data[ 3: 0 ]; ODDR_D2 <= tx_data[ 7: 4 ]; tx_en <= 1'b1; end end assign DDR_OUT_CLK = tx_clk; //去掉#2, 即赋值没有延迟。 ODDR #( .DDR_CLK_EDGE( DDR_CLK_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_inst1 ( .Q( txd[ 0 ] ), // 1-bit DDR output .C( tx_clk ), // 1-bit clock input .CE( 1'b1 ), // 1-bit clock enable input .D1( ODDR_D1[ 0 ] ), // 1-bit data input (positive edge) .D2( ODDR_D2[ 0 ] ), // 1-bit data input (negative edge) .R( 1'b0 ), // 1-bit reset .S( DDR_S ) // 1-bit set ); ODDR #( .DDR_CLK_EDGE( DDR_CLK_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_inst2 ( .Q( txd[ 1 ] ), // 1-bit DDR output .C( tx_clk ), // 1-bit clock input .CE( 1'b1 ), // 1-bit clock enable input .D1( ODDR_D1[ 1 ] ), // 1-bit data input (positive edge) .D2( ODDR_D2[ 1 ] ), // 1-bit data input (negative edge) .R( 1'b0 ), // 1-bit reset .S( DDR_S ) // 1-bit set ); ODDR #( .DDR_CLK_EDGE( DDR_CLK_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_inst3 ( .Q( txd[ 2 ] ), // 1-bit DDR output .C( tx_clk ), // 1-bit clock input .CE( 1'b1 ), // 1-bit clock enable input .D1( ODDR_D1[ 2 ] ), // 1-bit data input (positive edge) .D2( ODDR_D2[ 2 ] ), // 1-bit data input (negative edge) .R( 1'b0 ), // 1-bit reset .S( DDR_S ) // 1-bit set ); ODDR #( .DDR_CLK_EDGE( DDR_CLK_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_inst4 ( .Q( txd[ 3 ] ), // 1-bit DDR output .C( tx_clk ), // 1-bit clock input .CE( 1'b1 ), // 1-bit clock enable input .D1( ODDR_D1[ 3 ] ), // 1-bit data input (positive edge) .D2( ODDR_D2[ 3 ] ), // 1-bit data input (negative edge) .R( 1'b0 ), // 1-bit reset .S( DDR_S ) // 1-bit set ); endmodule |
仿真波形如图14所示:

图14 IDDR_CLK
从图14可以看出,IDDR_CLK比IDDR_IN_CLK延迟900,利用IDDR_CLK可以正确恢复原数据包。缺点是当IDDR_IN_CLK的时钟频率频繁变换时应及时探测,并复位PLL2,保持PLL2及时锁定及频率跟踪。详细内容请参照网络开发中RXC测量部分。也可以利用tx_clk作为PLL的时钟输入,将输出时钟延迟900作为ODDR的输出时钟,方法与上面的方法几乎一致,感兴趣的朋友可以尝试。但需注意只能在发送端或接收端提供延迟,不能收发时钟两端都延迟。
-
- 在接收端利用IDELAY将时钟延迟一定的时间,一般2ns左右(2ns对于125M时钟为1/4的时钟周期,对于双边沿相当于延迟900)。
- 修改IDDR_decomposition.v程序如下,
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
`timescale 1ns / 1ps module IDDR_decomposition ( input rst, input IDDR_IN_CLK, input [ 3: 0 ] IDDR_D, output reg RX_DV, output reg RX_envelope, output reg[ 7: 0 ] pkt_data ); wire IDDR_CLK; wire IDDR_REF_CLK; wire locked; wire sys_rst = ~locked; wire delay_locked; wire sys_rst_delay = ~delay_locked; wire [ 3: 0 ] IDDR_Q1, IDDR_Q2; reg [ 7: 0 ] rx_data; always@( posedge IDDR_CLK or posedge sys_rst ) if ( sys_rst ) begin rx_data <= 0; end else begin rx_data <= { IDDR_Q2, IDDR_Q1 }; end reg [ 7: 0 ] data_count; reg [ 2: 0 ] detect_st; always@( posedge IDDR_CLK or posedge sys_rst_delay ) if ( rst ) begin detect_st <= 0; RX_DV <= 0; RX_envelope <= 0; data_count <= 0; pkt_data <= 0; end else case ( detect_st ) 0: begin RX_DV <= 0; data_count <= 0; RX_envelope <= 0; if ( rx_data == 8'hAA ) //8'b1010_1010 detect_st <= 1; end 1: begin if ( rx_data == 8'hAB ) //8'b1010_1011 ,SFD detect_st <= 2; else detect_st <= 0; end 2: begin RX_envelope <= 1; data_count <= data_count + 1; pkt_data <= rx_data; RX_DV <= 1; if ( data_count == 255 ) begin detect_st <= 0; end end default: detect_st <= 0; endcase IDDR #( .DDR_CLK_EDGE( "SAME_EDGE_PIPELINED" ), // "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_inst1 ( .Q1( IDDR_Q1[ 0 ] ), // 1-bit output for positive edge of clock .Q2( IDDR_Q2[ 0 ] ), // 1-bit output for negative edge of clock .C( IDDR_CLK ), // 1-bit clock input .CE( 1'b1 ), // 1-bit clock enable input .D( IDDR_D[ 0 ] ), // 1-bit DDR data input .R( 1'b0 ), // 1-bit reset .S( 1'b0 ) // 1-bit set ); IDDR #( .DDR_CLK_EDGE( "SAME_EDGE_PIPELINED" ), // "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_inst2 ( .Q1( IDDR_Q1[ 1 ] ), // 1-bit output for positive edge of clock .Q2( IDDR_Q2[ 1 ] ), // 1-bit output for negative edge of clock .C( IDDR_CLK ), // 1-bit clock input .CE( 1'b1 ), // 1-bit clock enable input .D( IDDR_D[ 1 ] ), // 1-bit DDR data input .R( 1'b0 ), // 1-bit reset .S( 1'b0 ) // 1-bit set ); IDDR #( .DDR_CLK_EDGE( "SAME_EDGE_PIPELINED" ), // "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_inst3 ( .Q1( IDDR_Q1[ 2 ] ), // 1-bit output for positive edge of clock .Q2( IDDR_Q2[ 2 ] ), // 1-bit output for negative edge of clock .C( IDDR_CLK ), // 1-bit clock input .CE( 1'b1 ), // 1-bit clock enable input .D( IDDR_D[ 2 ] ), // 1-bit DDR data input .R( 1'b0 ), // 1-bit reset .S( 1'b0 ) // 1-bit set ); IDDR #( .DDR_CLK_EDGE( "SAME_EDGE_PIPELINED" ), // "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_inst4 ( .Q1( IDDR_Q1[ 3 ] ), // 1-bit output for positive edge of clock .Q2( IDDR_Q2[ 3 ] ), // 1-bit output for negative edge of clock .C( IDDR_CLK ), // 1-bit clock input .CE( 1'b1 ), // 1-bit clock enable input .D( IDDR_D[ 3 ] ), // 1-bit DDR data input .R( 1'b0 ), // 1-bit reset .S( 1'b0 ) // 1-bit set ); PLL2 PLL2_inst ( // Clock in ports // Clock out ports .clk_out1 (), .clk_out2 ( IDDR_REF_CLK ), // Status and control signals .reset ( rst ), .locked ( locked ), .clk_in1 ( IDDR_IN_CLK ) ); localparam PHY_IODELAY_GROUP = "ETH_PHY_group"; ( * IODELAY_GROUP = PHY_IODELAY_GROUP * ) IDELAYE2 # ( .CINVCTRL_SEL ( "FALSE" ), // TRUE, FALSE .DELAY_SRC ( "IDATAIN" ), // IDATAIN, DATAIN .HIGH_PERFORMANCE_MODE ( "FALSE" ), // TRUE, FALSE .IDELAY_TYPE ( "FIXED" ), // FIXED, VARIABLE, or VAR_LOADABLE .IDELAY_VALUE ( 15 ), // 0 to 31 .REFCLK_FREQUENCY ( 200.0 ), .PIPE_SEL ( "FALSE" ), .SIGNAL_PATTERN ( "CLOCK" ) ) // CLOCK, DATA idelaye2_data_bus ( .DATAOUT ( IDDR_CLK ), .DATAIN ( 1'b0 ), // Data from FPGA logic .C ( 1'b0 ), .CE ( 1'b0 ), .INC ( 1'b0 ), .IDATAIN ( IDDR_IN_CLK ), // Driven by IOB .LD ( 1'b0 ), .REGRST ( 1'b0 ), .LDPIPEEN ( 1'b0 ), .CNTVALUEIN ( 5'b00000 ), .CNTVALUEOUT (), .CINVCTRL ( 1'b0 ) ); ( * IODELAY_GROUP = PHY_IODELAY_GROUP * ) IDELAYCTRL delayctrl ( .RDY ( delay_locked ), .REFCLK ( IDDR_REF_CLK ), .RST ( sys_rst ) ); endmodule |

图15 Artix-7 Idelay Tap计算
-
- 仿真波形如图16所示。

图16 利用端口IDELAY将IDDR的时钟延迟900
由于测试器件为ARTIX系列不支持ODELAY, 如果选用的器件为Kintex或更高档器件,也可以用ODELAY的方式实现。关于IDELAY及ODELAY的更多的细节将会在IOdelay及应用章节中讲解。