Vivado 2023.1 状态机实战:4位密码锁6状态转换与10秒倒计时设计
Vivado 2023.1 状态机实战:4位密码锁6状态转换与10秒倒计时设计
1. 状态机设计基础与密码锁应用场景
在FPGA设计中,状态机(Finite State Machine, FSM)是实现复杂控制逻辑的核心方法之一。密码锁系统作为典型的时序控制应用,其核心控制模块非常适合采用状态机来实现。通过状态机,我们可以清晰地定义密码锁的各种工作状态及其转换条件,使设计更加模块化和可维护。
状态机在密码锁中的应用主要体现在以下几个方面:
- 状态定义:明确划分密码锁的工作阶段,如锁定、输入、比较等
- 状态转换:规范各状态之间的转移条件和顺序
- 输出控制:根据当前状态控制LED、数码管等外设
Verilog状态机编码风格对比
| 编码风格 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 一段式 | 代码紧凑 | 可读性差,调试困难 | 简单状态机 |
| 二段式 | 结构清晰 | 可能存在组合逻辑输出 | 中等复杂度设计 |
| 三段式 | 时序明确,可靠性高 | 代码量稍多 | 复杂控制系统 |
在密码锁设计中,我们推荐采用三段式状态机实现,因为它能提供最佳的时序控制和设计可靠性。下面是一个基本的三段式状态机框架:
// 状态定义 typedef enum { S_LOCK, S_KEYIN, S_COMPARE, S_PASS, S_ERROR, S_MODIFY } state_t; // 状态寄存器 always @(posedge clk or posedge reset) begin if (reset) current_state <= S_LOCK; else current_state <= next_state; end // 状态转移逻辑 always @(*) begin case (current_state) S_LOCK: begin if (key_pressed) next_state = S_KEYIN; else next_state = S_LOCK; end // 其他状态转移条件... endcase end // 输出逻辑 always @(posedge clk) begin case (current_state) S_LOCK: begin led_open <= 0; led_close <= 1; end // 其他状态输出... endcase end2. 密码锁状态机详细设计与实现
2.1 六状态定义与功能说明
本设计采用六个状态构建密码锁的核心控制逻辑,每个状态对应密码锁的一个特定工作模式:
s_lock(锁定状态)
- 系统初始状态
- 数码管显示"LC"(Locked)
- 关锁指示灯亮(F2灯亮,F1灯灭)
- 等待用户开始输入密码
s_keyin(密码输入状态)
- 用户正在输入密码
- 启动10秒倒计时并在数码管显示
- 记录用户按键序列
- 密码输入完成或超时后退出
s_compare(密码比较状态)
- 比较用户输入与预设密码
- 瞬时状态,立即跳转到s_pass或s_error
- 不直接控制外设
s_pass(密码正确状态)
- 密码验证成功
- 数码管显示"OP"(Open)
- 开锁指示灯亮(F1灯亮,F2灯灭)
- 允许修改密码操作
s_error(密码错误状态)
- 密码验证失败
- 保持锁定状态
- 可添加错误计数限制功能
s_modify(密码修改状态)
- 授权用户修改密码
- 需再次验证原密码
- 接收并保存新密码
2.2 状态转移图与转换条件
状态转移是状态机设计的核心,需要明确各状态之间的转换条件和优先级。以下是密码锁状态机的关键转移逻辑:
s_lock → s_keyin: 任意密码键按下 s_keyin → s_compare: 确认键按下或4位密码输入完成 s_keyin → s_lock: 倒计时结束(10秒) s_compare → s_pass: 输入密码==存储密码 s_compare → s_error: 输入密码!=存储密码 s_pass → s_modify: 修改键按下 s_modify → s_pass: 新密码确认完成 s_pass → s_lock: 锁定键按下 s_error → s_lock: 自动返回或手动确认倒计时模块设计要点:
- 使用系统时钟分频产生1Hz计时信号
- 10秒倒计时计数器在进入s_keyin状态时启动
- 倒计时值实时显示在数码管上
- 倒计时结束触发状态转移至s_lock
// 10秒倒计时模块示例 module countdown( input clk, input reset, input start, output reg [3:0] count, output reg timeout ); always @(posedge clk or posedge reset) begin if (reset) begin count <= 4'd10; timeout <= 0; end else if (start) begin if (count == 4'd0) begin timeout <= 1; end else begin count <= count - 1; timeout <= 0; end end else begin count <= 4'd10; timeout <= 0; end end endmodule3. EGO1开发板外设接口与状态机整合
3.1 开发板资源映射与配置
EGO1开发板提供了丰富的外设接口,需要正确定义FPGA引脚约束以实现状态机与硬件的连接。以下是关键外设的引脚分配建议:
按键分配:
- 4位密码输入:SW0-SW3
- 确认键:BTN0
- 修改键:BTN1
- 锁定键:BTN2
LED指示:
- 开锁状态:LD0(F1)
- 关锁状态:LD1(F2)
数码管显示:
- 倒计时显示:DIG0-DIG1
- 状态显示:DIG2-DIG3("OP"/"LC")
XDC约束文件关键内容:
# 按键约束 set_property PACKAGE_PIN R11 [get_ports {key[0]}] set_property IOSTANDARD LVCMOS33 [get_ports {key[*]}] # LED约束 set_property PACKAGE_PIN K3 [get_ports led_open] set_property IOSTANDARD LVCMOS33 [get_ports led_open] # 数码管约束 set_property PACKAGE_PIN B4 [get_ports {seg[0]}] set_property IOSTANDARD LVCMOS33 [get_ports {seg[*]}]3.2 状态机与外设的协同设计
状态机需要根据当前状态控制外设的显示和行为,同时响应外设的输入信号。这种双向交互需要仔细设计时序以避免冲突。
状态输出控制真值表:
| 状态 | LED_open | LED_close | 数码管显示 | 其他输出 |
|---|---|---|---|---|
| s_lock | 0 | 1 | LC | - |
| s_keyin | 0 | 1 | 倒计时 | - |
| s_compare | - | - | - | - |
| s_pass | 1 | 0 | OP | 解锁信号 |
| s_error | 0 | 1 | LC | 错误提示 |
| s_modify | 1 | 0 | - | 密码修改 |
注意:数码管显示需要特别处理,因为要同时显示状态和倒计时。建议采用多路复用技术,在s_keyin状态显示倒计时,其他状态显示相应状态代码。
4. Vivado 2023.1中的实现与调试技巧
4.1 工程创建与状态机编码规范
在Vivado 2023.1中创建状态机项目时,建议遵循以下步骤:
- 新建RTL项目,选择EGO1开发板对应的器件型号(XC7A35T-1CSG324C)
- 添加状态机模块文件,采用SystemVerilog语法增强可读性
- 使用Vivado的状态机识别功能(在综合设置中启用FSM提取)
- 为状态变量添加(* fsm_encoding = "one_hot" *)属性优化性能
状态机编码风格建议:
- 使用typedef定义状态枚举,提高代码可读性
- 严格区分组合逻辑和时序逻辑
- 为每个状态添加详细注释说明其功能和行为
- 输出信号尽量寄存器输出,避免毛刺
// 使用SystemVerilog增强的状态机示例 module password_fsm( input logic clk, reset, input logic [3:0] key, input logic confirm, modify, lock, output logic led_open, led_close, output logic [7:0] seg, output logic [3:0] anode ); typedef enum logic [2:0] { S_LOCK, // 3'b000 S_KEYIN, // 3'b001 S_COMPARE, // 3'b010 S_PASS, // 3'b011 S_ERROR, // 3'b100 S_MODIFY // 3'b101 } state_t; (* fsm_encoding = "one_hot" *) state_t current_state, next_state; // 状态寄存器 always_ff @(posedge clk or posedge reset) if (reset) current_state <= S_LOCK; else current_state <= next_state; // 状态转移逻辑 always_comb begin next_state = current_state; unique case (current_state) S_LOCK: if (|key) next_state = S_KEYIN; S_KEYIN: if (confirm) next_state = S_COMPARE; else if (timeout) next_state = S_LOCK; // 其他状态转移... endcase end // 输出逻辑 always_ff @(posedge clk) begin case (current_state) S_LOCK: begin led_open <= 0; led_close <= 1; seg <= 8'b00111001; // 'L' anode <= 4'b1110; end // 其他状态输出... endcase end endmodule4.2 仿真验证与板上调试
状态机的验证分为仿真验证和硬件验证两个阶段,每个阶段都有其特定的调试技巧。
仿真验证要点:
- 创建测试平台模拟所有状态转移
- 特别关注边界条件(如倒计时结束瞬间)
- 验证错误处理路径(连续错误输入等)
- 检查状态输出是否符合预期
常用仿真技巧:
- 使用$display实时显示状态变化
- 添加断言(assert)验证关键条件
- 生成波形图检查时序关系
// 简单的测试平台示例 module tb_password_fsm(); logic clk = 0; logic reset; logic [3:0] key; logic confirm, modify, lock; logic led_open, led_close; logic [7:0] seg; logic [3:0] anode; password_fsm uut(.*); always #5 clk = ~clk; initial begin reset = 1; key = 0; confirm = 0; modify = 0; lock = 0; #100 reset = 0; // 测试正常解锁流程 key = 4'b0001; // 按下1键 #10 key = 4'b0000; #100; // 继续模拟其他按键... #1000 $finish; end always @(uut.current_state) $display("State changed to %s at %t", uut.current_state.name(), $time); endmodule板上调试技巧:
- 使用Vivado硬件管理器实时监控信号
- 添加调试核(ILA)捕获关键信号
- 利用LED显示内部状态(如状态编码)
- 分模块验证,先确保外设驱动正常
调试建议:当状态机行为异常时,首先检查时钟和复位信号是否稳定,然后确认状态转移条件是否被正确触发,最后验证输出逻辑是否符合预期。
