基于FPGA的数据采集系统(二)

目录

五、同步FIFO模块

六、DAC驱动模块

1.TLV5618数模转换详解

2.TLV5618接口设计

七、ADC驱动模块

1.ADC128s022模数转换详解

2.ADC128s022接口时序设计


本设计工程文件下载链接(包含注释):https://download.csdn.net/download/qq_33231534/12450178  


五、同步FIFO模块

FIFO根据需求可分为同步FIFO和异步FIFO,同步FIFO读写共用同一个时钟周期,异步FIFO读写数据分别用不同的时钟周期。FIFO设计重点是对写满和读空标志的设计,即写满而不溢出,读空又不多读。下表为FIFO主要信号列表:

表5.1:同步FIFO信号列表
 信号名称 I/O 位数 功能描述
clk I 1 系统时钟50MHz
rst_n I 1 系统复位
wrreq I 1 写使能
data  I WIDTH 写数据
rdreq I 1 读使能
q   O WIDTH 读数据
empty O 1 空信号
full  O 1 满信号
half  O 1 半满信号
usedw O MAX_DEPTH_BIT fifo中剩余数据个数

其中WIDTH代表数据定义的宽度,MAX_DEPTH_BIT表示可设置的最大深度位数x,即最大深度为2^x-1,这两个参数可以在例化模块的时候根据自己的需求设置。

该模块可通过使用quartus软件自带的IP核创建,也可以自己编写代码。

下边是自己编写的代码,经过仿真测试,满足自己需求,其代码如下:syn_fifo.v

//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 PHF的CSDN   
//File name:           syn_fifo.v
//Last modified Date:  2020/5/18
//Last Version:        
//Descriptions:        Synchronize fifo 12*100(位宽*深度)
//-------------------------------------------------------------------

module sync_fifo
#(parameter WIDTH         = 12,          //缓存的数据宽度
  parameter DEPTH         = 100,         //缓存的数据深度
  parameter MAX_DEPTH_BIT = 7)           //可设置的最大深度位数7,即最大深度为2^7-1
(
    input                                 clk         , //系统时钟50MHz
    input                                 rst_n       , //系统复位
    input                                 wrreq       , //写使能
    input        [ WIDTH-1: 0]            data        , //写数据
    input                                 rdreq       , //读使能
    output  reg  [ WIDTH-1: 0]            q           , //读数据
    output                                empty       , //空信号
    output                                full        , //满信号
    output                                half        , //半满信号
    output  reg  [MAX_DEPTH_BIT-1:0]      usedw         //fifo中剩余数据个数    
);

reg   [ WIDTH-1: 0]     fifo_rom     [ DEPTH-1: 0]; //fifo存储单元


reg   [  MAX_DEPTH_BIT-1: 0]         wr_p     ; //写指针
reg   [  MAX_DEPTH_BIT-1: 0]         rd_p     ; //读指针

//当写使能且数据不满时写入数据
always  @(posedge clk )begin
    if(wrreq && !full) begin
        fifo_rom[wr_p] <= data;
    end
    else begin
        fifo_rom[wr_p] <= fifo_rom[wr_p];
    end
end

//写指针,当写使能且数据不满时,指针加一;当指针指向最大深度且有写使能时指针归零
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        wr_p <= 0;
    end
    else if(wrreq && !full) begin
        if(wr_p==DEPTH-1 && wrreq)
            wr_p <= 0;
        else
            wr_p <= wr_p + 1'b1;
    end
end

//读指针,,当读使能且数据不空时,指针加一,当指针指向最大深度且有读使能时指针归零
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        rd_p <= 0;
    end
    else if(rdreq && !empty) begin
        if(rd_p==DEPTH-1 && rdreq)
            rd_p <= 0;
        else
            rd_p <= rd_p + 1'b1;
    end
end

//读出数据,在读使能且数据不为空时读出数据
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        q <= 0;
    end
    else if(rdreq && !empty) begin
        q <= fifo_rom[rd_p];
    end
end

//fifo中数据个数
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        usedw <= 0;
    end
    else if((wrreq && !full && !rdreq) || (wrreq && rdreq && empty)) begin
        usedw <= usedw + 1'b1;
    end
    else if(!wrreq && !empty && rdreq)begin
        usedw <= usedw - 1'b1;
    end  
end

//满、空、半满标志
assign full  = usedw==DEPTH;
assign empty = usedw==0;
assign half  = usedw==DEPTH/2;

endmodule

仿真测试如下:

(1)写数据:在wrreq拉高时,开始写数据,这里从0开始每次加1将数据写入fifo,可以看到usedw每个上升沿也加1。

(2)读数据:在rdreq拉高时,开始读数据,这里第一个数据读出为0,读出第一个数据时,usedw减1,同时empty拉低。

(3)边读边写:wrreq和rdreq都拉高时,边读边写,此时usedw一直不变,同时读出的数据按照仿真一直递增。

基于FPGA的数据采集系统(二)_第1张图片


六、DAC驱动模块

本设计使用的DAC芯片是tlv5618a,该芯片是基于SPI协议的12位的DAC数模转换芯片。下边将对其具体讲解。

1.TLV5618数模转换详解

(1)TLV5618电路设计

基于FPGA的数据采集系统(二)_第2张图片

TLV5618与FPGA采用三线制SPI通信,其电路图如图5.2-2 所示。其中TLV5618的参考电压由LM4040-2.0提供,LM4040-2.0 是一个专用于12位精度场合的精密参考源,输出电压为2.048V。|

TLV5618是由两个电阻网络来实现两路数模转换,每路DAC的核心是一个拥有4096 (212) 个节点的电阻,对应了4096种不同的组合,每个电阻网络的一段连接到GND,另-端来自参考电压经过缓冲器后的输出。如果不考虑其他情况,该电阻网络型DAC的输出电压范围应该为0V~VREF,对应到AC620板上的电路,即0V~2.048V。另外在每个DAC通道的电阻网络电压输出后级,连接了一个2倍增益的轨对轨放大器。将电阻网络DAC单元的输出电压放大为2倍后输出到管脚。所以,TLV5618 芯片的实际输出电压范围为0V~2*VREF,对应到AC620板_上的电路,即0V~4.096V。当芯片上电时, DAC的值全部被复位到0。每个DAC通道的输出可由下列公式计算得出:

                                                 Vo (DACAB) = 2*REF * CODE/2^12 (V)

其中,REF是基准电压,本电路中为2.048V; CODE是数字电压输入值,范围0到2^12-1。当串行控制字中的数据部分为0~ 4095d时,与电压呈线性关系。需注意的是满量程输出电压由基准电压决定。

(2)TLV5618芯片引脚功能

表6.1:TLV5618芯片引脚功能表
引脚名 编号 I/O 功能描述
DIN 1 I 数字串行数据输入
SCLK 2 I 数字串行时钟输入
CS 3 I 片选端。低电平数据输入有效,用作使能输入端
OUTA 4 O DAC A 模拟电压输出
AGND 5 供电
REF 6 I 模拟基准电压输入
OUTB 7 O DAC B 模拟电压输出
VDD 8 供电 正电源

(3)TLV5618接口时序

当片选(CS)信号为低电平时,数据在每个SCLK信号的下降沿被移入芯片内部的寄存器。16位的数据按照高位在前,低位在后的顺序依次移入。当16位的数据移入完毕后,在第16个SCLK信号的下降沿之后的一个SCLK信号.上升沿,据根数据中的控制位,将数据制位移入保持寄存器A. B、缓冲器和控制寄存器。当片选(CS)信号进入上升沿时,再把数据送至12位AD转换器,如图所示。

基于FPGA的数据采集系统(二)_第3张图片

其时序要求为:

基于FPGA的数据采集系统(二)_第4张图片

TLV5618的16位数据格式如下:
 

表6.2:TLV5618 16位数据格式
D15 D14 D13 D12 D11 D10 D9 D8 D7 D6 D5 D4 D3 D2 D1 D0
R1 SPD PWR R0 MSB                                  12位数据                                           LSB

其中,SPD为速度控制位,PWR为电源控制位。上电时, SPD和PWR复位到0 (低速模式和正常工作)。

表7:SPD、PWR功能描述

  SPD PWR 1 快速模式 掉电模式 0 低速模式 正常模式

R1与R0所有可能的组合以及代表的含义如下所示。如果其中一个寄存器或者缓冲区被选择,那么12位数据将决定新的DAC输出电压值。

表6.3:R0、R1功能描述
R1 R0 描述
0 0 写数据到DAC B 和缓冲区
0 1 写数据到缓冲区
1 0 写数据到DAC A 同时将缓冲区数据更新到DAC B
1 1 保留

2.TLV5618接口设计

经查阅手册可知器件I作频率SCLK最大为20MHz,设计时留下一定余量,因此这里定义其工作频率为12.5MHz。 

可用线性序列机思想编写代码。因此只需要在逻辑中使用一个计数器来计数,然后每个计数值时就相当于在
t轴_上对应了一个相应的时间点,那么在这个时间点上,各个信号需要进行什么操作,直接赋值即可。但本模块没有用线性序列机思想编写代码。

如下表为TLV5618驱动模块信号端口列表:

表6.4:TLV5618驱动模块信号端口列表
 信号名称 I/O 位数 功能描述
clk I 1 系统时钟50MHz
rst_n I 1 系统复位
dac_data_in I 16 进行数模转换的数据输入
dac_en I 1 使能端口
din O 1 SPI接口的数据输入端口
dac_sclk O 1 SPI接口的时钟输入端口
cs O 1 SPI接口的片选输入端口
dac_down O 1 完成一次数模转换标志

其代码为:dac_driver.v


//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 phf的csdn 
//File name:           dac_driver.v
//Last modified Date:  2020/5/10
//Last Version:        
//Descriptions:        TLV5618驱动接口
//-------------------------------------------------------------------
module dac_driver(
    input                   clk         ,//系统时钟50MHz
    input                   rst_n       ,//系统复位
    input      [ 15: 0]     dac_data_in ,//进行数模转换的数据输入
    input                   dac_en      ,//使能端口
    output reg 			    din         ,//SPI接口的数据输入端口
    output reg              dac_sclk    ,//SPI接口的时钟输入端口
    output reg              cs          ,//SPI接口的片选输入端口
    output reg              dac_down     //完成一次数模转换标志
);

parameter DATA_NUM = 5'd16; //分频计数次数

reg   [  1: 0]         cnt;
wire                   add_cnt;
wire                   end_cnt;
reg   [  4: 0]         num;
reg   [ 15: 0]         data_in;

//输出片选信号
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        cs <= 1;
    end
    else if(dac_en == 1)begin
        cs <= 0;
    end
    else if(dac_down)begin
        cs <= 1;
    end
end 

//使能时寄存要转换的数据
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        data_in <= 0;
    end
    else if(dac_en == 1) begin
        data_in <= dac_data_in;
    end
    else begin
        data_in <= data_in;
    end
end

//分频计数
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt <= 0;
    end
    else if(add_cnt)begin
        if(end_cnt)
            cnt <= 0;
        else
            cnt <= cnt + 1'b1;
    end
end

assign add_cnt = cs==0;       
assign end_cnt = add_cnt && cnt==3;

//dac时钟输出
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==0)begin
        dac_sclk <= 0;
    end
    else if(cs==0) begin
        if(cnt==0 || cnt==1)
            dac_sclk <= 1; 
        else if(cnt==2 || cnt==3)
            dac_sclk <= 0;
    end
end

//dac转换数据计数
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        num <= DATA_NUM;
    end
    else if(end_cnt) begin
        if(num==0)begin
            num <=DATA_NUM;
        end
        else begin
             num <= num - 1'b1;
        end
    end
    else if(cs==1)begin
        num <= DATA_NUM; 
    end
end

//串行输出dac要转换的数据
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        din <= 0;
    end
    else if(cs == 0 && cnt==0) begin
        din <= data_in[num-1];
    end
end

//一次转换完成标志
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        dac_down <= 0;
    end
    else if(num==0 && cnt==0) begin
        dac_down <= 1;
    end
    else begin
        dac_down <= 0;
    end
end
endmodule

仿真测试如图:可以输出满足要求的时序。

 


七、ADC驱动模块

本设计使用的ADC芯片是adc128s022,该芯片是基于SPI协议的12位8通道ADC模数转换芯片。下边将对其具体讲解。

1.ADC128s022模数转换详解

(1)ADC128s022电路设计

基于FPGA的数据采集系统(二)_第5张图片

ADC128S022部分电路图如图5.3-2 所示。这里外接了一个抗混叠低通滤波器,其作用是为了避免输入信号的高频成分在ADC的基带中引起混叠。数字电源与模拟电源电压均为3.3V,且数字电源与模拟电源之间串联磁珠用于抑制电磁干扰。

(2)ADC128S022芯片引脚功能

表7.1:ADC128S022芯片引脚功能描述
引脚名 编号 I/O 功能描述
cs 1 I 片选端,下降沿时开始测量转换,低电平状态为正在转换
VA 2 供电 模拟电源输入正,也就是参考电压。2.7V~5.25V
AGND 3 供电 模拟地
IN0~IN7 4~11 I 模拟输入脚,电压输入范围为0~VREF
DGND 12 供电 数字地
VD 13 供电 数字电源输入正,2.7V~VA.
DIN 14 I 串行数据输入脚,SCLK. 上升沿采样输入
DOUT 15 O 转换结构输出脚,SCLK下降沿输出
SCLK 16 I 时钟输入,频率范围为0..8~3.2MHz

本款ADC为12位分辨率,因此lbit代表的电压值即为VA4096。手册中同时指出,当模拟输入电压低于VA/8192时,输出数据即为0000_ 0000 0000b。 同理由于芯片本身内部构造当输出数0000_ 0000 0000b变为0000_ 0000_ 0001b 时,实际输入电压变化为VA/8192 而不是VA/4096。 当输入电压大于等于VA-1.5*VA/4096,输出数据即为1111_1111 111b。

(3)ADC128S022接口时序

微控制器与ADC128S022的接口如下图 所示,该接口使用标准的SPI接口,因此可以直接连接到微控制器的片上SPI。 对于FPGA来说,则可按照SPI时序搭建控制电路,以实现对ADC128S022的控制。

基于FPGA的数据采集系统(二)_第6张图片 图:主机和ADC128S022接口示意图

ADC128S022通过SPI接口与控制器进行通信的时序图如下图所示。一个串行帧开始于CS的下降沿,结束于CS的上升沿。一帧包含 16个上升沿SCLK。.ADC的DOUT引脚当CS为高时代表空闲状态,当为低时为传输状态。也就是相当于CS可以充当输出使能。在CS为高时SCLK默认高。在头三个SCLK的循环,ADC处于采样模式(track)。 在接下来的13个循环为保持模式(hold),转换完成并且完成数据输出。下降沿1~4为前导零, 5~16 为输出转换结果。如果在持续测量模式中,ADC在N*16个SCLK的.上升沿会自动进去采样模式,在N*16+4个下降沿进入保持/转换模式。

基于FPGA的数据采集系统(二)_第7张图片 图:ADC128S022的SPI接口时序图

下表为ADC128S022的SPI接口时序要求:

基于FPGA的数据采集系统(二)_第8张图片

进入采样模式有三种方式,第一种当SCLK为高电平时CS变为低,当SCLK第一个下降沿到来时便进入采样模式,如时序图所示;第二种当SCLK为低电平时CS变低,自动进入采样模式,CS的下降沿即视为SCLK的第一个下降沿;第三种SCLK与CS一同变为低,这样对于两信号上升沿没有时序约束,但对于其下降沿有要求。

在DIN的8个控制寄存器,每位代表的含义如表11所示。.

表7.2:ADC128S022 Din控制寄存器
Bit7(MSB) Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0
DONTC DONTC ADD2 ADD1 ADD0 DONTC DONTC DONTC

其中ADD[2:0]代表的输入通道选择表如表12所示。

表7.3:ADD寄存器与输入通道选择对应表
ADD2 ADD1 ADD0 输入通道
0 0 0 IN0(default)
0 0 1 IN1
0 1 0 IN2
0 1 1 IN3
1 0 0 IN4
1 0 1 IN5
1 1 0 IN6
1 1 1 IN7

2.ADC128s022接口时序设计

经查阅手册可知器件工作频率SCLK推荐范围为0.8~3.2MHz,这里定义其工作频率为1.92MHz(周期为520ns)。设置一个两倍于SCLK的采样时钟SCLK2X,使用50M系统时钟十三分频而来即SCLK2X为3.84MHz。针对SCLK2X进行计数来确定图25.4中各个信号的状态。结合前面ADC的接口时序图,按照线性序列机的设计思路,可以整理得到每个信号发生变化时对应的时刻以及此时对应的计数器的值。表13为依照线性序列机的设计思想,整理得到的每个信号发生变化时对应的时刻以及此时对应的计数器的值。其中CS N为芯片状态标志信号,SCLK为芯片时钟输入脚,DIN 为芯片串行数据输入,DOUT为芯片串行数据输出。

表7.4:时间点对应信号操作表
计数器值 对应信号操作
0 cs = 0;
1 adc_sclk = 0;
2 adc_sclk = 1;
3 adc_sclk = 0;
4 adc_sclk = 1;
5 adc_sclk = 0;din <= r_channel[2];
6 adc_sclk = 1;
7 adc_sclk = 0;din <= r_channel[1];
8 adc_sclk = 1;
9 adc_sclk = 0;din <= r_channel[0];
10 adc_sclk = 1;data[11] = dout;
11 adc_sclk = 0;
12 adc_sclk = 1;data[10] = dout;
13 adc_sclk = 0;
14 adc_sclk = 1;data[9] = dout;
15 adc_sclk = 0;
16 adc_sclk = 1;data[8] = dout;
17 adc_sclk = 0;
18 adc_sclk = 1;data[7] = dout;
19 adc_sclk = 0;
20 adc_sclk = 1;data[6] = dout;
21 adc_sclk = 0;
22 adc_sclk = 1;data[5] = dout;
23 adc_sclk = 0;
24 adc_sclk = 1;data[4] = dout;
25 adc_sclk = 0;
26 adc_sclk = 1;data[3] = dout;
27 adc_sclk = 0;
28 adc_sclk = 1;data[2] = dout;
29 adc_sclk = 0;
30 adc_sclk = 1;data[1] = dout;
31 adc_sclk = 0;
32 adc_sclk = 1;data[0] = dout;
33 cs = 1;

如下表为ADC128S022驱动模块的信号端口列表:
 

表14:ADC128S022驱动模块的信号端口列表
 信号名称 I/O 位数 功能描述
clk I 1 系统时钟50MHz
rst_n I 1 系统复位
channel I 3 通道选择
adc_en I 1 使能单次转换,该信号为周期有效高脉冲使能一次转换
dout I 1 ADC转换结果,由ADC输给FPGA
din O 1 ADC控制信号,通道控制选择
adc_sclk O 1 ADC串行数据接口时钟信号
cs O 1 ADC串行数据接口使能信号
data_out O 12 ADC转换结果
adc_done O 1 转换完成信号,完成后输出一个周期高脉冲
adc_state O 1 ADC工作状态:0为空闲状态,1为转换状态

其代码如下:adc_driver.v

//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 PHF的CSDN   
//File name:           adc_driver.v
//Last modified Date:  2020/5/22
//Last Version:        
//Descriptions:        ADC128S022驱动模块
//-------------------------------------------------------------------

module adc_driver(
    input                   clk         ,//系统时钟50MHz
    input                   rst_n       ,//复位
    input   [2:0]           channel     ,//通道选择
    input                   adc_en      ,//使能单次转换,该信号为周期有效高脉冲使能一次转换
    input                   dout        ,//ADC转换结果,由ADC输给FPGA

    output  reg             din         ,//ADC控制信号,通道控制选择
    output  reg             adc_sclk    ,//ADC串行数据接口时钟信号
    output  reg             cs          ,//ADC串行数据接口使能信号
    output  reg[11:0]       data_out    ,//ADC转换结果
    output  reg             adc_done    ,//转换完成信号,完成后输出一个周期高脉冲
    output  reg             adc_state    //ADC工作状态:0为空闲状态,1为转换状态      
);

parameter DIV_CNT = 4'd13; //13分频,输出脉冲为两倍sclk

reg   [  2: 0]         r_channel        ;
reg   [  3: 0]         cnt              ;
wire                   add_cnt          ;
wire                   end_cnt          ;
reg                    sclk2x           ;

reg   [  5: 0]         sclk_gen_cnt     ;

reg   [ 11: 0]         data             ;

//设置下一个数据的adc输入通道
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        r_channel <= 0;
    end
    else if(adc_en) begin
        r_channel <= channel;
    end
    else begin
        r_channel <= r_channel;
    end
end

//adc状态:0为空闲,1为忙
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        adc_state <= 0;
    end
    else if(adc_en) begin
        adc_state <= 1;
    end
    else if(adc_done)begin
        adc_state <= 0;
    end
    else begin
        adc_state <= adc_state;
    end
end

//两倍sclk计数器
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt <= 0;
    end
    else if(add_cnt)begin
        if(end_cnt)
            cnt <= 0;
        else
            cnt <= cnt + 1'b1;
    end
end

assign add_cnt = adc_state==1;       
assign end_cnt = add_cnt && cnt== DIV_CNT-1;   

//两倍sclk输出
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        sclk2x <= 0;
    end
    else if(adc_state && cnt==0) begin
        sclk2x <= 1;
    end
    else begin
        sclk2x <= 0;
    end
end

//sclk2x计数
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        sclk_gen_cnt <= 0;
    end
    else if(adc_state && sclk2x)begin
        if(sclk_gen_cnt==33 || cs==1)
            sclk_gen_cnt <= 0;
        else
            sclk_gen_cnt <= sclk_gen_cnt + 1'b1;
    end
end
  
//输出cs,adc_sclk,din信号
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        cs <= 1;
        adc_sclk <= 1;
        din <= 0;
        data <= 0;
    end
    else if(adc_state && sclk2x) begin
        case(sclk_gen_cnt)
            0 :begin cs <= 0; end
            1 :begin adc_sclk <= 0; end
            2 :begin adc_sclk <= 1; end
            3 :begin adc_sclk <= 0; end
            4 :begin adc_sclk <= 1; end
            5 :begin adc_sclk <= 0; din <= r_channel[2]; end
            6 :begin adc_sclk <= 1; end
            7 :begin adc_sclk <= 0; din <= r_channel[1]; end
            8 :begin adc_sclk <= 1; end
            9 :begin adc_sclk <= 0; din <= r_channel[0]; end
            10,12,14,16,18,20,22,24,26,28,30,32:
                begin 
                    adc_sclk <= 1; 
                    data <= {data[10:0],dout};
                end
            11,13,15,17,19,21,23,25,27,29,31:
                begin 
                    adc_sclk <= 0; 
                end
            33:begin cs <= 1; end    
            default: cs <= 1;
        endcase   
    end
end

//adc转换结束标志
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        adc_done <= 0;
    end
    else if(adc_state && sclk2x && sclk_gen_cnt==33) begin
        adc_done <= 1;
    end
    else begin
        adc_done <= 0;
    end
end

//转换后数据输出
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        data_out <= 0;
    end
    else if(adc_state && sclk2x && sclk_gen_cnt==33) begin
        data_out <= data;
    end
    else begin
        data_out <= data_out;
    end
end

endmodule

仿真测试如下:

为了测试模块功能,模拟ADC芯片的输出。这里用Sin3e产生一个正弦波文件,位宽12位,个数为4096,并以sin_ 12bit.txt 保存当前工程下的simulation目录下的modelsim文件夹。这样就需要将产生的数据,发送到ADC驱动模块的输入线DOUT,上,这里
使用系统任务$readmemb,其可以用来从文件中读取数据到存储器中。$readmemb在本人博客下已有介绍,这里不再赘述。

其测试程序如下:

`timescale 1 ns/ 1 ns
`define sin_data_file "./sin_12bit.txt"
module adc_driver_tb();
// constants                                           
// test vector input registers
reg adc_en;
reg [2:0] channel;
reg clk;
reg dout;
reg rst_n;
// wires                                               
wire adc_done;
wire adc_sclk;
wire adc_state;
wire cs;
wire [11:0]  data_out;
wire din;

reg   [ 11: 0]         memory [4095:0];

parameter clk_period = 20;
integer i;
integer address;

// assign statements (if any)                          
adc_driver i1 (
// port map - connection between master ports and signals/registers   
	.adc_done(adc_done),
	.adc_en(adc_en),
	.adc_sclk(adc_sclk),
	.adc_state(adc_state),
	.channel(channel),
	.clk(clk),
	.cs(cs),
	.data_out(data_out),
	.din(din),
	.dout(dout),
	.rst_n(rst_n)
);
    
initial $readmemb(`sin_data_file,memory);

initial clk = 1;
always #(clk_period/2) clk = ~clk;

initial begin
    #2;
    rst_n=0; channel=0; adc_en=0;
    dout=0; address=0;
    #(clk_period*5);
    rst_n=1;
    #(clk_period*5);
    channel=5;

    for(i=0;i<3;i=i+1)begin
        for(address=0;address<4096;address=address+1)begin
            adc_en=1;
            #(clk_period);
            adc_en=0;
            gene_dout(memory[address]);
            @(posedge adc_done);
            #(clk_period*10);
            $display("%d: %d", address, memory[address]);
        end
    end

    #(clk_period*1000);
    $stop;
end

task gene_dout;
    input [15:0] vdata;
    reg   [4:0 ] cnt;
    begin
        cnt=0;
        wait(!cs);
        while(cnt<16)begin
            @(negedge adc_sclk) dout = vdata[15-cnt];
            cnt =cnt + 1'b1;
        end
    end
endtask

endmodule

仿真结果:

在Modelsim里将data_out用模拟输出显示,操作如下图:

基于FPGA的数据采集系统(二)_第9张图片基于FPGA的数据采集系统(二)_第10张图片

仿真波形如图所示,其结果符合预期结果。

基于FPGA的数据采集系统(二)_第11张图片

你可能感兴趣的:(FPGA学习,#,3,数据采集系统设计)