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

library utils;
library i2c;

entity regs is
  generic (
    DELAY : integer := 15);

  port (
    clk_i  : in std_logic;
    rst_i : in std_logic;
    rst_on : out std_logic;
    err_noack_o : out std_logic;
    bus_busy_o : out std_logic;
    dev_busy_o : out std_logic;
    sda_io : inout std_logic;
    scl_io : inout std_logic);

end entity regs;

architecture a1 of regs is
  constant ADDRESS : std_logic_vector(6 downto 0) := "1110101";

  constant REGS_COUNT : integer := 20;
  type regs_t is array (0 to REGS_COUNT - 1) of std_logic_vector(7 downto 0);
  signal curr_regs : regs_t;
  signal next_regs : regs_t;

  signal rst_n : std_logic;
  signal rst_sync : std_logic;

  signal sda, scl : std_logic;
  signal sda_enable, scl_enable : std_logic;

  signal tx_valid, tx_ready, tx_clear_buffer : std_logic;
  signal tx_data : std_logic_vector(7 downto 0);

  signal rx_valid, rx_confirm : std_logic;
  signal rx_data : std_logic_vector(7 downto 0);

  signal curr_reg_address_filled : std_logic;
  signal next_reg_address_filled : std_logic;

  signal curr_reg_address : unsigned(7 downto 0);
  signal next_reg_address : unsigned(7 downto 0);

  signal dev_busy : std_logic;

  signal curr_dev_busy : std_logic;
  signal next_dev_busy : std_logic;

  signal rw : std_logic;
begin
  rst_n <= not rst_sync;
  rst_on <= rst_n;

  sync_reset: entity utils.metastability_filter
    port map (
      clk_i    => clk_i,
      signal_i => rst_i,
      signal_o => rst_sync);

  dev_busy_o <= dev_busy;

  next_dev_busy <= dev_busy;

  next_reg_address_filled <= '0' when curr_dev_busy = '0' and dev_busy = '1' and rw = '0' else
                             '1' when curr_reg_address_filled = '0' and rx_valid = '1' else
                             curr_reg_address_filled;

  rx_confirm <= rx_valid;

  next_reg_address <= unsigned(rx_data) when rx_valid = '1' and curr_reg_address_filled = '0' else
                      (curr_reg_address + 1) mod REGS_COUNT when rx_valid = '1' else
                      (curr_reg_address + 1) mod REGS_COUNT when tx_valid = '1' and dev_busy = '1' and rw = '1' else
                      curr_reg_address;

  tx_clear_buffer <= not curr_reg_address_filled;
  tx_data <= curr_regs(to_integer(curr_reg_address));
  tx_valid <= '1' when tx_ready = '1' and dev_busy = '1' and rw = '1' else
              '0';

  set_next_regs: process (all) is
  begin  -- process set_next_regs
    next_regs <= curr_regs;

    if rx_valid = '1' and curr_reg_address_filled = '1' then
      next_regs(to_integer(curr_reg_address)) <= rx_data;
    end if;
  end process set_next_regs;

  i2c_slave: entity i2c.slave
    generic map (
      SCL_FALLING_DELAY => DELAY)
    port map (
      clk_i          => clk_i,
      rst_in         => rst_n,
      address_i      => ADDRESS,
      generate_ack_i => '1',
      expect_ack_i   => '1',

      rx_valid_o     => rx_valid,
      rx_data_o      => rx_data,
      rx_confirm_i   => rx_confirm,
      rx_stretch_i   => '0',

      tx_ready_o     => tx_ready,
      tx_valid_i     => tx_valid,
      tx_data_i      => tx_data,
      tx_stretch_i   => '0',
      tx_clear_buffer_i => tx_clear_buffer,

      err_noack_o    => err_noack_o,
      rw_o           => rw,
      dev_busy_o     => dev_busy,
      bus_busy_o     => bus_busy_o,
      sda_i          => sda,
      scl_i          => scl,
      sda_enable_o   => sda_enable,
      scl_enable_o   => scl_enable);

  sda_open_buffer: entity utils.open_drain_buffer
    port map (
      pad_io   => sda_io,
      enable_i => sda_enable,
      state_o  => sda);
  scl_open_buffer: entity utils.open_drain_buffer
    port map (
      pad_io   => scl_io,
      enable_i => scl_enable,
      state_o  => scl);

  set_regs: process (clk_i) is
  begin  -- process set_regs
    if rising_edge(clk_i) then          -- rising clock edge
      if rst_n = '0' then              -- synchronous reset (active low)
        curr_reg_address_filled <= '0';
        curr_dev_busy <= '0';
        curr_reg_address <= "00000000";
        curr_regs <= (others => (others => '0'));
      else
        curr_reg_address_filled <= next_reg_address_filled;
        curr_dev_busy <= next_dev_busy;
        curr_reg_address <= next_reg_address;
        curr_regs <= next_regs;
      end if;
    end if;
  end process set_regs;

end architecture a1;
