Encoding SystemVerilog State Machines

There are many different ways of creating state machines, this article will describe how I tackle them using SystemVerilog and the automatic state encoding feature of Vivado synthesis. SystemVerilog’s enumerated type allows the designer to create a data type whose range of legal values are names - this is really useful for representing the states of a state machine:

  1. enum {idle, state0, state1, state2} currState, nextState;

Listing 1

My usual style of writing state machines is to use two separate always blocks, one for describing the synchronous current state registers and one for describing the combinatorial next state logic, hence I make use of the always_ff and always_comb statements as a way of, hopefully, guaranteeing that I get what I want. I also describe the state transitions with unique case as an extra bit of checking.

The complete state machine code for this state diagram..



Figure 1: State Diagram

..looks like this:

  1. enum {idle, state0, state1, state2} currState, nextState;
  2. /*------------------------------------------------------------------
  3.   Current state registers
  4. ------------------------------------------------------------------*/
  5. always_ff @(posedge CLK) begin : currStateRegs
  6.  if (RST == 1) begin
  7.   currState <= idle;
  8.  end else begin
  9.   currState <= nextState;
  10.  end
  11. end : currStateRegs
  12. /*------------------------------------------------------------------
  13.   Next state logic
  14. ------------------------------------------------------------------*/
  15. always_comb begin : nextStateLogic
  16.  nextState = currState; // default is to stay in current state
  17.  unique case (currState)
  18.   idle   : if (START == 1) nextState = state0;
  19.   state0 : nextState = state1;
  20.   state1 : if (HOLD == 0) nextState = state2;
  21.   state2 : nextState = idle;
  22.  endcase
  23. end : nextStateLogic

Listing 2

You will notice two things that should worry you if you are trying to synthesize this code..

  1. How does a synthesis tool know what hardware to generate from the names of the enumerated type?
  2. There is nothing in this code that determines the encoding scheme for the states.

The first point is easily dealt with – the default is to use int for each enumerated value and so a 32bit value should be generated for each state. The second point is down to the synthesis tool – without any guidance as to which encoding scheme to use, Vivado’s internal algorithms will determine the best one. If we synthesize the code in Listing 2, we can check the synthesis report to see exactly what Vivado implemented:


Figure 2: Synthesis report #1

In this case, Vivado has used sequential encoding and “trimmed” down the default 32bits to the number of bits actually required for the chosen coding scheme (two bits for four states). Note that Vivado will not always choose sequential encoding, it may well use some other scheme depending on clock speeds, number of states, fanout, etc.

Writing your state machines in the way described above is going to be pretty much all you need for most designs, but sooner or later you are going to want to tell Vivado that it must use a particular encoding scheme. We can do this by applying the fsm_encoding attribute to the state registers:

  1. typedef enum {idle, state0, state1, state2} stateCoding_t;
  2. (* fsm_encoding = "one_hot" *) stateCoding_t currState;
  3. stateCoding_t nextState;

Listing 3

Note how in listing 3 we use a typedef to create a new data type (called stateCoding_t) from the enumerated type. We then declare the current and next states as being of type stateCoding_t, and we add the fsm_encoding attribute to encode the current registers as one-hot. This time, the synthesis report shows that the state machine is encoded as one-hot and that again, Vivado has trimmed down the default 32bits to the four bits required to one-hot encode a state machine with four states:


Figure 3: Synthesis report #2

So now we know how to force Vivado to use a particular encoding scheme. The last step in this article is to learn how to force Vivado to use a custom encoding scheme chosen by the user. We do this by assigning “states” to each value of the enumerated type and then by stopping Vivado from automatically recognising the state machine with fsm_encoding set to “none”:

  1. typedef enum logic[3:0] {idle = 4'b1000,
  2.                          state0 = 4'b0100,
  3.                          state1 = 4'b0010,
  4.                          state2 = 4'b0001} stateCoding_t;
  5. (* fsm_encoding = "none" *) stateCoding_t currState = idle;
  6. stateCoding_t nextState;

Listing 4

The above listing contains another interesting point, note how we define the power-on/post-configuration state as idle.

This article has not covered every situation but it should allow a new System Verilog user to start writing effective state machines and control the state encoding.