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原语
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
);
这个原语可以复制到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的原语如下,
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(C), // 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 );
-
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实体程序:
`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
仿真程序:
`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如下:
`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 模式。程序如下:
`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:
`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。
-
发送端数据包生成程序
`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
`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
-
仿真程序
`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程序如下,
`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如下:
`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程序如下,
`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及应用章节中讲解。
老师您好,本篇文章最后上传的工程项目代码与文章上方例2的工程代码是重复的,需要重新上传一下文章最后的工程代码,谢谢老师!
老师,我看到文章中有两次出现“在对时钟C进行约束时应将约束频率翻倍”,没理解这句话,时钟C已经将上下沿的数据都锁存了,锁存的数据拿去用就可以了,为啥要将时钟C的约束翻倍?
翻倍的原因是,始终采用上下边沿,要利用XDC文件告诉Vivado,此处仅给定始终频率是不够的,实际上建立时间仅是常规时钟的一半