ARCHITECTURE RTL OF controller IS

  signal en1, enInt: std_ulogic;

  constant opCodeLength : integer := 5;
  subtype opCodeType is std_ulogic_vector(opCodeLength-1 downto 0);
  constant opLoad  : opCodeType := "00000";
  constant opInput : opCodeType := "00010";
  constant opFetch : opCodeType := "00011";
  constant opAnd   : opCodeType := "00101";
  constant opOr    : opCodeType := "00110";
  constant opXor   : opCodeType := "00111";
  constant opTest  : opCodeType := "01001";
  constant opComp  : opCodeType := "01010";
  constant opAdd   : opCodeType := "01100";
  constant opAddCy : opCodeType := "01101";
  constant opSub   : opCodeType := "01110";
  constant opSubCy : opCodeType := "01111";
  constant opShRot : opCodeType := "10000";
  constant opRet   : opCodeType := "10101";
  constant opOutput: opCodeType := "10110";
  constant opStore : opCodeType := "10111";
  constant opCall  : opCodeType := "11000";
  constant opJump  : opCodeType := "11010";
  constant opIntF  : opCodeType := "11110";

  constant branchConditionLength : integer := 3;
  subtype branchConditionType is std_ulogic_vector(branchConditionLength-1 downto 0);
  constant brAw  : branchConditionType := "000";
  constant brZ   : branchConditionType := "100";
  constant brNZ  : branchConditionType := "101";
  constant brC   : branchConditionType := "110";
  constant brNC  : branchConditionType := "111";

  signal aluOpSel: std_ulogic;
  signal regWriteEn: std_ulogic;

  signal flagsEn, flagsEnable: std_ulogic;
  signal carrySaved: std_ulogic;
  signal zeroSaved: std_ulogic;

  signal branchEnable1, branchEnable: std_ulogic;
  signal discardOpCode: std_ulogic;

  signal updateIntFlag: std_ulogic;

BEGIN
  ------------------------------------------------------------------------------
                                                                -- Enable signal
  buildEnable: process(reset, clock)
  begin
    if reset = '1' then
      en1 <= '0';
    elsif rising_edge(clock) then
      en1 <= '1';
    end if;
  end process buildEnable;

  enInt <= en1 and en;  -- don't enable very first instruction twice

  ------------------------------------------------------------------------------
                                                                 -- ALU controls
  selectdataSource: process(opCode)
  begin
    aluOpSel      <= '0';
    portInSel     <= '0';
    scratchpadSel <= '0';
    case opCode(opCodeLength-1 downto 0) is
      when opLoad  => aluOpSel      <= '1';
      when opInput => portInSel     <= '1';
      when opFetch => scratchpadSel <= '1';
      when opAnd   => aluOpSel      <= '1';
      when opOr    => aluOpSel      <= '1';
      when opXor   => aluOpSel      <= '1';
      when opTest  => aluOpSel      <= '1';
      when opComp  => aluOpSel      <= '1';
      when opAdd   => aluOpSel      <= '1';
      when opAddCy => aluOpSel      <= '1';
      when opSub   => aluOpSel      <= '1';
      when opSubCy => aluOpSel      <= '1';
      when opShRot => aluOpSel      <= '1';
      when others  => aluOpSel      <= '-';
                      portInSel     <= '-';
                      scratchpadSel <= '-';
    end case;
  end process selectdataSource;

  registerFileSel <= aluOpSel and      twoRegInstr;
  instrDataSel    <= aluOpSel and (not twoRegInstr);

  regWriteEn <= enInt and (not discardOpCode);

  regWriteTable: process(opCode, regWriteEn)
  begin
    case opCode(opCodeLength-1 downto 0) is
      when opLoad  => regWrite <= regWriteEn;
      when opInput => regWrite <= regWriteEn;
      when opFetch => regWrite <= regWriteEn;
      when opAnd   => regWrite <= regWriteEn;
      when opOr    => regWrite <= regWriteEn;
      when opXor   => regWrite <= regWriteEn;
      when opAdd   => regWrite <= regWriteEn;
      when opAddCy => regWrite <= regWriteEn;
      when opSub   => regWrite <= regWriteEn;
      when opSubCy => regWrite <= regWriteEn;
      when opShRot => regWrite <= regWriteEn;
      when others  => regWrite <= '0';
    end case;
  end process regWriteTable;

  ------------------------------------------------------------------------------
                                                                 -- I/O controls
  readStrobe  <= enInt when (opCode = opInput) and (discardOpCode = '0')
    else '0';
  writeStrobe <= enInt when (opCode = opOutput) and (discardOpCode = '0')
    else '0';

  ------------------------------------------------------------------------------
                                                          -- scratchpad controls
  scratchpadWrite <= '1' when opCode = opStore else '0';

  ------------------------------------------------------------------------------
                                                                  -- Carry logic
  flagsEn <= enInt and (not branchEnable);

  flagsEnableTable: process(opCode, flagsEn)
  begin
    case opCode(opCodeLength-1 downto 0) is
      when opAnd   => flagsEnable <= flagsEn;
      when opOr    => flagsEnable <= flagsEn;
      when opXor   => flagsEnable <= flagsEn;
      when opTest  => flagsEnable <= flagsEn;
      when opComp  => flagsEnable <= flagsEn;
      when opAdd   => flagsEnable <= flagsEn;
      when opAddCy => flagsEnable <= flagsEn;
      when opSub   => flagsEnable <= flagsEn;
      when opSubCy => flagsEnable <= flagsEn;
      when opShRot => flagsEnable <= flagsEn;
      when others  => flagsEnable <= '0';
    end case;
  end process flagsEnableTable;

  saveCarries: process(reset, clock)
  begin
    if reset = '1' then
      carrySaved <= '0';
      zeroSaved <= '0';
    elsif rising_edge(clock) then
      if flagsEnable = '1' then
        carrySaved <= cOut;
        zeroSaved <= zero;
      end if;
    end if;
  end process saveCarries;

  cIn <= carrySaved;

  ------------------------------------------------------------------------------
                                                     -- Program counter controls
  checkBranchCondition: process(branchCond, zeroSaved, carrySaved)
  begin
    case branchCond(branchConditionLength-1 downto 0) is
      when brAw => branchEnable1 <= '1';
      when brZ  => branchEnable1 <= zeroSaved;
      when brNZ => branchEnable1 <= not zeroSaved;
      when brC  => branchEnable1 <= carrySaved;
      when brNC => branchEnable1 <= not carrySaved;
      when others => branchEnable1 <= '-';
    end case;
  end process checkBranchCondition;

  branchEnableTable: process(opCode, branchEnable1, discardOpCode)
  begin
    if discardOpCode = '0' then
      case opCode(opCodeLength-1 downto 0) is
        when opRet  => branchEnable <= branchEnable1;
        when opCall => branchEnable <= branchEnable1;
        when opJump => branchEnable <= branchEnable1;
        when others  => branchEnable <= '0';
      end case;
    else
      branchEnable <= '0';
    end if;
  end process branchEnableTable;

  progCounterControlTable: process(opCode, enInt, branchEnable)
  begin
    incPC <= enInt;
    loadInstrAddress <= '0';
    loadStoredPC     <= '0';
    case opCode(opCodeLength-1 downto 0) is
      when opRet  => incPC <= not branchEnable;
                     loadStoredPC <= enInt and branchEnable;
      when opCall => incPC <= not branchEnable;
                     loadInstrAddress <= enInt and branchEnable;
      when opJump => incPC <= not branchEnable;
                     loadInstrAddress <= enInt and branchEnable;
      when others => null;
    end case;
  end process progCounterControlTable;

  -- If a branch condition is met, the next operation has to be discarded.
  -- This is due to the synchronous operation of the program ROM: the
  -- instructions are provided one clock period after the program counter.
  -- so while the branch operation is processed, the next instruction is
  -- already being fetched.
  delayBranchEnable: process(reset, clock)
  begin
    if reset = '1' then
      discardOpCode <= '0';
    elsif rising_edge(clock) then
      discardOpCode <= branchEnable;
    end if;
  end process delayBranchEnable;

  ------------------------------------------------------------------------------
                                                       -- Stack pointer controls
  pcStackControlTable: process(discardOpCode, opCode, enInt)
  begin
    storePC <= '0';
    prevPC  <= '0';
    if discardOpCode = '0' then
      case opCode(opCodeLength-1 downto 0) is
        when opRet  => prevPC <= enInt;
        when opCall => storePC <= enInt;
        when others  => null;
      end case;
    end if;
  end process pcStackControlTable;


  ------------------------------------------------------------------------------
                                                            -- interrupt control
  updateIntFlag <= '1' when opCode = opIntF else '0';

END ARCHITECTURE RTL;
