数字电路通常分为组合逻辑电路和时序电路,
组合逻辑电路 outputs = F(current inputs) 时序电路 outputs = F(current
inputs,past inputs)有限状态机就是时序电路的数学抽象,一个有限状态机系统包括inputs ,outputs,
states .状态机分为同步状态机(synchronous)和异步状态机(asynchronous),异步状态机由于输出信号不稳定,所以不详细讨论,对绝大多数设计来说,用的最广泛的是同步状态机。下面主要讨论了同步状态机的设计。
一.状态机的基础知识
1.1. moore状态机和mealy状态机的区别:
2.1.1moore状态机输出只依赖于及其的当前状态,与输入信号无关。这是moore状态机的优点。moore状态机比较容易用数学的方式来分析,因此被更广泛的用在代数状态机理论中(algebraic
FSM theory)。
Mealy状态机输出依赖于机器现在的状态和输入的值,如果输入改变,输出可以在一个时钟周期中将发生了改变。
state memory :保存现在的状态(current state s(t) )
state transistion function :根据现态和输入x(t),s(t+1)来决定下一个状态。
Output function :根据s(t)和x(t)来决定最后的输出。
Mealy 状态机通常可以有更少的状态变量,因此在工程领域有更为广阔的应用,
状态变量越少,则所需的存储单元就越少。
下面用简单的实例来具体说明两者编程的区别,和综合出来的结果的不同:
Mealy状态机的简单例子:
1. 源程序:
demo_process:process(clk,reset)
begin
if(reset = '1')then
state
<= s0;
out1
<= (others=>'0');
elsif rising_edge(clk) then
case state is
when s0 => if(in1 = '1')then
state <= s1;
out1 <= "1000";
end if;
when s1 => if(in1 = '0')then
state <= s2;
out1 <= "1001";
end if;
when s2 => if(in1 = '1')then
state <= s3;
out1 <= "1100";
end if;
when s3 => if(in1 = '0')then
state <= s0;
out1 <= "1111";
end if;
when others =>
null;
end case;
end if;
end process;
1.模块表示图:
综合图上可以明显得看出红线即input所参与决定的是状态的产生和输出。而且输出out1为了防止输出的波形不好(因为通过组合电路输出的波形不稳定),所以加了一个触发器。
2.门级综合结果(仅作参考):
其中状态机模块编码采用两个触发器
所以如果在语言中没有对状态进行编码,那么综合器会自动将编码方式设为顺序编码。
Moore状态机的简单例子:
1源程序:
demo_process:process(clk,reset)
begin
if(reset = '1')then
state <= s0;
out1 <= (others=>'0');
elsif rising_edge(clk) then
case state is
when s0 => if(in1 = '1')then
state <= s1;
end if;
out1 <= "1000";
when s1 => if(in1 = '0')then
state <= s2;
end if;
out1 <= "1001";
when s2 => if(in1 = '1')then
state <= s3;
end if;
out1 <= "1100";
when s3 => if(in1 = '0')then
state <= s0;
end if;
out1 <= "1111";
when others =>
null;
end case;
end if;
end process;
从以上两种状态机的例子可以看出,同一周期对同样的输入,两种仿真结果一致,只是在综合上有所区别。
1.2 创建状态图或者状态转换表。
设计者通常可以用两种工具来简化状态表的建立过程:状态图和转换表。状态图提供了一种图形化的,易理解的对FSM操作的描述,但限制于相对较小的设计,当设计太复杂而无法创建状态图时,应使用转换表。
1.2.1 创建状态图
状态图应该从复位状态开始,每一个圈表示一个状态,状态图是有向图,圈内部应标上状态的名称和此状态的输出赋值,两个状态之间的转变通过一个有向线段表示,转变条件应标志在有向线段上面,该条件与时钟的上升沿同步。
1.2.2 创建转换表
状态图是帮助理解状态之间关系的有效一种方法,然而对于许多状态的大电路,状态会变得凌乱,极难画出可用的形式。这时,普遍采用一种称作转换表的文本描述方式,创建转换表的方法与状态图相同,不同的是转换列在一个表中。
上图的转换表如下:
From state condition Next state
Out put Data operate
State0 In1 = ‘1’ State1
Out1<=”0001” none
State1 In1 = ‘0’ State2
Out1<=”0010” none
State2 In1 = ‘1’ State3
Out1<=”0100” none
State3 none State4
Out1<=”1000” none
State4 None State0
Out1<=”1111” none
注意:在写状态图或者转换表的时候要坚持互斥原则,离开任何节点的线段上的逻辑表达式必须成对互斥,即没有在离开同一节点不同线段上的两个表达式同时为真的情况,如果两个这样的表达式同时是逻辑1,那么状态机就进入了两个不同的次状态,这是不容许的。
互斥测试的是离开同一节点的两个表达式的逻辑and必须为逻辑0。
1.4 状态的编码方式
在vhdl原码中是否对状态机编码作出规定并不影响状态机的功能,综合工具提供状态优化器,他可以在综合过程中确定状态编码。
状态编码主要有5种编码方式:
1.顺序编码。
2.格雷码编码。
3.单热编码(one-hot)。
4.随机编码。
5.自动编码(面积最小化)。
状态机在传统上是按二进制编码的。然而采用Gray编码,相邻状态可减少瞬变的次数。有时不可能在所有状态中使用Gray编码,则应在状态矢量中增加触发器的数量以减少开关的次数。另一种方法是使用one-hot编码,虽然该编码使用的触发器较多,即可减少组合逻辑的使用,在带多个输出且每个输出是几个状态的函数的状态机中更是如此。根据状态机的形式,设计者可在Gray、One-hot或二进制间进行选择。
现在就详细说一下one-hot 状态机:
1.ne-hot编码:s0 = “0001” ; s1 = “0010”; s2 = “0100”;
s3 = “1000”;
每一个状态用掉一个触发器,状态数等于触发器的数目。这就意味着触发器数目增加,而状态译码组合电路被优化掉了。对于寄存器资源丰富的xilinx器件来说,是非常适合的。
2.one-hot 状态机的好处:
现在的FPGA每一个逻辑块中都包含了一个和多个触发器,对于仅需要触发器的one-hot编码解码来说,提供了很好的条件。下面列举了一些用one-hot
设计的好处:
- 对寄存器资源丰富xilinx fpga 来说更易于适配和布线。
- one-hot状态机是典型的相当快速的状态机。他的速度与状态的个数没有任何关系,仅仅决定于状态变迁到一个特殊状态的这种转换的数量(instead
depend only on the number of transitions into a particular state)。
- 不用担心你会在发现最佳的状态机编码方式。因为其他设计的状态机如果在加入一些 状态或者改变其他一些什么的话,那就可能不再最优了。One-hot
编码方式在所有状态机中是最佳的,最优的。
- one-hot 状态机很容易设计。状态图能够直接被画成原理图或者被直接用vhdl语言写出来,而不用编码成状态表。
- 修改起来简单明了。增加和删掉一些状态或者改变一些敏感量等式能被综合器很容易的执行,而不会影响余下的状态机。
- 很容易从vhdl 或者 verilog 综合。
- 比其他一些高性能的状态机没有任何的布线面积的浪费。
- 能够用静态时序分析的方法很容易的找出危险的不合理的状态机转换路径。
3. 举例说明:
type STATE_TYPE is (s0, s1, s2, s3);
attribute ENUM_ENCODING: STRING;
attribute ENUM_ENCODING of STATE_TYPE: type is "0001 0010 0100
1000";
signal CS, NS: STATE_TYPE;
begin
-- build the state flops
SYNC_PROC: process (clk, reset)
begin
if (reset='1') then
CS <= s0;
elsif rising_edge(clk) then
CS <= NS;
end if;
end process;
-- state machine
COMB_PROC: process (CS,in1)
begin
case CS is
when s0 =>out1
<= (others=>'0');
if(in1 = '1')then
NS <= s1;
out1 <= "1000";
end if ;
when s1 => if(in1 = '0')then
NS <= s2;
out1 <= "1001";
end if;
when s2 => if(in1 = '1')then
NS <= s3;
out1 <= "1100";
end if;
when s3 => if(in1
= '0')then
NS <= s0;
out1 <= "1111";
end if;
when others =>
null;
end case;
end process;
在s0状态里,有一个细节,out1 <= (others=>'0');这句话在if外面,和在if里面的out1
<= "1000";这句话不会自相矛盾。第一句话是s0状态的输出。而第二句话则是s1状态的输出。
上例输出赋值随着时钟的跳变而赋值,这样在输出时就放了一个触发器。
如果在output中不想加入触发器的话,那么,就可以把输出单独拿出来,输出就直接经过组合逻辑,不经过触发器。
process(cs)
begin
case cs is
when s0 => out1 <= "0000";
when s1 => out1 <= "1000";
when s2 => out1 <= "1001";
when s3 => out1 <= "1100";
when others => null;
end case;
end process;
如果在程序中不对状态进行one-hot编码的话,那么有综合器生成的编码方式只能是顺序编码,或者格雷码,所以尽量在程序中确定编码的格式。
二.状态机的设计步骤:
1.深入的理解问题(Understand the problem)。用非常严谨的风格去解释问题的描述是非常重要的。对于状态机,你必须搞清楚什么样的输入会产生什么样的输出。
2.获得一个对状态机的理论性的描述(Obtain an abstract representation
of the FSM)。一旦你理解了问题,你必须用一种易于操作的形式表达出来,状态图是一种比较好的表示方法。
3.对状态机进行优化(Perform state minimization. )。从上一步过来,通常会导致很多状态,很多状态会有相同的行为描述,这些状态应该被优化成同一个状态。
4.进行状态编码的赋值(Perform state assignment)。编码方式好坏决定了执行的速度。
5.选择何种类型的触发器来实现状态机(Choose flip-flop types for implementing
the FSM's state),J-K触发器会减少门,但会改变转换表。D触发器是比较简明的实现过程。
6.实现有限状态机(Implement the finite state machine)。
三.具体实例:
为了说明以上的设计步骤,我们设计并综合一个状态机的来控制一个简单的自动售货机。
下面是怎样控制自动售货机去工作:
当自动售货机收到15分硬币时,它就送出一包口香糖。售货机只有一个口来接收五分的硬币和一角的硬币,而且每次只能有一个硬币投入。一个机械传感器可以指示出这个口中投入的是五分的或是一角的硬币,控制器的输出控制着单包的口香糖通过出物口送到消费者的手里。
更进一步补充:如果一个顾客送入的是两个一角的硬币,那么他就能找回一个5分的硬币。每次口香糖输出后,状态机就会自动复位。
1.深入理解这个问题
设计状态机的第一步是理解所要设计的问题。于是我们画了一个模块图来理解输入和输出。
N(nickel)表示当一个五分硬币投入硬币投入口中产生的一个脉冲,D(dime)表示当一个一角的硬币投入产生的一个脉冲,open表示机器收到一角五分后产生打开物品输出口。
2. 理论上的表达(Abstract Representations)
一旦你理解了这个控制模型,就该设计更加完全具体的理论表达(abstract representation)。列举所有可能的输入和系统的构造,对确定一个有限状态机的各个状态非常有用。
下面为可能的输入:
- 三个五分的硬币顺序投入:
- 先投两个五分的硬币,再投一个一角的硬币:
- 先投一个五分的硬币,再投一个一角的硬币:
- 先投一个一角的硬币,再投一个五分的硬币:
- 顺序投入两个一角的硬币:
举个例子,如果机器顺序收到三个五分硬币,则状态机将走s0,s1,s3,s7 状态。
为了保持状态图的简单和可读性,我们的状态图中仅仅包括了引起状态改变的转变。例如,对于状态s0,如果N,D都无效,我们就默认状态仍然为s0。同样,我们也只在状态机中包括了输出有效(open)。
3.状态的最简化(State Minimization )
这九个状态并不是最优的状态。譬如,s4,s5,s6,s8这四个状态有着同样的功能,他们应该被组合成单个状态。
为了尽可能的减少状态机的数量,我们可以认为,不管是收到一角硬币,或者是两个五分的硬币,对状态机来说效果都是一样的。
我们用四个状态机就实现了前面所用九个状态机所能完成的过程。作为一个比较有用的简略描述,从状态10¢
到 15¢.,我们将条件标志成"N, D",表示如果N有效,或者D有效,状态将转换到15¢。
4.状态编码(State Encoding )
通过上面的做法,我们得到了状态机的最小状态数目,但是这仍然是一个符号,下面的图就是一个状态转换表,下一步就是状态编码。
用这种编码状态机方法能够对状态机综合所需要的硬件资源产生较大的影响。状态机自然的被按照以下编码,
5.实现(Implementation)
在选定好实现的存储单元后,下一步就是如何实现状态转换表了,我们将看到基于D触发器和J-K触发器的实现方式。
我们可以根据状态转换表直接得出下面最简的等式:
用逻辑门来实现:一共用了8个门,和两个触发器。
为了用J-K触发起来实现状态机,我们重新映射专门为J-K触发器而改变状态转换表。
用J-K触发器的方法用了七个门,和两个触发器,适度的减少了硬件的资源。
6.讨论
我们主要通过设计一个简单的自动售货机来描述了一个完全的有限状态机的设计过程我们采用了状态图的方法。
因为不止一个状态图能得到同样的输入输出功能,找到一个尽可能少的状态的描述方法是非常重要的。这样通常会减少状态机实现的复杂度。例如,这个例子开始时,我们用了9个状态,这样就需要4个触发器,但是通过优化状态,我们将四个触发器减少到两个触发器。
一旦我们得到了一个最优化的状态描述,下一步就是选择一个好的状态编码方式,正确的编码方式能够更深一步的减少下一状态和输出模块所需要的逻辑门。在这个例子中,我们仅仅用了最明显,最简单的状态赋值。
最后一步是选择适合状态寄存器的触发器类型。在这个例子中,基于D触发器的实现方式更为易懂。我们不用重新映射状态转换表,但是我们比基于J-K触发器的实现方式多用了一些门。
7.用mealy 和moore状态机来实现区别:
对于moore状态机,输出赋值在他被声明的状态机里面,输入条件的改变被标志在弧线上,组合逻辑功能能被合理的作为弧线上的标志合理的接受。
对于mealy状态机,输出被放在状态转换弧线上。输入和输出被坚决的分开了,譬如,如果我们在10¢
状态 ,D或者N有效的话,OPEN将会被有效。任何一个N后者D产生的脉冲都会导致口香糖被错误的送出。
四:写状态机需要注意的问题:
设计方案完全取决于设计要求:面积,速度。
1:如果有可能的话,选择output = state 类型状态机是一个理想的方案,因为速度最快,使用的面积最小。
2:在设计状态机时,对于所有的输出信号在每一个时钟周期的取值,都必须作出非常明确的定义。也就是说在每一个状周期里面,对每一个输出值,在该状态可能的各种情况下,都必须有明确的赋值。否则,综合结果会多很多不必要的门。
这是因为:由于没有在每一个状态周期里面给输出信号赋值,因而需要有硬件来维持原来的输出信号值保持不变。综合工具解决的唯一办法:把输出信号值反馈给多路选择器,当某一时钟周期没有给输出信号赋值时,多路选择器再把这个值送给输出信号,使得输出信号值得以维持不变。(这就是锁存LATCH效应)
3:注意消除锁存器,看下面的例子
type state_type is (grant0,
grant1, idle0, idle1);
signal state, next_state:
state_type;
begin
state_diagram: process (state, req0, req1) begin
case state is
when grant0 =>
gnt0 <= '0'; gnt1 <= '1';
if req0 = '1' and req1 = '0' then
next_state <= grant1;
elsif (req0 and req1) = '1' then
next_state <= idle0;
else
next_state <= grant0;
end if;
when grant1 =>
gnt0 <= '1'; gnt1 <= '0';
if req0 = '0' and req1 = '1' then
next_state <= grant0;
elsif (req0 and req1) = '1' then
next_state <= idle1;
else
next_state <= grant1;
end if;
when idle0 =>
gnt0 <= '1'; gnt1 <= '1';
if req1 = '0' then
next_state <= grant1;
elsif req0 = '0' and req1 = '1' then next_state
<= grant0;
else
next_state <= idle0;
end if;
when idle1 =>
gnt0 <= '1'; gnt1 <= '1';
if req0 = '0' then
next_state <= grant0;
elsif req0 = '1' and req1 = '0' then next_state
<= grant1;
else
next_state <= idle1;
end if;
end case;
end process;
人们很可能会忽略case语句每个分支的else子句。也就是说,如果没有提供else子句,那么next_state应该保留它以前的值。它所带来的问题是,为了保持它原来的值,必须产生一个混合锁存器。但这并不是所希望的结果。下面讨论一下其中的原因。假定状态机处于idle0,而信号req1在下一个时钟边缘之前早就变化了,从1到0,然后又回到1。req1从1到0时,执行state_diagram过程,next_state赋值为grant1;当req1变回1时,再次执行该过程,这一次,if和elseif子句为假。由于没有一个else子句规定next_state应该为idle0,因而next_state仍将保持它以前的值,grant1。在下一个时钟的上升沿,状态机将在它不应该变化的时候发生变化。因此,利用描述组合逻辑的过程产生不需要的锁存器时,应该小心。幸运的是,对描述组合逻辑的所有过程来说,它们总是包含一个else子句,能够明确地指出在该过程中被赋值的所有信号的值。
|