一、前言

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

 `timescale 1ns / 1ps

 module adc_interface#(parameter WIDTH = ,
FRAME_LEN =
//WAIT_CYC = 125_000_000//1s = 1000_000_000ns 1000_000_000/8 = 125_000_000
)
(
input clk0, //125MHZ
input clk1, input [WIDTH-:] da,
input [WIDTH-:] db,
output adc_pd,//省电模式选择 output pll_ce,
output pll_rst_n,
output pen, input user_clk,//125MHZ
input rst_n,
input en,
output reg [WIDTH*-:] dout = ,
output reg dout_vld =
); function integer clogb2 (input integer bit_depth);
begin
for(clogb2=; bit_depth>; clogb2=clogb2+)
bit_depth = bit_depth >> ;
end
endfunction localparam DATA_CNT_W = clogb2(FRAME_LEN-); (*DONT_TOUCH = "true"*)reg setup_flag = ;
reg [WIDTH-:] data_a = ,data_b = ;
reg data_a_vld = ,data_b_vld = ;
reg wr_en = ;
reg [WIDTH*-:] wr_data = ;
reg rd_en = ;
wire empty;
wire full;
wire [WIDTH*-:] rd_data;
(*DONT_TOUCH = "true"*)wire en_pos;
(*DONT_TOUCH = "true"*)reg [ (DATA_CNT_W-):] data_cnt = ;
wire add_data_cnt ;
wire end_data_cnt ;
reg en_r0 = ,en_r1 = ,en_r2 = ,en_r3 = ; assign pll_ce = 'b1;
assign pll_rst_n = 'b1;
assign adc_pd = 'b0;
assign pen = 'b1; /***************************采集触发**************************************/
//异步处理
always@(posedge clk0)begin
en_r0 <= en;
en_r1 <= en_r0;
en_r2 <= en_r1;
en_r3 <= en_r2;
end assign en_pos = en_r2 == 'b1 && en_r3 == 1'b0; always @(posedge clk0 or negedge rst_n)begin
if(rst_n=='b0)begin
setup_flag <= ;
end
else if(end_data_cnt)
setup_flag <= ;
else if(en_pos)begin
setup_flag <= 'b1;
end
end always @(posedge clk0 or negedge rst_n) begin
if (rst_n==) begin
data_cnt <= ;
end
else if(add_data_cnt) begin
if(end_data_cnt)
data_cnt <= ;
else
data_cnt <= data_cnt+ ;
end
end
assign add_data_cnt = (setup_flag);
assign end_data_cnt = add_data_cnt && data_cnt == (FRAME_LEN)- ; /***************************clk0(dco_p)采集DB**************************************/
always@(posedge clk0 or negedge rst_n)begin
if(rst_n == )
data_b <= ;
else
data_b <= db;
end always@(posedge clk0 or negedge rst_n)begin
if(rst_n == )
data_b_vld <= ;
else if(setup_flag)
data_b_vld <= 'b1;
else
data_b_vld <= ;
end /****************************clk1(dco_n)采集DA**************************************/
always @(negedge clk0 or negedge rst_n)begin
if(rst_n=='b0)begin
data_a <= ;
end
else begin
data_a <= da;
end
end always@(negedge clk0 or negedge rst_n)begin
if(rst_n == )begin
data_a_vld <= ;
end
else if(setup_flag)begin
data_a_vld <= 'b1;
end
else
data_a_vld <= ;
end
/****************************FIFO写逻辑**************************************/
//FIFO:width 16bit depth 16 async always @(negedge clk0 or negedge rst_n)begin
if(rst_n=='b0)begin
wr_en <= ;
end
else if(data_a_vld & data_b_vld)begin
wr_en <= 'b1;
end
else
wr_en <= ;
end always @(negedge clk0 or negedge rst_n)begin
if(rst_n=='b0)begin
wr_data <= ;
end
else begin
wr_data <= {data_b,data_a};//高字节为后一个数据
end
end /****************************FIFO读侧逻辑**************************************/
//非空即读
always@(*)begin
if(~empty)
rd_en = 'b1;
else
rd_en = ;
end always @(posedge user_clk or negedge rst_n)begin
if(rst_n == )
dout <= ;
else
dout <= rd_data;
end always @(posedge user_clk or negedge rst_n)begin
if(rst_n == )
dout_vld <= ;
else if(rd_en)begin
dout_vld <= 'b1;
end
else
dout_vld <= ;
end //FIFO instance
fifo_generator_2 interface_fifo (
.wr_clk(~clk0), // input wire wr_clk
.rd_clk(user_clk), // input wire rd_clk
.din(wr_data), // input wire [15 : 0] din
.wr_en(wr_en), // input wire wr_en
.rd_en(rd_en), // input wire rd_en
.dout(rd_data), // output wire [15 : 0] dout
.full(full), // output wire full
.empty(empty), // output wire empty
.rd_data_count(rd_data_count), // output wire [3 : 0] rd_data_count
.wr_data_count(wr_data_count) // output wire [3 : 0] wr_data_count
); endmodule

adc_interface

  顶层模块代码:

 `timescale 1ns / 1ps

 module top#(parameter DATA_W = ,//改动参数需要重新配置IP核
CHANNEL_NUM = )
(
input dco_p,//125MHZ
input dco_n,
input [DATA_W-:] adc_p1,//通道A
input [DATA_W-:] adc_p2,//通道B
output adc_pd,
output pll_ce,
output pll_rst_n,
output pen, //user interface signals input clk,//100M
input rst_n,
input en//上升沿有效 有效一次则将之后采集到的一帧数据写入到FFT模块进行运算
);
/*********************************parameters*******************************************/
function integer clogb2 (input integer bit_depth);
begin
for(clogb2=; bit_depth>; clogb2=clogb2+)
bit_depth = bit_depth >> ;
end
endfunction localparam FFT_W = ,
FFT_N = ,
DATA_EACH_CHANNEL = FFT_N/CHANNEL_NUM; //log2
localparam DECH_W = clogb2(DATA_EACH_CHANNEL-); /*********************************variables*******************************************/
genvar ii;
wire clk_out0,locked0;
reg locked0_r0 = ,locked0_r1 = ;
wire clk_user;
wire dco;
wire dco_bufg; wire [DATA_W*-:] data_adc;
wire data_adc_vld;
(*DONT_TOUCH = "true"*)wire [DATA_W*-:] din;
(*DONT_TOUCH = "true"*)wire din_vld;
wire din_sop,din_eop;
reg [ (DECH_W-):] data_cnt = ;
wire add_data_cnt ;
wire end_data_cnt ; /******************************clock generators****************************************/ //user clock generator
clk_wiz_0 user_clock_gen
(
// Clock out ports
.clk_out1(clk_out0), // output clk_out0 125MHZ
// Status and control signals
.locked(locked0), // output locked
// Clock in ports
.clk_in1(clk)); // input clk_in1 100MHZ //pll lock信号同步
always@(posedge clk_out0)begin
locked0_r0 <= locked0;
locked0_r1 <= locked0_r0;
end assign clk_user = clk_out0 & locked0_r1; // ADC clock generator
IBUFDS #(
.DIFF_TERM("FALSE"),
.IBUF_LOW_PWR("FALSE"),
.IOSTANDARD("DEFAULT")
) IBUFDS_inst (
.O(dco),
.I(dco_p),
.IB(dco_n)
); BUFG BUFG_inst(
.O(dco_bufg),
.I(dco)
); /**********************************ADC interface module***********************************/
adc_interface#(.WIDTH(DATA_W),
.FRAME_LEN(DATA_EACH_CHANNEL))
u_adc_interface
(
//adc -> fpga
. clk0 (dco_bufg) , //125MHZ 与dco_p同相
. clk1 (~dco_bufg), //125MHZ 与dco_n反相
. da (adc_p1) ,
. db (adc_p2) ,
//fpga -> adc
. adc_pd (adc_pd) ,//省点模式选择
. pll_ce (pll_ce) ,
. pll_rst_n (pll_rst_n) ,
. pen (pen),
//user
. user_clk (clk_user),//125MHZ
. rst_n (rst_n),
. dout (data_adc),//debug
. dout_vld (data_adc_vld),
. en (en)//上升沿有效
);
//data counter
always @(posedge clk_user or negedge rst_n) begin
if (rst_n==) begin
data_cnt <= ;
end
else if(add_data_cnt) begin
if(end_data_cnt)
data_cnt <= ;
else
data_cnt <= data_cnt+ ;
end
end assign add_data_cnt = data_adc_vld ;
assign end_data_cnt = add_data_cnt && data_cnt == (DATA_EACH_CHANNEL)- ; //input data to the user defined logic
assign din_sop = add_data_cnt && data_cnt == ;
assign din_eop = end_data_cnt;
assign din = data_adc;
assign din_vld = data_adc_vld; //user logic end 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接口设计。

FPGA高速ADC接口实战——250MSPS采样率ADC9481的更多相关文章

  1. AM5728通过GPMC接口与FPGA高速数据通信实现

    硬件:AM5728开发板:Artix-7开发板软件:Linux am57xx-evm 4.4.19:Vivado 2015.2作者:杭州矢志信息科技有限公司邮箱:admin@sysjoint.com ...

  2. DM8168通过GPMC接口与FPGA高速数据通信实现

    硬件:TI达芬奇TMS320DM8168(以下简称DSP).EP4CE6E22C8N(以下简称FPGA) 软件:linux-2.6.37 转载请注明出处- http://www.cnblogs.com ...

  3. 基于FPGA的PCIe接口实现(具体讲解了数据流向)

    时间:2014-12-09 来源:西安电子科技大学电子工程学院 作者:姜 宁,陈建春,王 沛,石 婷 摘要 PCI Express是一种高性能互连协议,被广泛应用于网络适配.图形加速器.网络存储.大数 ...

  4. 用STM32内置的高速ADC实现简易示波器

    做一个数字采样示波器一直是我长久以来的愿望,不过毕竟这个目标难度比较大,涉及的方面实在太多,模拟前端电路.高速ADC.单片机.CPLD/FPGA.通讯.上位机程序.数据处理等等,不是一下子就能成的,慢 ...

  5. CC1605&CC1604 usb3.0+FPGA 高速视频采集 双目相机测评

    CC1605&CC1604 usb3.0+FPGA 高速视频采集 双目相机测评 摄像头配置:ov5640.OV5642.mt9p031.mt9m001c12stm OV5640 xclk:24 ...

  6. FPGA控制RGMII接口PHY芯片基础

    一.前言 网络通信中的PHY芯片接口种类有很多,之前接触过GMII接口的PHY芯片RTL8211EG.但GMII接口数量较多,本文使用RGMII接口的88E1512搭建网络通信系统.这类接口总线位宽小 ...

  7. Postman系列三:Postman中post接口实战(上传文件、json请求)

    一:接口测试过程中GET请求与POST请求的主要区别 从开发角度我们看get与post的主要区别是:1.Get是用来从服务器上获得数据,而Post是用来向服务器上传递数据:2.Get安全性比Post低 ...

  8. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(二)

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

  9. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(三)

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

随机推荐

  1. Collection、List、Set集合概括

    1.Collection是一个接口,定义了集合相关的操作方法,其有两个子接口List和Set. 2.List和Set的区别    List是有序的可重复集合,Set是无序的不可重复集合. 3.集合持有 ...

  2. Spring @Conditional注解的使用

    Spring Boot的强大之处在于使用了Spring 4框架的新特性:@Conditional注释,此注释使得只有在特定条件满足时才启用一些配置. 下面来介绍如何使用Condition 首先写一个类 ...

  3. Spring加载xml配置文件的方式 ApplicationContext

    大家都知道Java读普通文件是通过Basic I/O 中的InputStream.OutStream.Reader.Writer 等实现的.在spring 框架中,它是怎样识别xml这个配置文件的呢? ...

  4. Spring中的Lookup(方法注入)

    在使用Spring时,可能会遇到这种情况:一个单例的Bean依赖另一个非单例的Bean.如果简单的使用自动装配来注入依赖,就可能会出现一些问题,如下所示: 单例的Class A @Component ...

  5. 1. Java面向对象之泛型-认识泛型

    package generic; class Point{ private  Object x; private  Object y; public Object getX() { return x; ...

  6. dirlock_windows.go

    package dirlock type DirLock struct {     dir string } func New(dir string) *DirLock {     return &a ...

  7. BZOJ_3689_异或之_可持久化Trie+堆

    BZOJ_3689_异或之_可持久化Trie+堆 Description 给定n个非负整数A[1], A[2], ……, A[n]. 对于每对(i, j)满足1 <= i < j < ...

  8. AngularJs 服务 广播

    1, angularJs的服务有provider,Service, Factory. Factory是对Service的封装,Service是对Provider的封装. Provide的源码如下: f ...

  9. Redis详解(一)------ redis的简介与安装

    工作中一直在用 Redis,但是一直没有进行系统的总结,这个系列的博客将整体的介绍 Redis 的用法. 1.Redis 的简介 Redis:REmote DIctionary Server(远程字典 ...

  10. Ubuntu18.04美化主题(mac主题)

    前端时间Ubuntu18.04LTS发布,碰巧之前用的Ubuntu16.04出了一点问题,懒得解决,索性就换了Ubuntu18.04. 成果: 参考博客:https://www.cnblogs.com ...