状态机 精讲

来源:互联网 发布:淘宝员工数量 编辑:程序博客网 时间:2024/06/11 00:56

   一直以来都是在用两段式状态机,觉得挺好用的,也挺顺手的。但是今天看了特权的博客觉得自己还真是井底的啦蛤蟆呀。

 

参考了网上的一些前辈blog和自己搜集到的资料,整理了一下读书笔记,虽然内容基本上都是别人写的,但是整理起来也好累   = =

 

 

 

1. 有限状态机FSM(Finite State Machine)

 

  • 组成元素:输入、状态、状态转移条件、输出;

     

  • 可以分为两类:

    Mealy状态机:时序逻辑的输出不仅取决于当前状态,还与输入有关;

    Moore状态机:时序逻辑的输出只与当前状态有关;

     

  • 描述方式:

    ① 状态转移图:设计分析时使用,工具自动翻译的代码效率不高,适合规模小的设计;对于大规模设计,HDL更好;

    ② 状态转移表;

    ③ HDL描述;

     

  • 设计步骤:

    ① 逻辑抽象,得到状态转移图:确定输入、输出、状态变量、画状态转移图;

    ② 状态简化,得到最简的状态转移图:合并等价状态;

    ③ 状态编码:binary、gray、one-hot编码方式;

    ④ 用HDL描述;

 

2. Coding Style

 

状态机一般有三种写法,他们在速度、面积、代码可维护性等各个方面互有优劣。

 

一段式:只有一个always block,把所有的逻辑(输入、输出、状态)都在一个always block中实现;这种写法看起来很简洁,但是不利于维护。如果状态复杂一些就很容易出错。不推荐这种方法,但是在简单的状态机可以使用。

 

二段式:有两个always block,把时序逻辑和组合逻辑分隔开来。时序逻辑里进行当前状态和下一状态的切换,组合逻辑实现各个输入、输出以及状态判断。这种写法不仅便于阅读、理解、维护,而且利于综合器优化代码,利于用户添加合适的时序约束条件,利于布局布线器实现设计。在两段式描述中,当前状态的输出用组合逻辑实现,可能存在竞争和冒险,产生毛刺。则要求对状态机的输出用寄存器打一拍,但很多情况不允许插入寄存器节拍,此时使用三段式描述。其优势在于能够根据状态转移规律,在上一状态根据输入条件判断出当前状态的输出,从而不需要额外插入时钟节拍。

 

三段式:有三个always block,一个时序逻辑采用同步时序的方式描述状态转移,一个采用组合逻辑的方式判断状态转移条件、描述状态转移规律,第三个模块使用同步时序的方式描述每个状态的输出。代码容易维护,时序逻辑的输出解决了两段式组合逻辑的毛刺问题,但是从资源消耗的角度上看,三段式的资源消耗多一些,且输出比另外两种会延时一个时钟周期。

三段式模板:

[plain] view plaincopy
 
  1. always @ ( posedge clk or negedge rst_n ) begin  
  2.     if ( !rst_n )  
  3.         CS <= IDLE;  
  4.     else  
  5.         CS <= NS;  
  6. end  
  7.   
  8. always @*  begin  
  9.     NS = 'bx;           //初始化寄存器,避免生成latch  
  10.     case (CS)                   //注意为CS  
  11.         IDLE: begin  
  12.               end;  
  13.         S1: begin  
  14.             end;  
  15.        default:  
  16.             NS = 'bx;           //与硬件电路一致  
  17.     endcase  
  18. end  
  19.   
  20. always @ (posedge clk or negedge rst_n) begin  
  21.     if ( !rst_n ) begin  
  22.                   end  
  23.     else begin  
  24.             ...         //初始化一组值,避免latch  
  25.             case (NS)           //注意为NS  
  26.                 ...  
  27.                 default: ;  
  28.             endcase  
  29.            end  
  30. end  

注意:

  • 在三段式描述中,在第二个 always block判断状态转移的case使用的是当前状态CS,而第三个 always block同步时序输出的case使用的下一状态NS
  • 在第二个always block中代码开头NS有初值x,
    [plain] view plaincopy
     
    1. always @* begin  
    2.         NS = 'bx;  
    3.         case (CS)  
    4.              ...  
    5.     end  
    有两个作用:第一,仿真的时候可以很好的考察FSM的完备性,如果不完备,则进入任意状态,仿真的时候可以很快发现;第二,实现时综合器对x的处理时“don't care”,即任何没有定义的状态寄存器向量(CS)都会被忽略,综合器为其生成的电路最简洁,同时满足要求。
  • 在第二个always block的case语句的default分支
    [plain] view plaincopy
     
    1. default: NS = 'bx;  
    设置“default: NS = 'bx”与实际硬件电路相一致。如果将缺省状态设置为某一确定的状态(例如:设置"default: NS = S1",行不行呢?尽管综合器产生的逻辑和设置“default:state='bx”时相同,但是状态机的Verilog HDL模型综合前和综合后的仿真结果会不一致。因为启动仿真器时,状态机所有的输入都不确定,因此立即进入default状态。如果通过设置将状态变量设为S1,但是实际硬件电路的状态机在通电之后,进入的状态是不确定的,很可能不是S1的状态,这样就会产生不必要的冲突。因此,还是设置“default: NS = 'bx”与实际硬件电路相一致。

    状态机中状态变量初始化赋值:如果所有的状态都使用到了,可以使用x,它可以让综合器删除不必要的译码电路,使生成的电路简洁,并与设计要求一致,另外调试的时候很方便;如果状态没有使用完,有多余的状态,则不能使用x,应该设置为IDLE或者0。这样做能使状态机若偶然进入多余状态后仍能在下一时钟跳变沿时返回正常工作状态,否则会引起死锁。

  • 在第三个always block中,输出结果在case前有默认值。默认值最好为0,这样可以避免latch的产生,不仅减少了代码量,而且强调显示了case内哪个输出发生了变化

 

3. Others

 

1. 编码方式

常用的编码有三种:Binary、Gray、One-Hot编码。

Binary、Gray-code编码使用最少的触发器,较多的组合逻辑。而One-Hot 编码反之。由于CPLD更多的提供组合逻辑资源,而FPGA更多的提供触发器资源,所以CPLD 多使用Gray-code,而FPGA 多使用One-Hot编码。另一方面,对于小型设计使用Gray-code和Binary编码更有效,而大型状态机使用One-Hot更高效。

看Synplicity的文档,推荐在24个状态以上会用格雷码,在5~24个状态会用独热码,在4个状态以内用二进制码,肯定独热码比二进制码在实现FSM部分会占更多资源,但是译码输出控制简单,所以如果状态不是太多,独热码较好。状态太少译码不会太复杂,二进制就可以了。状态太多,前面独热码所占资源太多,综合考虑就用格雷码了。(Xilinx ISE 默认的FSM encoding algorithm值为auto,编写简单的 状态机测试,Binary的编码会被工具自动优化为One-Hot编码)

各编码的优缺点:

二进制码采用最简单的递增的编码方式对状态进行编码,对于n个状态的状态机,共需要 log2(n)个触发器表示所有的状态。在状态很多的情况下,可以大大减少触发器的数量,对设计的面积有积极的作用。但是在状态跳转过程中,很可能出现多位同时变化的情况,容易在 next state的生成逻辑上产生毛刺。同时,输出也是所有状态位的译码,译码逻辑多数很复杂,往往成为整个设计的关键路径。

格雷码类似二进制编码,但是采用了格雷码的编码方式,每两个相邻的状态只有一位信号变化,避免了next state上毛刺的产生。同时两个相邻状态的输出译码变得简单了,避免了复杂组合逻辑的产生。但是格雷码的这些优点都是建立在状态跳转是顺序执行的基础上的。如果状态机有很多随机跳转和分支,格雷码的实际效果和二进制码相差无几,优势荡然无存。

当然在设计中还可以采用复杂的编码方式,通过对状态跳转的分析,设计一套编码来避免二进制编码的缺点。这种做法工作量很大,而且设计不具有可维护性,实际设计中并不可取。

最后剩下的就是独热码了,也就是One-Hot。One-Hot编码方式是当前设计中最常用的状态机编码方式。One-Hot 编码在一组 0 中只有一个 1,对一个 n 个状态的 FSM 设计,需要 n 个触发器。但是在任意两个状态之间跳转都只有两位状态位变化,不会产生非常复杂的组合逻辑。各个状态之间的译码也相对简单。

编写一个很简单的状态机测试Binary & One-Hot encoding  RTL Schematic,结果如下,One-Hot方式综合出来的电路更加简洁。

P.S. 还有一种编码为 indexed One-Hot 编码,但是这种方式中的第二个always block中如果状态机跑飞,则很难回到正常状态,需要额外的逻辑进行判断,即indexed one-hot编码 + 逻辑判断才是完整的安全编码。

 

2. 避免产生latch

 

好的状态机设计时要有错误自动恢复的能力,即当进入错误的状态时,能自动的重新进入循环,避免出现"跑飞“的现象。

消除 FSM中 latches的最简易方法是在执行case语句前对输出值进行初始化赋值。

对应于编码就是对 case,if-else 语句要特别注意,要写完备的条件判断语句。, 使用“case”语句的时候要用“default”建立默认状态,使用“if-else”语句时要将所有需要判断的条件列出后,用“else”建立默认状态。

Verilog设计中可以使用”full-case“ ”parallel-case“指令来实现消除输出锁存。(一般情况下尽量不要使用这两条指令,仅在onehot状态机中使用)所谓 Full Case 是指:FSM 的所有编码向量都可以与 case 结构的某个分支或 default 默认情况匹配起来。如果一个 FSM 的状态编码是8bit,则对应的256 个状态编码(全状态编码是 2^n个)都可以与case 的某个分支或者default 映射起来。所谓 Parallel Case 是指:在 case 结构中,每个 case 的判断条件表达式有且仅有唯一的 case 语句的分支与之对应,即两者关系是一一对应关系。

 

3. 增加输出寄存器消除毛刺

 

有两个方法实现FSM输出状态寄存,一是将状态变量编码,每个输出是编码后的状态变量中的一位;二是使用三个always模块,一个 always模块采用同步时序的方式描述状态转移,一个采用组合逻辑的方式判断状态转移条件,描述状态转换规律,第三个 always模块使用同步时序电路描述每个状态的输出,又称三段式写法。

状态变量编码(摘录自《Coding And Scripting Techniques For FSM Designs WithSynthesis-Optimized, Glitch-Free Outputs》)...

三段式...

 

4. FSM初始化

 

一个完备的状态机(健壮性强)应该具备初始化(reset)状态和默认(default)状态。FPGA上电后,GSR信号拉高,对所有的寄存器、RAM等单元复位/置位,这时配置于FPGA的逻辑并未生效,所以不能保证正确的进入初始化状态。一种简单的方法是采用异步复位信号,当然也可以使用同步复位,要注意同步复位的逻辑设计。另一种方法是将默认的初始状态的编码设计为全0(带初始状态为全0的One-Ho编码),这样当GSR复位后,状态机自动进入初始状态。

 

5. 参数定义使用parameter

 

因为`define宏定义方式在编译时自动替换整个设计中所有的宏,而parameter仅仅定义模块内部的参数,不会与其他模块的参数混淆。

 

6. Full-Case和Parallel-Case综合属性

 

Full Case:FSM的所有编码向量都与case结构的某个分支或default匹配起来。例如,一个FSM的状态编码是8'b,则有2^8=256种编码方式,都要与case的某一分支或default对应起来。不完整的case语句会产生latch。

parallel case:在case结构中,每个case的判断条件表达式有且仅有唯一的case分支与之对应。不平行的case可能会产生priority encode。

目前,知名的综合器如Synplify Pro等工具都支持这些综合属性。合理使用可以增强设计的安全性,改善状态机的译码逻辑,但是不当使用会占用大量的逻辑资源,恶化FSM的时序表现。Altera不推荐使用这两条约束语句,而应当在代码中满足这两条规则(Xilinx ISE默认的属性也为none)。综合考虑,仅在One-Hot编码中使用。

 

7. FSM的时序优化——输出逻辑优化和响应速度

 

  1. 输出采用寄存器驱动可以优化FSM的时序性能

    影响一个时序电路运行速度的主要因素是两个寄存器之间的组合逻辑路径的长度,越长的组合逻辑路径,其时序性能越差。一个电路中的最长组合逻辑路径决定了这个电路最高运行速率,该路径就是这个电路的关键路径(critical path)。所以,切断最长的组合逻辑路径可以提高局部电路的运行速度,切断多个较长的组合逻辑路径可以提高整体电路的运行速度。切断组合逻辑路径的工具就是寄存器。所以,添加寄存器可以提高电路的性能,但是会增加资源的消耗,这也是速度(speed)和面积(area)互为矛盾的原因。

    在考虑FSM的时序问题时,不能独立的考虑FSM本身,需要综合考虑FSM模块和下游模块构成的电路。在FSM不采用输出寄存时,在FSM模块和下游模块的接口处,是两部分组合逻辑直接相连。关键路径由两部分组合逻辑构成;在FSM采用输出寄存时,两个模块的组合逻辑被分隔成两部分,可能的关键路径被切断了。同步寄存器输出和组合逻辑输出就是采用资源优化还是速度优化的问题。组合逻辑表面上会提高电路的速度,看似节省了资源而且提高了速度,其实不然(对FPGA结构来说,节省寄存器不等于节省LE;对同步设计来说,异步输出速度上的优势是可以忽略的)。

  2. 输出采用寄存器驱动会降低输出队输入信号变化的响应速度?

    到底应不应该采用FSM输出寄存器?是否必须在时序性能和响应速度之间做出取舍?在同步设计中,答案是必须牺牲一定的响应速度以换取更佳的时序性能,只考虑响应速度会带来潜在的时序性能损失。

    在考虑FSM的响应速度时,不能独立的考虑FSM本身,需要综合考虑FSM模块和上、下游模块构成的电路。采用输出寄存器的电路可以运行在更高的时钟频率下,所引入的响应时延与更高的运行频率相比是可以牺牲的。

  3. 组合逻辑的实现——硬连线(hardwired)和微编码(microcoded)

    ...

参考资料:

1. foreveryoung 《状态机设计》

2. freeny  blog《Verilog HDL学习心得》 http://www.cnblogs.com/freeny/archive/2012/04/14/2447612.html

3. Clifford E. Cummings 《State Machine Coding Styles for Synthesis》

   《The Fundamentals of Efficient Synthesizable Finite State Machine Design using NC-Verilog and BuildGates》

4. 《设计与验证——Verilog HDL》王诚  EDA先锋工作室。

5. 《Verilog HDL程序设计与实践》 云创工作室。

1 0
原创粉丝点击