手把手教你构建首个 FPGA LED 闪烁器:分步教程
分步指南:构建你的第一个 FPGA LED 闪烁器
第一部分:设计 VHDL 或 Verilog 代码
本教程将教你编写 VHDL 和 Verilog 代码,以用户定义频率驱动 LED。根据工作流选择适合的语言。
编写 HDL 时必须验证设计行为是否符合预期。错误难免,因此仿真必不可少。教程分为两个关键阶段:
- HDL 设计
- HDL 仿真
跳过仿真会导致昂贵的上板调试。请将仿真视为强制检查点。
项目需求
编写 HDL 使 LED 以 100 Hz、50 Hz、10 Hz 或 1 Hz、占空比 50% 闪烁。两个拨码开关选择频率,额外的 LED_EN 开关置高电平才启用 LED。FPGA 运行在 25 MHz 晶振时钟下。
频率选择真值表:
| 使能 | 开关 1 | 开关 2 | LED 驱动频率 |
|---|---|---|---|
| 0 | – | – | 禁用 |
| 1 | 0 | 0 | 100 Hz |
| 1 | 0 | 1 | 50 Hz |
| 1 | 1 | 0 | 10 Hz |
| 1 | 1 | 1 | 1 Hz |
信号汇总:
| 信号名 | 方向 | 描述 |
|---|---|---|
| i_clock | 输入 | 25 MHz 时钟 |
| i_enable | 输入 | 使能开关(逻辑 0 = LED 关) |
| i_switch_1 | 输入 | 频率选择开关 1 |
| i_switch_2 | 输入 | 频率选择开关 2 |
| o_led_drive | 输出 | LED 驱动信号 |
四个并行计数器进程监控 25 MHz 时钟,为各目标频率产生翻转信号。即使某频率未被选中,其计数器仍持续运行——这是硬件并发的核心原则。
拨码开关构成多路选择器,将选中的翻转信号路由至 LED 输出。多路选择器属纯组合逻辑,无需时钟。
下图展示架构框图:

VHDL 实现
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity tutorial_led_blink is port ( i_clock : in std_logic; i_enable : in std_logic; i_switch_1 : in std_logic; i_switch_2 : in std_logic; o_led_drive : out std_logic ); end tutorial_led_blink; architecture rtl of tutorial_led_blink is -- 生成所需频率的常量: -- 公式:(25 MHz / 目标频率 * 50% 占空比) -- 100 Hz: 25,000,000 / 100 * 0.5 = 125,000 constant c_CNT_100HZ : natural := 125000; constant c_CNT_50HZ : natural := 250000; constant c_CNT_10HZ : natural := 1250000; constant c_CNT_1HZ : natural := 12500000; -- 计数器信号 signal r_CNT_100HZ : natural range 0 to c_CNT_100HZ; signal r_CNT_50HZ : natural range 0 to c_CNT_50HZ; signal r_CNT_10HZ : natural range 0 to c_CNT_10HZ; signal r_CNT_1HZ : natural range 0 to c_CNT_1HZ; -- 翻转信号 signal r_TOGGLE_100HZ : std_logic := '0'; signal r_TOGGLE_50HZ : std_logic := '0'; signal r_TOGGLE_10HZ : std_logic := '0'; signal r_TOGGLE_1HZ : std_logic := '0'; -- 一位选择信号 signal w_LED_SELECT : std_logic; begin -- 所有进程在各自频率翻转特定信号,持续运行 p_100_HZ : process (i_clock) is begin if rising_edge(i_clock) then if r_CNT_100HZ = c_CNT_100HZ-1 then -- -1 因计数器从 0 开始 r_TOGGLE_100HZ <= not r_TOGGLE_100HZ; r_CNT_100HZ <= 0; else r_CNT_100HZ <= r_CNT_100HZ + 1; end if; end if; end process p_100_HZ; p_50_HZ : process (i_clock) is begin if rising_edge(i_clock) then if r_CNT_50HZ = c_CNT_50HZ-1 then r_TOGGLE_50HZ <= not r_TOGGLE_50HZ; r_CNT_50HZ <= 0; else r_CNT_50HZ <= r_CNT_50HZ + 1; end if; end if; end process p_50_HZ; p_10_HZ : process (i_clock) is begin if rising_edge(i_clock) then if r_CNT_10HZ = c_CNT_10HZ-1 then r_TOGGLE_10HZ <= not r_TOGGLE_10HZ; r_CNT_10HZ <= 0; else r_CNT_10HZ <= r_CNT_10HZ + 1; end if; end if; end process p_10_HZ; p_1_HZ : process (i_clock) is begin if rising_edge(i_clock) then if r_CNT_1HZ = c_CNT_1HZ-1 then r_TOGGLE_1HZ <= not r_TOGGLE_1HZ; r_CNT_1HZ <= 0; else r_CNT_1HZ <= r_CNT_1HZ + 1; end if; end if; end process p_1_HZ; -- 基于开关输入创建多路选择器 w_LED_SELECT <= r_TOGGLE_100HZ when (i_switch_1 = '0' and i_switch_2 = '0') else r_TOGGLE_50HZ when (i_switch_1 = '0' and i_switch_2 = '1') else r_TOGGLE_10HZ when (i_switch_1 = '1' and i_switch_2 = '0') else r_TOGGLE_1HZ; -- 仅当 i_enable 为高时允许 o_led_drive 驱动(与门)。 o_led_drive <= w_LED_SELECT and i_enable; end rtl;
Verilog 实现
module tutorial_led_blink ( i_clock, i_enable, i_switch_1, i_switch_2, o_led_drive ); input i_clock; input i_enable; input i_switch_1; input i_switch_2; output o_led_drive; // 常量(参数)生成所需频率: // 输入时钟 25 kHz(任意选取)。 // 公式:(25 kHz / 100 Hz * 50% 占空比) // 100 Hz: 25,000 / 100 * 0.5 = 125 parameter c_CNT_100HZ = 125; parameter c_CNT_50HZ = 250; parameter c_CNT_10HZ = 1250; parameter c_CNT_1HZ = 12500; // 计数器信号 reg [31:0] r_CNT_100HZ = 0; reg [31:0] r_CNT_50HZ = 0; reg [31:0] r_CNT_10HZ = 0; reg [31:0] r_CNT_1HZ = 0; // 翻转信号 reg r_TOGGLE_100HZ = 1'b0; reg r_TOGGLE_50HZ = 1'b0; reg r_TOGGLE_10HZ = 1'b0; reg r_TOGGLE_1HZ = 1'b0; // 一位选择信号 reg r_LED_SELECT; wire w_LED_SELECT; begin // 所有 always 块在各自频率翻转特定信号,持续运行 always @ (posedge i_clock) begin if (r_CNT_100HZ == c_CNT_100HZ-1) begin r_TOGGLE_100HZ <= !r_TOGGLE_100HZ; r_CNT_100HZ <= 0; end else r_CNT_100HZ <= r_CNT_100HZ + 1; end always @ (posedge i_clock) begin if (r_CNT_50HZ == c_CNT_50HZ-1) begin r_TOGGLE_50HZ <= !r_TOGGLE_50HZ; r_CNT_50HZ <= 0; end else r_CNT_50HZ <= r_CNT_50HZ + 1; end always @ (posedge i_clock) begin if (r_CNT_10HZ == c_CNT_10HZ-1) begin r_TOGGLE_10HZ <= !r_TOGGLE_10HZ; r_CNT_10HZ <= 0; end else r_CNT_10HZ <= r_CNT_10HZ + 1; end always @ (posedge i_clock) begin if (r_CNT_1HZ == c_CNT_1HZ-1) begin r_TOGGLE_1HZ <= !r_TOGGLE_1HZ; r_CNT_1HZ <= 0; end else r_CNT_1HZ <= r_CNT_1HZ + 1; end // 基于开关输入创建多路选择器 always @ (*) begin case ({i_switch_1, i_switch_2}) // 拼接运算符 2'b11 : r_LED_SELECT <= r_TOGGLE_1HZ; 2'b10 : r_LED_SELECT <= r_TOGGLE_10HZ; 2'b01 : r_LED_SELECT <= r_TOGGLE_50HZ; 2'b00 : r_LED_SELECT <= r_TOGGLE_100HZ; endcase end assign o_led_drive = r_LED_SELECT & i_enable; // 替代多路选择器写法(功能相同,但可读性稍差) // assign w_LED_SELECT = i_switch_1 ? (i_switch_2 ? r_TOGGLE_1HZ : r_TOGGLE_10HZ) : //(i_switch_2 ? r_TOGGLE_50HZ : r_TOGGLE_100HZ); // assign o_led_drive = w_LED_SELECT & i_enable; end endmodule 下一步:在 VHDL 或 Verilog 中仿真该设计,确认行为正确后再部署。
VHDL
- ModelSim 仿真中查看 VHDL 变量的完整技巧
- 用 OSVVM 掌握约束随机验证
- 精通 VHDL Wait 语句:Wait On、Wait Until 与组合用法
- VHDL基础测验第4部分:验证你的理解
- VHDL过程示例:标准逻辑向量自增
- 善用VHDL记录类型构建整洁可复用FIFO接口
- 掌握 VHDL Loop 与 Exit 构造:实用指南
- 在 VHDL 进程中使用敏感度列表实现可靠的 RTL 设计
- 优雅结束 VHDL 测试平台仿真
- 使用VHDL不纯函数提升FSM可读性与可维护性
- 在块RAM中设计健壮的VHDL环形缓冲区FIFO
- 在Linux VPS上用Jenkins、Vivado、GitHub自动化FPGA开发