心跳灯
正如C程序初学者尝试的第一个程序是写一个“hello World”一样,FPGA初学者的第一个程序一般都为“心跳(heart beat)灯”,也就是通过系统的时钟进行分频,产生周期性的低频信号(高、低电平交替变化驱动LED),控制LED灯的点亮和熄灭。
1. 硬件平台
我们以STEP-MXO2第一代为例,对于其它平台只需要注意其时钟的频率以及管脚的分配。
2. 设计要求
- 初步了解数字设计的基本思想和开发流程
- 熟悉Diamond软件和仿真软件的使用方法及流程
- 控制LED闪烁,闪烁周期为1秒
- 了解FPGA时钟的使用
- 了解时钟分频的工作原理
3. 工作原理
按照设计要求,控制LED闪烁的周期为1秒,则LED灯需要点亮0.5s,然后熄灭0.5s,然后点亮0.5s,然后熄灭0.5s,循环执行。
通过上面的分析,我们发现LED的控制信号就是一个周期为1s,占空比为50%的信号,我们可以定义一个寄存器和一个计数器,计数器负责计数满0.5s时寄存器翻转,实现对LED的闪烁控制。
开发板上晶振为25MHz,0.5s对应晶振12500000个周期的时间,所以计数器对晶振信号计数,当计12500000个数时,控制寄存器翻转。
4. 硬件连接
LED也叫发光二极管,可以把电能转化成光能,因为是二极管的一种,由一个PN接组成,具有单向导电性。它的正向伏安特性曲线很陡,使用时必须串联限流电阻以控制通过二极管的电流,LED的亮度与电流有关,一般LED能够承受的最大电流为20mA(具体需要看使用的LED的参数),本设计使用的限流电阻为1K。下图为LED的硬件电路:
LED电路连接
本设计中我们只控制LD1(LED)进行闪烁,由上图可知当控制信号LED1端为高电平时D1熄灭,当控制信号LED1端为低电平时LD1点亮。
数码管模块、小脚丫与FPGA的引脚连接关系如下:
LED模块 | LED1 |
---|---|
FPGA管脚 | A3 |
5. 代码设计
5.1 LED按照一定频率开/关
module LEDblink(clk, LED); input clk; // clock typically from 10MHz to 50MHz output LED; // create a binary counter reg [31:0] cnt; always @(posedge clk) cnt <= cnt+1; assign LED = cnt[22]; // blink the LED at a few Hz (using the 23th bit of the counter, use a different bit to modify the blinking rate) endmodule
5.2 LED半亮
一种解决方案是将与LED串联使用的电阻器的值加倍。另一个解决方案是驱动LED FPGA输出时间的一半“足够快”,这样你的眼睛平均光,看到LED半亮。
module LEDhalflit(clk, LED); input clk; output LED; reg toggle; always @(posedge clk) toggle <= ~toggle; // toggles at half the clk frequency assign LED = toggle; endmodule
5.4 精调LED的亮度
对于更多的LED强度控制,PWM是一个理想的解决方案。这里有一个例子,使用4位控制选择16个强度级别的LED。
module LED_PWM(clk, PWM_input, LED); input clk; input [3:0] PWM_input; // 16 intensity levels output LED; reg [4:0] PWM; always @(posedge clk) PWM <= PWM[3:0] + PWM_input; assign LED = PWM[4]; endmodule
通过不断改变LED的强度,LED看起来会“发光”。
module LEDglow(clk, LED); input clk; output LED; reg [23:0] cnt; always @(posedge clk) cnt <= cnt+1; reg [4:0] PWM; wire [3:0] intensity = cnt[23] ? cnt[22:19] : ~cnt[22:19]; // ramp the intensity up and down always @(posedge clk) PWM <= PWM[3:0] + intensity; assign LED = PWM[4]; endmodule
5.3 LED按照设定频率闪动
我们首先定义一个参数:parameter CNT_NUM = 12500000 ,我们将参数定义放在模块的端口声明中,这样在模块被其他模块调用时可以通过端口重新定义参数的值。
计数器cnt位宽为24, 2^24 = 16777216, 大于我们设定的计数终值CNTNUM = 12500000。cnt计数范围为0~(CNTNUM-1),共计12500000个数,0.5s。
然后使用计数器计数触发寄存器信号不断翻转,如下:
reg [23:0] cnt; reg clk_div; always@(posedge clk_in or negedge rst_n_in) begin if(!rst_n_in) begin cnt <= 24'd0; clk_div <= 1; end else begin if(cnt>=CNT_NUM-1) begin cnt <= 24'd0; clk_div <= ~clk_div; end else cnt <= cnt + 1; end end
5.2 测试文件
测试文件,也称testbench文件,是一种验证的手段,testbench文件在软件环境中模拟实际环境,产生被测模块输入端口需要的激励信号,对被测模块的输出信号分析,达到评估被测模块的目的。
对于测试文件,我们需要提供被测模块(Blink)需要的时钟(sysclk)及复位(sysrst_n),如下:
reg sys_clk; initial sys_clk = 1'b0; always sys_clk = #(CLK_PERIOD/2) ~sys_clk; reg sys_rst_n; //active low initial begin sys_rst_n = 1'b0; #100; sys_rst_n = 1'b1; end
然后调用被测模块(Blink),注意模块调用时参数的重定义及端口的传递,为了方便仿真,减少仿真时间,我们将设计文件中的计数终值参数(CNT_NUM)在测试文件中重新赋值为10,这样我们在仿真时计数器的计数范围应该为0~9。
模块调用方法如下:
blink #(.CNT_NUM(10)) blink_uut( .clk_in (sys_clk), .rst_n_in (sys_rst_n), .led_out (led_out) );
引脚分配如下:
管脚名称 | clkin | rstnin | ledout | ||
---|---|---|---|
FPGA管脚 | C1 | B1 | A3 |
6. 仿真结果
7. 资源报告
资源 | 数量 | 比例 | 说明 |
---|---|---|---|
LUTs | 36 | 3% | |
寄存器 | 25 | 2% | |
存储器 | 0 | 0% | |
IO管脚 | 3 | ||
时钟频率 | 25MHz |
8. 学习的知识点
- LED闪烁原理
- FPGA时钟的使用
- 时钟分频
9. 参考阅读
10. 相关设计文档
文件名称 | 功能 |
---|---|
Blink.v | LED闪烁 |
Blink_test.v | 测试文件 |
11. 扩展实验
XO2 FPGA自带时钟发生器,但其精度较低(5%),对于基本的功能性的实验是够了的,但对于高精度要求的应用,其芯片内部振荡器产生的时钟就无法满足要求,因此在我们的小脚丫开发板上还连接了外部晶振产生的25MHz的时钟信号,在上述的实验中我们采用了25MHz的时钟信号来产生LED的交替控制信号,用户可以尝试采用内部的时钟产生所需要的LED控制信号。