zoukankan      html  css  js  c++  java
  • FPGA高速ADC接口实战——250MSPS采样率ADC9481

    一、前言

      最近忙于硕士毕业设计和论文,没有太多时间编写博客,现总结下之前在某个项目中用到的一个高速ADC接口设计部分。ADC这一器件经常用于无线通信、传感、测试测量等领域。目前数字系统对高速数据采集的需求与日俱增,本文使用了米联客的一款速率较高的AD/DA模块ADQ9481来阐述利用FPGA设计高速ADC接口的技术要点。

    二、ADC硬件特性分析

      首先必须通过datasheet分析其核心参数、接口定义和时序要求。ADC9481的采样率为250MSPS,精度8bit。其原理结构图如下:

      主要引脚说明:

      CLK+-:差分时钟输入,信号频率为250MHz

      VIN+-:模拟信号输入,范围是1Vpp

      VREF:电压参考输入/输出,这里使用内部固定参考电压模式

      SENSE:参考模式选择

      D7A~D0A:通道A数字信号输出

       D7B~D0B:通道B数字信号输出

      DCO+-:数字差分时钟输出,信号频率为125MHz

       S1:数据格式选择,该接口电压决定数格式时原码还是补码

      PDWN:低功耗选通

      接下来看看接口时序:

      很容易看出A和B两个数字输出通道是交替输出的,通道A在DCO+上升沿输出,B在DCO-上升沿输出。DCO+-的频率仅是采样率250MHz的一半,也就是降低了对数字系统处理速率的要求。

    三、ADC接口设计

       根据上述时序关系可知,FPGA端需要在DCO+上升沿采集通道B数据,在DCO-上升沿采集通道A数据。并且由于在DCO+-同一变化沿时刻,通道A为前一个数据,因此要注意数据的采集顺序。这类数据采集的普遍做法是将数据存入到RAM中,然后利用本地时钟同步。具体方法是:按照两通道的数据顺序对数据进行拼接,之后缓存到异步FIFO中。本地PLL生成的125MHz时钟作为读侧和后续处理时钟信号。这里就要利用Xilinx FPGA的“原语”中的IBUFDS+BUFG,依次是差分输入缓冲器和全局缓冲器。前者可将差分信号转变为单端信号,后者则可让时钟信号到达FPGA内部逻辑引脚的时延和抖动最小。综上,ADC接口硬件架构如图:

     四、HDL代码编写

       根据前文所述的硬件架构,ADC接口HDL代码如下:

      1 `timescale 1ns / 1ps
      2 
      3 module adc_interface#(parameter WIDTH = 8,
      4                                 FRAME_LEN = 512
      5                                 //WAIT_CYC = 125_000_000//1s = 1000_000_000ns   1000_000_000/8 = 125_000_000
      6               )
      7     (
      8     input                       clk0,     //125MHZ
      9     input                       clk1,
     10     
     11     input       [WIDTH-1:0]     da,
     12     input       [WIDTH-1:0]     db,
     13     output                      adc_pd,//省电模式选择
     14 
     15     output                      pll_ce,
     16     output                      pll_rst_n,
     17     output                      pen,
     18     
     19     input                       user_clk,//125MHZ
     20     input                       rst_n,
     21     input                       en,
     22     output reg [WIDTH*2-1:0]    dout = 0,
     23     output reg                  dout_vld = 0
     24     );
     25 
     26     function integer clogb2 (input integer bit_depth);
     27       begin
     28         for(clogb2=0; bit_depth>0; clogb2=clogb2+1)
     29           bit_depth = bit_depth >> 1;
     30       end
     31     endfunction
     32 
     33     localparam DATA_CNT_W = clogb2(FRAME_LEN-1);
     34 
     35 (*DONT_TOUCH = "true"*)reg setup_flag = 0;
     36 reg [WIDTH-1:0] data_a = 0,data_b = 0;
     37 reg data_a_vld = 0,data_b_vld = 0;
     38 reg wr_en = 0;
     39 reg [WIDTH*2-1:0] wr_data = 0;
     40 reg rd_en = 0;
     41 wire empty;
     42 wire full;
     43 wire [WIDTH*2-1:0] rd_data;
     44 (*DONT_TOUCH = "true"*)wire en_pos;
     45 (*DONT_TOUCH = "true"*)reg [ (DATA_CNT_W-1):0]  data_cnt  =0   ;
     46 wire        add_data_cnt ;
     47 wire        end_data_cnt ;
     48 reg en_r0 = 0,en_r1 = 0,en_r2 = 0,en_r3 = 0;
     49 
     50     assign pll_ce     = 1'b1;
     51     assign pll_rst_n  = 1'b1;
     52     assign adc_pd     = 1'b0;
     53     assign pen        = 1'b1;
     54 
     55     /***************************采集触发**************************************/
     56     //异步处理
     57     always@(posedge clk0)begin
     58         en_r0 <= en;
     59         en_r1 <= en_r0;
     60         en_r2 <= en_r1;
     61         en_r3 <= en_r2;
     62     end
     63 
     64     assign en_pos = en_r2 == 1'b1 && en_r3 == 1'b0;
     65 
     66 always  @(posedge clk0 or negedge rst_n)begin
     67     if(rst_n==1'b0)begin
     68         setup_flag <= 0;
     69     end
     70     else if(end_data_cnt)
     71         setup_flag <= 0;
     72     else if(en_pos)begin
     73         setup_flag <= 1'b1;
     74     end
     75 end
     76 
     77 always @(posedge clk0 or negedge rst_n) begin 
     78     if (rst_n==0) begin
     79         data_cnt <= 0; 
     80     end
     81     else if(add_data_cnt) begin
     82         if(end_data_cnt)
     83             data_cnt <= 0; 
     84         else
     85             data_cnt <= data_cnt+1 ;
     86    end
     87 end
     88 assign add_data_cnt = (setup_flag);
     89 assign end_data_cnt = add_data_cnt  && data_cnt == (FRAME_LEN)-1 ;
     90 
     91 
     92 /***************************clk0(dco_p)采集DB**************************************/
     93     always@(posedge clk0 or negedge rst_n)begin
     94         if(rst_n == 0)
     95             data_b <= 0;
     96         else
     97             data_b <= db;
     98     end
     99 
    100     always@(posedge clk0 or negedge rst_n)begin
    101         if(rst_n == 0)
    102             data_b_vld <= 0;
    103         else if(setup_flag)
    104             data_b_vld <= 1'b1;
    105         else
    106             data_b_vld <= 0;
    107     end
    108 
    109  /****************************clk1(dco_n)采集DA**************************************/
    110     always  @(negedge clk0 or negedge rst_n)begin
    111         if(rst_n==1'b0)begin
    112             data_a <= 0;
    113         end
    114         else begin
    115             data_a <= da;
    116         end
    117     end
    118 
    119     always@(negedge clk0 or negedge rst_n)begin
    120         if(rst_n == 0)begin
    121             data_a_vld <= 0;
    122         end
    123         else if(setup_flag)begin
    124             data_a_vld <= 1'b1;
    125         end
    126         else
    127             data_a_vld <= 0;
    128     end
    129  /****************************FIFO写逻辑**************************************/
    130 //FIFO:width 16bit  depth 16 async
    131 
    132 always  @(negedge clk0 or negedge rst_n)begin
    133     if(rst_n==1'b0)begin
    134         wr_en <= 0;
    135     end
    136     else if(data_a_vld & data_b_vld)begin
    137         wr_en <= 1'b1;
    138     end
    139     else
    140         wr_en <= 0;
    141 end
    142 
    143 always  @(negedge clk0 or negedge rst_n)begin
    144     if(rst_n==1'b0)begin
    145         wr_data <= 0;
    146     end
    147     else begin
    148         wr_data <= {data_b,data_a};//高字节为后一个数据
    149     end
    150 end
    151 
    152     /****************************FIFO读侧逻辑**************************************/
    153     //非空即读
    154     always@(*)begin
    155         if(~empty)
    156             rd_en = 1'b1;
    157         else
    158             rd_en = 0;
    159     end
    160 
    161     always  @(posedge user_clk or negedge rst_n)begin
    162         if(rst_n == 0)
    163             dout <= 0;
    164         else
    165             dout <= rd_data;
    166     end
    167 
    168     always  @(posedge user_clk or negedge rst_n)begin
    169         if(rst_n == 0)
    170             dout_vld <= 0;
    171         else if(rd_en)begin
    172             dout_vld <= 1'b1;
    173         end
    174         else
    175             dout_vld <= 0;
    176     end
    177 
    178 
    179 //FIFO instance 
    180 fifo_generator_2 interface_fifo (
    181   .wr_clk(~clk0),                // input wire wr_clk
    182   .rd_clk(user_clk),                // input wire rd_clk
    183   .din(wr_data),                      // input wire [15 : 0] din
    184   .wr_en(wr_en),                  // input wire wr_en
    185   .rd_en(rd_en),                  // input wire rd_en
    186   .dout(rd_data),                    // output wire [15 : 0] dout
    187   .full(full),                    // output wire full
    188   .empty(empty),                  // output wire empty
    189   .rd_data_count(rd_data_count),  // output wire [3 : 0] rd_data_count
    190   .wr_data_count(wr_data_count)  // output wire [3 : 0] wr_data_count
    191 );
    192 
    193 
    194 endmodule
    adc_interface

      顶层模块代码:

      1 `timescale 1ns / 1ps
      2 
      3 module top#(parameter DATA_W = 8,//改动参数需要重新配置IP核
      4                       CHANNEL_NUM = 2)
      5    (
      6     input                                       dco_p,//125MHZ 
      7     input                                       dco_n,
      8     input       [DATA_W-1:0]                    adc_p1,//通道A
      9     input       [DATA_W-1:0]                    adc_p2,//通道B
     10     output                                      adc_pd,
     11     output                                      pll_ce,
     12     output                                      pll_rst_n,
     13     output                                      pen,
     14     
     15     //user interface signals
     16     
     17     input                                       clk,//100M
     18     input                                       rst_n,
     19     input                                       en//上升沿有效 有效一次则将之后采集到的一帧数据写入到FFT模块进行运算
     20     );
     21 /*********************************parameters*******************************************/
     22     function integer clogb2 (input integer bit_depth);
     23       begin
     24         for(clogb2=0; bit_depth>0; clogb2=clogb2+1)
     25           bit_depth = bit_depth >> 1;
     26       end
     27     endfunction
     28 
     29     localparam  FFT_W = 20,
     30                 FFT_N = 1024,
     31                 DATA_EACH_CHANNEL = FFT_N/CHANNEL_NUM;
     32 
     33     //log2 
     34     localparam DECH_W = clogb2(DATA_EACH_CHANNEL-1);
     35 
     36     
     37 /*********************************variables*******************************************/
     38 genvar ii;
     39 wire clk_out0,locked0;
     40 reg locked0_r0 = 0,locked0_r1 = 0;
     41 wire clk_user;
     42 wire dco;
     43 wire dco_bufg;
     44 
     45 wire [DATA_W*2-1:0] data_adc;
     46 wire data_adc_vld;
     47 (*DONT_TOUCH = "true"*)wire [DATA_W*2-1:0] din;
     48 (*DONT_TOUCH = "true"*)wire din_vld;
     49 wire din_sop,din_eop;
     50 reg [ (DECH_W-1):0]  data_cnt =0    ;
     51 wire        add_data_cnt ;
     52 wire        end_data_cnt ;
     53 
     54 /******************************clock generators****************************************/
     55    
     56 //user clock generator
     57  clk_wiz_0 user_clock_gen
     58    (
     59     // Clock out ports
     60     .clk_out1(clk_out0),     // output clk_out0  125MHZ
     61     // Status and control signals
     62     .locked(locked0),       // output locked
     63    // Clock in ports
     64     .clk_in1(clk));      // input clk_in1 100MHZ 
     65 
     66     //pll lock信号同步
     67     always@(posedge clk_out0)begin
     68         locked0_r0 <= locked0;
     69         locked0_r1 <= locked0_r0;
     70     end
     71 
     72     assign clk_user = clk_out0 & locked0_r1;
     73   
     74 
     75 // ADC clock generator
     76 IBUFDS #(
     77     .DIFF_TERM("FALSE"),
     78     .IBUF_LOW_PWR("FALSE"),
     79     .IOSTANDARD("DEFAULT")
     80 ) IBUFDS_inst (
     81     .O(dco),
     82     .I(dco_p),
     83     .IB(dco_n)
     84 );
     85 
     86 BUFG BUFG_inst(
     87     .O(dco_bufg),
     88     .I(dco)
     89 );
     90    
     91 /**********************************ADC interface module***********************************/
     92 adc_interface#(.WIDTH(DATA_W),
     93                .FRAME_LEN(DATA_EACH_CHANNEL))
     94 u_adc_interface
     95 (
     96     //adc -> fpga
     97     . clk0        (dco_bufg)  ,     //125MHZ 与dco_p同相
     98     . clk1        (~dco_bufg),      //125MHZ 与dco_n反相
     99     . da          (adc_p1)  ,
    100     . db          (adc_p2)  ,
    101     //fpga -> adc
    102     . adc_pd      (adc_pd)  ,//省点模式选择
    103     . pll_ce      (pll_ce)  ,
    104     . pll_rst_n   (pll_rst_n)  ,
    105     . pen         (pen),
    106     //user
    107     . user_clk    (clk_user),//125MHZ
    108     . rst_n       (rst_n),
    109     . dout        (data_adc),//debug
    110     . dout_vld    (data_adc_vld),
    111     . en          (en)//上升沿有效
    112     );
    113 //data counter
    114 always @(posedge clk_user or negedge rst_n) begin 
    115     if (rst_n==0) begin
    116         data_cnt <= 0; 
    117     end
    118     else if(add_data_cnt) begin
    119         if(end_data_cnt)
    120             data_cnt <= 0; 
    121         else
    122             data_cnt <= data_cnt+1 ;
    123    end
    124 end
    125 
    126 assign add_data_cnt = data_adc_vld ;
    127 assign end_data_cnt = add_data_cnt  && data_cnt == (DATA_EACH_CHANNEL)-1 ;
    128 
    129 //input data to the user defined logic
    130     assign din_sop  = add_data_cnt && data_cnt == 0;
    131     assign din_eop  = end_data_cnt;  
    132     assign din      = data_adc;
    133     assign din_vld  = data_adc_vld;
    134 
    135 
    136 //user logic end  
    137 
    138 endmodule
    top

      上述代码是之前做ADC采集信号频谱分析的部分代码,因此adc_interface模块中每触发一次则连续采集一帧数据长度,用于FFT运算。用户可以根据项目需求自行改动。顶层模块中则例化IBUFDS+BUFG原语,以及后续的自定义处理模块。

    五、板级调试

      行为仿真是FPGA开发中必不可少的重要环节,通过充分测试可节省很多调试时间,这里仅给出板级调试结果。信号发生器产生三角波,利用ILA抓取芯片内部实时数据,并以模拟形式显示:

      由于在接口模块中将两通道输入拼接为一个数据,这里拆分后观察数据数值。可见拼接后数据波形呈现三角波形状,且幅值增大过程中高字节较大,幅值降低过程中高字节较小,说明数据拼接顺序无误,高字节为当前节拍后一个采样数据。两路输出数据最高位为0,证明输出数据格式是自然二进制数。

      若想细致地观察数据的模拟形状,可以通过灵活的TCL脚本将ILA抓取数据导出,并在MATLAB中查看。TCL命令为:

    write_hw_ila_data E:/fpga_files/wave_file.csv [upload_hw_ila_data hw_ila_1] -csv_file –forcewave

      命令格式是:write_hw_ila_data <文件路径及文件名> [upload_hw_ila_data <ILA名称>] -csv_file -forcewave。键入该命令后,指定路径下会产生CSV文件。如让信号发生器产生频率为1MHz,峰峰值是1Vpp,偏移幅值是0.7V的正弦波。导出ILA抓取数据,并在MATLAB中绘制曲线如图:

       整体来看还是比较简单的。后边如有机会接触采样率更高的ADC芯片,会总结基于Select I/O IP Core的LVDS接口设计。

  • 相关阅读:
    在Android初次的前期学习中的十二个小例子(附案例下载)
    实验二 汇编命令(伪指令)实验
    实验一 用机器指令和汇编指令编程
    用汇编实现十六进制数转化为八进制数(除法)
    用汇编语言实现从1加到100(1+2+...+100)
    实验一 绘制任意斜率的直线段 | 使用VS2017工具
    Nginx+Keepalived实现Nginx高可用负载均衡
    Linux系统在线扩容(根目录)磁盘空间
    Redis集群部署
    CentOS7安装OpenStack-11.部署Ceph分布式存储架构
  • 原文地址:https://www.cnblogs.com/moluoqishi/p/10641557.html
Copyright © 2011-2022 走看看