当前位置:网站首页>吃透Chisel语言.30.Chisel进阶之通信状态机(二)——FSMD:以Popcount为例
吃透Chisel语言.30.Chisel进阶之通信状态机(二)——FSMD:以Popcount为例
2022-08-02 06:45:00 【github-3rr0r】
Chisel进阶之通信状态机(二)——FSMD:以Popcount为例
上一篇文章以闪光灯为例,介绍了通信状态机的写法,用于将大的复杂的状态机分解为小的多个相互通信的状态机来实现,可以保证使用资源更少,维护、修改也更容易。不过上一篇文章中的通信状态机之间的通信都是控制信号,还未涉及数据信号。这一篇文章就一起学习带数据通路的状态机,并以Popcount计数器为例进行介绍。
带数据通路的状态机
通信状态机的典型例子就是带数据通路的状态机,这种状态机有专门的名字,即FSMD(Finite-State Machine with Datapath,带数据通路的有限状态机)。其中状态机控制数据通路,数据通路执行计算。FSMD的输入有来自环境的输入和来自数据通路的输入,其中来自环境的输入会输入到数据通路,数据通路又生成数据。下图就是一个典型的FSMD:
Popcount的例子
上图的例子其实就是个计算Popcount的FSMD,这个Popcount也叫Hamming Weight(汉明权重),指的是一个二进制串中1
的数量。
Popcount单元包含数据输入din
和结果输出popCount
,两个都连接到数据通路。对于输入输出,我们使用ready-valid握手协议。当发送端数据有效的时候,valid
信号被设置,当接受端可以接受数据的时候,ready
信号被设置。当两个信号都被设置的时候,数据传输就会发生。握手信号连接到FSM上,FSM连接到数据通路上,包括FSM到数据通路的控制信号和数据通路到FSM的状态信号。
下一步我们就可以设计这个FSM了,首先从状态转换图开始,也就是上面给出的例子图。FSM从Idle
状态开始,等待输入。当数据到达的时候,会给出一个valid
信号,FSM会进入到Load
状态来读取一个移位寄存器。然后FSM进入下一个状态Count
,这里二进制串中的1
会被顺序计数。这里我们使用一个移位寄存器,一个加法器,一个累加器寄存器,以及一个向下计数器来完成计数。当向下计数器到零的时候,计算就完成了,FSM进入下一个状态Done
,此时带valid
信号的FSM信号就给出了,FSM信号中包含了将要被使用的popCount
值。当收到了接收端的ready
信号后,FSM就转移到Idle
状态,准备计算下一个popCount
。
下面的代码是顶层模块的描述,会对FSM和数据部分进行初始化,并将他们连接起来:
class PopCount extends Module {
val io = IO(new Bundle {
// 输入数据有效
val dinValid = Input(Bool())
// 可以接收数据
val dinReady = Output(Bool())
// 输入数据
val din = Input(UInt(8.W))
// 输出结果有效
val popCountValid = Output(Bool())
// 可以输出数据
val popCountReady = Input(Bool())
// 输出结果
val popCount = Output(UInt(4.W))
})
// fsm部分
val fsm = Module(new PopCountFSM)
// 数据通路部分
val data = Module(new PopCountDataPath)
// fsm和顶层接口的连接
fsm.io.dinValid := io.dinValid
io.dinReady := fsm.io.dinReady
io.popCountValid := fsm.io.popCountValid
fsm.io.popCountReady := io.popCountReady
// 数据通路和顶层接口的连接
data.io.din := io.din
io.popCount := data.io.popCount
// 数据通路和fsm之间的连接
data.io.load := fsm.io.load
fsm.io.done := data.io.done
}
注释简单地说明了顶层模块代码的含义,这里就不多说了。下面开始看数据通路的构造,下图是数据通路部分的示意图:
数据din
首先输入到shf
寄存器中。在加载数据的时候,cnt
寄存器置零。为了统计1
的数量,regData
寄存会右移(图片中的shf
),最低有效位在每个时钟周期都加到regPopCount
上(图片中的cnt
)。还有个寄存器图中没有画出来,它执行倒数计数,直到输入中所有的位都以最低有效位的形式移出,计数器为0的时候就表明popCount
计算结束了。此时FSM会切换到Done
状态,在popCountReady
信号被设置时输出结果信号。当结果被读取时,通过设置popCountValid
信号输出数据并让FSM切换回Idle
状态。下面是数据通路部分的Chisel代码实现:
class PopCountDataPath extends Module {
val io = IO(new Bundle {
val din = Input(UInt(8.W))
val load = Input(Bool())
val popCount = Output(UInt(4.W))
val done = Output(Bool())
})
val dataReg = RegInit(0.U(8.W))
val popCountReg = RegInit(0.U(4.W))
val counterReg = RegInit(0.U(4.W))
dataReg := 0.U ## dataReg(7, 1)
popCountReg := popCountReg + dataReg(0)
val done = counterReg === 0.U
when (!done) {
counterReg := counterReg - 1.U
}
when (io.load) {
dataReg := io.din
popCountReg := 0.U
counterReg := 8.U
}
// 调试用
printf("%b %d\t", dataReg, popCountReg)
io.popCount := popCountReg
io.done := done
}
在load
信号有效时,regData
寄存器会加载输入,regPopCount
寄存器会复位到0
,计数寄存器regCount
会设置为需要被移位的位数。否则,regData
寄存右移,被移出的最低有效位会加到regPopCount
寄存器上,倒数计数器regCount
自减一。当计数器为零时,regPopCount
的值就是要计算的popCount
。
而PopCountFSM
有三种状态,从idle
开始。当输入数据有效信号dinValid
被设置时,FSM会切换到count
状态,并等待数据通路完成计算,当popCount
有效时,FSM切换到done
状态,直到接收到popCntReady
信号并传送完数据,再切换为idle
状态,等待下一轮计算。FSM部分的Chisel实现如下:
class PopCountFSM extends Module {
val io = IO(new Bundle {
val dinValid = Input(Bool())
val dinReady = Output(Bool())
val popCountValid = Output(Bool())
val popCountReady = Input(Bool())
val load = Output(Bool())
val done = Input(Bool())
})
val idle :: count :: done :: Nil = Enum(3)
val stateReg = RegInit(idle)
io.load := false.B
io.dinReady := false.B
io.popCountValid := false.B
switch(stateReg) {
is(idle) {
io.dinReady := true.B
when(io.dinValid) {
io.load := true.B
stateReg := count
}
}
is(count) {
when(io.done) {
stateReg := done
}
}
is(done) {
io.popCountValid := true.B
when(io.popCountReady) {
stateReg := idle
}
}
}
// 调试用
printf("state: %b\n", stateReg)
}
这一部分的代码和之前状态机的代码类似,就不解释了。下面是测试代码:
import chisel3._
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec
class SimpleTestExpect extends AnyFlatSpec with ChiselScalatestTester {
"DUT" should "pass" in {
test(new PopCount) {
dut =>
dut.clock.step()
dut.io.din.poke("b10010011".U)
dut.io.dinValid.poke(true.B)
for (a <- 0 until 12) {
dut.clock.step()
}
dut.io.popCountReady.poke(true.B)
dut.clock.step()
dut.clock.step()
dut.clock.step()
dut.clock.step()
}
}
}
输出如下:
0 0 state: 0
0 0 state: 0
10010011 0 state: 1
1001001 1 state: 1
100100 2 state: 1
10010 2 state: 1
1001 2 state: 1
100 3 state: 1
10 3 state: 1
1 3 state: 1
0 4 state: 1
0 4 state: 10
0 4 state: 10
0 4 state: 10
0 4 state: 0
10010011 0 state: 1
1001001 1 state: 1
测试通过。
结语
这一篇文章以Popcount为例,介绍了带数据通路的有限状态机FSMD的写法与实现,对于后面写复杂的系统有很关键的指导意义。我们可以注意到,在FSMD的实现中,状态机之间的通信我们使用了Ready-Valid
握手协议,这是一种常见的通信接口协议,但每次都这么写显然有点复杂。而Chisel中自带了Ready-Valid
相关的函数DecoupledIO
,用于对数据信号进行Ready-Valid
协议的封装,下一篇文章我们就来学习这个重要又方便的函数。
边栏推荐
猜你喜欢
随机推荐
技术管理三级跳
海缆探测仪TSS350(二)
About the local server problem after ue4.27 pixel streaming package
正则表达式的理解学习
【故障诊断分析】基于matlab FFT轴承故障诊断【含Matlab源码 2001期】
July 18-July 31, 2022 (Ue4 video tutorials and documentation, 20 hours. Total 1412 hours, 8588 hours left)
雷达人体存在感应器方案,智能物联网感知技术,实时感应人体存在
暑假第五周总结
“蔚来杯“2022牛客暑期多校训练营4,签到题NDKHL
反射课后习题及做题记录
Two good php debug tutorials
【红队】ATT&CK - 创建或修改系统进程实现持久化(更新ing)
【机器学习】实验1布置:基于决策树的英雄联盟游戏胜负预测
倍福使用AdsRemote组件实现和C#的ADS通讯
堡垒机、堡垒机的原理
awk语法-01-基础语法(命令、选项、内部变量)
队列题目:无法吃午餐的学生数量
Unity Shader学习(七)纹理图像的简单使用
MQ带来的一些问题、及解决方案
封装class类一次性解决全屏问题