Why use a state machine

  1. Efficient sequence control model;
  2. It is easy to optimize the design using off-the-shelf EDA tools;
  3. The performance of state machine is stable, and it is easy to form synchronous sequential logic module with good performance, which is beneficial to solve the competition and risk phenomenon in large-scale logic circuit design. FSM gives designers more solutions to eliminate circuit burrs and enhance system stability than other solutions.
  4. High-speed performance, in high-speed communication and high-speed control, the state machine has great advantages; A state machine functions more like a CPU in terms of sequence control;
  5. High reliability. The state machine is composed of pure hardware circuits and does not rely on the execution of software instructions, so it does not have many inherent defects in the process of CPU running software. Various fault-tolerant techniques can be used in state machine design. When a state machine enters an illegal state, it takes a very short time to pick it out and enter the normal state. It usually takes only 2-3 clocks, i.e. tens of nanoseconds, and does not cause great harm to the system.
  • Mealy state machine: Output is determined by the current state and input.

  • Moore state machine: The output is determined only by the current state.

Why use a three-stage state machine

  • FSM, like other designs, is best designed using a synchronous timing approach to improve design stability and eliminate burrs.
  • After the realization of the state machine, generally speaking, the state transition part is synchronous timing circuit, and the judgment of the state transition condition is combinatorial logic. The reason two-part coding is more reasonable than one-part coding is that the synchronization timing and composition logic are implemented separately in separate ALWAYS blocks. This is not only easy to read, understand, and maintain, but more importantly, it helps the integrator to optimize the code, the user to add appropriate timing constraints, and the layout cabler to implement the design. One-paragraph FSM description is not conducive to timing constraints, function changes, debugging, etc., and cannot well represent Miller FSM output, which is easy to write Latches, resulting in logical function errors.
  • In the general two-paragraph description, in order to describe the output of the current state, many designers are used to implement the current output by combinatorial logic. This combinatorial logic still has the potential for burrs and is not conducive to constraints, integrators and layout cablers for high-performance designs. So if the design allows the insertion of an extra clock beat, it is required to register the output of the state machine as much as possible.
  • If parenthesis is not allowed to register a beat, it can be solved by the three-paragraph description method. Compared with two sections, the key lies in that the output of the current state is judged according to the input conditions in the previous state according to the rule of state transition, so the register output can be realized without inserting extra clock beats.
  • An FSM description that contains n always blocks cannot be described as an N-section description method. Syntactically, you can split a ALWAYS module into multiple ALWAYS modules, or vice versa, combine multiple ALWAYS modules into a single ALWAYS module. The n-section FSM description method emphasizes a modeling idea, which is by no means a simple always statement block number.

Three state machine models

Relationships between various modeling approaches

One-paragraph and three-paragraph

  • Combining the combinatorial logic in the three-step state machine is the same as one-step modeling. Therefore, the biggest difference between these two modeling methods is that when using one-step modeling of FSM register output, it is necessary to comprehensively consider which sub-states the state will enter under what state transition conditions. Then the output of each substate is described separately under the case branch of each state, which is not consistent with the habit of thinking. When describing the FSM output in three-stage modeling, you only need to specify the case-sensitive table as a sub-state register and directly describe the state output in the case branch of each sub-state without considering the condition of state transition.

Two-stage and three-stage

  • From a code point of view, the first two sections of the three-step modeling are exactly the same as the two-step modeling, with only one more section of register FSM output. In general, it is recommended to use register outputs to improve output timing conditions and to avoid burrs in composite circuits.
  • However, circuit design is not static, and in some cases, two-stage structure is more advantageous than three-stage structure. Compared with this state machine modeling diagram, the two-part combination logic (state transition condition combination logic and output combination logic) is separated by the state register. In the three-stage structure, on the path from the input to the output of the register state, the combination logic of the two parts (the combination logic of the state transition condition and the combination logic of the output) can be seen as a whole from the time sequence. Therefore, the combination logic of this path is more complicated, and the timing sequence of this path is relatively tight.
  • Two-end modeling uses state registers to segment combinatorial logic, while three-stage modeling moves registers to the end of combinatorial logic. If the combination logic before the register is too complex, it is bound to become the critical path of the whole design, so it is not suitable to use three-stage modeling, but two-stage modeling. The solution to the burr of the two-stage modeling combinational logic is to insert additional registers in the post-FSM level, adjust the timing, and complete the function.

State machine design skills

coding

Binary and grey-code use the fewest triggers and more combinative logic, one-hot and vice versa. Gray-code is used for CPLD combination logic resources. FPGA more trigger resources, using one-HOT.

Binary (sequential) encoding gray code disadvantages

Using sequential encoding, transition states “00” and “11” may occur during state transition from “01” to “10”. This is because in the state transition process of the intermediate signal, the high and low turnover time of the state register may be inconsistent. The high turnover speed is fast, and the transition state “00” will be generated, and the transition state “00” will be generated on the contrary. If gray code is used, the transition state caused by delay can be largely eliminated because there is only one bit difference between two adjacent states. However, if there are multiple transition paths from one state to the next, there is no guarantee that there will be only one bit change in the jump. Therefore, the unique thermal code is generally used.

Of course, gray code also has two characteristics: asynchronous output, low power devices

If the output of the state machine or any logic of the state machine operation is asynchronous, it is usually best to use gray codes. Because asynchronous circuits do not protect against race conditions and burrs, different paths between two digits in the state register can cause unpredictable behavior related to layout configurations and parasitic parameters. Gray codes only undergo one unit inversion for each state transition, thus eliminating the race condition in asynchronous logic. In addition, FPGA is best to use unique thermal codes.

Initialization status of the FSM node

A complete state machine (robustness) should have both an initial state and a default state. When the chip is powered on or reset, the state machine should automatically reset all the judgment conditions and enter the initialization state. It should be noted that most FPgas have Globe Set/Reset (GSR) signal. When THE FPGA is powered on, the GSR signal is raised and all the registers, RAM and other units are Reset/Set. At this time, the logic configured in the FPGA does not take effect, so it cannot be guaranteed to enter the initialization state correctly. Therefore, using GSR to try to enter the initialization state of FPGA, often produce all kinds of unnecessary trouble. The general method is to use asynchronous reset signal, of course, can also use synchronous reset, but pay attention to the design of synchronous reset logic. Another way to solve this problem is to set the default initial state code to all zero, so that when the GSR is reset, the state machine automatically enters the initial state.

Default FSM status

A complete state machine should also contain a default state to ensure that the logic does not fall into an “infinite loop” when transition conditions are not met or the state changes abruptly. This is an important requirement for state machine robustness, known as “self-recovery.” The if… Else statements use full-fledged conditional statements. Case statements use default to establish the default state. You can add an additional default state, which automatically changes to IDLE and restarts the state machine.

FSM output

The Mealy state machine for FSM is described in two paragraphs. The output logic can be “? Statement description, or use case statement to determine the transfer condition and input signal. If the output conditions are complex and some outputs are shared by multiple states, you are advised to use Task/endTask to encapsulate the outputs for module reuse.

State machine Example

A one-paragraph state machine example

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
Copy the code

Example of a two-stage state machine

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
Copy the code

Example of a three-section state machine

The output state of the three-stage state machine can be either combinational logic or sequential logic, and sequential logic is generally used to avoid circuit burrs. The output synchronization process is usually case, so here’s the example from the book, case

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
Copy the code

Adi-ad7980 sample code

Two always blocks, three ways of thinking

// -----------------------------------------------------------------------------
// 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   = 0000001000.;  //minimum time between two ADC conversions (Tcyc) [s]
parameter real ADC_CONV_TIME    = 0000000670.;  //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)
beginadc_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
Copy the code

Another way of writing a state machine

module  simple_fsm(
     input  wire  sys_clk    ,   // System clock 50MHz
     input  wire  sys_rst_n  ,   // Global reset
     input  wire  pi_money   ,   // Coin can be: no coin (0), 1 yuan (1)
     outputreg    po_cola       Po_cola = 1, Po_Cola = 0, po_Cola = 0
);

// Only three states, use unique heat code
parameter  IDLE =3'b001;
parameter  ONE  =3'b010;
parameter  TWO  =3'b100;

reg[2:0]  state;           

// The first paragraph of the state machine describes the current state. How does the input jump to the next state
always@ (posedge sys_clk ornegedge sys_rst_n)
     if(sys_rst_n ==1'b0)
         state <= IDLE;    // In any case, just press reset to return to the initial state
     else   
         case(state)
                IDLE  : if(pi_money ==1'b1)// Determine the input
                           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;    // If the state machine jumps out of the encoded state, it returns to its original state
         endcase

// The second paragraph describes the current state state and how input pi_money affects po_COLA output
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 
Copy the code

Public account – Darwin’s explanation

The most critical part of the state machine, whether the synthesizer can synthesize RTL code into a state machine, depends on how the code is implemented. You see that the code uses a two-step state machine, but it feels strange. The difference between the state machine and the other data is that it uses a new way of writing it. Many people have seen state machine code written in one-step, two-step, three-step (one-step refers to the use of sequential logic in a state machine to describe both the state transition and the data output; In two-stage state machine, sequential logic is used to describe state transition in the first stage and combinatorial logic is used to describe data output in the second stage. In three-stage state machine, sequential logic is used to describe state transition in the first stage, combinational logic is used to judge state transition conditions and describe state transition rules in the second stage, and state output is described in the third stage state machine, which can be output by combinational circuit or sequential circuit). This one before, 2-phase, three-step are classic old writing, also some old engineer is still used to writing, the old method was established according to the theory of state machine model after the abstract design, the implementation of the code to write code, in strict accordance with the fixed format or comprehensive device will not be able to identify you write the code as a state machine, Because early development tools recognized only fixed state machine formats, code integrators that were not written in standard formats could not be synthesized to look like state machines. This often increases the difficulty of design. Many people need to understand the theoretical model when learning, and they can design a good state machine only after repeated learning and understanding for a long time. Therefore, we need to improve.

Wang Jinming, digital System Design and Verilog HDL

There are three objects in the state machine design
  • Current State (CS)
  • The Next State, the Next State (NS)
  • Output Logic (OL)
The way a state machine is described
  • 3 process description :CS NS OL is described by an ALWAYS process block
  • Dual-process description 1:CS+NS OL
  • Dual-process description 2:CS NS+OL
  • Single process description :CS+NS+OL

Other additions to the book

Synchronous or asynchronous reset

  • The synchronous reset signal resets the finite state machine when the edge of the clock jump arrives, and assigns the reset value to the output signal and returns the finite state machine to its initial state. Add an if statement at the beginning of the state transition that evaluates the synchronous reset signal. This way, if the value of the output signal is not specified, the output signal value remains unchanged. This situation requires additional registers to hold the original value, thus increasing resource consumption, so the value of the output signal should be specified in the if statement.
  • If you only need to reset during power-on and system errors, the asynchronous reset mode is better than the synchronous one. Cause: Synchronous reset occupies more extra resources, while asynchronous reset can eliminate the possibility of introducing additional registers. And Verilog language with asynchronous reset signal is simple to describe, only need to introduce asynchronous reset signal in the process of describing the status register.