Vivado 按键消抖与复位模块代码修复详解:KEY_TOP、key、rst_gen 完整实战

本文基于实际 Vivado 工程代码,讲解 KEY_TOP、按键消抖模块 key、复位产生模块 rst_gen 和 clk_wiz_0 的配合方式,并修复原代码中的语法错误与逻辑问题。


摘要

在一个典型的 FPGA 按键控制 LED 工程中,通常需要完成以下几件事:

  1. 将外部差分时钟输入到 clk_wiz_0;
  2. 由 clk_wiz_0 产生系统工作时钟;
  3. 使用 rst_gen 产生上电复位;
  4. 使用 key 模块完成按键同步和消抖;
  5. 将按键状态输出到 LED。

本文重点分析以下代码:

  • KEY_TOP
  • key
  • rst_gen
  • clk_wiz_0 例化模板

并给出可以综合的完整修复版本。


关键词

Vivado、Verilog、KEY_TOP、clk_wiz_0、rst_gen、key、按键消抖、FPGA、同步、复位


一、工程整体结构

顶层模块 KEY_TOP 的连接关系如下:

复制代码
i_clk_p/i_clk_n
      |
      v
  clk_wiz_0
      |
      v
    w_clk
      |
      +------------------+
      |                  |
      v                  v
   rst_gen             key
      |                  |
      v                  v
    w_rst              o_led
      |
      v
    key.i_rst

功能划分:

模块 功能
clk_wiz_0 将 100 MHz 差分输入时钟转换为 10 MHz 系统时钟
rst_gen 上电后产生持续若干周期的复位信号
key 对按键进行同步、消抖和状态翻转
KEY_TOP 连接时钟、复位、按键和 LED

二、原始代码的主要问题

1. localparam 缺少分号

原代码:

复制代码
localparam P_FILTER_TARGET=P_FLITERT_TIM/(1000000000/P_CLK_FRQ)

缺少结尾的分号:

复制代码
;

应改为:

复制代码
localparam integer P_FILTER_TARGET =
    P_FILTER_TIME_NS / (1_000_000_000 / P_CLK_FREQ);

同时 P_FLITERT_TIM 属于拼写错误,建议改成更清晰的:

复制代码
P_FILTER_TIME_NS

2. <== 是非法语法

原代码:

复制代码
ri_key[0]<==i_key;

Verilog 中没有 <== 运算符,应该写成:

复制代码
ri_key[0] <= i_key;

3. else 缺少 begin/end

原代码:

复制代码
if(i_rst)
    ri_key<=3'b111;
else

    ri_key[0]<==i_key;
    ri_key[1]<=ri_key[0];
    ri_key[2]<=ri_key[1];

即使把 <== 改成 <=,也只有第一条赋值属于 else:

复制代码
else
    ri_key[0] <= i_key;
    ri_key[1] <= ri_key[0];   // 会变成无条件执行
    ri_key[2] <= ri_key[1];

正确写法:

复制代码
if (i_rst) begin
    ri_key <= 3'b111;
end else begin
    ri_key[0] <= i_key;
    ri_key[1] <= ri_key[0];
    ri_key[2] <= ri_key[1];
end

4. 隐式端口连接不推荐

原代码:

复制代码
key#(
    ...
)key_u0(
    .i_clk,
    .i_rst,
    .i_key,
    .o_key_status
);

.i_clk 这种写法属于 SystemVerilog 风格。

如果 Vivado 将文件当作 Verilog 处理,可能报错。

建议明确写端口连接:

复制代码
key #(
    ...
) u_key (
    .i_clk       (w_clk),
    .i_rst       (w_rst),
    .i_key       (i_key),
    .o_key_status(o_led)
);

5. 消抖计数器逻辑不完整

原代码:

复制代码
else if(ri_key[2]==1 && ri_key[1]==0 || r_cnt>0)
    r_cnt <= r_cnt + 1'b1;
else
    r_cnt <= r_cnt;

问题在于:

  • r_cnt > 0 后计数器会一直递增;
  • 按键释放后计数器不会清零;
  • 完成一次翻转后不会停止;
  • 多次按键时滤波窗口不稳定;
  • 计数器绕回后可能产生误触发。

正确思路是:

复制代码
按键同步
  -> 等待连续稳定低电平
  -> 计数达到滤波时间
  -> 翻转一次状态
  -> 等待按键释放
  -> 准备下一次按键

三、修复后的完整代码

以下代码已经修正语法、端口连接和消抖逻辑。

复制代码
`timescale 1ns / 1ps

module KEY_TOP (
    input  wire i_clk_p,
    input  wire i_clk_n,
    input  wire i_key,
    output wire o_led
);

    wire w_clk;
    wire w_rst;

    /*
     * Clock Wizard:
     * 输入差分时钟 100 MHz
     * 输出时钟 clk_out1 = 10 MHz
     */
    clk_wiz_0 u_clk_wiz (
        .clk_out1  (w_clk),
        .reset     (1'b0),
        .locked    (),
        .clk_in1_p (i_clk_p),
        .clk_in1_n (i_clk_n)
    );

    /*
     * 上电复位:
     * w_rst 保持 100 个 w_clk 周期的高电平,然后释放。
     */
    rst_gen #(
        .P_CLK_CYCLE(100)
    ) u_rst_gen (
        .i_clk(w_clk),
        .o_rst(w_rst)
    );

    /*
     * 按键消抖:
     * 10 MHz 时钟,滤波时间 10 ms。
     */
    key #(
        .P_CLK_FREQ       (10_000_000),
        .P_FILTER_TIME_NS (10_000_000)
    ) u_key (
        .i_clk       (w_clk),
        .i_rst       (w_rst),
        .i_key       (i_key),
        .o_key_status(o_led)
    );

endmodule


module key #(
    parameter integer P_CLK_FREQ       = 10_000_000,
    parameter integer P_FILTER_TIME_NS = 10_000_000
)(
    input  wire i_clk,
    input  wire i_rst,
    input  wire i_key,
    output wire o_key_status
);

    /*
     * P_FILTER_TIME_NS 单位是 ns,
     * P_CLK_FREQ 单位是 Hz。
     *
     * 10 MHz 时钟周期 = 100 ns
     * 10 ms 滤波时间 = 10_000_000 ns
     * 需要计数 = 10_000_000 / 100 = 100_000
     */
    localparam integer P_FILTER_TARGET =
        P_FILTER_TIME_NS / (1_000_000_000 / P_CLK_FREQ);

    reg [2:0]  ri_key;
    reg        ro_key_status;
    reg [31:0] r_cnt;
    reg        r_done;

    wire [2:0] w_key_next;
    wire       w_filter_target;

    assign w_key_next     = {ri_key[1:0], i_key};
    assign w_filter_target = (r_cnt == P_FILTER_TARGET - 1);
    assign o_key_status    = ro_key_status;

    always @(posedge i_clk or posedge i_rst) begin
        if (i_rst) begin
            ri_key        <= 3'b111;
            ro_key_status <= 1'b0;
            r_cnt         <= 32'd0;
            r_done        <= 1'b0;
        end
        else begin
            ri_key <= w_key_next;

            /*
             * 连续三个采样值都为 0,
             * 认为按键已经稳定按下。
             */
            if (w_key_next != 3'b000) begin
                r_cnt  <= 32'd0;
                r_done <= 1'b0;
            end
            else if (!r_done) begin
                if (w_filter_target) begin
                    ro_key_status <= ~ro_key_status;
                    r_done        <= 1'b1;
                end
                else begin
                    r_cnt <= r_cnt + 32'd1;
                end
            end
            else begin
                r_cnt  <= r_cnt;
                r_done <= r_done;
            end
        end
    end

endmodule


module rst_gen #(
    parameter integer P_CLK_CYCLE = 100
)(
    input  wire i_clk,
    output reg  o_rst
);

    reg [31:0] r_cnt;

    initial begin
        r_cnt = 32'd0;
        o_rst = 1'b1;
    end

    always @(posedge i_clk) begin
        if (r_cnt < (P_CLK_CYCLE - 1)) begin
            r_cnt <= r_cnt + 32'd1;
            o_rst <= 1'b1;
        end
        else begin
            o_rst <= 1'b0;
        end
    end

endmodule

四、关键代码解析

1. 时钟频率与滤波时间

复制代码
key #(
    .P_CLK_FREQ       (10_000_000),
    .P_FILTER_TIME_NS (10_000_000)
) u_key (...);

含义:

复制代码
P_CLK_FREQ       = 10 MHz
P_FILTER_TIME_NS = 10 ms

计算:

复制代码
时钟周期 = 1 / 10 MHz = 100 ns
滤波计数 = 10 ms / 100 ns = 100000

所以:

复制代码
P_FILTER_TARGET = 100000

2. 三级同步

复制代码
assign w_key_next = {ri_key[1:0], i_key};

每次时钟上升沿:

复制代码
ri_key <= w_key_next;

这样按键信号被同步到 w_clk 时钟域,并形成三级采样。


3. 消抖计数

复制代码
if (w_key_next != 3'b000) begin
    r_cnt  <= 32'd0;
    r_done <= 1'b0;
end

只要按键不是连续稳定低电平,计数器就清零。

当连续稳定低电平达到:

复制代码
w_filter_target = (r_cnt == P_FILTER_TARGET - 1)

则:

复制代码
ro_key_status <= ~ro_key_status;
r_done <= 1'b1;

翻转一次状态,并标记 r_done。

r_done 的作用是:

复制代码
防止按键一直按住时反复翻转

只有按键释放后:

复制代码
w_key_next != 3'b000

才会清除 r_done,允许下一次按键。


五、Vivado 工程需要做什么

1. 添加源文件

在 Vivado 中添加:

复制代码
KEY_TOP 和 key 所在文件
rst_gen.v

操作:

复制代码
Add Sources
  -> Add or create design sources
  -> Add Files

2. 添加 IP 核

复制代码
Add Sources
  -> Add Existing IP
  -> 选择:
     key.srcs/sources_1/ip/clk_wiz_0/clk_wiz_0.xci

然后在:

复制代码
Sources
  -> IP Sources
  -> clk_wiz_0

右键:

复制代码
Generate Output Products

六、约束文件 XDC

还需要在 XDC 文件中约束:

  • i_clk_p
  • i_clk_n
  • i_key
  • o_led

例如:

复制代码
# 差分输入时钟
set_property PACKAGE_PIN <你的时钟P引脚> [get_ports i_clk_p]
set_property PACKAGE_PIN <你的时钟N引脚> [get_ports i_clk_n]
set_property IOSTANDARD LVDS [get_ports {i_clk_p i_clk_n}]

# 按键
set_property PACKAGE_PIN <你的按键引脚> [get_ports i_key]
set_property IOSTANDARD LVCMOS33 [get_ports i_key]

# LED
set_property PACKAGE_PIN <你的LED引脚> [get_ports o_led]
set_property IOSTANDARD LVCMOS33 [get_ports o_led]

具体引脚编号和电平标准必须根据你的开发板原理图填写。


七、验证方法

1. 确认 Clock Wizard 配置

打开 clk_wiz_0:

复制代码
Sources
  -> IP Sources
  -> clk_wiz_0
  -> Re-customize IP

确认:

复制代码
Input Clock  = 100 MHz
Output Clock = 10 MHz

如果输入频率不是 100 MHz,key 模块的滤波时间会不准确。

2. 综合和实现

依次执行:

复制代码
Generate Output Products
Run Synthesis
Run Implementation
Generate Bitstream

3. 上板测试

预期现象:

复制代码
上电后:
  o_led = 0

按下按键并保持超过 10 ms:
  o_led 翻转一次

再次按下:
  o_led 再次翻转

八、总结

原始代码主要问题包括:

  1. localparam 缺少分号;
  2. <== 非法语法;
  3. else 缺少 begin/end;
  4. 隐式端口连接不规范;
  5. 参数名拼写错误;
  6. 消抖计数器不会释放后清零;
  7. 缺少一次有效翻转后的锁定标志。

修复后:

  • 语法可以正常综合;
  • 按键经过三级同步;
  • 滤波时间可配置;
  • 按键稳定后只翻转一次;
  • 按键释放后可以重新触发;
  • rst_gen 和 clk_wiz_0 配合清晰。

这套代码可以作为 Vivado 按键消抖、复位产生和时钟管理的基础模板继续扩展。

相关推荐
炎龙铠甲会合体3 小时前
AXI2ICACHE— AXI4-Full 四路组相联指令 Cache 设计与 UVM 验证
fpga开发·面试·芯片验证
bingcheby3 小时前
FMQL15/20/45 (复旦微青龙系列 PSOC)新手使用说明
fpga开发
漫漫长远4 小时前
低功耗UPF介绍(一)
笔记·嵌入式硬件·学习·fpga开发
自小吃多15 小时前
【Verilog/FPGA】存储器(数组)
嵌入式硬件·fpga开发
szxinmai主板定制专家15 小时前
RK3588+FPGA异构架构|高速数据采集+边缘AI落地应用全解析
人工智能·嵌入式硬件·fpga开发·架构·zynq
明德扬21 小时前
FPGA MIPI 多路视频聚合方案
fpga开发·mipi视频拼接·mipi多路拼接·mipi协议·mipi视频拼接方案·fpga mipi方案·mipi csi
XMAIPC_Robot21 小时前
为什么大型储能需要边缘 AI 协调控制器?RK3588+FPGA 高速采集方案
人工智能·嵌入式硬件·fpga开发·arm+fpga·rk3588+fpga·协调控制器
szxinmai主板定制专家21 小时前
RK3576+CODESYS+RK182X+FPGA异构架构:半导体精密设备一体化控制器方案
人工智能·fpga开发·架构·rk3576+codesys
szxinmai主板定制专家21 小时前
半导体设备控制器:RK3576+CODESYS+RK182X异构架构,实现晶圆视觉检测与运动联动闭环
人工智能·fpga开发·架构·视觉检测·边缘计算·codesys·rk3576