您的当前位置:首页正文

吃透Chisel语言.28.Chisel进阶之有限状态机(二)——Mealy状态机及与Moore状态机的对比

2024-12-01 来源:个人技术集锦

Chisel进阶之有限状态机(二)——Mealy状态机及与Moore状态机的对比

上一篇文章我们介绍了基本有限状态机(FSM),即Moore机,对应的是输出只和当前状态有关的有限状态机,这一篇我们则学习输出依赖于输入的状态机,即Mealy有限状态机,通过上升沿检测的例子来阐明相关特性,进一步比较Moore机和Mealy机的区别。

Mealy有限状态机

在Moore机中,输出仅依赖于当前状态,这意味着输入的改变可以被看作是最早的导致下一个时钟周期输出变化的因素。如果我们向观察一个间接的改变,我们需要一个构建一个从输入到输出的组合逻辑的路径。现在我们先考虑一个最小的例子,边缘检测电路,之前我们用一行Chisel代码描述了上升沿的检测:

val risingEdge = din & !(RegNext(din))

下图就是上面的上升沿检测器的示意图:

当当前输入为1且上一个周期的输入为0的时候,输出会维持一个周期的1。状态寄存器只是个D触发器,下一个状态作为输入,我们也可以认为这是个一个时钟周期的延迟单元。而输出逻辑会比较当前状态和当前输入。此时可以看到,输出不仅依赖于当前状态,还依赖于输入,也就是说还存在一个组合逻辑路径在FSM的输入和输出之间,这种FSM就叫做Mealy有限状态机

下面是一般Mealy机的示意图:

和Moore类似,寄存器包含了当前的状态state,下一状态逻辑根据当前状态state和当前输入in来计算下一个状态的值next_state。每到下一个时钟周期,next_state会变成下一个state。而不同的是,输出逻辑会根据当前状态state输入in来计算输出out

下图是边缘检测电路的Mealy机的状态转换图:

由于状态寄存器只包含一个D触发器,因此只可能有两种状态,在这个例子中分别为zeroone。因为Mealy机的输出不仅仅依赖于当前状态,还依赖于输入,所以我们不能将输出描述为状态圈的一部分,而是在状态之间的转换箭头上标记输入值(条件)和输出(放在斜杠之后)。需要注意的是,现在我们还会画出自转换,比如当前状态为zero输入也为0那FSM则会保持在状态zero。仅在从状态zero到状态one转换时,我们的上升沿FSM才会生成输出1。在状态one下,输入为1的时候输出为0。我们只希望每个上升沿产生一个周期的脉冲。

下面是上升沿检测Mealy机的Chisel实现代码:

import chisel3._
import chisel3.util._

class RisingFsm extends Moudle {
    val io = IO(new Bundle {
        val din = Input(Bool())
        val risingEdge = Output(Bool())
    })
    
    // 两种状态
    val zero :: one :: Nil = Enum(2)
    
    // 状态寄存器
    val stateReg = RegInit(zero)
    
    // 输出的默认值
    io.risingEdge := false.B
    
    // 状态转换逻辑和输出逻辑
    switch (stateReg) {
        is(zero) {
            when(io.din) {
                stateReg := one
                io.risingEdge := true.B
            }
        }
        is(one) {
            when(!io.din) {
                stateReg := zero
            }
        }
    }
}

代码和之前的例子差不多,就不过多解释了。需要注意的是输出逻辑已经是状态转换逻辑的一部分了,状态从zeroone的时候输出为true.B,否则的话输出就是默认值false.B

但是问题来了,我们前面已经有一行Chisel代码就实现了这个上升沿检测,那用成熟的FSM来实现上升沿检测是不是最好的解决方案呢?硬件消耗其实是一样的,两种方法都需要一个D触发器用于存储状态信息,不过FSM的组合逻辑会稍微复杂一些,因为状态改变依赖于当前状态和输入。对于这个功能而言,单行的Chisel代码是更容易写也更容易读懂的,这对于我们来说很关键,因此单行的实现是更好的。

但这并不是说Mealy机就没用了,我们只是展示了一个Mealy机的小例子。FSM应该用在更复杂的电路里面,比如有三个或者更多状态的电路。

Moore机和Mealy机对比

为了阐明Moore机和Mealy机的不同,我们用Moore机重新实现一下边缘检测。下图展示了用Moore机实现上升沿检测的示意图:

最先应该注意到的是Moore即需要三种状态,而Mealy机只需要两种状态。其中,状态puls用于生成单周期的脉冲信号。FSM只会在puls状态停留一个时钟周期,然后就会回到状态zero或状态one,等待输入再次变化。我们在图中用状态转换箭头上的标签表示输入条件,在状态圈内用数字表示输出。

下面是Moore机版本的上升沿检测电路实现:

import chisel3._
import chisel3.util._

class RisingMooreFsm extends Module {
    val io = IO(new Bundle {
        val din = Input(Bool())
        val risingEdge = Output(Bool())
    })
    
    // 三种状态
    val zero :: puls :: one :: Nil = Enum(3)
    
    // 状态寄存器
    val stateReg = RegInit(zero)
    
    // 状态转换逻辑
    switch (stateReg) {
        is(zero) {
            when(io.din) {
                stateReg := puls
            }
        }
        is(puls) {
            when(io.din) {
                stateReg := one
            } .otherwise {
                stateReg := zero
            }
        }
        is(one) {
            when(!io.din) {
                stateReg := zero
            }
        }
    }
    
    // 输出逻辑
    io.risingEdge := stateReg === puls
}

可以看出,Moore机版本比Mealy机版本或一句话版本用了双倍的D触发器,这也导致状态转换逻辑比Mealy机版本和一句话版本更大。

下图是Mealy机和Moore机两个版本的上升沿检测的时序波形图:

我们可以看到,Mealy机的输出和输入的上升沿非常接近,而Moore机的输出的上升沿滞后了一个周期。我们还可以注意到,Moore机的高电平输出维持了完整的一个时钟周期,而Mealy机通常不超过一个时钟周期,到当前时钟周期结束就也随之结束了。

通过以上的例子,我们可以发现Mealy机比Moore机更优,一方面是用了更少的状态信息因此有更少的逻辑,另一方面响应时间也没有延迟,比Moore机更快。然而在更大型的设计中,Mealy机中的组合逻辑路径可能会导致一些问题。首先FSM之间的通信链(下一部分会详细说)中,这个组合逻辑会变得很长,另一方面,如果通信FSM之间构成了一个环路,就会导致一个组合逻辑环路,在同步设计中会造成错误。由于Moore机中带状态寄存器的组合逻辑电路的切割,上面的问题在通信Moore机中都不存在。

总的来说,Moore机更适用于通信状态机的构建,他们比Mealy机更健壮。我们只在同一周期内的反应时间及其重要时才使用Mealy机。像一些小的电路,比如说上升沿检测,实际上也是Mealy机,也是可以的。

结语

这一部分的两篇文章介绍了Chisel中两种状态机——Moore机和Mealy机的实现,比较了两者在资源消耗和时序上的差异,对于我们构建自己的有限状态机是很有帮助的,也为使用哪种状态机给出了指导意见。有限状态机在数字设计中尤为重要,而目前仅探讨了有限状态机,下一部分将会介绍通信状态机,它是多个状态机组合在一起相互通信的结果,敬请期待。

显示全文