一.原理图
此黑金开发板的LED灯为共阴级连接,即I/O口输出高电平为亮。
二.Verilog HDL代码及讲解
Verilog的语法和C语言有些相似,如果有C的基础则更容易理解。
`timescale 1ns / 1ps
module led_test
(
input clk, // 系统时钟50MHz
input rst_n, // 复位,低电平有效
output reg[3:0] led // LED位控制
);
//定义定时器的计数器
reg [31:0] counter;
//循环计数器:0~4s
always@(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
counter <= 32'd0; //有复位低电平信号时,计数器清零
else if (counter == 32'd199_999_999) //4s的计数器 (50M*4-1=199999999)
counter <= 32'd0; //到达4s后清零,重新计数
else
counter <= counter + 32'd1; //计数器每1/50ns自加1
end
//LED控制
always@(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
led <= 4'b0001; //又复位低电平信号时,led复位
else if (counter == 32'd49_999_999) //计数器计到1s时,LED1亮
led <= 4'b0001;
else if (counter == 32'd99_999_999) //计数器计到2s时,LED2亮
led <= 4'b0010;
else if (counter == 32'd149_999_999) //计数器计到3s时,LED3亮
led <= 4'b0100;
else if (counter == 32'd199_999_999) //计数器计到4s时,LED4亮
led <= 4'b1000;
end
endmodule
语法解释:
1.timescale 1ns / 1ps
:此命令属于编译预处理,是用来说明跟在该命令后面的模块延时的时间单位和时间精度,此处可不用过多关注。
2.module + name (······)
:代码中的led_test即为模块名称,括号中的内容是端口定义和I/O说明。
也可以把端口定义和I/O说明分开编写:
module led_test(clk,rst_n,led);
input clk,
input rst_n,
output reg[3:0] led
3.reg [31:0] timer
:reg说明counter是寄存器类型,可用于保存数值,常用于时序逻辑电路;[31:0]说明counter是32位的位宽,此处的timer为定时器的计数器,因此要保存计数值。此外,verilog语法规定,对于没有声明类型的输入输出端口默认为wire类型,该类型相当于物理连线,常用于组合逻辑电路。
4.always @(posedge clk or negedge rst_n) begin······end
:always过程块,有点类似单片机编程中的while(1){},用于执行具体的功能。括号中的叫做敏感列表,是执行always后面代码的条件。clk是时钟信号,rst_n是复位信号,后续会与硬件层连接,posedge和negedge分别代表上升沿和下降沿。整条语句意思便是,当时钟上升沿到来或者复位键被按下(产生了下降沿)时,将执行此always后紧跟的语句块。
此外,会发现代码下半段还有一个always @(posedge clk or negedge rst_n) begin······end
,这里便要理解FPGA与单片机的不同之处,即并行处理。因为此处两个always后的执行条件相同,因此满足此条件时,两个always后的语句块会同时执行,而不是像单片机那样先执行上面的程序。
注:
1.如果没有posedge或negedge代表上下边沿都会使其执行。
2.always后面的语句块中赋值表达式的左边必须为reg类型,例如代码中的counter和led都事先声明是reg类型。
3.多个always语句块中不能出现对同一个变量赋值,否则并行处理时就产生了冲突。
4.always既可描述组合逻辑也可以描述时序逻辑,此代码中即为时序逻辑。
5.if条件语句基本和c相似,此处不做过多说明。
6.数据类型:代码中有1'b0
意思为1位的二进制数0,32'd0
意思是32位的十进制数0。同理16'o
和10'h
即分别代表16位的八进制数和10位的十六进制数。需要注意的是,此处所说的位数是将数值转换为二进制后的位数,用于限定后面数值的范围,类似于单片机编程中所说的char类型为8位,但此处的位数可任意设定。例如:5‘d10 -> 0 1010,6’h10 -> 01 0000。如果后面的数值大小超过限定范围则会截取低位,例如3’d13 -> 1 101 -> 101 -> 5。
32'd199_999_999
:下划线只是为了分隔,方便读数。
7.代码中出现的 ”<=“叫做非阻塞赋值,还有一种“=”为阻塞赋值,在此处看不出其特别之处。
例如:
非阻塞赋值 阻塞赋值
a <= 1; a = 1;
b <= a; b = a;
左边非阻塞赋值实际执行时,上下两条语句同时执行(并行)。结果为a=1,b=a前一次的值,而非刚得到的1。
右边阻塞赋值实际执行时,就和c语言一样,从上而下顺序执行。结果为a=1,b=1。
实际上,“<=”常用于时序逻辑电路,“=”常用于组合逻辑电路。
逻辑解释
此芯片的时钟频率为50MHz!
每个时钟上升沿同时执行两段always语句。即在第一个always中计数寄存器counter每隔1/50ns自加1(有点类似单片机中的定时器功能),在第二个always中counter每增加50 000 000(即1s),换成下一个led灯亮。同时,如果复位按键被按下,两个always语句也会执行,并满足了if的条件,从而进行counter和led的清零复位。
此外还有一种方法:
always @(posedge clk or negedge rst_n)
begin
if (!rst_n)
counter <= 32'd0;
else if (counter < 32'd50_000_000)
counter <= counter + 1'b1;
else
counter <= 32'd0;
end
always @(posedge clk or negedge rst_n)
begin
if (!rst_n)
led <= 4'b0001;
else if(counter == 32'd50_000_000)
led[3:0] <= {led[2:0],led[3]};
else
led <= led;
end
此代码中出现了一个{ ,}
的语法,意思是组合。例如此代码中便是把led拆成高1位和低3位后,重新组合,把最高位调到最低位。
这种方法更类似于单片机通过定时器和位移的方式实现流水灯。
三.软件端口与硬件I/O对应
Reset复位按键与N13口相连。