Menu Close

Xilinx FPGA DDR 接口及Verilog实现

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及应用章节中讲解。

附件下载

Posted in FPGA, FPGA, FPGA 教材教案, FPGA硬件资源, IC, Verilog, 元器件, 教材与教案, 文章, 编程语言, 资料区

3 Comments

  1. 8班王飞飞

    老师您好,本篇文章最后上传的工程项目代码与文章上方例2的工程代码是重复的,需要重新上传一下文章最后的工程代码,谢谢老师!

  2. 12班卫劲风15216671210

    老师,我看到文章中有两次出现“在对时钟C进行约束时应将约束频率翻倍”,没理解这句话,时钟C已经将上下沿的数据都锁存了,锁存的数据拿去用就可以了,为啥要将时钟C的约束翻倍?

发表回复

相关链接