2

This is a generic question that has bugged me since I was able to understand the Basics of a finite state machine. Suppose I have four states s0 - s3, where the FSM will automatically start at 's0' after power is applied. After some defined delay, the FSM shall enter 's1' - the same goes for the other states. The delay between the different states is not the same.

For example:

Power up -> 's0' -> 100 ms -> 's1' -> 50 us -> 's2' -> 360 us -> 's3' -> 's3'

In a procedural language as C, I'd just call a delay routine with one parameter being the required delay and be done with it.

How do I implement this sort of FSM elegantly ?

best, Chris

user2286339
  • 184
  • 1
  • 4
  • 18
  • Not sure what your skill level with VHDL is, but do you know about clock dividers? You could divide your device clock speed to get a 10us (1MHz) clock speed and then use a counter to count up to your various delays. – codedude Jun 30 '15 at 12:46

4 Answers4

4

My pattern : a delay counter which each state transition can program as and when required, i.e. at the start of each new delay.

It's all synthesisable, though some tools (notably Synplicity) have trouble with accurate Time calculations unless your clock period is an integer number of nanoseconds. For more information on this bug, see this Q&A. If you run into this situation, magic numbers (32000 instead of Synplicity's calculated 32258 in that question) may be the simplest workaround.

Wrapping it in an entity/architecture left as an (easy) exercise.

-- first, some declarations for readability instead of magic numbers
constant clock_period : time := 10 ns; 
--WARNING : Synplicity has a bug : by default it rounds to nanoseconds!
constant longest_delay : time := 100 ms;
subtype delay_type is natural range 0 to longest_delay / clock_period;

constant reset_delay : delay_type := 100 ms / clock_period - 1;
constant s1_delay  : delay_type := 50 us / clock_period - 1;
constant s2_delay  : delay_type := 360 us / clock_period - 1;
-- NB take care to avoid off-by-1 error!

type state_type is (s0, s1, s2, s3);

-- now the state machine declarations:

signal state : state_type;
signal delay : delay_type;

-- now the state machine itself:

process(clock, reset) is

begin
   if reset = '1' then
      state <= s0;
      delay <= reset_delay;
   elsif rising_edge(clock) then
      -- default actions such as default outputs first
      -- operate the delay counter
      if delay > 0 then 
         delay <= delay - 1;
      end if;
      -- state machine proper
      case state is
      when s0 =>
         -- do nothing while delay counts down
         if delay = 0 then
            --start 50us delay when entering S1
            delay <= s1_delay;
            state <= s1;
         end if;
      when s1 =>
         if delay = 0 then
            delay <= s2_delay;
            state <= s2;
         end if;
      when s2 =>
         if delay = 0 then
            state <= s3;
         end if;
      when others =>
         null;
      end case;       
   end if;
end process;
Community
  • 1
  • 1
  • Thanks Brian. To have your code compile in ISE 14.7, I had to change the following lines: type delay_type is range 0 to longest_delay / clock_period; constant reset_delay : integer := 5000000; constant s1_delay : integer := 2500; constant s2_delay : integer := 18000; Vivado 2015.1 could easily synthesize your code from the get-go, but since I don't want to scrap my Spartan-3E board, I am stuck with ISE for now. – user2286339 Jul 01 '15 at 12:34
  • Hmmm I'm pretty sure I've compiled it in ISE ... aaah, for Spartan-6. There is an ISE option to "use new VHDL parser" for older devices for VHDL code that triggers the older parser's bugs (and doesn't hit the new bugs!) - this ought to work. –  Jul 01 '15 at 13:13
  • This is what I came up with: http://pastebin.com/V1QDcQV6. Even if I set "-use_new_parser yes" (with Spartan3 or 6) I see errors starting at line 44. What am I missing ? – user2286339 Jul 01 '15 at 15:55
  • What you are missing is my own confusion! Lline 44 should read `subtype delay_type is ... ` making it a subtype of Natural rather than a new type. –  Jul 01 '15 at 16:57
  • That was it. Now, ISE happily synthesizes the code. Thank you ! – user2286339 Jul 01 '15 at 21:28
1

You could use a combination of a clock divider and counters. Find out what the clock speed on your device is. All the delays you mentioned are factorable by 10us so I'll use a clock divider to get to that speed. Let's assume your original clock speed of your device is 50MHz. You'll need to find out how many cycles you'll need to count to 10us. The following calculation does that:

# of cycles = 10ms * 50MHz = 5000 cycles

So you're going to need a counter that counts to 5000. A rough example would be the following:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity new_clk is
    Port (
        clk_in : in  STD_LOGIC; -- your 50MHZ device clock
        reset  : in  STD_LOGIC;
        clk_out: out STD_LOGIC -- your new clock with a 10us period
    );
end clk200Hz;

architecture Behavioral of new_clk is
    signal temporal: STD_LOGIC;
    signal counter : integer range 0 to 4999 := 0;
begin
    clk_div: process (reset, clk_in) begin
        if (reset = '1') then
            temporal <= '0';
            counter <= 0;
        elsif rising_edge(clk_in) then
            if (counter = 4999) then
                temporal <= NOT(temporal);
                counter <= 0;
            else
                counter <= counter + 1;
            end if;
        end if;
    end process;

    clk_out <= temporal;
end Behavioral;

Note how the counter goes from 0 to 4999. The signal clk_out will now have a period of 10us. You can use this to generate your delays now.

For example, for your 360us delay, count 36 periods of the clk_out signal. The code will be roughly similar to what is above but this time you're counting clk_out and your counter is only going from 0 to 35.

(I can add more later but this should get you started.)

codedude
  • 6,244
  • 14
  • 63
  • 99
0

Check chapters 8-9 of "Finite State Machines in Hardware: Theory and Design (with VHDL and SystemVerilog)" MIT Press, 2013, for a detailed discussion covering any case and many complete examples.

VAP
  • 541
  • 3
  • 3
-1

a Little Ode To Brian Drummond's Splendid Answer : Thanks Brian ! :)

The Main Difference is The Removal of The Fixed Upper Limit To The Delay's Length : It's Now Only Limited By The 'delay' SIGNAL Type's Length - Which Can, Generally, Be As Long As Needed.

LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.NUMERIC_STD.ALL;

ENTITY Test123 IS

    PORT (
        clk_in1 : IN std_logic := '0';
        rst1, en1 : IN std_logic;
        );

END ENTITY Test123;

ARCHITECTURE Test123_Arch OF Test123 IS

    -- first, some declarations for readability instead of magic numbers
    CONSTANT clock_period : TIME := 20 ns; -- 50 MHz
    --WARNING : Synplicity has a bug : by default it rounds to nanoseconds!
    CONSTANT reset_delay : TIME := 100 ms - clock_period;
    CONSTANT s1_delay : TIME := 50 us - clock_period;
    CONSTANT s2_delay : TIME := 360 us - clock_period;
    -- NB take care to avoid off-by-1 error!

    -- now the state machine declarations:
    TYPE state_type IS (s0, s1, s2, s3);
    SIGNAL state : state_type;
    --
    --signal delay : unsigned(47 downto 0) := (others => '0'); -- a 48-Bit 'unsigned' Type, Along a 50-MHz Clock, Evaluates To an Upper-Limit of ~90,071,992.5474 Seconds.
    SIGNAL delay : NATURAL := 0; -- a 'natural' Type, Along a 50-MHz Clock, Evaluates To an Upper-Limit of ~85.8993459 Seconds.
    --

    FUNCTION time_to_cycles(time_value : TIME; clk_period : TIME) RETURN NATURAL IS
    BEGIN
        -- RETURN TO_UNSIGNED((time_value / clk_period), 48); -- Return a 48-Bit 'unsigned'
        RETURN (time_value / clk_period); -- Return a 32-Bit 'natural'
    END time_to_cycles;
    --

    BEGIN
        -- now the state machine itself:

        sm0 : PROCESS (clk_in1, rst1)
        BEGIN
            IF (rst1 = '1') THEN
                state <= s0;
                delay <= time_to_cycles(reset_delay, clock_period);

            ELSIF rising_edge(clk_in1) THEN
                -- default actions such as default outputs first
                -- operate the delay counter
                IF (delay > 0) THEN
                    delay <= delay - 1;
                END IF;
                -- state machine proper
                CASE state IS
                    WHEN s0 =>
                        -- do nothing while delay counts down
                        IF (delay = 0) THEN
                            --start 50us delay when entering S1
                            delay <= time_to_cycles(s1_delay, clock_period);
                            state <= s1;
                        END IF;
                    WHEN s1 =>
                        IF (delay = 0) THEN
                            delay <= time_to_cycles(s2_delay, clock_period);
                            state <= s2;
                        END IF;
                    WHEN s2 =>
                        IF (delay = 0) THEN
                            state <= s3;
                        END IF;
                    WHEN OTHERS =>
                        NULL;
                END CASE;
            END IF;
        END PROCESS;

END ARCHITECTURE Test123_Arch;
Mr. Z.
  • 1
  • 1
  • After removing the deliberate upper limit, have fun with overflow issues and undocumented synthesis features! –  Oct 13 '21 at 11:34