【FPGA入门实战:Verilog实现边沿检测电路(附Testbench仿真)】

FPGA入门实战:Verilog实现边沿检测电路(附Testbench仿真)

一、 前言

在FPGA逻辑设计中,边沿检测是一个非常基础且极其重要的功能。无论是在按键消抖、串口通信起始位判断,还是在高速数据的时钟同步中,我们都需要精准地捕捉信号的跳变瞬间(上升沿、下降沿或双边沿)。

本文将通过一个简单的Verilog实例,详细讲解单时钟沿同步的边沿检测原理,并提供完整的Testbench代码进行仿真验证。

二、 设计思路

边沿检测的核心思想非常简单:打拍(Delay)。

由于FPGA是同步时序电路,我们通常在时钟的上升沿对信号进行采样。如果我们把当前时刻的信号 sig与上一时刻的信号 sig_dly进行比较,就能判断出信号是否发生了变化。

假设时钟为 clk:

1.打一拍:

在 clk的驱动下,将输入信号 sig寄存一拍,得到 sig_dly。

2.逻辑判断:

上升沿:上一拍是0,这一拍是1。即 rise_edge = ~sig_dly & sig。

下降沿:上一拍是1,这一拍是0。即 fall_edge = sig_dly & ~sig。

双边沿:只要两拍数据不同,就说明发生了跳变。即 dual_edge = sig_dly ^ sig。

三、 RTL代码实现

下面是边沿检测模块的Verilog代码。

  1. 边沿检测模块 (edge_test1.v)
c 复制代码
module edge_test1(
    input           clk,        // 时钟信号
    input           rst_n,      // 复位信号 (低有效)
    input           sig,        // 待检测信号
    
    output  wire    rise_edge,  // 上升沿检测信号 (脉冲)
    output  wire    fall_edge,  // 下降沿检测信号 (脉冲)
    output  wire    dual_edge   // 双边沿检测信号 (脉冲)
);

    reg    sig_dly;             // 将待检测信号延迟一拍

    // 时序逻辑:打拍
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            sig_dly <= 1'b0;    // 复位时清零
        end
        else begin
            sig_dly <= sig;     // 将当前信号存入寄存器
        end
    end
    
    // 组合逻辑:边沿判断
    assign rise_edge = ~sig_dly & sig;  // 上升沿:0 -> 1
    assign fall_edge = sig_dly & ~sig;  // 下降沿:1 -> 0
    assign dual_edge = sig_dly ^ sig;   // 双边沿:0<->1 变化

endmodule

代码解析:

sig_dly:这是关键寄存器,它保存了上一时钟周期 sig的值。

上升沿:如果 sig_dly是0,sig是1,说明信号从低变高,rise_edge输出一个时钟周期的高电平。

下降沿:如果 sig_dly是1,sig是0,说明信号从高变低,fall_edge输出一个时钟周期的高电平。

双边沿:异或运算(XOR)的特性是"相同为0,不同为1"。只要 sig和 sig_dly不一样,就代表发生了跳变。

RTL视图如下:

四、 Testbench 仿真

为了验证设计的正确性,我们需要编写Testbench。

  1. 仿真激励文件 (tb_edge_test1.v)
c 复制代码
`timescale 1ns/1ps  // 时间单位/精度

module tb_edge_test1();

    reg          clk;               
    reg          rst_n;
    reg          sig;
    
    wire         rise_edge;
    wire         fall_edge; 
    wire         dual_edge;
    
    parameter T = 20;  // 时钟周期 20ns (50MHz)
    
    // 生成时钟
    always #(T/2) clk = ~clk;
    
    // 激励信号
    initial begin
        clk   = 1'b0;
        rst_n = 1'b0;
        sig   = 1'b0;
        
        #15
        rst_n = 1'b1;  // 释放复位
        
        #10
        sig = 1'b0;    // 保持低电平
        
        #10
        sig = 1'b1;    // 产生一个上升沿
        
        #30
        sig = 1'b0;    // 产生一个下降沿
        
        #10
        sig = 1'b1;    // 上升沿
        
        #20
        sig = 1'b0;    // 下降沿
        
        #100 $stop;    // 停止仿真
    end
    
    // 实例化待测模块 (DUT)
    edge_test1 u_edge_test1(
        .clk       (clk),
        .rst_n     (rst_n),
        .sig       (sig),
        .rise_edge (rise_edge),
        .fall_edge (fall_edge),
        .dual_edge (dual_edge)
    );

endmodule

五、 仿真波形分析

结果验证:

1.当 sig从 0 跳变到 1​ 时,rise_edge和 dual_edge同时拉高了一个时钟周期。

2.当 sig从 1 跳变到 0​ 时,fall_edge和 dual_edge同时拉高了一个时钟周期。

六、注意事项

1.信号同步问题:

本例中的 sig假设是同步信号(由同一时钟域产生)。如果 sig是外部输入(如按键、异步数据),必须先进行两级触发器同步(打两拍),否则可能会产生亚稳态,导致系统崩溃。

2.脉冲宽度:

本设计输出的脉冲宽度等于一个时钟周期。如果后续逻辑需要更宽的脉冲,或者需要跨时钟域处理,可能需要额外的计数器或握手机制。

3.资源消耗:

这种写法综合出来通常只需要几个LUT(查找表),资源消耗极低,适合大规模使用。

七、补充知识:亚稳态

在FPGA和数字电路设计中,"亚稳态"(Metastability)​ 是一个绕不开的核心痛点。为了让你彻底弄懂它,我们抛开枯燥的学术定义,从它的本质来掰扯清楚。

你可以把亚稳态简单理解为:信号在"0"和"1"之间"卡住"了。

为了让你知其然也知其所以然,我们从以下几个维度来彻底解剖它:

  1. 直观比喻:站在电梯里的尴尬时刻
    想象一个数字系统里的高电平(1)是站在电梯地面,低电平(0)是蹲在电梯底部。
    正常情况下,信号要么稳稳站在地面(1),要么稳稳蹲在底部(0)。
    但是,如果外部信号(比如按键输入)恰好在电梯关门的一瞬间(时钟上升沿)冲进电梯,会发生什么?
    它既没完全蹲下去,也没完全站起来,而是卡在了半空中(中间电压值,比如 1.2V)。这就是亚稳态。
    在这个状态下,系统根本不知道它到底是 0 还是 1,整个系统就会陷入逻辑混乱。
  2. 专业视角:违背了"建立时间"与"保持时间"
    在数字电路中,所有的触发器(Flip-Flop,也就是代码里的 reg)都像是一个严格的安检口。
    为了保证数据能被稳定采样,输入信号必须满足两个硬性规定:
    建立时间 (Setup Time):在时钟上升沿到来之前,数据必须提前稳定多久。
    保持时间 (Hold Time):在时钟上升沿到来之后,数据必须继续保持稳定多久。
    亚稳态产生的根本原因就是:输入信号的变化(跳变)刚好发生在触发器的建立时间和保持时间内。
    此时,触发器"懵"了,它无法判断这个信号究竟是上一个值还是下一个值,于是输出端就会产生一个介于 0 和 1 之间的非法电压,也就是进入了亚稳态。
  3. 亚稳态的危害:一颗老鼠屎坏了一锅粥
    你可能会想:"卡住就卡住呗,等下个时钟周期不就好了?"
    没那么简单,亚稳态的危害在于它的传染性:
    逻辑误判:当前级电路输出一个不确定的中间电压,后级电路可能把它当成 0,也可能当成 1。这会导致整个系统的逻辑走向完全错误。
    毛刺传播:亚稳态不仅是一个不确定的电平,它还可能在一定时间内发生振荡(高低跳动)。这个"抖动"一旦传递给后面的其他寄存器,就会导致错误的信号在整个芯片里像瘟疫一样扩散,最终引发系统死机或跑飞。
  4. 怎么消灭亚稳态?------"打两拍"绝招
    既然亚稳态这么可怕,那怎么防备呢?在FPGA中,对付异步信号(比如你从外部引脚直接输入的 sig),业界公认的黄金法则就是:同步器(Synchronizer),俗称"打两拍"。
    具体怎么做?
    不要用外部信号直接去当条件判断,而是让它连续经过两个级联的寄存器,这两个寄存器都用系统时钟 clk驱动:
c 复制代码
reg sig_dly1, sig_dly2;

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        sig_dly1 <= 1'b0;
        sig_dly2 <= 1'b0;
    end else begin
        sig_dly1 <= sig;      // 第一拍:大概率解除亚稳态
        sig_dly2 <= sig_dly1; // 第二拍:100%稳定输出
    end
end
// 后续逻辑全部使用 sig_dly2 进行判断

原理是什么?

第一拍 (sig_dly1):把疯狂跳动的异步信号强行抓进系统时钟域。这一步有极小概率抓到亚稳态,但没关系。

第二拍 (sig_dly2):因为一个时钟周期的时间远大于触发器退出亚稳态的时间,所以在第二个时钟上升沿到来时,sig_dly1早就已经稳定下来了。这样,sig_dly2输出的信号就绝对是干净、稳定、安全的!

(注:这也是为什么我在上文代码里强调,如果 sig是外部按键或异步数据,必须先打两拍同步后再做边沿检测的原因。)

希望这篇博文对你有帮助!如果有任何问题,欢迎在评论区留言讨论。😊

相关推荐
風清掦7 小时前
【STM32学习笔记-14】WDG看门狗 - 14.2 WWDG窗口看门狗
笔记·stm32·单片机·嵌入式硬件·学习·fpga开发
尤老师FPGA10 小时前
HDMI数据的接收发送实验(十二)
fpga开发
坏孩子的诺亚方舟14 小时前
FPGA神经网络数学基础0
人工智能·神经网络·线性代数·fpga开发
熠速14 小时前
PolarBox高性能实时仿真系统
arm开发·fpga开发·嵌入式实时数据库·硬件在环半实物仿真
南檐巷上学15 小时前
基于Zynq-7020的带有正弦波发生器的8051软核设计
单片机·嵌入式硬件·fpga开发·fpga
思尔芯S2C16 小时前
FPGA原型验证中的内存模型应用:基于DDR5的Linux系统启动与测试
fpga开发·内存模型·ddr4·ddr5·memory model·hbm3·prototyping
hai3152475431 天前
RISC-V CVA6 AXI适配器+DMA桥蜂鸟E203处理器的总线接口单元(BIU)仲裁器
驱动开发·fpga开发·硬件架构·硬件工程·精益工程
高速上的乌龟1 天前
Lattice LFCPNX-100 HSB+Fpga开发详解:2.3 Hololink 顶层模块深度全解析
linux·fpga开发
ALINX技术博客1 天前
【FPGA 开发教程】基于 ALINX FPGA 开发板实现 USB3.2 高速通信(Z7-P+FL2010)
fpga开发·fpga·fmc子卡·usb3.2通信
Ricky05531 天前
搭载实时 FPGA 处理系统的航天器上用于海上监视的超分辨率YOLO目标检测技术(意大利2026年研究)
yolo·目标检测·fpga开发