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代码如下:
`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的更多相关文章
- AM5728通过GPMC接口与FPGA高速数据通信实现
硬件:AM5728开发板:Artix-7开发板软件:Linux am57xx-evm 4.4.19:Vivado 2015.2作者:杭州矢志信息科技有限公司邮箱:admin@sysjoint.com ...
- DM8168通过GPMC接口与FPGA高速数据通信实现
硬件:TI达芬奇TMS320DM8168(以下简称DSP).EP4CE6E22C8N(以下简称FPGA) 软件:linux-2.6.37 转载请注明出处- http://www.cnblogs.com ...
- 基于FPGA的PCIe接口实现(具体讲解了数据流向)
时间:2014-12-09 来源:西安电子科技大学电子工程学院 作者:姜 宁,陈建春,王 沛,石 婷 摘要 PCI Express是一种高性能互连协议,被广泛应用于网络适配.图形加速器.网络存储.大数 ...
- 用STM32内置的高速ADC实现简易示波器
做一个数字采样示波器一直是我长久以来的愿望,不过毕竟这个目标难度比较大,涉及的方面实在太多,模拟前端电路.高速ADC.单片机.CPLD/FPGA.通讯.上位机程序.数据处理等等,不是一下子就能成的,慢 ...
- CC1605&CC1604 usb3.0+FPGA 高速视频采集 双目相机测评
CC1605&CC1604 usb3.0+FPGA 高速视频采集 双目相机测评 摄像头配置:ov5640.OV5642.mt9p031.mt9m001c12stm OV5640 xclk:24 ...
- FPGA控制RGMII接口PHY芯片基础
一.前言 网络通信中的PHY芯片接口种类有很多,之前接触过GMII接口的PHY芯片RTL8211EG.但GMII接口数量较多,本文使用RGMII接口的88E1512搭建网络通信系统.这类接口总线位宽小 ...
- Postman系列三:Postman中post接口实战(上传文件、json请求)
一:接口测试过程中GET请求与POST请求的主要区别 从开发角度我们看get与post的主要区别是:1.Get是用来从服务器上获得数据,而Post是用来向服务器上传递数据:2.Get安全性比Post低 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(二)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(三)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
随机推荐
- 富文本编辑器UEditor自定义工具栏(三、自定义工具栏功能按钮图标及工具栏样式简单修改)
导读 富文本编辑器UEditor提供丰富了定制配置项,如果想设置个性化的工具栏按钮图标有无办法呢?答案是肯定的!前两篇博文简要介绍了通过将原工具栏隐藏,在自定义的外部按钮上,调用UEditor各命令实 ...
- HTTP/HTTPS 学习笔记
超文本传输协议(HyperText Transfer Protocol) 伴随着计算机网络和浏览器的诞生,HTTP1.0也随之而来,处于计算机网络中的应用层,HTTP是建立在TCP协议之上的. HTT ...
- php自带验证邮箱 url ip函数
以前用PHP验证邮箱.URL.IP是否合法都是通过自己写正则来实现,但是有时候脑子发昏,可能会写出一个不是完全正确的正则,导致验证出错,今天发现原来PHP本身自带了验证邮箱.URL.IP是否合法的函数 ...
- java Queue中 add/offer,element/peek,remove/poll区别
转自https://blog.csdn.net/u012050154/article/details/60572567 java Queue中 add/offer,element/peek,remov ...
- readonly 和 disabled的区别
在开发的时候遇到了disabled不能传值的问题 但是readonly可以传值 学习源头: http://www.w3school.com.cn/tags/att_input_readonly.asp ...
- 深入css布局篇(2) — 定位与浮动
深入css布局(2) - 定位与浮动 在css知识体系中,除了css选择器,样式属性等基础知识外,css布局相关的知识才是css比较核心和重要的点.今天我们来深入学习一下css布局相关的知识 ...
- 领域驱动设计学习之路—DDD的原则与实践
本文是我学习Scott Millett & Nick Tune编著的<领域驱动设计模式.原理与实践>一书的学习笔记,一共会分为4个部分如下,此文为第1部分: ① 领域驱动设计的原则 ...
- Appscan 工具快速上手教程
1.appscan扫描 (1)白盒扫描=静态扫描,扫描源代码.(2)动态扫描=黑盒扫描,用工具来模拟黑客的攻击,查看应用层的响应.产品内部会有大量受攻击的库,当我们把一个模拟攻击发给我们的应用的时 ...
- 【转】asp.net基础-HttpModule
HttpModule是向实现类提供模块初始化和处置事件.当一个HTTP请求到达HttpModule时,整个ASP.NET Framework系统还并没有对这个HTTP请求做任何处理,也就是说此时对于H ...
- PdfReader按页将PDF切割成多个PDF
private MemoryStream GetNewPdfByPageNum(PdfReader pdfReader, int pageNum) { MemoryStream memoryStrea ...