FPGARelated.com
Forums

Style of coding complex logic (particularly state machines)

Started by Eli Bendersky August 24, 2006
Hello all,

In a recent thread (where the O.P. looked for a HDL "Code Complete"
substitute) an interesting discussion arised regarding the style of
coding state machines. Unfortunately, the discussion was mostly
academic without much real examples, so I think there's place to open
another discussion on this style, this time with real examples
displaying the various coding styles. I have also cross-posted this to
c.l.vhdl since my examples are in VHDL.

I have written quite a lot of VHDL (both for synthesis and simulation
TBs) in the past few years, and have adopted a fairly consistent coding
style (so consistent, in fact, that I use Perl scripts to generate some
of my code :-). My own style for writing complex logic and state
machines in particular is in separate clocked processes, like the
following:


type my_state_type is
(
  wait,
  act,
  test
);

signal my_state: my_state_type;
signal my_output;

...
...

my_state_proc: process(clk, reset_n)
begin
  if (reset_n = '0') then
    my_state <= wait;
  elsif (rising_edge(clk))
    case my_state is
      when wait =>
        if (some_input = some_value) then
          my_state <= act;
        end if;
        ...
        ...
      when act =>
        ...
      when test =>
        ...
      when others =>
        my_state <= wait;
     end case;
  end if;
end process;

my_output_proc: process(clk, reset_n)
begin
  if (reset_n = '0') then
    my_output <= '0';
  elsif (rising_edge(clk))
    if (my_state = act and some_input = some_other_val) then
      ...
    else
      ...
   end if;
  end if;
end process;


Now, people were referring mainly to two styles. One is variables used
in a single big process, with the help of procedures (the style Mike
Tressler always points to in c.l.vhdl), and another style - two
processes, with a combinatorial process.

It would be nice if the proponents of the other styles presented their
ideas with regards to the state machine design and we can discuss the
merits of the approaches, based on real code and examples.

Thanks
Eli

Hi Eli,
discussion about styles is not really satisfying. You find it in this 
newsgroup again and again, but in the end most people stick to the style 
they know best. Style is a personal queastion than a technical one.

Just to give you an example:
The 2-process -FSM you gave as an example always creates the registered 
outputs one clock after the state changes. That would drive me crazy 
when checking the simulation.

Why are you using if-(elsif?) in the second process? If you have an 
enumerated state type you could use a case there as well. Would look 
much nicer in the source, too.

Now... Will you change your style to overcome these "flaws" or are you 
still satisfied with it, becaused you are used to it?

Both is OK. :-)

Anyway, each style has it's pros and cons and it always depends on what 
you want to do.
-- has the synthesis result to be very fast or very small?
-- do you need to speed up your simulation
-- do you want easy readable sourcecode (that also is very personal, 
what one considers "readable" may just look like greek to someone else)
-- etc. etc.

So, there will be no common consensus.

Best regards
   Eilert



Eli Bendersky schrieb:
> Hello all, > > In a recent thread (where the O.P. looked for a HDL "Code Complete" > substitute) an interesting discussion arised regarding the style of > coding state machines. Unfortunately, the discussion was mostly > academic without much real examples, so I think there's place to open > another discussion on this style, this time with real examples > displaying the various coding styles. I have also cross-posted this to > c.l.vhdl since my examples are in VHDL. > > I have written quite a lot of VHDL (both for synthesis and simulation > TBs) in the past few years, and have adopted a fairly consistent coding > style (so consistent, in fact, that I use Perl scripts to generate some > of my code :-). My own style for writing complex logic and state > machines in particular is in separate clocked processes, like the > following: > > > type my_state_type is > ( > wait, > act, > test > ); > > signal my_state: my_state_type; > signal my_output; > > ... > ... > > my_state_proc: process(clk, reset_n) > begin > if (reset_n = '0') then > my_state <= wait; > elsif (rising_edge(clk)) > case my_state is > when wait => > if (some_input = some_value) then > my_state <= act; > end if; > ... > ... > when act => > ... > when test => > ... > when others => > my_state <= wait; > end case; > end if; > end process; > > my_output_proc: process(clk, reset_n) > begin > if (reset_n = '0') then > my_output <= '0'; > elsif (rising_edge(clk)) > if (my_state = act and some_input = some_other_val) then > ... > else > ... > end if; > end if; > end process; > > > Now, people were referring mainly to two styles. One is variables used > in a single big process, with the help of procedures (the style Mike > Tressler always points to in c.l.vhdl), and another style - two > processes, with a combinatorial process. > > It would be nice if the proponents of the other styles presented their > ideas with regards to the state machine design and we can discuss the > merits of the approaches, based on real code and examples. > > Thanks > Eli >
backhus wrote:
> Hi Eli, > discussion about styles is not really satisfying. You find it in this > newsgroup again and again, but in the end most people stick to the style > they know best. Style is a personal queastion than a technical one. > > Just to give you an example: > The 2-process -FSM you gave as an example always creates the registered > outputs one clock after the state changes. That would drive me crazy > when checking the simulation.
I guess this indeed is a matter of style. It doesn't drive me crazy mostly because I'm used to it. Except in rare cases, this single clock cycle doesn't change anything. However, the benefit IMHO is that the separation is cleaner, especially when a lot of signals depend on the state.
> > Why are you using if-(elsif?) in the second process? If you have an > enumerated state type you could use a case there as well. Would look > much nicer in the source, too.
I prefer to use if..else if there is only one "if". When there are "elsif"s, case is preferable.
> > Now... Will you change your style to overcome these "flaws" or are you > still satisfied with it, becaused you are used to it? > > Both is OK. :-) > > Anyway, each style has it's pros and cons and it always depends on what > you want to do. > -- has the synthesis result to be very fast or very small? > -- do you need to speed up your simulation > -- do you want easy readable sourcecode (that also is very personal, > what one considers "readable" may just look like greek to someone else) > -- etc. etc. > > So, there will be no common consensus. >
In my original post I had no intention to reach a common consensus. I wanted to see practical code examples which demonstrate the various techniques and discuss their relative merits and disadvantages. Kind regards, Eli
> Eli Bendersky schrieb: > > Hello all, > > > > In a recent thread (where the O.P. looked for a HDL "Code Complete" > > substitute) an interesting discussion arised regarding the style of > > coding state machines. Unfortunately, the discussion was mostly > > academic without much real examples, so I think there's place to open > > another discussion on this style, this time with real examples > > displaying the various coding styles. I have also cross-posted this to > > c.l.vhdl since my examples are in VHDL. > > > > I have written quite a lot of VHDL (both for synthesis and simulation > > TBs) in the past few years, and have adopted a fairly consistent coding > > style (so consistent, in fact, that I use Perl scripts to generate some > > of my code :-). My own style for writing complex logic and state > > machines in particular is in separate clocked processes, like the > > following: > > > > > > type my_state_type is > > ( > > wait, > > act, > > test > > ); > > > > signal my_state: my_state_type; > > signal my_output; > > > > ... > > ... > > > > my_state_proc: process(clk, reset_n) > > begin > > if (reset_n = '0') then > > my_state <= wait; > > elsif (rising_edge(clk)) > > case my_state is > > when wait => > > if (some_input = some_value) then > > my_state <= act; > > end if; > > ... > > ... > > when act => > > ... > > when test => > > ... > > when others => > > my_state <= wait; > > end case; > > end if; > > end process; > > > > my_output_proc: process(clk, reset_n) > > begin > > if (reset_n = '0') then > > my_output <= '0'; > > elsif (rising_edge(clk)) > > if (my_state = act and some_input = some_other_val) then > > ... > > else > > ... > > end if; > > end if; > > end process; > > > > > > Now, people were referring mainly to two styles. One is variables used > > in a single big process, with the help of procedures (the style Mike > > Tressler always points to in c.l.vhdl), and another style - two > > processes, with a combinatorial process. > > > > It would be nice if the proponents of the other styles presented their > > ideas with regards to the state machine design and we can discuss the > > merits of the approaches, based on real code and examples. > > > > Thanks > > Eli > >
Very interesting coding style. I'm curious why there are separate
clocked processes. You could just tack on the output code to the bottom
of the state transition process, but that is only a nit.

As long as I'm using registered outputs, I would personally prefer a
combined process, but that's just how I approach the problem. I want to
know everthing that happens in conjunction with a state by looking in
one place, not by looking here to see where/when the next state goes,
and then looking there to see what outputs are generated.

To illustrate, by modifying the original example:

 my_state_proc: process(clk, reset_n)
  type my_state_type is (wait, act, test);
  variable my_state: my_state_type;
 begin
   if (reset_n = '0') then
     my_state := wait;
     my_output <= '0';
   elsif (rising_edge(clk))
     case my_state is
       when wait =>
         if (some_input = some_value) then
           my_state := act;
         end if;
         ...
         ...
       when act =>
         if some_input = some_other_val then
           my_output <= yet_another_value;
         else
           ...
        end if;         ...
       when test =>
         ...
       when others =>
         my_state := wait;
      end case;
   end if;
 end process;

The only time I would use separate logic code for outputs is if I
wanted to have combinatorial outputs (from registered variables, not
from inputs). Then I would put the output logic code after the clocked
clause, inside the process. I try to avoid combinatorial
input-to-output paths if at all possible.

Then it would look like this:

 my_state_proc: process(clk, reset_n)
  type my_state_type is (wait, act, test);
  variable my_state: my_state_type;
 begin
   if (reset_n = '0') then
     my_state := wait;
     my_output <= '0';
   elsif (rising_edge(clk))
     case my_state is
       when wait =>
         if (some_input = some_value) then
           my_state := act;
         end if;
         ...
         ...
       when act =>
         if some_input = some_other_val then
           my_output <= yet_another_value;
         else
           ...
        end if;         ...
       when test =>
         ...
       when others =>
         my_state := wait;
      end case;
   end if;
   if state = act then -- cannot use process inputs here
      my_output <= yet_another_value; -- or here
   end if;
 end process;

Interestingly, the clock cycle behavior of the above is identical if I
changed the end of the process to:

      ...
      end case;
      -- you CAN use process inputs here:
      if (state = act) then
         my_output <= yet_another_value; -- or here
      end if;
   end if;
 end process;

Note that my_output is now a registered output from combinatorial
inputs, whereas before it was a combinatorial output from registered
values. Previously  you could not use process inputs, now you can.

Andy

Eli Bendersky wrote:
> backhus wrote: > > Hi Eli, > > discussion about styles is not really satisfying. You find it in this > > newsgroup again and again, but in the end most people stick to the style > > they know best. Style is a personal queastion than a technical one. > > > > Just to give you an example: > > The 2-process -FSM you gave as an example always creates the registered > > outputs one clock after the state changes. That would drive me crazy > > when checking the simulation. > > I guess this indeed is a matter of style. It doesn't drive me crazy > mostly because I'm used to it. Except in rare cases, this single clock > cycle doesn't change anything. However, the benefit IMHO is that the > separation is cleaner, especially when a lot of signals depend on the > state. > > > > > Why are you using if-(elsif?) in the second process? If you have an > > enumerated state type you could use a case there as well. Would look > > much nicer in the source, too. > > I prefer to use if..else if there is only one "if". When there are > "elsif"s, case is preferable. > > > > > Now... Will you change your style to overcome these "flaws" or are you > > still satisfied with it, becaused you are used to it? > > > > Both is OK. :-) > > > > Anyway, each style has it's pros and cons and it always depends on what > > you want to do. > > -- has the synthesis result to be very fast or very small? > > -- do you need to speed up your simulation > > -- do you want easy readable sourcecode (that also is very personal, > > what one considers "readable" may just look like greek to someone else) > > -- etc. etc. > > > > So, there will be no common consensus. > > > > In my original post I had no intention to reach a common consensus. I > wanted to see practical code examples which demonstrate the various > techniques and discuss their relative merits and disadvantages. > > Kind regards, > Eli > > > > > Eli Bendersky schrieb: > > > Hello all, > > > > > > In a recent thread (where the O.P. looked for a HDL "Code Complete" > > > substitute) an interesting discussion arised regarding the style of > > > coding state machines. Unfortunately, the discussion was mostly > > > academic without much real examples, so I think there's place to open > > > another discussion on this style, this time with real examples > > > displaying the various coding styles. I have also cross-posted this to > > > c.l.vhdl since my examples are in VHDL. > > > > > > I have written quite a lot of VHDL (both for synthesis and simulation > > > TBs) in the past few years, and have adopted a fairly consistent coding > > > style (so consistent, in fact, that I use Perl scripts to generate some > > > of my code :-). My own style for writing complex logic and state > > > machines in particular is in separate clocked processes, like the > > > following: > > > > > > > > > type my_state_type is > > > ( > > > wait, > > > act, > > > test > > > ); > > > > > > signal my_state: my_state_type; > > > signal my_output; > > > > > > ... > > > ... > > > > > > my_state_proc: process(clk, reset_n) > > > begin > > > if (reset_n = '0') then > > > my_state <= wait; > > > elsif (rising_edge(clk)) > > > case my_state is > > > when wait => > > > if (some_input = some_value) then > > > my_state <= act; > > > end if; > > > ... > > > ... > > > when act => > > > ... > > > when test => > > > ... > > > when others => > > > my_state <= wait; > > > end case; > > > end if; > > > end process; > > > > > > my_output_proc: process(clk, reset_n) > > > begin > > > if (reset_n = '0') then > > > my_output <= '0'; > > > elsif (rising_edge(clk)) > > > if (my_state = act and some_input = some_other_val) then > > > ... > > > else > > > ... > > > end if; > > > end if; > > > end process; > > > > > > > > > Now, people were referring mainly to two styles. One is variables used > > > in a single big process, with the help of procedures (the style Mike > > > Tressler always points to in c.l.vhdl), and another style - two > > > processes, with a combinatorial process. > > > > > > It would be nice if the proponents of the other styles presented their > > > ideas with regards to the state machine design and we can discuss the > > > merits of the approaches, based on real code and examples. > > > > > > Thanks > > > Eli > > >
Eli Bendersky wrote:
> Hello all, > > In a recent thread (where the O.P. looked for a HDL "Code Complete" > substitute) an interesting discussion arised regarding the style of > coding state machines. Unfortunately, the discussion was mostly > academic without much real examples, so I think there's place to open > another discussion on this style, this time with real examples > displaying the various coding styles. I have also cross-posted this to > c.l.vhdl since my examples are in VHDL. > > I have written quite a lot of VHDL (both for synthesis and simulation > TBs) in the past few years, and have adopted a fairly consistent coding > style (so consistent, in fact, that I use Perl scripts to generate some > of my code :-). My own style for writing complex logic and state > machines in particular is in separate clocked processes, like the > following: > > > type my_state_type is > ( > wait, > act, > test > ); > > signal my_state: my_state_type; > signal my_output; > > ... > ... > > my_state_proc: process(clk, reset_n) > begin > if (reset_n = '0') then > my_state <= wait; > elsif (rising_edge(clk)) > case my_state is > when wait => > if (some_input = some_value) then > my_state <= act; > end if; > ... > ... > when act => > ... > when test => > ... > when others => > my_state <= wait; > end case; > end if; > end process; > > my_output_proc: process(clk, reset_n) > begin > if (reset_n = '0') then > my_output <= '0'; > elsif (rising_edge(clk)) > if (my_state = act and some_input = some_other_val) then > ... > else > ... > end if; > end if; > end process; > > > Now, people were referring mainly to two styles. One is variables used > in a single big process, with the help of procedures (the style Mike > Tressler always points to in c.l.vhdl), and another style - two > processes, with a combinatorial process. > > It would be nice if the proponents of the other styles presented their > ideas with regards to the state machine design and we can discuss the > merits of the approaches, based on real code and examples. > > Thanks > Eli
I usually separate the state register and combinational logic for the following reason. First, I think that the term "coding style" is very misleading. It is more like "design style". My approach for designing a system (not just FSM) is - Study the specification and think about the hardware architecture - Draw a sketch of top-level block diagram and determine the functionalities of the blocks. - Repeat this process recursively if a block is too complex - Derive HDL code according to the block diagram and perform synthesis. This approach is based on the observation that synthesis software is weak on architecture-level manipulation but good at gate-level logic minimization. It allows me to have full control of the system architecture (e.g., I can easily identify the key components, optimize critical path etc.). The basic block diagram of FSM (and most sequential circuits) consists of a register, next-state logic and output logic. Based on my design style, it is natural to describe each block in a process or a concurrent signal assignment. The number of segments (process and concurrent signal assignments etc.) is really not an issue. It is just a by-product of this design style. The advantage of this approach is that I have better control on final hardware implementation. Instead of blindly relying on synthesis software and testing code in a trial-and-error basis, I can consistently get what I want, regardless which synthesis software is used. On the downside, this approach requires more time in initial design phase and the code is less compact. The VHDL code itself sometimes can be cumbersome. But it is clear and easy to comprehend when presented with the block diagram. One interesting example in FSM design is the look-ahead output buffer discussed in section 10.7.2 of "RTL Hardware Design Using VHDL" (http://academic.csuohio.edu/chu_p/), the book mentioned in the previous thread. It is a clever scheme to obtain a buffered Moore output without the one-clock delay penalty. The code follows the block diagram and uses four processes, one for state register, one for output buffer, one for next-state logic and one for look-ahead output logic. Although it is somewhat lengthy, it is easy to understand. I believe the circuit can be described by using one clocked process with proper mix of signals and variables and reduce the code length by 3 quarters, but I feel it will be difficult to relate the code with the actual circuit diagram and vice versa. My 2 cents. Mike G.
mikegurche@yahoo.com wrote:
> One interesting example in FSM design is the look-ahead output buffer > discussed in section 10.7.2 of "RTL Hardware Design Using VHDL" > (http://academic.csuohio.edu/chu_p/), the book mentioned in the > previous thread. It is a clever scheme to obtain a buffered Moore > output without the one-clock delay penalty. The code follows the block > diagram and uses four processes, one for state register, one for output > buffer, one for next-state logic and one for look-ahead output logic. > Although it is somewhat lengthy, it is easy to understand. I believe > the circuit can be described by using one clocked process with proper > mix of signals and variables and reduce the code length by 3 quarters, > but I feel it will be difficult to relate the code with the actual > circuit diagram and vice versa.
Combining the input and registered state this way allows for a non registered path from input to output. Is this ok? Or is there an assumption that the device connected to the output is itself latching on the clock edge? -Dave -- David Ashley http://www.xdr.com/dash Embedded linux, device drivers, system architecture
mikegurche@yahoo.com wrote:

> This approach is based on the observation that synthesis software is > weak on architecture-level manipulation but good at gate-level logic > minimization.
I have observed that synthesis software does what it is told. If I describe two gates and a flop, that is what I get. If I describe a fifo or an array of counters, that is what I get.
> The advantage of this approach is that I have better control on final > hardware implementation. Instead of blindly relying on synthesis > software and testing code in a trial-and-error basis, I can > consistently get what I want, regardless which synthesis software is > used.
What I want is a netlist that sims the same as my code and makes reasonable use of the device resources. Synthesis does a good job of this with the right design rules. Trial and error would only come into play if I were to run synthesis without simulation.
> On the downside, this approach requires more time in initial > design phase and the code is less compact. The VHDL code itself > sometimes can be cumbersome. But it is clear and easy to comprehend > when presented with the block diagram.
I prefer clean, readable code, verified by simulation and static timing. I use the rtl viewer to convert my logical description to a structural one for review. -- Mike Treseler
Mike Treseler wrote:
> mikegurche@yahoo.com wrote: > > > This approach is based on the observation that synthesis software is > > weak on architecture-level manipulation but good at gate-level logic > > minimization. > > I have observed that synthesis software does what it is told. > If I describe two gates and a flop, that is what I get. > If I describe a fifo or an array of counters, that > is what I get. > > > The advantage of this approach is that I have better control on final > > hardware implementation. Instead of blindly relying on synthesis > > software and testing code in a trial-and-error basis, I can > > consistently get what I want, regardless which synthesis software is > > used. > > What I want is a netlist that sims the same > as my code and makes reasonable use of the > device resources. Synthesis does a good > job of this with the right design rules. > Trial and error would only come into play > if I were to run synthesis without simulation. > > > On the downside, this approach requires more time in initial > > design phase and the code is less compact. The VHDL code itself > > sometimes can be cumbersome. But it is clear and easy to comprehend > > when presented with the block diagram. > > I prefer clean, readable code, > verified by simulation and static timing. > I use the rtl viewer to convert > my logical description to a structural > one for review. > > > -- Mike Treseler
Mike T., This issue has been debated in many threads and I don't want to do it again. The original poster, Eli, stated: ". . . I had no intention to reach a common consensus. I wanted to see practical code examples which demonstrate the various techniques and discuss their relative merits and disadvantages" I expressed my opinion and gave an example from a book. You can do the same. Whatever method you choose is fine with me, but I am irritated that you always think your way is THE WAY. Mike G.
mikegurche@yahoo.com wrote:
> The original poster, Eli, stated:
>> ". . . I had no intention to reach a common consensus. I wanted to >> see practical code examples which demonstrate the various techniques >> and discuss their relative merits and disadvantages"
I've already shared my examples. My posting was intended as part of the "discussion of their relative merits and disadvantages"
> I expressed my opinion and gave an example from a book. You can do > the same. Whatever method you choose is fine with me, but I am > irritated that you always think your way is THE WAY.
The vast majority of designers use your style, not mine. Backhus said it best: "discussion about styles is not really satisfying. You find it in this newsgroup again and again, but in the end most people stick to the style they know best. Style is a personal question than a technical one." -- Mike Treseler
Andy wrote:

[...]

> To illustrate, by modifying the original example: > > my_state_proc: process(clk, reset_n) > type my_state_type is (wait, act, test); > variable my_state: my_state_type; > begin > if (reset_n = '0') then > my_state := wait; > my_output <= '0'; > elsif (rising_edge(clk)) > case my_state is > when wait => > if (some_input = some_value) then > my_state := act; > end if; > ... > ... > when act => > if some_input = some_other_val then > my_output <= yet_another_value; > else > ... > end if; ... > when test => > ... > when others => > my_state := wait; > end case; > end if; > end process; > > The only time I would use separate logic code for outputs is if I > wanted to have combinatorial outputs (from registered variables, not > from inputs). Then I would put the output logic code after the clocked > clause, inside the process. I try to avoid combinatorial > input-to-output paths if at all possible. >
[...] Thanks for this example. I have been always trying to avoid variables for things like this and it's interesting to see them used correctly. The problem I see with the approach comes in complicated code where several signals depend on my_state (say 3-4 is enough). Then, the single-process-handling-everything becomes rather convoluted. Besides, since my_state is a variable local to the process, you can't see it outside so you can't use it to drive other signals. So basically you force all code dealing with my_state to be in one process. Another thing is that I prefer out-of-process statements for combinatorial logic, because IMHO it makes a cleaner separation (I immediately see it's combinatorial, without the need to see if it has some extra "end if"s below it that signify it's clocked. comb_out <= '1' when my_state = act else '0'; Somehow I immediately see the combinatorial logic here (if it gets too hairy a function can be coded). Eli