LVDS高速ADC接口, xilinx fpga实现
2017-08-05 19:40
936 查看
写在前面:最近一段时间在总结以前做过的项目,都是关于FPGA的接口技术和设计方法,让技术沉淀下来。会陆陆续续发表到博客,希望对大家理解FPGA有一点点帮助。
使用的AD芯片是ADI的AD9653,125M16bit高精度高速ADC,用到的采样速率是80M。其SPI配置会单独开一篇来讲,SPI配置里面有个大坑,本来以为调好了的,后来又发现了问题,调了三天才定位到问题在哪,这就是硬件的魅力(坑爹)所在了吧。这里主要介绍FPGA的接收部分。
有几点需要注意:
0 , 可以看出分成三种信号,数据采样时钟DCLK,帧同步信号FCLK,和输入数据DATA
1,输入数据采样时钟默认是已经对齐了输入数据的中点,但帧时钟是和数据字节边缘对齐的。
2,使用Iserdes接收数据,Idelay调整时钟延迟。
默认R_delay_cnt=0时,可以看到输入的正弦波形很乱
慢慢的增加R_delay_cnt,当R_delay_cnt=12时,开始出现稳定的正弦波,实验发现R_delay_cnt=14,15,16时恰好采到时钟的边缘,也就是跟输入的原始时钟对齐了,可以看到采到边缘是allign_word一直在跳变,有的是0,有的是1。一直到R_delay_cnt=18,正弦波都很稳定。有效窗口可以准确计算出来,200M的Idelay参考时钟,78ps/tap。7tap*78ps=546ps。说明数据的有效窗口很小,毕竟是320M的DDR,半个周期都才1.56ns.
继续增加R_delay_cnt,当R_delay_cnt=20时,正弦波又变得不规则了。
最后取R_delay_cnt=15,可以在代码里面写死。
有了上面的手动调节算法,自动训练的思路也很简单了。上电复位后R_delay_cnt一直自加,记下最后一个全0和第一个全1的值,取中点。这里只考虑了一种情况,还可能是从全1到全0的情况。代码如下
利用FC找到字节边界的代码如下
数据采样代码和上面的差不多
当数据率不是很高的IDDR数据,使用DDR替代Iserdes接收。IDDR和Iserdes使用的资源相同(待验证)
HR Bank真实的器件如下,一对IOB,可单独使用,可差分使用。后面的资源从上到下依次是ISERDES(ILOGIC),IDELAY,OLOGIC(OSERDES),ILOGIC,IDELAY,OLOGIC。(ILOGIC可作为IDDR,OLOGIC可作为ODDR)。左上角的是一个clock region(如X0Y2)的中间分布的四个BUFIO和BUGR(局部时钟驱动,局部时钟分频,二者延时相等)。后面是一个IDELAYCTRL。
下面分别详细介绍:
IDEALY,
经过IDELAY必须要经过ISERDES,可直通。
ISERDES,
ISERDES和ILOGIC使用相同的资源,可互换
ILOGIC,
OLOGIC,
OSERDES,和OLOGIC使用相同的资源,可互换
功能描述
• Edge triggered D type flip-flop(FF)
• DDR mode (SAME_EDGE or OPPOSITE_EDGE)
• Level sensitive latch(Latch)
• Asynchronous/combinatorial(直通)
LVDS
即Low-Voltage Differential Signaling。FPGA的selecteIO非常强大,支持各种IO接口标准,电压电流都可以配置。其接口速率可以达到几百M甚至上千M。使用lvds来接收高速ADC产生的数据会很方便。像ISERDES,IDDR,IDELAY,OSERDES,ODDR这种资源在FPGA的IOB中多得是(每个IO都对应有,最后具体介绍),根本不担心使用。最近刚在项目中用到,提供一个思路,具体的器件使用参考FPGA手册。使用的AD芯片是ADI的AD9653,125M16bit高精度高速ADC,用到的采样速率是80M。其SPI配置会单独开一篇来讲,SPI配置里面有个大坑,本来以为调好了的,后来又发现了问题,调了三天才定位到问题在哪,这就是硬件的魅力(坑爹)所在了吧。这里主要介绍FPGA的接收部分。
接收ADC数据的时序图,
有几点需要注意:
0 , 可以看出分成三种信号,数据采样时钟DCLK,帧同步信号FCLK,和输入数据DATA
1,输入数据采样时钟默认是已经对齐了输入数据的中点,但帧时钟是和数据字节边缘对齐的。
2,使用Iserdes接收数据,Idelay调整时钟延迟。
1,对数据采样时钟的处理如下
通过控制延时,使得CLK和经过IBUFDS的BitClk对齐,从而消除IBUFIO和BUFR还有net的延时。这样所有的输入信号都只经过了一个IBUFDS,延时相等。对Idelay的控制,可以手动调节,也可以用自动算法。(参考xapp524)IBUFDS #( .DIFF_TERM("TRUE"), // Differential Termination .IBUF_LOW_PWR("TRUE"), // Low power="TRUE", Highest performance="FALSE" .IOSTANDARD("DEFAULT") // Specify the input I/O standard ) IBUFDS_inst10 ( .O(W0_dc_clk), // Buffer output .I(I_AD_FPGA_DC_p), // Diff_p buffer input (connect directly to top-level port) .IB(I_AD_FPGA_DC_n) // Diff_n buffer input (connect directly to top-level port) ); wire W_delay_rdy; wire [4:0] W_delay_cnt; wire [7:0] W_allign_word; vio_0 vio_u ( .clk(W_fc_clk), // input wire clk .probe_in0(W_delay_rdy), // input wire [0 : 0] probe_in0 .probe_in1(W_allign_word),// input wire [7 : 0] probe_in1 .probe_out0(W_delay_cnt) // output wire [4 : 0] probe_out0 ); (* IODELAY_GROUP = "delay1" *) IDELAYCTRL IDELAYCTRL_inst1 ( .RDY(W_delay_rdy), // 1-bit output: Ready output .REFCLK(I_ref_clk_200m), // 1-bit input: Reference clock input .RST(~I_reset_n) // 1-bit input: Active high reset input ); (* IODELAY_GROUP = "delay1" *) IDELAYE2 #( .CINVCTRL_SEL("FALSE"), // Enable dynamic clock inversion (FALSE, TRUE) .DELAY_SRC("IDATAIN"), // Delay input (IDATAIN, DATAIN) .HIGH_PERFORMANCE_MODE("TRUE"), // Reduced jitter ("TRUE"), Reduced power ("FALSE") .IDELAY_TYPE("VAR_LOAD"), // 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("CLOCK") // DATA, CLOCK input signal ) IDELAYE2_inst1 ( .CNTVALUEOUT(), // 5-bit output: Counter value output .DATAOUT(W1_dc_clk), // 1-bit output: Delayed data output .C(W_fc_clk), // 1-bit input: Clock input .CE(1'b0), // 1-bit input: Active high enable increment/decrement input .CINVCTRL(1'b0), // 1-bit input: Dynamic clock inversion input .CNTVALUEIN(W_delay_cnt), // 5-bit input: Counter value input .DATAIN(1'b0), // 1-bit input: Internal delay data input .IDATAIN(W0_dc_clk), // 1-bit input: Data input from the I/O .INC(1'b0), // 1-bit input: Increment / Decrement tap delay input .LD(1'b1), // 1-bit input: Load IDELAY_VALUE input .LDPIPEEN(1'b0), // 1-bit input: Enable PIPELINE register to load data input .REGRST(1'b0) // 1-bit input: Active-high reset tap-delay input ); BUFIO BUFIO_p ( .O(W_dc_clk), // 1-bit output: Clock output (connect to I/O clock loads). .I(W2_dc_clk) // 1-bit input: Clock input (connect to an IBUF or BUFMR). ); BUFR #( .BUFR_DIVIDE("4"), // Values: "BYPASS, 1, 2, 3, 4, 5, 6, 7, 8" .SIM_DEVICE("7SERIES")// Must be set to "7SERIES" ) BUFR_inst ( .O(W_fc_clk), // 1-bit output: Clock output port .CE(1'b1), // 1-bit input: Active high, clock enable (Divided modes only) .CLR(1'b0), // 1-bit input: Active high, asynchronous clear (Divided modes only) .I(W2_dc_clk) // 1-bit input: Clock buffer input driven by an IBUF, MMCM or local interconnect ); ISERDESE2 #( .DATA_RATE("SDR"), // DDR, SDR .DATA_WIDTH(8), // 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(1'b0), .INIT_Q2(1'b0), .INIT_Q3(1'b0), .INIT_Q4(1'b0), .INTERFACE_TYPE("NETWORKING"),// MEMORY, MEMORY_DDR3, MEMORY_QDR, NETWORKING, OVERSAMPLE .IOBDELAY("IBUF"), // 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(1'b0), .SRVAL_Q2(1'b0), .SRVAL_Q3(1'b0), .SRVAL_Q4(1'b0) ) ISERDESE2_inst0 ( .O(W2_dc_clk), // 1-bit output: Combinatorial output .Q1(W_allign_word[0]), // Q1 - Q8: 1-bit (each) output: Registered data outputs .Q2(W_allign_word[1]), .Q3(W_allign_word[2]), .Q4(W_allign_word[3]), .Q5(W_allign_word[4]), .Q6(W_allign_word[5]), .Q7(W_allign_word[6]), .Q8(W_allign_word[7]), .SHIFTOUT1(), .SHIFTOUT2(), .BITSLIP(), .CE1(1'b1), .CE2(1'b1), .CLKDIVP(1'b0), // 1-bit input: TBD .CLK(W_dc_clk), // 1-bit input: High-speed clock .CLKB(~W_dc_clk), // 1-bit input: High-speed secondary clock .CLKDIV(W_fc_clk), // 1-bit input: Divided clock .OCLK(1'b0), // 1-bit input: High speed output clock used when INTERFACE_TYPE="MEMORY" .DYNCLKDIVSEL(1'b0), // 1-bit input: Dynamic CLKDIV inversion .DYNCLKSEL(1'b0), // 1-bit input: Dynamic CLK/CLKB inversion .D(W0_dc_clk), // 1-bit input: Data input .DDLY(W1_dc_clk), // 1-bit input: Serial data from IDELAYE2 .OFB(1'b0), // 1-bit input: Data feedback from OSERDESE2 .OCLKB(1'b0), // 1-bit input: High speed negative edge output clock .RST(~I_reset_n), // 1-bit input: Active high asynchronous reset .SHIFTIN1(1'b0), .SHIFTIN2(1'b0) );
1.1手动调节对齐
首先来看看手动调节算法,用vivado的vio可以很方便的输入输出,可手动在线修改观察现象,对后面的自动训练算法也有一定的启发作用。默认R_delay_cnt=0时,可以看到输入的正弦波形很乱
慢慢的增加R_delay_cnt,当R_delay_cnt=12时,开始出现稳定的正弦波,实验发现R_delay_cnt=14,15,16时恰好采到时钟的边缘,也就是跟输入的原始时钟对齐了,可以看到采到边缘是allign_word一直在跳变,有的是0,有的是1。一直到R_delay_cnt=18,正弦波都很稳定。有效窗口可以准确计算出来,200M的Idelay参考时钟,78ps/tap。7tap*78ps=546ps。说明数据的有效窗口很小,毕竟是320M的DDR,半个周期都才1.56ns.
继续增加R_delay_cnt,当R_delay_cnt=20时,正弦波又变得不规则了。
最后取R_delay_cnt=15,可以在代码里面写死。
1.2自动训练算法
既然有了手动调节的算法,为什么还要用自动训练对齐的算法呢?在高低温测试的时候,器件的延迟会受温度的影响发生变化,特别是在时钟频率很高,数据有效窗口很小的时候,这时候就需要能够动态的改变R_delay_cnt的值去自适应delay的变化,增加了鲁棒性。有了上面的手动调节算法,自动训练的思路也很简单了。上电复位后R_delay_cnt一直自加,记下最后一个全0和第一个全1的值,取中点。这里只考虑了一种情况,还可能是从全1到全0的情况。代码如下
wire W_delay_rdy ; reg [4:0] R_delay_cnt ; reg R_allign_down; reg [4:0] R_cnt_low ; reg [4:0] R_cnt_high ; reg [5:0] R_cnt_sum ; wire [4:0] W_half_cnt ; wire [4:0] W_delay_cnt ; wire [7:0] W_allign_word; //auto allign fsm //00000000->11111111 // | // half always @(posedge W_fc_clk or negedge I_reset_n) begin if(~I_reset_n) begin R_allign_do eb8c wn <= 0; end else if(&R_cnt_tx) begin R_allign_down <= 0; end else if(W_allign_word==8'hff) begin R_allign_down <= 1; end end always @(posedge W_fc_clk or negedge I_reset_n) begin if(~I_reset_n) begin R_delay_cnt <= 0; end else if(&R_cnt_tx) begin R_delay_cnt <= 5'b1; end else if(~R_allign_down) begin R_delay_cnt <= R_delay_cnt + 1'b1; end end always @(posedge W_fc_clk or negedge I_reset_n) begin if(~I_reset_n) begin R_cnt_low <= 0; end else if(W_allign_word==8'h00) begin R_cnt_low <= R_delay_cnt; end end always @(posedge W_fc_clk or negedge I_reset_n) begin if(~I_reset_n) begin R_cnt_high <= 0; end else if(W_allign_word==8'hff) begin R_cnt_high <= R_delay_cnt; end end always @(posedge W_fc_clk or negedge I_reset_n) begin if(~I_reset_n) begin R_cnt_sum <= 0; end else if(R_allign_down) begin R_cnt_sum <= {{R_cnt_low[4],R_cnt_low} + {R_cnt_high[4],R_cnt_high}}; end end assign W_half_cnt = R_cnt_sum[5:1]; assign W_delay_cnt = R_allign_down? W_half_cnt: R_delay_cnt;
2,对帧同步信号和数据的处理
用上面产生的数据采样时钟同时去采样FCLK和DATA,使用Iserdes可以1:8进行串并转换。但是我们不知道字节的边界在哪里,所以要使用一个bit_slip对串转并的结果进行移位,移位的同时检测FCLK转换的输出,当输出是8’b11110000的时候就停止移位。利用FC找到字节边界的代码如下
//-------------------------------------FC handle bit_slip------------------------------------- IBUFDS #( .DIFF_TERM("TRUE"), // Differential Termination .IBUF_LOW_PWR("TRUE"), // Low power="TRUE", Highest performance="FALSE" .IOSTANDARD("DEFAULT") // Specify the input I/O standard ) IBUFDS_inst9 ( .O(W_fc_refclk), // Buffer output .I(I_AD_FPGA_FC_p), // Diff_p buffer input (connect directly to top-level port) .IB(I_AD_FPGA_FC_n) // Diff_n buffer input (connect directly to top-level port) ); ISERDESE2 #( .DATA_RATE("DDR"), // DDR, SDR .DATA_WIDTH(8), // 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(1'b0), .INIT_Q2(1'b0), .INIT_Q3(1'b0), .INIT_Q4(1'b0), .INTERFACE_TYPE("NETWORKING"),// 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(1'b0), .SRVAL_Q2(1'b0), .SRVAL_Q3(1'b0), .SRVAL_Q4(1'b0) ) ISERDESE2_inst9 ( .O(), // 1-bit output: Combinatorial output .Q1(W_fc_patten[0]), // Q1 - Q8: 1-bit (each) output: Registered data outputs .Q2(W_fc_patten[1]), .Q3(W_fc_patten[2]), .Q4(W_fc_patten[3]), .Q5(W_fc_patten[4]), .Q6(W_fc_patten[5]), .Q7(W_fc_patten[6]), .Q8(W_fc_patten[7]), .SHIFTOUT1(), .SHIFTOUT2(), .BITSLIP(R_bit_slip), .CE1(1'b1), .CE2(1'b1), .CLKDIVP(1'b0), // 1-bit input: TBD .CLK(W_dc_clk), // 1-bit input: High-speed clock .CLKB(~W_dc_clk), // 1-bit input: High-speed secondary clock .CLKDIV(W_fc_clk), // 1-bit input: Divided clock .OCLK(1'b0), // 1-bit input: High speed output clock used when INTERFACE_TYPE="MEMORY" .DYNCLKDIVSEL(1'b0), // 1-bit input: Dynamic CLKDIV inversion .DYNCLKSEL(1'b0), // 1-bit input: Dynamic CLK/CLKB inversion .D(W_fc_refclk), // 1-bit input: Data input .DDLY(1'b0), // 1-bit input: Serial data from IDELAYE2 .OFB(1'b0), // 1-bit input: Data feedback from OSERDESE2 .OCLKB(1'b0), // 1-bit input: High speed negative edge output clock .RST(~I_reset_n), // 1-bit input: Active high asynchronous reset .SHIFTIN1(1'b0), .SHIFTIN2(1'b0) ); always @(posedge W_fc_clk or negedge I_reset_n) begin if(~I_reset_n) begin R_bit_slip <= 0; R_wait <= 0; end else begin if (R_wait==2'd3 && W_fc_patten!=8'b11110000) begin R_bit_slip <= 1; R_wait <= 2'd1; end else begin R_bit_slip <= 0; R_wait <= R_wait + 1'd1; end end end
数据采样代码和上面的差不多
IBUFDS #( .DIFF_TERM("TRUE"), // Differential Termination .IBUF_LOW_PWR("TRUE"), // Low power="TRUE", Highest performance="FALSE" .IOSTANDARD("DEFAULT") // Specify the input I/O standard ) IBUFDS_inst1 ( .O(W_data_in1[0]), // Buffer output .I(I_ad_lvds_d0_p[0]), // Diff_p buffer input (connect directly to top-level port) .IB(I_ad_lvds_d0_n[0]) // Diff_n buffer input (connect directly to top-level port) ); ISERDESE2 #( .DATA_RATE("DDR"), // DDR, SDR .DATA_WIDTH(8), // 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(1'b0), .INIT_Q2(1'b0), .INIT_Q3(1'b0), .INIT_Q4(1'b0), .INTERFACE_TYPE("NETWORKING"),// 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(1'b0), .SRVAL_Q2(1'b0), .SRVAL_Q3(1'b0), .SRVAL_Q4(1'b0) ) ISERDESE2_inst1 ( .O(), // 1-bit output: Combinatorial output .Q1(W_ad_data1[0]), // Q1 - Q8: 1-bit (each) output: Registered data outputs .Q2(W_ad_data1[1]), .Q3(W_ad_data1[2]), .Q4(W_ad_data1[3]), .Q5(W_ad_data1[4]), .Q6(W_ad_data1[5]), .Q7(W_ad_data1[6]), .Q8(W_ad_data1[7]), .SHIFTOUT1(), .SHIFTOUT2(), .BITSLIP(R_bit_slip), .CE1(1'b1), .CE2(1'b1), .CLKDIVP(1'b0), // 1-bit input: TBD .CLK(W_dc_clk), // 1-bit input: High-speed clock .CLKB(~W_dc_clk), // 1-bit input: High-speed secondary clock .CLKDIV(W_fc_clk), // 1-bit input: Divided clock .OCLK(1'b0), // 1-bit input: High speed output clock used when INTERFACE_TYPE="MEMORY" .DYNCLKDIVSEL(1'b0), // 1-bit input: Dynamic CLKDIV inversion .DYNCLKSEL(1'b0), // 1-bit input: Dynamic CLK/CLKB inversion .D(W_data_in1[0]), // 1-bit input: Data input .DDLY(1'b0), // 1-bit input: Serial data from IDELAYE2 .OFB(1'b0), // 1-bit input: Data feedback from OSERDESE2 .OCLKB(1'b0), // 1-bit input: High speed negative edge output clock .RST(~I_reset_n), // 1-bit input: Active high asynchronous reset .SHIFTIN1(1'b0), .SHIFTIN2(1'b0) );
当数据率不是很高的IDDR数据,使用DDR替代Iserdes接收。IDDR和Iserdes使用的资源相同(待验证)
附:用到的FPGA资源详解
HR Bank真实的器件如下,一对IOB,可单独使用,可差分使用。后面的资源从上到下依次是ISERDES(ILOGIC),IDELAY,OLOGIC(OSERDES),ILOGIC,IDELAY,OLOGIC。(ILOGIC可作为IDDR,OLOGIC可作为ODDR)。左上角的是一个clock region(如X0Y2)的中间分布的四个BUFIO和BUGR(局部时钟驱动,局部时钟分频,二者延时相等)。后面是一个IDELAYCTRL。
下面分别详细介绍:
IDEALY,
经过IDELAY必须要经过ISERDES,可直通。
ISERDES,
ISERDES和ILOGIC使用相同的资源,可互换
ILOGIC,
OLOGIC,
OSERDES,和OLOGIC使用相同的资源,可互换
功能描述
• Edge triggered D type flip-flop(FF)
• DDR mode (SAME_EDGE or OPPOSITE_EDGE)
• Level sensitive latch(Latch)
• Asynchronous/combinatorial(直通)
相关文章推荐
- (Xilinx)FPGA中LVDS差分高速传输的实现
- (Xilinx)FPGA中LVDS差分高速传输的实现
- (Xilinx)FPGA中LVDS差分高速传输的实现
- FPGA中LVDS差分高速传输的实现
- DSP与FPGA高速接口实现
- AM5728通过GPMC接口与FPGA高速数据通信实现
- (转)FPGA中LVDS差分高速传输的实现
- -02-Xilinx的SerDes接口介绍【Xilinx-LVDS读写功能实现】
- Xilinx-7Series-FPGA高速收发器使用学习—概述与参考时钟篇
- FPGA通过SPI对ADC配置简介(五)-----Verilog实现3线SPI配置
- FPGA:实现串行接口 RS232
- Xilinx 7 Series FPGA时钟网络的区别(BUFG,BUFGR,BUFIO)以及ISE实现流程软件工作内容
- 利用FPGA实现摄像机传感器接口
- 从Xilinx FFT IP核到FPGA实现OFDM
- .Net WebAPI 高速下载文件接口实现
- Xilinx FPGA ML605 开发笔记——简单中断实现
- .Net WebAPI 高速下载文件接口实现
- xilinx fpga学习笔记7:实现属性参数的功能
- 175-基于TI DSP TMS320C6455、Xilinx V5 FPGA XC5VSX95T的高速数据处理核心板
- 一步一步开始FPGA逻辑设计 - 高速接口之PCIe