FPGA--有限状态机(FSM)的设计

学习有限状态机(FSM)的设计

  • 学习有限状态机FSM的设计
  • 状态机设计
    • 1状态机分类
    • 2有限状态机的状态编码
      • 二进制码
      • 格雷码
      • 独热码
      • 自定义编码
      • 编码总结
    • 3状态机的设计准则
      •  基本准则
      •  一般步骤
    • 4状态机的描述方法
    • 5状态机模板
      • 一段式
      • 两段式
      • 三段式一
      • 三段式格式 二onehot 编码
  • 总结

什么是状态机?简单来说,就是通过不同的状态迁移来完成一些特定的顺序逻辑。
FSM定义:
一个有限状态机是一个设备,或者是一个设备模型,具有有限数量的状态,它可以在任何给定的时间根据输入进行操作,使得一个状态变换到另一个状态,或者是使一个输入或者一种行为的发生。一个有限状态机在任何瞬间只能处在一种状态。
它的优点:
1.编程快速简单,2.易于调试,3.很少的计算开销,4.直觉性,5.灵活性。

FPGA--有限状态机(FSM)的设计_第1张图片
图1-1 状态机图例

无论是在C\C++等的高级语言编程,还是用硬件描述语言(verilog HDL)中都会涉及到相关的知识点,学习下来作为自己的知识储备确是一件幸事。


状态机设计

1、状态机分类

如果时序逻辑的输出不但取决于状态还取决于输入,称为 Mealy 状态机。如果输入变化,这类状态机的输出可能在一个时钟周期的中间跟着改变。

FPGA--有限状态机(FSM)的设计_第2张图片
图1-2 Mealy 状态机

而有些时序逻辑电路的输出只取决于当前状态,这样的电路就称为 Moore状态机。这类状态机输出和输入成隔离状态:输出将在剩余的时钟周期内保持稳定(为一个常数),即使输入在时钟周期内变化。

FPGA--有限状态机(FSM)的设计_第3张图片
图1-3 Moore状态机

2、有限状态机的状态编码

常用的编码有三种:二进制编码、 格雷码 (Gray) 、独热码 (one-hot) 编码。另外,还可以自定义编码,比如在高速设计中以状态编码作为输出。

二进制码

二进制码从一个状态转换到相邻状态时,可能有多个比特位发生变化,易产生中间状态转移问题,状态机的速度也要比采用其它编码方式慢。

格雷码

格雷码两个相邻的码值仅有一位就可区分,这将会减少电路中相邻物理信号线同时变化的情况,因而可以减少电路中的电噪声。Johnson码也有同样的特点,但是要用较多的位数。

独热码

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

自定义编码

自定义编码相对灵活。在设计高速电路时,常常有必要使状态机的输出与时钟几乎完全同步。有一个办法是把状态变量(也可能是状态变量中的几位)直接用作输出,称为输出编码的状态指定。属于 Moore 状态机。但这种方法也有缺点,就是开关的维持时间必须与状态维持的时间一致,需要增加状态才能实现。
设计高速状态机时,在输出逻辑 G 后面再加一组与时钟同步的寄存器输出流水线寄存器,让 G 所有的输出信号在下一个时钟跳变沿时同时存入寄存器组,即完全同步地输出,把这种输出称为流水线化的输出。

编码总结

从设计大小的角度来看,在小设计中可以考虑使用 Gray 码或 one-hot。大设计中,由于现在技术进步几乎不用考虑逻辑资源不够的问题,可以考虑使用one-hot 编码以提高速度。至于顺序二进制编码,一般不予考虑。但是要达到最佳性能,需要使用更高级的编码算法,针对给定的状态机进行分析。
从 FPGA/CPLD 的角度来看,由于 CPLD 提供更多的组合逻辑资源,而 FPGA提供更多的触发器资源,所以 CPLD 多使用 Gray 码,而 FPGA 多使用 one-hot编码。

3、状态机的设计准则

1 基本准则

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

2 一般步骤

  1. 逻辑抽象得出状态转换图
      就是把给出的一个实际逻辑关系表示为时序逻辑函数,可以用状态转换表来描述,也可以用状态转换图来描述。这就需要:
      1) 分析给定的逻辑问题,确定输入变量、输出变量以及电路的状态数。通常是取原因(或条件)作为输入变量,取结果作为输出变量。
      2) 定义输入、输出逻辑状态的含义,并将电路状态顺序编号。
      3) 按照要求列出电路的状态转换表或画出状态转换图。
    这样,就把给定的逻辑问题抽象到了一个时序逻辑函数了。

  2. 状态化简
      如果在状态转换图中出现这样两个状态,它们在相同的输入转换到同一状态去,并得出一样的输出,则称为等价状态。显然等价状态是重复的,一合并为一个电路的状态数越少,存储电路也就越简单。状态化简的目的就是在于将等价状态尽可能地的合并,以得到最简的状态转换图。

  3. 状态分配

  4. 选定触发器的类型并求出状态方程、驱动方程和输出方程。

  5. 按照方程得出逻辑图
      用Verilog HDL来描述有限状态机,可以充分发挥硬件描述语言的抽象建模能力,使用always块语句和case(if)等条件语句及赋值语句即可方便实现。具体的逻辑化简、逻辑电路到触发器映射均可有计算机自动完成。步骤中的2、4、5不再需要人为干预,使电路设计工作得到简化,效率也有很大的提高。
    ———————————–节选之夏宇闻老师的Verilog HDL数字系统块设计教程(第2版)

4、状态机的描述方法

(1)一段式
使用一个always模块包含整个状态机他不符合将时序和组合逻辑分开描述的Coding Style,而且在描述当前状态时要考虑下个状态的输出,整个代码不清晰,不利于维护修改,并且不利于附加约束,不利于综合器和布局布线器对设计的优化。一般来说,一段式代码长度会比两段式冗长大约80%到150%左右。

FPGA--有限状态机(FSM)的设计_第4张图片
图1-4 一段式状态机

(2)两段式
使用两个always模块,其中一个always模块采用同步时序的方式描述状态转移,而另一个模块采用组合逻辑的方式判断状态转移条件、描述状态转移规律,称两段式FSM描述方法;为了使FSM描述清晰简洁、易于维护、易于附加时序约束,使综合器和布局布线器更好的优化设计,推荐使用两段式FSM描述方法。请注意,虽然下一状态寄存器 NS 为寄存器类型,但是在两段式 FSM 的判断状态转移条件的 always 块中,实际对应的真实硬件电路是纯组合逻辑电路。

FPGA--有限状态机(FSM)的设计_第5张图片
图1-5 两段式状态机

(3)三段式
使用三个 always 模块,一个 always 模块采用同步时序的方式描述状态转移,一个采用组合逻辑的方式判断状态转移条件、描述状态转换规律,第三个 always模块使用同步时序电路描述每个状态的输出,称三段式写法。

FPGA--有限状态机(FSM)的设计_第6张图片
图1-6 三段式状态机

综上,得出三种描述 FSM 方法的比较表

FPGA--有限状态机(FSM)的设计_第7张图片

5、状态机模板

一段式

###两段式

两段式

二段式格式(组合逻辑输出)
always @ (posedge clk or negedge nrst)
if (!nrst)
  CS <= IDLE;
else
  CS <=NS;
  
//敏感列表必须完整:当前状态及所有相关组合逻辑输入
always @ (nrst or CS or i1 or i2) begin
//第二段 always 中,组合逻辑电平要维持超过一个 clock,仿真时注意。
  NS = 3'b0; //此处赋值一般有三种方式:(1) X,(2) IDLE (3)不改变
        //注:因为仿真时“X”视为“don’t care”,所以赋值“X”适合 debug
        //而实现时,赋值“IDLE”或者“0”
  ERROR_out;
  case (CS)
    IDLE: begin
      IDLE_out;
      if (~i1) NS = IDLE;
      if (i1 && i2) NS = S1;
      if (i1 && ~i2) NS = ERROR;// 里面判断条件一定要包含所有情况(即 i1 和
      i2 的四种情况)!可以用 else 保证包含完全。
    end
    S1: begin
      S1_out;
      if (~i2) NS = S1;
      if (i2 && i1) NS = S2;
      if (i2 && (~i1)) NS = ERROR;
    end
    S2: begin
      S2_out;
      if (i2) NS = S2;
      if (~i2 && i1) NS = IDLE;
      if (~i2 && (~i1)) NS = ERROR;
    end
    ERROR: begin
      ERROR_out;
      if (i1) NS = ERROR;
      if (~i1) NS = IDLE;
    end
  endcase
end
task S1_out;//对于输出一般用组合逻辑描述,比较简便的方法是用 task 将输出封装起来。
  {o1,o2,err} = 3'b100;
Endtask

三段式(一)

FPGA--有限状态机(FSM)的设计_第8张图片
图1-7 三段式状态机示例

module fsm_cc8_3r(output reg y1, y2, y3,input jmp, go, sk0, sk1, clk, rst_n);
parameter S0 = 4'b0000,
      S1 = 4'b0001,
      S2 = 4'b0010,
      S3 = 4'b0011,
      S4 = 4'b0100,
      S5 = 4'b0101,
      S6 = 4'b0110,
      S7 = 4'b0111,
      S8 = 4'b1000,
      S9 = 4'b1001;
reg [3:0] state, next;
  
//第一个进程,同步时序 always 块,格式化描述次态寄存器迁移到现态寄存器
always @(posedge clk or negedge rst_n) //异步复位
  if (!rst_n)
    state <= S0;
  else
    state <= next; //注意,使用的是非阻塞赋值
//第二个进程,组合逻辑块,判断状态转移条件,电平触发,敏感列表要完备
always @(state or jmp or go or sk0 or sk1) 
 begin
    next = 4'bS0; //要初始化,使得系统复位后能进入正确的状态,和 case 中的 default 作用相当,但
            //不完全等同于把其放在 default 里赋值。因为根据顺序执行语句,这里对所有未
            //在case 语句中赋值的变量赋值。

    case (state) //每一状态的条件判断要完备!
    S0 : if (!go) next = S0; //阻塞赋值
      else if (jmp) next = S3;
      else next = S1;
    S1 : if (jmp) next = S3;
        else next = S2;
    S2 : if (jmp) next = S3;
        else next = S9;
    S3 : if (jmp) next = S3;
        else next = S4;
    S4 : if (jmp) next = S3;
        else if (sk0 && !jmp) next = S6;
        else next = S5;
    S5 : if (jmp) next = S3;
        else if (!sk1 && !sk0 && !jmp) next = S6;
        else if (!sk1 && sk0 && !jmp) next = S7;
        else if ( sk1 && !sk0 && !jmp) next = S8;
        else next = S9;
    S6 : if (jmp) next = S3;
        else if (go && !jmp) next = S7;
        else next = S6;
    S7 : if (jmp) next = S3;
        else next = S8;
    S8 : if (jmp) next = S3;
        else next = S9;
    S9 : if (jmp) next = S3;
        else next = S0;
  endcase
 end

//第三个进程,同步时序 always 模块,格式化描述次态寄存器输出
always @(posedge clk or negedge rst_n)
  if (!rst_n) 
  begin //初始化
    y1 <= 1'b0;
    y2 <= 1'b0;
    y3 <= 1'b0;
  end
  else begin //可以防止 latch,如上述。
    y1 <= 1'b0;
    y2 <= 1'b0;
    y3 <= 1'b0;
  case (next) //注意是下一个状态,即预判
    S0, S2, S4, S5 : ; // default outputs
    S7 : y3 <= 1'b1; //注意是非阻塞逻辑
    S1 : y2 <= 1'b1;
    S3 : begin
      y1 <= 1'b1;
      y2 <= 1'b1;
    end
    S8 : begin
      y2 <= 1'b1;
      y3 <= 1'b1;
    end
    S6, S9 : begin
      y1 <= 1'b1;
      y2 <= 1'b1;
      y3 <= 1'b1;
    end
  endcase
 end
endmodule

三段式格式 (二)–onehot 编码

parameter S0 = 0, S1 =1, S2 =2, S3 =3, S4 =4, S5= 5, S6 = 6, S7 = 7, S8 = 8, S9 =9;
reg [9:0] current_state, next_state;

//第一个进程,同步时序 always 模块,格式化描述次态寄存器迁移到现态寄存器
always @ (posedge clk or negedge rst_n) //异步复位
  if(!rst_n) 
    begin
    current_state <=0;
    current_state[S0] <=1’b1;
    end
  else
    current_state <= next_state; //注意,使用的是非阻塞赋值
    
//第二个进程,组合逻辑 always 块,判断状态转移条件, onehot 需要加综合约束
full case
always @ (current_state or input1 or input2 ) //电平触发,包括所有组合逻辑输入
  begin
    next_state = 0; //初始化(0 或 IDLE),使系统复位后能进入正确的状态
    case(1’b1) // synthesis parallel_case full_case
      current_state[S0]: if(...)
      next_state [S1]= 1’b1; //阻塞赋值
      current_state[S1]: if(...)
      next_state [S2]= 1’b1; //阻塞赋值
      ...
    endcase
  end
 
//第三个进程,同步时序 always 模块,格式化描述次态寄存器输出
always @ (posedge clk or negedge rst_n)
  if(!rst_n) begin
    Out1<= 1’b0;
    Out2<= 1’b0;
    Out3<= 1’b0;
  end
  else begin
    Out1<= 1’b0;
    Out2<= 1’b0;
    Out3<= 1’b0;
    case(1’b1) //注意是下一个状态,即预判
      next_state [S0]: out1 <= 1'b1; //注意是非阻塞逻辑
      next_state [S1]: out2 <= 1'b1;
      …
    endcase //注意,没有 default 选项了
  end

总结

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

参考:
1、有限状态机(FSM)设计原理 http://blog.csdn.net/Pieces_thinking/article/details/76132201?locationNum=1&fps=1
2、状态机设计 V2.0 by foreveryoung http://www.cnblogs.com/foreveryoung/

你可能感兴趣的:(个人随记,FPGA,FPGA,状态机)