目录
五、同步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主要信号列表:
信号名称 | 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一直不变,同时读出的数据按照仿真一直递增。
本设计使用的DAC芯片是tlv5618a,该芯片是基于SPI协议的12位的DAC数模转换芯片。下边将对其具体讲解。
(1)TLV5618电路设计
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芯片引脚功能
引脚名 | 编号 | 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转换器,如图所示。
其时序要求为:
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输出电压值。
R1 | R0 | 描述 |
0 | 0 | 写数据到DAC B 和缓冲区 |
0 | 1 | 写数据到缓冲区 |
1 | 0 | 写数据到DAC A 同时将缓冲区数据更新到DAC B |
1 | 1 | 保留 |
经查阅手册可知器件I作频率SCLK最大为20MHz,设计时留下一定余量,因此这里定义其工作频率为12.5MHz。
可用线性序列机思想编写代码。因此只需要在逻辑中使用一个计数器来计数,然后每个计数值时就相当于在
t轴_上对应了一个相应的时间点,那么在这个时间点上,各个信号需要进行什么操作,直接赋值即可。但本模块没有用线性序列机思想编写代码。
如下表为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芯片是adc128s022,该芯片是基于SPI协议的12位8通道ADC模数转换芯片。下边将对其具体讲解。
(1)ADC128s022电路设计
ADC128S022部分电路图如图5.3-2 所示。这里外接了一个抗混叠低通滤波器,其作用是为了避免输入信号的高频成分在ADC的基带中引起混叠。数字电源与模拟电源电压均为3.3V,且数字电源与模拟电源之间串联磁珠用于抑制电磁干扰。
(2)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的控制。
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个下降沿进入保持/转换模式。
下表为ADC128S022的SPI接口时序要求:
进入采样模式有三种方式,第一种当SCLK为高电平时CS变为低,当SCLK第一个下降沿到来时便进入采样模式,如时序图所示;第二种当SCLK为低电平时CS变低,自动进入采样模式,CS的下降沿即视为SCLK的第一个下降沿;第三种SCLK与CS一同变为低,这样对于两信号上升沿没有时序约束,但对于其下降沿有要求。
在DIN的8个控制寄存器,每位代表的含义如表11所示。.
Bit7(MSB) | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
DONTC | DONTC | ADD2 | ADD1 | ADD0 | DONTC | DONTC | DONTC |
其中ADD[2:0]代表的输入通道选择表如表12所示。
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 |
经查阅手册可知器件工作频率SCLK推荐范围为0.8~3.2MHz,这里定义其工作频率为1.92MHz(周期为520ns)。设置一个两倍于SCLK的采样时钟SCLK2X,使用50M系统时钟十三分频而来即SCLK2X为3.84MHz。针对SCLK2X进行计数来确定图25.4中各个信号的状态。结合前面ADC的接口时序图,按照线性序列机的设计思路,可以整理得到每个信号发生变化时对应的时刻以及此时对应的计数器的值。表13为依照线性序列机的设计思想,整理得到的每个信号发生变化时对应的时刻以及此时对应的计数器的值。其中CS N为芯片状态标志信号,SCLK为芯片时钟输入脚,DIN 为芯片串行数据输入,DOUT为芯片串行数据输出。
计数器值 | 对应信号操作 |
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驱动模块的信号端口列表:
信号名称 | 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用模拟输出显示,操作如下图:
仿真波形如图所示,其结果符合预期结果。