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的控制,可以手動調節,也可以用自動算法。(參考xapp254)
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_down 《= 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(直通)
-
FPGA
+關注
關注
1629文章
21736瀏覽量
603420 -
lvds
+關注
關注
2文章
1043瀏覽量
65817 -
ADC接口
+關注
關注
0文章
5瀏覽量
3082
發布評論請先 登錄
相關推薦
評論