7 输入处理 (Input Processing)
在数字电路中,从外部世界进入同步电路的输入信号通常是异步的,即它们不与系统时钟同步。这种异步输入可能导致以下问题:
- 亚稳态(Metastability):输入信号在时钟上升沿附近变化,可能违反触发器的建立时间和保持时间,导致不确定状态。
- 多位寄存冲突:信号在不同延迟下注册到不同的时钟周期,可能导致电路行为错误。
- 噪声与抖动:例如机械开关产生的抖动(bouncing)或信号尖峰。
本章主要描述如何通过数字电路设计来处理异步输入信号,解决上述问题。
7.1 异步输入 (Asynchronous Input)
异步输入的定义与问题
- 异步信号是指不与系统时钟同步的输入信号。
- 当异步信号在系统时钟的上升沿附近发生变化时,可能导致触发器的建立时间(setup time)或保持时间(hold time)被破坏。
- 这种情况会导致触发器进入亚稳态(Metastable State),即输出值介于 0 和 1 之间,或者长时间无法稳定。
- 亚稳态最终会稳定,但它可能会导致输出在短时间内不可预测,影响电路功能。
亚稳态的解决方案
尽管我们无法完全避免亚稳态,但我们可以限制其影响。常见的解决方法是使用两个级联触发器(two-stage flip-flops)作为输入同步器(Input Synchronizer):
- 第一个触发器:接收异步输入信号。即使进入亚稳态,经过一定时间后会稳定。
- 第二个触发器:在稳定信号的基础上注册一次,确保信号与系统时钟同步。
这种方法能够显著降低亚稳态的影响,确保异步信号被安全地引入同步电路。
输入同步器的结构
图 7.1 显示了一个典型的 输入同步器:
- 输入信号 (btn):来自外部世界的异步信号。
- 两个触发器级联:通过两个连续寄存器将异步信号同步到系统时钟域。
- 输出信号 (btnSync):同步后的信号,可以安全地在同步电路中使用。
Chisel 实现:输入同步器
Chisel 提供了非常简洁的语法来实现输入同步器,使用两个级联的 RegNext
:
代码示例:
val btnSync = RegNext(RegNext(btn))
代码解析:
RegNext(btn)
:将异步输入信号btn
注册到第一个寄存器。- 第二个
RegNext
:将第一个寄存器的输出注册到第二个寄存器,进一步稳定信号。 btnSync
:最终同步后的信号,确保它与系统时钟同步,可以安全地用于同步电路。
同步复位信号
除了常规异步输入信号,复位信号(reset)也需要同步到系统时钟:
- 复位信号通常来自外部按键,可能是异步的。
- 异步复位的释放(deassertion)必须与系统时钟同步,以避免复位后电路处于不确定状态。
实现方法:与输入同步器相同,将复位信号通过两个寄存器级联同步。
亚稳态的概率与影响
虽然输入同步器可以大幅降低亚稳态的影响,但无法完全消除:
- 亚稳态的持续时间与系统时钟周期成反比。时钟频率越高,亚稳态的概率越高。
- 实际设计中,两个级联触发器已经足够满足绝大多数应用场景。
总结
- 异步输入问题:异步输入信号可能违反触发器的建立时间和保持时间,导致亚稳态,影响系统稳定性。
- 输入同步器:使用两个级联触发器同步异步输入,减少亚稳态的影响,确保信号与时钟同步。
- Chisel 实现:使用
RegNext
级联两级寄存器实现输入同步器。 - 同步复位信号:外部复位信号也需要同步,以确保安全的电路启动。
通过输入同步器,可以安全地将异步输入信号引入同步电路,提升系统的稳定性和可靠性。
7.2 去抖动 (Debouncing)
去抖动主要用于处理机械开关或按钮的输入信号,这些信号在状态切换时可能会出现短时间的抖动。
- 当开关从 开 到 关,或从 关 到 开 时,由于机械结构的特性,信号可能在 0 和 1 之间反复跳变,形成 抖动。
- 如果这些抖动未经处理,电路可能检测到多个不必要的信号过渡,导致误触发。
为了解决这个问题,可以通过时间采样的方法将抖动信号进行滤波处理,确保只检测到单一的有效过渡。
1. 去抖动的基本原理
假设最大抖动时间为 tbouncet_{\text{bounce}},我们可以选择一个大于该时间的采样周期 TT,使得采样点之间的间隔满足:
T>tbounceT > t_{\text{bounce}}
这样,在信号从 0 变为 1 的过程中,最多只有一个采样点会落在抖动区域内:
- 抖动前的采样点稳定地读取 0。
- 抖动区域的采样点可能读到 0 或 1,但不会影响整体结果。
- 抖动后的采样点稳定地读取 1。
最终,去抖动后的信号会稳定地检测到 0 到 1 或 1 到 0 的单次过渡。
2. 图示说明
图 7.2 展示了信号去抖动的示意图:
bouncing in:表示输入信号的抖动情况,信号在抖动时间内快速跳变。
debounced A
和
debounced B :去抖动后的输出信号,两个版本的信号都有单一的过渡点。
- A 和 B 的区别在于去抖动的延迟不同,但都保证只有一次过渡。
3. Chisel 实现去抖动电路
设计思路
- 计数器:实现一个定时器,按照设定的采样周期对输入信号进行采样。
- 采样逻辑:当计数器到达最大值时,将输入信号的当前值存储到一个寄存器中,作为去抖动后的信号输出。
- 输入同步:去抖动电路通常放在输入同步器之后,确保信号已与时钟同步。
Chisel 代码实现
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 // 采样输入信号(已同步)
}
代码解析
- 采样频率设置
fac
设置采样频率为 100 Hz,假设系统时钟频率为 100 MHz。- 通过计数器
cntReg
计数,当达到fac-1
时生成 tick 信号。
- 计数器逻辑
cntReg
每个时钟周期加 1。- 当
tick
信号触发时,计数器重置为 0。
- 去抖动信号存储
- 在
tick
信号触发时,将同步后的输入信号btnSync
存储到btnDebReg
中。 btnDebReg
是去抖动后的输出信号。
- 在
4. 采样频率的选择
去抖动电路的关键在于正确选择采样频率:
- 最大抖动时间 tbouncet_{\text{bounce}}:通常在 5~10 ms 之间。
- 采样周期 TT:需要满足 T>tbounceT > t_{\text{bounce}}。例如,100 Hz 的采样频率对应的周期为 10 ms。
如果采样周期太短,可能会捕获到抖动信号,导致去抖动失败; 如果采样周期太长,会引入较大的延迟,影响系统响应速度。
5. 去抖动电路的位置
去抖动电路通常位于输入同步器之后:
- 输入同步器:将异步输入信号与时钟同步,确保信号稳定。
- 去抖动电路:对同步后的信号进行时间采样,滤除抖动。
6. 总结
抖动问题:机械开关或按钮在状态切换时会产生抖动,导致不必要的过渡信号。
时间采样方法:通过设置一个大于最大抖动时间的采样周期,对输入信号进行定时采样,滤除抖动。
Chisel 实现 :
- 使用计数器生成 tick 信号,实现定时采样。
- 采样周期由系统时钟和计数器的最大值决定。
- 将采样信号存储到寄存器中,作为去抖动后的输出。
电路设计原则 :
- 先同步,后去抖动。
- 合理选择采样周期,确保有效去抖动,同时保证系统响应速度。
通过去抖动电路,可以有效地处理抖动信号,确保输入信号的稳定性和可靠性。
7.3 输入信号的滤波 (Filtering of the Input Signal)
在实际硬件设计中,输入信号可能会受到噪声干扰,导致出现短时间的尖峰脉冲。这些脉冲可能会被同步器或去抖动单元不小心采样到,导致电路误触发。为了解决这一问题,可以使用多数投票电路(Majority Voting Circuit)对输入信号进行滤波。
1. 多数投票电路的原理
多数投票电路是一种简单而有效的信号滤波方法,它通过对连续多次采样的信号执行多数决策来确定最终输出信号:
- 在最简单的形式下,对输入信号进行 3 次采样。
- 使用逻辑运算确定三个采样结果中多数的值(2 个或以上)。
- 如果信号中存在短暂的脉冲(尖峰),它不会影响最终的输出,因为该信号在 3 个采样周期中不会占据多数。
多数投票电路与中值滤波类似,能够过滤掉时间较短的噪声信号,确保输出信号的稳定性。
2. 多数投票电路的结构
图 7.3 展示了一个 3 位移位寄存器与多数投票电路的结构:
3 位移位寄存器:通过
tick
采样周期对输入信号din
进行连续 3 次采样。多数投票逻辑 :
- 通过逻辑运算组合寄存器的 3 位输出 aa、bb、cc,得到最终的输出信号
dout
。 - 逻辑表达式为:
- 通过逻辑运算组合寄存器的 3 位输出 aa、bb、cc,得到最终的输出信号
dout=(a & b) ∣ (a & c) ∣ (b & c)\text{dout} = (a , & , b) , | , (a , & , c) , | , (b , & , c)
该表达式确保三个输入中任意两个相等的值将决定输出结果。
3. Chisel 实现多数投票滤波器
代码实现:
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))
代码解析:
- 移位寄存器:
shiftReg
是一个 3 位寄存器,每次tick
信号触发时,输入信号btnDebReg
通过移位寄存器存储。shiftReg(1, 0) ## btnDebReg
实现了左移操作,并将新输入值拼接到最低位。
- 多数投票逻辑:
- 使用逻辑运算表达式计算三个采样结果的多数值。
- 任何两个相等的位将决定最终的输出
btnClean
。
4. 使用滤波后的信号
滤波后的信号 btnClean
是经过去抖动和滤波处理后的稳定信号,可以安全地用于电路的进一步处理。 例如,我们可以检测 上升沿(rising edge)以触发某个操作(如计数器增加):
代码示例:
val risingEdge = btnClean & !RegNext(btnClean) // 检测上升沿
// 使用滤波后的信号触发计数器
val reg = RegInit(0.U(8.W))
when (risingEdge) {
reg := reg + 1.U
}
代码解析:
- 上升沿检测:
RegNext(btnClean)
保存btnClean
的前一个时钟周期值。btnClean & !RegNext(btnClean)
表示当前时钟周期为高电平,而前一个周期为低电平,检测到上升沿。
- 触发操作:
- 当检测到
btnClean
的上升沿时,计数器reg
加 1。
- 当检测到
5. 多数投票电路的优势与应用
优势:
- 滤除短时间噪声:通过连续采样并执行多数投票,消除短时间的信号尖峰。
- 简单易实现:逻辑结构简单,仅需移位寄存器和少量逻辑运算。
应用场景:
- 按钮去抖动:结合去抖动单元,进一步滤除残留的噪声。
- 输入信号滤波:在噪声较多的环境中对传感器输入信号进行滤波。
- 边沿检测:检测经过滤波处理后的信号的上升沿或下降沿,触发系统事件。
6. 总结
- 噪声问题:输入信号可能包含短时间的尖峰脉冲,影响电路功能。
- 多数投票滤波:通过 3 次采样和逻辑运算,消除短时间的信号噪声,确保输出信号的稳定性。
- Chisel 实现:使用 3 位移位寄存器和逻辑表达式实现多数投票电路。
- 上升沿检测:利用滤波后的信号检测边沿事件,触发系统操作。
通过多数投票电路,可以进一步增强输入信号的稳定性,与同步器和去抖动单元配合使用,有效解决输入信号噪声问题。
7.4 将输入处理封装为函数 (Combining the Input Processing with Functions)
在硬件设计中,输入处理电路(如同步器、去抖动器和滤波器)虽然简单,但常常会被重复使用。为了提高代码的可复用性和简洁性,可以将这些电路封装为Chisel函数。这样,我们可以轻松地调用这些函数创建输入处理模块,而不必每次都重新编写代码。
1. Chisel 输入处理函数
以下是常见输入处理的函数封装:
输入同步器 (sync
)
- 功能:使用两个级联触发器将异步输入信号与系统时钟同步。
- 实现:
def sync(v: Bool) = RegNext(RegNext(v))
- 参数:
v
是异步输入信号。 - 返回值:同步后的信号。
上升沿检测 (rising
)
- 功能:检测信号的上升沿,即当前周期为高电平而前一周期为低电平。
- 实现:
def rising(v: Bool) = v & !RegNext(v)
- 参数:
v
是输入信号。 - 返回值:上升沿检测信号(单周期高电平)。
生成定时信号 (tickGen
)
- 功能:生成周期性单周期高电平的 tick 信号。
- 实现:
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 位移位寄存器执行多数投票,滤除输入信号中的短时间噪声。
- 实现:
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. 使用这些函数的完整示例
以下代码展示如何将输入处理电路组合起来,使用封装的函数实现信号同步、去抖动和滤波:
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
}
代码解析
sync
:将io.btnU
输入信号同步到系统时钟域。tickGen
:生成周期性 tick 信号,作为采样信号。- 去抖动处理:使用 tick 信号对同步后的信号进行采样,存储到
btnDeb
中。 filter
:进一步使用多数投票滤波器对去抖动信号进行滤波,确保输出稳定。rising
:检测滤波后的信号的上升沿。- 计数器:在检测到上升沿时,计数器
reg
加 1。
7.5 同步复位 (Synchronizing Reset)
1. 复位信号的同步问题
在数字电路设计中,复位信号用于将寄存器初始化到定义的状态。
- 复位信号可能是异步输入,直接将其连接到寄存器的复位端可能违反时序约束,导致建立时间和保持时间问题。
- 异步释放(deassertion)可能导致电路不同部分在不同时钟周期复位,造成不一致。
2. 解决方案:复位信号同步化
解决方法是将复位信号同步到系统时钟域,与其他异步输入信号的同步方法相同:
- 通过两个级联触发器实现同步复位信号。
3. Chisel 实现同步复位
代码示例:
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
}
代码解析
RegNext(RegNext(reset))
:将异步复位信号通过两个级联寄存器同步到时钟域,生成syncReset
。- 复位连接:将同步后的复位信号
syncReset
连接到模块cnt
的复位输入端reset
。 - 顶层模块设计:
SyncReset
作为顶层模块,通过同步化复位信号控制内部模块的复位。
4. 总结
- 输入处理函数封装:将同步器、去抖动器和滤波器等小电路封装为函数,提高代码的可复用性和简洁性。
- 同步复位:复位信号必须同步到时钟域,以避免时序约束问题和不一致的复位状态。
- Chisel 实现:通过
RegNext
实现同步复位,并将其应用于模块的复位输入。
通过这些函数和复位同步处理,可以构建稳定可靠的输入信号处理和复位系统,确保整个电路在时钟域内安全运行。
7.6 练习 (Exercise)
目标
设计一个基于输入按钮的计数器系统,并在 FPGA 开发板上通过 LED 显示计数值(二进制格式)。该设计要求观察按钮输入时的抖动问题,然后通过完整的输入处理链解决该问题。
任务概述
- 按钮抖动观察
- 直接将按钮输入连接到计数器的触发信号,观察按键输入的抖动现象。
- 由于按钮的机械抖动,按一次按钮可能会导致计数器的多次递增。
- 解决抖动问题
- 构建一个完整的输入信号处理链,包括以下模块:
- 输入同步器:将异步输入按钮信号同步到系统时钟域。
- 去抖动电路:滤除按钮输入的抖动,通过时间采样确保只检测到单次有效的按钮按下。
- 多数投票电路:进一步抑制噪声,确保信号稳定。
- 上升沿检测电路:检测按钮信号的上升沿,触发计数器递增操作。
- 构建一个完整的输入信号处理链,包括以下模块:
- 系统仿真与验证
- 使用不同的采样频率(例如 1 Hz)模拟按钮抖动情况。
- 按键释放信号也经过多数投票滤波,只有释放时间超过 1–2 秒时才被识别为有效信号。
设计流程
1. 输入同步器
首先将异步输入按钮信号同步到系统时钟域:
val btnSync = RegNext(RegNext(io.btn)) // 同步输入按钮信号
2. 去抖动电路
通过定时器对同步后的按钮信号进行去抖动处理,采样频率设定为 1 Hz:
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 位移位寄存器实现多数投票滤波,确保按钮信号的稳定性:
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)) // 8 位计数器
when (risingEdge) {
counter := counter + 1.U
}
6. LED 显示
通过 LED 显示计数器的值:
io.leds := counter // 将计数器值输出到 LED
完整代码示例
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
}
仿真与测试
- 观察抖动问题
- 直接将按钮信号连接到计数器触发信号,快速按下按钮,观察 LED 显示的跳变次数。
- 加入去抖动和滤波电路
- 通过 1 Hz 采样频率和多数投票滤波,解决按钮抖动问题。
- 确保按钮按下和释放的状态都被正确检测。
- 测试释放信号
- 按钮释放时间短于 1–2 秒时,电路不识别释放信号;只有释放时间长于设定值时才有效。
总结
- 输入处理链:包括同步器、去抖动器、多数投票滤波器和上升沿检测电路,确保按钮输入的可靠性。
- 采样频率:合理设置采样频率(如 1 Hz)过滤按钮的机械抖动。
- 系统验证:通过仿真和 FPGA 板上的测试,验证设计是否能正确处理按钮按下和释放信号。
通过本练习,可以掌握完整的输入处理链设计,有效解决按钮抖动和噪声问题,并在实际硬件中实现可靠的输入信号处理。
是的,这些输入同步器、去抖动器和多数投票滤波器在 Verilog 中也可以实现,它们是硬件设计中的常见模块。虽然 Verilog 和 Chisel 的风格有所不同,但原理和最终生成的硬件电路是一样的。下面是 Verilog 中对应实现的内容:
1. 输入同步器 (Input Synchronizer)
输入同步器的目的是将异步信号同步到系统时钟域中,避免时序问题和亚稳态。
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 实现
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
原理
输入同步器:将按键输入信号同步到系统时钟域。
时间采样 :
- 当输入信号与当前输出信号相同时,计数器重置。
- 当输入信号连续保持稳定一段时间(计数器溢出)时,更新输出信号。
3. 多数投票滤波器 (Majority Voting Filter)
多数投票滤波器通过连续采样多次输入信号,取多数值作为最终输出,用于进一步抑制短时间噪声。
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 实现
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 实现
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
总结
- 输入同步器:解决异步输入的时序问题,防止亚稳态。
- 去抖动器:通过时间采样消除按钮抖动。
- 多数投票滤波器:进一步稳定输入信号,抑制短时噪声。
- 上升沿检测:捕获输入信号的有效跳变。
- 计数器:在按键按下时触发计数。
这些模块在 Verilog 中实现起来相对简单,并且是数字设计中常用的基础组件,可以灵活组合使用。