本帖最后由 FPGA课程 于 2024-9-7 14:40 编辑
软件版本:VIVADO2021.1
操作系统:WIN10 64bit
硬件平台:适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA
实验平台:米联客-MLK-H3-CZ08-7100开发板
板卡获取平台:https://milianke.tmall.com/
登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!
1概述高速串行通信优势非常巨大,只需要很少的IO引脚就可以实现高速通信,这也是当今FPGA高速接口的核心技术。比如XILINX的7代FPGA,GTX可以达到10.3125Gbps,ultrascale FPGA的GTH可以达到16Gbps。目前国产FPGA还难以达到这么高的接口速度。 高速串行通信经常需要用到XILINX FPGA内部专用的SERDESE模块来实现串并转换。LVDS配合SERDESE可以充分发挥FPGA的高速接口优势。SERDESE分输入和输出,输入采用ISERDESE,输出采用OSERDESE。OSERDESE的使用要比ISERDESE简单。不同的FPGA构架,SERDESE的结构有一些差异,在使用的时候需要注意,比如7代FPGA的SERDESE和ultrascale系列的SERDESE就有一些差异,7代的FPGA程序移植到ultrascale系需要做一些必要的修改。 米联客2022版本教程需要更加全方位介绍FPGA的基础知识,当然也包括了一些底层的接口技术。本文笔者主要根据官方技术手册,以及官方给出的demo例程,通过仿真手段,让读者更加全方位了解XILINX FPGA底层硬件的高速接口应用技术。 本文涉及到一些概念也会是读者第一次遇到,包括idelay延迟原语的使用,时钟管理原语MMCME2_ADV和PLLE2_ADV的使用,ISERDESE串并转换和OSERDESE,并串转换原语的使用。其中很关键一点时使用idelay延迟模块以及ISERDESE原语中BITSLIP功能,实现数据的正确采集。 本文需要用到的官方技术文档包括:对于7代FPGA包括ug471,xapp585。 本文首先以xapp585提供的demo介绍实现7:1的并串转换和1:7的串并转换。 2 IDELAYE介绍XILINX FPGA高速通信中经常会用到idelaye模块对信号做细微的时序调整。 2.1IDELAYCTRL- IDELAYCTRL IDELAYCTRL_inst (
- .RDY(RDY), // 1-bit output: Ready output
- .REFCLK(REFCLK), // 1-bit input: Reference clock input
- .RST(RST) // 1-bit input: Active high reset input
- );
复制代码
IDELAYCTRL用于管理组内所有IDELAY或ODELAY的资源一个IO BANK里面只有一个IDELAYCTRL。REFCLK是参考时钟输入,这个时钟决定了每个tap的精度,tap的分辨率计算公式如下: TIDELAYRESOLUTION=1/(32x 2 x FREF)) 这样对于200M参考时钟,1个tap分辨率为:1/200M*32*2=78ps。 2.2IDELAYE2
- IDELAYE2 #(
- .CINVCTRL_SEL("FALSE"), // Enable dynamic clock inversion (FALSE, TRUE)
- .DELAY_SRC("IDATAIN"), // Delay input (IDATAIN, DATAIN)
- .HIGH_PERFORMANCE_MODE("FALSE"), // Reduced jitter ("TRUE"), Reduced power ("FALSE")
- .IDELAY_TYPE("FIXED"), // FIXED, VARIABLE, VAR_LOAD, VAR_LOAD_PIPE
- .IDELAY_VALUE(0), // Input delay tap setting (0-31)
- .PIPE_SEL("FALSE"), // Select pipelined mode, FALSE, TRUE
- .REFCLK_FREQUENCY(200.0), // IDELAYCTRL clock input frequency in MHz (190.0-210.0, 290.0-310.0).
- .SIGNAL_PATTERN("DATA") // DATA, CLOCK input signal
- )
- IDELAYE2_inst (
- .CNTVALUEOUT(CNTVALUEOUT), // 5-bit output: Counter value output
- .DATAOUT(DATAOUT), // 1-bit output: Delayed data output
- .C(C), // 1-bit input: Clock input
- .CE(CE), // 1-bit input: Active high enable increment/decrement input
- .CINVCTRL(CINVCTRL), // 1-bit input: Dynamic clock inversion input
- .CNTVALUEIN(CNTVALUEIN), // 5-bit input: Counter value input
- .DATAIN(DATAIN), // 1-bit input: Internal delay data input
- .IDATAIN(IDATAIN), // 1-bit input: Data input from the I/O
- .INC(INC), // 1-bit input: Increment / Decrement tap delay input
- .LD(LD), // 1-bit input: Load IDELAY_VALUE input
- .LDPIPEEN(LDPIPEEN), // 1-bit input: Enable PIPELINE register to load data input
- .REGRST(REGRST) // 1-bit input: Active-high reset tap-delay input
- );
复制代码
参数描述 参数 | 值 | 默认 | 描述 | IDELAY_TYPE | String: FIXED, VARIABLE, VAR_LOAD或 VAR_LOAD_PIPE | FIXED | 定义延时类型 | DELAY_SRC | String: IDATAIN,DATAIN | IDATAIN | 选择延时源 | IDELAY_VALUE | Integer: 0 to 31 | 0 | 固定延时值和其他模式的初值 | HIGH_PERFORMANCE_MODE | Boolean: FALSE 或TRUE | TRUE | 为TRUE的时候减少输出jitter,否则减少功耗 | SIGNAL_PATTERN | String:DATA, CLOCK | DATA | 指定时序分析工具按时钟还是数据路径进行分析 | REFCLK_FREQUENCY | Real: 190 to 210 或290 to 310 | 200 | 设置静态时序分析的Tap值 | CINVCTRL_SEL | Boolean: FALSE or TRUE | FALSE | 设置是否动态转换C的极性 | PIPE_SEL | Boolean: FALSE or TRUE | FALSE | 选择是否使用Pipline模式 |
信号描述 端口 | 方向 | 位宽 | 描述 | C | I | 1 | 时钟输入,当使用VARIABLE, VAR_LOAD或 VAR_LOAD_PIPE模式下的时钟输入,必须是由BUFG或BUFR驱动,当使用SelectIO资源时,必须和它的时钟源相同。 | REGRST | I | 1 | 复位pipeline寄存器,仅在VAR_LOAD_PIPE模式下有效 | LD | I | 1 | VARIABLE模式:加载预编程值; VAR_LOAD模式:加载CNTVALUEIN值; VAR_LOAD_PIPE模式:加载当前pipeline寄存器的值。 | INC | I | 1 | INC/DEC增加,减少的Tap数 | CE | I | 1 | 使能INC和DEC功能,只在VARIABLE, VAR_LOAD或 VAR_LOAD_PIPE模式下有效;当CE保持为高的时候,每一个C周期都增加或减少一个TIDELAYRESOLUTION的时延。 | CINVCTRL | I | 1 | 动态翻转C的极性,当使用该功能时,需禁止IDELAY control引脚2个cycle。 | CNTVALUEIN | I | 5 | 来自FPGA逻辑的动态Tap值 | IDATAIN | I | 1 | 来自IBUF的数据输入,输出可驱动FPGA逻辑、ILOGICE和ISERDESE | DATAIN | I | 1 | 来自FPGA逻辑的数据输入,输出不可驱动IOB | LDPIPEEN | I | 1 | 使能pipeline寄存器加载数据 | DATAOUT | O | 1 | 延时输出的数据 | CNTVALUEOUT | O | 5 | Tap值输出,用于FPGA监测 | 2.3 IDELAY Timing DiagramIDELAY_TYPE = VARIABLE, IDELAY_VALUE = 0, and DELAY_SRC = IDATAIN
Clock Event 0: 在LD有效前,CNTVALUEOUT输出为未知值; Clock Event 1: 在C的上升沿,检测到复位(LD为高电平),加载预装值IDELAY_VALUE,数据在抽头(抽头为了形象描述数据的位的延迟位置)tap0上。 Clock Event 2: CE和INC上的脉冲在C的上升沿被捕获。这表示递增操作。输出增加1个tap延迟输出,这个时候抽头在tap1上。 Clock Event 3: CE和INC不再有效, 输出会保持在tap1上,直到LD,CE或INC引脚上有进一步的动作。也就是后面的数据都会由1个tap的延迟吗,如果是200M时钟就是78ps。
IDELAY timing diagram in VAR_LOAD mode
Clock Event 0: 在LD有效前,CNTVALUEOUT输出为未知值; Clock Event 1: 在C的上升沿采样到LD有效,此时DATAOUT延时CNTVALUEIN指定的延时Taps,改变tap Setting到Tap2,CNTVALUEOUT更新到新的Tap值; Clock Event 2: INC和CE有效,此时指定了增量操作,Tap值加1,DATAOUT输出从Tap2更新到Tap3,CNTVALUEOUT更新到新的Tap值; Clock Event 3: LD有效,DATAOUT输出延时更新到Tap10,CNTVALUEOUT更新到新的Tap值。 3 SERDESE原语介绍3.1 7系列FPGA的OSERDESE
- OSERDESE2 #(
- .DATA_RATE_OQ("DDR"), // DDR, SDR
- .DATA_RATE_TQ("DDR"), // DDR, BUF, SDR
- .DATA_WIDTH(4), // Parallel data width (2-8,10,14)
- .INIT_OQ(1'b0), // Initial value of OQ output (1'b0,1'b1)
- .INIT_TQ(1'b0), // Initial value of TQ output (1'b0,1'b1)
- .SERDES_MODE("MASTER"), // MASTER, SLAVE
- .SRVAL_OQ(1'b0), // OQ output value when SR is used (1'b0,1'b1)
- .SRVAL_TQ(1'b0), // TQ output value when SR is used (1'b0,1'b1)
- .TBYTE_CTL("FALSE"), // Enable tristate byte operation (FALSE, TRUE)
- .TBYTE_SRC("FALSE"), // Tristate byte source (FALSE, TRUE)
- .TRISTATE_WIDTH(4) // 3-state converter width (1,4)
- )
- OSERDESE2_inst (
- .OFB(OFB), // 1-bit output: Feedback path for data
- .OQ(OQ), // 1-bit output: Data path output
- // SHIFTOUT1 / SHIFTOUT2: 1-bit (each) output: Data output expansion (1-bit each)
- .SHIFTOUT1(SHIFTOUT1),
- .SHIFTOUT2(SHIFTOUT2),
- .TBYTEOUT(TBYTEOUT), // 1-bit output: Byte group tristate
- .TFB(TFB), // 1-bit output: 3-state control
- .TQ(TQ), // 1-bit output: 3-state control
- .CLK(CLK), // 1-bit input: High speed clock
- .CLKDIV(CLKDIV), // 1-bit input: Divided clock
- // D1 - D8: 1-bit (each) input: Parallel data inputs (1-bit each)
- .D1(D1),
- .D2(D2),
- .D3(D3),
- .D4(D4),
- .D5(D5),
- .D6(D6),
- .D7(D7),
- .D8(D8),
- .OCE(OCE), // 1-bit input: Output data clock enable
- .RST(RST), // 1-bit input: Reset
- // SHIFTIN1 / SHIFTIN2: 1-bit (each) input: Data input expansion (1-bit each)
- .SHIFTIN1(SHIFTIN1),
- .SHIFTIN2(SHIFTIN2),
- // T1 - T4: 1-bit (each) input: Parallel 3-state inputs
- .T1(T1),
- .T2(T2),
- .T3(T3),
- .T4(T4),
- .TBYTEIN(TBYTEIN), // 1-bit input: Byte group tristate
- .TCE(TCE) // 1-bit input: 3-state clock enable
- );
复制代码
参数功能: Attribute | Description | Value | Default Value | DATA_RATE_OQ | 数据传输是DDR或SDR方式 | String: SDR or DDR | DDR | DATA_RATE_TQ | 三态是DDR或SDR或BUF方式 | String: BUF, SDR, or DDR | DDR | DATA_WIDTH | 定义并行端口的位宽,这个值和DATA_RATE_OQ相关 | Integer: 2, 3, 4, 5, 6, 7, 8, 10, or 14 In SDR mode, 2, 3, 4, 5, 6, 7, and 8 are valid. In DDR mode, 2, 4, 6, 8, 10, and 14 are valid. |
| SERDES_MODE | OSERDESE2是master还是slave | String: MASTER or SLAVE | MASTER | TRISTATE_WIDTH | 定义并串转换,三态转换器的位宽 | Integer: 1 or 4 | 4 | TBYTE_CTL | 仅通过MIG(ddr 控制器)使用 | FALSE, TRUE | FALSE | TBYTE_SRC | 仅通过MIG(ddr 控制器)使用 | FALSE, TRUE | FALSE |
信号功能: Port Name | Type | Width | Description | OQ | Output | 1 | 串行数据输出 | OFB | Output | 1 | 数据路径输出反馈到ISERDESE2或连接到ODELAYE2 | TQ | Output | 1 | 三态控制输出到IOB | TFB | Output | 1 | 三态控制输出,说明OSERDESE2是三态 | SHIFTOUT1 | Output | 1 | 进位输出以扩展数据宽度。连接到主OSERDESE2的SHIFTIN1 | SHIFTOUT2 | Output | 1 | 进行输出以扩展数据宽度。连接到主OSERDESE2的SHIFTIN2 | CLK | Input | 1 | 高速时钟输入 | CLKDIV | Input | 1 | CLK时钟的分频时钟 | D1 to D8 | Input | 1 | 并行数据输入 | TCE | Input | 1 | 三态时钟使能 | OCE | Input | 1 | 输出数据时钟使能 | TBYTEIN | Input | 1 | 字节组三态输入 | TBYTEOUT | Output | 1 | 字节组三态输出 | RST | Input | 1 | 高电平复位 | SHIFTIN1 | Input | 1 | 进位输入以扩展数据宽度。连接到从属OSERDESE2的SHIFTOUT1 | SHIFTIN2 | Input | 1 | 进位输入以扩展数据宽度。连接到从属OSERDESE2的SHIFTOUT2 | T1 to T4 | Output | 1 | 并联三态输入 |
1、OSERDESE2级联扩展位宽 下图通过级联2个OSERDESE2实现10:1的并串转换
2、Timing Characteristics of 2:1 SDR Serialization
Clock Event 1 OSERDESE2的D1 D2端口数据为未知 Clock Event 2 数据A B 到达OSERDESE2的D1 D2端口 Clock Event 3 在AB被采样后的一个CLK周期,数据位A出现在OQ,再下一个周期,输出数据位B 3、Timing Characteristics of 8:1 DDR Serialization
Clock Event 1 OSERDESE2的D1 D2端口数据为未知 Clock Event 2 数据A B 到达OSERDESE2的输入端口,并被采样 Clock Event 3 输入从OQ依次输出ABCDEFGH Clock Event 4 下一组输出开始 4、Timing Characteristics of 4:1 DDR 3-State Controller Serialization
Clock Event 1 T1,T2和T4被驱动为低电平以释放三态条件。 OSERDESE2中T1-T4和D1-D4的串行化路径相同(包括等待时间),因此在时钟事件1期间,EFGH位始终与T1-T4引脚上显示的0010对齐。 Clock Event 2 在将EFGH采样到OSERDESE2之后的一个CLK周期的OQ处依次出现数据位E,F,X,H 3.2 7系列FPGA的ISERDESE
- ISERDESE2 #(
- .DATA_RATE("DDR"), // DDR, SDR
- .DATA_WIDTH(4), // Parallel data width (2-8,10,14)
- .DYN_CLKDIV_INV_EN("FALSE"), // Enable DYNCLKDIVINVSEL inversion (FALSE, TRUE)
- .DYN_CLK_INV_EN("FALSE"), // Enable DYNCLKINVSEL inversion (FALSE, TRUE)
- // INIT_Q1 - INIT_Q4: Initial value on the Q outputs (0/1)
- .INIT_Q1(1'b0),
- .INIT_Q2(1'b0),
- .INIT_Q3(1'b0),
- .INIT_Q4(1'b0),
- .INTERFACE_TYPE("MEMORY"), // MEMORY, MEMORY_DDR3, MEMORY_QDR, NETWORKING, OVERSAMPLE
- .IOBDELAY("NONE"), // NONE, BOTH, IBUF, IFD
- .NUM_CE(2), // Number of clock enables (1,2)
- .OFB_USED("FALSE"), // Select OFB path (FALSE, TRUE)
- .SERDES_MODE("MASTER"), // MASTER, SLAVE
- // SRVAL_Q1 - SRVAL_Q4: Q output values when SR is used (0/1)
- .SRVAL_Q1(1'b0),
- .SRVAL_Q2(1'b0),
- .SRVAL_Q3(1'b0),
- .SRVAL_Q4(1'b0)
- )
- ISERDESE2_inst (
- .O(O), // 1-bit output: Combinatorial output
- // Q1 - Q8: 1-bit (each) output: Registered data outputs
- .Q1(Q1),
- .Q2(Q2),
- .Q3(Q3),
- .Q4(Q4),
- .Q5(Q5),
- .Q6(Q6),
- .Q7(Q7),
- .Q8(Q8),
- // SHIFTOUT1, SHIFTOUT2: 1-bit (each) output: Data width expansion output ports
- .SHIFTOUT1(SHIFTOUT1),
- .SHIFTOUT2(SHIFTOUT2),
- .BITSLIP(BITSLIP), // 1-bit input: The BITSLIP pin performs a Bitslip operation synchronous to
- // CLKDIV when asserted (active High). Subsequently, the data seen on the Q1
- // to Q8 output ports will shift, as in a barrel-shifter operation, one
- // position every time Bitslip is invoked (DDR operation is different from
- // SDR).
- // CE1, CE2: 1-bit (each) input: Data register clock enable inputs
- .CE1(CE1),
- .CE2(CE2),
- .CLKDIVP(CLKDIVP), // 1-bit input: TBD
- // Clocks: 1-bit (each) input: ISERDESE2 clock input ports
- .CLK(CLK), // 1-bit input: High-speed clock
- .CLKB(CLKB), // 1-bit input: High-speed secondary clock
- .CLKDIV(CLKDIV), // 1-bit input: Divided clock
- .OCLK(OCLK), // 1-bit input: High speed output clock used when INTERFACE_TYPE="MEMORY"
- // Dynamic Clock Inversions: 1-bit (each) input: Dynamic clock inversion pins to switch clock polarity
- .DYNCLKDIVSEL(DYNCLKDIVSEL), // 1-bit input: Dynamic CLKDIV inversion
- .DYNCLKSEL(DYNCLKSEL), // 1-bit input: Dynamic CLK/CLKB inversion
- // Input Data: 1-bit (each) input: ISERDESE2 data input ports
- .D(D), // 1-bit input: Data input
- .DDLY(DDLY), // 1-bit input: Serial data from IDELAYE2
- .OFB(OFB), // 1-bit input: Data feedback from OSERDESE2
- .OCLKB(OCLKB), // 1-bit input: High speed negative edge output clock
- .RST(RST), // 1-bit input: Active high asynchronous reset
- // SHIFTIN1, SHIFTIN2: 1-bit (each) input: Data width expansion input ports
- .SHIFTIN1(SHIFTIN1),
- .SHIFTIN2(SHIFTIN2)
- );
- // End of ISERDESE2_inst instantiation
复制代码
参数功能: Attribute | Description | Value | Default Value | DATA_RATE | 数据传输是DDR或SDR方式 | String: SDR or DDR | DDR | DATA_WIDTH | 定义串并转换并口的位宽 | Integer: 2, 3, 4, 5, 6, 7, 8, 10 or 14. If DATA_RATE = DDR, value is limited to 4, 6, 8, 10, or 14. If DATA_RATE = SDR, value is limited to 2, 3, 4, 5, 6, 7, or 8. | 4 | DYN_CLKDIV_INV_EN | CLKDIV时钟极性动态设置使能,仅在MEMORY_QDR 和MEMORY_DDR3模式下使用,本文用不到。 | Boolean: TRUE or FALSE | FALSE | DYN_CLK_INV_EN | CLK时钟极性动态设置使能,仅在MEMORY_QDR 和MEMORY_DDR3模式下使用,本文用不到。 | Boolean: TRUE or FALSE | MEMORY | INTERFACE_TYPE | 接口类型是memory 或者 networking | String: MEMORY,MEMORY_DDR3,MEMORY_QDR,OVERSAMPLE, orNETWORKING | 4 | NUM_CE | 使能需要用到的时钟数量(CE1和CE2) | Integer: 1 or 2 | 2 | OFB_USED | 启用从OLOGICE2 / 3,OSERDESE2 OFB引脚到ISERDESE2 OFB引脚的路径。禁止使用D输入引脚。 | Boolean: TRUE or FALSE | FALSE | SERDES_MODE | 扩展模式下ISERDESE2是主模式还是从模式 | String: MASTER or SLAVE | MASTER | INIT_Q1 | 设置第一个采样寄存器的初始值 | Binary: 0 or 1 | 0 | INIT_Q2 | 设置第二个采样寄存器的初始值 | Binary: 0 or 1 | 0 | INIT_Q3 | 设置第三个采样寄存器的初始值 | Binary: 0 or 1 | 0 | INIT_Q4 | 设置第四个采样寄存器的初始值 | Binary: 0 or 1 | 0 | SRVAL_Q1 | 设置第一个采样寄存器复位后的值 | Binary: 0 or 1 | 1 | SRVAL_Q2 | 设置第二个采样寄存器复位后的值 | Binary: 0 or 1 | 1 | SRVAL_Q3 | 设置第三个采样寄存器复位后的值 | Binary: 0 or 1 | 1 | SRVAL_Q4 | 设置第四个采样寄存器复位后的值 | Binary: 0 or 1 | 1 | IOBDELAY | 设置延迟输入是否寄存输出 | NONE, IBUF, IFD, or BOTH | NONE |
信号功能: Port Name | Type | Width | Description | Q1 – Q8 | Output | 1 (each) | 串转并后的并行输出 | O | Output | 1 | 输出的是没有寄存过的输入D或者DDLY | SHIFTOUT1 | Output | 1 | 进位级联,当多个ISERDESE级联,接到下一个ISERDESE的SHIFTIN1 | SHIFTOUT2 | Output | 1 | 进位级联,当多个ISERDESE级联,接到下一个ISERDESE的SHIFTIN2 | D | Input | 1 | 来自IOB串行数据输入 | DDLY | Input | 1 | 来自IDELAY2延迟输入后的数据输入 | CLK | Input | 1 | 高速时钟输入 | CLKB | Input | 1 | 第二个高速时钟输入,仅用于MEMORY_QDR模式。除非处于MEMORY_QDR模式,否则始终连接到~CLK。 | CE1, CE2 | Input | 1 | 时钟输入使能 | RST | Input | 1 | 高电平复位 | CLKDIV | Input | 1 | CLK时钟的分频时钟 | CLKDIVP | Input | 1 | 仅通过MIG工具支持。由PHASER_IN在MEMORY_DDR3模式下划分的CLK来源。 所有其他模式都接地。 | OCLK | Input | 1 | 用于存储器应用的高速时钟输入 | BITSLIP | Input | 1 | 调用Bitslip操作, | SHIFTIN1 | Output | 1 | 进位级联,当多个ISERDESE级联,接到上一个ISERDESE的SHIFTOUT1 | SHIFTIN2 | Output | 1 | 进位级联,当多个ISERDESE级联,接到上一个ISERDESE的SHIFTOUT2 | OFB | Output | 1 | 来自OLOGICE2或OLOGICE3和OSERDESE2输出的反馈路径 | DYNCLKDIVSEL | Input | 1 | 动态选择CLKDIV反转 | DYNCLKSEL | Input | 1 | 动态选择CLK和CLKB反转 |
ISERDESE2使用起来相对复杂一些,接下来我们对一些必要的概念继续介绍。 1、ISERDESE2数据转换位宽的支持 ISERDESE2 在SDR模式下数据转换的位宽可以为2、3、4、5、6、7、8bit,在DDR模式时,数据转换位宽为4、6、8bit, 2个ISERDESE2级联使用,DDR模式可以支持10、14bit。如下图所示通过2个ISERDESE2级联出10bit,14bit位宽数据接口。
2、串并转换后的数据顺序
如上图所示,可以看到数据的大小端是反了过来。 3、数据对齐 Bitslip用来实现并行数据的边界对齐。串行输入的8bit的数据,经过串并转换后,数据之间可能会错位,这是串并转换无法识别的,因此Bitslip就专门用来找到用户需要的并行数据边界。 对于SDR模式,Bitslip使能1次,则数据会左移1次,对于8bit并行数据,移动8次完成一个循环,可以这样无止境的循环,直到找到用户定义的并行数据。对于DDR模式,Bitslip工作方式不同,Bitslip使能1次,数据会右移1次或者左移3次,两者交替进行,同样移动8次完成一个循环。
Clock Event 1 第一个字CDAB已被采样到ISERDESE2的输入侧寄存器中。Bitslip引脚未置位,数据没有任何重新对齐。 Clock Event 2 Bitslip引脚被置为有效,这使Bitslip控制器在内部将所有数据位向右移一位。 Bitslip在一个(仅一个)CLKDIV周期内保持高电平。 Clock Event 3 置位Bitslip后的三个CLKDIV周期,Bitslip操作完成,新的移位数据作为BCDA在输出上可用。 Clock Event 4 于ISERDESE2配置为1:4,因此Bitslip最多可以再声明两次。 在第二次移位后(此DDR还剩三个位置),在Q4-Q1上提供了(必需)输出ABCD。 在第三次移位后(右移一个位置),输出DABC在Q4-Q1上可用。 第四次移位(剩下三个位置)之后,原始输出CDAB在Q4-Q1上可用,并且Bitslip已完成所有四个输入组合的循环。 4、时钟
1)、网络接口类型时钟要求(NETWORKING Interface Type) CLK 和CLKDIV必须确保相位相同。 CLK由BUFIO驱动,CLKDIV由BUFR驱动 CLK由MMCM或PLL驱动,CLKDIV由同一MMCM或PLL的CLKOUT [0:6]驱动 当使用MMCM驱动ISERDESE2的CLK和CLKDIV时,不能混用提供ISERDESE2的缓冲器类型。例如,如果CLK由BUFG驱动,则CLKDIV也必须由BUFG驱动。另外,MMCM可以通过BUFIO和BUFR驱动ISERDESE2。 2、内存接口类型时钟要求(MEMORY Interface Type) CLK由BUFIO驱动,OCLK由BUFIO驱动并且CLKDIV由BUFR驱动 CLK由MMCM或者PLL驱动,OCLK由MMCM或者PLL驱动,CLKDIV由相同的MMCM或者PLL驱动 CLK由BUFG驱动,OCLK由BUFG驱动,CLKDIV由不同的BUFG驱动 以上是笔者整理的关于ISERDESE2的资料,更多详细的资料可以阅读ug471 4 SERDESE的应用对于用过米联客板卡的用户不知不觉中已经用上了SERDESE了,比如HDMI输出的IP就是用到了OSERDESE,实现了10:1的并串转换。 Xilinx 7系列FPGA包含ISERDES和OSERDES原语,串并设计非常简单,并且在使用逐位偏移校正时,可以根据芯片的系列和等级在415 Mbps至1200 Mbps的速度下进行更高的操作而较低的操作速度使用静态数据对齐。 本文主要以官方的xapp585介绍SERDESE的应用。一般官方给出的demo功能都会比较复杂,但是各方面需要演示的功能比较全面。 xapp585介绍了如何结合使用混合模式时钟管理器(MMCM)或锁相环(PLL)来有效地使用ISERDES和OSERDES,以使用低压差分信号(LVDS)数据接收和传输7:1数据,使用逐位偏移校正时,传输速率为415 Mbps至1200 Mb / s,具体取决于所使用的系列和速度等级。笔者把使用到了ug471,ug472,xapp585以及源码放到uisrc/06_doc路径下,方便读者查阅。其中xapp585中verilog源码放到uisrc/01_rtl中。
xapp585中提供的demo实现了7:1并串转换和1:7串并转换。对于SDR模式可以直接通过1个OSERDES实现并串转换,1个ISERDES实现串并转换。对于DDR模式,可以使用2个SERDES扩展到14个位宽实现。具体的下面我们以xapp585给出的demo分析。 4.1XAPP585源码概述首先看下SDR模式的源码,可以看到RX端的串并转换有比TX端的并串转换要负责
以下是DDR模式下的源码
在xapp585中,RX接收端需要实现6条line的串并转换,读者看到下面的图是不是有些疑惑?下图只有5条line多出的一条line是什么呢?这时候我们有必要看下代码,编写代码的人,必须有代码分析的能力,我们学习编程,应当以阅读代码为主,查阅资料为辅。
下面代码来自xapp585中SDR接口RX接收部分,结合上图,可以看出demo中的代码有2个通路,每个通路数据通道是5条Line还有一条line是时钟。
DDR接口类似xapp585demo演示的是SDR和DDR实现相同的串并转换方案
4.2并串转换PLL时钟ISERDES和OSERDES都支持SDR和DDR模式,SDR消耗的资源少,但是受限于FPGA的最高运行时钟,如果超过了FPGA的最大运行时钟必须考虑用DDR模式。下表是7系列FPGA的时钟参数情况。
以OSERDES中clock_generator_pll_7_to_1_diff_sdr.v为例。文件中MCME2_ADV和PLLE2_ADV原语是实现XILINX FPGA时钟管理的IP 原。Xapp585支持2种方式。本例中实际应用了PLLE2_ADV。 - IBUFGDS #(
- .DIFF_TERM (DIFF_TERM))
- clk_iob_in (
- .I (clkin_p),
- .IB (clkin_n),
- .O (clkint));
- generate
- if (USE_PLL == "FALSE") begin : loop8 // use an MMCM
- assign status[6] = 1'b1 ;
- assign status[3:2] = 2'b00 ;
-
- MMCME2_ADV #(
- .BANDWIDTH ("OPTIMIZED"),
- .CLKFBOUT_MULT_F (7*MMCM_MODE),
- .CLKFBOUT_PHASE (0.0),
- .CLKIN1_PERIOD (CLKIN_PERIOD),
- .CLKIN2_PERIOD (CLKIN_PERIOD),
- .CLKOUT0_DIVIDE_F (MMCM_MODE),
- .CLKOUT0_DUTY_CYCLE (0.5),
- .CLKOUT0_PHASE (0.0),
- .CLKOUT1_DIVIDE (8),
- .CLKOUT1_DUTY_CYCLE (0.5),
- .CLKOUT1_PHASE (0.0),
- .CLKOUT2_DIVIDE (7*MMCM_MODE),
- .CLKOUT2_DUTY_CYCLE (0.5),
- .CLKOUT2_PHASE (0.0),
- .CLKOUT3_DIVIDE (8),
- .CLKOUT3_DUTY_CYCLE (0.5),
- .CLKOUT3_PHASE (0.0),
- .CLKOUT4_DIVIDE (8),
- .CLKOUT4_DUTY_CYCLE (0.5),
- .CLKOUT4_PHASE (0.0),
- .CLKOUT5_DIVIDE (8),
- .CLKOUT5_DUTY_CYCLE (0.5),
- .CLKOUT5_PHASE (0.0),
- .COMPENSATION ("ZHOLD"),
- .DIVCLK_DIVIDE (1),
- .REF_JITTER1 (0.100))
- tx_mmcme2_adv_inst (
- .CLKFBOUT (txpllmmcm_x1),
- .CLKFBOUTB (),
- .CLKFBSTOPPED (),
- .CLKINSTOPPED (),
- .CLKOUT0 (txpllmmcm_xn),
- .CLKOUT0B (),
- .CLKOUT1 (),
- .CLKOUT1B (),
- .CLKOUT2 (),
- .CLKOUT2B (),
- .CLKOUT3 (),
- .CLKOUT3B (),
- .CLKOUT4 (),
- .CLKOUT5 (),
- .CLKOUT6 (),
- .DO (),
- .DRDY (),
- .PSDONE (),
- .PSCLK (1'b0),
- .PSEN (1'b0),
- .PSINCDEC (1'b0),
- .PWRDWN (1'b0),
- .LOCKED (mmcm_lckd),
- .CLKFBIN (pixel_clk),
- .CLKIN1 (clkint),
- .CLKIN2 (1'b0),
- .CLKINSEL (1'b1),
- .DADDR (7'h00),
- .DCLK (1'b0),
- .DEN (1'b0),
- .DI (16'h0000),
- .DWE (1'b0),
- .RST (reset)) ;
- if (PIXEL_CLOCK == "BUF_G") begin // Final clock selection
- BUFG bufg_mmcm_x1 (.I(txpllmmcm_x1), .O(pixel_clk)) ;
- assign status[1:0] = 2'b00 ;
- end
- else if (PIXEL_CLOCK == "BUF_R") begin
- BUFR #(.BUFR_DIVIDE("1"),.SIM_DEVICE("7SERIES"))bufr_mmcm_x1 (.I(txpllmmcm_x1),.CE(1'b1),.O(pixel_clk),.CLR(1'b0)) ;
- assign status[1:0] = 2'b01 ;
- end
- else begin
- BUFH bufh_mmcm_x1 (.I(txpllmmcm_x1), .O(pixel_clk)) ;
- assign status[1:0] = 2'b10 ;
- end
-
- if (TX_CLOCK == "BUF_G") begin // Sample clock selection
- BUFG bufg_mmcm_xn (.I(txpllmmcm_xn), .O(txclk)) ;
- assign status[5:4] = 2'b00 ;
- end
- else if (TX_CLOCK == "BUFIO") begin
- BUFIO bufio_mmcm_xn (.I (txpllmmcm_xn), .O(txclk)) ;
- assign status[5:4] = 2'b11 ;
- end
- else begin
- BUFH bufh_mmcm_xn (.I(txpllmmcm_xn), .O(txclk)) ;
- assign status[5:4] = 2'b10 ;
- end
-
- end
- else begin // Use a PLL
- assign status[6] = 1'b0 ;
- assign status[3:2] = 2'b00 ;
- PLLE2_ADV #(
- .BANDWIDTH ("OPTIMIZED"),
- .CLKFBOUT_MULT (7*MMCM_MODE),
- .CLKFBOUT_PHASE (0.0),
- .CLKIN1_PERIOD (CLKIN_PERIOD),
- .CLKIN2_PERIOD (CLKIN_PERIOD),
- .CLKOUT0_DIVIDE (MMCM_MODE),
- .CLKOUT0_DUTY_CYCLE (0.5),
- .CLKOUT0_PHASE (0.0),
- .CLKOUT1_DIVIDE (14*MMCM_MODE),
- .CLKOUT1_DUTY_CYCLE (0.5),
- .CLKOUT1_PHASE (0.0),
- .CLKOUT2_DIVIDE (7*MMCM_MODE),
- .CLKOUT2_DUTY_CYCLE (0.5),
- .CLKOUT2_PHASE (0.0),
- .CLKOUT3_DIVIDE (8),
- .CLKOUT3_DUTY_CYCLE (0.5),
- .CLKOUT3_PHASE (0.0),
- .CLKOUT4_DIVIDE (8),
- .CLKOUT4_DUTY_CYCLE (0.5),
- .CLKOUT4_PHASE (0.0),
- .CLKOUT5_DIVIDE (8),
- .CLKOUT5_DUTY_CYCLE (0.5),
- .CLKOUT5_PHASE (0.0),
- .COMPENSATION ("ZHOLD"),
- .DIVCLK_DIVIDE (1),
- .REF_JITTER1 (0.100))
- tx_mmcme2_adv_inst (
- .CLKFBOUT (txpllmmcm_x1),
- .CLKOUT0 (txpllmmcm_xn),
- .CLKOUT1 (),
- .CLKOUT2 (),
- .CLKOUT3 (),
- .CLKOUT4 (),
- .CLKOUT5 (),
- .DO (),
- .DRDY (),
- .PWRDWN (1'b0),
- .LOCKED (mmcm_lckd),
- .CLKFBIN (pixel_clk),
- .CLKIN1 (clkint),
- .CLKIN2 (1'b0),
- .CLKINSEL (1'b1),
- .DADDR (7'h00),
- .DCLK (1'b0),
- .DEN (1'b0),
- .DI (16'h0000),
- .DWE (1'b0),
- .RST (reset)) ;
- if (PIXEL_CLOCK == "BUF_G") begin // Final clock selection
- BUFG bufg_mmcm_x1 (.I(txpllmmcm_x1), .O(pixel_clk)) ;
- assign status[1:0] = 2'b00 ;
- end
- else if (PIXEL_CLOCK == "BUF_R") begin
- BUFR #(.BUFR_DIVIDE("1"),.SIM_DEVICE("7SERIES"))bufr_mmcm_x1 (.I(txpllmmcm_x1),.CE(1'b1),.O(pixel_clk),.CLR(1'b0)) ;
- assign status[1:0] = 2'b01 ;
- end
- else begin
- BUFH bufh_mmcm_x1 (.I(txpllmmcm_x1), .O(pixel_clk)) ;
- assign status[1:0] = 2'b10 ;
- end
-
- if (TX_CLOCK == "BUF_G") begin // Sample clock selection
- BUFG bufg_mmcm_xn (.I(txpllmmcm_xn), .O(txclk)) ;
- assign status[5:4] = 2'b01 ;
- end
- else if (TX_CLOCK == "BUFIO") begin
- BUFIO bufio_mmcm_xn (.I (txpllmmcm_xn), .O(txclk)) ;
- assign status[5:4] = 2'b11 ;
- end
- else begin
- BUFH bufh_mmcm_xn (.I(txpllmmcm_xn), .O(txclk)) ;
- assign status[5:4] = 2'b10 ;
- end
-
- end
- endgenerate
复制代码
txclk : pixel_clk = 7:1,其中txpllmmcm_x1对应pixel_clk ,txpllmmcm_xn对应txclk对于时钟计算可阅读ug472,这里不是本文的重点。
再来看下DDR下由于无法直接用1个OSERDES实现7:1的转换,官方给出的例子使用了2个OSERDES,这个我们下面会分析,先看下时钟的关系:pixel_clk: txclk_div: txclk=2:1:7 这个比值关系对应的是每2个pixel_clk也就是2个7bits数据共14bits经过2个OSERDES以1:7的速率实现并串转换(DDR模式下,1个txclk可以发2个bit数据)。当读者看到我的分析肯定还是有疑惑所以一定要对照源码分析。 - IBUFGDS #(
- .DIFF_TERM (DIFF_TERM))
- clk_iob_in (
- .I (clkin_p),
- .IB (clkin_n),
- .O (clkint));
- generate
- if (USE_PLL == "FALSE") begin : loop8 // use an MMCM
- assign status[6] = 1'b1 ;
-
- MMCME2_ADV #(
- .BANDWIDTH ("OPTIMIZED"),
- .CLKFBOUT_MULT_F (7*MMCM_MODE),
- .CLKFBOUT_PHASE (0.0),
- .CLKIN1_PERIOD (CLKIN_PERIOD),
- .CLKIN2_PERIOD (CLKIN_PERIOD),
- .CLKOUT0_DIVIDE_F (2*MMCM_MODE),
- .CLKOUT0_DUTY_CYCLE (0.5),
- .CLKOUT0_PHASE (0.0),
- .CLKOUT1_DIVIDE (14*MMCM_MODE),
- .CLKOUT1_DUTY_CYCLE (0.5),
- .CLKOUT1_PHASE (0.0),
- .CLKOUT2_DIVIDE (7*MMCM_MODE),
- .CLKOUT2_DUTY_CYCLE (0.5),
- .CLKOUT2_PHASE (0.0),
- .CLKOUT3_DIVIDE (8),
- .CLKOUT3_DUTY_CYCLE (0.5),
- .CLKOUT3_PHASE (0.0),
- .CLKOUT4_DIVIDE (8),
- .CLKOUT4_DUTY_CYCLE (0.5),
- .CLKOUT4_PHASE (0.0),
- .CLKOUT5_DIVIDE (8),
- .CLKOUT5_DUTY_CYCLE (0.5),
- .CLKOUT5_PHASE (0.0),
- .COMPENSATION ("ZHOLD"),
- .DIVCLK_DIVIDE (1),
- .REF_JITTER1 (0.100))
- tx_mmcme2_adv_inst (
- .CLKFBOUT (txpllmmcm_x1),
- .CLKFBOUTB (),
- .CLKFBSTOPPED (),
- .CLKINSTOPPED (),
- .CLKOUT0 (txpllmmcm_xn),
- .CLKOUT0B (),
- .CLKOUT1 (txpllmmcm_d2),
- .CLKOUT1B (),
- .CLKOUT2 (),
- .CLKOUT2B (),
- .CLKOUT3 (),
- .CLKOUT3B (),
- .CLKOUT4 (),
- .CLKOUT5 (),
- .CLKOUT6 (),
- .DO (),
- .DRDY (),
- .PSDONE (),
- .PSCLK (1'b0),
- .PSEN (1'b0),
- .PSINCDEC (1'b0),
- .PWRDWN (1'b0),
- .LOCKED (mmcm_lckd),
- .CLKFBIN (pixel_clk),
- .CLKIN1 (clkint),
- .CLKIN2 (1'b0),
- .CLKINSEL (1'b1),
- .DADDR (7'h00),
- .DCLK (1'b0),
- .DEN (1'b0),
- .DI (16'h0000),
- .DWE (1'b0),
- .RST (reset)) ;
- if (PIXEL_CLOCK == "BUF_G") begin // Final clock selection
- BUFG bufg_mmcm_x1 (.I(txpllmmcm_x1), .O(pixel_clk)) ;
- assign status[1:0] = 2'b00 ;
- end
- else if (PIXEL_CLOCK == "BUF_R") begin
- BUFR #(.BUFR_DIVIDE("1"),.SIM_DEVICE("7SERIES"))bufr_mmcm_x1 (.I(txpllmmcm_x1),.CE(1'b1),.O(pixel_clk),.CLR(1'b0)) ;
- assign status[1:0] = 2'b01 ;
- end
- else begin
- BUFH bufh_mmcm_x1 (.I(txpllmmcm_x1), .O(pixel_clk)) ;
- assign status[1:0] = 2'b10 ;
- end
- if (INTER_CLOCK == "BUF_G") begin // Intermediate clock selection
- BUFG bufg_mmcm_d2 (.I(txpllmmcm_d2), .O(txclk_div)) ;
- assign status[3:2] = 2'b00 ;
- end
- else if (INTER_CLOCK == "BUF_R") begin
- BUFR #(.BUFR_DIVIDE("1"),.SIM_DEVICE("7SERIES"))bufr_mmcm_d2 (.I(txpllmmcm_d2),.CE(1'b1),.O(txclk_div),.CLR(1'b0)) ;
- assign status[3:2] = 2'b01 ;
- end
- else begin
- BUFH bufh_mmcm_d2 (.I(txpllmmcm_d2), .O(txclk_div)) ;
- assign status[3:2] = 2'b10 ;
- end
-
- if (TX_CLOCK == "BUF_G") begin // Sample clock selection
- BUFG bufg_mmcm_xn (.I(txpllmmcm_xn), .O(txclk)) ;
- assign status[5:4] = 2'b00 ;
- end
- else if (TX_CLOCK == "BUFIO") begin
- BUFIO bufio_mmcm_xn (.I (txpllmmcm_xn), .O(txclk)) ;
- assign status[5:4] = 2'b11 ;
- end
- else begin
- BUFH bufh_mmcm_xn (.I(txpllmmcm_xn), .O(txclk)) ;
- assign status[5:4] = 2'b10 ;
- end
-
- end
- else begin
- assign status[6] = 1'b0 ; // Use a PLL
- PLLE2_ADV #(
- .BANDWIDTH ("OPTIMIZED"),
- .CLKFBOUT_MULT (7*MMCM_MODE),
- .CLKFBOUT_PHASE (0.0),
- .CLKIN1_PERIOD (CLKIN_PERIOD),
- .CLKIN2_PERIOD (CLKIN_PERIOD),
- .CLKOUT0_DIVIDE (2*MMCM_MODE),
- .CLKOUT0_DUTY_CYCLE (0.5),
- .CLKOUT0_PHASE (0.0),
- .CLKOUT1_DIVIDE (14*MMCM_MODE),
- .CLKOUT1_DUTY_CYCLE (0.5),
- .CLKOUT1_PHASE (0.0),
- .CLKOUT2_DIVIDE (7*MMCM_MODE),
- .CLKOUT2_DUTY_CYCLE (0.5),
- .CLKOUT2_PHASE (0.0),
- .CLKOUT3_DIVIDE (8),
- .CLKOUT3_DUTY_CYCLE (0.5),
- .CLKOUT3_PHASE (0.0),
- .CLKOUT4_DIVIDE (8),
- .CLKOUT4_DUTY_CYCLE (0.5),
- .CLKOUT4_PHASE (0.0),
- .CLKOUT5_DIVIDE (8),
- .CLKOUT5_DUTY_CYCLE (0.5),
- .CLKOUT5_PHASE (0.0),
- .COMPENSATION ("ZHOLD"),
- .DIVCLK_DIVIDE (1),
- .REF_JITTER1 (0.100))
- tx_mmcme2_adv_inst (
- .CLKFBOUT (txpllmmcm_x1),
- .CLKOUT0 (txpllmmcm_xn),
- .CLKOUT1 (txpllmmcm_d2),
- .CLKOUT2 (),
- .CLKOUT3 (),
- .CLKOUT4 (),
- .CLKOUT5 (),
- .DO (),
- .DRDY (),
- .PWRDWN (1'b0),
- .LOCKED (mmcm_lckd),
- .CLKFBIN (pixel_clk),
- .CLKIN1 (clkint),
- .CLKIN2 (1'b0),
- .CLKINSEL (1'b1),
- .DADDR (7'h00),
- .DCLK (1'b0),
- .DEN (1'b0),
- .DI (16'h0000),
- .DWE (1'b0),
- .RST (reset)) ;
- if (PIXEL_CLOCK == "BUF_G") begin // Final clock selection
- BUFG bufg_mmcm_x1 (.I(txpllmmcm_x1), .O(pixel_clk)) ;
- assign status[1:0] = 2'b00 ;
- end
- else if (PIXEL_CLOCK == "BUF_R") begin
- BUFR #(.BUFR_DIVIDE("1"),.SIM_DEVICE("7SERIES"))bufr_mmcm_x1 (.I(txpllmmcm_x1),.CE(1'b1),.O(pixel_clk),.CLR(1'b0)) ;
- assign status[1:0] = 2'b01 ;
- end
- else begin
- BUFH bufh_mmcm_x1 (.I(txpllmmcm_x1), .O(pixel_clk)) ;
- assign status[1:0] = 2'b10 ;
- end
- if (INTER_CLOCK == "BUF_G") begin // Intermediate clock selection
- BUFG bufg_mmcm_d2 (.I(txpllmmcm_d2), .O(txclk_div)) ;
- assign status[3:2] = 2'b00 ;
- end
- else if (INTER_CLOCK == "BUF_R") begin
- BUFR #(.BUFR_DIVIDE("1"),.SIM_DEVICE("7SERIES"))bufr_mmcm_d2 (.I(txpllmmcm_d2),.CE(1'b1),.O(txclk_div),.CLR(1'b0)) ;
- assign status[3:2] = 2'b01 ;
- end
- else begin
- BUFH bufh_mmcm_d2 (.I(txpllmmcm_d2), .O(txclk_div)) ;
- assign status[3:2] = 2'b10 ;
- end
-
- if (TX_CLOCK == "BUF_G") begin // Sample clock selection
- BUFG bufg_mmcm_xn (.I(txpllmmcm_xn), .O(txclk)) ;
- assign status[5:4] = 2'b00 ;
- end
- else if (TX_CLOCK == "BUFIO") begin
- BUFIO bufio_mmcm_xn (.I (txpllmmcm_xn), .O(txclk)) ;
- assign status[5:4] = 2'b11 ;
- end
- else begin
- BUFH bufh_mmcm_xn (.I(txpllmmcm_xn), .O(txclk)) ;
- assign status[5:4] = 2'b10 ;
- end
-
- end
- endgenerate
复制代码
4.3并串转换OSERDESE使用以上我们简单介绍了时钟部分的设计,接下来看数据部分,具体如何实现并串转换。首先我们看下如何将时钟通过OSERDESE发送出去。 先看下简单的SDR实现数据时钟的发送对应的文件是serdes_7_to_1_diff_sdr.v,其中clk_pattern=7'b1100001,可以在top5x2_7to1_sdr_tx.v找到parameter [6:0] TX_CLK_GEN = 7'b1100001 ;这是一个占空比为3:4的时钟,SDR模式只要一个OSERDES就可以实现。 - OBUFDS io_clk_out (
- .O (clkout_p),
- .OB (clkout_n),
- .I (tx_clk_out));
- OSERDESE2 #(
- .DATA_WIDTH (7), // SERDES word width
- .TRISTATE_WIDTH (1),
- .DATA_RATE_OQ ("SDR"), // <SDR>, DDR
- .DATA_RATE_TQ ("SDR"), // <SDR>, DDR
- .SERDES_MODE ("MASTER")) // <DEFAULT>, MASTER, SLAVE
- oserdes_cm (
- .OQ (tx_clk_out),
- .OCE (1'b1),
- .CLK (txclk),
- .RST (reset_int),
- .CLKDIV (pixel_clk),
- .D8 (1'b0),
- .D7 (clk_pattern[6]),
- .D6 (clk_pattern[5]),
- .D5 (clk_pattern[4]),
- .D4 (clk_pattern[3]),
- .D3 (clk_pattern[2]),
- .D2 (clk_pattern[1]),
- .D1 (clk_pattern[0]),
- .TQ (),
- .T1 (1'b0),
- .T2 (1'b0),
- .T3 (1'b0),
- .T4 (1'b0),
- .TCE (1'b1),
- .TBYTEIN (1'b0),
- .TBYTEOUT (),
- .OFB (),
- .TFB (),
- .SHIFTOUT1 (),
- .SHIFTOUT2 (),
- .SHIFTIN1 (1'b0),
- .SHIFTIN2 (1'b0)) ;
复制代码
再看下DDR模式下的数据时钟的发送,对应的文件是serdes_7_to_1_diff_ddr.v,可以看到DDR模式下采用了2个OSERDES级联实现和SDR的时钟参数一样clk_pattern=7'b1100001。如果有一些疑问可以看下前面“串并转换后的数据顺序”中图片描述。这里用2个OSERDESE2发送了2个clk_pattern。 - OBUFDS io_clk_out (
- .O (clkout_p),
- .OB (clkout_n),
- .I (tx_clk_out));
- OSERDESE2 #(
- .DATA_WIDTH (14), // SERDES word width
- .TRISTATE_WIDTH (1),
- .DATA_RATE_OQ ("DDR"), // <SDR>, DDR
- .DATA_RATE_TQ ("SDR"), // <SDR>, DDR
- .SERDES_MODE ("MASTER")) // <DEFAULT>, MASTER, SLAVE
- oserdes_cm (
- .OQ (tx_clk_out),
- .OCE (1'b1),
- .CLK (txclk),
- .RST (reset_intr),
- .CLKDIV (txclk_div),
- .D8 (clk_pattern[0]),
- .D7 (clk_pattern[6]),
- .D6 (clk_pattern[5]),
- .D5 (clk_pattern[4]),
- .D4 (clk_pattern[3]),
- .D3 (clk_pattern[2]),
- .D2 (clk_pattern[1]),
- .D1 (clk_pattern[0]),
- .TQ (),
- .T1 (1'b0),
- .T2 (1'b0),
- .T3 (1'b0),
- .T4 (1'b0),
- .TCE (1'b1),
- .TBYTEIN (1'b0),
- .TBYTEOUT (),
- .OFB (),
- .TFB (),
- .SHIFTOUT1 (),
- .SHIFTOUT2 (),
- .SHIFTIN1 (cascade_cdi),
- .SHIFTIN2 (cascade_cti)) ;
- OSERDESE2 #(
- .DATA_WIDTH (14), // SERDES word width.
- .TRISTATE_WIDTH (1),
- .DATA_RATE_OQ ("DDR"), // <SDR>, DDR
- .DATA_RATE_TQ ("SDR"), // <SDR>, DDR
- .SERDES_MODE ("SLAVE")) // <DEFAULT>, MASTER, SLAVE
- oserdes_cs (
- .OQ (),
- .OCE (1'b1),
- .CLK (txclk),
- .RST (reset_intr),
- .CLKDIV (txclk_div),
- .D8 (clk_pattern[6]),
- .D7 (clk_pattern[5]),
- .D6 (clk_pattern[4]),
- .D5 (clk_pattern[3]),
- .D4 (clk_pattern[2]),
- .D3 (clk_pattern[1]),
- .D2 (1'b0),
- .D1 (1'b0),
- .TQ (),
- .T1 (1'b0),
- .T2 (1'b0),
- .T3 (1'b0),
- .T4 (1'b0),
- .TCE (1'b1),
- .TBYTEIN (1'b0),
- .TBYTEOUT (),
- .OFB (),
- .TFB (),
- .SHIFTOUT1 (cascade_cdi),
- .SHIFTOUT2 (cascade_cti),
- .SHIFTIN1 (1'b0),
- .SHIFTIN2 (1'b0)) ;
复制代码
再看SDR的OSERDES数据的并串发送部分,以下代码中主要用到了generate语法对0~4路5条line进行并串数据转换 - OBUFDS io_clk_out (
- .O (clkout_p),
- .OB (clkout_n),
- .I (tx_clk_out));
- OSERDESE2 #(
- .DATA_WIDTH (14), // SERDES word width
- .TRISTATE_WIDTH (1),
- .DATA_RATE_OQ ("DDR"), // <SDR>, DDR
- .DATA_RATE_TQ ("SDR"), // <SDR>, DDR
- .SERDES_MODE ("MASTER")) // <DEFAULT>, MASTER, SLAVE
- oserdes_cm (
- .OQ (tx_clk_out),
- .OCE (1'b1),
- .CLK (txclk),
- .RST (reset_intr),
- .CLKDIV (txclk_div),
- .D8 (clk_pattern[0]),
- .D7 (clk_pattern[6]),
- .D6 (clk_pattern[5]),
- .D5 (clk_pattern[4]),
- .D4 (clk_pattern[3]),
- .D3 (clk_pattern[2]),
- .D2 (clk_pattern[1]),
- .D1 (clk_pattern[0]),
- .TQ (),
- .T1 (1'b0),
- .T2 (1'b0),
- .T3 (1'b0),
- .T4 (1'b0),
- .TCE (1'b1),
- .TBYTEIN (1'b0),
- .TBYTEOUT (),
- .OFB (),
- .TFB (),
- .SHIFTOUT1 (),
- .SHIFTOUT2 (),
- .SHIFTIN1 (cascade_cdi),
- .SHIFTIN2 (cascade_cti)) ;
- OSERDESE2 #(
- .DATA_WIDTH (14), // SERDES word width.
- .TRISTATE_WIDTH (1),
- .DATA_RATE_OQ ("DDR"), // <SDR>, DDR
- .DATA_RATE_TQ ("SDR"), // <SDR>, DDR
- .SERDES_MODE ("SLAVE")) // <DEFAULT>, MASTER, SLAVE
- oserdes_cs (
- .OQ (),
- .OCE (1'b1),
- .CLK (txclk),
- .RST (reset_intr),
- .CLKDIV (txclk_div),
- .D8 (clk_pattern[6]),
- .D7 (clk_pattern[5]),
- .D6 (clk_pattern[4]),
- .D5 (clk_pattern[3]),
- .D4 (clk_pattern[2]),
- .D3 (clk_pattern[1]),
- .D2 (1'b0),
- .D1 (1'b0),
- .TQ (),
- .T1 (1'b0),
- .T2 (1'b0),
- .T3 (1'b0),
- .T4 (1'b0),
- .TCE (1'b1),
- .TBYTEIN (1'b0),
- .TBYTEOUT (),
- .OFB (),
- .TFB (),
- .SHIFTOUT1 (cascade_cdi),
- .SHIFTOUT2 (cascade_cti),
- .SHIFTIN1 (1'b0),
- .SHIFTIN2 (1'b0)) ;
复制代码
DDR的数据的OSERDES并串发送部分,以下代码中主要用到了generate语法对0~4路5条line进行并串数据转换 - generate
- for (i = 0 ; i <= (D-1) ; i = i+1) begin : loop0
- OBUFDS io_data_out (
- .O (dataout_p[i]),
- .OB (dataout_n[i]),
- .I (tx_data_out[i]));
- // re-arrange data bits for transmission and invert lines as given by the mask
- // NOTE If pin inversion is required (non-zero SWAP MASK) then inverters will occur in fabric, as there are no inverters in the OSERDESE2
- // This can be avoided by doing the inversion (if necessary) in the user logic
- // TX_SWAP_MASK not available when IN_FIFO is used
- for (j = 0 ; j <= 13 ; j = j+1) begin : loop1
- if (DATA_FORMAT == "PER_CLOCK") begin
- assign mdataina[14*i+j] = dataint[D*j+i] ^ TX_SWAP_MASK[i] ;
- end
- else begin
- if (j < 7) begin
- assign mdataina[14*i+j] = dataint[(7*i)+j] ^ TX_SWAP_MASK[i] ;
- end
- else begin
- assign mdataina[14*i+j] = dataint[(7*i)+j-7+D*7] ^ TX_SWAP_MASK[i];
- end
- end
- end
- OSERDESE2 #(
- .DATA_WIDTH (14), // SERDES word width
- .TRISTATE_WIDTH (1),
- .DATA_RATE_OQ ("DDR"), // <SDR>, DDR
- .DATA_RATE_TQ ("SDR"), // <SDR>, DDR
- .SERDES_MODE ("MASTER")) // <DEFAULT>, MASTER, SLAVE
- oserdes_m (
- .OQ (tx_data_out[i]),
- .OCE (1'b1),
- .CLK (txclk),
- .RST (reset_intr),
- .CLKDIV (txclk_div),
- .D8 (mdataina[(14*i)+7]),
- .D7 (mdataina[(14*i)+6]),
- .D6 (mdataina[(14*i)+5]),
- .D5 (mdataina[(14*i)+4]),
- .D4 (mdataina[(14*i)+3]),
- .D3 (mdataina[(14*i)+2]),
- .D2 (mdataina[(14*i)+1]),
- .D1 (mdataina[(14*i)+0]),
- .TQ (),
- .T1 (1'b0),
- .T2 (1'b0),
- .T3 (1'b0),
- .T4 (1'b0),
- .TCE (1'b1),
- .TBYTEIN (1'b0),
- .TBYTEOUT (),
- .OFB (),
- .TFB (),
- .SHIFTOUT1 (),
- .SHIFTOUT2 (),
- .SHIFTIN1 (cascade_di[i]),
- .SHIFTIN2 (cascade_ti[i])) ;
- OSERDESE2 #(
- .DATA_WIDTH (14), // SERDES word width.
- .TRISTATE_WIDTH (1),
- .DATA_RATE_OQ ("DDR"), // <SDR>, DDR
- .DATA_RATE_TQ ("SDR"), // <SDR>, DDR
- .SERDES_MODE ("SLAVE")) // <DEFAULT>, MASTER, SLAVE
- oserdes_s (
- .OQ (),
- .OCE (1'b1),
- .CLK (txclk),
- .RST (reset_intr),
- .CLKDIV (txclk_div),
- .D8 (mdataina[(14*i)+13]),
- .D7 (mdataina[(14*i)+12]),
- .D6 (mdataina[(14*i)+11]),
- .D5 (mdataina[(14*i)+10]),
- .D4 (mdataina[(14*i)+9]),
- .D3 (mdataina[(14*i)+8]),
- .D2 (1'b0),
- .D1 (1'b0),
- .TQ (),
- .T1 (1'b0),
- .T2 (1'b0),
- .T3 (1'b0),
- .T4 (1'b0),
- .TCE (1'b1),
- .TBYTEIN (1'b0),
- .TBYTEOUT (),
- .OFB (),
- .TFB (),
- .SHIFTOUT1 (cascade_di[i]),
- .SHIFTOUT2 (cascade_ti[i]),
- .SHIFTIN1 (1'b0),
- .SHIFTIN2 (1'b0)) ;
- end
- endgenerate
复制代码
4.4SDR ISERDESE使用4.4.1SDR ISERDESE时钟恢复ISERDESE的作用是和OSERDESE相反的过程,但是比OSERDESE稍微复杂一些。ISERDESE代码需要实现自动找到最佳的数据采样点,自动完成数据对其,其中的加入了IDELAY和BITSLIP功能的使用,以及一些状态机代码。笔者下面根据XAPP585中给出的demo源码分析。 我们先看下新的概念IDELAY(ug471中有详细介绍)。 为了使用IDELAY需要先设置IDELAYCTRL,并且提供200M或者300M的参考时钟,如下代码,这段代码在文件top5x2_7to1_sdr_rx.v中,在top5x2_7to1_ddr_rx.v中也有相同的代码。 - IBUF iob_200m_in(
- .I (refclkin),
- .O (refclkint));
- BUFG bufg_200_ref (
- .I (refclkint),
- .O (refclkintbufg)) ;
-
- IDELAYCTRL icontrol ( // Instantiate input delay control block
- .REFCLK (refclkintbufg),
- .RST (reset),
- .RDY (delay_ready));
复制代码
代码会自动搜索最佳的delay延迟,这是如何实现的呢?首先我们看下代码中的bit_rate_value的值,在文件serdes_1_to_7_mmcm_idelay_sdr.v中有如下代码。比如设置bit_rate_value=0560,那么代表了serdes的速率是560Mbps,根据下面代码得出bt_val=5’h17(bt_val=5’d23)。如果idelay的参考时钟是200MHZ 那么每个延迟节拍是78ps,23*78ps= 1,794 ps = 557 Mb/s.最接近560Mbps. - assign bit_time_value = bt_val ;
- always @ (bit_rate_value) begin // Generate tap number to be used for input bit rate
- if (bit_rate_value > 16'h1068) begin bt_val <= 5'h0C ; end
- else if (bit_rate_value > 16'h0986) begin bt_val <= 5'h0D ; end
- else if (bit_rate_value > 16'h0916) begin bt_val <= 5'h0E ; end
- else if (bit_rate_value > 16'h0855) begin bt_val <= 5'h0F ; end
- else if (bit_rate_value > 16'h0801) begin bt_val <= 5'h10 ; end
- else if (bit_rate_value > 16'h0754) begin bt_val <= 5'h11 ; end
- else if (bit_rate_value > 16'h0712) begin bt_val <= 5'h12 ; end
- else if (bit_rate_value > 16'h0675) begin bt_val <= 5'h13 ; end
- else if (bit_rate_value > 16'h0641) begin bt_val <= 5'h14 ; end
- else if (bit_rate_value > 16'h0611) begin bt_val <= 5'h15 ; end
- else if (bit_rate_value > 16'h0583) begin bt_val <= 5'h16 ; end
- else if (bit_rate_value > 16'h0557) begin bt_val <= 5'h17 ; end
- else if (bit_rate_value > 16'h0534) begin bt_val <= 5'h18 ; end
- else if (bit_rate_value > 16'h0513) begin bt_val <= 5'h19 ; end
- else if (bit_rate_value > 16'h0493) begin bt_val <= 5'h1A ; end
- else if (bit_rate_value > 16'h0475) begin bt_val <= 5'h1B ; end
- else if (bit_rate_value > 16'h0458) begin bt_val <= 5'h1C ; end
- else if (bit_rate_value > 16'h0442) begin bt_val <= 5'h1D ; end
- else if (bit_rate_value > 16'h0427) begin bt_val <= 5'h1E ; end
- else begin bt_val <= 5'h1F ; end
- end
复制代码
以上是完成了最高速度,最大延迟的参数选择,具体如何自动计算呢?先看下XAPP585中给出的计算原理。 找到传输速率对应的最大delay延迟参数,最大延迟参数的延迟不超过最大速度的周期,我们这里仿真文件中需要选择时钟为80M(默认是100M)在tb_top5x2_7to1_sdr设置always #(6250) pixelclock_p = ~pixelclock_p ;所以计算出串行速度是7*80M=560Mbps 有了个560Mbps的参数,就可以选出bit_rate_value为0557,所以bt_val为17h 先取出bt_val的中间值,17h的中间值是0Bh。把差分时钟的n延迟0Bh,把差分时钟的p延迟17h。代码如下: - // Clock input
- IBUFGDS_DIFF_OUT #(
- .DIFF_TERM (DIFF_TERM),
- .IBUF_LOW_PWR ("FALSE"))
- iob_clk_in (
- .I (clkin_p),
- .IB (clkin_n),
- .O (rx_clk_in_p),
- .OB (rx_clk_in_n));
- genvar i ;
- genvar j ;
- IDELAYE2 #(
- .HIGH_PERFORMANCE_MODE (HIGH_PERFORMANCE_MODE),
- .IDELAY_VALUE (1),
- .DELAY_SRC ("IDATAIN"),
- .IDELAY_TYPE ("VAR_LOAD"))
- idelay_cm(
- .DATAOUT (rx_clkin_p_d),
- .C (rxclk_div),
- .CE (1'b0),
- .INC (1'b0),
- .DATAIN (1'b0),
- .IDATAIN (rx_clk_in_p),
- .LD (1'b1),
- .LDPIPEEN (1'b0),
- .REGRST (1'b0),
- .CINVCTRL (1'b0),
- .CNTVALUEIN (c_delay_in),
- .CNTVALUEOUT ());
- IDELAYE2 #(
- .HIGH_PERFORMANCE_MODE (HIGH_PERFORMANCE_MODE),
- .IDELAY_VALUE (1),
- .DELAY_SRC ("IDATAIN"),
- .IDELAY_TYPE ("VAR_LOAD"))
- idelay_cs(
- .DATAOUT (rx_clk_in_n_d),
- .C (rxclk_div),
- .CE (1'b0),
- .INC (1'b0),
- .DATAIN (1'b0),
- .IDATAIN (~rx_clk_in_n),
- .LD (1'b1),
- .LDPIPEEN (1'b0),
- .REGRST (1'b0),
- .CINVCTRL (1'b0),
- .CNTVALUEIN ({1'b0, bt_val[4:1]}),
- .CNTVALUEOUT ());
复制代码
通过ISERDESE把时钟的n实现串并转换。 - ISERDESE2 #(
- .DATA_WIDTH (7),
- .DATA_RATE ("SDR"),
- // .SERDES_MODE ("MASTER"),
- .IOBDELAY ("IFD"),
- .INTERFACE_TYPE ("NETWORKING"))
- iserdes_cm (
- .D (1'b0),
- .DDLY (rx_clk_in_n_d),
- .CE1 (1'b1),
- .CE2 (1'b1),
- .CLK (rxclk),
- .CLKB (~rxclk),
- .RST (rstcserdes),
- .CLKDIV (rxclk_div),
- .CLKDIVP (1'b0),
- .OCLK (1'b0),
- .OCLKB (1'b0),
- .DYNCLKSEL (1'b0),
- .DYNCLKDIVSEL (1'b0),
- .SHIFTIN1 (1'b0),
- .SHIFTIN2 (1'b0),
- .BITSLIP (bslip),
- .O (),
- .Q8 (),
- .Q7 (clk_iserdes_data[0]),
- .Q6 (clk_iserdes_data[1]),
- .Q5 (clk_iserdes_data[2]),
- .Q4 (clk_iserdes_data[3]),
- .Q3 (clk_iserdes_data[4]),
- .Q2 (clk_iserdes_data[5]),
- .Q1 (clk_iserdes_data[6]),
- .OFB (),
- .SHIFTOUT1 (),
- .SHIFTOUT2 ());
复制代码
通过控制每次减1个tap,减少idelay延迟,并且通过ISERDESE检测时钟的跳变。当第一次跳变发生的时候,代表检测到了时钟的跳变沿,假设这个时候bt_val=05h,05h<0Bh 所以计算出时钟的0BH+05H =10H 具体代码如下: - always @ (posedge rxclk_div) begin //
- clk_iserdes_data_d <= clk_iserdes_data ;
- if ((clk_iserdes_data != clk_iserdes_data_d) && (clk_iserdes_data != 7'h00) && (clk_iserdes_data != 7'h7F)) begin
- data_different <= 1'b1 ;
- end
- else begin
- data_different <= 1'b0 ;
- end
- end
- always @ (posedge rxclk_div) begin // clock delay shift state machine
- not_rx_mmcm_lckd_int <= ~(mmcm_locked & idelay_rdy) ;
- rstcserdes <= not_rx_mmcm_lckd_int | rst_iserdes ;
- if (not_rx_mmcm_lckd_int == 1'b1) begin
- scount <= 6'h00 ;
- state2 <= 0 ;
- state2_count <= 5'h00 ;
- locked_out <= 1'b0 ;
- chfoundc <= 1'b1 ;
- c_delay_in <= bt_val ; // Start the delay line at the current bit period
- rst_iserdes <= 1'b0 ;
- c_loop_cnt <= 2'b00 ;
- end
- else begin
- if (scount[5] == 1'b0) begin
- scount <= scount + 6'h01 ;
- end
- state2_count <= state2_count + 5'h01 ;
- if (chfoundc == 1'b1) begin
- chfound <= 1'b0 ;
- end
- else if (chfound == 1'b0 && data_different == 1'b1) begin
- chfound <= 1'b1 ;
- end
- if ((state2_count == 5'h1F && scount[5] == 1'b1)) begin
- case(state2)
- 0 : begin // decrement delay and look for a change
- if (chfound == 1'b1 || (c_loop_cnt == 2'b11 && c_delay_in == 5'h00)) begin // quit loop if we've been around a few times
- chfoundc <= 1'b1 ;
- state2 <= 1 ;
- end
- else begin
- chfoundc <= 1'b0 ;
- if (c_delay_in != 5'h00) begin // check for underflow
- c_delay_in <= c_delay_in - 5'h01 ;
- end
- else begin
- c_delay_in <= bt_val ;
- c_loop_cnt <= c_loop_cnt + 2'b01 ;
- end
- end
- end
- 1 : begin // add half a bit period using input information
- state2 <= 2 ;
- if (c_delay_in < {1'b0, bt_val[4:1]}) begin // choose the lowest delay value to minimise jitter
- c_delay_in_target <= c_delay_in + {1'b0, bt_val[4:1]} ;
- end
- else begin
- c_delay_in_target <= c_delay_in - {1'b0, bt_val[4:1]} ;
- end
- end
- 2 : begin
- if (c_delay_in == c_delay_in_target) begin
- state2 <= 3 ;
- end
- else begin
- if (c_delay_in_ud == 1'b1) begin // move gently to end position to stop MMCM unlocking
- c_delay_in <= c_delay_in + 5'h01 ;
- c_delay_in_ud <= 1'b1 ;
- end
- else begin
- c_delay_in <= c_delay_in - 5'h01 ;
- c_delay_in_ud <= 1'b0 ;
- end
- end
- end
- 3 : begin rst_iserdes <= 1'b1 ; state2 <= 4 ; end // remove serdes reset
- default : begin // issue locked out signal
- rst_iserdes <= 1'b0 ; locked_out <= 1'b1 ;
- end
- endcase
- end
- end
- end
复制代码
如果解出的时钟值不是1100001或者1100011就需要用到Bslip调整,每次设置Bslip调整一次。 - // Bitslip state machine
- always @ (posedge rxclk_div)
- begin
- if (locked_out == 1'b0) begin
- bslip <= 1'b0 ;
- bsstate <= 1 ;
- enable <= 1'b0 ;
- bcount <= 4'h0 ;
- bs_finished <= 1'b0 ;
- not_bs_finished <= 1'b1 ;
- end
- else begin
- enable <= 1'b1 ;
- if (enable == 1'b1) begin
- if (clk_iserdes_data != 7'b1100001) begin flag1 <= 1'b1 ; end else begin flag1 <= 1'b0 ; end
- if (clk_iserdes_data != 7'b1100011) begin flag2 <= 1'b1 ; end else begin flag2 <= 1'b0 ; end
- if (bsstate == 0) begin
- if (flag1 == 1'b1 && flag2 == 1'b1) begin
- bslip <= 1'b1 ; // bitslip needed
- bsstate <= 1 ;
- end
- else begin
- bs_finished <= 1'b1 ; // bitslip done
- not_bs_finished <= 1'b0 ; // bitslip done
- end
- end
- else if (bsstate == 1) begin
- bslip <= 1'b0 ;
- bcount <= bcount + 4'h1 ;
- if (bcount == 4'hF) begin
- bsstate <= 0 ;
- end
- end
- end
- end
- end
复制代码
现在结合下仿真结果看下,在下图的箭头所示,接收的clk_data是7bit时钟数据,通过调整c_delay_in值调整idelay每次减1个tap,直到clk_data发生改变,可以看到这个值是0b整好是和中间值相等。这个时候状态机state2也会进入状态1,这个时候计算出c_delay_in_target=0,这个就是我们需要延迟的时间,可以看到需要延迟的时间为0,这种情况非常理想,如果实际程序跑起来一般不会是0,因为我们这里只做了RTL级行为仿真。之后进入状态2.
在状态2中继续调整c_delay_in直到和c_delay_in_target相等,如下图,可以看到clk_data=1100001b 是占比3:4的时钟就恢复出来了。
4.4.2SDR ISERDESE数据恢复再看SDR数据部分的串并接收,先看下面源码,ISERDESE的应用,关键还是如何获取的idelay的延迟参数。这里有delay_controller_wrap.v中的代码控制数据部分的延迟参数。 - generate
- for (i = 0 ; i <= D-1 ; i = i+1)
- begin : loop3
- delay_controller_wrap # (
- .S (7))
- dc_inst (
- .m_datain (mdataout[7*i+6:7*i]),
- .s_datain (sdataout[7*i+6:7*i]),
- .enable_phase_detector (enable_phase_detector),
- .enable_monitor (enable_monitor),
- .reset (not_bs_finished),
- .clk (rxclk_div),
- .c_delay_in ({1'b0, bt_val[4:1]}),
- .m_delay_out (m_delay_val_in[5*i+4:5*i]),
- .s_delay_out (s_delay_val_in[5*i+4:5*i]),
- .data_out (mdataoutd[7*i+6:7*i]),
- .bt_val (bt_val),
- .del_mech (1'b0),
- .m_delay_1hot (m_delay_1hot[32*i+31:32*i]),
- .results (eye_info[32*i+31:32*i])) ;
- // Data bit Receivers
- IBUFDS_DIFF_OUT #(
- .DIFF_TERM (DIFF_TERM))
- data_in (
- .I (datain_p[i]),
- .IB (datain_n[i]),
- .O (rx_data_in_p[i]),
- .OB (rx_data_in_n[i]));
- assign rx_data_in_m[i] = rx_data_in_p[i] ^ RX_SWAP_MASK[i] ;
- assign rx_data_in_s[i] = ~rx_data_in_n[i] ^ RX_SWAP_MASK[i] ;
- IDELAYE2 #(
- .HIGH_PERFORMANCE_MODE (HIGH_PERFORMANCE_MODE),
- .IDELAY_VALUE (0),
- .DELAY_SRC ("IDATAIN"),
- .IDELAY_TYPE ("VAR_LOAD"))
- idelay_m(
- .DATAOUT (rx_data_in_md[i]),
- .C (rxclk_div),
- .CE (1'b0),
- .INC (1'b0),
- .DATAIN (1'b0),
- .IDATAIN (rx_data_in_m[i]),
- .LD (1'b1),
- .LDPIPEEN (1'b0),
- .REGRST (1'b0),
- .CINVCTRL (1'b0),
- .CNTVALUEIN (m_delay_val_in[5*i+4:5*i]),
- .CNTVALUEOUT ());
- ISERDESE2 #(
- .DATA_WIDTH (7),
- .DATA_RATE ("SDR"),
- .SERDES_MODE ("MASTER"),
- .IOBDELAY ("IFD"),
- .INTERFACE_TYPE ("NETWORKING"))
- iserdes_m (
- .D (1'b0),
- .DDLY (rx_data_in_md[i]),
- .CE1 (1'b1),
- .CE2 (1'b1),
- .CLK (rxclk),
- .CLKB (~rxclk),
- .RST (rst_iserdes),
- .CLKDIV (rxclk_div),
- .CLKDIVP (1'b0),
- .OCLK (1'b0),
- .OCLKB (1'b0),
- .DYNCLKSEL (1'b0),
- .DYNCLKDIVSEL (1'b0),
- .SHIFTIN1 (1'b0),
- .SHIFTIN2 (1'b0),
- .BITSLIP (bslip),
- .O (),
- .Q8 (),
- .Q7 (mdataout[7*i+0]),
- .Q6 (mdataout[7*i+1]),
- .Q5 (mdataout[7*i+2]),
- .Q4 (mdataout[7*i+3]),
- .Q3 (mdataout[7*i+4]),
- .Q2 (mdataout[7*i+5]),
- .Q1 (mdataout[7*i+6]),
- .OFB (),
- .SHIFTOUT1 (),
- .SHIFTOUT2 ());
- IDELAYE2 #(
- .HIGH_PERFORMANCE_MODE (HIGH_PERFORMANCE_MODE),
- .IDELAY_VALUE (0),
- .DELAY_SRC ("IDATAIN"),
- .IDELAY_TYPE ("VAR_LOAD"))
- idelay_s(
- .DATAOUT (rx_data_in_sd[i]),
- .C (rxclk_div),
- .CE (1'b0),
- .INC (1'b0),
- .DATAIN (1'b0),
- .IDATAIN (rx_data_in_s[i]),
- .LD (1'b1),
- .LDPIPEEN (1'b0),
- .REGRST (1'b0),
- .CINVCTRL (1'b0),
- .CNTVALUEIN (s_delay_val_in[5*i+4:5*i]),
- .CNTVALUEOUT ());
- ISERDESE2 #(
- .DATA_WIDTH (7),
- .DATA_RATE ("SDR"),
- // .SERDES_MODE ("SLAVE"),
- .IOBDELAY ("IFD"),
- .INTERFACE_TYPE ("NETWORKING"))
- iserdes_s (
- .D (1'b0),
- .DDLY (rx_data_in_sd[i]),
- .CE1 (1'b1),
- .CE2 (1'b1),
- .CLK (rxclk),
- .CLKB (~rxclk),
- .RST (rst_iserdes),
- .CLKDIV (rxclk_div),
- .CLKDIVP (1'b0),
- .OCLK (1'b0),
- .OCLKB (1'b0),
- .DYNCLKSEL (1'b0),
- .DYNCLKDIVSEL (1'b0),
- .SHIFTIN1 (1'b0),
- .SHIFTIN2 (1'b0),
- .BITSLIP (bslip),
- .O (),
- .Q8 (),
- .Q7 (sdataout[7*i+0]),
- .Q6 (sdataout[7*i+1]),
- .Q5 (sdataout[7*i+2]),
- .Q4 (sdataout[7*i+3]),
- .Q3 (sdataout[7*i+4]),
- .Q2 (sdataout[7*i+5]),
- .Q1 (sdataout[7*i+6]),
- .OFB (),
- .SHIFTOUT1 (),
- .SHIFTOUT2 ());
- for (j = 0 ; j <= 6 ; j = j+1) begin : loop1 // Assign data bits to correct serdes according to required format
- if (DATA_FORMAT == "PER_CLOCK") begin
- assign rx_data[D*j+i] = mdataoutd[7*i+j] ;
- end
- else begin
- assign rx_data[7*i+j] = mdataoutd[7*i+j] ;
- end
- end
- end
- endgenerate
复制代码
delay_controller_wrap.v中的代码看起来比较难以理解,先看下仿真结果。可以看到s_delay_out的值为00h,m_delay_out的值为0bh.至于这个delay_controller_wrap.v中其他一些功能都没用到。
4.5DDR ISERDESE使用4.5.1DDR ISERDESE时钟恢复前面分析SDR ISERDESE就花费了不少篇幅,DDR ISERDESE要更难一些,但是应用上更广泛一些。有了SDR ISERDESE部分的详细分析,DDR ISERDESE就是难度大了分析起来也简单多了。由于无法直接使用1个ISERDESE实现1:7的转换。 首先看下top5x2_7to1_ddr_rx.v中的代码,这里的时钟是151.5M, 151.5M *7=1060Mbps,bit_rate_value=1060。 - 10n_x_serdes_1_to_7_mmcm_idelay_ddr #(
- .N (2),
- .SAMPL_CLOCK ("BUFIO"),
- .INTER_CLOCK ("BUF_R"),
- .PIXEL_CLOCK ("BUF_G"),
- .USE_PLL ("FALSE"),
- .HIGH_PERFORMANCE_MODE ("FALSE"),
- .D (D), // Number of data lines
- .REF_FREQ (200.0), // Set idelay control reference frequency
- .CLKIN_PERIOD (6.600), // Set input clock period
- .MMCM_MODE (1), // Parameter to set multiplier for MMCM to get VCO in correct operating range. 1 multiplies input clock by 7, 2 multiplies clock by 14, etc
- .DIFF_TERM ("TRUE"),
- .DATA_FORMAT ("PER_CLOCK")) // PER_CLOCK or PER_CHANL data formatting
- rx0 (
- .clkin_p (clkin_p),
- .clkin_n (clkin_n),
- .datain_p (datain_p),
- .datain_n (datain_n),
- .enable_phase_detector (1'b1), // enable phase detector operation
- .enable_monitor (1'b0), // enables data eye monitoring
- .dcd_correct (1'b0), // enables clock duty cycle correction
- .rxclk (),
- .rxclk_d4 (), // intermediate clock, use with data monitoring logic
- .idelay_rdy (delay_ready),
- .pixel_clk (rx_pixel_clk),
- .reset (reset),
- .rx_mmcm_lckd (rx_mmcm_lckd),
- .rx_mmcm_lckdps (rx_mmcm_lckdps),
- .rx_mmcm_lckdpsbs (rx_mmcm_lckdpsbs),
- .clk_data (),
- .rx_data (rxdall),
- .bit_rate_value (16'h1050), // required bit rate value in BCD
- .bit_time_value (),
- .status (),
- .eye_info (), // data eye monitor per line
- .m_delay_1hot (), // sample point monitor per line
- .debug ()) ; // debug bus
复制代码
bit_rate_value=1060,idelay的参考时钟是200M,可以得出delay的tap最大值为0DH,如果参考时钟是300M,中有dcd_correct的功能,笔者暂时不清楚如何使用也就不分析,我们这里的时钟是200M - if (REF_FREQ < 210.0) begin
- always @ (bit_rate_value) begin // Generate tap number to be used for input bit rate (200 MHz ref clock)
- if (bit_rate_value > 16'h1984) begin bt_val <= 5'h07 ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h1717) begin bt_val <= 5'h08 ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h1514) begin bt_val <= 5'h09 ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h1353) begin bt_val <= 5'h0A ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h1224) begin bt_val <= 5'h0B ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h1117) begin bt_val <= 5'h0C ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h1027) begin bt_val <= 5'h0D ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h0951) begin bt_val <= 5'h0E ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h0885) begin bt_val <= 5'h0F ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h0828) begin bt_val <= 5'h10 ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h0778) begin bt_val <= 5'h11 ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h0733) begin bt_val <= 5'h12 ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h0694) begin bt_val <= 5'h13 ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h0658) begin bt_val <= 5'h14 ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h0626) begin bt_val <= 5'h15 ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h0597) begin bt_val <= 5'h16 ; del_mech <= 1'b0 ; end
- else if (bit_rate_value > 16'h0570) begin bt_val <= 5'h17 ; del_mech <= 1'b0 ; end
- else if (bit_rate_value > 16'h0546) begin bt_val <= 5'h18 ; del_mech <= 1'b0 ; end
- else if (bit_rate_value > 16'h0524) begin bt_val <= 5'h19 ; del_mech <= 1'b0 ; end
- else if (bit_rate_value > 16'h0503) begin bt_val <= 5'h1A ; del_mech <= 1'b0 ; end
- else if (bit_rate_value > 16'h0484) begin bt_val <= 5'h1B ; del_mech <= 1'b0 ; end
- else if (bit_rate_value > 16'h0466) begin bt_val <= 5'h1C ; del_mech <= 1'b0 ; end
- else if (bit_rate_value > 16'h0450) begin bt_val <= 5'h1D ; del_mech <= 1'b0 ; end
- else if (bit_rate_value > 16'h0435) begin bt_val <= 5'h1E ; del_mech <= 1'b0 ; end
- else begin bt_val <= 5'h1F ; del_mech <= 1'b0 ; end // min bit rate 420 Mbps
- end
- end else begin
- always @ (bit_rate_value or dcd_correct) begin // Generate tap number to be used for input bit rate (300 MHz ref clock)
- if ((bit_rate_value > 16'h2030 && dcd_correct == 1'b0) || (bit_rate_value > 16'h1845 && dcd_correct == 1'b1)) begin bt_val <= 5'h0A ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h1836 && dcd_correct == 1'b0) || (bit_rate_value > 16'h1669 && dcd_correct == 1'b1)) begin bt_val <= 5'h0B ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h1675 && dcd_correct == 1'b0) || (bit_rate_value > 16'h1523 && dcd_correct == 1'b1)) begin bt_val <= 5'h0C ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h1541 && dcd_correct == 1'b0) || (bit_rate_value > 16'h1401 && dcd_correct == 1'b1)) begin bt_val <= 5'h0D ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h1426 && dcd_correct == 1'b0) || (bit_rate_value > 16'h1297 && dcd_correct == 1'b1)) begin bt_val <= 5'h0E ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h1328 && dcd_correct == 1'b0) || (bit_rate_value > 16'h1207 && dcd_correct == 1'b1)) begin bt_val <= 5'h0F ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h1242 && dcd_correct == 1'b0) || (bit_rate_value > 16'h1129 && dcd_correct == 1'b1)) begin bt_val <= 5'h10 ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h1167 && dcd_correct == 1'b0) || (bit_rate_value > 16'h1061 && dcd_correct == 1'b1)) begin bt_val <= 5'h11 ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h1100 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0999 && dcd_correct == 1'b1)) begin bt_val <= 5'h12 ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h1040 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0946 && dcd_correct == 1'b1)) begin bt_val <= 5'h13 ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h0987 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0897 && dcd_correct == 1'b1)) begin bt_val <= 5'h14 ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h0939 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0853 && dcd_correct == 1'b1)) begin bt_val <= 5'h15 ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h0895 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0814 && dcd_correct == 1'b1)) begin bt_val <= 5'h16 ; del_mech <= 1'b0 ; end
- else if ((bit_rate_value > 16'h0855 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0777 && dcd_correct == 1'b1)) begin bt_val <= 5'h17 ; del_mech <= 1'b0 ; end
- else if ((bit_rate_value > 16'h0819 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0744 && dcd_correct == 1'b1)) begin bt_val <= 5'h18 ; del_mech <= 1'b0 ; end
- else if ((bit_rate_value > 16'h0785 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0714 && dcd_correct == 1'b1)) begin bt_val <= 5'h19 ; del_mech <= 1'b0 ; end
- else if ((bit_rate_value > 16'h0754 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0686 && dcd_correct == 1'b1)) begin bt_val <= 5'h1A ; del_mech <= 1'b0 ; end
- else if ((bit_rate_value > 16'h0726 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0660 && dcd_correct == 1'b1)) begin bt_val <= 5'h1B ; del_mech <= 1'b0 ; end
- else if ((bit_rate_value > 16'h0700 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0636 && dcd_correct == 1'b1)) begin bt_val <= 5'h1C ; del_mech <= 1'b0 ; end
- else if ((bit_rate_value > 16'h0675 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0614 && dcd_correct == 1'b1)) begin bt_val <= 5'h1D ; del_mech <= 1'b0 ; end
- else if ((bit_rate_value > 16'h0652 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0593 && dcd_correct == 1'b1)) begin bt_val <= 5'h1E ; del_mech <= 1'b0 ; end
- else begin bt_val <= 5'h1F ; del_mech <= 1'b0 ; end // min bit rate 631 Mbps
- end
- end
复制代码
先取出bt_val的中间值,0Dh的中间值是06h。把差分时钟的n延迟06h,把差分时钟的p延迟0Dh。代码如下,由于ddr模式没有直接实现1:7转换,所以先用iserdes实现了1:4转换,再用gearbox实现4:7的转换。 - if (REF_FREQ < 210.0) begin
- always @ (bit_rate_value) begin // Generate tap number to be used for input bit rate (200 MHz ref clock)
- if (bit_rate_value > 16'h1984) begin bt_val <= 5'h07 ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h1717) begin bt_val <= 5'h08 ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h1514) begin bt_val <= 5'h09 ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h1353) begin bt_val <= 5'h0A ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h1224) begin bt_val <= 5'h0B ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h1117) begin bt_val <= 5'h0C ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h1027) begin bt_val <= 5'h0D ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h0951) begin bt_val <= 5'h0E ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h0885) begin bt_val <= 5'h0F ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h0828) begin bt_val <= 5'h10 ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h0778) begin bt_val <= 5'h11 ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h0733) begin bt_val <= 5'h12 ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h0694) begin bt_val <= 5'h13 ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h0658) begin bt_val <= 5'h14 ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h0626) begin bt_val <= 5'h15 ; del_mech <= 1'b1 ; end
- else if (bit_rate_value > 16'h0597) begin bt_val <= 5'h16 ; del_mech <= 1'b0 ; end
- else if (bit_rate_value > 16'h0570) begin bt_val <= 5'h17 ; del_mech <= 1'b0 ; end
- else if (bit_rate_value > 16'h0546) begin bt_val <= 5'h18 ; del_mech <= 1'b0 ; end
- else if (bit_rate_value > 16'h0524) begin bt_val <= 5'h19 ; del_mech <= 1'b0 ; end
- else if (bit_rate_value > 16'h0503) begin bt_val <= 5'h1A ; del_mech <= 1'b0 ; end
- else if (bit_rate_value > 16'h0484) begin bt_val <= 5'h1B ; del_mech <= 1'b0 ; end
- else if (bit_rate_value > 16'h0466) begin bt_val <= 5'h1C ; del_mech <= 1'b0 ; end
- else if (bit_rate_value > 16'h0450) begin bt_val <= 5'h1D ; del_mech <= 1'b0 ; end
- else if (bit_rate_value > 16'h0435) begin bt_val <= 5'h1E ; del_mech <= 1'b0 ; end
- else begin bt_val <= 5'h1F ; del_mech <= 1'b0 ; end // min bit rate 420 Mbps
- end
- end else begin
- always @ (bit_rate_value or dcd_correct) begin // Generate tap number to be used for input bit rate (300 MHz ref clock)
- if ((bit_rate_value > 16'h2030 && dcd_correct == 1'b0) || (bit_rate_value > 16'h1845 && dcd_correct == 1'b1)) begin bt_val <= 5'h0A ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h1836 && dcd_correct == 1'b0) || (bit_rate_value > 16'h1669 && dcd_correct == 1'b1)) begin bt_val <= 5'h0B ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h1675 && dcd_correct == 1'b0) || (bit_rate_value > 16'h1523 && dcd_correct == 1'b1)) begin bt_val <= 5'h0C ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h1541 && dcd_correct == 1'b0) || (bit_rate_value > 16'h1401 && dcd_correct == 1'b1)) begin bt_val <= 5'h0D ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h1426 && dcd_correct == 1'b0) || (bit_rate_value > 16'h1297 && dcd_correct == 1'b1)) begin bt_val <= 5'h0E ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h1328 && dcd_correct == 1'b0) || (bit_rate_value > 16'h1207 && dcd_correct == 1'b1)) begin bt_val <= 5'h0F ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h1242 && dcd_correct == 1'b0) || (bit_rate_value > 16'h1129 && dcd_correct == 1'b1)) begin bt_val <= 5'h10 ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h1167 && dcd_correct == 1'b0) || (bit_rate_value > 16'h1061 && dcd_correct == 1'b1)) begin bt_val <= 5'h11 ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h1100 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0999 && dcd_correct == 1'b1)) begin bt_val <= 5'h12 ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h1040 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0946 && dcd_correct == 1'b1)) begin bt_val <= 5'h13 ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h0987 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0897 && dcd_correct == 1'b1)) begin bt_val <= 5'h14 ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h0939 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0853 && dcd_correct == 1'b1)) begin bt_val <= 5'h15 ; del_mech <= 1'b1 ; end
- else if ((bit_rate_value > 16'h0895 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0814 && dcd_correct == 1'b1)) begin bt_val <= 5'h16 ; del_mech <= 1'b0 ; end
- else if ((bit_rate_value > 16'h0855 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0777 && dcd_correct == 1'b1)) begin bt_val <= 5'h17 ; del_mech <= 1'b0 ; end
- else if ((bit_rate_value > 16'h0819 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0744 && dcd_correct == 1'b1)) begin bt_val <= 5'h18 ; del_mech <= 1'b0 ; end
- else if ((bit_rate_value > 16'h0785 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0714 && dcd_correct == 1'b1)) begin bt_val <= 5'h19 ; del_mech <= 1'b0 ; end
- else if ((bit_rate_value > 16'h0754 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0686 && dcd_correct == 1'b1)) begin bt_val <= 5'h1A ; del_mech <= 1'b0 ; end
- else if ((bit_rate_value > 16'h0726 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0660 && dcd_correct == 1'b1)) begin bt_val <= 5'h1B ; del_mech <= 1'b0 ; end
- else if ((bit_rate_value > 16'h0700 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0636 && dcd_correct == 1'b1)) begin bt_val <= 5'h1C ; del_mech <= 1'b0 ; end
- else if ((bit_rate_value > 16'h0675 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0614 && dcd_correct == 1'b1)) begin bt_val <= 5'h1D ; del_mech <= 1'b0 ; end
- else if ((bit_rate_value > 16'h0652 && dcd_correct == 1'b0) || (bit_rate_value > 16'h0593 && dcd_correct == 1'b1)) begin bt_val <= 5'h1E ; del_mech <= 1'b0 ; end
- else begin bt_val <= 5'h1F ; del_mech <= 1'b0 ; end // min bit rate 631 Mbps
- end
- end
复制代码
4.5.2DDR ISERDESE数据恢复Gearbox实现4:7转换,代码在gearbox_4_to_7.v中Gearbox中使用到了BRAM - module gearbox_4_to_7 (input_clock, output_clock, datain, reset, jog, reset_out, dataout) ;
- parameter integer D = 8 ; // Parameter to set the number of data lines
- input input_clock ; // high speed clock input
- input output_clock ; // low speed clock input
- input [D*4-1:0] datain ; // data inputs
- input reset ; // Reset line
- input jog ; // jog input, slips by 4 bits
- output reg [1:0] reset_out ; // reset out signal
- output reg [D*7-1:0] dataout ; // data outputs
-
- reg [3:0] read_addra ;
- reg [3:0] read_addrb ;
- reg [3:0] read_addrc ;
- reg [3:0] write_addr ;
- reg read_enable ;
- reg read_enable_dom_ch ;
- wire [D*4-1:0] ramouta ;
- wire [D*4-1:0] ramoutb ;
- wire [D*4-1:0] ramoutc ;
- reg local_reset ;
- reg local_reset_dom_ch ;
- reg [1:0] mux ;
- wire [D*4-1:0] dummy ;
- reg jog_int ;
- reg rst_int ;
- genvar i ;
- always @ (posedge input_clock) begin // generate local sync reset
- if (reset == 1'b1) begin
- local_reset <= 1'b1 ;
- reset_out[0] <= 1'b1 ;
- end else begin
- local_reset <= 1'b0 ;
- reset_out[0] <= 1'b0 ;
- end
- end
- always @ (posedge input_clock) begin // Gearbox input - 4 bit data at input clock frequency
- if (local_reset == 1'b1) begin
- write_addr <= 4'h0 ;
- read_enable <= 1'b0 ;
- end
- else begin
- if (write_addr == 4'hD) begin
- write_addr <= 4'h0 ;
- end
- else begin
- write_addr <= write_addr + 4'h1 ;
- end
- if (write_addr == 4'h3) begin
- read_enable <= 1'b1 ;
- end
- end
- end
- always @ (posedge output_clock) begin
- read_enable_dom_ch <= read_enable ;
- local_reset_dom_ch <= local_reset ;
- end
- always @ (posedge output_clock) begin // Gearbox output - 10 bit data at output clock frequency
- reset_out[1] <= rst_int ;
- if (local_reset_dom_ch == 1'b1 || read_enable_dom_ch == 1'b0) begin
- rst_int <= 1'b1 ;
- end
- else begin
- rst_int <= 1'b0 ;
- end
- if (reset_out[1] == 1'b1) begin
- read_addra <= 4'h0 ;
- read_addrb <= 4'h1 ;
- read_addrc <= 4'h2 ;
- jog_int <= 1'b0 ;
- end
- else begin
- case (jog_int)
- 1'b0 : begin
- case (read_addra)
- 4'h0 : begin read_addra <= 4'h1 ; read_addrb <= 4'h2 ; read_addrc <= 4'h3 ; mux <= 2'h1 ; end
- 4'h1 : begin read_addra <= 4'h3 ; read_addrb <= 4'h4 ; read_addrc <= 4'h5 ; mux <= 2'h2 ; end
- 4'h3 : begin read_addra <= 4'h5 ; read_addrb <= 4'h6 ; read_addrc <= 4'h7 ; mux <= 2'h3 ; end
- 4'h5 : begin read_addra <= 4'h7 ; read_addrb <= 4'h8 ; read_addrc <= 4'h9 ; mux <= 2'h0 ; end
- 4'h7 : begin read_addra <= 4'h8 ; read_addrb <= 4'h9 ; read_addrc <= 4'hA ; mux <= 2'h1 ; end
- 4'h8 : begin read_addra <= 4'hA ; read_addrb <= 4'hB ; read_addrc <= 4'hC ; mux <= 2'h2 ; end
- 4'hA : begin read_addra <= 4'hC ; read_addrb <= 4'hD ; read_addrc <= 4'hD ; mux <= 2'h3 ; jog_int <= jog ; end
- default : begin read_addra <= 4'h0 ; read_addrb <= 4'h1 ; read_addrc <= 4'h2 ; mux <= 2'h0 ; end
- endcase
- end
- 1'b1 : begin
- case (read_addra)
- 4'h1 : begin read_addra <= 4'h2 ; read_addrb <= 4'h3 ; read_addrc <= 4'h4 ; mux <= 2'h1 ; end
- 4'h2 : begin read_addra <= 4'h4 ; read_addrb <= 4'h5 ; read_addrc <= 4'h6 ; mux <= 2'h2 ; end
- 4'h4 : begin read_addra <= 4'h6 ; read_addrb <= 4'h7 ; read_addrc <= 4'h8 ; mux <= 2'h3 ; end
- 4'h6 : begin read_addra <= 4'h8 ; read_addrb <= 4'h9 ; read_addrc <= 4'hA ; mux <= 2'h0 ; end
- 4'h8 : begin read_addra <= 4'h9 ; read_addrb <= 4'hA ; read_addrc <= 4'hB ; mux <= 2'h1 ; end
- 4'h9 : begin read_addra <= 4'hB ; read_addrb <= 4'hC ; read_addrc <= 4'hD ; mux <= 2'h2 ; end
- 4'hB : begin read_addra <= 4'hD ; read_addrb <= 4'h0 ; read_addrc <= 4'h1 ; mux <= 2'h3 ; jog_int <= jog ; end
- default : begin read_addra <= 4'h1 ; read_addrb <= 4'h2 ; read_addrc <= 4'h3 ; mux <= 2'h0 ; end
- endcase
- end
- endcase
- end
- end
- generate
- for (i = 0 ; i <= D-1 ; i = i+1)
- begin : loop0
- always @ (posedge output_clock) begin
- case (mux)
- 2'h0 : dataout[7*i+6:7*i] <= { ramoutb[4*i+2:4*i+0], ramouta[4*i+3:4*i+0]} ;
- 2'h1 : dataout[7*i+6:7*i] <= {ramoutc[4*i+1:4*i+0], ramoutb[4*i+3:4*i+0], ramouta[4*i+3]} ;
- 2'h2 : dataout[7*i+6:7*i] <= {ramoutc[4*i+0], ramoutb[4*i+3:4*i+0], ramouta[4*i+3:4*i+2]} ;
- default : dataout[7*i+6:7*i] <= { ramoutb[4*i+3:4*i+0], ramouta[4*i+3:4*i+1]} ;
- endcase
- end
- end
- endgenerate
-
- // Data gearboxes
- generate
- for (i = 0 ; i <= D*2-1 ; i = i+1)
- begin : loop2
- RAM32M ram_inst (
- .DOA (ramouta[2*i+1:2*i]),
- .DOB (ramoutb[2*i+1:2*i]),
- .DOC (ramoutc[2*i+1:2*i]),
- .DOD (dummy[2*i+1:2*i]),
- .ADDRA ({1'b0, read_addra}),
- .ADDRB ({1'b0, read_addrb}),
- .ADDRC ({1'b0, read_addrc}),
- .ADDRD ({1'b0, write_addr}),
- .DIA (datain[2*i+1:2*i]),
- .DIB (datain[2*i+1:2*i]),
- .DIC (datain[2*i+1:2*i]),
- .DID (dummy[2*i+1:2*i]),
- .WE (1'b1),
- .WCLK (input_clock));
- end
- endgenerate
- endmodule
复制代码
相对于SDR模式下只要完成1:7的时钟设计,对于DDR需要完成rxclk:rxclk_d4:pixel_clk=3.5:1.75:1的时钟设计。rxclk:rxclk_d4是完成1:4的转换。然后rxclk_d4:pixel_clk完成4:7的转换。 通过控制每次减1个tap,减少idelay延迟,并且通过ISERDESE检测时钟的跳变。当第一次跳变发生的时候,代表检测到了时钟的跳变沿,具体代码如下: - always @ (posedge pixel_clk) begin // retiming
- clk_iserdes_data_d <= clk_iserdes_data ;
- if ((clk_iserdes_data != clk_iserdes_data_d) && (clk_iserdes_data != 7'h00) && (clk_iserdes_data != 7'h7F)) begin
- data_different <= 1'b1 ;
- end
- else begin
- data_different <= 1'b0 ;
- end
- end
-
- always @ (posedge rxclk_d4) begin // clock delay shift state machine
- not_rx_mmcm_lckd_intd4 <= ~(mmcm_locked & idelay_rdy) ;
- rstcserdes <= not_rx_mmcm_lckd_intd4 | rst_iserdes ;
- if (not_rx_mmcm_lckd_intd4 == 1'b1) begin
- scount <= 6'h00 ;
- state2 <= 0 ;
- state2_count <= 5'h00 ;
- locked_out <= 1'b0 ;
- chfoundc <= 1'b1 ;
- c_delay_in <= bt_val ; // Start the delay line at the current bit period
- rst_iserdes <= 1'b0 ;
- c_loop_cnt <= 2'b00 ;
- end
- else begin
- if (scount[5] == 1'b0) begin
- scount <= scount + 6'h01 ;
- end
- state2_count <= state2_count + 5'h01 ;
- data_different_dom_ch <= data_different ;
- if (chfoundc == 1'b1) begin
- chfound <= 1'b0 ;
- end
- else if (chfound == 1'b0 && data_different_dom_ch == 1'b1) begin
- chfound <= 1'b1 ;
- end
- if ((state2_count == 5'h1F && scount[5] == 1'b1)) begin
- case(state2)
- 0 : begin // decrement delay and look for a change
- if (chfound == 1'b1 || (c_loop_cnt == 2'b11 && c_delay_in == 5'h00)) begin // quit loop if we've been around a few times
- chfoundc <= 1'b1 ;
- state2 <= 1 ;
- end
- else begin
- chfoundc <= 1'b0 ;
- if (c_delay_in != 5'h00) begin // check for underflow
- c_delay_in <= c_delay_in - 5'h01 ;
- end
- else begin
- c_delay_in <= bt_val ;
- c_loop_cnt <= c_loop_cnt + 2'b01 ;
- end
- end
- end
- 1 : begin // add half a bit period using input information
- state2 <= 2 ;
- if (c_delay_in < {1'b0, bt_val[4:1]}) begin // choose the lowest delay value to minimise jitter
- c_delay_in_target <= c_delay_in + {1'b0, bt_val[4:1]} ;
- end
- else begin
- c_delay_in_target <= c_delay_in - {1'b0, bt_val[4:1]} ;
- end
- end
- 2 : begin
- if (c_delay_in == c_delay_in_target) begin
- state2 <= 3 ;
- end
- else begin
- if (c_delay_in_ud == 1'b1) begin // move gently to end position to stop MMCM unlocking
- c_delay_in <= c_delay_in + 5'h01 ;
- c_delay_in_ud <= 1'b1 ;
- end
- else begin
- c_delay_in <= c_delay_in - 5'h01 ;
- c_delay_in_ud <= 1'b0 ;
- end
- end
- end
- 3 : begin rst_iserdes <= 1'b1 ; state2 <= 4 ; end // remove serdes reset
- default : begin // issue locked out signal
- rst_iserdes <= 1'b0 ; locked_out <= 1'b1 ;
- end
- endcase
- end
- end
- end
复制代码
时钟部分解析并且得出idelay的延迟参数仿真结果,如下分析过程可以参考SDR的模式分析
数据部分的分析不再详细描述和SDR方式类似,正确解析输出和时钟的最终结果。
由于本文以官方代码讲解为主,本文的方法也是适用于最新的ultrascale 和ultrascale+ FPGA。但是ultrascale 和ultrascale+ FPGA 使用的是serdese3 以及 idelaye3 ,具体的细节参数上有差异,笔者计划后面在本文的基础上,增加从serdese2移植到serdese3的内容。
|