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

library utils;

entity startstop_condition_generator is

  generic (
    DELAY : integer range 0 to 31);

  port (
    clk_i                 : in  std_logic;
    rst_in                : in  std_logic;

    sda_i                 : in  std_logic;
    scl_rising_i          : in  std_logic;
    scl_falling_i         : in  std_logic;
    scl_falling_delayed_i : in  std_logic;
    sda_enable_o          : out std_logic;

    start_condition_i     : in  std_logic;
    stop_condition_i      : in  std_logic;

    gen_start_i           : in  std_logic;
    gen_stop_i            : in  std_logic;

    req_scl_fall_o        : out std_logic;
    req_scl_rise_o        : out std_logic;

    early_condition_o     : out std_logic;

    done_o                : out std_logic);

end entity startstop_condition_generator;

architecture a1 of startstop_condition_generator is
  -- 1. prepare sda to NOT condition level,
  -- 2. request to rise the scl line,
  -- 3. generate the condition - set sda to condition level,
  -- 4. request to fall the scl line (this is to allow for disengaging sda_enable
  -- and take it from another entity instead, without communicating the current
  -- level.),
  -- 5. done!
  type state_t is (IDLE, PREREQ_SCL_FALL, PREPARE_SDA, REQ_SCL_RISE, GEN_COND, REQ_SCL_FALL, EARLY_COND, DONE);
  signal curr_state : state_t := IDLE;
  signal next_state : state_t;

  signal curr_count : integer range 0 to DELAY;
  signal next_count : integer range 0 to DELAY;

  signal curr_scl : std_logic;
  signal next_scl : std_logic;

  signal curr_count_en : std_logic;
  signal next_count_en : std_logic;

  signal request_sda : std_logic;
  signal any_request : std_logic;
begin  -- architecture a1
  any_request <= gen_start_i or gen_stop_i;
  request_sda <= '0' when gen_start_i = '1' else
                 '1' when gen_stop_i = '1' else
                 'X';

  done_o <= '1' when curr_state = DONE else '0';
  early_condition_o <=  '1' when curr_state = EARLY_COND else '0';

  req_scl_rise_o <= '1' when curr_state = REQ_SCL_RISE else '0';
  req_scl_fall_o <= '1' when curr_state = REQ_SCL_FALL or curr_state = PREREQ_SCL_FALL else '0';

  sda_enable_o <= request_sda when curr_state = PREPARE_SDA or curr_state = REQ_SCL_RISE else
                  not request_sda when curr_state = GEN_COND or curr_state = REQ_SCL_FALL else
                  '0';

  next_scl <= '1' when scl_rising_i = '1' else
              '0' when scl_falling_delayed_i = '1' else
              curr_scl;

  next_count <= 0 when curr_state /= next_state else
                curr_count + 1 when curr_count < DELAY and curr_count_en = '1' else
                curr_count;

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

    if curr_state = IDLE then
      next_count_en <= '0';

      if any_request = '1' then
        if curr_scl = '1' and sda_i /= not request_sda then
          next_state <= PREREQ_SCL_FALL;
        elsif curr_scl = '1' then
          next_state <= GEN_COND;
        elsif sda_i = not request_sda then
          next_state <= REQ_SCL_RISE;
        else
          next_state <= PREPARE_SDA;
        end if;
      end if;
    elsif curr_state = PREREQ_SCL_FALL then
      if scl_falling_i = '1' then
        next_count_en <= '1';
      elsif curr_count = DELAY then
        next_state <= PREPARE_SDA;
        next_count_en <= '0';
      elsif curr_scl = '1' then
        next_count_en <= '0';
      end if;
    elsif curr_state = PREPARE_SDA then
      next_count_en <= '1';

      if curr_scl = '1' then
        -- cannot do anything :(
      elsif curr_count = DELAY then
        next_state <= REQ_SCL_RISE;
        next_count_en <= '0';
      elsif curr_scl = '1' then
        next_state <= GEN_COND;
        next_count_en <= '0';
      end if;
    elsif curr_state = REQ_SCL_RISE then
      if scl_rising_i = '1' then
        next_count_en <= '1';
      elsif curr_count = DELAY then
        next_state <= GEN_COND;
        next_count_en <= '0';
      elsif curr_scl = '1' then
        next_count_en <= '1';
      end if;
    elsif curr_state = GEN_COND then
      -- assume correct condition here. If it's the wrong one,
      -- state entity should take care of that. (abort)
      if start_condition_i = '1' or stop_condition_i = '1' then
        next_count_en <= '1';
      elsif curr_count = DELAY then
        if gen_start_i = '1' then
          next_state <= REQ_SCL_FALL;
        else
          next_state <= DONE;
        end if;
        next_count_en <= '0';
      end if;
    elsif curr_state = REQ_SCL_FALL then
      if scl_falling_i = '1' then
        next_count_en <= '1';
      elsif curr_count = DELAY then
        next_state <= DONE;
        next_count_en <= '0';
      elsif curr_scl = '0' then
        next_count_en <= '1';
      end if;
    elsif curr_state = DONE then
      next_count_en <= '0';
      if any_request = '0' then
        next_state <= IDLE;
      end if;
    elsif curr_state = EARLY_COND then
      next_count_en <= '0';
      if any_request = '0' then
        next_state <= IDLE;
      end if;
    end if;

    if start_condition_i = '1' or stop_condition_i = '1' then
      if curr_state = PREREQ_SCL_FALL or curr_state = PREPARE_SDA or curr_state = REQ_SCL_RISE then
        next_state <= EARLY_COND;
      end if;
    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_count <= 0;
        curr_count_en <= '0';
        curr_scl <= '1';
      else
        curr_scl <= next_scl;
        curr_state <= next_state;
        curr_count <= next_count;
        curr_count_en <= next_count_en;
      end if;
    end if;
  end process set_regs;

end architecture a1;
