吃透Chisel语言.26.Chisel进阶之输入信号处理(二)——多数表决器滤波、函数抽象和异步复位

Chisel进阶之输入信号处理(二)——多数表决器滤波、函数抽象和异步复位

上一篇文章我们用简简单单几行代码就实现了异步输入信号的同步化和去抖动,足够应付按钮和开关的异步抖动输入了。这一篇文章我们继续输入信号处理,进一步解决可能存在的噪音问题,尝试为输入信号加一个噪声滤波器,并用函数把这些处理方式抽象整合到一起,最后我们还会简单讨论一下复位信号的同步化。

输入信号滤波

有时候我们的输入信号可能会有噪声,可能会夹杂一些毛刺,这些都是我们不希望通过输入同步器和去抖动单元采样得到的。一个选择是用多数表决电路来过滤这些毛刺,最简单的情况就是接受三个采样然后执行多数表决投票。这个多数函数(Majority Function)是和中值函数(Median Function)相关的,返回值是大多数的值。在我们这个场合,我们用采样来去抖动,然后在采样信号上执行多数表决。多数表决可以确保信号稳定的时间可以比采样周期长。

下图展示了多数表决器的电路:

在这里插入图片描述

这个表决器包含了一个三位的移位寄存器,该移位寄存器由用于去抖动采样的tick信号驱动。三个寄存器的输出都会输入到多数表决器中,然后多数表决函数会过滤掉任何短于采样周期的信号变化。

下面的Chisel代码就是三位的移位寄存器的实现,由tick信号驱动,寄存器的三位值输入到多数表决器并得到信号btnClean

val shiftReg = RegInit(0.U(3.W))
when (tick) {
    // 左移并输入最低有效位
    shiftReg := shiftReg(1, 0) ## btnDebReg
}
// 多数表决
val btnClean = (shiftReg(2) & shiftReg(1)) | (shiftReg(2) | shiftReg(1)) | (shiftReg(1) | shiftReg(0))

为了使用这个精心处理过的输入信号的输出,我们首先需要用一个RegNext延迟单元来检测上升沿,即比较信号和当前的btnClean值,如果处于上升沿的话,则计数器值加一:

val risingEdge = btnClean & !RegNext(btnClean)

// 用去抖动的、滤波后的按钮信号来驱动计数器自增
val reg = RegInit(0.U(8.W))
when (risingEdge) {
    reg := reg + 1.U
}

把输入信号处理过程抽象为函数!

这一部分我们总结一下输入信号处理。就目前而言,前面输入信号处理相关的Chisel代码都不长,但都是可以重用的块,因此我们可以考虑把它们都装到函数里面。前面已经提到过怎么把小的模块抽象成轻量级Chisel函数了,这些函数会创建硬件实例,比如sync函数会创建两个触发器连接输入和其他电路,函数会返回第二个触发器的输出。如果有用的话,这些函数还可以作为一些工具类的对象。

把输入信号处理过程都抽象为函数代码如下:

// 输入信号同步
def sync(v: Bool) = RegNext(RegNext(v))

// 上升沿检测
def rising(v: Bool) = v & !RegNext(v)

// tick生成
def tickGen(fac: Int) = {
    val reg = RegInit(0.U(log2Up(fac).W))
    val tick = reg === (fac - 1).U
    reg := Mux(tick, 0.U, reg + 1.U)
    tick
}

// 滤波器
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))
}

现在我们就可以方便地使用这些信号处理函数了:

// 信号处理函数的使用
val FAC = 100000000 / 1000 
// 同步化
val btnSync = sync(io.btnU)

// 采样去抖动
val tick = tickGen(FAC)
val btnDeb = Reg(Bool())
when (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
}

复位信号的同步

所有数字电路都需要复位信号来将寄存器重置到已定义的状态,在Chisel中寄存器的复位状态通过RegInit构造器设置。复位信号通常是到电路的异步信号输入,这意味着直接把复位输入接到电路的复位控制可能会导致违反时序约束。在异步复位的情况下,可能会违反触发器的建立和保持时间,因此在异步复位的情况下,仍然需要和时钟进行同步。具体来说,复位信号的释放需要和时钟同步。然后异步复位的另一个问题上电路的不同部分可能在两个不同的时钟周期内复位,导致不一致。

解决办法就是把复位信号和其他异步输入信号一样用两个触发器处理一下先。

Chisel设计中复位信号和时钟信号通常都是隐藏的,但是我们可以访问和设置它们。每个模块都有隐式的reset字段。解决方案就是整一个顶层模块,这个顶层模块执行外部复位信号的同步,并把同步复位信号与所有需要复位信号的模块连接到一起,比如下面的代码如下:

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
}

SyncReset就是个包含了计数器WhenCounter的顶层模块。顶层模块的复位信号叫作reset,它连接到输入同步器RegNext(RegNext(reset))上。然后输入同步器的输出syncReset又连接到计数器的复位输入上(cnt.reset := syncReset)。

结语

输入信号的处理就到这里了,除了了解了输入信号处理的过程,我们还对异步信号的时序问题有了更深的了解。这一部分的最后,我们还用函数抽象的方法把简单的小模块封装成函数方便调用,如果用得很多的话,我们还可以把这些函数放到类似utils的类里面。下一部分我们继续Chisel的进阶内容,讨论数字设计中非常重要的有限状态机,并为后面的状态机的通信打下基础。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值