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 end

2. 密码锁状态机详细设计与实现

2.1 六状态定义与功能说明

本设计采用六个状态构建密码锁的核心控制逻辑,每个状态对应密码锁的一个特定工作模式:

  1. s_lock(锁定状态)

    • 系统初始状态
    • 数码管显示"LC"(Locked)
    • 关锁指示灯亮(F2灯亮,F1灯灭)
    • 等待用户开始输入密码
  2. s_keyin(密码输入状态)

    • 用户正在输入密码
    • 启动10秒倒计时并在数码管显示
    • 记录用户按键序列
    • 密码输入完成或超时后退出
  3. s_compare(密码比较状态)

    • 比较用户输入与预设密码
    • 瞬时状态,立即跳转到s_pass或s_error
    • 不直接控制外设
  4. s_pass(密码正确状态)

    • 密码验证成功
    • 数码管显示"OP"(Open)
    • 开锁指示灯亮(F1灯亮,F2灯灭)
    • 允许修改密码操作
  5. s_error(密码错误状态)

    • 密码验证失败
    • 保持锁定状态
    • 可添加错误计数限制功能
  6. 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 endmodule

3. 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_openLED_close数码管显示其他输出
s_lock01LC-
s_keyin01倒计时-
s_compare----
s_pass10OP解锁信号
s_error01LC错误提示
s_modify10-密码修改

注意:数码管显示需要特别处理,因为要同时显示状态和倒计时。建议采用多路复用技术,在s_keyin状态显示倒计时,其他状态显示相应状态代码。

4. Vivado 2023.1中的实现与调试技巧

4.1 工程创建与状态机编码规范

在Vivado 2023.1中创建状态机项目时,建议遵循以下步骤:

  1. 新建RTL项目,选择EGO1开发板对应的器件型号(XC7A35T-1CSG324C)
  2. 添加状态机模块文件,采用SystemVerilog语法增强可读性
  3. 使用Vivado的状态机识别功能(在综合设置中启用FSM提取)
  4. 为状态变量添加(* 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 endmodule

4.2 仿真验证与板上调试

状态机的验证分为仿真验证和硬件验证两个阶段,每个阶段都有其特定的调试技巧。

仿真验证要点

  1. 创建测试平台模拟所有状态转移
  2. 特别关注边界条件(如倒计时结束瞬间)
  3. 验证错误处理路径(连续错误输入等)
  4. 检查状态输出是否符合预期

常用仿真技巧

  • 使用$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

板上调试技巧

  1. 使用Vivado硬件管理器实时监控信号
  2. 添加调试核(ILA)捕获关键信号
  3. 利用LED显示内部状态(如状态编码)
  4. 分模块验证,先确保外设驱动正常

调试建议:当状态机行为异常时,首先检查时钟和复位信号是否稳定,然后确认状态转移条件是否被正确触发,最后验证输出逻辑是否符合预期。