赞
踩
本文摘自《FPGA之道》,上两篇博文分别讲到了状态机的概念以及状态机的模型,可知状态机分为摩尔状态机以及mealy状态机,根据系统设计需求也可分为设计成1型以及2型等。本文一起看下FSM的具体设计需要考虑哪些内容以及如何设计状态机,一起看看作者想和我们分享的内容。
作为一名FPGA开发者,我们的工作可不仅仅是将别人画好的状态转移图翻译成HDL代码而已,更重要的是,我们要能够设计出满足项目需求的状态机。既然要设计状态机,那么参考【状态机的概念->状态机的组成六要素】小节,可知我们需要从六个方面入手,即状态集合、初态、终态、输入符号集、输出符号集、状态转移函数,其中除了输入符号集、输出符号集外,其余其实都是关于状态机的状态设计。 再结合【状态机的模型】小节的描述,可知输入符号集是和状态转移函数相关的、输出符号集是和状态集合相关的。因此,整个状态机的设计实际上就是状态的设计,那么本章节,就简要的为大家介绍一下状态的设计方法与思路。
要想设计状态,首先要明确什么是真正的状态。在最开始介绍状态机的时候,我们就说“每个FPGA开发者都有意或无意的、不可避免的使用着状态机”,到底这是为什么呢?我们有过这样的介绍——“如果数字电路满足任意时刻的输出仅仅取决于该时刻的输入,那么该数字电路为组合逻辑电路。相反,如果数字电路任意时刻的输出不仅取决于当前时刻的输入,而且还取决于数字电路原来的状态,那么该数字电路为时序逻辑电路。”由此可见,时序电路的概念其实就是状态机的概念,因此时序电路本身就是一个状态机,而组合逻辑其实也可以看做一个仅有一个状态的状态机。在【状态机的概念->状态机的组成六要素】小节中,提到过,“如果仅有一个状态节点的话,状态机便不存在状态跳转现象,因此它的性质和特点也就得不到体现”,所以接下来,我们将着重讨论时序逻辑,并从中引出关于状态的一些概念。
既然时序逻辑就是一个状态机,那么无论我们设计出来的时序逻辑有多么的复杂、多么的庞大,它都逃不出Mix型状态机的范畴。而从Mix型状态机的原理结构框图来看,时序逻辑中的记忆单元,除了用作输出寄存器之外的,剩下的全部都被用作状态寄存器。那么,假设一个时序逻辑中用到了20个触发器,若其中有10个是作为输出寄存器而存在,那么剩下的10个触发器必然是作为状态寄存器而存在。从逻辑规模上来看,该时序逻辑应该是一个非常、非常小规模的时序逻辑,因为它一共只用到了20个触发器而已,而现今最强大的FPGA芯片中所包含的触发器量级约为千万个左右。但是对于本例,尽管表示一个状态仅需要用到10个触发器,那么根据触发器只能保持逻辑1和逻辑0的特性,可知该状态机也要包含多达1024个状态——这便是时序逻辑中真正的状态个数。
由此可见,真正的状态,就是由时序逻辑状态机中状态寄存器所反映出的状态。如果状态寄存器由N个触发器组成,那么状态机就具有2^N个真正的状态。
在【真正的状态】小节中,我们看到,如此简单的一个时序逻辑,竟然就具有上千个真正的状态,那稍微复杂一些的时序逻辑,其状态个数岂不是早就已经超出人类处理的极限?看到这里,不知你是否心已凉了半截,对基于状态机的设计思路有些望而却步了呢?千万别慌,因为真正的状态从来都不是FPGA设计者所需要关注的,让我们继续分析上例:
综上所述,我们可以得到一个关于状态的等式:
真正的状态 = 抽象的状态 + 中间变量;
上述等式恰恰也反映出了一个问题,那就是为什么在【状态机的模型】章节中,所有的原理结构框图中都不存在输出到次态或者上次输出到下次输出的反馈。这是因为对于真正的状态来说,次态本身就是现态的一种完全反馈;而对于我们人脑设计出来的抽象状态来说,除了抽象状态本身,中间变量也是其必要的反馈因素。因此,采用抽象状态所设计出来的状态机,再配合中间变量的必要反馈,就能够极大的简化状态机的逻辑描述,让基于状态机的设计思路为FPGA开发者带来事半功倍的效果。
经过上一小节的分析,我们了解到状态的设计实际上主要就是指抽象状态的设计,由于人脑对复杂事物的抽象作用——化繁为简、共性提取等等——使得我们可以将一些复杂的时序逻辑归纳成十几个甚至几个抽象状态,这样一来,状态转移图便能比较轻松的得出,而以此为基础,便能够比较轻松的完成整个状态机的HDL编码。不过话说回来,以上的一切,都是以能够归纳出比较简洁且合理的抽象状态为前提的,那么本小节接下来的部分,就来简单探讨一下抽象状态的设计方法。注意,为了简便起见,本书以后提到的状态机的“状态”如无特别说明,皆指“抽象的状态”,而非“真正的状态”。
时序逻辑中,真正的状态的状态集合非常之大,且划分的太细,不利于HDL的描述,因此我们通常都是利用抽象的状态来创建状态机。在上一小节中,给出如下等式:
真正的状态 = 抽象的状态 + 中间变量;
上式其实还反映出一个事实,那就是抽象的状态与中间变量之间是可以相互转化的。比方说,如果希望状态机从某一时刻开始,等待N+1个时钟周期后,才开始下一步的工作,那么可以有三种设计方法:
针对上例,当N值很小的时候,采用方法一会令状态机的描述更加简单;当N值较大时,采用方法二会令状态机的描述更为简单;当N值非常大的时候,中间变量的累加会消耗较多的时间,因此采用方法三会让状态机的性能达到最优。
由此可见,若抽象的状态的状态集合越小,状态机的结构描述就越容易,但由于中间变量变多,则会导致每一个状态所处理的事务过于复杂;反之,中间变量越少,每个状态所需处理的功能就越简单,但由于抽象的状态的状态集合变大,则会导致状态机的结构描述过于复杂。因此,我们必须在状态个数和变量个数之间需求一个平衡点,以使得整个状态机的描述较为容易。
这一个平衡点是必然存在的,但是要想找到它,可不像数学中求抛物线的极值点那样容易,因此我们退而求其次,不去寻找这一平衡点,而是去寻找一个接近该平衡点的次优方案。通常来说,跟整个状态机的工作都相关的那些状态寄存器多被抽象为抽象的状态,而那些仅跟状态机部分或局部工作相关的状态寄存器多被抽象为中间变量。上述思路虽然给出了抽象状态和中间变量的一种简单的划分方式,但是它其实一无是处,因为它其实是从时序电路中提取出状态机的描述,而我们需要做的工作则恰恰相反,是从实际问题出发提取出状态机的描述,进而再由编译器翻译成时序电路。考虑到抽象状态与中间变量之间存在着互补的转换关系,因此在进行状态机的抽象时,我们并没有必要去刻意找出中间变量,只要能够设计出合情合理的抽象状态,中间变量、输入、输出等问题也就会迎刃而解,整个状态机的设计也自然会水到渠成。那么接下来的几个小节,就让我们来仔细讨论一下状态的设计思路吧。
根据时序逻辑功能的不同以及抽象方法的不同,状态机中的状态也都形态各异,但是,通过观察这些五花八门的状态,可以发现它们都是由以下这些基本状态中的一种或几种组成,因此,在介绍如何设计状态之前,我们还是先来了解一下这些基本状态吧。
初始态,即状态机的初态,它是指FPGA上电后,该部分时序逻辑所应该进入的状态。每一个状态机都必须具有一个初始状态,因为状态机接下来的工作,都是以初始状态为基础进行的。初始态不仅仅指明了状态机的工作起点,它还肩负着整个状态机的初始化工作,即设定好中间变量以及输出的初值。
结束态,即状态机的终态,它是指状态机工作完成后,该部分时序逻辑所应该进入的状态,注意,一旦进入该状态后,状态机将不会再发生状态跳转。如果有些事情在FPGA上电后只需要做一次或有限次,那么该状态机所处理的事务就是有限的,此时状态机就需要有一个结束态来表明其工作任务的完成。不过大部分FPGA中的状态机所处理的事务都是无限的,因此结束态对于它们来说都是不必要的。最后,结束态不仅仅指明了状态机的工作终点,它还肩负着整个状态机的善后工作,即设定好中间变量以及输出的终值。
状态机中,除了初始态和结束态以外,其余的状态都可以称之为中继态。中继态在状态机的工作中起着承上启下的作用,正是它们组成了从初始态到结束态(如果有的话)的通路。
如果一个状态的次态可以有多种不同的可能,那么该状态就是一个分支态。正是由于分支态的作用,使得当外界输入或者中间变量不同时,状态机便会沿着状态图中不同的路径来进行工作,从而即使状态机的状态数量相对较少,也能对外界呈现出千变万化的效果。
复位态,是指在某些情况下,系统打算放弃当前的操作,并重新开始一次新的操作时,所需要进入的状态。由此可见,复位态也兼具指明状态机“新的”工作起点,同时对整个状态机进行重新初始化的工作,因此,虽然初始态和复位态的意义并不相同,但通常情况下,初始态也即是复位态。
空闲态也是一类比较常见的状态,当处于该状态时,状态机一般是不完成任何具体工作的,正如这类状态的名字一样,状态机此时是空闲的。那么到底状态机何时才能开始一件新的事务呢?这个谁都说不准,因为要想跳出空闲状态,状态机的外界输入和中间变量必须满足一定的条件,只有当条件满足时,状态机才会被触发,从而跳转到一个正常的工作状态中。这也就是说,状态机在空闲状态下仅完成对触发条件的等待与监听,这就好比处理器的中断机制一样,当没有中断发生时,处理器按照预先设定好的程式工作,一旦发现中断,则立马跳转到中断程式工作。由此可见,对于一些无法确定、难以预期的处理需求,通常都必须采用空闲状态。
多余态,即真实的状态中的不可达状态,由于状态机从设计角度来说,并不会工作在这些状态下,因此这些状态也称为多余态。按理说,状态机的状态转移图中并不存在这些多余态,因此从理论上来说,状态机不可能会跳转至任何一个多余态。但是,现实世界中总是存在着各种各样的噪声和干扰(FPGA芯片工作的环境越极端,这种影响就越明显,例如卫星环境下的单粒子翻转效应),再加上我们编写的代码也总是或多或少的存在着一些疏漏,都会导致状态机的跳转发生非预期的情况,此时如果我们不对这些多余状态进行处理,状态机将会进入假死状态,因为我们并没有告诉时序逻辑该如何从多余态中进行自我恢复。综上所述,对于那些不可达态数目不等于零的状态机,我们最好将所有的不可达态抽象为一个多余态,并添加相应的处理机制,以防万一。千万注意,状态机中是否存在着不可达态,只能通过真实的状态数和状态寄存器的个数来判断,因为即使不考虑中间变量的影响,假设状态机的抽象状态数量正好等于2的整数次幂,但是由于编译器的优化作用以及抽象状态的编码方式,也会导致抽象状态中存在不可达态(例如,若采用one-hot编码方式,状态机必然存在非常多的不可达态,具体原因可参考【状态的编码方式】章节)。因此,从HDL代码的角度来判断状态机中是否存在着不可达态是很难的,所以一个比较实用且非常推荐的方法就是对每一个状态机都添加一个多余态,从而达到“有则改之无则加勉”的效果。
将待解决的实际问题抽象成状态机是一件非常灵活的事情,经过不断的摸索、尝试和总结,每个人都会形成自己的风格。不过灵活归灵活,在进行状态抽象的时候,还是有一些基本原则可以参考的,介绍如下:
流程其实就是多个事务的有序组合。要完成一件事情,通常可以将它分成多个事务,即按照“先做什么,再做什么,……,最后做什么”的思路来将一个较为复杂的事情拆分成一系列比较简单事务,从而化一个复杂的问题为多个简单的问题,逐个解决即可完成任务。例如,要想为家人做上一顿可口的饭菜,按照流程的思路,大致如此——先要解决买菜的问题,然后是洗菜、切菜,接下来是炒菜、做主食,最后是盛菜、盛饭。这中按照流程来划分出多个事务的思想在人类日常的生活和生产中经常可以看到,因此,在进行状态机状态的抽象时,如果能够将时序电路所需完成的任务按照流程的思想分解开来,那么流程中的每一个事务就可以做为一个状态。
功能其实就是多个事务的无序组合。要想准确的描述一个事物,通常可以将这个事物按照其功能分成多个事务,即按照“可以做什么,也可以做什么,……,还可以做什么”的思路来将一个较为复杂的事物拆分成一系列事务,从而化一个复杂的事物为多个简单功能事务的集合,逐个介绍即可完成对整个事物的准确描述。例如,要想向别人炫耀一下新买的手机,大致如此——“你看,咱这个手机不光可以听音乐、看视频、收广播,还可以上网、拍照、玩游戏,倍有面子”等等。这种按照功能来划分出多个事务的思想在人类日常的生活和生产中也经常可以看到,因此,在进行状态机状态的抽象时,如果能够将时序电路所应具有的特征按照功能的思想分解开来,那么功能集合中的每一个事务就可以做为一个状态。
基于流程的思想侧重于从“如何做”的角度来解决问题,而基于功能的思想则侧重于从“做哪些”的角度来解决问题,这两种思想各有所长、各有侧重,需要针对具体的状态抽象问题来综合考虑。
不过无论是基于流程还是基于功能的状态抽象思想,其对于原始问题分解的粗细程度都是非常主观的,因此最后,我们还需要在上述抽象后的状态基础上,进一步分析每个状态的复杂程度。如果发现某些状态过于复杂,那么将再次按照基于流程或者基于功能的思想继续对其进行分解;反之,如果发现某些相临的状态均过于简单,也可以对它们进行合并。例如,你本来计划晚上花一个小时做语文作业、一个小时做数学作业、一个小时做英语作业,可是今天数学老师发了两套真题作为作业,而语文老师和英语老师都仅留了几道简单的问答题作为作业,那么你显然需要调整你的计划,调整后的情况大致如此——花一个小时的时间做完文科的作业,花一个小时的时间做完数学真题A套,再花一个小时的时间做完数学真题B套。
由此可见,按照复杂度的状态抽象是对前两种状态抽象思路很好的补充。
虽然采用“抽象状态+中间变量”的思路来设计状态机,能够极大的简化状态机的结构,让时序逻辑的描述变得更加简洁、清晰和容易,但是,随着待解决问题的复杂度和内容不断增加,抽象状态的集合也会变得非常庞大,况且,当问题复杂到一定程度,人脑已经无法从中抽象出合理的状态集合了。更为不幸的是,通常FPGA开发者所需解决的时序逻辑问题,往往都很难将其抽象成一个孤立的状态机,因为需要解决的问题往往都没那么简单。那么,之前我们花了那么多篇幅来介绍状态机的设计方法是否全都白费力气了呢?显然不是。本小节就来讨论一下,在设计复杂时序逻辑时,该如何利用状态机的设计思想。
“团结起来力量大”,“一个人做不了的事情也许十个人就能完成”,没错,当我们面对复杂问题的时候,完全可以通过使用多个状态机协同工作的方式来解决,即设计状态机群,千万不要总想着用一个状态机就将其搞定。当然了,从本质上来说,无论多么复杂的时序逻辑,只要它只包含一个时钟域,那么按照时序逻辑的定义来看,它其实都相当于一个状态机,但是由于人脑的能力有限,所以我们必须将其拆分成多个子部分,并分别进行状态机的抽象与描述。没错,这其实就是【开发流程篇->FPGA功能代码的编写】章节中提到的层次化、分模块的编程思路。那么,按照多级状态机的结构特点,可从中总结出五类最基本的状态机群结构,即并联式状态机群、串联式状态机群、串行式状态机群、嵌套式状态机群、总分式状态机群,下面将分别进行介绍。
使用单个状态机最大的弊端就是任一时刻,状态机只能处于一个状态,因此各个状态对应的动作都是逐个发生的。如果同时有两件事情需要做,那么解决方法只有一个,就是将这两件事对应的处理状态合并在一起了,但是这样做会增加单个状态的复杂度,导致描述困难;如果这两件事情开始的时间和结束的时间都不相关,那么状态设计的难度就更加大了;如果类似这样待处理的事情不止两件,那么人脑几乎就无法仅用一个状态机来对其进行抽象;如果这些事情又不属于同一个时钟域,那么电脑也无法仅用一个状态机来实现它了,因为它就不可能仅用一个状态机来解决。
由此可见,与其费尽心思地在单个状态机的设计思路中钻牛角尖,不如换个思路,既然这些事情之间没有什么相关性,那么只需要将每一件需要处理的事情抽象成一个状态机即可,这样一来,每个状态机的结构都会比较简单,整个复杂时序逻辑问题也便迎刃而解。
综上所述,凡是将一个复杂的时序问题拆分成多个独立的子时序问题,进而采用多个状态机分别解决的抽象结构,就叫做并联式状态机群。如下即为并联式状态机群的原理图:
如果需要做的多件事情不光可能并发,而且彼此之间还有着明显的传承关系,即数据传递,那么此时就应该用到串联式状态机群结构。注意,所谓串联式状态机群,并不是指前一级状态机的工作结束后才开始下一级状态机的工作,它内部的所有状态机仍然是同时工作,这点与并联式状态机群是相同的,不同之处仅仅在于串联式状态机之间存在着数据传递。由于数据传递对状态机行为的影响并不是直接的,因此从功能上来看,各级状态机之间仍是独立的。如下即为串联式状态机群的原理图:
如果需要做的多件事情之间有着明显的先后关系且不能并发,那么这正好符合单个状态机的状态变迁规律,但是若每件事情本身又都比较复杂,若采用一个状态机便难以对所有的事情完成抽象。那么此时,可以为每一件事情分配一个状态机,只不过需要在各个状态机之间传递控制信号,即前一级状态机在工作结束后才会向下一级状态机发送可以开始工作的控制信号,这便是串行式状态机群。
由此可见,“串行”与“串联”仅一字之差,但一个是顺序工作、一个却是并发工作,一个状态机彼此相关、一个状态机彼此独立,因此有着本质的区别,一定要注意区分。不过这里需要说明一点,FPGA内部所有的电路、逻辑全部都是并行、并发工作的,之所以状态机群有着“串行”的概念,是因为状态机中可以有“空闲态”,只要保证跳转条件不被触发,状态机便会一直工作于“空闲态”,从而给人以状态机没有工作的感觉。如下即为串行式状态机群的原理图,可见其通常也伴有数据的传递:
如果需要做的多件事情之间虽然不会并发,但却没有确定的先后关系,那么其实这也符合单个状态机的状态变迁规律,但是当每件事情本身又都比较复杂时,仍然无法采用一个状态机来对其进行抽象。不过此时,串行式状态机群结构就无法解决问题,因为这些事情之间是没有确定的先后关系的。所以,为了解决当前的问题,就需要用到另一种状态机群结构——嵌套式状态机群。
嵌套式状态机群的思路是:先从每一件事情抽象出一个独立的子状态机,最后再编写一个更高一层级的主状态机,每个子状态机都对应主状态机中的一个状态,子状态机完成具体的工作,主状态机的状态仅完成子状态机的调用和监控工作,注意,如果只调用、不监控的话,最好在主状态机和子状态机之间添加控制信号等的缓冲区,否则子状态机可能会漏掉一些响应。例如,我们需要编写一个SDRAM的控制器,SDRAM的常用操作有初始化、读、写、刷新等,那么,我们可以编写四个子状态机分别对应完成SDRAM的初始化、读、写、刷新工作,然后再编写一个主状态机,状态集合包括——初始化、读、写、刷新这四个状态,当主状态机跳转至这四个状态之一时,便向子状态机发送相应的开始触发信号,并监听子状态机反馈的任务完成信号,以保证在当前操作完成后,才能开始下一个操作。由此可见,嵌入式状态机就相当于将原主状态机每一个状态该做的事情用子状态机来实现,所以子状态机好像是嵌入到相应主状态机的状态中,故得此名。通过上面的分析可知,嵌入式状态机群的各个子状态机之间也不是独立的,如下即为嵌入式状态机群的原理图:
如果需要做的多件事情之间不仅没有确定的先后关系,而且还可能有一次性触发多件事情的情况,那么就需要用到总分式状态机群结构了。与嵌入式状态机群的思想类似,只不过总分式状态机群中,主状态机的一个状态可能会触发及监控多个子状态机进行工作罢了。由于子状态机的工作仍受制于主状态机,因此它们之间仍不是完全独立的,如下即为总分式状态机群的原理图:
时序逻辑的设计其实就是状态机群的设计,上述五种基本的状态机群结构在实际中可以非常灵活的结合运用,因此如果要去观察一些较为复杂的时序逻辑对应的状态机群关系,可以发现以上五种基本机群均可以变化为“你中有我,我中有你”,而且根据状态机将一个信号当成数据来看还是控制信号来看,甚至也会在不改变功能的情况下让这些基本机群“你变成我,我变成你”,所以状态机群的设计非常灵活,不易把握。不过通常来说,比较推荐的状态机群设计原则如下:
第一步:结合“并联”、“串联”的状态机群思路,将复杂的时序逻辑问题划分为多个独立的子问题。对于每个子问题,如果其仍过于复杂,那么继续采用这种思路对其进行划分,每划分一次,状态机群树状结构的深度便向下增加一层。通常来说,这一步最后的状态机群树状结构以不超过3层或4层为宜。该步其实就是著名的“模块化设计思想”,它的好处有很多,诸如便于仿真、便于问题定位、便于功能重用等等,更为详细的说明参见【本篇->编程思路->设计方法学讨论】相关章节。
第二步:对于状态机群树状结构中的那些叶子节点,如果其功能已经足够简单,那么就直接使用单个状态机完成其功能抽象;否则,结合“串行”、“嵌套”、“总分”的状态机群思路,完成其功能抽象。当采用“嵌套”或“总分”的思路时,子状态机最好是不需要再拆分成状态机群结构,如果子状态机真的太过复杂,那么更大的可能性是你在第一步的时候,状态机群的划分有些不太合理。
值得一提的是,其实状态机群的设计并不是在需要着手编写代码的时候才开始的,其实早在FPGA项目方案的设计阶段,这项工作就已经开始。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。