资讯详情

吃透Chisel语言.29.Chisel进阶之通信状态机(一)——通信状态机:以闪光灯为例

Chisel进阶之通信状态机(一)——通信状态机:以闪光灯为例

在最后一部分,我们学习了单个有限状态机Chisel描述方法,但单个有限状态机通常很难描述稍微复杂的数字设计。在这种情况下,问题可以分为两个或两个以上的小而简单FSM。这个FSM之间使用信号进行通信FSM另一种输出FSM输入,这个FSM观察另一个FSM的输出。把一个大的FSM分成简单的小FSM做法叫分解FSM。通信FSM通常根据设计规格直接设计,因为如果先使用单个,FSM实现设计太大了。本文将学习通信状态机的设计实现。

通信FSM——以闪光灯为例

为了讨论通信FSM,我们用闪光灯的例子,它有输入start和一个输出light,闪光灯的设计规格如下:

  1. start闪烁开始于一个周期内的高电平时;
  2. 会闪三次;
  3. 一次闪烁中,lightonoff四个周期;
  4. 序列之后,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个周期用于生成所需的时间序列,定时器的规格如下:

  1. timerLoad设置时,定时器加载一个不依赖状态的倒数计数器;
  2. timerSelect加载5或3会选择;
  3. timerDone当计数器完成倒数时,将设置并保持设置状态;
  4. 在其他情况下,定时器将倒数。

下面的代码显示了闪光灯的定时器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都会赋默认值,所有space1space2里面可以省略timerSelectlight的赋值,但建议最好别这么写。

不过这个解决方案在主FSM中还是有冗余代码,flash1flash2flash3状态的功能一致,space1space2也是如此。我们可以把这几种flash状态分解到第二个计数器里面,那么主FSM就只有三个状态了offflashspace。下图就展示了新的通信FSM设计:

现在图里面有两个计数FSM了,一个用于计数onoff间隔的时钟周期数,另一个用于计数剩下的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会需要在我们想改变onoff时长的时候被修改。

测试代码如下:

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信号开始。

标签: 连接器poke

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台