一、前言
最近忙于硕士毕业设计和论文,没有太多时间编写博客,现总结下之前在某个项目中用到的一个高速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@(p