1.有限状态机

1.1 概述

有限状态机是指输出取决于过去输入部分和当前输入部分的时序逻辑电路。有限状态机又可以认为是组合逻辑和寄存器逻辑的一种组合。状态机特别适合描述那些发生有先后顺序或者有逻辑规律的事情,其实这就是状态机的本质。状态机就是对具有逻辑顺序或时序规律的事件进行描述的一种方法

在实际的应用中根据状态机的输出是否与输入条件相关,可将状态机分为两大类,即摩尔 (Moore) 型状态机和米勒 (Mealy) 型状态机。

1  Mealy型状态转移图

 

1.2 状态机的描述方法

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

1.3 FSM的状态编码

二进制码(Binary)和格雷码(Gray)属于压缩状态编码,这种编码的优点是使用的状态向量最少,但是需要较多的逻辑资源用来状态译码。二进制码从一个状态转换到相邻状态时,可能有多个比特位发生变化,易产生中间状态转移问题,状态机的速度也要比采用其它编码方式慢。格雷码两个相邻的码值仅有一位就可区分,这将会减少电路中相邻物理信号线同时变化的情况,因而可以减少电路中的电噪声。Johnson码也有同样的特点,但是要用较多的位数。

独热码(One-hot)指对任意给定的状态,状态寄存器中只有l位为1,其余位都为0n状态的有限状态机需要n个触发器,但这种有限状态机只需对寄存器中的一位进行译码,简化了译码逻辑电路,额外触发器占用的面积可用译码电路省下来的面积抵消。当设计中加入更多的状态时,译码逻辑没有变得更加复杂,有限状态机的速度仅取决于到某特定状态的转移数量,而其它类型有限状态机在状态增加时速度会明显下降。独热码还具有设计简单、修改灵活、易于综合和调试等优点。独热码相对于二进制码,速度快但占用面积大。

二进制码        格雷码            独热

parameter[20]         parameter[20]            parameted30]

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 FSMVerilog HDL 设计的基本准则

(1) 一个Verilog模块至多描述一个有限状态机。这样不仅可以简化状态的定义、修改和调试,还可以利用一些EDA工具来协助设计。

(2) 使用参数给状态赋值,而不是用宏定义(‘define)。因为’define宏定义在编译时自动替换整个设计中所定义的宏,而parameter仅仅定义模块内部的参数,定义的参数不会与模块外的其它状态机混淆。

(3) always模块写组合逻辑时,采用阻塞赋值,而在always块中建立时序电路时,用非阻塞赋值。这样才能保证有限状态机综合前和综合后仿真的一致性。

1.5 设计举例

现以一个简单的交通灯控制电路为例介绍用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 alwaysFSM

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

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/21),则会现实精确的1S计时。

综合后的硬件电路结构如图:

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

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

 

小结:

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-alwaysFSM

三段式(three-alwaysFSM交通灯代码如下:

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,即东西南北方向的灯都只亮黄灯;当开始工作时,仍会实现S1S2S3S4S1不断循环。

          如图,在综合后的前行为仿真可以很理想的实现效果,但是经过翻译、映射、布局布线之后的后行为仿真则无法出现正确的效果,不知道是什么原因?

 

小结:

1)三段式描述FSM的输出时,只需指定case敏感表为次态寄存器,然后直接在每个次态的case分支中描述该状态的输出即可,根本不用考虑转移条件。本例的FSM很简单,如果设计的FSM复杂一些的话,三段式描述的优势就会凸显出来。

2)三段式描述方法与两段式描述相比,虽然代码结构复杂了一些,但是换来的却是使FSM 做到了同步寄存器输出,消除了组合逻辑输出的不稳定性,而且更利于时序路径分组,一般来说,其在FPGA/CPLD 等可编程逻辑器件上的综合与布局布线效果更佳。


1.5.3 总结

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

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

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

 

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