--------------------------------------------------------------------------------
-- CERN (BE-CO-HT)
-- I2C master and its driver 
-- https://www.ohwr.org/projects/conv-common-gw
--------------------------------------------------------------------------------
--
-- unit name:     Top-level simulation of conv_common_gw
--
-- description:
--
--   
--
--------------------------------------------------------------------------------
-- Copyright (c) 2010-2012 CERN / BE-CO-HT
--------------------------------------------------------------------------------
-- GNU LESSER GENERAL PUBLIC LICENSE
--------------------------------------------------------------------------------
-- This source file is free software; you can redistribute it and/or modify it
-- under the terms of the GNU Lesser General Public License as published by the
-- Free Software Foundation; either version 2.1 of the License, or (at your
-- option) any later version. This source is distributed in the hope that it
-- will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
-- of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-- See the GNU Lesser General Public License for more details. You should have
-- received a copy of the GNU Lesser General Public License along with this
-- source; if not, download it from http://www.gnu.org/licenses/lgpl-2.1.html
--------------------------------------------------------------------------------

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


entity i2c_master_and_driver is
  port (
    clk_i      : in  std_logic;
    rst_n_i    : in  std_logic;
    
    -- Driver interface
    ready_o    : out std_logic;
    start_i    : in  std_logic;
    finish_o   : out std_logic;
    rdwr_i     : in  std_logic; -- 1: read, 0: write
    slv_addr_i : in  std_logic_vector(6 downto 0);
    reg_addr_i : in  std_logic_vector(31 downto 0);
    send_val_i : in  std_logic_vector(31 downto 0);
    rcvd_val_o : out std_logic_vector(31 downto 0);

    -- I2C interface
    scl_i      : in  std_logic;
    scl_o      : out std_logic;
    scl_oen    : out std_logic;
    sda_i      : in  std_logic;
    sda_o      : out std_logic;
    sda_oen    : out std_logic
  );
  
end entity i2c_master_and_driver;

architecture arch of i2c_master_and_driver is
  type t_state_i2c_mst is
    (
      IDLE,
      I2C_ADDR, I2C_ADDR_ACK,

      WB_ADDR_B0, WB_ADDR_B0_ACK,
      WB_ADDR_B1, WB_ADDR_B1_ACK,

      ST_OP,

      RD_RESTART, RD_RESTART_ACK,
      RD, RD_ACK,

      WR, WR_ACK,

      STO,

      SUCCESS,
      ERR
    );
  -- I2C signals
  signal state_i2c_mst                : t_state_i2c_mst;
  
  signal mst_sta                      : std_logic;
  signal mst_sto                      : std_logic;
  signal mst_rd                       : std_logic;
  signal mst_wr                       : std_logic;
  signal mst_ack                      : std_logic;
  signal mst_dat_in                   : std_logic_vector(7 downto 0);
  signal mst_dat_out                  : std_logic_vector(7 downto 0);
  signal mst_cmd_ack                  : std_logic;
  signal ack_fr_slv                   : std_logic;
  signal cnt                          : unsigned(2 downto 0);
  signal once                         : boolean;
  signal byte_cnt                     : unsigned(1 downto 0);
  signal rcvd                         : std_logic_vector(31 downto 0);
  signal send                         : std_logic_vector(31 downto 0);
  
  signal rst                          : std_logic;
begin
  
  rst <= not rst_n_i; 
  -- from general-cores/modules/wishbone/wb_i2c_master/
  cmp_master : entity work.i2c_master_byte_ctrl  
    port map(
      clk      => clk_i,
      rst      => rst,
      nReset   => rst_n_i,
      ena      => '1',

      clk_cnt  => x"0005", -------------x"0027",

      -- input signals
      start    => mst_sta,
      stop     => mst_sto,
      read     => mst_rd,
      write    => mst_wr,
      ack_in   => mst_ack,
      din      => mst_dat_in,

      -- output signals
      cmd_ack  => mst_cmd_ack,
      ack_out  => ack_fr_slv,
      i2c_busy => open,
      i2c_al   => open,
      dout     => mst_dat_out,

      -- i2c lines
      scl_i    => scl_i,
      scl_o    => scl_o,
      scl_oen  => scl_oen,
      sda_i    => sda_i,
      sda_o    => sda_o,
      sda_oen  => sda_oen
    );
  ------------------------------------------------------------------------------
  -- This FSM controls the signals to the master component to implement the I2C
  -- protocol defined together with ELMA. The FSM is controlled by the
  -- stimuli process below
  ------------------------------------------------------------------------------
  p_mst_fsm : process (clk_i) is
  begin
    if rising_edge(clk_i) then
      if (rst_n_i = '0') then
        state_i2c_mst      <= IDLE;
        mst_sta    <= '0';
        mst_wr     <= '0';
        mst_sto    <= '0';
        mst_rd     <= '0';
        mst_dat_in <= (others => '0');
        mst_ack    <= '0';
        cnt        <= (others => '0');
        once       <= true;
        byte_cnt   <= (others => '0');
        rcvd       <= (others => '0');
        send       <= (others => '0');
        ready_o    <= '0';
        finish_o   <= '0';
      else
        case state_i2c_mst is

          when IDLE =>
            if (start_i = '1') then
              state_i2c_mst <= I2C_ADDR;
              send  <= std_logic_vector(send_val_i);
              ready_o     <= '0';
            else
              ready_o     <= '1';
            end if;
            finish_o   <= '0';        

          when I2C_ADDR =>
            mst_sta   <= '1';
            mst_wr    <= '1';
            mst_dat_in <= slv_addr_i & '0';
            if (mst_cmd_ack = '1') then
              mst_sta <= '0';
              mst_wr  <= '0';
              state_i2c_mst   <= I2C_ADDR_ACK;
            end if;

          when I2C_ADDR_ACK =>
            cnt <= cnt + 1;
            if (cnt = 7) then
              if (ack_fr_slv = '0') then
                state_i2c_mst <= WB_ADDR_B0;
              else
                state_i2c_mst <= ERR;
              end if;
            end if;

          when WB_ADDR_B0 =>
            mst_wr <= '1';
            mst_dat_in <= reg_addr_i(15 downto 8);
            if (mst_cmd_ack = '1') then
              mst_wr <= '0';
              state_i2c_mst  <= WB_ADDR_B0_ACK;
            end if;

          when WB_ADDR_B0_ACK =>
            cnt <= cnt + 1;
            if (cnt = 7) then
              if (ack_fr_slv = '0') then
                state_i2c_mst <= WB_ADDR_B1;
              else
                state_i2c_mst <= ERR;
              end if;
            end if;

          when WB_ADDR_B1 =>
            mst_wr <= '1';
            mst_dat_in <= reg_addr_i(7 downto 0);
            if (mst_cmd_ack = '1') then
              mst_wr <= '0';
              state_i2c_mst  <= WB_ADDR_B1_ACK;
            end if;

          when WB_ADDR_B1_ACK =>
            cnt <= cnt + 1;
            if (cnt = 7) then
              if (ack_fr_slv = '0') then
                state_i2c_mst <= ST_OP;
              else
                state_i2c_mst <= ERR;
              end if;
            end if;

          when ST_OP =>
            if (rdwr_i = '1') then
              state_i2c_mst <= RD_RESTART;
            else
              state_i2c_mst <= WR;
            end if;

          when RD_RESTART =>
            mst_wr <= '1';
            mst_dat_in <= slv_addr_i & '1';
            mst_sta <= '1';
            if (mst_cmd_ack = '1') then
              mst_sta <= '0';
              mst_wr  <= '0';
              state_i2c_mst <= RD_RESTART_ACK;
            end if;

          when RD_RESTART_ACK =>
            cnt <= cnt + 1;
            if (cnt = 7) then
              if (ack_fr_slv = '0') then
                state_i2c_mst <= RD;
                rcvd <= (others => '0');
              else
                state_i2c_mst <= ERR;
              end if;
            end if;

          when RD =>
            mst_rd <= '1';
            mst_ack <= '0';
            if (byte_cnt = 3) then
              mst_ack <= '1';
            end if;
            if (mst_cmd_ack = '1') then
              mst_rd <= '0';
              byte_cnt <= byte_cnt + 1;
              rcvd     <= mst_dat_out & rcvd(31 downto 8);
              mst_ack  <= '0';
              state_i2c_mst    <= RD;
              if (byte_cnt = 3) then
                state_i2c_mst  <= STO;
              end if;
            end if;

          when RD_ACK =>
            cnt <= cnt + 1;
            if (cnt = 7) then
              byte_cnt <= byte_cnt + 1;
              rcvd     <= mst_dat_out & rcvd(31 downto 8);
              mst_ack  <= '0';
              state_i2c_mst <= RD;
              if (byte_cnt = 3) then
                state_i2c_mst  <= STO;
              end if;
            end if;

          when WR =>
            mst_wr <= '1';
            mst_dat_in <= send(7 downto 0);
            if (mst_cmd_ack = '1') then
              mst_wr <= '0';
              state_i2c_mst  <= WR_ACK;
            end if;

          when WR_ACK =>
            cnt <= cnt + 1;
            if (cnt = 7) then
              if (ack_fr_slv = '0') then
                byte_cnt <= byte_cnt + 1;
                send     <= x"00" & send(31 downto 8);
                state_i2c_mst    <= WR;
                if (byte_cnt = 3) then
                  state_i2c_mst  <= STO;
                end if;
              else
                state_i2c_mst <= ERR;
              end if;
            end if;

          when STO =>
            mst_sto <= '1';
            if (mst_cmd_ack = '1') then
              mst_sto <= '0';
              state_i2c_mst   <= IDLE;
            end if;
            finish_o   <= '1';

          when ERR =>
            if (once) then
              report("Error!");
              once <= false;
            end if;

          when others =>
            state_i2c_mst <= ERR;

        end case;
      end if;
    end if;
  end process p_mst_fsm;

 rcvd_val_o <=  rcvd;
end architecture arch;

