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

entity master_state is

  port (
    clk_i                    : in  std_logic;
    rst_in                   : in  std_logic;
    rst_i2c_o                : out std_logic;
-- control interface
    start_i                  : in  std_logic;
    stop_i                   : in  std_logic;
    run_i                    : in  std_logic;
    rw_i                     : in  std_logic;
-- error inputs and config
    expect_ack_i             : in  std_logic;
    noack_address_i          : in  std_logic;
    noack_data_i             : in  std_logic;
    unexpected_sda_address_i : in  std_logic;
    unexpected_sda_data_i    : in  std_logic;
    condition_early_i        : in std_logic;
-- error outputs (reset on new run)
    err_noack_address_o      : out std_logic;
    err_noack_data_o         : out std_logic;
    err_arbitration_o        : out std_logic;
    err_general_o            : out std_logic;
-- i2c condition detection
    start_condition_i        : in  std_logic;
    stop_condition_i         : in  std_logic;
-- rx, tx comms
    waiting_for_data_i       : in std_logic;
    rx_done_i                : in std_logic;
    tx_done_i                : in std_logic;
-- data for address_generator
    address_gen_start_o      : out std_logic;
    address_gen_done_i       : in  std_logic;
    address_gen_store_o      : out std_logic;
-- data for cond generator
    req_start_o              : out std_logic;
    req_stop_o               : out std_logic;
    req_cond_done_i          : in  std_logic;
-- data for scl generator
    req_scl_continuous_o     : out std_logic;
-- active component
    cond_gen_o               : out std_logic;
    address_gen_o            : out std_logic;
    receive_o                : out std_logic;
    transmit_o               : out std_logic;
    dev_busy_o               : out std_logic;
    bus_busy_o               : out std_logic);

end entity master_state;

architecture a1 of master_state is
  type state_t is (
    IDLE,
    GENERATING_START,                   -- requesting start condition
    GENERATED_START,                    -- start condition generated, waiting
                                        -- for gen done
    GENERATING_ADDRESS,
    TRANSMITTING,
    RECEIVING,
    GENERATING_STOP,
    GENERATED_STOP,
    BUS_BUSY,
    ERR);

  signal curr_state : state_t;
  signal next_state : state_t;

  signal curr_err_arbitration : std_logic;
  signal next_err_arbitration : std_logic;

  signal curr_err_noack_data : std_logic;
  signal next_err_noack_data : std_logic;

  signal curr_err_noack_address : std_logic;
  signal next_err_noack_address : std_logic;

  signal curr_err_general : std_logic;
  signal next_err_general : std_logic;

  signal curr_gen_start_req : std_logic;
  signal next_gen_start_req : std_logic;

  signal curr_gen_stop_req : std_logic;
  signal next_gen_stop_req : std_logic;

  signal any_err, any_terminal_err : std_logic;
  signal control_over_bus : std_logic;
  signal can_gen_cond : std_logic;
  signal cond_gen : std_logic;
begin  -- architecture a1
  any_err <= curr_err_arbitration or curr_err_noack_data or curr_err_noack_address or curr_err_general;
  any_terminal_err <= curr_err_noack_data or curr_err_noack_address or curr_err_general;
  control_over_bus <= '1' when curr_state /= IDLE and curr_state /= BUS_BUSY else '0';
  can_gen_cond <= '1' when (curr_state = IDLE and start_condition_i = '0' and stop_condition_i = '0') or
                           ((curr_state = TRANSMITTING or curr_state = RECEIVING) and (waiting_for_data_i = '1' or tx_done_i = '1' or rx_done_i = '1')) else '0';

  req_scl_continuous_o <= '1' when curr_state = GENERATING_ADDRESS or
                          curr_state = TRANSMITTING or curr_state = RECEIVING else '0';

  req_start_o <= '1' when curr_state = GENERATING_START or curr_state = GENERATED_START else '0';
  req_stop_o <= '1' when curr_state = GENERATING_STOP or curr_state = GENERATED_STOP else '0';
  cond_gen_o <= cond_gen;
  dev_busy_o <= '1' when curr_state /= IDLE and curr_state /= BUS_BUSY and curr_state /= ERR else '0';

  address_gen_start_o <= '1' when curr_state = GENERATED_START and next_state = GENERATING_ADDRESS else '0';
  address_gen_store_o <= start_i;

  receive_o <= '1' when curr_state = RECEIVING else '0';
  transmit_o <= '1' when curr_state = TRANSMITTING else '0';
  address_gen_o <= '1' when curr_state = GENERATING_ADDRESS else '0';
  bus_busy_o <= '1' when curr_state = BUS_BUSY else '0';

  err_noack_address_o <= curr_err_noack_address;
  err_noack_data_o <= curr_err_noack_data;
  err_arbitration_o <= curr_err_arbitration;
  err_general_o <= curr_err_general;

  cond_gen <= '1' when
                curr_state = GENERATING_STOP or curr_state = GENERATED_STOP or
                curr_state = GENERATING_START or curr_state = GENERATED_START else '0';

  rst_i2c_o <= any_err or cond_gen;

  next_gen_start_req <= '0' when curr_state = GENERATING_START or curr_state = GENERATED_START else
                        '1' when start_i = '1' and run_i = '1' else
                        curr_gen_start_req;

  next_gen_stop_req <= '0' when curr_state = GENERATING_STOP or curr_state = GENERATED_STOP else
                       '1' when stop_i = '1' else
                        curr_gen_stop_req;

  next_err_general <= '0' when curr_state = GENERATING_START else
                      '1' when curr_err_general = '1' else
                      '1' when curr_state = GENERATING_STOP and start_condition_i = '1' else
                      '1' when curr_state = GENERATING_START and stop_condition_i = '1' else
                      '1' when curr_state /= GENERATING_STOP and control_over_bus = '1' and stop_condition_i = '1' else
                      '1' when curr_state /= GENERATING_START and control_over_bus = '1' and start_condition_i = '1' else
                      '1' when curr_state = ERR else
                      '0';

  next_err_arbitration <= '0' when start_condition_i = '1' else
                          '1' when curr_err_arbitration = '1' else
                          '1' when unexpected_sda_data_i = '1' and curr_state = TRANSMITTING else
                          '1' when unexpected_sda_address_i = '1' and curr_state = GENERATING_ADDRESS else
                          '1' when condition_early_i = '1' and curr_state = GENERATED_START else
                          '0';

  next_err_noack_data <= '0' when start_condition_i = '1' else
                         '1' when curr_err_noack_data = '1' else
                         '1' when noack_data_i = '1' and curr_state = TRANSMITTING and expect_ack_i = '1' else
                         '0';

  next_err_noack_address <= '0' when start_condition_i = '1' else
                            '1' when curr_err_noack_address = '1' else
                            '1' when noack_address_i = '1' and curr_state = GENERATING_ADDRESS and expect_ack_i = '1' else
                            '0';

  set_next_state: process (all) is
  begin  -- process set_next_state
    next_state <= curr_state;

    if curr_state = IDLE then
      if start_condition_i = '1' then
        next_state <= BUS_BUSY;
      end if;
    elsif curr_state = GENERATING_START then
      if start_condition_i = '1' then
        next_state <= GENERATED_START;
      elsif req_cond_done_i = '1' then
        -- done before cond generated?
        -- That's not right...
        next_state <= ERR;
      end if;
    elsif curr_state = GENERATED_START then
      if any_terminal_err = '1' then
        next_state <= GENERATING_STOP;
      elsif req_cond_done_i = '1' then
        next_state <= GENERATING_ADDRESS;
      elsif condition_early_i = '1' then
        next_state <= BUS_BUSY;
      end if;
    elsif curr_state = GENERATING_ADDRESS then
      if any_terminal_err = '1' then
        next_state <= GENERATING_STOP;
      elsif address_gen_done_i = '1' then
        if any_err = '1' then
          next_state <= GENERATING_STOP;
        elsif rw_i = '0' then
          next_state <= TRANSMITTING;
        else
          next_state <= RECEIVING;
        end if;
      end if;
    elsif curr_state = TRANSMITTING then
      if any_terminal_err = '1' then
        next_state <= GENERATING_STOP;
      end if;
    elsif curr_state = RECEIVING then
      if any_terminal_err = '1' then
        next_state <= GENERATING_STOP;
      end if;
    elsif curr_state = GENERATING_STOP then
      if stop_condition_i = '1' then
        next_state <= GENERATED_STOP;
      elsif req_cond_done_i = '1' then
        -- done before cond generated?
        -- That's not right...
        next_state <= ERR;
      end if;
    elsif curr_state = GENERATED_STOP then
      if req_cond_done_i = '1' then
        next_state <= IDLE;
      elsif condition_early_i = '1' then
        next_state <= IDLE;
      end if;
    elsif curr_state = ERR then
      -- not much to do
      -- This state should not be possible
      -- if the components in i2c master are
      -- behaving correctly
    elsif curr_state = BUS_BUSY then
      if stop_condition_i = '1' then
        next_state <= IDLE;
      end if;
    end if;

    if can_gen_cond = '1' then
      if curr_gen_start_req = '1' and curr_state /= GENERATING_START and curr_state /= GENERATED_START then
        next_state <= GENERATING_START;
       
        -- if no control over bus, do not
        -- allow generating stop condition
        -- TODO consider not checking that?
      elsif curr_gen_stop_req = '1' and control_over_bus = '1' and curr_state /= GENERATING_STOP and curr_state /= GENERATED_STOP then
        next_state <= GENERATING_STOP;
      end if;
    end if;

    if curr_err_arbitration = '1' and (curr_state = GENERATING_ADDRESS or curr_state = TRANSMITTING) then
      next_state <= BUS_BUSY;
    end if;
  end process set_next_state;

  set_regs: process (clk_i) is
  begin  -- process set_regs
    if rising_edge(clk_i) then          -- rising clock edge
      if rst_in = '0' then              -- synchronous reset (active low)
        curr_state <= IDLE;
        curr_err_arbitration <= '0';
        curr_err_noack_data <= '0';
        curr_err_noack_address <= '0';
        curr_err_general <= '0';
        curr_gen_start_req <= '0';
        curr_gen_stop_req <= '0';
      else
        curr_state <= next_state;
        curr_err_arbitration <= next_err_arbitration;
        curr_err_noack_data <= next_err_noack_data;
        curr_err_noack_address <= next_err_noack_address;
        curr_err_general <= next_err_general;
        curr_gen_start_req <= next_gen_start_req;
        curr_gen_stop_req <= next_gen_stop_req;
      end if;
    end if;
  end process set_regs;

end architecture a1;
