Search code examples
vhdlstate-machine

VHDL State Machine Problems - Repeats States


We are building a processor for our final project. The control unit is a state machine, but it seems to get stuck in states for longer than it should, and thus it repeats instructions.

We are using Vivado 2015.4 and the Nexys4 board.

So, with a single line of instructions to store a value onto the 7-segments loaded up into the instruction memory, the states go:

Fetch =>
Fetch =>
Fetch =>
L_S_D (Load/Store Decode) =>
L_S_E (Load/Store Execute) =>
S_Mem (Store Memory Access) =>
Fetch =>
L_S_D =>
L_S_E =>
S_Mem =>
Fetch =>
L_S_D =>
L_S_E =>
Fetch (forever)

On the two complete run-throughs, the seven-segments display. On the third, incomplete run-through, they do not.

I'm attaching the state machine (relevant states) and the program counter-related code, because I think that's where the problem is.

State machine:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity Fred is
    Port ( Inst : in STD_LOGIC_vector (31 downto 21);
           clk : in std_logic;
           rst : in std_logic;
           Reg2Loc : out std_logic;
           ALUSRC : out std_logic;
           MemtoReg : out std_logic;
           RegWrite : out std_logic;
           Branch : out std_logic;
           ALUOp : out std_logic_vector (1 downto 0);
           UnconB : out std_logic;
           en : out std_logic;
           wea : out std_logic;
           PCWrite : out std_logic;
           REGCEA : out std_logic;
           LEDCode : out std_logic_vector (4 downto 0));
end Fred;

architecture Behavioral of Fred is
    Type type_fstate is (Fetch, L_S_D, L_S_E, L_Mem, S_Mem,
        L_WB, R_I_D, I_E, R_E, I_WB, R_WB, B_E, CBZ_D, B_WB, CBZ_E,
            CBZ_WB);
    attribute enum_encoding : string;
    attribute enum_encoding of type_fstate : type is "one-hot";
    signal current_state : type_fstate;
    signal next_state : type_fstate;
begin

    clockprocess : process (clk, rst, current_state)
    begin 
        if rst = '1' then
            next_state <= Fetch;
        elsif clk'EVENT and clk = '1' then
            next_state <= current_state;
        end if;
    end process clockprocess;

    state_logic: process (next_state)
    begin
            case next_state is
                when Fetch => --00001
                    if ((Inst = "11111000010")) then --LDUR
                        current_state <= L_S_D;
                    elsif ((Inst = "11111000000")) then --STUR
                        current_state <= L_S_D;                        
                    --Additional State Logic Here
                    else
                        current_state <= Fetch;
                    end if;

                when L_S_D => --00010
                    current_state <= L_S_E;

                when L_S_E => --00011
                    if ((Inst = "11111000010")) then
                        current_state <= L_Mem;
                    elsif ((Inst = "11111000000")) then
                        current_state <= S_Mem;
                    end if;

                when S_Mem => --00110
                    current_state <= Fetch;

                --Additional States Here

                when others =>
                    current_state <= Fetch;
            end case;
    end process state_logic;

    output_logic : process (next_state)
    begin
        case next_state is
            when Fetch =>
                Reg2Loc <= '0';
                ALUSRC <= '0';
                MemtoReg <= '0';
                RegWrite <= '0';
                Branch <= '0';
                ALUOp <= "00";
                UnconB <= '0';
                en <= '0';
                wea <= '0';
                PCWrite <= '0';
                REGCEA <= '1';
                LEDCode <= "00001";
            when L_S_D =>
                Reg2Loc <= '1';
                ALUSRC <= '0';
                MemtoReg <= '0';
                RegWrite <= '0';
                Branch <= '0';
                ALUOp <= "00";
                UnconB <= '0';
                en <= '0';
                wea <= '0';
                PCWrite <= '0';
                REGCEA <= '0';
                LEDCode <= "00010";
            when L_S_E =>
                Reg2Loc <= '1';
                ALUSRC <= '1';
                MemtoReg <= '0';
                RegWrite <= '0';
                Branch <= '0';
                ALUOp <= "00";
                UnconB <= '0';
                en <= '0';
                wea <= '0';
                PCWrite <= '1';
                REGCEA <= '0';
                LEDCode <= "00011";
            when S_Mem =>
                Reg2Loc <= '1';
                ALUSRC <= '1';
                MemtoReg <= '0';
                RegWrite <= '0';
                Branch <= '0';
                ALUOp <= "00";
                UnconB <= '0';
                en <= '1';
                wea <= '1';
                PCWrite <= '0';
                REGCEA <= '0';
                LEDCode <= "00110";
            --Additional State Outputs Here                                                                                                                                                        
            when others =>
                Reg2Loc <= '0';
                ALUSRC <= '0';
                MemtoReg <= '0';
                RegWrite <= '0';
                Branch <= '0';
                ALUOp <= "00";
                UnconB <= '0';
                en <= '0';
                wea <= '0';
                PCWrite <= '0';
                REGCEA <= '0';
                LEDCode <= "00000";
        end case;
    end process output_logic;
end Behavioral;

Datapath:

entity Datapath is
    Port (BTNClock : in STD_LOGIC;
          clock : in STD_LOGIC;
          UncondBranch : in STD_LOGIC;
          CondBranch : in STD_LOGIC;
          RRtwoSelect : in STD_LOGIC;
          RegWriteSelect : in STD_LOGIC;
          ALUSource : in STD_LOGIC;
          ALUOpCode : in STD_LOGIC_VECTOR(1 downto 0);
          WriteSelect : in STD_LOGIC;
          MemWrite : in STD_LOGIC;
          REGCEA : in STD_LOGIC;
          PCWrite : in STD_LOGIC;
          seg_select : out STD_LOGIC_vector(6 downto 0);
          anode_select : out STD_LOGIC_vector(7 downto 0);
          ins_out : out STD_LOGIC_VECTOR(31 downto 0);
          RAMSelect : in STD_LOGIC;
          ALUEleven : out STD_LOGIC;
          REGEleven : out STD_LOGIC;
          SwitchReset : in STD_LOGIC);
end Datapath;

architecture Behavioral of Datapath is

    signal PC : STD_LOGIC_VECTOR(9 downto 0);
    signal instruction : STD_LOGIC_VECTOR(31 downto 0);
    signal BranchSelect : STD_LOGIC;
    signal ZeroBranch : STD_LOGIC;
    signal RRtwo : STD_LOGIC_VECTOR(4 downto 0);
    signal RegDataOut1 : STD_LOGIC_VECTOR(63 downto 0);
    signal RegDataOut2 : STD_LOGIC_VECTOR(63 downto 0);
    signal ALUMuxOut : STD_LOGIC_VECTOR(63 downto 0);
    signal SignExtendOut : STD_LOGIC_VECTOR(63 downto 0);
    signal BranchExtend : STD_LOGIC_VECTOR(9 downto 0);
    signal ALUOut : STD_LOGIC_VECTOR(63 downto 0);
    signal ALUZero : STD_LOGIC;
    signal MemoryOut : STD_LOGIC_VECTOR(63 downto 0);
    signal WriteMuxOut : STD_LOGIC_VECTOR(63 downto 0);
    signal Branch : STD_LOGIC_VECTOR(9 downto 0);
    signal PCNext : STD_LOGIC_VECTOR(9 downto 0);
    signal PCIncrement : STD_LOGIC_VECTOR(9 downto 0);
    signal ALUCommand : STD_LOGIC_VECTOR(3 downto 0);
    signal InstEn : STD_LOGIC := '1';
    signal OnlySeven : STD_LOGIC_VECTOR(0 downto 0);
    signal SevSegReset : STD_LOGIC := '0';

begin

    OnlySeven(0) <= MemWrite and not ALUOut(11);
    BranchSelect <= UncondBranch or ZeroBranch;
    ZeroBranch <= CondBranch and ALUZero;
    ins_out <= instruction;
    ALUEleven <= ALUout(11);
    REGEleven <= RegDataOut1(11);

    --Program Counter
    PCReg : PCounter port map ( clk => BTNClock,
               wea => PCWrite,
               newaddress => PCNext,
               thisaddress => PC);

    --Incremental adder
    IncAddr : B_adder port map ( a => PC,
               x => PCIncrement);

    --Branch Adder
    BranchAddr : In_adder port map ( a => PC,
                   b => BranchExtend,
                   x => Branch);

    --Next Instruction Address Mux
    NextPCMux : nine_mux port map ( s => BranchSelect,
                   in1 => PCIncrement,
                   in2 => Branch,
                   output => PCNext);

 --Additional Datapath Elements Here
 end Behavioral;

Program Counter:

entity PCounter is
    Port ( clk : in STD_LOGIC; --clock
           wea : in STD_LOGIC; --write enable
           newaddress : in STD_LOGIC_VECTOR (9 downto 0); --new address coming in
           thisaddress : out STD_LOGIC_VECTOR (9 downto 0) --current address to be executed
           );
end PCounter;

architecture Behavioral of PCounter is
    signal reg: std_logic_vector(9 downto 0); --internal register storage

begin
    process(clk) --nothing happens if this register isn't selected
    begin
        if clk'EVENT and clk = '1' then
                thisaddress <= reg; --send out currently saved address
            if wea = '1' then    
                reg <= newaddress; --and set register to next address
            end if;
            else
                reg <= reg; --otherwise, maintain current value
        end if;
    end process; 
end Behavioral;

This adder just adds one to the value currently in the PC:

entity B_adder is
    Port ( a : in STD_LOGIC_VECTOR (9 downto 0);
           x : out STD_LOGIC_VECTOR (9 downto 0));
end B_adder;

architecture Behavioral of B_adder is

begin

            x <= a + 1;

end Behavioral;

This little mux will select if the next address is coming from the branch adder (not included here) or from the incremental adder above:

entity nine_mux is
    Port ( s : in STD_LOGIC;
           in1 : in STD_LOGIC_VECTOR (9 downto 0);
           in2 : in STD_LOGIC_VECTOR (9 downto 0);
           output : out STD_LOGIC_VECTOR (9 downto 0));
end nine_mux;

architecture Behavioral of nine_mux is

begin

            with s select
                output <= in1 when '0',
                    in2 when others;


end Behavioral;

And this is how the control unit is mapped to the datapath:

entity WholeThing is
    Port ( BTNClock : in STD_LOGIC;
           BTNReset : in STD_LOGIC;
           SwitchReset : in STD_LOGIC;
           clock : in STD_Logic;
           LEDs : out STD_LOGIC_VECTOR(4 downto 0);
           seg : out STD_LOGIC_vector(6 downto 0);
           an : out STD_LOGIC_vector(7 downto 0);
           alu11 : out STD_LOGIC;
           reg11 : out STD_LOGIC
           );
end WholeThing;

architecture Behavioral of WholeThing is

    signal instruction : STD_LOGIC_VECTOR(31 downto 0);
    signal Reg2Loc : STD_LOGIC;
    signal ALUSRC : std_logic;
    signal MemtoReg : std_logic;
    signal RegWrite : std_logic;
    signal Branch : std_logic;
    signal ALUOp : std_logic_vector (1 downto 0);
    signal UnconB : std_logic;
    signal en : std_logic;
    signal wea : std_logic;
    signal PCWrite : std_logic;
    signal REGCEA : std_logic;
    signal SwRst : STD_LOGIC;

begin

    --SwitchReset <= SwRst;

    --Control Unit
    CU : Fred port map ( Inst => instruction(31 downto 21),
           clk => BTNClock,
           rst => BTNReset,
           Reg2Loc => Reg2Loc,
           ALUSRC => ALUSRC,
           MemtoReg => MemtoReg,
           RegWrite =>RegWrite,
           Branch => Branch,
           ALUOp => ALUOp,
           UnconB => UnconB,
           en => en,
           wea => wea,
           PCWrite => PCWrite,
           REGCEA => REGCEA,
           LEDCode => LEDs);

    --Datapath
    DP : Datapath port map (BTNClock => BTNClock,
           clock => clock,
           UncondBranch => UnconB,
           CondBranch => Branch,
           RRtwoSelect => Reg2Loc,
           RegWriteSelect => RegWrite,
           ALUSource => ALUSRC,
           ALUOpCode => ALUOp,
           WriteSelect => MemtoReg,
           MemWrite => wea,
           REGCEA => REGCEA,
           PCWrite => PCWrite,
           seg_select => seg,
           anode_select => an,
           ins_out => instruction,
           RAMSelect => en,
           ALUEleven => alu11,
           REGEleven => reg11,
           SwitchReset => SwitchReset
           );


end Behavioral;

Solution

  • The FSM - the main problem

    Your second process should implement default assignments to spare lots of unnecessary else branches where you define self-edged of your FSM graph. Because you missed that, your FSM creates additional latches for signal current_state! Check your synthesis report for latch warning and you might find multiple of them.

    Other mistakes in the same file

    • You mixed up current_state and next_state. The meaning of the signals does not reflect your code! Your case statement needs to switch on current_state.

    • Don't use the 3-process pattern to describe an FSM. This is a nightmare of code readability and maintenance! One one can read and verify the behavior of this FSM form.

    • You're using the attribute enum_encoding in a wrong way:

      1. to define an FSM encoding, apply fsm_encoding to the state signal
      2. to define a user-defined encoding apply fsm_encoding with value user to your state signal and apply enum_encoding with a space separated list of binary values to your state type.
    • Don't use asynchronous reset. Synchronous, clocked processes have only one signal in the sensitivity list!

    • Combinational processes need to list all read signals in there sensitivity list!

    • You shouldn't use clk'EVENT and clk = '1'. Use rising_edge(clk) instead.

    • If you switch on the same signal multiple times, use a case statement, but no if-elsif construct!

    • Your eyes and probably also your LEDs will not be fast enough to see and display LEDCode.

    Corrected code:

    architecture Behavioral of Fred is
      attribute fsm_encoding : string;
    
      type type_fstate is (
        Fetch, L_S_D, L_S_E, L_Mem, S_Mem,
        L_WB, R_I_D, I_E, R_E, I_WB, R_WB, B_E, CBZ_D, B_WB, CBZ_E,
        CBZ_WB);
    
      signal current_state : type_fstate := Fetch;
      signal next_state    : type_fstate;
      attribute fsm_encoding of current_state : signal is "one-hot";
    begin
      clockprocess : process(clk)
      begin 
        if rising_edge(clk) then
          if rst = '1' then
            current_state <= Fetch;
          else
            current_state <= next_state;
          end if;
        end if;
      end process;
    
      state_logic: process (current_state, Inst)
      begin
        next_state <= current_state;
    
        Reg2Loc  <= '0';
        ALUSRC   <= '0';
        MemtoReg <= '0';
        RegWrite <= '0';
        Branch   <= '0';
        ALUOp    <= "00";
        UnconB   <= '0';
        en       <= '0';
        wea      <= '0';
        PCWrite  <= '0';
        REGCEA   <= '0';
        LEDCode  <= "00000";
    
        case current_state is
          when Fetch => --00001
            REGCEA  <= '1';
            LEDCode <= "00001";
    
            case Inst is
              when "11111000010" => --LDUR
                next_state <= L_S_D;
              when "11111000000" => --STUR
                next_state <= L_S_D;                        
    
              --Additional State Logic Here
              when others =>
                next_state <= Fetch;
            end case;
    
          when L_S_D => --00010
            Reg2Loc <= '1';
            LEDCode <= "00010";
    
            next_state <= L_S_E;
    
          when L_S_E => --00011
            Reg2Loc <= '1';
            ALUSRC  <= '1';
            PCWrite <= '1';
            LEDCode <= "00011";
    
            case Inst is
              when "11111000010" =>
                next_state <= L_Mem;
              when "11111000000" =>
                next_state <= S_Mem;
              when others =>
                -- ???
            end case;
    
          when S_Mem => --00110
            Reg2Loc <= '1';
            ALUSRC  <= '1';
            en      <= '1';
            wea     <= '1';
            LEDCode <= "00110";
    
            next_state <= Fetch;
    
          --Additional States Here
    
          when others =>
            next_state <= Fetch;
        end case;
      end process;
    
    end architecture;
    

    Mistakes in the PC:

    • Never assign something like this reg <= reg in VHDL!

    • You're using arithmetic on type std_logic_vector. This operation is:

      1. not defined for that type, or
      2. you're using a non IEEE package like synopsys.std_logic_unsigned, which shouldn't be used at all. Use package ieee.numeric_std and types signed/unsigned if you need arithmetic operations.
    • Your program counter (PC) does not count (yet?). Based on the single responsibility principle, your PC should be able to:

      • load a new instruction pointer
      • increment an instruction pointer
      • output the current instruction pointer
    • Your PC assigns the output thisaddress with one cycle of delay. Normally, this will break any CPU functionality ...

    • As you're going to implement your design on an FPGA device, make sure to initialize all signals that are translated to memory (e.g. registers) with appropriate init values.

    Improved Code:

    architecture Behavioral of PCounter is
      signal reg: unsigned(9 downto 0) := (others => '0');
    begin
    
      process(clk)
      begin
        if rising_edge(clk) then
          if wea = '1' then    
            reg <= unsigned(newaddress);
          end if;
        end if;
      end process; 
    
      thisaddress <= reg;
    end architecture;
    

    Your adder B_adder

    Is is wise to implement a one-line in an entity consuming 9 lines of code?
    Moreover, your code is describing an incrementer, but not an adder.

    Describing a Multiplexer

    A Multiplexer is not described with a with ... select statement. This will create a priority logic like a chain of if-elseif branches.

    output <= in1 when (s = '0') else in2;
    

    As this is now a size independent one-liner, screw the nine_mux entity.