Question

I am taking a class on embedded system design and one of my class mates, that has taken another course, claims that the lecturer of the other course would not let them implement state machines like this:

architecture behavioral of sm is
    type state_t is (s1, s2, s3);
    signal state : state_t;
begin
    oneproc: process(Rst, Clk)
    begin
        if (Rst = '1') then
            -- Reset
        elsif (rising_edge(Clk)) then
            case state is
                when s1 =>
                    if (input = '1') then
                        state <= s2;
                    else
                        state <= s1;
                    end if;
                    ...
                    ...
                    ...

            end case;
        end if;
    end process;
end architecture;

But instead they had to do like this:

architecture behavioral of sm is
    type state_t is (s1, s2, s3);
    signal state, next_state : state_t;
begin
    syncproc: process(Rst, Clk)
    begin
        if (Rst = '1') then
            --Reset
        elsif (rising_edge(Clk)) then
            state   <= next_state;
        end if;
    end process;

    combproc: process(state)
    begin
        case state is
            when s1 =>
                if (input = '1') then
                    next_state <= s2;
                else
                    next_state <= s1;
                end if;
                ...
                ...
                ...

        end case;
    end process;
end architecture;

To me, who is very inexperienced, the first method looks more fool proof since everything is clocked and there is less (no?) risk of introducing latches.

My class mate can't give me any reason for why his lecturer would not let them use the other way of implementing it so I'm trying to find the pros and cons of each. Is any of them prefered in industry? Why would I want to avoid one or the other?

Was it helpful?

Solution

The single process form is simpler and shorter. This alone reduces the chance that it contains errors.

However the fact that it also eliminates the "incomplete sensitivity list" problem that plagues the other's combinational process should make it the clear winner regardless of any other considerations.

And yet there are so many texts and tutorials advising the reverse, without properly justifying that advice or (in at least one case I can't find atm) introducing a silly mistake into the single process form and rejecting the entire idea on the grounds of that mistake.

The only thing (AFAIK) the single-process form doesn't do well is un-clocked outputs. These are (IMO) poor practice anyway as they can be races at the best of times, and could be handled by a separate combinational process for that output only if you really had to.

I'm guessing there was originally some practical reason behind it; maybe a mid-1990s synthesis tool that couldn't reliably handle the single process form, and that made it into the original documentation that the lecturers learned from. Like those blasted non-standard std_logic_arith libraries. And so the myth has been perpetuated...

Those same lecturers would probably have a fit if they saw what can pass through a modern synthesis tool : integers, enumerations, record types, loops, functions and procedures updating signals (Xilinx ISE is now fine with these. Some versions of Synplicity have trouble with functions, but accept an identical procedure with an Out parameter).

One other comment : I prefer if Rst = '1' then over if (Rst = '1') then. It looks less like line noise (or C).

OTHER TIPS

I agree with Brian on this. The only issue with the one process state machine is you cannot have un-clocked outputs, which is an issue if you need 0 latency on input to output. Otherwise the one process model helps to minimize bugs as it clearly relates the outputs to the state.

I was taught the two process model in school, but have discovered that the one process model is what is generally accepted in industry. I believe the reasoning for using the two process model in school is it gives students an understanding of how the placement of combinational logic relative to registers changes based on how the code is written (which IMO is very important when starting out) and what it means for their design. However simply forcing you to use the two process model with no explanation does not accomplish this.

the first method looks more fool proof since everything is clocked and there is less (no?) risk of introducing latches.

Yes, the first method where everything is clocked has no chance of introducing latches. It may introduce flipflops, but that's fine.

The 2nd method can introduce true asynchronous latches, which even in the best case are not very well handled by the back end FPGA tools I've used, and are not supported at all in some architectures, so would have to be built out of gates or lookup-tables.

In addition, if you get your sensitivity list wrong in the second process, your simulation can differ from your synthesis result! This is because synthesisers (for reasons I've given up trying to understand) treat the sensitivity list as if it were populated with all the signals you read (completely ignoring the VHDL language spec in the process) whereas the simulator will do exactly what you said.

Ugh. I hate the dual process state machine thing personally. He is probably an old guy and this was the most reliable way to do it 20 years ago. The tools understand your way and I personally like that approach better.

Your classmate is absolutely right. The problem here is that your question is not complete. The reason for your colleague's code to be better than yours is that people normally define the output values and the next state values in the same process, as shown below (this is the same as your own code, just with the output values added to it, which results in a "bad"code):

 elsif (rising_edge(clk)) then
    case state is
        when s1 =>
            --define outputs:
            outp1 <= ...;
            outp2 <= ...;
            ...
            --define next state:
            if (input = '1') then
               state <= s2;
            else
               state <= s1;
             end if;
          when s2 =>
             ...
          ...
        end case;
    end if;

Recall that in an FSM the output is produced by the combinational logic section, therefore it is memoryless. However, in the code above, the output values get registered, which is not what the FSM must produce. Indeed, registering the outputs is a case-by-case decision EXTERNAL to the FSM (the outputs could be registered, for example, for glitch removal, which is a PARTICULAR, PLANNED decision, not a FORCED situation, as in the code above).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top