小梅哥和你一起深入学习FPGA之点亮LED灯
在昨天更新的目录里面,并没有安排这个实验,第一个实验应该是独立按键的检测与消抖。可是,当小梅哥来做按键消抖的实验时,才发现没有做基本的输出设备,因此按键检测的结果无法直观的展示出来。也算是为后续实验做铺垫吧,第一个实验就安排成了点亮LED灯。
一、 实验目的
实现4个LED灯的亮灭控制
二、 实验原理
LED灯的典型电路如下2-1所示,我们控制led灯的亮灭,实质就是去控制FPGA的IO输给LED负极一个低电平或者高电平。从图中可知,我们给对应的led负极上一个低电平,就会有对应的电流通过 电阻,流过led灯,于是LED灯就会被点亮;当给led负极一个高电平时,led两端电压相等,因此没有电流流过,led则呈熄灭状态。
图2-1 led灯典型电路
三、 硬件设计
本实验的硬件电路即如图2-1所示,读者一看即懂,因此本节内容略。
四、 架构设计
虽然本实验只是一个简单的点亮led灯实验,整个实验代码不过四五行,但是为了遵循小梅哥一直喜欢的那种模块化的设计理念,因此本设计还是将led的驱动做成子模块的形式。
本实验由两个模块组成,分别为led驱动模块和顶层例化模块,可能看过其它开发板资料的同学会觉得这样反而增加了系统的复杂程度,但是,小梅哥如此设计必定有我的道理,图4-1为本实验的模块组织结构
图4-1 led实验模块组织结构图
由图可知本实验仅有n个输出端口,对应了n个led灯(为了代码的可移植性,这里并没有将led的个数限定死,而是采用了参数化的设计,因此,在实际使用过程中,就可根据实际不同的需要,自由的调整led的个数)。在modelsim仿真过程中,所有信号必须要有复位初始值,因此复位信号(Rst_n)必不可少。可能读者这里会发现,与我昨天所写的端口命名规范有出入。如果按照我所出的规范中来命名的话,则应该将复位信号命名为Global_Rst,对于这个问题,暂时小梅哥不做深入解说,其实严格意义上来说,这里的这个Rst_n应该只能算是一个内部信号,该信号在实际工程应用中往往由锁相环产生。这里因为为了配合仿真,因此该信号就暂时被引出来,做了全局复位信号。详细的关于全局复位与内部复位信号的处理,小梅哥在后面涉及到锁相环的使用的实验中会详细解说。
表4-1 led实验端口说明
五、 代码组织方式
本实验中,每个模块也就四五行的代码,因此谈不上代码组织方式,因此本节从略。
六、 关键代码解读
- module LED_Driver #(parameter Width = 1)/*定义位宽参数*/
- (Rst_n,Sig,Led);/*定义模块端口*/
- input Rst_n;
- input [Width-1:0] Sig;
-
- output [Width-1:0] Led;
-
- assign Led = (Rst_n)?Sig:{Width{1'b1}};/*复位输出全1,否则按照Sig的值输出*/
-
- endmodule
复制代码
以上为LED驱动模块的代码,第1行定义了一个参数“Width”,即位宽,因此在例化(调用)此模块时,根据实际需要给Width赋予不同的值,则可实现不同的LED位宽的设置。第4至7行为输入输出端口定义,具体信号含义见表4-1。第9行为LED输出赋值语句,有关该代码的含义,请读者自行阅读夏宇闻老师的《Verilog数字系统设计教程》一书。小梅哥精力和时间实在太有限,没办法一一帮大家补充Verilog的知识,望见谅。总之,该语句实现了当复位信号为低电平时(系统处于复位状态),所有LED全部熄灭;当复位信号为高电平(系统正常工作)时,led输出对应的Sig信号各位的状态。
- module LED_TOP(Rst_n,Led);
-
- input Rst_n;
-
- output [3:0] Led;
-
- LED_Driver
- #( /*参数例化*/
- .Width (4)
- )
- LED_Driver_inst(/*端口例化*/
- .Rst_n(Rst_n),
- .Sig(4'b1001),/*OFF ON ON OFF*/
- .Led(Led)
- );
- endmodule
复制代码
以上为LED实验的顶层模块,其中将位宽参数例化为了4,即4个LED。因为没有其他模块提供Sig信号,因此直接将该信号赋值为4’b1001。则如果下载到实验板上,会看到4个led灯分别处于“灭 亮 亮 灭”的状态。
七、 测试平台设计
本实验主要对LED的输出和输入与复位的关系进行测试仿真,通过仿真,即可验证设计的正确性和合理性。相关testbench的代码如下:
- `timescale 1ns/1ns
- module LED_Driver_tb;
-
- reg Rst_n;
- reg [3:0] Sig;
-
- wire [3:0] Led;
-
- LED_Driver
- #( /*参数例化*/
- .Width (4)
- )
- LED_Driver_inst(/*端口例化*/
- .Rst_n(Rst_n),
- .Sig(Sig),/*OFF ON ON OFF*/
- .Led(Led)
- );
-
- initial begin
- Rst_n = 0;
- Sig = 4'b1010;
- #100;
- Rst_n = 1;
- #40 Sig = 4'b0001;
- #40 Sig = 4'b0010;
- #40 Sig = 4'b0011;
- #40 Sig = 4'b0100;
- #40 Sig = 4'b0101;
- #40 Sig = 4'b0110;
- #40 Sig = 4'b0111;
- #40 Sig = 4'b1000;
- #40 Sig = 4'b1001;
- #40 Sig = 4'b1010;
- #40 Sig = 4'b1011;
- #40 Sig = 4'b1100;
- #40 Sig = 4'b1101;
- #40 Sig = 4'b1110;
- #40 Sig = 4'b1111;
- #40;
- $stop;
- end
-
- endmodule
复制代码
由testbench中可以看出,初始值给Sig赋了一个初始值,系统处于复位状态,100ns后,复位过程结束,系统进入正常工作状态,Sig信号每隔一定时间变化一次。因此,只需要观察Led信号与Sig信号的关系,即可验证设计的正确与否。
八、 仿真分析
由上图仿真结果可知,当复位信号为低电平时,Led输出全部为1,则所有LED熄灭,当复位信号为高电平时,则Led输出与Sig信号一致,证明设计正确无误。对于顶层模块,仿真没有什么太大的实际意义,因此不做仿真。
九、 下板验证
手头暂无开发板,板级验证略。
十、 总结
可能很多初学FPGA,又看过其它一些资料的同学会对小梅哥的这种组织方式感觉不太习惯,认为本来一个代码模块就能搞定的事儿被我硬生生拆成了两个,增加了系统的复杂性。其实,我这里主要是强调了一种建模的思想,我们所做的模块一定要具有灵活性和通用性,当其它设计中需要用到该外设时,只需要关心其内部端口就行了,在顶层例化时只需要将对应信号接到该端口上就能实现功能了,不用再专门为了特定应用再写一次。目前系统简单,可能大家还看不出这种方式的优势,随着以后的设计越来越复杂,大家就能很明显的看到这种设计方式的优势所在了。
|