流水灯里面注意的问题
在学习单片机时我们刚开始都是通过点亮一个LED灯来给我们自己一个真实的直观的认识,因此在建立的第一个FPGA工程的时候,我们也使用了一个点亮LED的范例,这里我们将讨论在FPGA中如何实现点亮LED这一话题。
1、程序编写
一般FPGA的核心电平是1.2V,管脚电平是3.3V,所以,要想点亮哪个LED,只要给连接的那个管脚赋0,不点亮赋1就可以了(注意此处对管脚赋0或1取决于硬件电路的设计,一般来讲如果LED的负极挂接在FPGA的IO口上时则赋0点亮,反之赋1点亮)。
点亮四个LED的程序我们是这样写的:
1 module led ( 2 clk,rst_n, 3 led 4 ); 5 6 input clk; 7 input rst_n; 8 output[3:0] led; 9 reg[3:0] led_r; 10 11 always @(posedge clk , negedge rst_n) 12 begin 13 if(!rst_n) 14 led_r <= 4‘b1111; 15 else 16 led_r <= 4‘b0; // led是一端接上拉电阻,输入低电平点亮 17 end 18 assign led = led_r; 19 endmodule
2、程序简介
这里我就讲点Verilog的经验之谈,有不妥的地方还请大家多指正。关于Verilog语法,还请大家找本书系统地学习下,这里无法一点一点细讲,要细讲的话书上是最合适的。推荐两本书,一本是夏宇闻老师的《Verilog 数字系统设计教程》 ,另外一本《设计与验证Verilog HDL》也很不错。
Verilog的一个程序模块,是以module 和 endmodule 开头和结尾的。跟在module后面的是这个模块的模块名。模块名后面的括号里列出了这个模块的输入输出管脚列表。再接下来我们要对这些管脚是输入input 还是输出 output 以及是多少位进行描述。再接下来差不多就是程序的正文了。
我们知道,数字电路就分为两种:组合逻辑电路和时序逻辑电路。其实我们写的FPGA程序它不叫程序,它一般叫做硬件描述语言,(这里我也是图方便混着叫了,嘿嘿)。说这个呢,想表达这么个意思:我们写了FPGA程序,然后Quartus/ISE帮我们综合到FPGA芯片中,在FPGA内部就是生成两种数字电路,组合逻辑电路和时序逻辑电路。
所以,我们写程序,也就两种逻辑电路的描述。凡是clock信号有关系的就是时序逻辑,跟clock没什么关系的就是组合逻辑(这是我妄自断言,还没有出处,有问题的话请拍砖)。这就引出了wire数据型和reg数据型的问题,还有阻塞赋值和非阻塞赋值。这些概念大家一定要在书里仔细阅读。
学习Verilog并不难,花个一天时间看下书,弄个程序写一下基本就会了。对于以前一直从事c语言,c++软件开发的同学,首先得理解这样的问题。我们所写的Verilog程序,并非软件程序,它是用来描述硬件电路的代码:
3、 换个玩法
四个LED亮着是不是没什么好玩的呢?我们可不可以这样的,让四个灯轮流亮起来,就是第一个灯亮了之后熄灭,然后第二个亮再熄灭,接着第三个,第四个。这个流水灯,或者叫跑马灯,大家在做单片机实验的时候估计都做过吧。这里我们只要做一个移位寄存器就可以了。我们修改下面这段程序
1 reg[3:0] led_r; 2 always @(posedge clk,negedge rst_n) 3 begin 4 if(!rst_n) 5 led_r <= 4‘b0111; 6 else 7 led_r <= {led_r[0],led_r[3:1]}; 8 end
{}是Verilog里的拼接运算符,这句{led_r[0],led_r[3:1]}的意思就是把上次led_r的第0位移到最高位,然后高3位往右移。也就是说,最初,我们给四个led的赋值是0111,那只有最高位的那个led灯是点亮的。第一次移位后变成1011,那就只有第二个是亮的;接着1101,第三个;1110,第四个;然后又变成第一个0111。
4、点灯进阶
按原理分析,我们应该是实现了流水灯这个功能了。下载到板子上看看效果吧。结果估计另大家都失望了,怎么回事?四个LED灯都还亮着。聪明的同学估计找到原因了,但我还想买个关子,我们先做下面的实验。
1 module led ( 2 clk,rst_n, 3 led 4 ); 5 6 input clk; 7 input rst_n; 8 output[3:0] led; 9 reg[19:0] cnt; 10 11 always @(posedge clk , negedge rst_n) 12 begin 13 if(!rst_n) 14 cnt <= 20‘b0; 15 else 16 cnt <= cnt + 1‘b1; 17 end 18 reg enable_r; 19 always @(posedge clk , negedge rst_n) 20 begin 21 if(!rst_n) 22 enable_r <= 1‘b0; 23 else if (cnt == 20‘hfffff) 24 enable_r <= 1‘b1; 25 else 26 enable_r <= 1‘b0; 27 end 28 29 wire enable; 30 assign enable = enable_r; 31 32 reg[3:0] led_r; 33 always @(posedge clk , negedge rst_n) 34 begin 35 if(!rst_n) 36 led_r <= 4‘b1; 37 else if(enable) 38 led_r <= {led_r[0],led_r[3:1]}; 39 else ; 40 end 41 42 wire[3:0] led; 43 assign led = led_r; 44 endmodule
我又写了上面这段程序,程序中我增加了一个计数器cnt。开始,计数器是20位的,每个系统时钟的上升沿 posedge clk 执行一次计数器 +1 操作 cnt <= cnt + 1‘b1 。 在计数器计数到满的时候,我定义了一个enable信号 else if (cnt == 20‘hfffff) enable_r <= 1‘b1;而其他时候,我都让这个 enable 信号维持低电平。(这里加一句,计数器到满的时候 cnt = 20‘hfffff再 + 1 它就溢出了,回到20’h0)。
1 reg[3:0] led_r; 2 always @(posedge clk or negedge rst_n) 3 begin 4 if(!rst_n) 5 led_r <= 4‘b1; 6 else if(enable) 7 led_r <= {led_r[0],led_r[3:1]}; 8 else ; 9 end
然后我们再看这段程序,我让它只有在 enable == 1’b1 的时候进行移位。大家把这段程序下载到板子上,有什么现象?四个led在闪动。
5、答疑解惑
好了,这个关子就卖到这里了。问题出在我们的眼睛骗了我们。百度一下,知道一个数据:人眼分辨事物的最高频率为24HZ,即反应一次要0.042s 。我们最初的移位是用的系统时钟clk,它是50MHZ,也就是说灯的每盏灯的亮灭是按50M的速度在移动,眼睛当然是完全看不出来啦,所以效果跟四个灯全亮是一样的。
后面我们用了一个计数器,使移位变成了2的20次方个CLK时间,即频率变成了50M/1048576 ,约等于50 HZ,这时我们的眼睛还是分辨不了,所以我们看到四个灯都在闪烁。
接下来,我们再把计数增大一点,把移位的频率降下来,就能看到流水灯的现象了。
这是个有趣的现象,相同原理的实验我在本科的时候做过,那时候也没太在意。最近发现安装在山地车轮胎上的“风火轮”也是用的这个原理哦!
6、总结
这一篇我们讲了LED点亮的原理,以及计数器的设计,还有流水灯的设计。好玩吧!有兴趣的欢迎加入一起学习!