Chisel进阶之通信状态机(一)——通信状态机:以闪光灯为例
在最后一部分,我们学习了单个有限状态机Chisel描述方法,但单个有限状态机通常很难描述稍微复杂的数字设计。在这种情况下,问题可以分为两个或两个以上的小而简单FSM。这个FSM之间使用信号进行通信FSM另一种输出FSM输入,这个FSM观察另一个FSM的输出。把一个大的FSM分成简单的小FSM做法叫分解FSM。通信FSM通常根据设计规格直接设计,因为如果先使用单个,FSM实现设计太大了。本文将学习通信状态机的设计实现。
通信FSM——以闪光灯为例
为了讨论通信FSM,我们用闪光灯的例子,它有输入start
和一个输出light
,闪光灯的设计规格如下:
- 当
start
闪烁开始于一个周期内的高电平时; - 会闪三次;
- 一次闪烁中,
light
为on
为off
四个周期; - 序列之后,FSM会将
light
置为off
等下一个start
;
直接用一个FSM要实现这个闪光灯,该实现了FSM27中状态:1是等待输入的初始状态, 3 × 6 3\times 6 3×6个关于on
状态, 2 × 4 2\times 4 2×4个关于off
状态,共27个状态。这里就不写这个实现代码了。
那我们可以把这个大FSM分为两个小FSM:一个主FSM定时器用于实现闪烁逻辑FSM用于实现等待,这两个显示在下图中FSM组合:
定时器FSM将倒数6个周期或4个周期用于生成所需的时间序列,定时器的规格如下:
- 当
timerLoad
设置时,定时器加载一个不依赖状态的倒数计数器; timerSelect
加载5或3会选择;timerDone
当计数器完成倒数时,将设置并保持设置状态;- 在其他情况下,定时器将倒数。
下面的代码显示了闪光灯的定时器FSM和主FSM实现:
val timerReg = RegInit(0.U) // 计时器连接 val timerLoad = WireDefault(false.B) // 给load启动信号后的定时器 val timerSelect = WireDefault(true.B) // 选择6周期循环或4周期循环循环 val timerDone = Wire(Bool()) timerDone := timerReg === 0.U timerLoad := timerDone // 定时器FSM(倒数计数器) when(!timerDone) {
timerReg := timerReg - 1.U } when(timerLoad) {
when(timerSelect) {
timerReg
:
=
5.U
}
.otherwise
{
timerReg
:
=
3.U
}
}
val off
:: flash1
:: space1
:: flash2
:: space2
:: flash3
:: Nil
= Enum
(
6
)
val stateReg
= RegInit
(off
)
val light
= WireDefault
(
false
.B
)
// FSM的输出
// 主FSM switch
(stateReg
)
{
is
(off
)
{
timerLoad
:
=
true
.B timerSelect
:
=
true
.B when
(start
)
{
stateReg
:
= flash1
}
} is
(flash1
)
{
timerSelect
:
=
false
.B light
:
=
true
.B when
(timerDone
)
{
stateReg
:
= space1
}
} is
(space1
)
{
when
(timerDone
)
{
stateReg
:
= flash2
}
} is
(flash2
)
{
timerSelect
:
=
false
.B light
:
=
true
.B when
(timerDone
)
{
stateReg
:
= space2
}
} is
(space2
)
{
when
(timerDone
)
{
stateReg
:
= flash3
}
} is
(flash3
)
{
timerSelect
:
=
false
.B light
:
=
true
.B when
(timerDone
)
{
stateReg
:
= off
}
}
}
代码就不解释了,就提示一点,switch语句里面没有赋值的wire
都会赋默认值,所有space1
和space2
里面可以省略timerSelect
和light
的赋值,但建议最好别这么写。
不过这个解决方案在主FSM中还是有冗余代码,flash1
、flash2
和flash3
状态的功能一致,space1
和space2
也是如此。我们可以把这几种flash
状态分解到第二个计数器里面,那么主FSM就只有三个状态了off
、flash
和space
。下图就展示了新的通信FSM设计:
现在图里面有两个计数FSM了,一个用于计数on
和off
间隔的时钟周期数,另一个用于计数剩下的flash
次数。下面的代码就是向下计数FSM部分:
val cntReg = RegInit(0.U)
cntDone := cntReg === 0.U
// 向下计数FSM
when(cntLoad) {
cntReg := 2.U}
when(cntDecr) {
cntReg := cntReg - 1.U}
注意计数器加载了2以闪烁三次,因为它会倒数剩下的闪烁次数,并在定时器完成是递减到space
状态。下面的代码展示了完整的新的闪光灯FSM实现:
import chisel3._
import chisel3.util._
class SimpleFsm extends Module {
val io = IO(new Bundle {
val start = Input(Bool())
val light = Output(Bool())
val state = Output(UInt()) // 用于调试
})
val light = WireDefault(false.B)
val timerLoad = WireDefault(false.B)
val timerSelect = WireDefault(true.B)
val timerDone = Wire(Bool())
val timerReg = RegInit(0.U)
timerDone := timerReg === 0.U
timerLoad := timerDone
when(!timerDone) {
timerReg := timerReg - 1.U
}
when(timerLoad) {
when(timerSelect) {
timerReg := 5.U
}.otherwise {
timerReg := 3.U
}
}
val cntLoad = WireDefault(false.B)
val cntDecr = WireDefault(true.B)
val cntDone = Wire(Bool())
val cntReg = RegInit(0.U)
cntDone := cntReg === 0.U
when(cntLoad) {
cntReg := 2.U }
when(cntDecr) {
cntReg := cntReg - 1.U }
val off :: flash :: space :: Nil = Enum(3)
val stateReg = RegInit(off)
switch(stateReg) {
is(off) {
timerLoad := true.B
timerSelect := true.B
cntLoad := true.B
when(io.start) {
stateReg := flash }
}
is(flash) {
cntLoad := false.B
timerSelect := false.B
light := true.B
when(timerDone & !cntDone) {
stateReg := space }
when(timerDone & cntDone) {
stateReg := off }
}
is(space) {
cntDecr := timerDone
when(timerDone) {
stateReg := flash }
}
}
io.light := light
io.state := stateReg
}
object MyModule extends App {
println(getVerilogString(new SimpleFsm()))
}
除了将主FSM的状态减少为3中以外,我们新的配置也更加可配置了。没有任何一个FSM会需要在我们想改变on
、off
时长的时候被修改。
测试代码如下:
import chisel3._
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec
class SimpleTestExpect extends AnyFlatSpec with ChiselScalatestTester {
"DUT" should "pass" in {
test(new SimpleFsm) {
dut =>
dut.clock.step()
println(dut.io.state.peekInt(), dut.io.light.peekBoolean())
dut.io.start.poke(true.B)
for (a <- 0 until 50) {
dut.clock.step()
dut.io.start.poke(false.B)
println(a, dut.io.state.peekInt(), dut.io.light.peekBoolean())
}
}
}
}
观察测试的输出,可知测试通过。
结语
这一篇文章我们以闪光灯电路为例,探索了通信电路,尤其是通信FSM的写法,FSM之间仅交换了控制信号。不过电路之间还可以交换数据,对于数据的协调交换,我们需要使用握手信号,这一部分的最后一篇文章我们就会一起学习用于单向数据交换的控制流的ready-valid
接口。下一篇文章,我们将会学习带数据通路的状态机,从手动指定ready-valid
信号开始。