-------------------------------------------------------------------------------
-- Filename:        i2c_master_byte_ctrl.vhd
--
-- Description:     Byte controller section
--                  
-- VHDL-Standard:   VHDL'93
-------------------------------------------------------------------------------

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;

entity I2C_MASTER_BYTE_CTRL is
	generic (
	  ClockDiv: integer);
	port (
		Clk    : in std_logic;
		Reset  : in std_logic;	-- async reset (FPGA compatible)

		-- input signals
		En      : in std_logic; -- core enable signal
		Start   : in std_logic;
		Stop    : in std_logic;
		Read    : in std_logic;
		Write   : in std_logic;
		AckIn   : in std_logic;
		DIn     : in std_logic_vector(7 downto 0);

		-- output signals
		CmdAck  : out std_logic;
		AckOut  : out std_logic;
		i2cBusy : out std_logic;
		i2cAl   : out std_logic;
		DOut    : out std_logic_vector(7 downto 0);

		-- i2c lines
		SCLi    : in  std_logic; -- i2c clock line input
		SCLo    : out std_logic; -- i2c clock line output
		nSCLoe  : out std_logic; -- i2c clock line output enable, active low
		SDAi    : in  std_logic; -- i2c data line input
		SDAo    : out std_logic; -- i2c data line output
		nSDAoe  : out std_logic  -- i2c data line output enable, active low
	);
end entity I2C_MASTER_BYTE_CTRL;

architecture rtl of I2C_MASTER_BYTE_CTRL is
	component I2C_MASTER_BIT_CTRL is
	  generic (
	    ClockDiv: integer);
	  port (
		  Clk     : in  std_logic;
		  Reset   : in  std_logic;
		  En      : in  std_logic;	 -- core enable signal

		  Cmd     : in  std_logic_vector(3 downto 0);
		  CmdAck  : out std_logic; -- command done
		  Busy    : out std_logic; -- i2c bus busy
		  Al      : out std_logic; -- arbitration lost

		  DIn     : in  std_logic;
		  DOut    : out std_logic;

		  -- i2c lines
	    SCLi    : in  std_logic; -- i2c clock line input
	    SCLo    : out std_logic; -- i2c clock line output
	    nSCLoe  : out std_logic; -- i2c clock line output enable, active low
	    SDAi    : in  std_logic; -- i2c data line input
	    SDAo    : out std_logic; -- i2c data line output
	    nSDAoe  : out std_logic  -- i2c data line output enable, active low
	  );
	end component I2C_MASTER_BIT_CTRL;

	-- commands for bit_controller block
	constant I2C_CMD_NOP  	: std_logic_vector(3 downto 0) := "0000";
	constant I2C_CMD_START	: std_logic_vector(3 downto 0) := "0001";
	constant I2C_CMD_STOP	 : std_logic_vector(3 downto 0) := "0010";
	constant I2C_CMD_READ	 : std_logic_vector(3 downto 0) := "0100";
	constant I2C_CMD_WRITE	: std_logic_vector(3 downto 0) := "1000";

	-- signals for bit_controller
	signal CoreCmd : std_logic_vector(3 downto 0);
	signal CoreAck, CoreTxD, CoreRxD : std_logic;
	signal Al : std_logic;

	-- signals for shift register
	signal Sr : std_logic_vector(7 downto 0); -- 8-bit shift register
	signal Shift, Ld : std_logic;

	-- signals for state machine
	signal Go, HostAck : std_logic;
	signal DCount : unsigned(2 downto 0); -- data counter
	signal CountDone : std_logic;

begin
	-- hookup bit_controller
	bit_ctrl: I2C_MASTER_BIT_CTRL
	  generic map (
	    ClockDiv => ClockDiv)
	  port map(
		  Clk     => clk,
		  Reset   => Reset,
		  En      => En,
		  Cmd     => CoreCmd,
		  CmdAck  => CoreAck,
		  Busy    => i2cBusy,
		  Al      => Al,
		  DIn     => CoreTxD,
		  DOut    => CoreRxD,
		  SCLi    => SCLi,
		  SCLo    => SCLo,
		  nSCLoe  => nSCLoe,
		  SDAi    => SDAi,
		  SDAo    => SDAo,
		  nSDAoe  => nSDAoe);

	i2cAl <= Al;

	-- generate host-command-acknowledge
	CmdAck <= HostAck;

	-- generate go-signal
	Go <= (Read or Write or Stop) and not HostAck;

	-- assign Dout output to shift-register
	DOut <= Sr;

	-- generate shift register
	shift_register: process(Clk, Reset)
	begin
	    if Reset = '1' then
	      sr <= (others => '0');
	    elsif clk'event and clk = '1' then
	      if Ld = '1' then
	        Sr <= DIn;
	      elsif Shift = '1' then
	        Sr <= Sr(6 downto 0) & CoreRxD;
	      end if;
	    end if;
	end process shift_register;

	-- generate data-counter
	data_cnt: process(Clk, Reset)
	begin
	    if Reset = '1' then
	      DCount <= (others => '0');
	    elsif (clk'event and clk = '1') then
	      if ld = '1' then
	        DCount <= "111";  -- load counter with 7
	      elsif Shift = '1' then
	        DCount <= DCount - 1;
	      end if;
	    end if;
	end process data_cnt;

	CountDone <= '1' when DCount = 0 else '0';

	--
	-- state machine
	--
	statemachine : block
	    type states is (st_idle, st_start, st_read, st_write, st_ack, st_stop);
	    signal c_state : states;
	begin
	    --
	    -- command interpreter, translate complex commands into simpler I2C commands
	    --
	    nxt_state_decoder: process(Clk, Reset)
	    begin
	        if Reset = '1' then
	          CoreCmd  <= I2C_CMD_NOP;
	          CoreTxD  <= '0';
	          Shift    <= '0';
	          Ld       <= '0';
	          HostAck  <= '0';
	          c_state  <= st_idle;
	          AckOut   <= '0';
	        elsif Clk'event and Clk = '1' then
	          if Al = '1' then
	            CoreCmd  <= I2C_CMD_NOP;
	            CoreTxD  <= '0';
	            Shift    <= '0';
	            ld       <= '0';
	            HostAck  <= '0';
	            c_state  <= st_idle;
	            AckOut   <= '0';
	          else
	            -- initialy reset all signal
	            CoreTxd  <= Sr(7);
	            Shift    <= '0';
	            Ld       <= '0';
	            HostAck  <= '0';

	            case c_state is
	              when st_idle =>
	                 if Go = '1' then
	                   if Start = '1' then
	                     c_state <= st_start;
	                     CoreCmd <= I2C_CMD_START;
	                   elsif Read = '1' then
	                     c_state <= st_read;
	                     CoreCmd <= I2C_CMD_READ;
	                   elsif Write = '1' then
	                     c_state <= st_write;
	                     CoreCmd <= I2C_CMD_WRITE;
	                   else -- stop
	                     c_state <= st_stop;
	                     CoreCmd <= I2C_CMD_STOP;
	                   end if;

	                   Ld <= '1';
	                 end if;

	              when st_start =>
	                 if CoreAck = '1' then
	                   if Read = '1' then
	                     c_state <= st_read;
	                     CoreCmd <= I2C_CMD_READ;
	                   else
	                     c_state <= st_write;
	                     CoreCmd <= I2C_CMD_WRITE;
	                   end if;

	                   Ld <= '1';
	                 end if;

	              when st_write =>
	                 if CoreAck = '1' then
	                   if CountDone = '1' then
	                     c_state <= st_ack;
	                     CoreCmd <= I2C_CMD_READ;
	                   else
	                     c_state <= st_write;       -- stay in same state
	                     CoreCmd <= I2C_CMD_WRITE;  -- write next bit
	                     Shift   <= '1';
	                   end if;
	                 end if;

	              when st_read =>
	                 if CoreAck = '1' then
	                   if CountDone = '1' then
	                     c_state <= st_ack;
	                     CoreCmd <= I2C_CMD_WRITE;
	                   else
	                     c_state <= st_read;      -- stay in same state
	                     CoreCmd <= I2C_CMD_READ; -- read next bit
	                   end if;

	                   Shift     <= '1';
	                   CoreTxD   <= AckIn;
	                 end if;

	              when st_ack =>
	                 if CoreAck = '1' then
	                   -- check for stop; Should a STOP command be generated ?
	                   if Stop = '1' then
	                     c_state <= st_stop;
	                     CoreCmd <= I2C_CMD_STOP;
	                   else
	                     c_state <= st_idle;
	                     CoreCmd <= I2C_CMD_NOP;

	                     -- generate command acknowledge signal
	                     HostAck <= '1';
	                   end if;

	                   -- assign ack_out output to core_rxd (contains last received bit)
	                   AckOut  <= CoreRxD;

	                   CoreTxD <= '1';
	                 else
	                   CoreTxD <= AckIn;
	                 end if;

	              when st_stop =>
	                 if CoreAck = '1' then
	                   c_state <= st_idle;
	                   CoreCmd <= I2C_CMD_NOP;

	                   -- generate command acknowledge signal
	                   HostAck <= '1';
	                 end if;

	              when others => -- illegal states
	                 c_state <= st_idle;
	                 CoreCmd <= I2C_CMD_NOP;
	                 report ("Byte controller entered illegal state.");

	            end case;

	          end if;
	        end if;
	    end process nxt_state_decoder;

	end block statemachine;

end architecture rtl;

