跳到主要内容

实验 05:按键控制 LED

🛒 开发板购买: EK303 GW1N FPGA核心板


一、实验目的

  1. 掌握 Verilog 模块例化——将实验 04 的消抖模块作为子模块调用。
  2. 学习参数传递 #(.PARAM(val)) 的语法,实现可配置的模块复用。
  3. 理解 Toggle 翻转逻辑——按一次亮、再按灭。
  4. 掌握多模式状态机设计——在不同的 LED 工作模式间切换。

二、实验过程

2.1 硬件连接

外设引脚
KEY169
KEY270
LED1~3Pin 8 / 9 / 10

2.2 设计思路

系统结构

KEY1 ──→ [消抖] ──→ key1_pressed ──→ [Toggle逻辑] ──→ led1
KEY2 ──→ [消抖] ──→ key2_pressed ──→ [模式状态机] ──→ led2, led3

功能设计

按键功能详细
KEY1Toggle按一次 LED1 翻转亮灭状态
KEY2模式切换按一次切换 LED2/LED3 工作模式

三种模式

模式LED 行为
模式 0全部常灭
模式 1LED2 以 1Hz 闪烁
模式 2LED2 和 LED3 交替闪烁

2.3 Verilog 模块设计

顶层模块名key_ctrl_led

例化的子模块:两个 key_debounce 实例(分别处理 KEY1 和 KEY2)。

顶层端口

端口方向位宽说明
clk_50minput1时钟
key1, key2input1两个按键输入
led1, led2, led3output1三个 LED

内部信号

信号说明
key1_pressedKEY1 消抖后的脉冲
key2_pressedKEY2 消抖后的脉冲
mode (2 bit)当前模式编号 (0/1/2)
led1_stateLED1 当前亮灭状态
blink_cnt (26 bit)1Hz 闪烁计数器

逻辑描述

  1. 例化两个消抖模块,分别处理 KEY1、KEY2。
  2. key1_pressed 脉冲到来 → led1_state <= ~led1_state(Toggle)。
  3. key2_pressed 脉冲到来 → mode <= mode + 1(循环 0→1→2→0)。
  4. 根据 mode 值驱动 LED2/LED3:
  • mode 0:全部输出 0(灭)。
  • mode 1:LED2 用 1Hz 计数器翻转。
  • mode 2:LED2 和 LED3 以 1Hz 交替(LED2 亮时 LED3 灭,反之亦然)。

2.4 引脚约束

信号约束
key1, key2IO_LOC 对应按键引脚,PULL_MODE=UP
led1~3Pin 8/9/10,IO_TYPE=LVCMOS33

三、实验现象

操作现象
按下 KEY1LED1 亮;再按,LED1 灭;交替切换
按下 KEY2(第 1 次)进入模式 1:LED2 开始以 1Hz 闪烁
按下 KEY2(第 2 次)进入模式 2:LED2 和 LED3 交替闪烁(你亮我灭)
按下 KEY2(第 3 次)进入模式 0:所有 LED 灭
继续按 KEY2模式循环 0→1→2→0...

按键响应准确,每个模式切换有即时视觉反馈。


四、掌握知识点

知识点说明
模块例化key_debounce #(.DEBOUNCE_MS(20)) u_key1(...)
参数传递#(.PARAM(val)) 让模块可配置
Toggle 逻辑state <= ~state 一比特翻转
多模式状态机case(mode) 控制不同行为
交替闪烁led2 <= blink; led3 <= ~blink 反向驱动

五、思考题

  1. 如果 KEY2 要支持 5 种模式,需要修改哪些代码?位宽够吗?
  2. 怎么在模式切换时让 LED 闪烁两次作为"模式切换确认"信号?
  3. 消抖模块的 DEBOUNCE_MS 参数在例化时怎么传入?

附录:源码清单

top.v

// =============================================================================
// top.v - 按键控制LED (Key Control LED)
// 芯片: GW1N-LV9LQ144C6/I5
// 时钟: 50MHz (Pin 98)
//
// 功能:
// KEY1 (Pin 69): Toggle LED1 — 按一次翻转LED1亮灭状态
// KEY2 (Pin 70): 模式切换 — 按一次在三种模式间循环切换
//
// 模式0: 全部LED灭
// 模式1: LED2 以 1Hz 闪烁
// 模式2: LED2 和 LED3 以 1Hz 交替闪烁 (你亮我灭)
//
// 子模块:
// 例化两个 key_debounce 实例 (来自实验04), 分别处理 KEY1 和 KEY2
// =============================================================================

module key_ctrl_led (
input wire clk_50m, // 50MHz 系统时钟
input wire key1, // KEY1 (Pin 69, 按下低电平)
input wire key2, // KEY2 (Pin 70, 按下低电平)
output reg led1, // LED1 (Pin 8, Toggle控制)
output reg led2, // LED2 (Pin 9, 模式控制)
output reg led3 // LED3 (Pin 10, 模式控制)
);

// =========================================================================
// 本地参数
// =========================================================================
localparam BLINK_HALF_MAX = 26'd24_999_999; // 0.5s @ 50MHz → 1Hz闪烁
localparam MODE_MAX = 2'd2; // 最大模式编号

// =========================================================================
// 内部信号
// =========================================================================
wire key1_pressed; // KEY1 消抖后的脉冲
wire key2_pressed; // KEY2 消抖后的脉冲

reg [1:0] mode; // 当前模式: 0/1/2
reg [25:0] blink_cnt; // 1Hz 闪烁计数器

// =========================================================================
// 子模块例化: KEY1 消抖
// 参数 DEBOUNCE_MS=20 → 20ms 消抖
// =========================================================================
key_debounce #(
.DEBOUNCE_MS(20)
) u_key1_debounce (
.clk_50m (clk_50m),
.key_in (key1),
.key_pressed (key1_pressed)
);

// =========================================================================
// 子模块例化: KEY2 消抖
// =========================================================================
key_debounce #(
.DEBOUNCE_MS(20)
) u_key2_debounce (
.clk_50m (clk_50m),
.key_in (key2),
.key_pressed (key2_pressed)
);

// =========================================================================
// KEY1 处理: Toggle LED1
// 每次 key1_pressed 脉冲到来时,翻转 LED1 的亮灭状态
//
// 效果:
// 按一下 → LED1 亮 (led1=1)
// 再按 → LED1 灭 (led1=0)
// 再按 → LED1 亮 (led1=1) ... 如此往复
// =========================================================================
always @(posedge clk_50m) begin
if (key1_pressed)
led1 <= ~led1;
end

// =========================================================================
// KEY2 处理: 模式切换
// 每次 key2_pressed 脉冲到来时,mode 循环递增: 0→1→2→0→1→2...
// =========================================================================
always @(posedge clk_50m) begin
if (key2_pressed) begin
if (mode == MODE_MAX)
mode <= 2'd0; // 超出最大值则回0
else
mode <= mode + 1'b1;
end
end

// =========================================================================
// 1Hz 闪烁计数器
// 自由运行: 0→24,999,999→0 循环
// 半周期 = 0.5s = 25,000,000 个时钟周期
// =========================================================================
reg blink_toggle; // 1Hz 翻转信号

always @(posedge clk_50m) begin
if (blink_cnt == BLINK_HALF_MAX) begin
blink_cnt <= 26'd0;
blink_toggle <= ~blink_toggle;
end else begin
blink_cnt <= blink_cnt + 1'b1;
end
end

// =========================================================================
// 模式输出逻辑
//
// 模式0 (2'd0): 全部LED灭
// led2 = 0, led3 = 0
//
// 模式1 (2'd1): LED2 1Hz闪烁, LED3灭
// led2 = blink_toggle, led3 = 0
//
// 模式2 (2'd2): LED2/LED3 交替闪烁
// led2 = blink_toggle
// led3 = ~blink_toggle (与LED2反相, 你亮我灭)
// =========================================================================
always @(posedge clk_50m) begin
case (mode)
2'd0: begin
led2 <= 1'b0;
led3 <= 1'b0;
end

2'd1: begin
led2 <= blink_toggle;
led3 <= 1'b0;
end

2'd2: begin
led2 <= blink_toggle;
led3 <= ~blink_toggle;
end

default: begin
led2 <= 1'b0;
led3 <= 1'b0;
end
endcase
end

// =========================================================================
// 上电初始值
// =========================================================================
initial begin
led1 = 1'b0; // 初始LED1灭
mode = 2'd0; // 初始模式0 (全灭)
blink_toggle = 1'b0; // 闪烁信号初值
end

endmodule