0

I'm working on creating a state machine and vhdl code for a task I've been given. It involves a machine which uses a FPGA to control its functions. It has a 100 MHZ clock with 50% duty cycle. The user presses a button on the machine and a led is turned on. Once this happens, the machine begins looking for a data sequence(preamble) on the data line. Once the preamble is detected, a servo rotates clockwise to a 90 degree position, remains up for 10 sec, after which the whole system resets. The servo has a period of 20ms, the starting position has a duty cycle of 1.0ms and the vertical position has a duty cycle of 1.5ms. The preamble sequence is 1-0-1-0-0-0-0-1-0-1-0-0-0-0-0-0-1 with a bit period of 0.5 microseconds.

Its been some years since I've done anything with FPGAs or VHDL so this is a bit of a struggle for me at the moment. I'm currently working on the state machine but I'm having some difficulty due to the number of states. I have 20 states in my state machine. S0 is the initial state, S1 is the state with the led on, S2-S18 represent the states where I'm detecting the preamble, S19 is the state where servo is rotating, and then the state machine goes back to the initial state. I'm not sure if I'm going about this correctly but any help or suggestions would be appreciated.

Picture of FSM

  • Your reasoning looks good so far. I'm just wondering about S19 though. How long does it take for the servo to reach 90 degrees from the initial position? And then how long to get back to the initial position for the reset state? Should S19 be broken up into 3 states? i.e. S19: move from initial position to 90 degrees; S20: wait 10 seconds; S21: move back to initial position. It's also worth considering that some design tools have a State Machine Wizard, so you can draw the diagram and it will generate the VHDL for you. – tim Feb 12 '20 at 01:15
  • Hmm. Definitely worth considering Tim. I think you make some valid points. I think it may be easier to break the servo state into 3 states. I’m not exactly sure, timing wise, how long it takes to get from start to vertical or from vertical to start. All i’ve given is period of the servo and duty cycles for the individual servo positions. I’m hoping that maybe the individual times can be figured out from that info. – Tellrell White Feb 12 '20 at 01:38
  • I don't think there is enough info given to work out the inertia, lag and response time of the servo. Something else to think about is the timing clocks: 100 MHz; 20 ms; 0.5 microseconds; 10 seconds. Are these provided by hardware external to the FPGA? Consider using PLLs if your design tool and target FPGA allow them. Or use a clock divider or counter. – tim Feb 12 '20 at 01:52
  • Where did your notebook annotation 'State Machine Challenge' come from? Why a monolithic FSM as the solution, there are easier solutions than worrying about [branches for rejected preamble matches](https://stackoverflow.com/questions/60181822/fsm-for-long-bit-sequence)? A shift register can be used to match to the preamble for instance. It requires the same hardware complexity as a one-hot statem machine implementation. Neither of your questions contain specific programming issues. –  Feb 12 '20 at 20:05
  • This a problem/task I was given and one of the requirements was to create a state machine with corresponding vhdl code. However, I'm sure that there are easier methods to solve the problem. – Tellrell White Feb 14 '20 at 23:21
  • I completely agree that an FSM is not the right tool for (all of) this job. The main preamble detection can be done with a shift register and an equality check. This removes almost all of the states. The few remaining states could be implemented as a simple state machine (or similar). – Harry Feb 16 '20 at 07:24

1 Answers1

0

Finite State Machine - Without Shift Register

Here's what I got. I didn't understand your transition from S18 to S19. It seems to me that the preamble has been detected when it is in S18, therefore no need for an extra detection transition. I've also split your S19 into 3 states, i.e. S18, S19 and S20.

State diagram without shift register

Finite State Machine - With Shift Register

Here is the state diagram for a shift register being used to detect the preamble sequence. S1 waits for a shift register to detect the preamble sequence.

State diagram without shift register

VHDL Implementation of FSM

Synchronous State Machine

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity StateMachineChallenge is
    generic
    (
        NUM_STATES: natural := 5;
        NUM_OUTPUTS: natural := 6
    );
    port
    (
        clock: in std_logic;
        reset: in std_logic;
        button: in std_logic;
        found_preamble: in std_logic;
        timer_a_timeout: in std_logic;
        timer_b_timeout: in std_logic;
        timer_c_timeout: in std_logic;
        state: out natural range 0 to NUM_STATES := 0;
        outputs: out std_logic_vector(NUM_OUTPUTS - 1 downto 0) := (others => '0')
    );
end entity;

architecture V1 of StateMachineChallenge is

    constant S0: natural range 0 to NUM_STATES := 0;
    constant S1: natural range 0 to NUM_STATES := 1;
    constant S2: natural range 0 to NUM_STATES := 2;
    constant S3: natural range 0 to NUM_STATES := 3;
    constant S4: natural range 0 to NUM_STATES := 4;

    constant LED_OFF, FIND_PREAMBLE_OFF, TIMER_A_OFF, TIMER_B_OFF, TIMER_C_OFF: std_logic := '0';
    constant LED_ON, FIND_PREAMBLE_ON, TIMER_A_ON, TIMER_B_ON, TIMER_C_ON: std_logic := '1';

    constant SERVO_POSITION_START: std_logic := '0';  -- 1.0 ms
    constant SERVO_POSITION_90DEG: std_logic := '1';  -- 1.5 ms

    type TOutputsTable is array(0 to NUM_STATES - 1) of std_logic_vector(NUM_OUTPUTS - 1 downto 0);
    constant OUTPUTS_TABLE: TOutputsTable :=
    (
        -- LED, Find preamble, Servo position, Timer A run, Timer B run, Timer C run
        (LED_OFF, FIND_PREAMBLE_OFF, SERVO_POSITION_START, TIMER_A_OFF, TIMER_B_OFF, TIMER_C_OFF),
        (LED_ON, FIND_PREAMBLE_ON, SERVO_POSITION_START, TIMER_A_OFF, TIMER_B_OFF, TIMER_C_OFF),
        (LED_ON, FIND_PREAMBLE_OFF, SERVO_POSITION_90DEG, TIMER_A_ON, TIMER_B_OFF, TIMER_C_OFF),
        (LED_ON, FIND_PREAMBLE_OFF, SERVO_POSITION_90DEG, TIMER_A_OFF, TIMER_B_ON, TIMER_C_OFF),
        (LED_ON, FIND_PREAMBLE_OFF, SERVO_POSITION_START, TIMER_A_OFF, TIMER_B_OFF, TIMER_C_ON)
    );
    signal next_state: natural range 0 to NUM_STATES := S0;
    signal next_outputs: std_logic_vector(NUM_OUTPUTS - 1 downto 0) := OUTPUTS_TABLE(S0);

begin
    --
    -- State register and outputs register.
    --
    process(clock, reset)
    begin
        if reset = '1' then
            state <= S0;
            outputs <= OUTPUTS_TABLE(S0);
        elsif rising_edge(clock) then
            state <= next_state;
            outputs <= next_outputs;
        end if;
    end process;

    --
    -- Next state logic
    --
    process(reset, state, button, found_preamble, timer_a_timeout, timer_b_timeout, timer_c_timeout)
    begin
        if reset = '1' then
            next_state <= S0;
        else
            case state is
                when S0 =>  -- Reset
                    if button = '1' then
                        next_state <= S1;
                    else
                        next_state <= S0;
                    end if;
                when S1 =>  -- Looking for preamble
                    if found_preamble then
                        next_state <= S2;
                    else
                        next_state <= S1;
                    end if;
                when S2 =>  -- Moving servo to 90 degrees
                    if timer_a_timeout then
                        next_state <= S3;
                    else
                        next_state <= S2;
                    end if;
                when S3 =>  -- Waiting for 10 seconds
                    if timer_b_timeout then
                        next_state <= S4;
                    else
                        next_state <= S3;
                    end if;
                when S4 =>  -- Moving servo to start position
                    if timer_c_timeout then
                        next_state <= S0;
                    else
                        next_state <= S4;
                    end if;
                when others =>
                    next_state <= S0;
            end case;
        end if;
    end process;

    --
    -- Next outputs logic
    --
    process(reset, next_state)
    begin
        if reset = '1' then
            next_outputs <= OUTPUTS_TABLE(S0);
        else
            next_outputs <= OUTPUTS_TABLE(next_state);
        end if;
    end process;

end architecture;

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity TestBench is
end entity;

architecture V1 of TestBench is

    constant SYS_CLOCK_FREQ: real := 100000000.0;  -- Hz
    constant SYS_CLOCK_PERIOD: time := 1.0 sec / SYS_CLOCK_FREQ;

    signal halt_sys_clock: boolean := false;
    signal sys_clock: std_logic;
    signal reset: std_logic;

    signal button: std_logic;
    signal found_preamble:  std_logic;
    signal timer_a_timeout: std_logic;
    signal timer_b_timeout: std_logic;
    signal timer_c_timeout: std_logic;
    
    constant NUM_STATES: natural := 5;
    constant NUM_OUTPUTS: natural := 6;
    
    signal state: natural range 0 to NUM_STATES;

    signal outputs: std_logic_vector(NUM_OUTPUTS - 1 downto 0) := (others => '0');

    signal led: std_logic;
    signal find_preamble: std_logic;
    signal servo_position: std_logic;
    signal timer_a_enable: std_logic;
    signal timer_b_enable: std_logic;
    signal timer_c_enable: std_logic;

    component StateMachineChallenge is
        generic
        (
            NUM_STATES: natural := NUM_STATES;
            NUM_OUTPUTS: natural := NUM_OUTPUTS
        );
        port
        (
            clock: in std_logic;
            reset: in std_logic;
            button: in std_logic;
            found_preamble: in std_logic;
            timer_a_timeout: in std_logic;
            timer_b_timeout: in std_logic;
            timer_c_timeout: in std_logic;
            state: out natural range 0 to NUM_STATES := 0;
            outputs: out std_logic_vector(NUM_OUTPUTS - 1 downto 0) := (others => '0')
        );
    end component;
    
begin

    SysClockGenerator: process
    begin
        while not halt_sys_clock loop
            sys_clock <= '1';
            wait for SYS_CLOCK_PERIOD / 2.0;
            sys_clock <= '0';
            wait for SYS_CLOCK_PERIOD / 2.0;
        end loop;
        wait;
    end process SysClockGenerator;
    
    ResetProcess: process
    begin
        reset <= '0';
        wait for 1 ns;
        reset <= '1';
        wait for 10 ns;
        reset <= '0';
        --wait for 21 ms;
        wait for 1 ms;
        halt_sys_clock <= true;
        wait;
    end process ResetProcess;
    
    ButtonPress: process
    begin
        button <= '0';
        wait for 1 us;
        button <= '1';
        wait for 1 us;
        button <= '0';
        wait;
    end process ButtonPress;
    
    FindPreamble: process(sys_clock)
        variable count: natural := 0;
    begin
        if rising_edge(sys_clock) and find_preamble = '1' then
            count := count + 1;
        end if;
        if count = 17 * 50 then
            found_preamble <= '1';
        else
            found_preamble <= '0';
        end if;
    end process FindPreamble;
    
    TimerA: process(sys_clock)
        variable count: natural := 0;
    begin
        if rising_edge(sys_clock) and timer_a_enable = '1' then
            count := count + 1;
        end if;
        if count = 200 then
            timer_a_timeout <= '1';
        else
            timer_a_timeout <= '0';
        end if;
    end process TimerA;
    
    TimerB: process(sys_clock)
        variable count: natural := 0;
    begin
        if rising_edge(sys_clock) and timer_b_enable = '1' then
            count := count + 1;
        end if;
        if count = 1000 then
            timer_b_timeout <= '1';
        else
            timer_b_timeout <= '0';
        end if;
    end process TimerB;
    
    TimerC: process(sys_clock)
        variable count: natural := 0;
    begin
        if rising_edge(sys_clock) and timer_c_enable = '1' then
            count := count + 1;
        end if;
        if count = 200 then
            timer_c_timeout <= '1';
        else
            timer_c_timeout <= '0';
        end if;
    end process TimerC;
    
    DUT: StateMachineChallenge
        generic map
        (
            NUM_STATES => NUM_STATES,
            NUM_OUTPUTS => NUM_OUTPUTS
        )
        port map
        (
            clock => sys_clock,
            reset => reset,
            button => button,
            found_preamble => found_preamble,
            timer_a_timeout => timer_a_timeout,
            timer_b_timeout => timer_b_timeout,
            timer_c_timeout => timer_c_timeout,
            state => state,
            outputs => outputs
        );
    
    (led, find_preamble, servo_position, timer_a_enable, timer_b_enable, timer_c_enable) <= outputs;
        
end architecture;

Clock Timings

Clock Timings

VHDL to Generate Clock Strobes

--
-- Clock Strobe
--
-- Generates a slow clock strobe from a fast clock.
--

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity ClockStrobe is
    generic
    (
        max_count: natural
    );
    port
    (
        clock: in std_logic;
        clock_strobe: out std_logic
    );
end;

architecture V1 of ClockStrobe is
begin
    -- Create clock strobe.
    process(clock)
        variable counter: natural range 0 to max_count := 0;
    begin
        if rising_edge(clock) then
            counter := counter + 1;
            if counter = max_count then
                clock_strobe <= '1';
                counter := 0;
            else
                clock_strobe <= '0';
            end if;
        end if;
    end process;

end architecture;

--
-- Test Bench
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity TestBench is
end entity;

architecture V1 of TestBench is

    constant SYS_CLOCK_FREQ: real := 100000000.0;  -- Hz
    constant SYS_CLOCK_PERIOD: time := 1.0 sec / SYS_CLOCK_FREQ;

    constant MAX_COUNT_2MHz: natural := 50;
    constant MAX_COUNT_50Hz: natural := 2000000;

    signal stop_clock: boolean := false;
    signal clock: std_logic;
    signal clock_strobe_2MHz: std_logic;
    signal clock_strobe_50Hz: std_logic;

    component ClockStrobe is
        generic
        (
            max_count: natural
        );
        port
        (
            clock: in std_logic;
            clock_strobe: out std_logic
        );
    end component;
    
begin

    ClockGenerator: process
    begin
        while not stop_clock loop
            clock <= '0';
            wait for SYS_CLOCK_PERIOD / 2.0;
            clock <= '1';
            wait for SYS_CLOCK_PERIOD / 2.0;
        end loop;
        wait;
    end process ClockGenerator;

    ClockStrobe2MHz: ClockStrobe
        generic map
        (
            max_count => MAX_COUNT_2MHz
        )
        port map
        (
            clock => clock,
            clock_strobe => clock_strobe_2MHz
        );

    ClockStrobe50Hz: ClockStrobe
        generic map
        (
            max_count => MAX_COUNT_50Hz
        )
        port map
        (
            clock => clock,
            clock_strobe => clock_strobe_50Hz
        );

    -- Preamble process.
    process(clock)
    begin
        if rising_edge(clock) then
            if clock_strobe_2MHz then
                -- Process the next preamble bit.
            end if;
        end if;
    end process;

    -- Servo process.
    process(clock)
    begin
        if rising_edge(clock) then
            if clock_strobe_50Hz then
                -- Process the servo.
            end if;
        end if;
    end process;

end architecture;

Simulation

Only shows the 2 MHz strobe. Simulation showing 2 MHz strobe

Community
  • 1
  • 1
tim
  • 482
  • 3
  • 12
  • I got it. Just for understanding, why did you have the majority of the states going back to S2 and S8 and S17 going back to S1? And what about S3? – Tellrell White Feb 12 '20 at 06:21
  • Oops! I've corrected S3 and S10. The reason they go back to S1 and S2 is because they ruin the preamble sequence. The 0's go back to the beginning of the sequence to look for an initial 1 again. The 1's go back to the S2 because the transition from S1 is also a 1 (the loop back is treated as the first 1 in the new sequence). – tim Feb 12 '20 at 06:43
  • Thanks Tim for your help. Is it necessary to create a truth table and k maps based on the state machine to assist with creating the vhdl code? – Tellrell White Feb 14 '20 at 23:24
  • It depends on your experience. Since you're asking if it's necessary, I suppose you should. To make things simpler, I would convert S1 to S17 into a shift register that sets a flag when the preamble matches the required sequence. Also, Timer A and Timer C could be replaced with angular positional sensors that could be used to flag when the required position has been reached. – tim Feb 15 '20 at 15:41