目录
需求:
- 博宸电子的ZYNQ7020DEV开发板
- Vivado 2018.3
- 一定的verilog语言基础
一.任务剖析
1.1 实验目的
在1s内实现开发板上四个led灯的轮流闪烁,每个灯亮起0.25s.
1.2原理图及其分析
博主目前学习的是博宸电子的ZYNQ7020DEV开发板,下面是关于本次分享led流水灯的原理图
图1 涉及本次led流水灯的电路
首先,从图1来看,每个led端口都和一个阻值为4.7k的电阻和一个发光二极管相连,并且发光二极管负端接地。要使得led发光,那么就要令发光二极管导通,即led端口应拉高。在编写代码中,当led输入端口输入为“1”时,代表拉高。
图2 涉及本次led流水灯的引脚
开发板上共有六个led灯。LED1,LED2上电即点亮,故不考虑。
对于剩下的四个led灯,其引脚分配如下:
灯号 | 引脚 |
---|---|
LED3 | Y14 |
LED4 | AA13 |
LED5 | AB14 |
LED6 | AB15 |
清楚了上述背景知识,接下来我们就可以编写代码啦。
二.总代码
2.1 敲写代码
2.1.1 代码内容
module led_stream(
input clk,
input rst_n,
output reg [3:0] led //设置为寄存器类型,否则第二个always模块会报错
);
//定义一个1s的计时器,时钟频率为50MHz
reg [25:0] cnt;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt <= 26'd0;
else if(cnt < 26'd5000_0000)
cnt <= cnt + 1'b1;
else cnt <= 26'd0;
end
//通过计时器控制led交替闪烁,1s内闪烁4个灯
always @(cnt) begin
if(cnt<26'd1250_0000) led <= 4'b0001;
if(cnt<26'd2500_0000 && cnt >= 26'd1250_0000) led <= 4'b0010;
if(cnt<26'd3750_0000 && cnt >= 26'd2500_0000) led <= 4'b0100;
if(cnt<26'd5000_0000 && cnt >= 26'd3750_0000) led <= 4'b1000;
end
endmodule
2.1.2 代码分析
-
时间:
开发板内部自带有一个50M赫兹的晶振,即系统时钟clk为50M赫兹,也就是1s内时钟clk有50*10的6次方个上升沿。为了能够实现计时1s(实验目的),所以需要定义一个计数器cnt。时钟clk每达到一次上升沿,计数器cnt计数一次。所以当计数器cnt等于50000000(50M)时,时间等于1s。
0.25s也就是50000000(50M)的四分之一,即12500000. -
计数:
当系统复位时,cnt赋值为0,准备开始计数计时。
clk每上升一次,cnt的数值加一。
当cnt值等于50M时,cnt赋值为0,重新开始计数。
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt <= 26'd0;
else if(cnt < 26'd5000_0000)
cnt <= cnt + 1'b1;
else cnt <= 26'd0;
end
- 点亮:
本实验先令LED3亮起0.25s,再让LED4亮起0.25s,随后是LED5,最后是LED6。 所以:
当cnt的值处于0~12500000时,LED3端口赋值1,其余端口赋值为0.
当cnt的值处于12500000~25000000时,LED4端口赋值1,其余端口赋值为0.
当cnt的值处于25000000~37500000时,LED5端口赋值1,其余端口赋值为0.
当cnt的值处于37500000~5000000时,LED6端口赋值1,其余端口赋值为0.
always @(cnt) begin
if(cnt<26'd1250_0000) led <= 4'b0001;
if(cnt<26'd2500_0000 && cnt >= 26'd1250_0000) led <= 4'b0010;
if(cnt<26'd3750_0000 && cnt >= 26'd2500_0000) led <= 4'b0100;
if(cnt<26'd5000_0000 && cnt >= 26'd3750_0000) led <= 4'b1000;
end
- module:
定义两个输入:clk,rst-n。
定义一个的输出:reg [3:0] led
因为有四个led灯,所以led应该有四位,即[3:0],从3数到0
并且led在always语句进行赋值,所以应该是reg寄存器类型
module led_stream(
input clk,
input rst_n,
output reg [3:0] led //设置为寄存器类型,否则第二个always模块会报错
);
2.2 引脚与电压分配
- 方式一
按照上图箭头所指的四个部分,依次完成分配。最终结果应与上图一致。
- 方式二
点任选两个个方框中任意一个,左键点击
选择第一项,加入一个新的约束文档
进行命名,这里与项目名保持一致
点击方框,直到看见xdc文件,在右侧屏幕进行手动引脚即电压分配
这里为了大家编写方便,我将代码复制到下边了
set_property PACKAGE_PIN Y14 [get_ports {led[0]}]
set_property PACKAGE_PIN AA13 [get_ports {led[1]}]
set_property PACKAGE_PIN AB14 [get_ports {led[2]}]
set_property PACKAGE_PIN AB15 [get_ports {led[3]}]
set_property PACKAGE_PIN W17 [get_ports clk]
set_property PACKAGE_PIN V4 [get_ports rst_n]
set_property IOSTANDARD LVCMOS33 [get_ports rst_n]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports {led[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led[3]}]
当然大家也不必担心,代码这么多自己该如何编写。它的格式都很固定,只需要根据引脚及电压更改一些参数而已。并且,当你采用方式一进行引脚分配后,Vivado会自动为你生成你的分配代码。
三. 仿真
因为有的同学手上可能没有板子,不过没有关系,Vivado上自带有仿真系统,为了让大家不受板子限制,博主在此也是做出了本次实验的相应仿真部分,供大家参考。仿真所涉及到的知识点会在以后详细讲解,这里大家先看看最后效果就好。
3.1 建立仿真
老样子,二选一
按照图片,选择第三个,建立仿真文件,下一步
命名,这里多加入一个tb下划线,用来警醒自己这应该作为头部文件
然后就可以开始编写代码啦
3.2 仿真代码
大家直接复制粘贴到代码编写区域即可
`timescale 1ns / 1ps
module tb_led_stream();
reg sys_clk;
reg sys_rst_n;
wire [3:0] led;
initial begin //初始化
sys_clk = 1'b0;
sys_rst_n = 1'b0;
#1 sys_rst_n = 1'b1;
end
always #10 sys_clk = ~sys_clk; //定义一个50M赫兹的时钟
led_stream u_led_stream( //例化
.clk (sys_clk),
.rst_n (sys_rst_n),
.led (led)
);
endmodule
然后点击下图
:
进行仿真,仿真结果如下:
可以看见,每隔0.25s(250ms)不同的led灯依次点亮,满足实验要求。所以本次实验成功!
四.知识点
4.1 Verilog语法
- 时序always模块
此模块的代码框架基本如下:
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
//初始化你的参数
end
else
//编写代码逻辑
end
并且大多数采用阻塞赋值,即“<=”
- 电平always模块
此模块的代码框架基本如下:
always @(*) begin
//代码逻辑部分
end
并且大多数采用非阻塞赋值,即“=”
- 数值的定义
对寄存器类型的变量而言,其赋值需要根据参数的位数进行。
reg [4:0] num; //5位,所以num最大是 2*2*2*2*2 - 1 = 31
num = 5'd30; //十进制d下赋值30;
num = 5'b01011; //二进制b下赋值01011;
//同理,还有八进制,十六进制赋值,但最大不能超过十进制下的31;