可综合的状态机编码风格
前言
Steve Golson 1994年的论文 State Machine Design Techniques for Verilog and VHDL 是一篇关于使用 Verilog、VHDL 和 Synopsys 工具进行状态机设计的优秀论文。Steve 的论文还提供了关于特定状态机类型起源的深入背景。
本文 State Machine Coding Styles for Synthesis 详细介绍了对状态机设计的其他见解,包括编码风格方法和一些其他技巧。
状态机分类
根据从每种状态机生成的输出类型分类,有两种类型的状态机。第一种是 Moore 状态机,其输出仅是当前状态的函数,第二种是 Mealy 状态机,其中一个或多个输出是当前状态和一个或多个输入的函数。
除了根据各自的输出生成类型对状态机进行分类外,还经常根据每个状态机使用的状态编码对状态机进行分类。一些更常见的状态编码风格包括:highly-encoded binary (or binarysequential),gray-code,Johnson,one-hot,almost one-hot and one-hot with zero-idle。
使用图 2 所示的 Moore FSM 状态图,本文将详细介绍使用 highly-encoded binary,one-hot and one-hot with zero-idle 编码风格的状态机。本文还详细介绍了使用 Synopsys FSM 工具生成 binary,gray and one-hot 状态机的方法。图 2 中状态机的三种编码风格的编码示例,加上一个带有正确 Synopsys FSM Tool 注释的示例,已经包含在本文的末尾。
FSM Verilog 模块
指导原则:使每个状态机成为一个单独的 Verilog 模块。
将每个状态机与其他合成逻辑分开可以简化状态机定义、修改和调试的任务。还有一些 EDA 工具可以帮助 FSM 的设计和文档编制,但一般来说,只有当 FSM 没有与其他逻辑代码混合在一起时,它们才能很好地工作。
状态分配
指导原则:使用带有符号状态名的参数进行状态赋值。
定义和使用符号状态名使 Verilog 代码更具可读性,并且在必要时简化了重新定义状态的任务。Examples 1-3 显示了图 2 中 FSM 状态图的 binary,one-hot and one-hot with zeroidle 参数定义。
Example 3 所示的 simplified one-hot encoding 使用十进制数索引到状态寄存器。这种技术允许对单个比特进行比较,而不是使用 Example 2 中所示的完整状态参数对整个状态向量进行比较。
对于具有许多具有复杂方程的互连的状态机(包括到一个特定状态的大量连接),one-hot with zero-idle encoding 可以产生非常有效的 FSM。通常,会向 IDLE 状态或另一个公共状态(如本例中的 ERROR 状态)进行多次跳转。
也可以使用宏定义编译器指令(ˋdefine)定义符号状态名。但是 ˋdefine 创建的是一个全局定义,与 ˋdefine 定义的常量不同的是,参数声明的是该模块的局部常量,这允许设计具有重复状态名称的多个 FSM,例如 IDLE 或 READ,每个都有唯一的状态编码。
有时,FSM 代码是用参数定义的状态定义编写的,但是随后的代码仍然在模块的其他地方包含显式的二进制状态编码。这违背了使用符号标记参数的目的。只使用预定义的参数名进行状态测试和 next 状态分配。
关于使用 Synopsys 生成的 binary, gray and one-hot encodings 的附加说明,请参见 “Synopsys FSM Tool” 一节。
两段式状态机
可综合状态机可以用多种方式编码。两种最常见、易于理解且高效的方法是两段式状态机和一段式状态机。
最容易理解和实现的方法是两段式状态机,其输出分配,包含在 next 状态组合逻辑的 always 块,或单独的连续分配输出中。该方法将 Verilog 代码划分为图 1 所示的主要 FSM 块:时钟当前状态逻辑、next 状态组合逻辑和输出组合逻辑。
时序逻辑 Always 块
指导原则:在时序逻辑 always 块中只使用 Verilog 非阻塞赋值。
指导原则:在用于生成时序逻辑的所有 always 块中只使用 Verilog 非阻塞赋值。
Verilog 非阻塞赋值模拟了实际硬件的流水线寄存器行为,并消除了许多潜在的 Verilog 竞争条件。许多工程师使用内部分配时间延迟进行非阻塞分配(如 Example 5 所示)。在非阻塞分配中使用内部分配时间延迟有两个好的理由和一个坏的理由。
好的理由:(1)在时钟寄存器上给出一个 clk->q 延迟的外观(如使用波形查看器所见);(2)当从 RTL 模型驱动大多数门级模型时,有助于避免保持时间问题。
坏的理由:“我们添加延迟是因为 Verilog 的非阻塞任务被破坏了!” - 这不是真的。
当实现 a binary encoded or a verbose one-hot encoded FSM 时,在复位时,状态寄存器将被分配为 IDLE 状态(或同等状态)(Example 5)。
当实现 a simplified one-hot encoded FSM 时,在复位时,状态寄存器将被分配为全零,然后立即重新分配状态寄存器的 IDLE 位(Example 6)。注意,有两个非阻塞赋值给同一个位赋值。这由 IEEE Verilog 标准定义,在这种情况下,最后一个非阻塞赋值取代之前的任何非阻塞赋值(更新状态寄存器的 IDLE 位)。
当实现 a one-hot with zero-idle encoded FSM 时,在复位时,状态寄存器将被分配为全零(Example 7)。
组合逻辑 Always 块
指导原则:在组合逻辑 always 块中只使用 Verilog 阻塞赋值。
编写一个组合逻辑 always 块来更新 next 状态值。这个 always 块是由一个敏感列表触发的,该列表对来自同步 always 块的状态寄存器和状态机的所有敏感输入。
在 always 块敏感列表的下一行上放置一个默认的 next 状态赋值。这个默认赋值由 case 语句中的 next 状态赋值更新。有三种常用的默认 next 状态赋值:(1)next 设置为全 x,(2)next 设置为预定的恢复状态,如 IDLE,(3)next 仅设置为状态寄存器的值。
通过将默认的 next 状态赋值为 x,如果在 case 语句中没有显式地赋值所有状态转换,则预合成仿真模型将导致状态机输出变为未知。这是调试状态机设计的一种有用的技术,而且这些 x 将被合成工具视为 “不关心”。
有些设计要求将 next 状态赋值为已知状态,而不是赋值为 x。示例包括:卫星应用、医疗应用、使用 FSM 触发器作为诊断扫描链的一部分的设计以及使用正式验证工具进行等效检查的设计。将默认的 next 状态分配为 IDLE 或全0 通常可以满足这些设计需求,并且进行初始默认赋值可能比在 case 语句中对所有显式的 next 状态转换赋值进行编码更容易。
使默认的 next 状态分配等于当前状态是一种编程风格,已被 PLD 设计人员使用多年。
next 状态赋值在 case 语句内有效地更新。
FSM 输出逻辑
将输出逻辑编码为连续赋值的单独块或组合逻辑 always 块。如果输出分配被编码为组合 always 块的一部分,那么输出分配也可以放入具有有意义名称的 Verilog task 中,如图 5 所示。在 case 语句的每个状态中调用 task。
一般来说,与为每个状态(case 项)分配所有输出和在输出应该改变时高亮显示相比,此方法需要的编码更少。
Mealy 和寄存器输出
通过限定输出连续赋值,可以很容易地将 Mealy 输出添加到 Verilog 代码中:
assign rd_out = (state == READ) & !rd_strobe_n;
或者通过在组合逻辑 always 块中限定输出赋值:
case (state)
...
READ: if (!rd_strobe_n) rd_out = 1'b1;
寄存器输出可以添加到 Verilog 代码中,使用时序逻辑 always 块中的非阻塞赋值对输出进行赋值。FSM 可以被编码为一个时序逻辑的 always 块,也可以在设计中添加第二个时序逻辑的 always 块。
一段式状态机
一般来说,一段式状态机比两段式状态机的仿真效率略高,因为输入只在时钟变化时进行检查;然而,这种状态机可能更难以修改和调试。
在一段式状态机状态机的 always 块中放置输出赋值时,必须考虑以下几点:
将输出赋值放置在 always 块中会推断出输出触发器。还必须记住,放置在 always 块内部的输出赋值是 “下一个输出” 赋值,这可能更容易导致代码出错。
注意:时序逻辑 always 块内部的输出赋值不能是 Mealy 输出。
Full_case / parallel_case
在 Verilog 和 VHDL 中,case 语句是一个 “从许多中选择一个” 的构造。case 语句由关键字 case 和与后续 case 项进行比较的 case 表达式组成。根据 case 表达式依次测试 case 项,当检测到 case 表达式与其中一个 case 项匹配时,执行相应的操作,跳过其余的 case 项,并从 endase 语句后的第一个语句继续执行程序。
case (case_expression (with 2n possible combinations))
case_item1 : <action #1>;
case_item2 : <action #2>;
case_item3 : <action #3>;
...
case_item2n-1: <action #2n-1>;
case_item2n: <action #2n>;
default: <default action>;
endcase
一个完整的 case 语句被定义为一个 case 语句,其中每个可能的输入模式都被显式定义。并行 case 语句被定义为在 case 项中没有重叠条件的 case 语句。
VHDL 的 case 语句要求是 “完整的”,这意味着每个可能的 case 项要么被显式地列为 case 项,要么在最后定义的 case 项之后必须有一个 “others =>” 语句。在实践中,几乎所有使用非位型数据类型的 VHDL case 语句都包含一个 “others =>” 语句来覆盖非二进制数据模式。
VHDL 的 case 语句也要求是 “并行的”,这意味着在 case 项列表中,没有 case 项与其他任何 case 项重叠。
Verilog case 语句没有强制要求是 “完整的” 或 “并行的”。
将 “// synopsys full case” 添加到 case 语句的末尾(在声明任何 case 项之前)来通知综合工具,对于综合结果,所有来自未显式声明的 case 项的输出都应该被视为 “不关心”。
将 “// synopsys parallel case” 添加到 case 语句的末尾(在声明任何 case 项之前),来通知综工具应该单独测试所有的 case 项,即使这些 case 项有重叠条件。
在 Verilog FSM 源代码中添加 “// synopsys full case parallel case” 或 “// synopsys full case parallel case” 指令通常在编写 one-hot or one-hot with zero-idle FSM 时是有益的。在这种情况下,假定只设置状态向量的一位,并且所有其他位模式组合都应被视为 “不关心”。还假定在 case 项列表中不应该有重叠条件。
请注意,full_case,parallel_case的使用可能会导致综合前设计仿真与综合后设计仿真不同,因为这些指令有效地向综合工具提供了原始 Verilog 模型中未包含的有关设计的信息。
Synopsys FSM Tool
使用 Synopsys FSM 工具可以实验不同的状态编码风格,如 binary, gray and one-hot codes。为了使用 FSM 工具,Verilog 代码必须包含 Synopsys 综合注释,以及一些不常见的 Verilog 代码语句。Synopsys FSM 工具对这些注释和代码段的排序非常严格,因此很容易对 FSM 工具进行错误的编码。
第一,参数必须包含一个范围(非常不寻常的 Verilog 编码风格)。如果参数声明中没有包含范围,则会报告错误消息 “Declaration of enumeration type requires range specification”。
第二,数值参数定义必须有位宽大小,否则 FSM 工具将所有数字解释为 32 位数字并报告无效编码错误。
第三,Synopsys 综合注释所需的位置如图所示。“// synopsys enum < name >” 必须放在参数范围声明之后,任何参数声明之前。
“/ synopsys state_vector < state_vector_name >” Synopsys 注释必须放在 state-reg 声明之前,而上面使用的 “// synopsys enum < name >” 注释必须放在 reg 范围声明之后,但在 state 和 next 声明之前。
下面是 dc_shell 命令示例,用于在状态机设计中调用 Synopsys FSM 工具。
示例