原创 State Machine Design & VHDL Coding

2009-3-13 01:59 3463 4 4 分类: FPGA/CPLD

State Machine Design & VHDL Coding



This section gives an overview of how to describe state machines with VHDL, it assumes some prior knowledge of state machine design.





Translating the state diagram to VHDL


The starting point for most state machine designs is a state flow diagram which shows the various states and the transitions between them. It can also show any outputs that are generated when the state machine is in a particular state. An example is shown below:

example.gif

Example state machine - The state machine is clocked by a signal 'clk' which is not shown on the flow diagram.


What information is important to us?



  • We need to know how the state machine will be clocked - using which signal and on which edge, rising or falling.
  • We need to know how the state machine will be initialized - using which signal, asynchronously or synchronous to the clock, active high or active low and the initial state itself.
  • We need to interpret the state diagram to decide the state transition conditions.
  • We need to choose a state assignment (or state encoding) - this will impact the number of flip-flops required & the speed (i.e. maximum clock frequency that can be employed).





Defining the clock


A state machine advances from one state to another when the particular state transition conditions are satisfied, synchronous to the state machine's clock input. This can be implemented in a process with the clock signal in the sensitivity list. The process will only be activated by events on the clock signal (clk) ensuring all signal and variable changes are synchronized to the clock.

state_machine  :   (clk)



(clk'EVENT clk = '1')



;

state_machine;

All statements enclosed within IF (clk'EVENT AND clk='1') THEN...END IF; will only be evaluated when the (clk'EVENT AND clk='1') condition is true, i.e. when a rising edge occurs on the clk signal. Your synthesis tool should interpret this as positve edge clocked registers.


Alternatively, a WAIT statement could be used, in which case a sensitivity list is not specified:

state_machine  :  (clk'EVENT  clk = '0'); 

state_machine;

All statements enclosed within WAIT UNTIL (clk'EVENT AND clk='0')...END PROCESS state_machine; will only be evaluated when the (clk'EVENT AND clk='0') condition is true, i.e. when a falling edge occurs on the clk signal.


Your synthesis tool should interpret this as negative edge clocked registers (and will probably need to invert your clock signal).


Note that in the first of these examples, the rising edge of clk is used to clock the state machine, in the second the falling edge is used. (see the section on signal attributes for more information).






Synchronous & asynchronous initialization


Some sort of initialization is usually necessary as the power-up state of the flip-flops may not be defined or a pushbutton reset may be included on the pcb. For simulation, std_logic type signals and variables will default to 'U' (uninitialized) if there is no explicit initialization.


Let's look at synchronous initialization first and assume that that the state machine must transition to the initial state from any other state when the reset input is low. Using the template that we developed earlier:

state_machine  :   (clk)

(clk'EVENT clk = '1')

(reset = '0')

state_vector <= idle;





;

;

state_machine;

The reset signal has not been included in the process sensitivity list, so events on the reset signal will not activate the process - it's still only the clock that activates the process. The second IF condition is only evaluated when a rising edge occurs on the clk signal, i.e. synchronous reset. The state_vector is either a VHDL signal or variable and it is assigned the value idle when the reset signal is '0' at a rising clock edge. What exactly state_vector and idle are will be decided by the state assignments that we choose.


Now for the asynchronous initialization. This time the reset signal must be included in the process sensitivity list as the state machine must respond to events on both the clk signal and the reset signal - so an event on either signal will activate the process.

state_machine :  (clk,reset)

(reset = '0')

state_vector <= idle; t

(clk'EVENT clk = '1')



;

state_machine;

Note that the reset has priority over the clock implicitly due to the fact that the IF statement is evaluated in sequential order and hence the initialization condition is checked first - if it evaluates to TRUE the state_vector will be assigned the value idle regardless of the value of clk.






State transitions


The templates we have developed so far allow us to define where our example state machine will start from (the initial state is idle) and when the state transitions will take place (synchronous with the rising or falling edge of clk). We now need to define the conditions that cause the state transitions to happen. Examining the example state diagram above we see that :



  • The state machine will go to the idle state whenever reset is '0' (i.e. asynchronous).
  • It will stay in idle until start is '0' then it will pass to state1.
  • From state1, it will pass into state2 if branch is '0', if branch is '1' it will pass into state4.
  • From state2, it will pass into state3.
  • From state3, it will pass into state4 if hold is '0', otherwise it will stay in state3.
  • From state4, it will pass into state5.
  • From state5, it will pass into idle.

The generally accepted method of coding a state flow is by using a CASE...WHEN construct with one WHEN for each state. The transition conditions for each state are described by IF...THEN...ELSE statements. The VHDL process will now look like this:

state_machine :  (clk,reset)

(reset='0')

state_vector <= idle;

(clk'EVENT clk='1')


state_vector

idle =>
(start = '0')
state_vector <= state1;

state_vector <= idle;
;

state1 =>
(branch = '1')
state_vector <= state4;

state_vector <= state2;
;

state2 =>
state_vector <= state3;

state3 =>
(hold = '0')
state_vector <= state4;

state_vector <= state_vector;
;

state4 =>
state_vector <= state5;

state5 =>
state_vector <= idle;

=>
state_vector <= idle;

;

;
state_machine;

The last remaining step is to choose values for the states.






Manual State encoding


Note : State encoding is also known as state assignment and the term 'explicit' is sometimes used instead of 'manual'.


Binary encoding

Using binary encoding, each state is assigned a unique binary number. Since our example has 6 states, we will require 3 bits to fully encode it and we will choose to assign the value "000" to idle, "001" to state1, "010" to state2 and so on. There is no strict reason to use this assignment order, but assigning "000" to idle (the initial state) makes sense as the flip-flops inside most types of FPGA will power-up already cleared (i.e. at logic '0') or will be cleared by their reset input. (this restriction does not apply to Xilinx FPGAs). The state encodings are explicitly declared as constants, either in the architecture declarative section or in the process declarative section or in a separate VHDL package - however declaring them in the architecture declarative section is more usual as they generally need only be visible inside the architecture where the state machine process resides. If they need to be visible outside of the architecture (i.e. used by other design entities), then a package can be used.

 arc_bincoded  bincoded 


idle : std_logic_vector(2 0) := "000";
state1 : std_logic_vector(2 0) := "001";
state2 : std_logic_vector(2 0) := "010";
state3 : std_logic_vector(2 0) := "011";
state4 : std_logic_vector(2 0) := "100";
state5 : std_logic_vector(2 0) := "101";



Gray encoding

This is a form of binary encoding in which only one bit of the state vector changes when moving from state to state so that the state vector doesn't pass through any intermediate values - important if it's being decoded asynchronously elsewhere in the FPGA. The states are then known as "adjacent states". The VHDL code for the state assignments is similar to the binary coding:

 arc_graycoded OF graycoded IS


idle : std_logic_vector(2 0) := "000";
state1 : std_logic_vector(2 0) := "100";
state2 : std_logic_vector(2 0) := "101";
state3 : std_logic_vector(2 0) := "111";
state4 : std_logic_vector(2 0) := "110";
state5 : std_logic_vector(2 0) := "010";



Enumerated type

First an enumeration type 'states' is declared whose range of possible values are the names of the states in the state machine. Then the state vector is declared as a signal of type states:

 arc_enumtype  enumtype 


states (idle,state1, state2, state3, state4, state5);
state_vector : states;



It is not immediately clear from this exactly how many bits are required to encode all the states (i.e. exactly how many bits are required for the state vector) and what the state encoding will be. Most VHDL compilers will correctly synthesise the required number of bits and default to a sequential binary state assignment, ie. "000", "001"...."101" as in our binary encoding example.


The default state encoding can be overidden by using the enum_encoding attribute:

 arc_enumtype  enumtype 


states (idle,state1, state2, state3, state4, state5);
enum_encoding : string;
enum_encoding OF states : "000 100 101 111 110 010";
state_vector : states;



This will assign the values "000"; to idle, "100" to state1, etc. The state assignment in this example is gray coded and the synthesis results are the same as the gray encoding example. This technique could be applied equally well to the binary coding scheme.


One-hot encoding (state-per-bit)

The one-hot encoding technique was developed as a consequence of the typical FPGA architecture - rich in registers but with limited fan-in to each internal logic block. It is sometimes known as state-per-bit encoding as the state vector has one bit for each state in the state machine. For more information, see the One-hot state machine & VHDL coding section.





Automatic State Assignment


Some VHDL synthesis tools will allow the state encoding scheme to be selected automatically using a 'synthesis directive' which takes the form of a VHDL attribute. For example, the Metamor compiler will automatically generate a one-hot encoded state machine if the following code is included:

 arc_onehot  onehot 


states (idle,state1, state2, state3, state4, state5);
enum_encoding OF states : "one hot";
state_vector : states;



Refer to the documentation supplied with the VHDL synthesis tool you are using.






Two process style


So far all the examples have used a single process to describe the state machine. A popular coding style is to use two processes, a combinatorial process and a synchronous process. In the combinatorial process, the state transition conditions are described in terms of the present state (and any inputs) and in the synchronous process the clocking is defined. Using this style for our example state machine (with asynchronous initialization and explicit binary state encoding) gives :

 arc_twoproc  twoproc 


idle : std_logic_vector(2 0) := "000";
state1 : std_logic_vector(2 0) := "001";
state2 : std_logic_vector(2 0) := "010";
state3 : std_logic_vector(2 0) := "011";
state4 : std_logic_vector(2 0) := "100";
state5 : std_logic_vector(2 0) := "101";

present_state : std_logic_vector(2 0);
next_state : std_logic_vector(2 0);





comb_proc : (present_state,start,branch,hold)



present_state

idle =>
(start = '0')
next_state <= state1;

next_state <= idle;
;

state1 =>
(branch = '1')
next_state <= state4;

next_state <= state2;
;

state2 =>
next_state <= state3;

state3 =>
(hold = '0')
state_vector <= state4;

state_vector <= state_vector;
;

state4 =>
next_state <= state5;

state5 =>
next_state <= idle;

=>
next_state <= idle;

;

comb_proc;



sync_proc : (clk,reset)

(reset = '0')
present_state <= idle;
(clk'EVENT clk = '0')
present_state <= next_state;
;
sync_proc;

arc_twoproc;

Synchronous initialization of two process state machine

To implement a synchronous initialization in a two process state machine, just change the synchronous process as follows:


sync_proc : (clk)



(clk'EVENT clk = '0')

(reset = '0')
present_state <= idle;


present_state <= next_state;

;

;
sync_proc;

arc_twoproc;





Using WHEN OTHERS


The CASE statements used in our examples all include the WHEN OTHERS condition which defines the state machine action when the state vector has a value that is not covered by the state assignments - the state machine is in an illegal state. This can happen in real-world design due to such things as noise, power glitches or flip-flop setup and hold violations.


The designer can choose to make the state machine 'fault tolerant' by forcing the state machine to a legal state (usually the initial state) whenever it is in an illegal state using the WHEN OTHERS as follows:

 =>         
next_state <= idle;

This however consumes a lot more logic resources. If fault recovery is not required then the designer can choose to define the uncovered values as 'don't care':

 =>                  
next_state <= ( =>'-');

This will allow the logic synthesis tool to minimize the state transition equations. Note that there must be one '-' for each bit in the state vector.


The WHEN OTHERS condition may also be needed for simulation even if all the states have been assigned - the state vector is usually defined as being a std_logic_vector type, and so each bit in the state vector has 9 possible values, not just '1' or '0'.






Generating other outputs


Most state machines are used to generate other outputs, not just the state vector itself. These other outputs are generated either from the state vector only (Moore machine) or from a combination of the state vector and other inputs (Mealy machine).


As an example, we will generate two outputs from our example state machine; out1 will be active high in state2 and state3 and out2 will be active high in state3 if an additional input, in1, is active high. The timing is shown below:

点击看大图

The outputs can be generated in one of several ways :


As a combinatorial function of the present state vector and the inputs
statefig1.gif

The output decoding logic introduces an extra propagation delay between the clock edge and the output signals. Assuming that the rising edge of clk is used to clock the present state registers (i.e. the state vector) then the clock edge to output delay is as shown below:

statefig6.gif

The total delay from the rising clock edge to when the output changes state is made up of the delay from the clock edge to when the state vector changes state (Tco) plus the propagation delay in the output decoding logic (Tpd).


If the state machine has been coded in the single process style, the state vector must be declared as a signal (so that it's visible outside the process), and then the outputs can be generated using a conditional signal assignment (WHEN...ELSE):

out1 <= '1'  ((state_vector=state2)  (state_vector=state3))
'0';

out2 <= '1' (in1='1' state_vector=state3)
'0';

If the two process style has been used, the output assignments can be placed inside a separate combinational process (i.e. a third process), with a signal assignment in each WHEN clause:

    
out_comb_proc : (present_state)



-- state transitions
present_state

idle =>
out1 <= '0';
out2 <= '0';

state1 =>
out1 <= '0';
out2 <= '0';

state2 =>
out1 <= '1';
out2 <= '0';

state3 =>
out1 <= '1';
out2 <= in1;

state4 =>
out1 <= '0';
out2 <= '0';

state5 =>
out1 <= '0';
out2 <= '0';

=>
out1 <= '0';
out2 <= '0';

;

out_comb_proc;

As a synchronous function of the present state vector and the inputs.
statefig2.gif

This is best described using a multi-process style as the code for the outputs can be kept seperate from the code for the state vector.

   arc_decpstate  decpstate 


idle : std_logic_vector(2 DOWNTO 0) := "000";
state1 : std_logic_vector(2 DOWNTO 0) := "001";
state2 : std_logic_vector(2 DOWNTO 0) := "010";
state3 : std_logic_vector(2 DOWNTO 0) := "011";
state4 : std_logic_vector(2 DOWNTO 0) := "100";
state5 : std_logic_vector(2 DOWNTO 0) := "101";

present_state : std_logic_vector(2 DOWNTO 0);
next_state : std_logic_vector(2 DOWNTO 0);
next_out1 : std_logic;
next_out2 : std_logic;





state_comb_proc : (present_state,start,branch,hold)



present_state IS

idle =>
(start = '0')
next_state <= state1;

next_state <= idle;
;

state1 =>
(branch = '1')
next_state <= state4;

next_state <= state2;
;

state2 =>
next_state <= state3;

state3 =>
(hold = '0') THEN
next_state <= state4;

next_state <= next_state;
;

state4 =>
next_state <= state5;

state5 =>
next_state <= idle;

=>
next_state <= idle;

;

state_comb_proc;


-- the next output combinatorial process
out_comb_proc : (present_state, hold)


-- output transitions
present_state

idle =>
next_out1 <= '0';
next_out2 <= '0';

state1 =>
next_out1 <= '1';
next_out2 <= '0';

state2 =>
next_out1 <= '1';
next_out2 <= in1;

state3 =>
(hold = '1')
next_out1 <= '1';
next_out2 <= in1;

next_out2 <= '0';
next_out1 <= '0';
;

state4 =>
next_out1 <= '0';
next_out2 <= '0';

state5 =>
next_out1 <= '0';
next_out2 <= '0';

=>
next_out1 <= '0';
next_out2 <= '0';

;

out_comb_proc;



sync_proc : (clk,reset)

(reset = '0')

present_state <= idle;
out1 <= '0';
out2 <= '0';

(clk'EVENT clk = '0')

present_state <= next_state;
out1 <= next_out1;
out2 <= next_out2;

;
sync_proc;

arc_decpstate;

The main advantage of a state machine implemented in this way, is that the time delay from the clock edge to when the outputs change state is the same as that for the state vector :

statefig7.gif

However the outputs can only change state when a clock edge occurs.


One disadvantage of this coding style can be seen by examining the output combinatorial process:

     state1 =>
next_out1 <= '1';
next_out2 <= '0';

Our original specification for the outputs required out1 to go active high in state2, but the above code snippet seems to indicate that it goes high in state1, but what the code is really "saying" is that when the state vector changes from state1 to state2, then the signal out1 is assigned the value of the signal next_out1. We can overcome this slight difficulty by using the next state vector instead of the present state.


As a synchronous function of the next state vector and the inputs
statefig3.gif

This is essentially the same as the previous coding style (and produces the same timing) but avoids the problem of outputs being specified with the states preceding the one in which they are actually active. The only change to the code is in the outputs combinatorial process. The next state vector (next_state)is now included in the sensitivity list instead of the present state vector:

  
out_comb_proc : (next_state, in1)



next_state

idle =>
next_out1 <= '0';
next_out2 <= '0';

state1 =>
next_out1 <= '0';
next_out2 <= '0';

state2 =>
next_out1 <= '1';
next_out2 <= '0';

state3 =>
next_out1 <= '1';
next_out2 <= in1;

state4 =>
next_out1 <= '0';
next_out2 <= '0';

state5 =>
next_out1 <= '0';
next_out2 <= '0';

=>
next_out1 <= '0';
next_out2 <= '0';

;

out_comb_proc;

The other difference that can be seen is for the output generation in state3:

   state3 =>
next_out1 <= '1';
next_out2 <= in1;

The next state vector is generated from the present state vector and the start, branch and hold inputs - so the code to generate the outputs when the next state vector is state3 isn't qualified with hold.


文章评论0条评论)

登录后参与讨论
我要评论
0
4
关闭 站长推荐上一条 /2 下一条