编 者 按
状态机的可监控维度该如何考虑
状态机该怎么监控
最近遇到一个关于状态机的问题,具体的业务就不讲了。关于FSM怎么写这种初级问题在这里也不讲了。这里我们只关注下在真实的应用场景里,从监控的角度来看,该如何去看待FSM。
FPGA设计里,除了功能实现之外,最重要的一部分就是DFX的设计。毕竟烧录完之后我们也只能通过DFX来去观测内部的状态(说JTAG的请绕过,那顶多是开发阶段)。在考虑资源允许的情况下,我们要做的应当是有充分的DFX能够帮助我们去观测内部的状态。一个工程做下来往往是在补DFX时觉得要添加的太多了,而到真实需要定位问题的时候则又会感叹“当初怎么不多添加些DFX”~
回到对FSM的处理,一开始想到的可能是:
- 把FSM的当前状态添加到DFX中。
没毛病,我们能够清楚的观察到当前状态机处于什么状态。但当状态机如果处于卡死不动的状态,那我们所需要的就是导致当前状态卡住的信号的状态:
- 把FSM各条件跳转的判断信号添加到DFX中。
上面两条也基本是我们之前设计常常会做的内容。然而这里面只是对下面的场景做了监控:
- 状态机卡住的场景——通过状态跳转条件的DFX信号去判断卡住的原因
对于DFX信号,像我们通过PCIe寄存器链路去读取DFX信号时不可能获取到每拍的结果,因而上面的DFX信号添加方式也就只能针对FSM卡死的情况进行定位判断。然而很不幸,这一次我遇到了一个状态机在跳转,只是没有跳转到一个需要响应某个动作的状态。由于代码从别人那里接手过来的,看代码也能对case场景进行一个判断。但回到监控的角度,只是没有跳转到一个需要响应某个动作的状态这个判断是我针对看到的DFX抓取信号来判断得到的结论,然而我并不能自证(当然可以通过仿真构造类似Case来进行验证,这不提了),毕竟DFX不是每拍的结果都能看得到的,针对线上的问题,所有的判断应当都是有充足的证据的,而不是结合观测加推断。那么针对这种场景,有必要再增加一种监控手段:
- 记录各状态之间是否有过跳转发生,软件可清零。
通过记录各状态之间是否发生过跳转,那么我们可以结合DFX当前状态来充分说明某个状态没有到达。而记录各状态之间是否有过跳转发生,所消耗的资源也非常少。
example
来看一个简单的状态机:
import spinal.lib.fsm._
class TopLevel extends Component {
val io = new Bundle {
val result = out Bool()
}
val fsm = new StateMachine {
val counter = Reg(UInt(8 bits)) init(0)
io.result := False
val stateA : State = new State with EntryPoint {
whenIsActive(goto(stateB))
}
val stateB : State = new State {
onEntry(counter := 0)
whenIsActive {
counter := counter + 1
when(counter === 4) {
goto(stateC)
}
}
onExit(io.result := True)
}
val stateC : State = new State {
whenIsActive(goto(stateA))
}
}
}
这里面会存在三个状态StateA、StateB、StateC:
- StateA——>StateB
- StateB——>StateC
- StateC——>StateA
那么我们需要记录的就是:
- StateA_to_fsm_stateB_change
- StateB_to_fsm_stateC_change
- StateC_to_fsm_stateA_change
在SpinalHDL里,这种活儿还是不要手动的好,当然是自动化的处理好。下面给一个Demo,可能有大神有更加优雅的解决方式,欢迎交流。
定义StateExtend:
class StateExtend(implicit stateMachineAccessor: StateMachineAccessor) extends State {
val nextStateBuffer=Set[StateExtend]()
def goto(state:StateExtend)={
nextStateBuffer.add(state)
stateMachineAccessor.goto(state)
}
}
主要是在原有State的基础上重定义了goto函数,记录了每个状态会跳转的下一状态。
然后定义FsmMonitor:
case class FsmState(monReg:Bool,curState:State,nextState:State)
case class FsmMonitor(implicit stateMachineAccessor: StateMachine ) extends Area{
val stateMonMap=Map[State,ArrayBuffer[FsmState]]()
val state_mon_clear=RegInit(False) simPublic()
def generateFsmMonitor()={
val current_state_dly=RegNext(stateMachineAccessor.stateReg)
val next_state_dly=RegNext(stateMachineAccessor.stateNext)
for(state<-stateMachineAccessor.states){
if(state.isInstanceOf[StateExtend]){
for (nextState <- state.asInstanceOf[StateExtend].nextStateBuffer){
val state_change=RegInit(False) setName (s"${state.getName()}_to_${nextState.getName()}_change")
when(state_mon_clear){
state_change.clear()
}elsewhen((current_state_dly===stateMachineAccessor.enumOf(state))){
when(next_state_dly===stateMachineAccessor.enumOf(nextState)){
state_change.set()
}
}
stateMonMap.getOrElse(state,ArrayBuffer[FsmState]()).append(FsmState(state_change,state,nextState))
}
}
}
}
}
在generateFsmMonitor中,会针对每个状态来分别创建跳转相应的跳转监控信号,并记录到stateMonMap中去。state_mon_clear可用于清零状态所有监控信号。通过regif可讲state_mon_clear及stateMonMap中的所有元素添加到寄存器总线中去(也可以直接用regif声明创建寄存器)。
最终,在使用时如下即可:
case class fsmTest() extends Component {
val counter = out(Reg(UInt(8 bits)) init (0))
val fsm = new StateMachine {
val stateA = new StateExtend() with EntryPoint setName("StateA")
val stateB, stateC = new StateExtend()
stateA.whenIsActive {
stateA.goto(stateB)
}
stateB.whenIsActive {
stateB.goto(stateC)
}
stateC.onEntry(counter := 0)
stateC.whenIsActive {
counter := counter + 1
when(counter === 3) {
stateC.goto(stateA)
}
}
val fsm_mon=FsmMonitor()
addPrePopTask(()=>{
fsm_mon.generateFsmMonitor()
})
}
}
差别点在于goto换成对应的StateA.goto等显示调用的形式。通过例化FsmMonitor调用generateFsmMonitor即可注册所有的状态跳转信号:
☆ END ☆
作者:玉骐
原文链接:Spinal FPGA
微信公众号:
推荐阅读
更多SpinalHDL技术干货请关注[Spinal FPGA]欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。