有限状态机(FSM)设计原理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Pieces_thinking/article/details/76132201

1.1 概述

  有限状态机是由寄存器组和组合逻辑构成的硬件时序电路。有限状态机的状态(即由寄存器组的1和0的组合状态所构成的有限个状态)只可能在同一时钟跳变沿的情况下才能从一个状态转向另一个状态。
  有限状态机的下一个状态不但取决于各个输入值,还取决于当前所在状态。这里指的是米里Mealy型有限状态机,而莫尔Moore型有限状态机的下一个状态只决于当前状态。
 


这里写图片描述
图 1  Mealy型状态转移图

1.2 状态机的描述方法

  状态机的描述方法多种多样,将整个状态机写到1个always 模块里,在该模块中既描述状态转移,又描述状态的输入和输出,这种写法一般被称为一段式FSM 描述方法;还有一种写法是使用两个always 模块,其中一个always 模块采用同步时序的方式描述状态转移,而另一个模块采用组合逻辑的方式判断状态转移条件,描述状态转移规律,这种写法被称为两段式FSM 描述方法;还有一种写法是在两段式描述方法的基础上发展而来的,这种写法使用3 个always模块,一个always 模块采用同步时序的方式描述状态转移,一个采用组合逻辑的方式判断状态转移条件,描述状态转移规律,第三个always 模块使用同步时序电路描述每个状态的输出,这种写法称为三段式写法。

1.3 FSM的状态编码

  二进制码(Binary)和格雷码(Gray)属于压缩状态编码,这种编码的优点是使用的状态向量最少,但是需要较多的逻辑资源用来状态译码。二进制码从一个状态转换到相邻状态时,可能有多个比特位发生变化,易产生中间状态转移问题,状态机的速度也要比采用其它编码方式慢。格雷码两个相邻的码值仅有一位就可区分,这将会减少电路中相邻物理信号线同时变化的情况,因而可以减少电路中的电噪声。Johnson码也有同样的特点,但是要用较多的位数。
独热码(One-hot)指对任意给定的状态,状态寄存器中只有l位为1,其余位都为0。n状态的有限状态机需要n个触发器,但这种有限状态机只需对寄存器中的一位进行译码,简化了译码逻辑电路,额外触发器占用的面积可用译码电路省下来的面积抵消。当设计中加入更多的状态时,译码逻辑没有变得更加复杂,有限状态机的速度仅取决于到某特定状态的转移数量,而其它类型有限状态机在状态增加时速度会明显下降。独热码还具有设计简单、修改灵活、易于综合和调试等优点。独热码相对于二进制码,速度快但占用面积大。

二进制码 格雷码 独热码 parameter[2:0] parameter[2:0] parameted3:0] S0=3’d0, S0=3’b000, S0=4’b0000, Sl=3’d1, Sl=3’b001, S1=4’b0001, S2=3’d2, S2=3’b011, S2=4’b0010, S3=3’d3, S3=3’b010, S3=4’b0100, S4=3’d4, S4=3’b110, S4=4’b1000,

1.4 FSM的Verilog HDL 设计的基本准则及有限状态机设计的一般步骤

1.4.1 基本准则

(1) 一个Verilog模块至多描述一个有限状态机。这样不仅可以简化状态的定义、修改和调试,还可以利用一些EDA工具来协助设计。
(2) 使用参数给状态赋值,而不是用宏定义(‘define)。因为’define宏定义在编译时自动替换整个设计中所定义的宏,而parameter仅仅定义模块内部的参数,定义的参数不会与模块外的其它状态机混淆。
(3) 用always模块写组合逻辑时,采用阻塞赋值,而在always块中建立时序电路时,用非阻塞赋值。这样才能保证有限状态机综合前和综合后仿真的一致性。

1.4.2 一般步骤

1. 逻辑抽象得出状态转换图
  就是把给出的一个实际逻辑关系表示为时序逻辑函数,可以用状态转换表来描述,也可以用状态转换图来描述。这就需要:
  1) 分析给定的逻辑问题,确定输入变量、输出变量以及电路的状态数。通常是取原因(或条件)作为输入变量,取结果作为输出变量。
  2) 定义输入、输出逻辑状态的含义,并将电路状态顺序编号。
  3) 按照要求列出电路的状态转换表或画出状态转换图。
这样,就把给定的逻辑问题抽象到了一个时序逻辑函数了。
2.状态化简
  如果在状态转换图中出现这样两个状态,它们在相同的输入转换到同一状态去,并得出一样的输出,则称为等价状态。显然等价状态是重复的,一合并为一个电路的状态数越少,存储电路也就越简单。状态化简的目的就是在于将等价状态尽可能地的合并,以得到最简的状态转换图。
3.状态分配
  状态分配又称状态编码。通常有很多编码方法,编码方案选择得当,设计的电路可以简单,反之,选的不好,则设计的电路就会复杂很多。在实际设计中,须综合考虑电路复杂度与电路性能之间的折中。在触发器资源丰富的FPGA或ASIC设计中,采用独热码即可用使电路性能得到保证又可以充分利用其触发器数量多的优势,也可以采用输出编码的状态指定来简化电路结构,并提高状态机的运行速度。
  
4.选定触发器的类型并求出状态方程、驱动方程和输出方程。
5.按照方程得出逻辑图
  用Verilog HDL来描述有限状态机,可以充分发挥硬件描述语言的抽象建模能力,使用always块语句和case(if)等条件语句及赋值语句即可方便实现。具体的逻辑化简、逻辑电路到触发器映射均可有计算机自动完成。步骤中的2、4、5不再需要人为干预,使电路设计工作得到简化,效率也有很大的提高。
———————————–节选之夏宇闻老师的Verilog HDL数字系统块设计教程(第2版)

1.5 设计举例

现以一个有限状态机设计的一般步骤
1. 逻辑抽象得出状态转换图

2.状态化简

3.状态分配

4.选定触发器的类型并求出状态方程、驱动方程和输出方程。
5.按照方程得出逻辑图

—————-节选之夏宇闻老师的Verilog HDL数字系统块设计教程(第2版)简单的交通灯控制电路为例介绍用Verilog HDL编写Moore型状态机的方法。
  功能描述:南北方向为主干道(L3~L1),绿灯时间为29S;东西方向为次干道(L6~L4),绿灯时间为19S;在一个方向(A)从红转绿前3S,另一方向(B)黄灯亮3S,这是为了B方向的人或车在黄灯亮时就停止行走,也使已经走出的人或车走尽,A方向的人和车再通行。
  状态S1:南北红灯亮,东西绿灯亮(时间为19秒)
  状态S2:南北红灯亮,东西黄灯亮(时间为3秒)
  状态S3:南北绿灯亮,东西红灯亮(时间为19秒)
  状态S4:南北绿灯亮,东西红灯亮(时间为19秒)
然后如此循环。

1.5.1一段式(one always)FSM

一段式(one always)FSM程序如下,其中部分代码己做解释:

module traffic_FSM(out,clock,clk,rst);
output [5:0] out;    //灯的输出状态:out[2:0]代表南北方向的红黄绿灯;
out[5:3]代表东西方向的红黄绿灯;
output clock;   //分频后的时钟信号;
input clk,rst;
reg [2:0] num;
reg [4:0] L_time;  //亮灯时间;
wire clock;
reg [5:0] out;
reg [3:0] C_state;  //当前状态
parameter [3:0]            S1 = 4'b0001,     //用独热码表示状态变量;
                                   S2 = 4'b0010,
                                   S3 = 4'b0100,
                                   S4 = 4'b1000;
fenpin clock1(clock,clk,rst);  //分频模块调用

always @ (posedge clock or negedge rst)
begin
       if(!rst)
              L_time <= 5'd19;
       else begin
                     L_time <= L_time -1;   //记时;
                     case(C_state)          //判断状态,并进行时间复位;
                            S1:begin  if(L_time==0) L_time <= 5'd2;  end
                            S2:begin  if(L_time==0) L_time <= 5'd28;  end
                         S3:begin  if(L_time==0) L_time <= 5'd2;   end
                         S4:begin  if(L_time==0) L_time <= 5'd18;  end
                      endcase
               end
end

always @ (posedge clock or negedge rst)
begin
       if(!rst)
              begin  C_state <= S1;  end
       else begin
                     case(C_state)      //判断状态转移,并输出结果;
                            S1:begin
                                    if(L_time==0)
                                          begin
                                                 C_state <= S2;
                                                 out <= 6'b010100;
                                          end
                                    else begin
                                                 C_state <= S1;
                                                 out <= 6'b001100;  //状态S1:南北红灯,东西绿灯;
                                            end
                               end
                            S2:begin
                                    if(L_time==0)
                                          begin
                                                 C_state <= S3;
                                                 out <= 6'b100001;
                                          end
                                    else begin
                                                 C_state <= S2;
                                                 out <= 6'b010100;  /状态S2:南北红灯,东西黄灯;
                                            end
                               end
                         S3:begin
                                    if(L_time==0)
                                          begin
                                                 C_state <= S4;
                                                 out <= 6'b100010;
                                          end
                                    else begin
                                                 C_state <= S3;
                                                 out <= 6'b100001; /状态S3:南北绿灯,东西红灯;
                                            end
                               end
                            S4:begin
                                    if(L_time==0)
                                          begin
                                                 C_state <= S1;
                                                 out <= 6'b001100;
                                          end
                                    else begin
                                                 C_state <= S4;
                                                 out <= 6'b100010; /状态S4:南北黄灯,东西红灯;
                                            end
                               end
                            default:out <= 6'b010010;
                     endcase
              end
end
endmodule
//**********10分频**********//
module fenpin(clock,clk,rst );
output clock;
input clk,rst;
reg clock;
reg [2:0] num;

always @ (posedge clk or negedge rst)
begin
       if(!rst)
              begin
                     num <= 0;
                     clock <= 0;
              end
       else if(num[2])
                     begin
                            clock <= !clock;
                            num <= 0;
                     end
       else begin
                     clock <= clock;
                     num <= num + 1;
               end
end
endmodule

  为了便于仿真观察结果,该程序假设FPGA的频率为10HZ,所以10分频后的clock时钟信号的频率为1HZ,即一个脉冲周期为1秒,用于进行交通灯的计时。其实,如果FPGA的晶振为50MHZ,则只需改变num的计数值为(50M/2—1),则会现实精确的1S计时。
  综合后的硬件电路结构如图:
  


这里写图片描述

  在完成翻译、映射、布局布线之后的后仿真结果如图:


这里写图片描述

  如图会显示,0C、14、21、22四个状态循环出现,实现交通灯自动控制。

小结:
  (1)分频部分应该做为一个单独的底层模块来设计,再从顶层模块中调用。
  (2)计数部分即L_time的计数和复位操作需要在一个单独的always块中设计;否则,若一个变量在两个always块中都被赋值时,则出提示出错(起初就犯了这样的错误导致综合不成功)。要认真体会Verilog HDL中的并行执行方式,这点和C语言差别较大。
  (3)由于一段式描述方法不符合将时序和组合逻辑分开描述的 Coding Style(代码风格),而且代码冗长、不清晰,不利于附加约束,不利于综合器和布局布线器对设计的优化,所以不提倡用此方法。
  两段式(two-always)的FSM交通灯程序代码这里就不做介绍了。
  两段式(two-always)描述的核心思想是一个always 模块采用同步时序方式描述状态转移;另一个模块采用组合逻辑方式判断状态转移条件,描述状态转移规律。虽然两段式FSM描述方法有很多好处,但是它有一个明显的弱点,就是其输出一般使用组合逻辑描述,而组合逻辑易产生毛刺等不稳定因素,并且在FPGA/CPLD等逻辑器件中过多的组合逻辑会影响实现的速率(这点与ASIC设计不同),所以如果时序允许则尽量在后级电路对FSM组合逻辑输出用寄存器寄存一个节拍,这样可以有效地消除毛刺。但是在很多情况下设计不允许插入额外的节拍,所以就需要使用三段式FSM,用一个同步时序always模块寄存FSM的输出。

1.5.2 三段式(three-always)FSM

三段式(three-always)FSM交通灯代码如下:

module FSM_3always(out,clock,clk,rst);
output [5:0] out;
output clock;
input clk,rst;
reg [4:0] L_time;
wire clock;
reg [5:0] out;
reg [3:0] CS,NS;
parameter [3:0]   //独热码且有零初始状态;
              IDLE = 4'b0000,
              S1 = 4'b0001,
              S2 = 4'b0010,
              S3 = 4'b0100,
              S4 = 4'b1000;
fenpin fenpin_10(clock,clk,rst);//分频模块调用;

always @ (posedge clock or negedge rst)  //时序模块描述状态转移
begin
       if(!rst)
              CS <= IDLE;
       else
              CS <= NS;
end
always @ (CS or L_time or rst)   //组合模块描述状态转且电平敏感表必须列完整,否则会产
begin                         生隐含锁存器
//     NS = 4'bx;
       case(CS)
              IDLE:begin
                      if(!rst) NS = IDLE;
                            else NS = S1;
                      end
              S1:  begin
                         if(L_time==0) NS = S2;
                      end
              S2:  begin
                            if(L_time==0) NS = S3;
                      end 
              S3:  begin
                            if(L_time==0) NS = S4;
                      end      
              S4:  begin
                            if(L_time==0) NS = S1;
                      end      
           default:NS = IDLE;
       endcase
end
always @ (posedge clock or negedge rst)
begin
       if(!rst)
              out <= 6'b010010;
       else  begin
                      case(NS)
                            IDLE:   out <= 6'b010010;
                            S1:     out <= 6'b001100;
                            S2:     out <= 6'b010100;
                            S3:     out <= 6'b100001;
                            S4:     out <= 6'b100010;
                            default:out <= 6'b010010;
                      endcase
                end
end
always @ (posedge clock or negedge rst)         //用于L_time的计数和复位;
begin
       if(!rst)
              L_time <= 5'bx;
       else begin
                     L_time <= L_time - 1;
                     case(CS)
                            S1:    begin
                                                 if(L_time==0) L_time <= 5'd2;
                                      end
                            S2:    begin
                                                 if(L_time==0) L_time <= 5'd28;
                                      end      
                            S3:    begin
                                                 if(L_time==0) L_time <= 5'd2;
                                      end      
                            S4:    begin
                                                 if(L_time==0) L_time <= 5'd18;
                                      end      
                            default:L_time <= 5'd18; //工作之前也就是S1状态到来之前不断的赋初值
                     endcase
              end                                
end
endmodule

  硬件电路结构如图:


这里写图片描述

  前仿真结果:


这里写图片描述

   由于和一段式的相同,程序中省略了分频模块的代码。
  这段程序在交通灯开始工作前(即rst为低电平时)设一个初始状态IDLE,即东西南北方向的灯都只亮黄灯;当开始工作时,仍会实现S1-S2-S3-S4-S1不断循环。
  如图,在综合后的前行为仿真可以很理想的实现效果,但是经过翻译、映射、布局布线之后的后行为仿真则无法出现正确的效果,不知道是什么原因?

小结:
  (1)三段式描述FSM的输出时,只需指定case敏感表为次态寄存器,然后直接在每个次态的case分支中描述该状态的输出即可,根本不用考虑转移条件。本例的FSM很简单,如果设计的FSM复杂一些的话,三段式描述的优势就会凸显出来。
  (2)三段式描述方法与两段式描述相比,虽然代码结构复杂了一些,但是换来的却是使FSM 做到了同步寄存器输出,消除了组合逻辑输出的不稳定性,而且更利于时序路径分组,一般来说,其在FPGA/CPLD 等可编程逻辑器件上的综合与布局布线效果更佳。

1.5.3 总结

  一段式FSM交通灯占用硬件资源表:


这里写图片描述

  三段式FSM交通灯占用硬件资源表:


这里写图片描述

  从上面两表对比可发现三段式所用到的LUT和Slices都比一段式的多,而触发器相对较少的前提上又多了5个锁存器,所以三段式明显占用较多的资源,但是其在速度方面较快。

  有限状态机是实现高效率、高可靠性数字系统的重要全知途径,使用Verilog HDL描述有限状态机时可以有不同的状态编码方式和描述风格,实际应用中应根据具体情况和要求来选择,编码方式的选择跟器件结构和状态数目有关,描述风格则推荐使用两段式和三段式,应该尽量避免使用一段式。

猜你喜欢

转载自blog.csdn.net/Pieces_thinking/article/details/76132201