学习fpga也有段时间了,但后台有几个朋友问我能不能分享一点简单入门例子,于是我打算发经典的如何控制led的例子,本文主要分享设计流程以及简单的verilog语法。
设计流程主要包括五个步骤模块设计、波形设计、编写rtl代码、仿真验证、上板验证,少部分根据设计的特殊性,有的需要进行画状态机和流程图。
1.按键控制小灯亮灭
实验目标:按下按键,小灯亮;松开按键,小灯灭。
了解硬件设计是写代码的前提,按键按下该管脚会拉低,led灯高电平点亮。
模块设计
模块设计:把复杂的功能进行拆分一个个独立的小模块,不仅增加模块的复用性,在后期的调试中也更简单。
波形设计
波形设计是野火家推出独有的设计方法,我个人觉得很受用。很好的体现了FPGA内部数字电路的并行执行的特点,不了解的朋友看看。
编写rtl代码:
FPGA采用的是硬件描述语言,语言构成电路。这与单片机不同,后者是如何写程序(指令)控制已有电路工作。
`timescale 1ns/1ns
module led
(
input wire key_in,
output wire led_out
);
assign led_out = ~key_in;
endmodule
仿真验证:
仿真验证又称测试脚本或激励,测试rtl代码中信号波形是否符合之前设计的波形。
`timescale 1ns/1ns
module tb_led();
reg key_in;
wire led_out;
initial key_in <= 1'b0;
always #10 key_in <= {$random} % 2;
led led_inst
(
.key_in (key_in ),
.led_out(led_out)
);
endmodule
引脚约束:
根据硬件原理图确定按键和 led 分别与 FPGA 芯片的哪个引脚对应。
#按键
NET "key_in" LOC = U10 | IOSTANDARD = LVCMOS33;
#led灯
NET "led_out" LOC = A11 | IOSTANDARD = LVCMOS33;
上板验证:
2.一上电小灯闪烁
实验目标:一上电或按下复位按键,小灯实现闪烁,闪烁一次的时间为1秒。
rtl代码:
`timescale 1ns/1ns
module breath_led
#(
parameter CNT_MAX = 25'd24_999_999
)
(
input wire sys_clk ,
input wire sys_rst_n ,
output reg led_out
);
reg [24:0] cnt; //经计算得需要25位宽的寄存器才够500ms
//cnt:计数器计数,当计数到CNT_MAX的值时清零
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt <= 25'b0;
else if(cnt == CNT_MAX)
cnt <= 25'b0;
else
cnt <= cnt + 1'b1;
//led_out:输出控制一个LED灯,每当计数满标志信号有效时取反
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
led_out <= 1'b0;
else if(cnt == CNT_MAX)
led_out <= ~led_out; //亮灭之间跳转
endmodule
验证:
仿真验证:
`timescale 1ns/1ns
module tb_ledflash();
reg sys_clk ;
reg sys_rst_n ;
wire led_out ;
initial begin
sys_clk = 1'b1;
sys_rst_n = 1'b0;
#20
sys_rst_n = 1'b1;
end
always #10 sys_clk = !sys_clk; //控制一个周期20ns
ledflash
#(
.CNT_MAX (25'd24 ) //改变CNT_MAX值的大小,缩短仿真时间
)
ledflash_inst
(
.sys_clk (sys_clk ) ,
.sys_rst_n (sys_rst_n) ,
. led_out ( led_out )
);
endmodule
上板验证:
效果为视频不方便展示。
3.流水灯
实验目标:每过0.5秒右移动一次
rtl代码:
`timescale 1ns/1ns
//流水灯:每过0.5s移一次
module water_led
#(
parameter CNT_MAX = 25'd24_999_999 , //0.5s计数最大值
parameter CNT = 3'd3
)
(
input wire clk ,
input wire rst ,
output reg [4:0] led
);
reg [24:0] cnt_500ms ;
reg [2:0] cnt;
//0.5s计时器
always@(posedge clk or negedge rst)
if(!rst)
cnt_500ms <= 25'b0;
else if(cnt_500ms == CNT_MAX)
cnt_500ms <= 25'b0;
else
cnt_500ms <= cnt_500ms + 1'b1;
//cnt计数周期为4的计数器
always@(posedge clk or negedge rst)
if(!rst)
cnt <= 3'd0;
else if((cnt_500ms == CNT_MAX-1'b1) && (cnt == CNT)) //计数满4,清零
cnt <= 3'd0;
else if(cnt_500ms == CNT_MAX-1'b1) //时序逻辑延后了一拍,这里给他减一对齐
cnt <= cnt + 1'b1;
else
cnt <= cnt;
always@(posedge clk or negedge rst) //case()分支语句
if(!rst)
led <= 4'b0001;
else case (cnt)
3'd0: led <= 4'b0001;
3'd1: led <= 4'b0010;
3'd2: led <= 4'b0100;
3'd3: led <= 4'b1000;
default: led <= 4'b0001;
endcase
endmodule
验证:
`timescale 1ns/1ns
module tb_water_led();
reg clk ;
reg rst ;
wire [3:0] led ;
initial begin
clk = 1'b1;
rst = 1'b0;
#20
rst = 1'b1;
end
always #10 clk = !clk;
water_led
#(
.CNT_MAX (25'd24 ) //改变CNT_MAX值的大小,缩短仿真时间
)
water_led_inst
(
.clk (clk ) ,
.rst (rst) ,
.led (led)
);
endmodule
仿真验证:
上板验证:
效果为视频不方便展示。
总结:
1.仿真就是测试rtl代码的正确性,编写整个系统的输入信号使其满足rtl代码的需求,就可以查看其内部逻辑的正确性。reg型表示的寄存器类型,reg相当于存储单元,具记忆功能; wire的本质是一条没有逻辑的连线,也就是说输入时什么输出也就是什么,无记忆功能。我们要对输入信号进行初始化、赋值故输入一般使用 reg,输出随输入变化故使用 wire。
说明:
本人使用的是野火家Xilinx Spartan6系列开发板及配套教程,以上内容如有疑惑或错误欢迎评论区指出,或者移步B站观看野火家教学视频。
开发软件:ise14.7 仿真:modelsim 10.5
如需上述资料私信或留下邮箱