1、第 6 章 如何写好状态机 节选自 Verilog 设计与验证 作者:吴继华、王诚 状态机是逻辑设计的重要内容,状态机的设计水平直接反应工程师的逻辑功底,所以许多公司的硬件和逻辑工程师面试中,状态机设计几乎是必选题目。本章在引入状态机设计思想的基础上,重点讨论如何写好状态机。 本章主要内容如下: 状态机的基本概念; 如何写好状态机; 使用 Synplify Pro 分析 FSM。 6.1 状态机的基本概念 本节的重点在于帮助读者理解状态机的基本概念和应用场合。 6.1.1 状态机是一种思想方法 相信大多数工科学生在学习数字电路时都学习过状态机 的基本概念,了解一些使用状态机描述时序电路的基本方
2、法。但是,笔者希望大家能扩展思维,认识到状态机不仅仅是一种时序电路设计工具,它更是一种思想方法。 我们先看下面一个简单的例子。在大学生活中,某学生的在校的学习生活可以简单地概括为宿舍、教室、食堂之间的周而复始,用图 6-1 就可以形象地表现出来。这里画这张图,并不是要讨论这个学生是否是一个“乖乖”类型学生,请大家注意,如果将图中的“地点”认为是“状态”,将“功能”认为是状态的“输出”,这张图就是一张标准的状态转移图,也就是说,我们用状态机的方式清晰地描述了这个学 生的在校生活方式。 第 6 章 如何写好状态机 138 图 6-1 某学生在校生活状态转移图 如果读者认为这张图描述的学生生活过于单
3、调而怀疑状态机描述方法的威力,我们再看看另一位生活丰富多彩的学生的在校生活,他(她)的在校生活方式可以用图 6-2 表示。 图 6-2 另一位学生在校生活状态转移图 同样如果将图中的“地点”认为是“状态”,将“功能”认为是状态的“输出”,将“条件”认为是状态转移的“输入条件”,图 6-2 也是一张标准的状态转移图,通过状态机的方式我们再次清晰地描述另一个学生的在校生活方式。 事实上使用状态机方式,我们可以细致入微地描述任何一个学生的在校生活方式。大家通过前面两个简单举例已经发现状态机特别适合描述那些有发生有先后顺序,或者有逻辑规律的事情 其实这就是状态机的本质。状态机的本质就是对具有 逻辑顺序
4、 或 时序规律 事件的一种描述方法。这个论断的最重要的两个词就是“逻辑顺序”和“时序规律”,这两点状态机的基本概念 139 就是状态机所要描述的核心和强项,换言之,所有具有逻辑顺序和时序规律的事情都适合用状态机描述。 很多初学者不知道何时应用状态机。这里介绍两种应用思路:第 一种思路,从状态变量入手。如果一个电路具有时序规律或者逻辑顺序,我们就可以自然而然地规划出状态,从这些状态入手,分析每个状态的输入,状态转移和输出,从而完成电路功能;第二种思路是首先明确电路的输出的关系,这些输出相当于状态的输出,回溯规划每个状态,和状态转移条件与状态输入。无论那种思路,使用状态机的目的都是要控制某部分电路
5、,完成某种具有逻辑顺序或时序规律的电路设计。 其实对于逻辑电路而言,小到一个简单的时序逻辑,大到复杂的微处理器,都适合用状态机方法进行描述。请读者打开思路,不要仅仅局限于时序逻辑,发现电路 的内在规律,确认电路的“状态变量”,大胆使用状态机描述电路模型。由于状态机不仅仅是一种电路描述工具,它更是一种思想方法,而且状态机的 HDL 语言表达方式比较规范,有章可循,所以很多有经验的设计者习惯用状态机思想进行逻辑设计,对各种复杂设计都套用状态机的设计理念,从而提高设计的效率和稳定性。 6.1.2 状态机基本要素与分类 状态机的基本要素有 3 个,其实我们在第一节的举例中都有涉及,只是没有点明,它们是
6、:状态、输出和输入。 状态:也叫状态变量。在逻辑设计中,使用状态划分逻辑顺序和时序规律。比如:设计伪随机码发生器时,可以用 移位寄存器序列作为状态;在设计电机控制电路时,可以以电机的不同转速作为状态;在设计通信系统时,可以用信令的状态作为状态变量等。 输出:输出指在某一个状态时特定发生的事件。如设计电机控制电路中,如果电机转速过高,则输出为转速过高报警,也可以伴随减速指令或降温措施等。 输入:指状态机中进入每个状态的条件,有的状态机没有输入条件,其中的状态转移较为简单,有的状态机有输入条件,当某个输入条件存在时才能转移到相应的状态。 根据状态机的输出是否与输入条件相关,可将状态机分为两大类:摩
7、尔( Moore)型状态机和米勒( Mealy)型状态机。 摩尔状态机:摩尔状态机的输出仅仅依赖于当前状态,而与输入条件无关。例如图 6-1 所示的例子,将图中的“地点”认为是“状态”,将“功能”认为是状态的“输出”,则每个输出仅仅与状态相关,所以它是一个摩尔型状态机。 米勒型状态机:米勒型状态机的输出不仅依赖于当前状态,而且取决于该状态的输入条件。例如图 6-2 所示的例子,将图中的“地点”认为是“状态”,将“功能”认为是状态的“输出”,将“条件”认为是状态转移的“输入条件”,大家可以发现,该学生到达什么地方,做什么事情都是由当前状态和输第 6 章 如何写好状态机 140 入条件共同决定 ,
8、所以它是一个米勒型状态机。 根据状态机的数量是否为有限个,可将状态机分为有限状态机( Finite State Machine,FSM)和无限状态机( Infinite State Machine, ISM)。逻辑设计中一般所涉及的状态都是有限的,所以以后我们所说的状态机都指有限状态机,用 FSM 表示。 6.1.3 状态机的基本描述方式 逻辑设计中,状态机的基本描述方式有 3 种,分别是:状态转移图,状态转移列表,HDL 语言描述。 状态转移图 状态转移图是状态机描述的最自然的方式。如本章第一节图 6-1, 6-2 都使用了状态转移图这 一描述方式。状态转移图经常在设计规划阶段定义逻辑功能时
9、使用,也可以在分析代码中状态机时使用,通过图形化的方式非常有助于理解设计意图。 另外值得一提的是目前有一些 EDA 工具支持状态转移图作为逻辑设计的输入,例如在 StateCAD。在该工具中设计者只要画出状态转移图就可以了,StateCAD 能自动将状态转移图翻译成 HDL 语言代码,而且翻译出来的代码规范、可读性较好、可综合、易维护。 StateCAD 还能能自动检测状态机的完备性和正确性,对状态转移图中的冗余状态、自锁状态、歧义转移条件和不完备状态机等隐含错误都会报 警,并协助设计者更正错误。最后 StateCAD 会自动生成设计的测试激励,并调用仿真程序,验证状态机的正确性,这个测试激励
10、甚至可在后仿真中使用。总之, StateCAD 提供了状态机的输入、翻译、检测、优化和测试等一条龙的服务,使状态机的设计变得安全、可靠、快速、便捷。这类自动转换状态转移图为 HDL 源代码的工具对设计、分析一些规模较小的状态机非常有效,但是由于自动反应的代码过于程式化,效率不是最高,所以对于较大规模的逻辑设计,一般还是推荐使用 HDL 语言之间描述。 使用 Synplify Pro 的 RTL 视图配合 FSM Viewer 可以将源代码中描述的 FSM 用状态转移图显示出来,使用图形化的界面帮助用户分析理解状态机。关于使用 FSM Viewer 分析状态机的方法在本章 6.3 节有详细介绍。
11、 状态转移列表 状态转移列表是用列表的方式描述状态机,是数字逻辑电路常用的设计方法之一,经常被用于对状态化简,对于可编程逻辑设计,由于可用逻辑资源比较丰富,而且状态编码要考虑设计的稳定性,安全性等因素,所以并不经常使用状态转移列表优化状态。 HDL 语言描述状态机 使用 HDL 语言描述状态机是本章讨论的重点,使用 HDL 语言描述状态机如何写好状态机 141 有一 定的灵活性,但是决不是天马行空,而是有章可循的。通过一些规范的描述方法,可以使 HDL 语言描述的状态机更安全、稳定、高效、易于维护。 6.2 如何写好状态机 本节重点讨论可综合的状态机描述的一些基本规范,即如何在 RTL 级描述
12、安全、高效的 FSM。 6.2.1 什么是 RTL 级好的 FSM 描述 首先介绍好的 RTL 级 FSM 的评判标准。其实评判 FSM 的标准很多,这里我们拣选最重要的几个方面讨论一下。好的 RTL 级 FSM 的评判标准如下: FSM 要安全,稳定性高。 所谓 FSM 安全是指 FSM 不会进入死循环,特别是不会进入非预知的状态,而且由于某些扰 动进入非设计状态,也能很快的恢复到正常的状态循环中来。这里面有两层含义,第一:要求该 FSM 的综合实现结果无毛刺等异常扰动;第二:要求状态机要完备,即使收到异常扰动进入非设计状态,也能很快恢复到正常状态。 FSM 速度快,满足设计的频率要求。 任
13、何 RTL 设计都应该满足设计的频率要求。 FSM 面积小,满足设计的面积要求。 同理任何 RTL 设计都应该满足设计的面积要求。 FSM 设计要清晰易懂、易维护。 不规范的 FSM 写法很难让其他人解读,甚至过一段时间后设计者也发现很难维护。 需要说明的是以上所列的各项标准,特别是前 3 项标准绝不是割裂的,它们直接有紧密的内在联系。如果读者读过本工作室的其他书籍,应该记得其中花了相当长的篇幅论述FPGA/CPLD 设计评判的两个基本标准:面积和速度。这里“面积”是指一个设计所消耗FPGA/CPLD 的逻辑资源数量;“速度”指设计在芯片上稳定运行所能够达到的最高频率。两者是对立统一的矛盾体,
14、要求一个设计同时具备设计面积最小,运行频率最高,这是不现实的。科学的设计目标应该是:在满足设计时序要求(包含对设计最高频率的要求)的前提下,占用最小的芯片面积,或者在所规定的面积下,使设计的时序余量更大,频 率更高。 另外,如果要求 FSM 安全,则很多时候需要使用“ full case”的编码方式,即将状态转移变量的所有向量组合情况都在 FSM 中有相应的处理,这经常势必意味着要多花更多的设计资源,有时也会影响 FSM 的频率。 所以,各条标准要综合考虑,根据设计的要求进行权衡。但是如果各条评判标准发生冲突时,请按照标准的罗列顺序考虑,前文标准的罗列顺序是根据这些标准在设计中的重要性排列的,
15、也就是说第一条“ FSM 要安全,稳定性高”的优先级最高,最重要;第四条第 6 章 如何写好状态机 142 “ FSM 设计要清晰易懂、易维护”的优先级最低,是相对次要的标准。 6.2.2 RTL 级状态机描述常用语法 本书第 2、 3 章论述了 Verilog 的基本语法和常用关键字,其中在 RTL 级设计可综合的FSM 相关的常用关键字如下: wire 、 reg等 对 wire 、 reg 等变量、向量定义不加累述,需要补充的是状态编码时(也就是用某种编码描述各个状态)一般都要使用 reg 寄存器型向量。 parameter 用于描述状态名称,增强源代码可读性,简化描述。 例: 某状态机
16、使用初始值为“ 0”的独热码( one-hot)编码方式定义的 4bit 宽度的状态变量 NS(代表 Next State,下一状态)和 CS(代表 Current State,当前状态),且状态机包含 5 个具体状态 IDLE(空闲状态)、 S1(工作状态 1)、 S2(工作状态 2)、 S3(工作状态 3)、 ERROR(告警状态),则代码如下: reg 3:0 NS,CS; parameter 3:0 /one hot with zero initial IDLE = 3b0000, S1 = 3b0001, S2 = 3b0010, S3 = 3b0100, ERROR = 3b100
17、0; always 在 FSM 设计中有 3 种 always 的使用方法,第 1 种用法是根据主时钟沿,完成同步时序的状态迁移。 例:某状态机从当前状态 CS 迁移到下一个状态 NS 可以如下表述: /sequential state transition always (posedge clk or negedge nrst) if (!nrst) CS = IDLE; else CS =NS; always 的第 2 种用法是根据信号敏感表,完成组合逻辑的输出。 always 的第 3 种用法是根据时钟沿,完成同步时序逻辑的输出。 case/endcase case/endcase 是
18、FSM 描述中最重要的语法关键字,这里我们要详细讨论一下。 case/endcase 的基本语法结构如下: case (case_expression) 如何写好状态机 143 case_item1 : case_item_statement1; case_item2 : case_item_statement2; case_item3 : case_item_statement3; case_item4 : case_item_statement4; default : case_item_statement5; endcase 其中, case_expression 就是 case 的判断
19、条件表达式,在 FSM 描述中,它一般为当前状态寄存器;每个 case_item 是 case 语句的分支列表,在 FSM 描述中,它一般为 FSM 中的所有状态的罗列,从中还可以分析出状态的编码方式; case_item_statement 为进入每个 case_item 的对应操作,在 FSM 中,即为每个状态对应的 状态转移或者输出,如果 case_item_statement 包含的操作不只一条,可以用 begin/end 嵌套多条操作; default 是个可选的关键字,用以指明当所列的所有 case_item 与 case_expression 都不匹配时的操作,在 FSM 设计中
20、,为了提高设计的安全性,排除所设计的 FSM 进入死循环,一般要求加上default 关键字来描述 FSM 所需状态的补集状态下的操作。另外 Verilog 还支持 casex 和 casez 等不同关键字,但是由于综合器对这两个关键字的支持情况略有差异,所以笔者建议初学者使用完整 的 case 结构而不使用 casex 或casez。 例 :某 FSM 的状态转移用 case/endcase 结构描述如下: case (CS) IDLE: begin IDLE_out; if (i1) NS = IDLE; if (i1 if (i1 end S1: begin S1_out; if (i2
21、) NS = S1; if (i2 if (i2 end S2: begin S2_out; if (i2) NS = S2; if (i2 if (i2 end ERROR: begin 第 6 章 如何写好状态机 144 ERROR_out; if (i1) NS = ERROR; if (i1) NS = IDLE; end default: begin Default_out; NS = ERROR; end endcase Verilog 的 case 结构虽然与 C 等高级语言的 case 结构虽然形式相似,但是本质不同。Verilog 的 case 结构对应并行判断的硬件结构,而
22、且当 case_expression 与任意一个case_item 匹配后,将忽略对其它 case_item 的判断,执行完匹配的 case_item_statement 后直接跳出 case 结构 。 task/endtask task/endtask 在描述状态机是主要用途是将不同状态对应的输出用task/endtask 封装,增强了代码的可维护性和可读性。 例: 某状态机的 IDLE 状态的输出可以用 task/endtask 封装为“ IDEL_out”任务: task IDLE_out; begin w_o1,w_o2,w_err = 3b000; end endtask 当然描述状
23、态机时也 会使用到其它一些常用的 RTL 级语法,如 if/else, assign等等,它们的功能和一般 RTL 描述方法一致,这里不在叙述。 6.2.3 推荐的状态机描述方法 状态机描述时关键是要描述清楚前面提到的几个状态机的要素,即如何进行状态转移;每个状态的输出是什么;状态转移是否和输入条件相关等。具体描述时方法各种各样,有的设计者习惯将整个状态机写到 1 个 always 模块里面,在该模块中即描述状态转移,又描述状态的输入和输出,这种写法一般被称为一段式 FSM 描述方法;还有一种写法是将用 2 个always 模块,其中一个 always 模块采用 同步时序描述状态转移;另一个模
24、块采用组合逻辑判断状态转移条件,描述状态转移规律,这种写法被称为两段式 FSM 描述方法;还有一种写法是在两段式描述方法基础上发展出来的,这种写法使用 3 个 always 模块,一个 always模块采用同步时序描述状态转移;第二个采用组合逻辑判断状态转移条件,描述状态转移规律;第三个 always 模块使用同步时序电路描述每个状态的输出,这种写法本书称为三段式写法。 如何写好状态机 145 一般而言,推荐的 FSM 描述方法是后两种,即两段式和三段式 FSM 描述方法。其原因为: FSM 和其他设计一样,最好使用同步时序方式设 计,以提高设计的稳定性,消除毛刺。状态机实现后,一般来说,状态
25、转移部分是同步时序电路而状态的转移条件的判断是组合逻辑。两段式之所以比一段式编码合理,就在于两段式编码将同步时序和组合逻辑分别放到不同的 always 程序块中实现。这样做的好处不仅仅是便于阅读、理解、维护,更重要的是利于综合器优化代码,利于用户添加合适的时序约束条件,利于布局布线器实现设计。而一段式 FSM 描述不利于时序约束、功能更改、调试等,而且不能很好的表示米勒 FSM 的输出,容易写出 Latches,导致逻辑功能错误。 在一般两段式描述中,为了便于 描述当前状态的输出,很多设计者习惯将当前状态的输出用组合逻辑实现。但是这种组合逻辑仍然有产生毛刺的可能性,而且不利于约束,不利于综合器
26、和布局布线器实现高性能的设计。因此如果设计运行额外的一个时钟节拍的插入( latency),则要求尽量对状态机的输出用寄存器寄存一拍。但是很多实际情况不允许插入一个寄存节拍,此时则可以通过三段式描述方法进行解决。三段式与两段式相比,关键在于根据状态转移规律,在上一状态根据输入条件判断出当前状态的输出,从而在不插入额外时钟节拍的前提下,实现了寄存器输出。 为了便于理解,我们通过一 个实例讨论这三种不同的写法。 【例 1-1】 使用不同的 FSM 描述风格描述状态机,参考示例详见本书附带光盘的“ Example-6-1” 目录。 在这个范例中我们将用一段式、两段式、三段式分别描述图 6-3 所示的
27、状态机。这里我们选用了一个非常典型的米勒型状态机,共有 4 个状态: IDEL, S1, S2, ERROR;输入信号为时钟 clk,低电平异步复位信号 nrst,输入信号 i1, i2,输出信号为 o1, o2 和 err,状态关系如图 6-2 所示。状态的输出如下: IDLE 状态的输出为: o1,o2,err = 3b000; S1 状态的输出为: o1,o2,err = 3b100; S2 状态的输出为: o1,o2,err = 3b010; ERROR 状态的输出为: o1,o2,err = 3b111。 第 6 章 如何写好状态机 146 图 6-3 例子的状态转移图 6.2.3.
28、1 一段式状态机描述方法(应该避免的写法) 该例的一段式描述代码如下: /1-paragraph method to describe FSM /Describe state transition, state output, input condition in 1 always block module state1 ( nrst,clk, i1,i2, o1,o2, err ); input nrst,clk; input i1,i2; output o1,o2,err; reg o1,o2,err; reg 2:0 NS; /NextState parameter 2:0 /one h
29、ot with zero idle IDLE = 3b000, S1 = 3b001, S2 = 3b010, ERROR = 3b100; /1 always block to describe state transition, state output, input condition 如何写好状态机 147 always (posedge clk or negedge nrst) if (!nrst) begin NS = IDLE; o1,o2,err = 3b000; end else begin NS = 3bx; o1,o2,err = 3b000; case (NS) IDL
30、E: begin if (i1) begino1,o2,err=3b000;NS = IDLE; end if (i1 NS = S1; end if (i1 NS = ERROR;end end S1: begin if (i2) begino1,o2,err=3b100;NS = S1; end if (i2 NS = S2; end if (i2 NS = ERROR;end end S2: begin if (i2) begino1,o2,err=3b010;NS = S2; end if (i2 NS = IDLE; end if (i2 NS = ERROR;end end ERR
31、OR: begin if (i1) begino1,o2,err=3b111;NS = ERROR;end if (i1) begino1,o2,err=3b000;NS = IDLE; end end endcase end endmodule 如前面介绍,一段式写法就是将状态的同步转移,状态输出和状态的输入条件都写在一个 always 模块中,一段式写法可以概括为图 6-4 描述的结构。 第 6 章 如何写好状态机 148 图 6-4 一段式 FSM 描述结构图 一段式描述方法将状态转移判断的组合逻辑和状态寄存器转移的时序逻辑混写在同一个always 模块中,不符合将时序和组合逻辑分开描述
32、的 Coding Style(代码风格),而且在描述当前状态时要考虑下个状态的输出,整个代码不清晰,不利于维护修改,并且不利于附加约束,不利于综合器和布局布线器对设计的优化。 另外,这种描述相对于两段式描述比较冗长。本例为了便于初学者掌握,选择了一个非常简单的米勒型状态机,不能很好的反应一段式比较冗长的缺点,但是如果状态机相对复杂些,一般来说,一段式代码长度会比两段式冗长大约 80到 150左右。 所以一段式 FSM 描述是不推荐的 FSM 描述方式,请读者一定要避免。 6.2.3.2 两段式状态机描述方法(推荐写法) 为了使 FSM 描述清晰简介,易于维护,易于附加时序约束,使综合器和布局布
33、线器更好的优化设计,推荐使用两段式 FSM 描述方法。 本例的两段式描述代码如下: /2-paragraph method to describe FSM /Describe sequential state transition in 1 sequential always block /State transition conditions in the other combinational always block /Package state output by task. Then register the output module state2 ( nrst,clk, i1,i
34、2, o1,o2, err ); 如何写好状态机 149 input nrst,clk; input i1,i2; output o1,o2,err; reg o1,o2,err; reg 2:0 NS,CS; parameter 2:0 /one hot with zero idle IDLE = 3b000, S1 = 3b001, S2 = 3b010, ERROR = 3b100; /sequential state transition always (posedge clk or negedge nrst) if (!nrst) CS = IDLE; else CS =NS; /c
35、ombinational condition judgment always (CS or i1 or i2) begin NS = 3bx; ERROR_out; case (CS) IDLE: begin IDLE_out; if (i1) NS = IDLE; if (i1 if (i1 end S1: begin S1_out; if (i2) NS = S1; if (i2 if (i2 end S2: begin S2_out; if (i2) NS = S2; if (i2 if (i2 第 6 章 如何写好状态机 150 end ERROR: begin ERROR_out;
36、if (i1) NS = ERROR; if (i1) NS = IDLE; end endcase end /output task task IDLE_out; o1,o2,err = 3b000; endtask task S1_out; o1,o2,err = 3b100; endtask task S2_out; o1,o2,err = 3b010; endtask task ERROR_out; o1,o2,err = 3b111; endtask endmodule 两段式写法是推荐的 FSM 描述方法之一,在此我们仔细讨论一下代码结构。两段式FSM 的核心就是:一个 alway
37、s 模块采用同步时序描述状态转移;另一个模块采用组合逻辑判断状 态转移条件,描述状态转移规律。两段式写法可以概括为图 6-5 描述的结构。 如何写好状态机 151 图 6-5 两段式 FSM 描述结构图 本例中,同步时序描述状态转移的 always 模块代码如下: always (posedge clk or negedge nrst) if (!nrst) CS = IDLE; else CS =NS; 其实这是一种程式化的描述结构,无论具体到何种 FSM 设计,都可以定义两个状态寄存器“ CS”和“ NS”,分别代表当前状态和下一状态,然后根据所需的复位方式(同步复位或异步复位),在时钟沿
38、到达时将 NS 赋给 CS。需要注意的是这个同步时序模块的赋值要采用非阻塞赋值“ =”。 本例中,另一个采用组合逻辑判断状态转移条件的 always 模块代码如下: /combinational condition judgment always (nrst or CS or i1 or i2) begin NS = 3bx; ERROR_out; case (CS) IDLE: begin IDLE_out; if (i1) NS = IDLE; if (i1 第 6 章 如何写好状态机 152 if (i1 end S1: begin S1_out; if (i2) NS = S1; if
39、 (i2 if (i2 end S2: begin S2_out; if (i2) NS = S2; if (i2 if (i2 end ERROR: begin ERROR_out; if (i1) NS = ERROR; if (i1) NS = IDLE; end endcase end 这个使用组合逻辑判断状态转移条件的 always 模块也可以看成格式化的书写结构。其中 always 的敏感列表为当前状态“ CS”,复位信号和输入条件(如果是米勒状态机,则必须有输入条件;如果是摩尔状态机,一般敏感表和后续逻辑判定没有输入),请大家注意电平敏感表必须列完整。本例中这段电平敏感列表为:
40、always (nrst or CS or i1 or i2) 一般来说,在这个组合 always 敏感表下先写一个默认的下一状态“ NS”的描述,然后根据实际的状态转移条件由内部的 case 或者 if.else 条件判断确定正确的转移。如本例中下面这段代码, begin NS = ERROR; ERROR_out; case (CS) 推荐在敏感表下的默认状态为不定状态 X,这样描述的好处有两个:第一在仿真时可以很好的考察所设计的 FSM 的完备性,如果所设计的 FSM 不完备,则会进入任意状态,仿真很容易发现;第二个好处是综合器对不定态 X 的处理是“ Dont Care”,即任何没有定
41、义的状态寄存器向量都会被忽略。这里赋值不定态的效果和使用 casez 或 casex 替代 case 的效果如何写好状态机 153 非常相似。 在每个 case 模块的内部的结构也非常相似,都是先描述当前状态的组合逻辑输出,然后根据输入条件(米勒 FSM)判定下一个状态。 该组合逻辑模块中所有的赋值推荐采用阻塞赋值“ =”。 请大家注意,虽然下一状态寄存器 NS 为寄存器类型,但是在两段式 FSM 的判断状态转移条件的 always 模块中,实际上对应的真实硬件电路是纯组合逻辑电路。 对于每个输出,一般用组合逻辑描述,比较简便的方法是用 task/endtask 将输出封装起来,这样做的好处不
42、仅仅是写法简单,而且利于复用共同的输出。例如本例中 S1 状态的输出被封装为 S1_out,在组合逻辑 always 模块中直接调用即可。 task S1_out; o1,o2,err = 3b100; endtask 组合逻辑容易产生毛刺,因此如果时序允许,请尽量对组合逻辑的输出插入一个寄存器节拍,这样可以很好的保证输出信号的稳定性。 6.2.3.3 三段式状态机描述方法(推荐写法) 两段式 FSM 描述方法虽然有很多好处,但是它有一个明显的弱点就是其输出一般使用组合逻辑描述,而组合逻辑易产生毛刺等不稳定因素,并且在 FPGA/CPLD 等逻辑器件中过多的组合逻辑会影响实现的速率(这 点与
43、ASIC 设计不同)。所以在上节我们特别提到了在两段式 FSM 描述方法中,如果时序允许插入一个额外的时钟节拍,则尽量在在后级电路对FSM 的组合逻辑输出用寄存器寄存一个节拍,则可以有效地消除毛刺。但是很多情况下,设计并不允许额外的节拍插入( Latency),此时,解决之道就是采用 3 段式 FSM 描述方法。三段式描述方法与两段式描述方法相比,关键在于使用同步时序逻辑寄存 FSM 的输出。 本例的三段式描述代码如下: /3-paragraph method to describe FSM /Describe sequential state transition in the 1st se
44、quential always block /State transition conditions in the 2nd combinational always block /Describe the FSM out in the 3rd sequential always block module state2 ( nrst,clk, i1,i2, o1,o2, err ); input nrst,clk; input i1,i2; output o1,o2,err; 第 6 章 如何写好状态机 154 reg o1,o2,err; reg 2:0 NS,CS; parameter 2:
45、0 /one hot with zero idle IDLE = 3b000, S1 = 3b001, S2 = 3b010, ERROR = 3b100; /1st always block, sequential state transition always (posedge clk or negedge nrst) if (!nrst) CS = IDLE; else CS =NS; /2nd always block, combinational condition judgment always (nrst or CS or i1 or i2) begin NS = 3bx; ca
46、se (CS) IDLE: begin if (i1) NS = IDLE; if (i1 if (i1 end S1: begin if (i2) NS = S1; if (i2 if (i2 end S2: begin if (i2) NS = S2; if (i2 if (i2 end ERROR: begin if (i1) NS = ERROR; if (i1) NS = IDLE; end endcase end 如何写好状态机 155 /3rd always block, the sequential FSM output always (posedge clk or neged
47、ge nrst) if (!nrst) o1,o2,err = 3b000; else begin o1,o2,err = 3b000; case (NS) IDLE: o1,o2,err=3b000; S1: o1,o2,err=3b100; S2: o1,o2,err=3b010; ERROR: o1,o2,err=3b111; endcase end endmodule 三段式写法可以概括为图 6-6 描述的结构。 图 6-6 三段式 FSM 描述结构图 对比一下上节两段式 FSM 的描述,读者可以清晰发现三段式与两段式 FSM 描述的最大区别在于两段式采用了组合逻辑输出,而三段式巧妙地根据下一状态的判断,用同步时序逻辑寄存 FSM 的输出。本例中就是下面一段代码, always (posedge clk or negedge nrst) if (!nrst) o1,o2,err = 3b000; 第 6 章 如何写好状态机 156 else begin o1,o2,err = 3b000; case (NS) IDLE: o1,o2,err=3b000; S1: o1,o2,err=3b100; S2: o1,o2,err=3b010; ERROR: o1,o2,err=3b111; end