官网连载的内容将出版成图书,并将录制视频,免费公开学习,欢迎大家留意。本连载前面是基础部分,与一般教材无异,后面是项目实践,是本连载的特色。如果你有一定的基础(能看懂verilog代码即可),那么可跳过前面部分,直接学习后面的项目实践。项目实践将有16个,从基础的闪烁灯开始,到最后是信号处理的项目,如信号发生器、FIR滤波器、插值滤波器和AD采集等。
本连载学习效果:不仅看能懂代码,还能知道每一行代码怎么写,怎么设计。
第二阶段 项目实践
本文的文档编号:000600000025
需要看对应的视频,请点击视频编号:002700000452
1、本文档讲述FPGA产生两路正弦波数据,一路直接由DA输出,一路经过FIR处理之后输出,然后传到示波器进行观察,从而了解FIR滤波器的效果
2、801开发板使用
第十四章 FIR滤波器设计
1 项目背景
1.1 FIR和IIR滤波器
FIR(Finite Impulse Response)Filter:有限冲激响应滤波器,又称为非递归线性滤波器。
FIR滤波器,顾名思义,其脉冲响应由有限个采样值构成。长度(抽头数)为N、阶数为N?1的FIR系统的转移函数、差分方程和单位冲激响应分别如下列三式所示。
图 510
IIR(Infinite Impulse Response)Filter:无限冲激响应滤波器,又称为递归线性滤波器。
FIR相对与IIR来说,具有如下的优点:
- 可以具备线性相位特性
线性相位的概念: 如果滤波器的N个实值系数为对称或者反对称结构,该滤波器具有线性相位。 W(n)=±W(N?1?n)W(n)=±W(N?1?n)
线性相位的特性:通过线性相位滤波器的信号的所有频率部分具有相同的延迟量。
- 易于设计
但FIR也有自身的缺点:同样指标的滤波器,FIR需要更多的参数,即实现时消耗更多的计算单元,产生更大的延迟。
1.2 FIR滤波器的原理
信号通过一个FIR滤波器其实就是信号与FIR滤波器的系数进行卷积(即乘累加)的过程。我们以一个简单信号模型为例,了解一下FIR波形器的原理。
现在有三组信号,分别是:
信号1:低频信号,即在时域上变化慢的信号,其输入先后为1 1 1 1 2 2 2 2。
信号2:直流信号,其输入先后为1 1 1 1 1 1 1 。
信号3:高频信号,即在时域上变化快的信号,其输入先后为1 2 1 2 1 2 1 2 。
简单的滤波器模型
低通滤波器:1 1
信号1与低通滤波器进行卷积运算,其结果再除以2,得到如下数据:1 1 1 1.5 2 2 2。可以看到,低频信号经过低通滤波器后,各个点仍然保持了其形状,而且在1变成2时,还变平缓了。
信号2与低通滤波器进行卷积运算,其结果再除以2,得到如下数据:1 1 1 1 1 1 1。可以看到,直流信号与输入的信号完成相同。
信号3与低通滤波器进行卷积运算,其结果再除以2,得到如下数据:1.5 1.5 1.5 1.5 1.5 1.5 1.5。可以看到,高频信号经过低通滤波器后,已经完成消去了形状,变成了直流信号。
再考虑另一种滤波器模型,高通滤波器:1 -1
信号1与高通滤波器进行卷积运算,其结果再除以2,得到如下数据:0 0 0 -0.5 0 0 0。可以看到,低频信号经过高通滤波器后,信号变化基本上消失。
信号2与低通滤波器进行卷积运算,其结果再除以2,得到如下数据:0 0 0 0 0 0 0。可以看到,直流信号仍然是没有变化。
信号3与低通滤波器进行卷积运算,其结果再除以2,得到如下数据:-0.5 0.5 -0.5 0.5 -0.5 0.5 -0.5 0.5。可以看到,高频信号已经仍然保持了变化的形状。
由这两个例子可以看出,FIR滤波器其实就是信号与FIR滤波器的系数进行卷积(即乘累加)的过程。通过调整滤波器系数、抽头个数,就可实现低通、高通、带通等滤波器。
1.3 FIR滤波器的设计
1.3.1 matlab产生滤波器系数
打开matlab在其命令窗口输入fdatool 按下回车
调出FIR滤波器的设计界面
图 511
在波形设计界面中,我们重要关注以下选项。
Response Type:选择可以选择滤波器的类型,可选择:lowpass低通滤波器、Highpass高通滤波器、bandpass带通滤波器、bandstop带阻滤波器。
Fs(采样频率):
Fstop :信号截止频率
Fpass:
Filter Order:用来设置滤波器的抽头个数。可以在specify order中输入个数,也可以选择Minimum order,让系统计算满足要求的前提下的最小抽头个数。
点击Design Filter,就可以计算出抽头系数。
产生系数后点击file 菜单里的Export 将系数保存到工作区
图 512
点击export
图 513
点击之后打开工作区里的Num
图 514
而后将下图第一列的数据复制粘贴到txt文件中
图 515
注意复制后需在两个系数间插入逗号(英文输入状态下的的逗号)
图 516
这样就得到滤波器的系数了。
1.3.2 FPGA生成FIR IP核
打开工程后,在IP catalog这一界面中选择DSP下一目录中选择Filter 在选择选择 FIR II
图 517
首先在Fitter这一界面做如下操作
图 518
Filter Type
Interpolation Factor:
Decimation Factor:
Max Number of channels:
Clock rate:填写本IP核的工作时钟频率。
clock slack:
Input sample rate (msps):采样率
点击coefficients,进入coefficients界面。
这一界面点击import from file ,弹出一下界面,找出我们之前用matlab生成的系数文件,点击import,导入成功后可以看到下图的 frequency response界面的波形发生变化。
图 519
在coefficient界面,还可以设置系数的格式、数据位宽等。
图 520
其它选项不改按默认的来点击finish即可。
以上就是生成 FIR滤波器的主要步骤。
2 设计目标
本次案例将使用到采样率大于100M的双通道的示波器。将示波器的两个通道,分别与FPGA的DA通道1和DA通道2相连,观察两路DA的输出。其连接示意如下图所示。
图 521
本案例是FPGA内部产生正弦信号,这个正弦信号一路输出给DA通道1,另一路经过FIR滤波器后,输出给DA通道2。
图 522
正弦信号的频率受开发板上的3个拨码开关控制,用3位信号key表示,一共可以产生8种频率。
正弦信号的频率 约等于: 100KHz * (key+1)。
例如,当key等于0时,产生约100KHz的正弦信号;
当key等于1时,产生约200KHz的正弦波;
当key等于7时,产生约800KHz的正弦波。
FIR滤波器是低通滤波器,其截止频率是500KHz,这样原则上超过500KHz的信号就会被滤除。滤波器的输出给通道2。
下面是示波器的显示效果,其中黄色是通道1输出的信号(上面的波形),下面蓝色是通道2的输出信号(下面的波形)。
下图是100KHz的信号图。
100KHz的信号图
下图是200KHz的信号图。
200KHz的信号图
下图是300KHz的信号图。
300KHz的信号图
下图是400KHz的信号图,可以看到已经衰减了。
400KHz的信号图
下图是500KHz的信号图,可以看到已经衰减的很小了。
500KHz的信号图
下图是600KHz的信号图,可以看到通道2已经没有波形。
600KHz的信号图
下图是700KHz的信号图,可以看到通道2已经没有波形。
700KHz的信号图
下图是800KHz的信号图,可以看到通道2已经没有波形。
800KHz的信号图
3 设计实现
3.1 顶层接口
新建目录:D:mdy_bookir_prj。在该目录中,新建一个名为fir_prj.v的文件,并用GVIM打开,开始编写代码。
我们要实现的功能,概括起来就是FPGA产生控制AD9709,让其中的通道A未滤波的正弦信号,让通道B输出滤波后的正弦信号。为了控制AD9709的工作模式,就要控制AD9709的MODE、SLEEP管脚;为了控制通道A,就需要控制AD9729的CLK1、WRT1、DB7~0P1管脚;为了控制通道B,就需要控制AD9729的CLK2、WRT2、DB7~0P2管脚。根据设计目标的要求, 整个工程需要以下信号:
1. 使用clk连接到晶振,表示50M时钟的输入。
2. 使用rst_n连接到按键,表示复位信号。
3. 使用3位信号key,表示三位拨码开关。
4. 使用dac_mode信号连接到AD9709的MODE管脚,用来控制其工作模式。
5. 使用dac_sleep信号连接到AD9709的SLEEP管脚,用来控制其睡眠模式。
6. 使用dac_clka信号连接到AD9709的CLK1管脚,用来控制通道A的时钟。
7. 使用dac_wra信号连接到AD9709的WRT1管脚,用来控制通道A的写使能。
8. 使用8位信号dac_da连接到AD9709的DB7~0P1管脚,用来控制通道A的写数据。
9. 使用dac_clkb号连接到AD9709的CLK2脚,用来控制通道B时钟。
10. 使用dac_wrb号连接到AD9709的WRT2脚,用来控制通道B使能。
11. 使用8位信号dac_db接到AD9709的DB7~0P2脚,用来控制通道B写数据。
综上所述,我们这个工程需要11个信号,时钟clk,复位rst_n,拨码开关的输入key,dac_mode、dac_sleep、dac_clka、dac_wra、dac_da、dac_clkb、dac_wrb和dac_db信号,其中dac_da和dac_db是8位信号,其他都是1位信号。下面表格表示了硬件电路图的连接关系。
器件 |
AD9709管脚 |
原理图信号 |
FPGA管脚 |
FPGA工程信号 |
U8 |
MODE |
DAC_MODE |
Y4 |
dac_mode |
SLEEP |
DAC_SLEEP |
H2 |
dac_sleep |
|
CLK1 |
DA_CLKA |
R2 |
dac_clka |
|
WRT1 |
DA_WRA |
U1 |
dac_wra |
|
DB7P1 |
DAC_DA7 |
AA1 |
dac_da[7] |
|
DB6P1 |
DAC_DA6 |
Y2 |
dac_da[6] |
|
DB5P1 |
DAC_DA5 |
Y1 |
dac_da[5] |
|
DB4P1 |
DAC_DA4 |
W2 |
dac_da[4] |
|
DB3P1 |
DAC_DA3 |
W1 |
dac_da[3] |
|
DB2P1 |
DAC_DA2 |
V2 |
dac_da[2] |
|
DB1P1 |
DAC_DA1 |
V1 |
dac_da[1] |
|
DB0P1 |
DAC_DA0 |
U2 |
dac_da[0] |
|
CLK2 |
DA_CLKB |
R1 |
dac_clkb |
|
WRT2 |
DA_WRB |
P2 |
dac_wrb |
|
DB7P2 |
DAC_DB7 |
P1 |
dac_db[7] |
|
DB6P2 |
DAC_DB6 |
N2 |
dac_db[6] |
|
DB5P2 |
DAC_DB5 |
N1 |
dac_db[5] |
|
DB4P2 |
DAC_DB4 |
M2 |
dac_db[4] |
|
DB3P2 |
DAC_DB3 |
M1 |
dac_db[3] |
|
DB2P2 |
DAC_DB2 |
J1 |
dac_db[2] |
|
DB1P2 |
DAC_DB1 |
J2 |
dac_db[1] |
|
DB0P2 |
DAC_DB0 |
H1 |
dac_db[0] |
|
X1 |
|
SYS_CLK |
G1 |
clk |
K1 |
|
SYS_RST |
AB12 |
rst_n |
将module的名称定义为fir_prj,代码如下:
1 2 3 4 5 6 7 8 9 |
module fir_prj( clk , rst_n , key , dac_mode , dac_sleep , dac_clka , dac_da , dac_wra , dac_clkb , dac_db , dac_wrb ); |
其中clk、rst_n是1位的输入信号,dac_da和dac_db是8位的输出信号,key是3位输入信号,dac_mode,dac_clka,dac_wra,dac_sleep,dac_clkb,dac_wrb是一位输出信号。
1 2 3 4 5 6 7 |
input clk ; input rst_n ; input [ 3-1:0] key ; output dac_mode ; output dac_clka ; output [ 8-1:0] dac_da ; output dac_wra ; output dac_sleep ; output dac_clkb ; output [ 8-1:0] dac_db ; output dac_wrb ;
|
3.2 正弦信号设计
假设产生的正弦信号命名为sin_data信号。sin_data是从表XX中选择出来的值,该表一共有128个点。该表的产生方法,请看案例“信号发生器和DA转换”一章的内容。
采样点i
sin_data
(16进制)
采样点i
sin_data
(16进制)
采样点i
sin_data
(16进制)
采样点i
sin_data
(16进制)
0
7F
32
FE
64
7D
96
1
1
85
33
FE
65
77
97
1
2
8C
34
FE
66
70
98
2
3
92
35
FD
67
6A
99
3
4
98
36
FC
68
64
100
4
5
9E
37
FA
69
5E
101
6
6
A4
38
F8
70
58
102
7
7
AA
39
F6
71
52
103
A
8
B0
40
F4
72
4C
104
C
9
B6
41
F1
73
46
105
F
10
BC
42
EF
74
41
106
12
11
C1
43
EB
75
3C
107
15
12
C6
44
E8
76
36
108
19
13
CB
45
E4
77
31
109
1D
14
D0
46
E0
78
2C
110
21
15
D5
47
DC
79
28
111
25
16
DA
48
D8
80
23
112
2A
17
DE
49
D3
81
1F
113
2E
18
E2
50
CE
82
1B
114
33
19
E6
51
C9
83
17
115
38
20
EA
52
C4
84
14
116
3E
21
ED
53
BE
85
11
117
43
22
F0
54
B9
86
E
118
49
23
F3
55
B3
87
B
119
4E
24
F5
56
AD
88
9
120
54
25
F7
57
A7
89
7
121
5A
26
F9
58
A1
90
5
122
60
27
FB
59
9B
91
3
123
67
28
FC
60
95
92
2
124
6D
29
FD
61
8F
93
1
125
73
30
FE
62
89
94
1
126
79
31
FE
63
82
95
1
127
7F
很自然地定义一个7位的选择信号addr。我们只要控制好addr,就能方便得到sin_data。因此可以写出下面代码。
1 2 3 4 5 6 7 8 9 |
always @(*)begin case(addr) 0: sin_data = 8'h7F; 1: sin_data = 8'h85; 2: sin_data = 8'h8C; 3: sin_data = 8'h92; 4: sin_data = 8'h98; 5: sin_data = 8'h9E; 6: sin_data = 8'hA4; 7: sin_data = 8'hAA; 8: sin_data = 8'hB0; 9: sin_data = 8'hB6; 10: sin_data = 8'hBC; 11: sin_data = 8'hC1; 12: sin_data = 8'hC6; 13: sin_data = 8'hCB; 14: sin_data = 8'hD0; 15: sin_data = 8'hD5; 16: sin_data = 8'hDA; 17: sin_data = 8'hDE; 18: sin_data = 8'hE2; 19: sin_data = 8'hE6; 20: sin_data = 8'hEA; 21: sin_data = 8'hED; 22: sin_data = 8'hF0; 23: sin_data = 8'hF3; 24: sin_data = 8'hF5; 25: sin_data = 8'hF7; 26: sin_data = 8'hF9; 27: sin_data = 8'hFB; 28: sin_data = 8'hFC; 29: sin_data = 8'hFD; 30: sin_data = 8'hFE; 31: sin_data = 8'hFE; 32: sin_data = 8'hFE; 33: sin_data = 8'hFE; 34: sin_data = 8'hFE; 35: sin_data = 8'hFD; 36: sin_data = 8'hFC; 37: sin_data = 8'hFA; 38: sin_data = 8'hF8; 39: sin_data = 8'hF6; 40: sin_data = 8'hF4; 41: sin_data = 8'hF1; 42: sin_data = 8'hEF; 43: sin_data = 8'hEB; 44: sin_data = 8'hE8; 45: sin_data = 8'hE4; 46: sin_data = 8'hE0; 47: sin_data = 8'hDC; 48: sin_data = 8'hD8; 49: sin_data = 8'hD3; 50: sin_data = 8'hCE; 51: sin_data = 8'hC9; 52: sin_data = 8'hC4; 53: sin_data = 8'hBE; 54: sin_data = 8'hB9; 55: sin_data = 8'hB3; 56: sin_data = 8'hAD; 57: sin_data = 8'hA7; 58: sin_data = 8'hA1; 59: sin_data = 8'h9B; 60: sin_data = 8'h95; 61: sin_data = 8'h8F; 62: sin_data = 8'h89; 63: sin_data = 8'h82; 64: sin_data = 8'h7D; 65: sin_data = 8'h77; 66: sin_data = 8'h70; 67: sin_data = 8'h6A; 68: sin_data = 8'h64; 69: sin_data = 8'h5E; 70: sin_data = 8'h58; 71: sin_data = 8'h52; 72: sin_data = 8'h4C; 73: sin_data = 8'h46; 74: sin_data = 8'h41; 75: sin_data = 8'h3C; 76: sin_data = 8'h36; 77: sin_data = 8'h31; 78: sin_data = 8'h2C; 79: sin_data = 8'h28; 80: sin_data = 8'h23; 81: sin_data = 8'h1F; 82: sin_data = 8'h1B; 83: sin_data = 8'h17; 84: sin_data = 8'h14; 85: sin_data = 8'h11; 86: sin_data = 8'hE ; 87: sin_data = 8'hB ; 88: sin_data = 8'h9 ; 89: sin_data = 8'h7 ; 90: sin_data = 8'h5 ; 91: sin_data = 8'h3 ; 92: sin_data = 8'h2 ; 93: sin_data = 8'h1 ; 94: sin_data = 8'h1 ; 95: sin_data = 8'h1 ; 96: sin_data = 8'h1 ; 97: sin_data = 8'h1 ; 98: sin_data = 8'h2 ; 99: sin_data = 8'h3 ; 100: sin_data = 8'h4 ; 101: sin_data = 8'h6 ; 102: sin_data = 8'h7 ; 103: sin_data = 8'hA ; 104: sin_data = 8'hC ; 105: sin_data = 8'hF ; 106: sin_data = 8'h12; 107: sin_data = 8'h15; 108: sin_data = 8'h19; 109: sin_data = 8'h1D; 110: sin_data = 8'h21; 111: sin_data = 8'h25; 112: sin_data = 8'h2A; 113: sin_data = 8'h2E; 114: sin_data = 8'h33; 115: sin_data = 8'h38; 116: sin_data = 8'h3E; 117: sin_data = 8'h43; 118: sin_data = 8'h49; 119: sin_data = 8'h4E; 120: sin_data = 8'h54; 121: sin_data = 8'h5A; 122: sin_data = 8'h60; 123: sin_data = 8'h67; 124: sin_data = 8'h6D; 125: sin_data = 8'h73; 126: sin_data = 8'h79; 127: sin_data = 8'h7F; endcase end |
接下来是设计信号addr。
addr是用来控制选择数据的地址,通过控制addr的增加值,就能产生多种频率的正弦波。
以频率为100KHz的正弦信号为例。该正弦信号的周期是10000ns。本工程的工作时钟是20ns,也就是10000/20 = 500个时钟输出一个正弦信号,也就是500个时钟将上表的128个值输出一遍。因此每个时钟addr增加的值:128/500 = 0.256。
按同样的分析方法,可以得到其他信号频率的addr增加值,总结如下。
100KHz的正弦信号,每个时钟addr增加:128/250 = 0.256
200KHz的正弦信号,每个时钟addr增加:128/250 = 0.512
300KHz的正弦信号,每个时钟addr增加:128/166.6667 = 0.7679
400KHz的正弦信号,每个时钟addr增加:128/125 = 1.024
500KHz的正弦信号,每个时钟addr增加:128/100 = 1.28
600KHz的正弦信号,每个时钟addr增加:128/83.3333 = 1.5358
700KHz的正弦信号,每个时钟addr增加:128/71.4286 = 1.792
800KHz的正弦信号,每个时钟addr增加:128/62.5 = 2.048
由于addr是表示0~127的整数,而addr每次增加的值包含小数,而FPGA是没有小数的。为此,我们将上面的小数乘以1024,然后取整,就变成了每次要增加的整数,结果保存到addr_tmp中。即:
100KHz的正弦信号,每个时钟addr_tmp增加:0.256 *1024 = 262.144 ≈ 262
200KHz的正弦信号,每个时钟addr_tmp增加:0.512 *1024 = 524.288 ≈ 524
300KHz的正弦信号,每个时钟addr_tmp增加:0.7679 *1024 =786.3296 ≈ 786
400KHz的正弦信号,每个时钟addr_tmp增加:1.024 *1024 =1028.576 ≈ 1029
500KHz的正弦信号,每个时钟addr_tmp增加:1.28 *1024 =1310.72 ≈ 1311
600KHz的正弦信号,每个时钟addr_tmp增加:1.5358 *1024 =1572.6592 ≈1573
700KHz的正弦信号,每个时钟addr_tmp增加:1.792 *1024 =1835.008 ≈ 1835
800KHz的正弦信号,每个时钟addr_tmp增加: 2.048 *1024 =2097.152 ≈ 2097
而上面8种频率信号,是由拨码信号key控制的。因此,可以写出addr_tmp的代码。
1 2 3 |
always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin addr_tmp <= 0; end else if(key==0) begin addr_tmp <= addr_tmp + 262; end else if(key==1) begin addr_tmp <= addr_tmp + 524; end else if(key==2) begin addr_tmp <= addr_tmp + 786; end else if(key==3) begin addr_tmp <= addr_tmp + 1029; end else if(key==4) begin addr_tmp <= addr_tmp + 1311; end else if(key==5) begin addr_tmp <= addr_tmp + 1573; end else if(key==6) begin addr_tmp <= addr_tmp + 1835; end else begin addr_tmp <= addr_tmp + 2097; end end |
1 |
assign addr = addr_tmp >>10 ; |
3.3 FIR滤波器设计
3.3.1 matlab生成FIR系数
打开matlab,在其命令窗口输入fdatool 按下回车调出波形设计界面。
在波形设计界面中
Response Type:案例要求滤波高于500KHz的信号,所以选择lowpass低通滤波器
Fstop: 截止频率设为600KHz
Fs:采样频率: 12.5MHz(12500Khz)
图 526
其它选项默认点击Design Filter
产生系数后点击file 菜单里的Export 将系数保存的工作区
图 527
点击export
图 528
点击之后打开工作区里的Num
图 529
而后将下图第一列的数据复制粘贴到txt文件中
图 530
注意复制后需在两个系数间插入逗号(英文输入状态下的的逗号)
图 531
3.3.2 新建FPGA工程
图 532
1.打开quartus,点击File 在File菜单中选择New Project Wizard.... 。
图 533
2.弹出Introduction界面选择Next。
图 534
3.设置工程目录,工程名,顶层模块名
工程目录设置为:D:mdy_bookir_prj
工程名:fir_prj
顶层模块名:fir_prj
填写完毕后,点击next之后进入下一界面。
图 535
工程类型界面,Project Type选择Empty project,选择空白工程。点Next进入下一个界面。
图 536
4.在文件添加界面,不选择任何文件。点击Next,进入下一个界面。
图 537
5.器件选择界面。在Device family这一项之中选择 Cyclone IV E;在下部的Available device 选择EP4CE6F23C8。完成后直接点击Finish。
3.3.3 FPGA生成FIR IP核
图 538
建立工程后,在quartus中IP catalog这一界面中选择DSP下一目录中选择Filter 再选择 FIR II。
图 539
点击后进入此界面给新生成的fir滤波器ip核选择如下路径:D:mdy_bookir_prjmy_fir.v,IPvariation file name这一项选择verilog。点击OK后,进入FIR滤波器设置界面。
图 540
在Fitter specification界面按如下设置:
Filter Type:要选择Single Rate,表示只采用一种采样率。
Clock Rate:因为我们工程使用的是50MHz时钟,所以此处要填写50MHz。
Input Sample Rate (PSPS):这个是填采样率。和matlab相匹配,因此要填12.5MHz。
其他参数默认。
然后点击coefficients选项卡。
图 541
单击import from file ,在输出的界面中,找出我们用MATLAB生成的系数文件:my_fir_coe.txt,点击import导入。导入成功后可以看到下图的 frequency response界面的波形发生变化。
在Coefficient Bit Width中,填写16。表示每个系数用16比特量化。
图 543
如上图,在Input/Output Options选项卡中,做如下设置。
Input Type:选择Signed Binary,表示输入的数据是有符号数(补码形式)。
Input Width:输入8。表示输入的数据是8位位宽。
Output Type:选择Signed Binary,表示输出的数据是有符号数(补码形式)。
MSB Rounding:选择Truncation。表示输出结果的高位要截断。
MSB Bits to Remove:填写3。表示MSB要截取3个符号位。
LSB Rounding:选择Truncation。表示输出结果的低位要截断。
LSB Bits to Remove:填写19。表示LSB要截断低19位。这样最终输出的结果就是8位。
其他选项默认,点击Finish,软件就会去生成FIR IP核。
图 544
出现上面的提示,就是生成成功了。
图 545
IP核生成后弹出此对话框点击yes 将此IP核添加进工程。
3.3.4 例化FIR IP核
用GVIM打开D:mdy_bookir_prjmy_fir.v文件,该文件就是生成的FIR IP核文件。
图 546
my_fir模块的各个信号的描述见下表。
信号名
I/O
位宽
作用
clk
I
1
时钟输入信号。在FIR设置时,就已经填写了50MHz,所以此时连接50MHz时钟。
reset_n
I
1
复位信号,低电平有效。
ast_sink_data
I
8
FIR滤波器输入的数据输入。注意,输入的是有符号数。
ast_sink_valid
I
1
FIR滤波器输入的数据有效指示信号。
ast_sink_error
I
1
输入数据错误指示信号。实在想不出有啥错误情况,所以此处直接填0。
ast_source_data
O
8
FIR滤波器的输出。注意,是有符号数。
ast_source_valid
O
1
FIR滤波器输出有效指示信号。由于输入一直有效,那么输出也一直有效,此信号可以忽略不用,例化时不连接。
ast_source_error
O
2
FIR滤波器输出错误指示信号。由于输入没错误,输出也不会有错误,所以可以忽略该信号,例化时不连接。
特别注意的是,滤波器的输入数据和输出数据都是有符号数(补码的形式,-128~127)。而我们知道,正弦信sin_data是无符号数(0~255)。所以要将sin_data变成有符号数,再送给FIR进行滤波。假设转换后的信号为fir_din,该信号位宽为8位。
无符号数转成有符号数的方法很简单:fir_din = sin_data - 128。读者有兴趣可以验证一下。
生成FIR IP核后,我们要对其进行例化,才行使用上这个IP核,例化名起名u_my_fir,fir的输出数据信号命名为fir_dout。
1 2 3 4 5 6 7 8 |
assign fir_din = sin_data - 128;
my_fir u_my_fir( .clk (clk ) , .reset_n (rst_n ) , .ast_sink_data (fir_din ) , .ast_sink_valid (1 ) , .ast_sink_error (0 ) , .ast_source_data (fir_dout) , .ast_source_valid( ) , .ast_source_error( ) ); |
3.4 DA接口信号设计
接下来是设计信号dac_da。dac_da是直接输出正弦信号,但由于DA的输出电压与dac_da是成反比例线性关系,所以dac_da都是按(255-sin_data)得到。那么可以写出dac_da的代码。
1 2 3 4 5 6 7 8 |
always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin dac_da <= 0; end else begin dac_da <= 255 - sin_data; end end |
接下来是设计信号dac_sleep,AD是一直工作的,所以要让dac_sleep一直为0。
dac_clka为了满足tS的时间要求,可以让dac_clka = ~clk。
dac_wra可以与dac_clka相同。
1 2 3 |
assign dac_sleep = 0 ; assign dac_wra = dac_clka ; assign dac_clka = ~clk ; |
接下来是设计信号dac_db。dac_db是直接输出滤波后的信号fir_dout。但要注意的是fir_dout是有符号数(范围是-128~127),所以要转有无符号数(0~255)。假设转换后的信号为fir_dout2,则fir_dout2 = fir_dout + 128。另外,由于DA的通道2的输出电压与dac_db是成反比例线性关系,所以dac_db都是按(255-fir_dout2)得到。那么可以写出dac_db的代码。
1 2 3 4 5 6 7 8 |
assign fir_dout2 = fir_dout + 128; always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin dac_db<= 0; end else begin dac_db<= 255 - fir_dout2; end end |
dac_clkb为了满足tS的时间要求,可以让dac_clkb = ~clk。
dac_wrb可以与dac_clkb相同。
1 2 3 |
assign dac_wrb = dac_clkb ; assign dac_clkb = ~clk ; |
3.5 信号定义
至此,模块主体已经完成。接下来是将module补充完整。
addr是用assign设计的,因此类型为wire。其值最大为127,一共有7根线,位宽为7,故而代码如下
1 |
wire [6:0] addr ; |
addr_tmp是用always设计的,因此类型为reg。如前面所述,该信号的位宽是17,故而代码如下
1 |
reg [16:0] addr_tmp ; |
sin_data是用always设计的,因此类型为reg。其最大值为255,要有8根线表示,位宽为8,故而代码如下
1 |
reg [7:0] sin_data ; |
fir_din是用assign设计的,因此类型为wire。其位宽为8,故而代码如下
1 |
wire [7:0] fir_din ; |
fir_dout是用例化模块的输出,非always设计的,因此类型为wire。其位宽为8,故而代码如下
1 |
wire [7:0] fir_dout ; |
fir_dout2是用assign设计的,非always设计的,因此类型为wire。其位宽为8,故而代码如下
1 |
wire [7:0] fir_dout2 ; |
dac_da是用always设计的,因此类型为reg。其位宽为8;dac_sleep是用assign设计的,因此类型为wire,位宽为1;dac_wra是用assign设计的,因此类型为wire,位宽为1;dac_clka是用assign设计的,因此类型为wire,位宽为1;dac_mode是用assign设计的,因此类型为wire,位宽为1。故而代码如下
1 |
reg [7:0] dac_da ; wire dac_sleep ; wire dac_wra ; wire dac_clka ; wire dac_mode ; |
dac_db是用always设计的,因此类型为reg。其位宽为8;dac_wrb是用assign设计的,因此类型为wire,位宽为1;dac_clkb是用assign设计的,因此类型为wire,位宽为1。故而代码如下
1 |
reg [7:0] dac_db ; wire dac_wrb ; wire dac_clkb ; |
在代码的最后一行写下endmodule
1 |
endmodule |
至此,整个代码的设计工作已经完成。下一步是新建工程和上板查看现象。
4 综合与上板
4.1 添加文件到工程
图 547
1.前面已经介绍了新建工程。现在打开quartus,在Project菜单中选择Add/Remove File to Project,弹出文件窗口。
图 548
点击右上角的,在弹出来的窗口中,双击选择D:mdy_bookir_prj目录下的fir_prj.v文件。然后记得要点Add,才算正式加到工程。
图 549
点OK关闭本窗口。
4.2 综合
图 550
在菜单栏中,选中Processing,然后选择Start Compilation,开始对整个工程进行编译和综合。
图 551
出现上面的界面,就说明编译综合成功。
4.3 配置管脚
图 552
在菜单栏中,选中Assignments,然后选择Pin Planner,就会弹出配置管脚的窗口。
图 553
在配置窗口中的location一列,可以填写每个管脚所对应的FPGA管脚号。
器件 |
AD9709管脚 |
原理图信号 |
FPGA管脚 |
FPGA工程信号 |
U8 |
MODE |
DAC_MODE |
Y4 |
dac_mode |
SLEEP |
DAC_SLEEP |
H2 |
dac_sleep |
|
CLK1 |
DA_CLKA |
R2 |
dac_clka |
|
WRT1 |
DA_WRA |
U1 |
dac_wra |
|
DB7P1 |
DAC_DA7 |
AA1 |
dac_da[7] |
|
DB6P1 |
DAC_DA6 |
Y2 |
dac_da[6] |
|
DB5P1 |
DAC_DA5 |
Y1 |
dac_da[5] |
|
DB4P1 |
DAC_DA4 |
W2 |
dac_da[4] |
|
DB3P1 |
DAC_DA3 |
W1 |
dac_da[3] |
|
DB2P1 |
DAC_DA2 |
V2 |
dac_da[2] |
|
DB1P1 |
DAC_DA1 |
V1 |
dac_da[1] |
|
DB0P1 |
DAC_DA0 |
U2 |
dac_da[0] |
|
CLK2 |
DA_CLKB |
R1 |
dac_clkb |
|
WRT2 |
DA_WRB |
P2 |
dac_wrb |
|
DB7P2 |
DAC_DB7 |
P1 |
dac_db[7] |
|
DB6P2 |
DAC_DB6 |
N2 |
dac_db[6] |
|
DB5P2 |
DAC_DB5 |
N1 |
dac_db[5] |
|
DB4P2 |
DAC_DB4 |
M2 |
dac_db[4] |
|
DB3P2 |
DAC_DB3 |
M1 |
dac_db[3] |
|
DB2P2 |
DAC_DB2 |
J1 |
dac_db[2] |
|
DB1P2 |
DAC_DB1 |
J2 |
dac_db[1] |
|
DB0P2 |
DAC_DB0 |
H1 |
dac_db[0] |
|
X1 |
|
SYS_CLK |
G1 |
clk |
K1 |
|
SYS_RST |
AB12 |
rst_n |
按上面配置好每个信号的管脚,其最终效果如下图。
图 554
关闭Pin Planner,软件自动会保存管脚配置信息。
4.4 再次综合
图 555
在菜单栏中,选中Processing,然后选择Start Compilation,开始对整个工程进行编译和综合。
图 556
出现上面的界面,就说明编译综合成功。
4.5 连接开发板
图 557
连接示意如上图所示。将电源接上开发板;USB BLASTER一端连接到JTAG插口,另一端连到PC的USB接口;将开发板上的P7接口和P11与示波器的两个通道相连。最后再将电源打开。
4.6 上板
图 558
在quartus的Task窗口中,右键Program Device 选择Open 进入烧录界面。
图 559
在上面的界面中,默认会选中文件output/fir_prj.sof,如果没有生成请看XXXX。
在上面的界面中,Hardware Setup的旁边会显示:USB-Blaster。如果不是,请看XXXX。
图 560
点击statr,在progress这一条显示100%即表示成功,此时可以看FPGA输出效果了。
更多FPGA技术资讯:微信公众号 fpga资讯
返回目录 >> 明德扬FPGA实验手册