实验 10:有限状态机(FSM)— LED 多模式控制
🛒 开发板: EK301 GW5A FPGA 开发板 芯片: GW5A-LV25UG324C1/I0 (Arora V 系列)
一、实验目的
- 掌握 **FPGA 三段式状态机(3-always FSM)**的标准写法——状态寄存器、次态逻辑、输出逻辑各自独立。
- 理解独热码(One-Hot) 与二进制编码状态机的区别及适用场景。
- 学会用状态转移图设计多模式 LED 控制逻辑。
- 掌握
always @(*)组合逻辑块的用法,理解与always @(posedge clk)时序逻辑的区别。
二、实验过程
2.1 硬件连接
| 信号 | 方向 | FPGA 引脚 | 说明 |
|---|---|---|---|
| CLK | 输入 | T9 | 50MHz 系统时钟 |
| KEY1 | 输入 | C10 | 模式切换(前进) |
| KEY2 | 输入 | A10 | 任意时刻回 IDLE |
| LED1~7 | 输出 | G9/E7/E6/A5/A4/B4/A3/C5 | 8 路蓝色 LED |
2.2 FSM 基础
有限状态机(Finite State Machine) 是数字逻辑设计的核心范式。FPGA 中最常用的写法是三段式:
第一段 — 状态寄存器 (时序逻辑):
always @(posedge clk) state <= next_state;
功能: 在时钟上升沿将次态锁存为现态
第二段 — 次态逻辑 (组合逻辑):
always @(*) next_state = f(state, inputs);
功能: 根据当前状态和输入,纯组合逻辑计算下一个状态
第三段 — 输出逻辑 (时序逻辑):
always @(posedge clk) output = f(state);
功能: 根据当前状态产生输出(可以是 Moore 型或 Mealy 型)
Moore vs Mealy:
| 类型 | 输出依赖 | 本实验中的体现 |
|---|---|---|
| Moore | 仅取决于当前状态 | 第三段的 case(state) |
| Mealy | 取决于状态 + 输入 | 第二段中 next_state = f(state, kp) |
本实验为 Moore 型三段式状态机——输出仅由
state决定。
2.3 状态定义与转移图
7 种 LED 模式(状态):
| 状态名 | 编码 | LED 行为 | 说明 |
|---|---|---|---|
| S_IDLE | 0 | 全灭 | 空闲状态,上电默认 |
| S_FLOW_R | 1 | 右移流水 | 亮点从左向右循环移动 |
| S_FLOW_L | 2 | 左移流水 | 亮点从右向左循环移动 |
| S_BLINK | 3 | 全闪烁 | 8 灯同时 1Hz 翻转 |
| S_ODD | 4 | 奇数闪烁 | LED2,3,5,7 闪烁 |
| S_EVEN | 5 | 偶数闪烁 | LED1,2,4,6 闪烁 |
| S_ALL | 6 | 全亮 | 8 灯全部点亮 |
状态转移图:
KEY1 KEY1 KEY1 KEY1 KEY1 KEY1
IDLE ──→ FLOW_R ──→ FLOW_L ──→ BLINK ──→ ODD ──→ EVEN ──→ ALL ──→ IDLE
↑ ↑ ↑ ↑ ↑ ↑ ↑ │
└────────┴─────────┴─────────┴────────┴────────┴────────┴────────┘
KEY2 (任意时刻回 IDLE)
KEY1:沿状态环前进。KEY2:任意时刻无条件回到 IDLE。
2.4 模块设计
模块名:fsm_led
端口:
| 端口 | 方向 | 位宽 | 说明 |
|---|---|---|---|
clk | input | 1 | 50MHz 系统时钟 |
key | input | [1:0] | KEY1/KEY2 |
led | output | [7:0] | LED 输出 |
内部信号:
| 信号 | 位宽 | 说明 |
|---|---|---|
state | [2:0] | 现态寄存器 |
next_state | [2:0] | 次态(组合逻辑计算) |
kp | [1:0] | 按键消抖脉冲 |
t1 | 1 | 1Hz tick(用于流水/闪烁) |
三、代码逐段讲解
3.1 第一段:状态寄存器
always @(posedge clk)
state <= next_state;
- 最简单的时序逻辑——每个时钟上升沿,用
next_state更新state。 - 这是三段式中唯一包含了"记忆"的 always 块。
state是寄存器(综合为 DFF),next_state是组合逻辑的输出。
3.2 第二段:次态逻辑(组合逻辑)
always @(*) begin
next_state = state; // 默认保持当前状态
case (state)
S_IDLE: if (kp[0]) next_state = S_FLOW_R;
S_FLOW_R: if (kp[0]) next_state = S_FLOW_L;
S_FLOW_L: if (kp[0]) next_state = S_BLINK;
S_BLINK: if (kp[0]) next_state = S_ODD;
S_ODD: if (kp[0]) next_state = S_EVEN;
S_EVEN: if (kp[0]) next_state = S_ALL;
S_ALL: if (kp[0]) next_state = S_IDLE;
default: next_state = S_IDLE;
endcase
if (kp[1]) next_state = S_IDLE; // KEY2: 无条件回 IDLE
end
关键设计要点:
-
开头赋值
next_state = state:- 这是组合逻辑 always 块的默认赋值。
- 保证没有
kp[0]触发时状态不变。 - 防止锁存器——组合逻辑中每条路径都必须有赋值,默认赋值是最安全的写法。
-
kp[1]放在最后:- 写在
endcase之后,优先级高于kp[0]的状态转移。 - 意味着同时按下 KEY1 和 KEY2,KEY2 优先(回 IDLE)。
- 写在
-
always @(*)敏感列表:@(*)是 Verilog-2001 语法,表示对块内所有信号敏感。state、kp任一变化都会重新计算next_state。
⚠️ 常见错误:忘记写
next_state = state默认赋值,导致某条路径没有赋值 → 综合器推断出锁存器(Latch)→ 警告 "inferred latch"。
3.3 第三段:输出逻辑
initial led = 8'b10000001;
always @(posedge clk) begin
case (state)
S_IDLE: led <= 8'h00; // 全灭
S_FLOW_R: if (t1) led <= {led[6:0], led[7]}; // 右移
S_FLOW_L: if (t1) led <= {led[0], led[7:1]}; // 左移
S_BLINK: if (t1) led <= ~led; // 全翻转
S_ODD: if (t1) led <= {led[6:0], ~led[6]}; // 奇数位闪烁
S_EVEN: if (t1) led <= {led[6:0], led[6]}; // 偶数位闪烁
S_ALL: led <= 8'hFF; // 全亮
default: led <= 8'h00;
endcase
end
各状态详解:
- S_FLOW_R(右移):
{led[6:0], led[7]}将最高位移到最低位,实现循环右移。配合t1(1Hz),每秒移动一次。 - S_FLOW_L(左移):
{led[0], led[7:1]}将最低位移到最高位,实现循环左移。 - S_BLINK(全闪):
led <= ~led,1Hz 翻转全部 8 位。 - S_ODD(奇数闪烁):利用移位 + 取反最高位,实现奇数位的交替亮灭。
~led[6]与移位拼接形成交替效果。 - S_EVEN(偶数闪烁):类似逻辑,但最高位不变(
led[6]直接拼接)。
⚠️ S_ODD 和 S_EVEN 的实现方式较简洁——通过
{led[6:0], ~led[6]}的拼接移位,每次 tick 保证奇数/偶数位翻转。注意观察实际效果,看奇偶位是否严格按预期闪烁。
3.4 1Hz tick 生成
localparam MAX_1HZ = 50_000_000 - 1;
reg [25:0] c1;
reg t1;
always @(posedge clk)
if (c1 == MAX_1HZ) begin c1 <= 0; t1 <= 1; end
else begin c1 <= c1 + 1; t1 <= 0; end
- 与实验 02 完全一致的 1Hz 单周期脉冲生成器。
t1在第三段中作为 LED 更新的使能信号。
四、实验现象
| 操作 | 状态 | LED 现象 |
|---|---|---|
| 上电 | IDLE | 全灭(等待按键) |
| 按 KEY1 一次 | FLOW_R | 单个亮点从左向右循环移动 |
| 按 KEY1 二次 | FLOW_L | 单个亮点从右向左循环移动 |
| 按 KEY1 三次 | BLINK | 8 灯同时 1Hz 闪烁 |
| 按 KEY1 四次 | ODD | 奇数位(LED2,3,5,7)交替亮灭 |
| 按 KEY1 五次 | EVEN | 偶数位(LED1,2,4,6)交替亮灭 |
| 按 KEY1 六次 | ALL | 8 灯全亮 |
| 按 KEY1 七次 | IDLE | 回到空闲(全灭) |
| 任意状态按 KEY2 | IDLE | 立即回到全灭 |
验证要点:
- ✅ KEY1 沿状态环顺序前进,无跳变。
- ✅ KEY2 在任何状态下都能立即回到 IDLE。
- ✅ 右移/左移速度为 ~1 步/秒。
- ✅ 上电后全灭,不自动进入任何模式(等待按键)。
- ✅ 切换到新状态后 LED 行为立即改变。
五、掌握知识点
| 知识点 | 说明 | 在本实验中的体现 |
|---|---|---|
| 三段式 FSM | 状态寄存器 + 次态逻辑 + 输出逻辑 | 3 个独立 always 块 |
| Moore 型状态机 | 输出仅取决于当前状态 | 第三段 case(state) 无外部输入 |
组合逻辑 always @(*) | 纯组合逻辑,敏感列表自动 | 第二段次态计算 |
| 默认赋值防锁存 | next_state = state 开头 | 第二段起始行 |
| 状态转移图 | 图形化描述 FSM 行为 | 7 状态环形转移 + KEY2 中断 |
| 独热码 | 每状态 1 个 FF,状态数 = 位宽 | 3-bit 编码 7 个状态(非严格独热) |
附录:源码
top.v
//////////////////////////////////////////////////////////////////////////////////
// Company: 武汉芯路恒科技有限公司
// Engineer: 小梅哥团队
// Web: www.corecourse.cn
//
// Create Date: 2020/07/20 00:00:00
// Design Name: fsm_led
// Module Name: fsm_led
// Project Name: EK301 实验08
// Description: 有限状态机(FSM) — LED多模式控制
// 三段式状态机: KEY1切换模式, KEY2回IDLE
// 7种LED模式循环: IDLE→右移→左移→闪烁→奇数→偶数→全亮→IDLE
//
// Dependencies: key_debounce.v (exp_06)
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 芯片: GW5A-LV25UG324C1/I0, 时钟50MHz
// 参照 ch10_fsm_hello 代码风格
//////////////////////////////////////////////////////////////////////////////////
module fsm_led (
input wire clk, // 50MHz 系统时钟
input wire reset_n, // 复位信号,低有效
input wire [1:0] key, // KEY1~KEY2 (按下=0)
output reg [7:0] led // LED1~LED8
);
wire reset = ~reset_n;
// ---- 按键消抖例化 ----
wire [1:0] key_flag;
wire [1:0] key_state; // 1=释放, 0=按下
key_debounce u_key0 (.clk(clk), .reset_n(reset_n), .key_in(key[0]), .key_flag(key_flag[0]), .key_state(key_state[0]));
key_debounce u_key1 (.clk(clk), .reset_n(reset_n), .key_in(key[1]), .key_flag(key_flag[1]), .key_state(key_state[1]));
// ---- 1Hz tick 生成 ----
localparam MAX_1HZ = 50_000_000 - 1;
reg [25:0] cnt_1hz;
reg tick_1hz;
always@(posedge clk or posedge reset)
if(reset)begin
cnt_1hz <= 26'd0;
tick_1hz <= 1'b0;
end
else if(cnt_1hz == MAX_1HZ)begin
cnt_1hz <= 26'd0;
tick_1hz <= 1'b1;
end
else begin
cnt_1hz <= cnt_1hz + 1'b1;
tick_1hz <= 1'b0;
end
// ---- 状态定义 (独热码) ----
localparam
S_IDLE = 3'b000,
S_FLOW_R = 3'b001,
S_FLOW_L = 3'b010,
S_BLINK = 3'b011,
S_ODD = 3'b100,
S_EVEN = 3'b101,
S_ALL = 3'b110;
reg [2:0] state;
reg [2:0] next_state;
// ---- 第一段: 状态寄存器 ----
always@(posedge clk or posedge reset)
if(reset)
state <= S_IDLE;
else
state <= next_state;
// ---- 第二段: 次态逻辑 ----
always@(*)begin
next_state = state;
case(state)
S_IDLE: if(key_flag[0] && key_state[0] == 1'b0) next_state = S_FLOW_R;
S_FLOW_R: if(key_flag[0] && key_state[0] == 1'b0) next_state = S_FLOW_L;
S_FLOW_L: if(key_flag[0] && key_state[0] == 1'b0) next_state = S_BLINK;
S_BLINK: if(key_flag[0] && key_state[0] == 1'b0) next_state = S_ODD;
S_ODD: if(key_flag[0] && key_state[0] == 1'b0) next_state = S_EVEN;
S_EVEN: if(key_flag[0] && key_state[0] == 1'b0) next_state = S_ALL;
S_ALL: if(key_flag[0] && key_state[0] == 1'b0) next_state = S_IDLE;
default: next_state = S_IDLE;
endcase
// KEY2任意时刻回IDLE
if(key_flag[1] && key_state[1] == 1'b0)
next_state = S_IDLE;
end
// ---- 第三段: 输出逻辑 (状态切换时初始化, tick时更新) ----
reg [2:0] state_prev;
always @(posedge clk) state_prev <= state;
wire state_changed = (state != state_prev);
always@(posedge clk or posedge reset)
if(reset) begin
led <= 8'b00000001;
end else if (state_changed) begin
// 进入新状态时设置初始LED值
case(state)
S_IDLE: led <= 8'h00;
S_FLOW_R: led <= 8'b00000001; // 初始LED1亮
S_FLOW_L: led <= 8'b10000000; // 初始LED8亮
S_BLINK: led <= 8'b11111111;
S_ODD: led <= 8'b01010101;
S_EVEN: led <= 8'b10101010;
S_ALL: led <= 8'hFF;
default: led <= 8'h00;
endcase
end else if (tick_1hz) begin
case(state)
S_FLOW_R: led <= {led[6:0], led[7]}; // 右移
S_FLOW_L: led <= {led[0], led[7:1]}; // 左移
S_BLINK: led <= ~led; // 全闪烁
S_ODD: led <= led ^ 8'b01010101; // 奇数翻转
S_EVEN: led <= led ^ 8'b10101010; // 偶数翻转
default: ;
endcase
end
endmodule