Skip to content

7 输入处理 (Input Processing)

在数字电路中,从外部世界进入同步电路的输入信号通常是异步的,即它们不与系统时钟同步。这种异步输入可能导致以下问题:

  1. 亚稳态(Metastability):输入信号在时钟上升沿附近变化,可能违反触发器的建立时间和保持时间,导致不确定状态。
  2. 多位寄存冲突:信号在不同延迟下注册到不同的时钟周期,可能导致电路行为错误。
  3. 噪声与抖动:例如机械开关产生的抖动(bouncing)或信号尖峰。

本章主要描述如何通过数字电路设计来处理异步输入信号,解决上述问题。

7.1 异步输入 (Asynchronous Input)

异步输入的定义与问题

  • 异步信号是指不与系统时钟同步的输入信号。
  • 当异步信号在系统时钟的上升沿附近发生变化时,可能导致触发器的建立时间(setup time)或保持时间(hold time)被破坏。
  • 这种情况会导致触发器进入亚稳态(Metastable State),即输出值介于 0 和 1 之间,或者长时间无法稳定。
  • 亚稳态最终会稳定,但它可能会导致输出在短时间内不可预测,影响电路功能。

亚稳态的解决方案

尽管我们无法完全避免亚稳态,但我们可以限制其影响。常见的解决方法是使用两个级联触发器(two-stage flip-flops)作为输入同步器(Input Synchronizer):

  1. 第一个触发器:接收异步输入信号。即使进入亚稳态,经过一定时间后会稳定。
  2. 第二个触发器:在稳定信号的基础上注册一次,确保信号与系统时钟同步。

这种方法能够显著降低亚稳态的影响,确保异步信号被安全地引入同步电路。

输入同步器的结构

图 7.1 显示了一个典型的 输入同步器

  • 输入信号 (btn):来自外部世界的异步信号。
  • 两个触发器级联:通过两个连续寄存器将异步信号同步到系统时钟域。
  • 输出信号 (btnSync):同步后的信号,可以安全地在同步电路中使用。

Chisel 实现:输入同步器

Chisel 提供了非常简洁的语法来实现输入同步器,使用两个级联的 RegNext

代码示例:

scala
val btnSync = RegNext(RegNext(btn))

代码解析:

  1. RegNext(btn):将异步输入信号 btn 注册到第一个寄存器。
  2. 第二个 RegNext:将第一个寄存器的输出注册到第二个寄存器,进一步稳定信号。
  3. btnSync:最终同步后的信号,确保它与系统时钟同步,可以安全地用于同步电路。

同步复位信号

除了常规异步输入信号,复位信号(reset)也需要同步到系统时钟:

  • 复位信号通常来自外部按键,可能是异步的。
  • 异步复位的释放(deassertion)必须与系统时钟同步,以避免复位后电路处于不确定状态。

实现方法:与输入同步器相同,将复位信号通过两个寄存器级联同步。

亚稳态的概率与影响

虽然输入同步器可以大幅降低亚稳态的影响,但无法完全消除:

  • 亚稳态的持续时间与系统时钟周期成反比。时钟频率越高,亚稳态的概率越高。
  • 实际设计中,两个级联触发器已经足够满足绝大多数应用场景。

总结

  1. 异步输入问题:异步输入信号可能违反触发器的建立时间和保持时间,导致亚稳态,影响系统稳定性。
  2. 输入同步器:使用两个级联触发器同步异步输入,减少亚稳态的影响,确保信号与时钟同步。
  3. Chisel 实现:使用 RegNext 级联两级寄存器实现输入同步器。
  4. 同步复位信号:外部复位信号也需要同步,以确保安全的电路启动。

通过输入同步器,可以安全地将异步输入信号引入同步电路,提升系统的稳定性和可靠性。

7.2 去抖动 (Debouncing)

去抖动主要用于处理机械开关或按钮的输入信号,这些信号在状态切换时可能会出现短时间的抖动。

  • 当开关从 ,或从 时,由于机械结构的特性,信号可能在 0 和 1 之间反复跳变,形成 抖动
  • 如果这些抖动未经处理,电路可能检测到多个不必要的信号过渡,导致误触发。

为了解决这个问题,可以通过时间采样的方法将抖动信号进行滤波处理,确保只检测到单一的有效过渡。

1. 去抖动的基本原理

假设最大抖动时间为 tbouncet_{\text{bounce}},我们可以选择一个大于该时间的采样周期 TT,使得采样点之间的间隔满足:

T>tbounceT > t_{\text{bounce}}

这样,在信号从 0 变为 1 的过程中,最多只有一个采样点会落在抖动区域内:

  • 抖动前的采样点稳定地读取 0
  • 抖动区域的采样点可能读到 01,但不会影响整体结果。
  • 抖动后的采样点稳定地读取 1

最终,去抖动后的信号会稳定地检测到 0 到 11 到 0单次过渡

2. 图示说明

图 7.2 展示了信号去抖动的示意图:

  • bouncing in:表示输入信号的抖动情况,信号在抖动时间内快速跳变。

  • debounced A

    debounced B :去抖动后的输出信号,两个版本的信号都有单一的过渡点。

    • AB 的区别在于去抖动的延迟不同,但都保证只有一次过渡。

3. Chisel 实现去抖动电路

设计思路

  1. 计数器:实现一个定时器,按照设定的采样周期对输入信号进行采样。
  2. 采样逻辑:当计数器到达最大值时,将输入信号的当前值存储到一个寄存器中,作为去抖动后的信号输出。
  3. 输入同步:去抖动电路通常放在输入同步器之后,确保信号已与时钟同步。

Chisel 代码实现

scala
val fac = 100000000 / 100  // 100 MHz 时钟,采样频率 100 Hz  

val btnDebReg = Reg(Bool())  // 存储去抖动后的信号  
val cntReg = RegInit(0.U(32.W))  // 定时器计数器  

val tick = cntReg === (fac - 1).U  // tick 信号:计数器到达最大值时触发  

cntReg := cntReg + 1.U  // 计数器自增  
when(tick) {  
  cntReg := 0.U          // 计数器重置  
  btnDebReg := btnSync   // 采样输入信号(已同步)  
}

代码解析

  1. 采样频率设置
    • fac 设置采样频率为 100 Hz,假设系统时钟频率为 100 MHz。
    • 通过计数器 cntReg 计数,当达到 fac-1 时生成 tick 信号。
  2. 计数器逻辑
    • cntReg 每个时钟周期加 1。
    • tick 信号触发时,计数器重置为 0。
  3. 去抖动信号存储
    • tick 信号触发时,将同步后的输入信号 btnSync 存储到 btnDebReg 中。
    • btnDebReg 是去抖动后的输出信号。

4. 采样频率的选择

去抖动电路的关键在于正确选择采样频率:

  • 最大抖动时间 tbouncet_{\text{bounce}}:通常在 5~10 ms 之间。
  • 采样周期 TT:需要满足 T>tbounceT > t_{\text{bounce}}。例如,100 Hz 的采样频率对应的周期为 10 ms。

如果采样周期太短,可能会捕获到抖动信号,导致去抖动失败; 如果采样周期太长,会引入较大的延迟,影响系统响应速度。

5. 去抖动电路的位置

去抖动电路通常位于输入同步器之后:

  1. 输入同步器:将异步输入信号与时钟同步,确保信号稳定。
  2. 去抖动电路:对同步后的信号进行时间采样,滤除抖动。

6. 总结

  1. 抖动问题:机械开关或按钮在状态切换时会产生抖动,导致不必要的过渡信号。

  2. 时间采样方法:通过设置一个大于最大抖动时间的采样周期,对输入信号进行定时采样,滤除抖动。

  3. Chisel 实现 :

    • 使用计数器生成 tick 信号,实现定时采样。
    • 采样周期由系统时钟和计数器的最大值决定。
    • 将采样信号存储到寄存器中,作为去抖动后的输出。
  4. 电路设计原则 :

    • 先同步,后去抖动。
    • 合理选择采样周期,确保有效去抖动,同时保证系统响应速度。

通过去抖动电路,可以有效地处理抖动信号,确保输入信号的稳定性和可靠性。

7.3 输入信号的滤波 (Filtering of the Input Signal)

在实际硬件设计中,输入信号可能会受到噪声干扰,导致出现短时间的尖峰脉冲。这些脉冲可能会被同步器或去抖动单元不小心采样到,导致电路误触发。为了解决这一问题,可以使用多数投票电路(Majority Voting Circuit)对输入信号进行滤波。

1. 多数投票电路的原理

多数投票电路是一种简单而有效的信号滤波方法,它通过对连续多次采样的信号执行多数决策来确定最终输出信号:

  • 在最简单的形式下,对输入信号进行 3 次采样
  • 使用逻辑运算确定三个采样结果中多数的值(2 个或以上)。
  • 如果信号中存在短暂的脉冲(尖峰),它不会影响最终的输出,因为该信号在 3 个采样周期中不会占据多数。

多数投票电路与中值滤波类似,能够过滤掉时间较短的噪声信号,确保输出信号的稳定性。

2. 多数投票电路的结构

图 7.3 展示了一个 3 位移位寄存器与多数投票电路的结构:

  1. 3 位移位寄存器:通过 tick 采样周期对输入信号 din 进行连续 3 次采样。

  2. 多数投票逻辑 :

    • 通过逻辑运算组合寄存器的 3 位输出 aa、bb、cc,得到最终的输出信号 dout
    • 逻辑表达式为:

dout=(a & b) ∣ (a & c) ∣ (b & c)\text{dout} = (a , & , b) , | , (a , & , c) , | , (b , & , c)

该表达式确保三个输入中任意两个相等的值将决定输出结果。

3. Chisel 实现多数投票滤波器

代码实现:

scala
val shiftReg = RegInit(0.U(3.W)) // 3 位移位寄存器初始化为 0  

when (tick) {
  // 左移寄存器并输入去抖动后的信号
  shiftReg := shiftReg(1, 0) ## btnDebReg
}

// 多数投票逻辑
val btnClean = (shiftReg(2) & shiftReg(1)) | 
               (shiftReg(2) & shiftReg(0)) | 
               (shiftReg(1) & shiftReg(0))

代码解析:

  1. 移位寄存器
    • shiftReg 是一个 3 位寄存器,每次 tick 信号触发时,输入信号 btnDebReg 通过移位寄存器存储。
    • shiftReg(1, 0) ## btnDebReg 实现了左移操作,并将新输入值拼接到最低位。
  2. 多数投票逻辑
    • 使用逻辑运算表达式计算三个采样结果的多数值。
    • 任何两个相等的位将决定最终的输出 btnClean

4. 使用滤波后的信号

滤波后的信号 btnClean 是经过去抖动和滤波处理后的稳定信号,可以安全地用于电路的进一步处理。 例如,我们可以检测 上升沿(rising edge)以触发某个操作(如计数器增加):

代码示例:

scala
val risingEdge = btnClean & !RegNext(btnClean) // 检测上升沿

// 使用滤波后的信号触发计数器
val reg = RegInit(0.U(8.W))
when (risingEdge) {
  reg := reg + 1.U
}

代码解析:

  1. 上升沿检测
    • RegNext(btnClean) 保存 btnClean 的前一个时钟周期值。
    • btnClean & !RegNext(btnClean) 表示当前时钟周期为高电平,而前一个周期为低电平,检测到上升沿。
  2. 触发操作
    • 当检测到 btnClean 的上升沿时,计数器 reg 加 1。

5. 多数投票电路的优势与应用

优势:

  1. 滤除短时间噪声:通过连续采样并执行多数投票,消除短时间的信号尖峰。
  2. 简单易实现:逻辑结构简单,仅需移位寄存器和少量逻辑运算。

应用场景:

  • 按钮去抖动:结合去抖动单元,进一步滤除残留的噪声。
  • 输入信号滤波:在噪声较多的环境中对传感器输入信号进行滤波。
  • 边沿检测:检测经过滤波处理后的信号的上升沿或下降沿,触发系统事件。

6. 总结

  1. 噪声问题:输入信号可能包含短时间的尖峰脉冲,影响电路功能。
  2. 多数投票滤波:通过 3 次采样和逻辑运算,消除短时间的信号噪声,确保输出信号的稳定性。
  3. Chisel 实现:使用 3 位移位寄存器和逻辑表达式实现多数投票电路。
  4. 上升沿检测:利用滤波后的信号检测边沿事件,触发系统操作。

通过多数投票电路,可以进一步增强输入信号的稳定性,与同步器和去抖动单元配合使用,有效解决输入信号噪声问题。

7.4 将输入处理封装为函数 (Combining the Input Processing with Functions)

在硬件设计中,输入处理电路(如同步器、去抖动器和滤波器)虽然简单,但常常会被重复使用。为了提高代码的可复用性和简洁性,可以将这些电路封装为Chisel函数。这样,我们可以轻松地调用这些函数创建输入处理模块,而不必每次都重新编写代码。

1. Chisel 输入处理函数

以下是常见输入处理的函数封装:

输入同步器 (sync)

  • 功能:使用两个级联触发器将异步输入信号与系统时钟同步。
  • 实现:
scala
def sync(v: Bool) = RegNext(RegNext(v))
  • 参数v 是异步输入信号。
  • 返回值:同步后的信号。

上升沿检测 (rising)

  • 功能:检测信号的上升沿,即当前周期为高电平而前一周期为低电平。
  • 实现:
scala
def rising(v: Bool) = v & !RegNext(v)
  • 参数v 是输入信号。
  • 返回值:上升沿检测信号(单周期高电平)。

生成定时信号 (tickGen)

  • 功能:生成周期性单周期高电平的 tick 信号。
  • 实现:
scala
def tickGen() = {
  val reg = RegInit(0.U(log2Up(fac).W))  // 计数器  
  val tick = reg === (fac - 1).U         // 计数达到设定值时触发 tick  
  reg := Mux(tick, 0.U, reg + 1.U)       // 计数器自增  
  tick
}
  • 参数:无(但内部会依赖周期因子 fac)。
  • 返回值:周期性 tick 信号。

输入信号滤波 (filter)

  • 功能:使用 3 位移位寄存器执行多数投票,滤除输入信号中的短时间噪声。
  • 实现:
scala
def filter(v: Bool, t: Bool) = {
  val reg = RegInit(0.U(3.W))  
  when (t) {  
    reg := reg(1, 0) ## v  // 左移并输入新值  
  }
  // 多数投票逻辑  
  (reg(2) & reg(1)) | (reg(2) & reg(0)) | (reg(1) & reg(0))
}
  • 参数 :

    • v:输入信号。
    • t:采样触发信号。
  • 返回值:经过滤波后的稳定信号。

2. 使用这些函数的完整示例

以下代码展示如何将输入处理电路组合起来,使用封装的函数实现信号同步、去抖动和滤波:

scala
val btnSync = sync(io.btnU)           // 同步异步输入信号  
val tick = tickGen()                  // 生成周期性 tick 信号  
val btnDeb = Reg(Bool())              // 去抖动后的信号寄存器  
when (tick) {                         // 使用 tick 信号采样去抖动  
  btnDeb := btnSync  
}

val btnClean = filter(btnDeb, tick)   // 对去抖动信号进行滤波  
val risingEdge = rising(btnClean)     // 检测滤波信号的上升沿  

// 使用去抖动和滤波后的上升沿信号触发计数器  
val reg = RegInit(0.U(8.W))  
when (risingEdge) {  
  reg := reg + 1.U  
}

代码解析

  1. sync:将 io.btnU 输入信号同步到系统时钟域。
  2. tickGen:生成周期性 tick 信号,作为采样信号。
  3. 去抖动处理:使用 tick 信号对同步后的信号进行采样,存储到 btnDeb 中。
  4. filter:进一步使用多数投票滤波器对去抖动信号进行滤波,确保输出稳定。
  5. rising:检测滤波后的信号的上升沿。
  6. 计数器:在检测到上升沿时,计数器 reg 加 1。

7.5 同步复位 (Synchronizing Reset)

1. 复位信号的同步问题

在数字电路设计中,复位信号用于将寄存器初始化到定义的状态。

  • 复位信号可能是异步输入,直接将其连接到寄存器的复位端可能违反时序约束,导致建立时间保持时间问题。
  • 异步释放(deassertion)可能导致电路不同部分在不同时钟周期复位,造成不一致。

2. 解决方案:复位信号同步化

解决方法是将复位信号同步到系统时钟域,与其他异步输入信号的同步方法相同:

  • 通过两个级联触发器实现同步复位信号。

3. Chisel 实现同步复位

代码示例:

scala
class SyncReset extends Module {
  val io = IO(new Bundle {
    val value = Output(UInt())
  })

  val syncReset = RegNext(RegNext(reset))  // 同步复位信号  
  val cnt = Module(new WhenCounter(5))     // 包含计数器的模块  
  cnt.reset := syncReset                  // 将同步后的复位信号连接到计数器的复位端  

  io.value := cnt.io.cnt
}

代码解析

  1. RegNext(RegNext(reset)):将异步复位信号通过两个级联寄存器同步到时钟域,生成 syncReset
  2. 复位连接:将同步后的复位信号 syncReset 连接到模块 cnt 的复位输入端 reset
  3. 顶层模块设计SyncReset 作为顶层模块,通过同步化复位信号控制内部模块的复位。

4. 总结

  1. 输入处理函数封装:将同步器、去抖动器和滤波器等小电路封装为函数,提高代码的可复用性和简洁性。
  2. 同步复位:复位信号必须同步到时钟域,以避免时序约束问题和不一致的复位状态。
  3. Chisel 实现:通过 RegNext 实现同步复位,并将其应用于模块的复位输入。

通过这些函数和复位同步处理,可以构建稳定可靠的输入信号处理和复位系统,确保整个电路在时钟域内安全运行。

7.6 练习 (Exercise)

目标

设计一个基于输入按钮的计数器系统,并在 FPGA 开发板上通过 LED 显示计数值(二进制格式)。该设计要求观察按钮输入时的抖动问题,然后通过完整的输入处理链解决该问题。

任务概述

  1. 按钮抖动观察
    • 直接将按钮输入连接到计数器的触发信号,观察按键输入的抖动现象。
    • 由于按钮的机械抖动,按一次按钮可能会导致计数器的多次递增。
  2. 解决抖动问题
    • 构建一个完整的输入信号处理链,包括以下模块:
      • 输入同步器:将异步输入按钮信号同步到系统时钟域。
      • 去抖动电路:滤除按钮输入的抖动,通过时间采样确保只检测到单次有效的按钮按下。
      • 多数投票电路:进一步抑制噪声,确保信号稳定。
      • 上升沿检测电路:检测按钮信号的上升沿,触发计数器递增操作。
  3. 系统仿真与验证
    • 使用不同的采样频率(例如 1 Hz)模拟按钮抖动情况。
    • 按键释放信号也经过多数投票滤波,只有释放时间超过 1–2 秒时才被识别为有效信号。

设计流程

1. 输入同步器

首先将异步输入按钮信号同步到系统时钟域:

scala
val btnSync = RegNext(RegNext(io.btn)) // 同步输入按钮信号

2. 去抖动电路

通过定时器对同步后的按钮信号进行去抖动处理,采样频率设定为 1 Hz:

scala
val cntReg = RegInit(0.U(32.W))  
val tick = cntReg === (100000000.U - 1.U) // 假设 100 MHz 时钟,1 Hz 采样频率  

cntReg := cntReg + 1.U  
when (tick) {  
  cntReg := 0.U  
  btnDeb := btnSync  // 每秒采样一次按钮信号  
}

3. 多数投票电路

使用 3 位移位寄存器实现多数投票滤波,确保按钮信号的稳定性:

scala
val shiftReg = RegInit(0.U(3.W))  

when (tick) {  
  shiftReg := shiftReg(1, 0) ## btnDeb // 左移并输入去抖动信号  
}

// 多数投票逻辑  
val btnClean = (shiftReg(2) & shiftReg(1)) |  
               (shiftReg(2) & shiftReg(0)) |  
               (shiftReg(1) & shiftReg(0))

4. 上升沿检测

检测去抖动并滤波后的信号的上升沿,以触发计数器:

scala
val risingEdge = btnClean & !RegNext(btnClean) // 上升沿检测

5. 计数器设计

当检测到按钮信号的上升沿时,计数器递增:

scala
val counter = RegInit(0.U(8.W))  // 8 位计数器  

when (risingEdge) {  
  counter := counter + 1.U  
}

6. LED 显示

通过 LED 显示计数器的值:

scala
io.leds := counter // 将计数器值输出到 LED

完整代码示例

scala
class ButtonCounter extends Module {
  val io = IO(new Bundle {
    val btn = Input(Bool())       // 输入按钮信号  
    val leds = Output(UInt(8.W))  // 输出 LED 信号  
  })

  // 1. 输入同步器  
  val btnSync = RegNext(RegNext(io.btn))  

  // 2. 去抖动电路  
  val cntReg = RegInit(0.U(32.W))  
  val tick = cntReg === (100000000.U - 1.U) // 1 Hz 采样  
  cntReg := cntReg + 1.U  
  val btnDeb = RegInit(false.B)  

  when (tick) {  
    cntReg := 0.U  
    btnDeb := btnSync  
  }

  // 3. 多数投票滤波  
  val shiftReg = RegInit(0.U(3.W))  
  when (tick) {  
    shiftReg := shiftReg(1, 0) ## btnDeb  
  }
  val btnClean = (shiftReg(2) & shiftReg(1)) |  
                 (shiftReg(2) & shiftReg(0)) |  
                 (shiftReg(1) & shiftReg(0))  

  // 4. 上升沿检测  
  val risingEdge = btnClean & !RegNext(btnClean)  

  // 5. 计数器  
  val counter = RegInit(0.U(8.W))  
  when (risingEdge) {  
    counter := counter + 1.U  
  }

  // 6. LED 显示  
  io.leds := counter
}

仿真与测试

  1. 观察抖动问题
    • 直接将按钮信号连接到计数器触发信号,快速按下按钮,观察 LED 显示的跳变次数。
  2. 加入去抖动和滤波电路
    • 通过 1 Hz 采样频率和多数投票滤波,解决按钮抖动问题。
    • 确保按钮按下和释放的状态都被正确检测。
  3. 测试释放信号
    • 按钮释放时间短于 1–2 秒时,电路不识别释放信号;只有释放时间长于设定值时才有效。

总结

  1. 输入处理链:包括同步器、去抖动器、多数投票滤波器和上升沿检测电路,确保按钮输入的可靠性。
  2. 采样频率:合理设置采样频率(如 1 Hz)过滤按钮的机械抖动。
  3. 系统验证:通过仿真和 FPGA 板上的测试,验证设计是否能正确处理按钮按下和释放信号。

通过本练习,可以掌握完整的输入处理链设计,有效解决按钮抖动和噪声问题,并在实际硬件中实现可靠的输入信号处理。

是的,这些输入同步器、去抖动器和多数投票滤波器在 Verilog 中也可以实现,它们是硬件设计中的常见模块。虽然 Verilog 和 Chisel 的风格有所不同,但原理和最终生成的硬件电路是一样的。下面是 Verilog 中对应实现的内容:

1. 输入同步器 (Input Synchronizer)

输入同步器的目的是将异步信号同步到系统时钟域中,避免时序问题和亚稳态。

Verilog 实现

verilog
module synchronizer (
    input wire clk,       // 系统时钟
    input wire async_in,  // 异步输入信号
    output reg sync_out   // 同步输出信号
);

    reg sync_ff1;  // 第一级寄存器
    reg sync_ff2;  // 第二级寄存器

    // 两级寄存器级联,去除亚稳态
    always @(posedge clk) begin
        sync_ff1 <= async_in;
        sync_ff2 <= sync_ff1;
    end

    assign sync_out = sync_ff2; // 同步后的输出信号

endmodule

原理

  • 两个寄存器级联,将异步输入信号同步到时钟边沿。
  • 第一级寄存器捕获异步输入信号,第二级寄存器输出稳定的同步信号。

2. 去抖动器 (Debouncer)

去抖动器通过时间采样的方式,过滤机械开关或按钮输入信号的抖动。

Verilog 实现

verilog
module debouncer (
    input wire clk,        // 系统时钟
    input wire btn_in,     // 按键输入信号
    output reg btn_out     // 去抖动后的信号输出
);

    reg [19:0] counter;    // 计数器,用于实现采样周期
    reg btn_sync;          // 同步后的按键输入

    // 输入同步器
    always @(posedge clk) begin
        btn_sync <= btn_in;
    end

    // 计数器实现去抖动
    always @(posedge clk) begin
        if (btn_sync == btn_out) begin
            counter <= 0;  // 如果输入信号和输出信号相同,重置计数器
        end else begin
            counter <= counter + 1;
            if (counter == 20'hFFFFF) begin
                btn_out <= btn_sync; // 当信号稳定一段时间后更新输出
            end
        end
    end

endmodule

原理

  1. 输入同步器:将按键输入信号同步到系统时钟域。

  2. 时间采样 :

    • 当输入信号与当前输出信号相同时,计数器重置。
    • 当输入信号连续保持稳定一段时间(计数器溢出)时,更新输出信号。

3. 多数投票滤波器 (Majority Voting Filter)

多数投票滤波器通过连续采样多次输入信号,取多数值作为最终输出,用于进一步抑制短时间噪声。

Verilog 实现

verilog
module majority_filter (
    input wire clk,        // 系统时钟
    input wire signal_in,  // 输入信号
    output wire signal_out // 滤波后的输出信号
);

    reg [2:0] shift_reg;   // 3 位移位寄存器

    // 移位寄存器
    always @(posedge clk) begin
        shift_reg <= {shift_reg[1:0], signal_in}; // 左移并插入新输入值
    end

    // 多数投票逻辑
    assign signal_out = (shift_reg[2] & shift_reg[1]) | 
                        (shift_reg[2] & shift_reg[0]) | 
                        (shift_reg[1] & shift_reg[0]);

endmodule

原理

  • 使用 3 位移位寄存器连续采样输入信号。
  • 通过逻辑运算决定输出值:若三位中任意两位为高电平,输出高电平。

4. 上升沿检测 (Edge Detection)

上升沿检测电路用于捕获信号从 0 跳变为 1 的瞬间。

Verilog 实现

verilog
module rising_edge_detector (
    input wire clk,          // 系统时钟
    input wire signal_in,    // 输入信号
    output wire rising_edge  // 上升沿检测输出
);

    reg signal_delay;        // 延时寄存器

    // 检测上升沿
    always @(posedge clk) begin
        signal_delay <= signal_in;
    end

    assign rising_edge = signal_in & ~signal_delay; // 上升沿:当前为 1,前一周期为 0

endmodule

原理

  • 使用寄存器延迟一个时钟周期,保存输入信号的前一状态。
  • 当前信号为高电平,前一状态为低电平时,输出上升沿信号。

5. 完整按键计数器设计

将上述模块组合,实现一个基于按键输入的计数器:

Verilog 实现

verilog
module button_counter (
    input wire clk,        // 系统时钟
    input wire btn,        // 按键输入
    output reg [7:0] count // 8 位计数器输出
);

    wire btn_sync;        // 同步后的信号
    wire btn_debounced;   // 去抖动后的信号
    wire rising_edge;     // 上升沿检测信号

    // 输入同步器
    synchronizer sync_inst (
        .clk(clk),
        .async_in(btn),
        .sync_out(btn_sync)
    );

    // 去抖动器
    debouncer debounce_inst (
        .clk(clk),
        .btn_in(btn_sync),
        .btn_out(btn_debounced)
    );

    // 上升沿检测
    rising_edge_detector edge_inst (
        .clk(clk),
        .signal_in(btn_debounced),
        .rising_edge(rising_edge)
    );

    // 计数器逻辑
    always @(posedge clk) begin
        if (rising_edge) begin
            count <= count + 1;
        end
    end

endmodule

总结

  1. 输入同步器:解决异步输入的时序问题,防止亚稳态。
  2. 去抖动器:通过时间采样消除按钮抖动。
  3. 多数投票滤波器:进一步稳定输入信号,抑制短时噪声。
  4. 上升沿检测:捕获输入信号的有效跳变。
  5. 计数器:在按键按下时触发计数。

这些模块在 Verilog 中实现起来相对简单,并且是数字设计中常用的基础组件,可以灵活组合使用。