一、前言

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

    MQ简介: MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法.应用程序通过写和检索出入列队的针对应用程序的数据(消息)来通信,而无需专用连接来链接它们.消息传 ...

  2. Alfred效率神器

    下图就是Alfred的主界面我们所有的操作都在这一个界面上进行.通过热键打开主界面(本人设置的是option+command),输入一个"a"后Alfred就会为我在候选界面上显示 ...

  3. explicit的作用

    用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换,例如:不加:Circle A = Circle(1.23) 加上之后:只能写:Circle A(1 ...

  4. Collections.synchronizedMap()、ConcurrentHashMap、Hashtable之间的区别

    为什么要比较Hashtable.SynchronizedMap().ConcurrentHashMap之间的关系?因为常用的HashMap是非线程安全的,不能满足在多线程高并发场景下的需求. 那么为什 ...

  5. ActiveX、OLE和COM的关系(转自百度文档)

    比较流行的组件模型有COM(Component Object Model,对象组件模型)/DCOM(Distributed COM,分布式对象组件模型)和CORBA(Common Object Req ...

  6. Fedora Linux安装deb包

    前言: 我们知道,在Linux系统上有两种主要的软件包格式,分别是RPM和deb. RPM是"RPM Package Manager(RPM软件包管理器)"的递归缩写.RPM是一种 ...

  7. 【数据结构】B-Tree, B+Tree, B*树介绍

    [摘要] 最近在看Mysql的存储引擎中索引的优化,神马是索引,支持啥索引.全是浮云,目前Mysql的MyISAM和InnoDB都支持B-Tree索引,InnoDB还支持B+Tree索引,Memory ...

  8. BZOJ_3362_[Usaco2004 Feb]Navigation Nightmare 导航噩梦_并查集

    BZOJ_3362_[Usaco2004 Feb]Navigation Nightmare 导航噩梦_并查集 Description     农夫约翰有N(2≤N≤40000)个农场,标号1到N,M( ...

  9. Python Django 2.2登录功能_2

    #Now 让我们继续对上篇的登录进行操作 #对于csrf,以后再开篇章记录 #修改index.html <form method="post" action="/l ...

  10. 协同过滤的R语言实现及改进

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 协同过滤算法是推荐系统最常用的算法之一,本文将介绍一种方法来使它可以在大型数据集上快速训练. 协同过滤算法(CF)是构建推荐系统时最常用的技 ...