本文主要利用Vivado工具,结合实际应用进行巩固学习,重点对设计思路进行理解
PLL的IP核配置
1.配置过程
PLL实际相当于FPGA内部的一个时钟管理模块,能提供不同频率、相位、占空比的时钟
配置过程:
2.实例应用
- 利用PLL进行时钟分频,并通过PLL产生的4个不同频率的时钟,分别驱动4个LED指示灯闪烁一样的频率
2.1源文件
PLL_design.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
module PLL_design(
input i_sys_clk, //外部输入50MHz时钟信号
input i_rst_n, //外部输入复位信号,低电平有效
output[7:0] o_led //8个LED指示灯亮灭控制
);
wire clk_12m5; //PLL输出12.5MHz时钟
wire clk_25m; //PLL输出25MHz时钟
wire clk_50m; //PLL输出50MHz时钟
wire clk_100m; //PLL输出100MHz时钟
wire sys_rst_n; //PLL输出的locked信号,作为FPGA内部的复位信号,低电平复位,高电平正常工作
//`define SIMULATION_AT7 //仿真取消注释,实际应用则加上注释
clk_wiz_0 clk_wiz_0_U1(
// Clock out ports
.clk_out1(clk_12m5), // output clk_out1
.clk_out2(clk_25m), // output clk_out2
.clk_out3(clk_50m), // output clk_out3
.clk_out4(clk_100m), // output clk_out4
// Status and control signals
.reset(!i_rst_n), // input reset
.locked(sys_rst_n), // output locked
// Clock in ports
.clk_in1(i_sys_clk) // input clk_in1
);
//-------------------------------------
//12.5MHz时钟进行分频闪烁,计数器为23位
led_controller #(`PARAMETER_COUNTER)
u2_led_controller_clk12m5(
.clk(clk_12m5), //时钟信号
.rst_n(sys_rst_n), //复位信号,低电平有效
.sled(o_led[0]) //LED指示灯接口
);
//-------------------------------------
//25MHz时钟进行分频闪烁,计数器为24位
led_controller #(`PARAMETER_COUNTER+1)
u3_led_controller_clk25m(
.clk(clk_25m), //时钟信号
.rst_n(sys_rst_n), //复位信号,低电平有效
.sled(o_led[1]) //LED指示灯接口
);
//-------------------------------------
//50MHz时钟进行分频闪烁,计数器为25位
led_controller #(`PARAMETER_COUNTER+2)
u4_led_controller_clk50m(
.clk(clk_50m), //时钟信号
.rst_n(sys_rst_n), //复位信号,低电平有效
.sled(o_led[2]) //LED指示灯接口
);
//-------------------------------------
//100MHz时钟进行分频闪烁,计数器为26位
led_controller #(`PARAMETER_COUNTER+3)
u5_led_controller_clk100m(
.clk(clk_100m), //时钟信号
.rst_n(sys_rst_n), //复位信号,低电平有效
.sled(o_led[3]) //LED指示灯接口
);
//-------------------------------------
//高4位LED指示灯关闭
assign o_led[7:4] = 4'b1111;
endmoduleled_controller.v
1 |
|
2.2Testbench
1 |
|
2.3仿真结果
自定义IP核的创建与配置
1.IP核的创建
工程源码:
1 |
|
2.IP核的配置
配置页面:
第一个配置页面名为 Identification,即用户定制 IP 核相关的配置信息,如 IP 核供应商(Vendor)、库名称 (Library)、IP 核名称(Name)、版本号(Version)、IP 核显示名称(Display name)、描 述(Description)、供应商显示名称(Vendor display name)、公司网址(Company url) 等。特别提醒大家别忽略了最下面的 Categories 项,默认是空白的,若点击右侧的小加号, 可以增加一个名称,例如本实例增加了一个名为 UserIP 的名称选项,将来生成的用户定制 IP 核在我们的 IP 核配置面板中将会归类到名为 UserIP 类别的文件夹下
第二个配置页面名为Compatibility,即设定该 IP 核所支持的器件家族(Family)
第三个配置页面名为File Groups,可以预览 IP 核包含的相关源码文件,在源码工程中包含的所有Verilog源码或者仿真测试脚本,也都会出现在这里,被集成到IP核中
第四个配置页面名为Customization Parameters,配置页面罗列源码中所有可配置的参数 (即parameter 所定义的)
第五个配置页面名为Ports and Interfaces,配置页面显示IP核的对外接口
第六个配置页面名为Addressing and Memory,配置页面则是针对含有总线接口,并且具有 多个寄存器需要寻址的 IP 核,我们的 IP 核则不需要,所以是空白的
第七个配置页面名为Customization GUI,配置页面则显示当前接口在 GUI 上的 layout 和 preview 信息
第八个配置页面名为Review and Package,在这里点击Package IP完成IP核的配置
打开 IP Catalog 后,我们可以看到刚刚定义的用户 IP 核 led_controller_v1_0 已经出现在了 UserIP 文件夹下面
3.移植IP核
找到创建IP核时所保存的位置,复制以下三个文件
在另一工程的ip文件夹下创建led_controller的文件夹
将刚刚复制的三个文件粘贴于此
在新工程中按如下图所示操作
添加led_controller所在路径
最终可以在IP Catalog中看到此自定义IP
按键消抖
1.思路
思路:每过20ms(在20ms内检测到有按键按下,则认为是抖动,计数器清零)才将输入的按键值锁存,当两次锁存的按键值发生变化时,则有相应按键按下
2.源代码
1 |
|
3.Testbench
1 |
|
4.仿真结果
数码管显示
1.思路
通过8bit的分时计数器div_cnt,每隔64个时钟周期显示一个数码管
片选数码管与显示数据的同步,此处时序很妙
2.源代码
- Seg8_display_design.v
1 |
|
- Counter_design.v
1 |
|
- Seg_drive_design.v
1 |
|
3.Testbench
1 |
|
4.仿真结果
4$\times$4矩阵按键
- 采集4X4矩阵按键的键值,输出到数码管的末位,数码管每新输入一位数据,都会将原有数据左移一位
1.思路
- 对列输入按键会触发行输出扫描信号,开始扫描,对于四行行信号,在同一时刻只会有一行信号被拉低,其余置高,只有被拉低的那一行信号是有效的,那么此时的列输入对应的就是这一组有效信号
- 通过状态机实现行信号的扫描,主要思路是:当检测到有按键按下时,进入状态K_H1OL,在这个状态内将行信号0拉低;接着进入状态K_H2OL,在这个状态中,先判断列输入信号是否有按键按下(即为1110,1101,1011,0111),若有效则说明是第0行的按键被按下了,否则将行信号1拉低;在进入K_H3OL状态之前,首先需判断按键是否弹起,若弹起则回到空闲状态,否则则继续进行行扫描,进入K_H3OL状态,在K_H3OL状态中的判断与K_H2OL一致,后面的状态跳变与状态内的操作依此类推。
2.源代码
- key4T4_sample_design.v
1 |
|
- key_filter_design.v
1 |
|
- seg7_display_design.v
1 |
|
- Keyboard4T4_design.v
1 |
|
3.Testbench
1 |
|
4.仿真结果
序列码状态机
1.思路
2.源代码
1 |
|
3.Testbench
1 |
|
4.仿真结果
UART的loopback实例
- 接收PC端发送的UART数据,原数据返回给PC端,即loopback功能
1.源代码
- uart_design.v
1 | //接收PC端发送的UART数据,原数据返回给PC端,即loopback功能 |
- my_uart_tx.v
1 | module my_uart_tx( |
- my_uart_rx.v
1 | module my_uart_rx( |
2.Testbench
1 |
|
3.仿真结果
- RTL视图
- 仿真波形:
- 发送数据:
SPI接口DAC驱动控制
- DAC:数字-模拟转换器
- 功能描述:DAC 芯片DAC081S101的模拟电压输出直接连接到D5指示灯的正端,它的电压值决定 了D5指示灯的亮暗程度。FPGA工程实例产生一个0-255循环递增的数据,通过SPI接口不断的写入到DAC中,输出的模拟电压可以控制LED的亮暗变化
1.思路
发起一次DAC芯片的写入操作,需要先拉低同步信号SYNC,同时在紧接着的连续 16 个同步时钟SCLK上升沿送出数据DIN,DAC芯片内部则在时钟SCLK的下降沿采样数据DIN
DA芯片通信数据帧格式:FPGA 作为通讯的主机,若要控制DAC芯片DAC081S101完成一次转换,则一共需要传输16位的数据。最高 2 位(DB15-14)不用;DB13-12是当前传输数据的控制位,写 2’b00 时表示正常的DAC数据输出操作;DB11-DB4对应有效数据D7-0;最后4位数据DB3-0也不用
DAC使能转换信号dac_en时序
DAC帧同步信号dac_sync_n时序:
2.源代码
SPI_DAC_design.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51module SPI_DAC_design(
input sys_clk_i, //外部输入50MHz时钟信号
input ext_rst_n, //外部输入复位信号,低电平有效
output dac_sync_n, //DAC帧同步信号,低电平有效
output dac_sclk, //DAC串行时钟信号
output dac_data //DAC串行数据信号
);
wire clk_12m5; //PLL输出12.5MHz时钟
wire clk_25m; //PLL输出25MHz时钟
wire clk_50m; //PLL输出50MHz时钟
wire clk_100m; //PLL输出100MHz时钟
wire sys_rst_n; //PLL输出的locked信号,作为FPGA内部的复位信号,低电平复位,高电平正常工作
//-------------------------------------
//PLL例化
clk_wiz_0 u1_clk_wiz_0
(
// Clock in ports
.clk_in1(sys_clk_i), // input clk_in1
// Clock out ports
.clk_out1(clk_12m5), // output clk_out1
.clk_out2(clk_25m), // output clk_out2
.clk_out3(clk_50m), // output clk_out3
.clk_out4(clk_100m), // output clk_out4
// Status and control signals
.reset(!ext_rst_n), // input reset
.locked(sys_rst_n)); // output locked
//-------------------------------------
//产生递增的DAC转换数据
wire[7:0] dac_out_bus; //DAC输出数据,模块内部自动判断该数据是否发生变化,若前后有变化,则通过IIC接口发起一次DAC转换数据写入操作,建议该数据变化速率不要超过1.5KHz
dac_dbgene uut_dac_dbgene(
.clk(clk_25m), //时钟信号
.rst_n(sys_rst_n), //复位信号,低电平有效
.dac_out_bus(dac_out_bus) //DAC转换数据
);
//-------------------------------------
//DAC5571的IIC写DA转换数据模块
dac_controller uut_dac_controller(
.clk(clk_25m), //时钟信号
.rst_n(sys_rst_n), //复位信号,低电平有效
.dac_out_bus(dac_out_bus), //DAC输出数据,模块内部自动判断该数据是否发生变化,若前后有变化,则通过IIC接口发起一次DAC转换数据写入操作,建议该数据变化速率不要超过1.5KHz
.dac_sync_n(dac_sync_n),
.dac_sclk(dac_sclk),
.dac_data(dac_data)
);
endmoduledac_dbgene.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30module dac_dbgene(
input clk, //时钟信号,25MHz
input rst_n, //复位信号,低电平有效
output reg[7:0] dac_out_bus //DAC转换数据
);
parameter MAX_CNT_VALUE = 18'd249_999;
//-------------------------------------------------
//10ms定时计数
reg[17:0] cnt; //10ms计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt <= 18'd0;
else if(cnt < MAX_CNT_VALUE)
cnt <= cnt+1'b1;
else
cnt <= 18'd0;
end
//-------------------------------------------------
//DA转换数据递增
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
dac_out_bus <= 18'd0;
else if(cnt == MAX_CNT_VALUE)
dac_out_bus <= dac_out_bus+1'b1;
end
endmoduledac_controller.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166module dac_controller(
input clk, //时钟信号,25MHz
input rst_n, //复位信号,低电平有效
input[7:0] dac_out_bus, //DAC输出数据,模块内部自动判断该数据是否发生变化,若前后有变化,则通过SPI接口发起一次DAC转换数据写入操作,建议该数据变化速率不要超过1.5KHz
output reg dac_sync_n, //DAC帧同步信号,低电平有效
output dac_sclk, //DAC串行时钟信号
output reg dac_data //DAC串行数据信号
);
reg[4:0] cstate;
reg[4:0] nstate;
//-------------------------------------------------
//DAC输出时钟产生,clk的4分频,即6.25MHz
reg[1:0] div;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n)
div <= 2'd0;
else
div <= div+1'b1;//00 01 10 11
end
assign dac_sclk = ~div[1];
wire div_en = (div == 2'd2);//在时钟sclk由高变低的瞬间置1
//-------------------------------------------------
//判断DAC输出数据是否变化,若变化则发起一次SPI数据写入操作
reg[7:0] dac_out_bus_r; //dac_data缓存寄存器
reg dac_en; //DAC转换使能信号,高电平有效
//锁存前一拍的DAC值
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
dac_out_bus_r <= 8'd0;
else if(div_en)
dac_out_bus_r <= dac_out_bus;
end
//判断是否需要写入新的DAC值,赋值DAC转换使能信号
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
dac_en <= 1'b0;
else if(div_en) begin
if(dac_out_bus_r != dac_out_bus)
dac_en <= 1'b1; //若DAC当前值与新的值不一致,则发起一次写DAC操作
else
dac_en <= 1'b0;
end
else ;
end
//-------------------------------------------------
parameter DAC_BIT15_14 = 2'b00; //don't care
parameter DAC_BIT13_12 = 2'b00; //2'b00--normal operation; 2'b01--1kohm to gnd; 2'b10--100kohm to gnd; 2'b11--high impedance
parameter DAC_BIT3_0 = 4'b0000; //don't care
wire[15:0] dac_parallel_data = {DAC_BIT15_14,DAC_BIT13_12,dac_out_bus_r,DAC_BIT3_0}; //写入DAC的16位数据
//-------------------------------------------------
//DAC芯片SPI时序状态机
parameter STATE_IDLE = 5'd0;
parameter STATE_DB15 = 5'd1;
parameter STATE_DB14 = 5'd2;
parameter STATE_DB13 = 5'd3;
parameter STATE_DB12 = 5'd4;
parameter STATE_DB11 = 5'd5;
parameter STATE_DB10 = 5'd6;
parameter STATE_DB09 = 5'd7;
parameter STATE_DB08 = 5'd8;
parameter STATE_DB07 = 5'd9;
parameter STATE_DB06 = 5'd10;
parameter STATE_DB05 = 5'd11;
parameter STATE_DB04 = 5'd12;
parameter STATE_DB03 = 5'd13;
parameter STATE_DB02 = 5'd14;
parameter STATE_DB01 = 5'd15;
parameter STATE_DB00 = 5'd16;
parameter STATE_DONE = 5'd17;
//时序逻辑,状态切换
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cstate <= STATE_IDLE;
else
cstate <= nstate;
end
//切换下一个状态
always @ (posedge clk or negedge rst_n) begin
if(!rst_n)
nstate <= STATE_IDLE;
else if(div_en) begin
case(cstate)
STATE_IDLE: begin
if(dac_en)
nstate <= STATE_DB15; //DAC芯片写操作
else
nstate <= STATE_IDLE;
end
STATE_DB15: nstate <= STATE_DB14;
STATE_DB14: nstate <= STATE_DB13;
STATE_DB13: nstate <= STATE_DB12;
STATE_DB12: nstate <= STATE_DB11;
STATE_DB11: nstate <= STATE_DB10;
STATE_DB10: nstate <= STATE_DB09;
STATE_DB09: nstate <= STATE_DB08;
STATE_DB08: nstate <= STATE_DB07;
STATE_DB07: nstate <= STATE_DB06;
STATE_DB06: nstate <= STATE_DB05;
STATE_DB05: nstate <= STATE_DB04;
STATE_DB04: nstate <= STATE_DB03;
STATE_DB03: nstate <= STATE_DB02;
STATE_DB02: nstate <= STATE_DB01;
STATE_DB01: nstate <= STATE_DB00;
STATE_DB00: nstate <= STATE_DONE;
STATE_DONE: nstate <= STATE_IDLE;
default: nstate <= STATE_IDLE;
endcase
end
else ;
end
//DAC帧同步信号赋值
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
dac_sync_n <= 1'b1;
else if((nstate == STATE_IDLE) && div_en && dac_en)
dac_sync_n <= 1'b0; //开始DAC操作时拉低帧同步信号
else if((nstate == STATE_DB00) && div_en)
dac_sync_n <= 1'b1; //结束DAC操作时拉高帧同步信号
else if((nstate == STATE_IDLE) || (nstate == STATE_DONE))
dac_sync_n <= 1'b1; //其他状态下,帧同步信号拉高
else
dac_sync_n <= 1'b0;
end
//并串转换,送16位DAC数据
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
dac_data <= 1'b0;
else begin
case(nstate)
STATE_IDLE: dac_data <= 1'b0;
STATE_DB15: dac_data <= dac_parallel_data[15];
STATE_DB14: dac_data <= dac_parallel_data[14];
STATE_DB13: dac_data <= dac_parallel_data[13];
STATE_DB12: dac_data <= dac_parallel_data[12];
STATE_DB11: dac_data <= dac_parallel_data[11];
STATE_DB10: dac_data <= dac_parallel_data[10];
STATE_DB09: dac_data <= dac_parallel_data[9];
STATE_DB08: dac_data <= dac_parallel_data[8];
STATE_DB07: dac_data <= dac_parallel_data[7];
STATE_DB06: dac_data <= dac_parallel_data[6];
STATE_DB05: dac_data <= dac_parallel_data[5];
STATE_DB04: dac_data <= dac_parallel_data[4];
STATE_DB03: dac_data <= dac_parallel_data[3];
STATE_DB02: dac_data <= dac_parallel_data[2];
STATE_DB01: dac_data <= dac_parallel_data[1];
STATE_DB00: dac_data <= dac_parallel_data[0];
STATE_DONE: dac_data <= 1'b0;
default: dac_data <= 1'b0;
endcase
end
end
endmodule
3.Testbench
1 |
|
4.仿真结果
RTL视图
仿真波形
IIC接口RTC时间显示控制
- 功能描述:本实例通过IIC接口定时读取RTC中的时、分、秒寄存器,同时将时、 分、秒数据通过UART发送到PC上的串口调试助手进行实时的显示;此外,PC上的串口调试助手也可以发送“0xaa+time+minute+second+0x55”这样的16进制字符串来重设RTC的时、 分、秒寄存器,重设后立即生效
1.思路
IIC协议:I2C通信中只涉及两条信号线,即时钟信号SCL和数据信号SDA。时钟信号为高电平时均可锁存数据(即时钟信号上升沿到下降沿之间)。当时钟信号SCL为高电平时,如果把数据信号SDA从高电平拉到低电平,则表示通信开始;如果把数据信号SDA从低电平拉到高电平,则表示通信结束。
器件地址(DEVICE ADDRESS):最低位R/W表示读或者写状态,1表示读,0表示写;A0一般是由芯片的引脚决定的,PCF8563芯片的A0引脚和INT引脚公用,电路中做了上拉,因此为1。其它几位的取值由芯片定义好,查阅芯片手册可以得到。