跳到主要内容

实验 10:有限状态机(FSM)— LED 多模式控制

🛒 开发板: EK301 GW5A FPGA 开发板 芯片: GW5A-LV25UG324C1/I0 (Arora V 系列)


一、实验目的

  1. 掌握 **FPGA 三段式状态机(3-always FSM)**的标准写法——状态寄存器、次态逻辑、输出逻辑各自独立。
  2. 理解独热码(One-Hot) 与二进制编码状态机的区别及适用场景。
  3. 学会用状态转移图设计多模式 LED 控制逻辑。
  4. 掌握 always @(*) 组合逻辑块的用法,理解与 always @(posedge clk) 时序逻辑的区别。

二、实验过程

2.1 硬件连接

信号方向FPGA 引脚说明
CLK输入T950MHz 系统时钟
KEY1输入C10模式切换(前进)
KEY2输入A10任意时刻回 IDLE
LED1~7输出G9/E7/E6/A5/A4/B4/A3/C58 路蓝色 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_IDLE0全灭空闲状态,上电默认
S_FLOW_R1右移流水亮点从左向右循环移动
S_FLOW_L2左移流水亮点从右向左循环移动
S_BLINK3全闪烁8 灯同时 1Hz 翻转
S_ODD4奇数闪烁LED2,3,5,7 闪烁
S_EVEN5偶数闪烁LED1,2,4,6 闪烁
S_ALL6全亮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

端口

端口方向位宽说明
clkinput150MHz 系统时钟
keyinput[1:0]KEY1/KEY2
ledoutput[7:0]LED 输出

内部信号

信号位宽说明
state[2:0]现态寄存器
next_state[2:0]次态(组合逻辑计算)
kp[1:0]按键消抖脉冲
t111Hz 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

关键设计要点

  1. 开头赋值 next_state = state

    • 这是组合逻辑 always 块的默认赋值
    • 保证没有 kp[0] 触发时状态不变。
    • 防止锁存器——组合逻辑中每条路径都必须有赋值,默认赋值是最安全的写法。
  2. kp[1] 放在最后

    • 写在 endcase 之后,优先级高于 kp[0] 的状态转移。
    • 意味着同时按下 KEY1 和 KEY2,KEY2 优先(回 IDLE)。
  3. always @(*) 敏感列表:

    • @(*) 是 Verilog-2001 语法,表示对块内所有信号敏感。
    • statekp 任一变化都会重新计算 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 三次BLINK8 灯同时 1Hz 闪烁
按 KEY1 四次ODD奇数位(LED2,3,5,7)交替亮灭
按 KEY1 五次EVEN偶数位(LED1,2,4,6)交替亮灭
按 KEY1 六次ALL8 灯全亮
按 KEY1 七次IDLE回到空闲(全灭)
任意状态按 KEY2IDLE立即回到全灭

验证要点

  • ✅ 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