状态机的设计与实现
0赞状态机的设计与实现
在数字逻辑电路中,状态机是一个非常重要的概念,也是常用的一种结构,状态机常常用于序列检测、序列信号的产生以及时序产生等方面。利用Verilog语言也可以编写出可综合的状态机,并有多种编写格式和编写原则,本文主要整理的是状态机的一般编写方法和形式,以及可综合的状态机的一些设计原则。
一、状态机的结构
1.1 状态机的组成
状态机是组合逻辑和寄存器逻辑的特殊组合,一般包括两个部分:组合逻辑部分和寄存器逻辑部分。寄存器用于存储状态,组合电路用于状态译码和产生输出信号。状态机的下一个状态及输出不仅与输入信号有关,还与寄存器当前状态有关,其基本要素有三个,即状态、输入和输出。
状态也叫做状态变量。在逻辑设计中,使用状态划分逻辑顺序和时序规律。例如,要设计一个交通灯控制器可以用允许通行、慢行和禁止通行作为状态;设计一个电梯控制器,每层就是一个状态等。
输入是指状态机中进入每个状态的条件。有的状态机没有输入条件,其中的状态转移比较简单;有的状态机有输入条件,当某个输入条件存在时,才能转移到相应的状态。例如,交通灯控制器就没有输入条件,状态随着时间的改变而自动跳转;电梯控制器是存在输入的,每层的上下按键,以及电梯内的层数选择按键都是输入,会对电梯的下一个状态产生影响。
输出是指在某一状态时特定发生的事件。例如,交通灯控制器在允许通行状态输出绿色,缓行状态输出黄色,禁止通行状态输出红色;电梯控制器在运行时一直会输出当前所在的层数及当前运行的方向(上升或下降)。
1.2 状态机的分类
根据输出是否与输入信号有关,状态机可以划分为Mealy型状态机和Moore型状态机两种;根据输出是否与输入信号同步,状态机可以划分为异步状态机和同步状态机两种。由于目前的电路设计中以同步设计为主,所以本文只介绍同步状态机。
1.2.1 Mealy型状态机
Mealy型状态机的输出同时依赖于当前的状态和输入信号,其结构如图1.1所示。输出可以在输入发生变化后立即改变,而与时钟信号无关。因此Mealy型状态机具有异步输出特性。在实际中,Mealy型状态机的应用比较广泛,该类型常常能够减少状态机的状态数。
1.2.2 Moore型状态机
Moore型状态机的输出仅依赖于当前的状态,其逻辑结构如图1.2所示。组合逻辑块将输入和当前状态映射为适当的次态,作为状态寄存器的输入,并在下一个时钟周期覆盖当前状态,使得状态机状态发生变化。输出是通过组合逻辑块计算得到的,本质上是当前状态的函数。其中,输出的变化和状态的变化都与时钟信号变化沿保持同步。
二、用Verilog描述状态机
2.1 状态编码方式
状态编码又称为状态分配。通常有多种编码方法,如果编码方案选择得当,设计的电路可以简单;反之,电路会占用过多的逻辑或降低速度。设计时,必须综合考虑电路复杂度和电路性能这两个因素。下面主要介绍二进制编码、格雷编码和独热码。
2.1.1 二进制编码
二进制编码和格雷编码都是压缩状态编码。二进制编码的优点是使用的状态向量最少,但从一个状态转换到相邻状态时,可能会有多个位发生变化,瞬变次数多,易产生毛刺。由于二进制编码的表示形式比较通用,在此不做详细的介绍。
2.1.2 格雷编码
格雷码在相邻状态的转换中,每次只有1位发生变化,虽然减少了产生毛刺和一些暂态的可能,但不适用于有很多状态跳转的情况。表2-1给出了十进制数字0~9的格雷码表示形式。
表2-1 格雷码数据列表
十进制数字 | 格雷码 | 十进制数字 | 格雷码 |
0 | 0010 | 5 | 1100 |
1 | 0110 | 6 | 1101 |
2 | 0111 | 7 | 1111 |
3 | 0101 | 8 | 1110 |
4 | 0100 | 9 | 1010 |
由于在有限状态机中,输出信号经常是通过状态的组合逻辑电路来驱动,因此有可能由于输入信号的不同时到达而产生毛刺。如果状态机的所有状态是一个顺序序列,则可通过格雷码来消除毛刺,但对于时序逻辑状态机中的复杂分支,格雷编码也不能达到消除毛刺的目的,所以此编码方式用得也不是太多。
2.1.3 独热码
独热码是指对任意给定的状态,状态向量中只有1位为1,其余位都为0。n状态的状态机就需要n个触发器。这种状态机的速度与状态的数量无关,仅取决于到某特定状态的转移数量,速度很快。当状态机的状态增加时,如果使用二进制编码,那么状态机速度会明显下降。而采用独热码,虽然多用了触发器,但由于状态译码简单,节省和简化了组合逻辑电路。独热码还具有设计简单、修改灵活、易于综合和调试等优点。表2-2给出了十进制数字0~9的独热码的表示形式。
表2-2 独热码数据列表
十进制数字 | 格雷码 | 十进制数字 | 格雷码 |
0 | 000_0000_00 | 5 | 000_0100_00 |
1 | 000_0000_01 | 6 | 000_1000_00 |
2 | 000_0000_10 | 7 | 001_0000_00 |
3 | 000_0001_00 | 8 | 010_0000_00 |
4 | 000_0010_00 | 9 | 100_0000_00 |
对于寄存器数量多而门逻辑相对缺乏的FPGA器件,采用独热码可以有效提高电路的速度和可靠性,也有利于提高器件资源的利用率。独热码有很多无效状态,应该确保状态机一旦进入无效状态时,可以立即跳转到确定的已知状态。
2.2 状态机的Verilog实现
基于Verilog HDL语言的状态机设计方法非常灵活,按代码描述方法的不同,可分为一段式描述、两段式描述和三段式描述等。不同的描述所对应的电路是不同的,因此最终的性能也是不同的。由于一段式的描述方式存在很多缺点,比如说可维护性差,代码复杂,不易修改和调试等,所以在此不做介绍。为了保证代码的规范性、可靠性和可维护性,下面分别介绍两段式和三段式的描述方法。
2.2.1 两段式描述法
在这种描述方法中,一个时序逻辑的always块用于给当前状态向量赋值,即完成改变状态的工作;另一个组合逻辑的always块用于计算下一状态和输出逻辑,通常用于描述组合输出的Moore状态机或异步Mealy状态机。
【例1】用两段式描述法描述状态机,二进制编码
module FSM_Two_Sec(clk, rst_n, sig1, sig2, a, b); input clk, rst_n; input sig1, sig2; output reg a, b; //定义三个状态参数,二进制编码 parameter S0 = 2'b00, S1 = 2'b01, S2 = 2'b10; reg [1:0] state, next_state; //时序逻辑的always块用于改变状态 always @ (posedge clk or negedge rst_n) begin if (!rst_n) state <= S0; else state <= next_state; end //组合逻辑的always块用于计算输出和下一状态 always @ (state or sig1 or sig2 ) //将输入和当前状态加入到敏感列表中 begin case (state) S0: begin b = 1'b0; if (sig1 || sig2) a = 1'b1; else a = 1'b0; if(sig1 == 1'b1) next_state = S1; else next_state = S0; end S1: begin b = 1'b1; a = 1'b0; if(sig2 == 1'b1) next_state = S2; else next_state = S0; end S2: begin b = 1'b0; a = 1'b0; next_state = S0; end default: begin a = 1'bx; b = 1'bx; next_state = S0; end endcase end endmodule |
2.2.2 三段式描述法
三段式描述法包含三个always块,其中有一个组合逻辑的always块,用于计算下一状态;有一个时序逻辑的always块,用于进行状态切换,即改变当前状态;还有另外一个always块,可以为时序逻辑,也可以为组合逻辑,用于计算状态机的输出,但需要注意的是,这样综合后的结果是不一样的。
【例2】用三段式描述法描述状态机,二进制编码
module FSM_Three_Sec1(clk, rst_n, sig1, sig2, a, b); input clk, rst_n; input sig1, sig2; output reg a, b; //定义状态参数,二进制编码 parameter[1:0] S0 = 2'b00, S1 = 2'b01, S2 = 2'b10; reg [1:0] state, next_state; //时序逻辑,用于进行改变状态 always @(posedge clk or negedge rst_n) begin if (!rst_n) state <= S0; else state <= next_state; end //时序逻辑,用于计算输出 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin a <= 0; b <= 0; end else begin case(state) S0: begin if (sig1 || sig2) begin a <= 1; b <= b; end else begin a <= 0; b <= b; end end S1: begin a <= a; b <= 1; end default: begin a <= a; b <= b; end endcase end end //组合逻辑,用于计算下一状态 always @(state or sig1 or sig2) begin case (state) S0: begin if(sig1) next_state = S1; else next_state = S0; end S1: begin if(sig2) next_state = S2; else next_state = S0; end S2: begin next_state = S0; end default: begin next_state = S0; end endcase end endmodule |
【例3】用三段式描述法描述状态机,二进制编码
module FSM_Three_Sec2(clk, rst_n, sig1, sig2, a, b); input clk, rst_n; input sig1, sig2; output reg a, b; //定义状态参数,二进制编码 parameter[1:0] S0 = 2'b00, S1 = 2'b01, S2 = 2'b10; reg [1:0] state, next_state; //组合逻辑,用于计算输出 always @(state or sig1 or sig2) begin a = (state == S0) && (sig1 || sig2); b = (state == S1); end //时序逻辑,用于改变状态 always @(posedge clk or negedge rst_n) begin if (!rst_n) state <= S0; else state <= next_state; end //组合逻辑,用于计算下一状态 always @(state or sig1 or sig2) begin case (state) S0: begin if(sig1) next_state = S1; else next_state = S0; end S1: begin if(sig2) next_state = S2; else next_state = S0; end S2: begin next_state = S0; end default: begin next_state = S0; end endcase end endmodule |
【例4】用三段式描述法描述状态机,独热编码
module FSM_Three_Sec3(clk, rst_n, sig1, sig2, a, b); input clk, rst_n; input sig1, sig2; output reg a, b; //定义状态参数,独热编码 parameter S0 = 3'b001, S1 = 3'b010, S2 = 3'b100; reg [2:0] state, next_state; //组合逻辑,用于计算输出 always @(state or sig1 or sig2) begin a = (state == S0) && (sig1 || sig2); b = (state == S1); end //时序逻辑,用于改变状态 always @ (posedge clk or negedge rst_n) begin if (!rst_n) state <= S0; else state <= next_state; end //组合逻辑,用于计算下一状态 always @ (state or sig1 or sig2) begin next_state = S0; case (state) S0: begin if(sig1) next_state = S1; else next_state = S0; end S1 : begin if(sig2) next_state = S2; else next_state = S0; end S2 : begin next_state = S0; end default: begin next_state = S0; end endcase end endmodule |
【例5】用三段式描述法描述状态机,独热编码的另一种形式
module FSM_Three_Sec4(clk, rst_n, sig1, sig2, a, b); input clk, rst_n; input sig1, sig2; output reg a, b; //定义状态参数,独热编码 parameter S0 = 0, S1 = 1, S2 = 2; reg [2:0] state, next_state; //组合逻辑,用于计算输出 always @(state or sig1 or sig2) begin a = (state == S0) && (sig1 || sig2); b = (state == S1); end //时序逻辑,用于改变状态 always @ (posedge clk or negedge rst_n) begin if (!rst_n) state <= 3'b001; else state <= next_state; end //组合逻辑,用于计算下一状态 always @ (state or sig1 or sig2) begin next_state = 3'b000; case (1'b1) state [S0]: begin if(sig1) next_state [S1] = 1'b1; else next_state [S0] = 1'b1; end state [S1]: begin if(sig2) next_state [S2] = 1'b1; else next_state [S0] = 1'b1; end state [S2]: begin next_state [S0] = 1'b1; end default: begin next_state [S0] = 1'b1; end endcase end endmodule |
三、设计可综合的状态机的指导原则
因为大多数FPGA内部的触发器数目相当多,又加上独热码状态机的译码逻辑最为简单,所以在设计采用FPGA实现的状态机时,往往采用独热码状态机(即每个状态只有一个寄存器置位的状态机)。
建议采用case语句来建立状态机的模型,因为这些语句表达清晰明了,可以方便地从当前状态分支转向下一个状态并设置输出。不要忘记写上case语句的最后一个分支default,并将状态变量设为'bx或初始化状态,这就等于告知编译器:case语句已经指定了所有的状态。这样综合器就可以删除不需要的译码电路,使生成的电路简洁,并与设计要求一致。
状态机应该有一个异步或同步复位端,以便在通电时将硬件电路复位到有效状态,也可以在操作中将硬件电路复位。
目前大多数综合器往往不支持在一个always块中由多个事件触发的状态机,为了能综合出有效的电路,用Verilog HDL描述的状态机应明确地由唯一时钟触发。如果设计要求必须有不同的时钟触发的状态机,可以采用以下办法:编写另一个模块,在那个模块中使用另外一个时钟;然后用实例引用的方法在另外一个模块中把它们连接起来。为了使设计比较简单,调试比较容易,应该尽量使这两个状态机的时钟有一定的关系。例如甲模块的时钟是乙模块时钟的同步计数器的输出。
千万不要使用综合工具来设计异步状态。因为目前大多数综合工具在对异步状态机进行逻辑优化时会胡乱地简化逻辑,使综合后的异步状态机不能正常工作。如果一定要设计异步状态机,建议采用电路图输入的方式,而不要直接用Verilog RTL级别的描述方法通过综合来产生。
在Verilog HDL中,状态必须明确赋值。通常使用参数(parameter)或宏定义(define)语句加上赋值语句来实现。
四、状态机设计总结
状态机设计的一般步骤为:
(1)逻辑抽象,得出状态转换图。就是把给出的一个实际逻辑关系表示为时序逻辑函数,可以用状态转换表来描述,也可以用状态转换图来描述。
(2)状态化简。如果在状态转换图中出现这样两个状态,它们在相同的输入下转换到同一状态去,并得到一样的输出,则成为等价状态。显然等价状态是重复的,可以合并为一个。电路的状态数越少,存储电路也就越简单。状态化简的目的就在于将等价状态尽可能的合并,以得到最简的状态转换图。
(3)状态分配。状态分配又称为状态编码。通常有很多种编码方式,编码方法选择得当,设计的电路可以简单,反之,选得不好,则设计的电路就会复杂许多。在实际设计时,须综合考虑电路复杂度与电路性能之间的折衷。在触发器资源丰富的FPGA或ASIC设计中,采用独热编码既可以使电路性能得到保证又可充分利用其触发器数量多的优势,也可以采取输出编码的状态指定来简化电路结构,并提高状态机的运行速度。
(4)选定触发器的类型并求出状态方程、驱动方程和输出方程。
(5)按照方程得出逻辑图。用Verilog HDL来描述有限状态机,可以充分发挥硬件描述语言的抽象建模能力,使用always块语句和case语句及赋值语句即可方便实现。具体的逻辑化简、逻辑电路到触发器映射均可由计算机自动完成。上述步骤中的第(2)步及第(4)、(5)步不再需要很多的人为干预,使电路设计工作得到简化,效率也有很大的提高。