FPGA状态机(读书笔记)

FPGA状态机(读书笔记)

    • 为什么使用状态机
    • 为什么使用三段式状态机
      • 三种状态机建模
      • 各种建模方法之间的关系
        • 一段式与三段式
        • 两段式与三段式
    • 状态机设计技巧
      • 编码
      • FSM的初始化状态
      • FSM的默认状态
      • FSM输出
    • 状态机示例
        • 一段式状态机示例
      • 两段式状态机示例
      • 三段式状态机示例
      • ADI-AD7980示例代码(两段式状态机实现)
    • 另一种状态机机写法
      • 公众号-达尔闻里的解释
      • 王金明老师的<数字系统设计与Verilog HDL>相关叙述
        • 状态机设计中包括三个对象
        • 描述状态机的方式
        • 书中其他补充 同步复位与异步复位
  • 《FPGA之道》状态机
    • 状态机模型
      • Moore型
        • Moore 1型
        • Moore 2型
        • Moore 3型
      • Mealy 型状态机
        • Mealy 1型
        • Mealy 2型
        • Mealy 3型
      • Mix型状态机

为什么使用状态机

  1. 高效的顺序控制模型;
  2. 容易利用现成的EDA工具进行优化设计;
  3. 性能稳定,状态机容易构成性能良好的同步时序逻辑模块,这对于解决大规模逻辑电路设计中的竞争和冒险现象大有益处。与其他方案相比,在消除电路的毛刺现象、强化系统稳定性方面,FSM使设计者拥有更多的解决方法;
  4. 高速性能,在高速通信和高速控制方面,状态机更具有巨大优势;在顺序控制方面,一个状态机的功能更类似于CPU;
  5. 高可靠性。状态机由纯硬件电路构成,运行不依赖软件指令的逐条执行,因此不存在CPU运行软件过程中许多固有缺陷;在状态机的设计中能够使用各种容错技术;当状态机进入非法状态并从中挑出进入正常状态所需时间十分短暂,通常只需要2-3个时钟既数十纳秒,对系统不会构成较大的危害。
  • Mealy 状态机:输出由当前状态及输入决定。

FPGA状态机(读书笔记)_第1张图片

  • Moore 状态机:输出仅由当前状态决定。

FPGA状态机(读书笔记)_第2张图片

为什么使用三段式状态机

  • FSM和其他设计一样,最好使用同步时序方法设计,以提高设计的稳定性,消除毛刺。
  • 状态机实现后,一般来说,状态转换部分是同步时序电路,而状态转换条件的判断是组合逻辑。两段式之所以比一段式编码合理,就在于两段编码将同步时序和组合逻辑分别放到不同的always程序块中实现。这样做不仅式便于阅读、理解、维护,更重要的是利于综合器优化代码、利于用户添加合适的时序约束条件、利于布局布线器实现设计。而一段式FSM描述不利于时序约束、功能更改、调试等,而且不能很好的表示米勒型FSM输出,容易写出Latches,导致逻辑功能错误。
  • 在一般的两段式描述中,为了便于描述当前状态的输出,很多设计者习惯将当前的输出用组合逻辑实现。这种组合逻辑仍然有可能产生毛刺,而且不利于约束、不利于综合器和布局布线器实现高性能的设计。因此如果设计允许额外的一个时钟节拍插入,则要求尽量对状态机的输出用寄存器寄存一拍。
  • 如果实际不允许插入语一个寄存节拍,此时可以通过三段式描述方法进行解决。三段与两段相比,关键在于根据状态转移规律,在上一状态根据输入条件判断当前状态的输出,从而在不插入额外时钟节拍的前提下实现了寄存器输出。
  • 不能说一段FSM的描述中,使用了n个always语句块就是n段式描述方法。从语法角度说,可以将一个always模块拆分成多个always模块,或者反之将多个always模块合并为一个always模块。n段式FSM描述方法强调的是一种建模思路,绝不是简单的always语句块个数。

三种状态机建模

FPGA状态机(读书笔记)_第3张图片

FPGA状态机(读书笔记)_第4张图片

FPGA状态机(读书笔记)_第5张图片

各种建模方法之间的关系

一段式与三段式
  • 将三段式状态机中的组合逻辑合并起来,就和一段式建模方式一样了。所以这两种建模方式最大的区别在于:使用一段式建模FSM的寄存器输出时,必须要综合考虑现态在何种状态转移条件下会进入哪些次态。然后在每个现态的case分支下分别描述每个次态的输出,这不符合思维习惯。三段式建模描述FSM的输出时,只需要指定case敏感表为次态寄存器,然后直接在每个次态的case分支中描述该状态输出即可,不用考虑状态转移条件。
    FPGA状态机(读书笔记)_第6张图片
两段式与三段式
  • 从代码上看,三段式建模的前两段与两段式建模完全相同,仅仅多了一段寄存器FSM输出。一般来说,使用寄存器输出可以改善输出时序条件,还能避免组合电路的毛刺,所以更推荐。
  • 但电路设计不是一成不变的,在某些情况下,两段式结构比三段式结构更有优势。对比这种状态机建模图,两段式用状态寄存器分割了两部分组合逻辑(状态转换条件组合逻辑和输出组合逻辑);三段式结构中,从输入到寄存器状态输出的路径上,这两部分组合逻辑(状态转换条件组合逻辑和输出组合逻辑),从时序上,这两部分组合逻辑完全可以看为一体。这样这条路径的组合逻辑就比较繁杂,该路径的时序也相对紧张。
  • 两端式建模中用状态寄存器分割了组合逻辑,而三段式将寄存器移到组合逻辑的最后端。如果寄存器前的组合逻辑过于复杂,势必会成为整个设计的关键路径,此时就不宜再使用三段式建模,而使用两段式建模。解决两段式建模组合逻辑产生毛刺的方法是,额外的在FSM后级插入寄存器,调整时序,完成功能。

状态机设计技巧

编码

Binary(二进制编码)、gray-code(格雷码)使用最少的触发器,较多的组合逻辑,one-hot(独热码)反之。CPLD组合逻辑资源多,使用gray-code;FPGA更多的触发器资源,使用one-hot。

FSM的初始化状态

一个完备的状态机(健壮性)应该具备初始化状态和默认状态。当芯片加电或者复位后,状态机应该能够自动将所有判断条件复位,并进入初始化状态。需要注明的一点是,大多是FPGA都有GSR(Globe Set/Reset)信号,当FPGA加电后,GSR信号拉高,对所有的寄存器、RAM等单元复位/置位,这时配置于FPGA的逻辑并未生效,所以不能保证正确进入初始化状态。所以使用GSR企图进入FPGA的初始化状态,常常会产生种种不必要的麻烦.一般方法是采用异步复位信号,当然也可以使用同步复位,但要注意同步复位逻辑的设计.解决这个问题的另一种方法是将默认的初始状态编码设置为全零,这样GSR复位后,状态机自动进入初始状态.

FSM的默认状态

完整的状态机还应该包含一个默认(default)状态,当转移条件不满足,或者状态发生了突变,要保证逻辑不会陷入"死循环".这是对状态机健壮性的一个重要要求,也就是常说的"自恢复"功能.对if…else语句使用完备的条件判断语句.case语句要用default建立默认状态.可以添加一个额外的default状态,一旦进入这个状态就自动转入IDLE状态,重新启动状态机.

FSM输出

两段式FSM描述Mealy状态机,输出逻辑可以用"?"语句描述,或者使用case语句判断转移条件与输入信号即可.输出条件比较复杂,而且多个状态共用某些输出,则建议使用task/endtask将输出封装起来,达到模块复用的目的.

状态机示例

一段式状态机示例
module state1 ( nrst,clk,
                i1,i2,
                o1,o2,
                err
               );

input          nrst,clk;
input          i1,i2;
output         o1,o2,err;
reg            o1,o2,err;

reg    [2:0]   NS; //NextState

parameter [2:0]      //one hot with zero idle
      IDLE   = 3'b000,
      S1     = 3'b001,
      S2     = 3'b010,
      ERROR  = 3'b100;

//1 always block to describe state transition, state output, state input condition
always @ (posedge clk or negedge nrst)
 if (!nrst)
    begin
       NS         <= IDLE;
      {o1,o2,err} <= 3'b000;
    end
 else
    begin
       NS         <=  3'bx;
      {o1,o2,err} <=  3'b000;
      case (NS)
        IDLE:  begin
                 if (~i1)         begin{o1,o2,err}<=3'b000;NS <= IDLE; end
                 if (i1 && i2)    begin{o1,o2,err}<=3'b100;NS <= S1;   end
                 if (i1 && ~i2)   begin{o1,o2,err}<=3'b111;NS <= ERROR;end
               end
        S1:    begin
                 if (~i2)         begin{o1,o2,err}<=3'b100;NS <= S1;   end
                 if (i2 && i1)    begin{o1,o2,err}<=3'b010;NS <= S2;   end
                 if (i2 && (~i1)) begin{o1,o2,err}<=3'b111;NS <= ERROR;end
               end
        S2:    begin
                 if (i2)          begin{o1,o2,err}<=3'b010;NS <= S2;   end
                 if (~i2 && i1)   begin{o1,o2,err}<=3'b000;NS <= IDLE; end
                 if (~i2 && (~i1))begin{o1,o2,err}<=3'b111;NS <= ERROR;end
               end
        ERROR: begin
                 if (i1)          begin{o1,o2,err}<=3'b111;NS <= ERROR;end
                 if (~i1)         begin{o1,o2,err}<=3'b000;NS <= IDLE; end
               end
      endcase
   end

endmodule

两段式状态机示例

module state2 ( nrst,clk,
                i1,i2,
                o1,o2,
                err
               );
         
input          nrst,clk;
input          i1,i2;
output         o1,o2,err;
reg            o1,o2,err;

reg    [2:0]   NS,CS;

parameter [2:0]      //one hot with zero idle
      IDLE   = 3'b000,
      S1     = 3'b001,
      S2     = 3'b010,
      ERROR  = 3'b100;

//sequential state transition
always @ (posedge clk or negedge nrst)
      if (!nrst)            
         CS <= IDLE;        
      else                  
         CS <=NS;           

//combinational condition judgment
always @ (nrst or CS or i1 or i2)
          begin
               NS = 3'bx;
               ERROR_out;
               case (CS)
                    IDLE:     begin
                                   IDLE_out;
                                   if (~i1)           NS = IDLE;
                                   if (i1 && i2)      NS = S1;
                                   if (i1 && ~i2)     NS = ERROR;
                              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

//output task
task IDLE_out;
     {o1,o2,err} = 3'b000;
endtask

task S1_out;
     {o1,o2,err} = 3'b100;
endtask

task S2_out;
     {o1,o2,err} = 3'b010;
endtask

task ERROR_out;
     {o1,o2,err} = 3'b111;
endtask

endmodule

三段式状态机示例

三段式状态机的输出态可以是组合逻辑也可以是时序逻辑,一般使用时序逻辑,可避免电路毛刺。输出同步进程通常是case(现态),下例是书中例程,是case(次态),这两种方式的详细对比可以参考下文的《FPGA之道》中的内容。

module state3 ( nrst,clk,
                i1,i2,
                o1,o2,
                err
               );
         
input          nrst,clk;
input          i1,i2;
output         o1,o2,err;
reg            o1,o2,err;

reg    [2:0]   NS,CS;

parameter [2:0]      //one hot with zero idle
      IDLE   = 3'b000,
      S1     = 3'b001,
      S2     = 3'b010,
      ERROR  = 3'b100;

//1st always block, sequential state transition
always @ (posedge clk or negedge nrst)
      if (!nrst)            
         CS <= IDLE;        
      else                  
         CS <=NS;           

//2nd always block, combinational condition judgment
always @ (nrst or CS or i1 or i2)
          begin
               NS = 3'bx;
               case (CS)
                    IDLE:     begin
                                   if (~i1)           NS = IDLE;
                                   if (i1 && i2)      NS = S1;
                                   if (i1 && ~i2)     NS = ERROR;
                              end
                    S1:       begin
                                   if (~i2)           NS = S1;
                                   if (i2 && i1)      NS = S2;
                                   if (i2 && (~i1))   NS = ERROR;
                              end
                    S2:       begin
                                   if (i2)            NS = S2;
			           if (~i2 && i1)     NS = IDLE;
                                   if (~i2 && (~i1))  NS = ERROR;
                              end
                    ERROR:    begin
                                   if (i1)            NS = ERROR;
                                   if (~i1)           NS = IDLE;
                              end
               endcase
         end

//3rd always block, the sequential FSM output
always @ (posedge clk or negedge nrst)
 if (!nrst)
      {o1,o2,err} <= 3'b000;
 else
    begin
       {o1,o2,err} <=  3'b000;
       case (NS)
           IDLE:  {o1,o2,err}<=3'b000;

           S1:    {o1,o2,err}<=3'b100;
           S2:    {o1,o2,err}<=3'b010;
           ERROR: {o1,o2,err}<=3'b111;
       endcase
    end

endmodule

ADI-AD7980示例代码(两段式状态机实现)

看似是两段式两段式状态机,其实是将三段式状态机中时序逻辑部分放到一个always块中(《FPGA之道》书中推荐使用这种写法)

// -----------------------------------------------------------------------------
// KEYWORDS : AD7980
// -----------------------------------------------------------------------------
// PURPOSE : Driver for the AD7980  16-Bit, 1 MSPS PulSAR ADC in MSOP/QFN
// -----------------------------------------------------------------------------
// REUSE ISSUES        
// Reset Strategy      : Active low reset signal
// Clock Domains       : 2 clocks - the system clock that drives the internal logic 
//                     : and a clock for ADC conversions
// Critical Timing     : N/A
// Test Features       : N/A
// Asynchronous I/F    : N/A
// Instantiations      : N/A
// Synthesizable (y/n) : Y
// Target Device       : AD7980
// Other               : The driver is intended to be used for AD7980 ADCs configured
//                     : in /CS MODE, 3-WIRE, WITHOUT BUSY INDICATOR 
// -----------------------------------------------------------------------------

`timescale 1ns/1ns //Use a timescale that is best for simulation.

//----------- Module Declaration -----------------------------------------------  

module AD7980

//----------- Ports Declarations -----------------------------------------------
(
    //clock and reset signals
    input               fpga_clk_i,      //system clock
    input               adc_clk_i,       //clock to be applied to ADC to read the conversions results
    input               reset_n_i,       //active low reset signal
    
    //IP control and data interface
    output     [15:0]   data_o,          //data read from the ADC
    output reg          data_rd_ready_o, //when set to high the data read from the ADC is available on the data_o bus
    
    //ADC control and data interface
    input               adc_sdo,        //ADC SDO signal
    input               adc_sdi,        //ADC SDI signal (not used in 3-WIRE mode)
    output              adc_sclk_o,     //ADC serial clock
    output              adc_cnv_o       //ADC CNV signal
);

//----------- Registers Declarations -------------------------------------------
reg [ 3:0]  adc_state;      //current state for the ADC control state machine
reg [ 3:0]  adc_next_state; //next state for the ADC control state machine
reg [ 3:0]  adc_state_m1;   //current state for the ADC control state machine in the ADC clock domain

reg [ 6:0]  adc_tcycle_cnt; //counts the number of FPGA clock cycles to determine when an ADC cycle is complete
reg [ 6:0]  adc_tcnv_cnt;   //counts the number of FPGA clock cycles to determine when an ADC conversion is complete
reg [ 4:0]  sclk_clk_cnt;   //counts the number of clocks applied to the ADC to read the conversion result

reg         adc_clk_en;     //gating signal for the clock sent to the ADC
reg         adc_cnv_s;      //internal signal used to hold the state of the ADC CNV signal
reg [15:0]  adc_data_s;     //interal register used to store the data read from the ADC

//----------- Wires Declarations -----------------------------------------------
wire        adc_sclk_s;     //internal signal for the clock sent to the ADC

//----------- Local Parameters -------------------------------------------------
//ADC states
parameter ADC_IDLE_STATE            = 4'b0001;
parameter ADC_START_CNV_STATE       = 4'b0010;
parameter ADC_END_CNV_STATE         = 4'b0100;
parameter ADC_READ_CNV_RESULT       = 4'b1000;

//ADC timing
parameter real FPGA_CLOCK_FREQ  = 100000000;    //FPGA clock frequency [Hz]
parameter real ADC_CYCLE_TIME   = 0.000001000;  //minimum time between two ADC conversions (Tcyc) [s]
parameter real ADC_CONV_TIME    = 0.000000670;  //conversion time (Tcnvh) [s]
parameter [6:0] ADC_CYCLE_CNT   = FPGA_CLOCK_FREQ * ADC_CYCLE_TIME - 1;
parameter [6:0] ADC_CNV_CNT     = FPGA_CLOCK_FREQ * ADC_CONV_TIME;

//ADC serial clock periods
parameter ADC_SCLK_PERIODS  = 5'd15; //number of clocks to be sent to the ADC to read the conversion result

//----------- Assign/Always Blocks ---------------------------------------------
assign adc_cnv_o    = adc_cnv_s;
assign adc_sclk_s   = adc_clk_i & adc_clk_en;
assign adc_sclk_o   = adc_sclk_s;
assign data_o       = adc_data_s;

//update the ADC timing counters
always @(posedge fpga_clk_i)
begin
    if(reset_n_i == 1'b0)
    begin
        adc_tcycle_cnt  <= 0;
        adc_tcnv_cnt    <= ADC_CNV_CNT;
    end
    else
    begin
        if(adc_tcycle_cnt != 0)
        begin
            adc_tcycle_cnt <= adc_tcycle_cnt - 1;
        end
        else if(adc_state == ADC_IDLE_STATE)
        begin
            adc_tcycle_cnt <= ADC_CYCLE_CNT;
        end
        
        if(adc_state == ADC_START_CNV_STATE)
        begin
            adc_tcnv_cnt <= adc_tcnv_cnt - 1;
        end
        else
        begin
           adc_tcnv_cnt <= ADC_CNV_CNT;
        end
    end    
end

//read data from the ADC
always @(negedge adc_clk_i)
begin
    if(adc_clk_en == 1'b1)
    begin
        adc_data_s   <= {adc_data_s[14:0], adc_sdo};
        sclk_clk_cnt <= sclk_clk_cnt - 1;
    end
    else
    begin
        sclk_clk_cnt <= ADC_SCLK_PERIODS;	
    end
end

//determine when the ADC clock is valid to be sent to the ADC
always @(negedge adc_clk_i)
begin
    adc_state_m1 <= adc_state;
    adc_clk_en   <= ((adc_state_m1 == ADC_END_CNV_STATE) || (adc_state_m1 == ADC_READ_CNV_RESULT) && (sclk_clk_cnt != 0)) ? 1'b1 : 1'b0;
end

//update the ADC current state and the control signals
always @(posedge fpga_clk_i)
begin
    if(reset_n_i == 1'b0)
    begin
        adc_state <= ADC_IDLE_STATE;
    end
    else
    begin
        adc_state <= adc_next_state;
        case (adc_state)
            ADC_IDLE_STATE:
            begin
                adc_cnv_s       <= 1'b0;
                data_rd_ready_o <= 1'b0;
            end
            ADC_START_CNV_STATE:
            begin
                adc_cnv_s       <= 1'b1;
                data_rd_ready_o <= 1'b1;
            end
            ADC_END_CNV_STATE:
            begin
                adc_cnv_s       <= 1'b0;
                data_rd_ready_o <= 1'b0;
            end
                ADC_READ_CNV_RESULT:
            begin
                adc_cnv_s       <= 1'b0;
                data_rd_ready_o <= 1'b0;
            end
        endcase
    end    
end

//update the ADC next state
always @(adc_state, adc_tcycle_cnt, adc_tcnv_cnt, sclk_clk_cnt)
begin
    adc_next_state <= adc_state;
    case (adc_state)
        ADC_IDLE_STATE:
        begin
            if(adc_tcycle_cnt == 0)
                begin
                    adc_next_state <= ADC_START_CNV_STATE;
                end
        end
        ADC_START_CNV_STATE:
        begin
            if(adc_tcnv_cnt == 0)
            begin
                adc_next_state <= ADC_END_CNV_STATE;   
            end
        end
        ADC_END_CNV_STATE:
        begin
            adc_next_state <= ADC_READ_CNV_RESULT;
        end
        ADC_READ_CNV_RESULT:
        begin
            if(sclk_clk_cnt == 1)
            begin
                adc_next_state <= ADC_IDLE_STATE;
            end
        end
        default:
        begin
            adc_next_state <= ADC_IDLE_STATE;
        end
    endcase
end

endmodule

另一种状态机机写法

module  simple_fsm(
     input  wire  sys_clk    ,   //系统时钟50MHz
     input  wire  sys_rst_n  ,   //全局复位
     input  wire  pi_money   ,   //投币方式可以为:不投币(0)、投1元(1)
     outputreg    po_cola       //po_cola为1时出可乐,po_cola为0时不出可乐 
;

//只有三种状态,使用独热码
parameter  IDLE =3'b001;
parameter  ONE  =3'b010;
parameter  TWO  =3'b100;

reg[2:0]  state;           

//第一段状态机,描述当前状态state如何根据输入跳转到下一状态
always@(posedge sys_clk ornegedge sys_rst_n)
     if(sys_rst_n ==1'b0)
         state <= IDLE;    //任何情况下只要按复位就回到初始状态
     else   
         case(state)
                IDLE  : if(pi_money ==1'b1)//判断输入情况
                           state <= ONE;
                        else
                           state <= IDLE;
                         
                ONE   : if(pi_money ==1'b1)
                           state <= TWO;           
                        else
                           state <= ONE;
                         
                TWO   : if(pi_money ==1'b1)
                           state <= IDLE;
                        else 
                           state <= TWO;
                         
                default:   state <= IDLE;    //如果状态机跳转到编码的状态之外也回到初始状态
         endcase

//第二段状态机,描述当前状态state和输入pi_money如何影响po_cola输出
always@(posedge sys_clk ornegedge sys_rst_n)
     if(sys_rst_n == 1'b0)   
         po_cola <=1'b0;
     else   if((state == TWO)&&(pi_money ==1'b1))     
         po_cola <=1'b1;
     else  
         po_cola <=1'b0;
        
endmodule 

公众号-达尔闻里的解释

状态机中最为关键的部分,综合器能不能将RTL代码综合为状态机的样子主要看这部分代码如何来实现的。大家看到代码使用的是二段式状态机,但是又感觉怪怪的,这个状态机之所以和其他资料上的有所区别,其实是使用了新的写法。很多人都见过其他资料上总结的状态机代码写法有一段式、二段式、三段式(一段式指的是在一段状态机中使用时序逻辑既描述状态的转移,也描述数据的输出;二段式指在第一段状态机中使用时序逻辑描述状态转移,在第二段状态机中使用组合逻辑描述数据的输出;三段式指在第一段状态机中采用时序逻辑描述状态转移,在第二段在状态机中采用组合逻辑判断状态转移条件描述状态转移规律,在第三段状态机中描述状态输出,可以用组合电路输出,也可以时序电路输出)。这种一段式、二段式、三段式其实都是之前经典的老写法,也是一些老工程师仍然习惯用的写法,老方法是根据状态机理论建立的模型抽象后设计的,其实现的代码要严格按照固定的格式来写代码,否则综合器将无法识别出你写的代码是个状态机,因为早期的开发工具只能识别出固定的状态机格式,如果不按照标准格式写代码综合器最后无法综合成为状态机的样子。这样往往增加了设计的难度,很多人学习的时候还要去了解理论模型,反复学习理解很久才能够设计好的状态机,所以需要我们改进。

王金明老师的<数字系统设计与Verilog HDL>相关叙述

状态机设计中包括三个对象
  • 当前状态,即现态(Current State,CS)
  • 下一个状态,即次态(Next State,NS)
  • 输出逻辑(Output Logic,OL)
描述状态机的方式
  • 三过程描述:CS NS OL各用一个always过程块描述
  • 双过程描述1:CS+NS OL
  • 双过程描述2:CS NS+OL
  • 单过程描述:CS+NS+OL
书中其他补充 同步复位与异步复位
  • 同步复位信号在时钟跳变的边沿到来时,对有限状态机进行复位操作,同时把复位值赋给输出信号并使有限状态机回到初始状态.在状态转移的开始部分加入对同步复位信号进行判断的if语句.这样,如果不指定输出信号的值,那么输出信号值将保持不变.这种情况会需要额外的寄存器来保持原值,从而增加了资源消耗,因此应该在if语句中指定输出信号的值.
  • 如果只需要在上电和系统错误时进行复位操作,那么异步复位方式比同步复位方式好.原因:同步复位方式占用较多的额外资源,而异步复位可以消除引入额外寄存器的可能性;而且带有异步复位信号的Verilog语言描述简单,只需要在描述状态寄存器的过程中引入异步复位信号即可.

《FPGA之道》状态机

状态机模型

Moore型

Moore型——状态机输出仅有现态决定,与输入无关

Moore 1型

FPGA状态机(读书笔记)_第7张图片

这种状态机的结构可以划分为两部分——状态转移部分和输出生成部分。

图中左半部分,输入和现态(现态寄存器的输出)通过组合逻辑共同作用产生次态,当下一次时钟有效沿到来的时候,现态寄存器发生更新,刚才产生的次态即成为新的现态,而新的现态和新的输入共同作用产生新的次态,如此往复。

图中右半部分,现态(现态寄存器的输出)直接通过组合逻辑产生当前的输出。有时候,不希望将这部分的组合逻辑延迟影响到后续模块的处理,可以添加输出寄存器来解决,需要注意一点,由于输出寄存器的更新需要等到下一次时钟的有效沿,因此经过输出寄存器寄存后的输出其实对应的是状态机的上一个状态。

Moore 2型

FPGA状态机(读书笔记)_第8张图片

Moore 2型状态机的结构仍划分为状态转移和输出两部分,对比Moore 1型状态机的原理结构框图,可以发现不同之处仅仅是输出生成部分的输入端由之前的现态(现态寄存器的输出)变为次态(由输入和现态通过组合逻辑产生)。这样一来,由于次态和次态决定的输出在同一个时钟周期内变得有效,那么在下一次时钟有效沿到来时,现态寄存器和输出寄存器将会同时进行更新,至此,次态变成新的现态,次态决定的输出变成新的输出。因此,Moore型状态机中经过寄存后的输出时对应于当前状态的。

Moore 3型

Moore 2型相较于1型有其自身的缺点,那就是产生输出的组合逻辑延迟比较大,因为2型将产生次态的组合逻辑和产生输出的组合逻辑级联,这种将两级组合逻辑进行级联的方式会限制状态机本身的时钟工作频率。

Moore 1型和2型的本质区别在于“由状态产生输出”这部分的组合逻辑所处的位置。1型中将这部分逻辑置于现态寄存器之后,该组合逻辑的时间延迟会影响后续电路的工作,有时需要插入寄存器解决这个问题。2型中将这部分逻辑置于次态产生逻辑之后,这样这个组合逻辑级联会影响状态机机自身的工作频率。将这两种状态机的有点相结合,得到Moore 3型状态机:

FPGA状态机(读书笔记)_第9张图片

Moore 3型将那些适合使用组合逻辑的输出使用1型中的方法来处理,而适合使用寄存器的输出采用2型的方式来处理。

如果后续模块为电平敏感的,那么自然选择寄存器形式的输出,不过FPGA中一般应该为全同步逻辑设计,所以绝大多数情况下,状态机机的前级输入、后级输出及本身是工作在一个时钟域内的,此时,假设由现态、输入产生次态的组合逻辑时间延迟为T1,由状态产生输出的逻辑组合的时间延迟为T2,而后续使用该输出的电路中,又会在输出信号到达下一个寄存器前引入时间延迟最大(考虑到会有多个地方使用该输出)为T3的组合逻辑。那个根据短板效应,若T1+T2 <= T2+T3,则该输出采用Moore 2型的寄存方式处理可以提升整个系统的性能;反之,该输出采用Moore 1型的组合方式可以提升系统性能。也就是看如何能使级联的组合逻辑延迟尽量小,现态寄存器和输出A寄存器可以打断这个延迟。

FPGA状态机(读书笔记)_第10张图片

Mealy 型状态机

Mealy 1型

FPGA状态机(读书笔记)_第11张图片

由于次态和输出均由现态和输入通过组合逻辑共同决定,因此可以将状态转移部分和输出生成部分合并成一个部分,兼并产生状态机的次态和输出。当下一次时钟有效沿到来后,现态寄存器完成刷新,次态成为了新的现态,而新的现态和新的输入共同作用产生新的次态和新的输出,如此往复。

域Moore 1型类似,Mealy 1型的输出也是直接通过组合逻辑产生的,若想通过添加输出寄存器来获得寄存输出,经过输出寄存器寄存后的输出其实对应的是状态机的上一个状态。

Mealy 2型

FPGA状态机(读书笔记)_第12张图片

将Mealy 1型按照Moore 2型的原理结构框图进行一些简单的修改,可以得到Mealy 2型状态机。Mealy 2型状态机重新将状态转移部分和输出部分分开,并做成级联的形式,所以输出生成部分便由次态和输入共同作用产生产生次态对应的输出。这样,次态和次态决定的输出在同一个时钟周期内变得有效,那么在下一次时钟有效沿到来时,现态寄存器和输出寄存器将会同时进行更新,至此,次态变成新的现态,次态决定的输出变成新的输出。Mealy 2型经过i寄存后的输出是对应于当前状态的。

Mealy 3型

Mealy 1型和2型本质区别在于“由状态产生输出”这部分组合逻辑所处的位置。1型是将这部分逻辑与次态产生逻辑并联,那么该组合逻辑的时间延迟会影响后续电路的工作;2型中将这部分逻辑与次态产生逻辑串联,那么这部分组合逻辑的时间延迟会影响状态机本身的工作。因此将二者结合得到3型:
FPGA状态机(读书笔记)_第13张图片

Mealy 3型状态机将适合使用组合逻辑的输出采用Mealy 1型的方式来处理,而适合使用寄存器的输出使用Mealy 2型的方式处理。所以,具体使用什么样的输出适合组合的形式,什么样的输出适合寄存的形式,需要根据具体的功能需求来确定。这与Moore型的不一样。主要因为:Mealy型状态机的输出除了与当前状态有关还与输出有关。对于相同的输入序列,Moore 1、2型状态机的状态变迁都是一样的,因此它们的输出也是完全一致的;但对于同样的输入序列,Mealy 1、2型状态机的状态变迁虽然是一样的,但Mealy 2型的输出实际上是由当前状态和上一次的输入得出的,这与Mealy 1型的输出是由当前状态和当前输入得出是不同的,故Mealy1、2之间不可随意替换。

Mix型状态机

Moore 3 + Mealy 3型得到下图的Mix型状态机,包含了四种类型的输出——Moore组合、Moore寄存、Mealy组合、Mealy寄存。

FPGA状态机(读书笔记)_第14张图片

你可能感兴趣的:(Verilog学习笔记,fpga,fpga/cpld,verilog)