Willis
Willis

Reputation: 5336

VHDL state machine is skipping states

I am developing a state machine in VHDL and it doesn't' seem to be functioning properly. The design is shown below:

SHARED VARIABLE XM_INDEX : NATURAL RANGE 0 TO 99 := 0;
SIGNAL XM_STATE_INDICATOR : STD_LOGIC_VECTOR (7 DOWNTO 0) := "00000000";
TYPE XM_STATE_TYPE IS (EMPTY, IDLE, POWER_UP, POWER_UP_CONFIRM, 
                       CHANNEL_SELECT, CHANNEL_SELECT_CONFIRM, VOLUME_CHANGE, 
                       VOLUME_CHANGE_CONFIRM, TRANSMIT_CHAR, TRANSMIT_CHAR_CONFIRM,
                       COMPLETED);
SIGNAL XM_CURRENT_STATE : XM_STATE_TYPE := EMPTY;
SIGNAL XM_NEXT_STATE : XM_STATE_TYPE := EMPTY;

XMStateMachineClock: PROCESS (CLK25, SYS_RST) IS
BEGIN
   IF (SYS_RST = '1') THEN
      XM_CURRENT_STATE <= EMPTY;
   ELSIF (RISING_EDGE(CLK25)) THEN
      XM_CURRENT_STATE <= XM_NEXT_STATE;
   END IF;               
END PROCESS XMStateMachineClock;

XMStateMachine: PROCESS (XM_CURRENT_STATE) IS
BEGIN
   -- Pend on current XM state
   CASE XM_CURRENT_STATE IS

      -- Empty: Debug only
      WHEN EMPTY =>
         XM_NEXT_STATE <= IDLE;
         XM_STATE_INDICATOR <= "00000001";

      -- Idle: Idle state
      WHEN IDLE =>
         IF XM_POWER_UP = '1' THEN
            XM_INDEX := 0;
            XM_NEXT_STATE <= POWER_UP;
            XM_STATE_INDICATOR <= "00000010";
         ELSE
            -- Remain in idle
            XM_NEXT_STATE <= IDLE;
            XM_STATE_INDICATOR <= "00000001";
         END IF;

      WHEN POWER_UP =>
         XM_NEXT_STATE <= TRANSMIT_CHAR;
         XM_STATE_INDICATOR <= "00000100";

      WHEN TRANSMIT_CHAR =>
         IF (XM_INDEX < 11) THEN
            XM_NEXT_STATE <= TRANSMIT_CHAR_CONFIRM;
            XM_STATE_INDICATOR <= "00001000";
         ELSE
            XM_NEXT_STATE <= COMPLETED;
            XM_STATE_INDICATOR <= "00000000";
         END IF;

      WHEN TRANSMIT_CHAR_CONFIRM =>
         XM_INDEX := XM_INDEX + 1;
         XM_NEXT_STATE <= TRANSMIT_CHAR;
         XM_STATE_INDICATOR <= "00000100";

      WHEN COMPLETED =>
         XM_NEXT_STATE <= COMPLETED;
         XM_STATE_INDICATOR <= "00000000";

      -- Default
      WHEN OTHERS =>

   END CASE;
END PROCESS XMStateMachine;

The state machine is being clocked at 25 MHz. Per my understanding, my state machine should progress between the states as follows:

enter image description here

However, what I see when I hook up my logic analyzer is the following:

enter image description here

It seems as if the state machine is only alternating between the transmit and transmit confirm states once, as opposed to the 11 times that is should, and I cannot figure out why.

Upvotes: 1

Views: 818

Answers (2)

user1155120
user1155120

Reputation:

The example code isn't compete and there's some chance chaning xm_index from a shared variable might upset some plans for it's use, should more than one process write to it. You could note that the user is responsible for controlling exclusive access in -1993 shared variables.

Creating a MCVE by providing a complete entity and architecture pair:

library ieee;
use ieee.std_logic_1164.all;

entity xm_sm is
    port (
        clk25:              in  std_logic;
        sys_rst:            in  std_logic;
        xm_power_up:        in  std_logic
    );
end entity;

architecture foo of xm_sm is

    -- shared variable xm_index:  natural range 0 to 99 := 0;
    signal xm_index:            natural range 0 to 99 := 0; -- CHANGED to SIGNAL
    signal xm_index_nxt:        natural range 0 to 99;  -- ADDED
    signal xm_state_indicator: std_logic_vector (7 downto 0) := "00000000";

    type xm_state_type is     (EMPTY, IDLE, POWER_UP, POWER_UP_CONFIRM, 
                               CHANNEL_SELECT, CHANNEL_SELECT_CONFIRM, 
                               VOLUME_CHANGE, VOLUME_CHANGE_CONFIRM, 
                               TRANSMIT_CHAR, TRANSMIT_CHAR_CONFIRM,
                               COMPLETED);
    signal xm_current_state:   xm_state_type := EMPTY;
    signal xm_next_state:      xm_state_type := EMPTY;

begin

xmstatemachineclock: 
    process (clk25, sys_rst) is
    begin
        if sys_rst = '1' then
            xm_current_state <= EMPTY;
            xm_index <= 0;  -- ADDED
        elsif rising_edge(clk25) then
            xm_current_state <= xm_next_state;
            xm_index <= xm_index_nxt;  -- ADDED
        end if;               
    end process xmstatemachineclock;

xmstatemachine: 
    process (xm_current_state, xm_power_up) is
    begin
       -- pend on current xm state
        case xm_current_state is

            -- empty: debug only
            when EMPTY =>
                xm_next_state <= IDLE;
                xm_state_indicator <= "00000001";

            -- idle: idle state
            when IDLE =>
                if xm_power_up = '1' then
                    xm_index_nxt <= 0;
                    xm_next_state <= POWER_UP;
                    xm_state_indicator <= "00000010";
                else
                    -- remain in idle
                    xm_next_state <= IDLE;
                    xm_state_indicator <= "00000001";
                end if;

            when POWER_UP =>
                xm_next_state <= TRANSMIT_CHAR;
                xm_state_indicator <= "00000100";

            when TRANSMIT_CHAR =>
                if xm_index < 11 then
                    xm_next_state <= TRANSMIT_CHAR_CONFIRM;
                    xm_state_indicator <= "00001000";
                else
                    xm_next_state <= COMPLETED;
                    xm_state_indicator <= "00000000";
                end if;

            when TRANSMIT_CHAR_CONFIRM =>
                if xm_index = 99 then   -- protect again overflow -- ADDED
                    xm_index_nxt <= 0;
                else
                    xm_index_nxt <= xm_index + 1;   -- CHANGED
                end if;
                -- xm_index_nxt <= xm_index + 1;
                xm_next_state <= TRANSMIT_CHAR;
                xm_state_indicator <= "00000100";

            when COMPLETED =>
                xm_next_state <= COMPLETED;
                xm_state_indicator <= "00000000";

            -- default
            when others =>

        end case;
    end process xmstatemachine;
end architecture;

This changes xm_index to a signal and including a next value as suggested by Alden in his answer. This works as long as there's only one process that writes to it. xm_index is also now set to 0 during reset. Additionally in the TRANSMIT_CHAR_CONFIRM of the xm_currrent_state case statement xm_index is protected against overflow as a matter of course. The range of xm_index (0 to 99) can be limited to the maximum value (11). It raises suspicions that we're not seeing all of the design.

Adding a test bench:

library ieee;
use ieee.std_logic_1164.all;

entity xm_sm_tb is
end entity;

architecture foo of xm_sm_tb is
    signal clk25:       std_logic := '0';
    signal sys_rst:     std_logic := '0';
    signal xm_power_up: std_logic := '0';
begin
DUT:
    entity work.xm_sm
        port map (
            clk25 => clk25,
            sys_rst => sys_rst,
            xm_power_up => xm_power_up
        );
CLOCK:
    process 
    begin
        wait for 50 ns;
        clk25 <= not clk25;
        if now > 3.1 us then
            wait;
        end if;
    end process;
STIMULI:
    process
    begin
        wait for 100 ns;
        sys_rst <= '1';
        wait for 100 ns;
        sys_rst <= '0';
        wait for 200 ns;
        xm_power_up <= '1';
        wait for 100 ns;
        xm_power_up <= '0';
        wait;
    end process;
end architecture;

and we get:

xm_sm_tb_fixed.png

Where we see we go through all the index values before finishing.

The original code successfully simulated but appears to have not synthesized to a working design due to the combinatorical loop:

XM_INDEX := XM_INDEX + 1;

where xm_loop is latched by a presumably one hot state representation for state TRANSMIT_CHAR_CONFIRM as a latch enable.

In simulation the sensitivity list being devoid of xm_index would prevent the adder from ripple incrementing xm_index. If xm_index had been in the process sensitivity list it would caused a bounds check violation on assignment after reaching 100. (Integer types aren't modular, they don't wrap and aren't proofed against overflow).

In synthesis without seeing the console output we might presume that the ripply time is sufficient to push the value of xm_index above 11 reliably in one clock time without wrapping to less than 11.

Upvotes: 1

Alden
Alden

Reputation: 2269

If you make XM_INDEX a signal have an XM_INDEX_NEXT that is latched in your XMStateMachineClock process and then change XM_INDEX := XM_INDEX + 1 to XM_INDEX_NEXT <= XM_INDEX + 1. I believe that this will fix your issue. XMStateMachine will also need to be sensitive to XM_INDEX.

Upvotes: 1

Related Questions