-------------------------------------------------------------------------------
-- Filename:        i2c_master_bit_ctrl.vhd
--
-- Description:     Bit controller section
--                  
-- VHDL-Standard:   VHDL'93
-------------------------------------------------------------------------------
--
-- Translate simple commands into SCL/SDA transitions
-- Each command has 5 states, A/B/C/D/idle
--
-- start:    SCL  ~~~~~~~~~~~~~~\____
--	     SDA  XX/~~~~~~~\______
--	          x | A | B | C | D | i
--
-- repstart  SCL  ______/~~~~~~~\___
--	     SDA  __/~~~~~~~\______
--	          x | A | B | C | D | i
--
-- stop      SCL  _______/~~~~~~~~~~~
--	     SDA  ==\___________/~~~~~
--	          x | A | B | C | D | i
--
--- write    SCL  ______/~~~~~~~\____
--	     SDA  XXX===============XX
--	          x | A | B | C | D | i
--
--- read     SCL  ______/~~~~~~~\____
--	     SDA  XXXXXXX=XXXXXXXXXXX
--	          x | A | B | C | D | i
--

-- Timing:      Normal mode     Fast mode
-----------------------------------------------------------------
-- Fscl         100KHz          400KHz
-- Th_scl       4.0us           0.6us   High period of SCL
-- Tl_scl       4.7us           1.3us   Low period of SCL
-- Tsu:sta      4.7us           0.6us   setup time for a repeated start condition
-- Tsu:sto      4.0us           0.6us   setup time for a stop conditon
-- Tbuf         4.7us           1.3us   Bus free time between a stop and start condition
--

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

entity 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 completed
		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 entity I2C_MASTER_BIT_CTRL;

architecture structural of i2c_master_bit_ctrl is
	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";

	type states is (idle, start_a, start_b, start_c, start_d, start_e,
	                stop_a, stop_b, stop_c, stop_d, rd_a, rd_b, rd_c, rd_d, wr_a, wr_b, wr_c, wr_d);
	signal c_state : states;

	signal inSCLoe, inSDAoe   : std_logic;          -- internal I2C lines
	signal SDAChk             : std_logic;          -- check SDA status (multi-master arbitration)
	signal dnSCLoe            : std_logic;          -- delayed scl_oen signals
	signal sSCL, sSDA         : std_logic;          -- synchronized SCL and SDA inputs
	signal ClkEn, SlaveWait   : std_logic;          -- clock generation signals
	signal iAl                : std_logic;          -- internal arbitration lost signal
	signal Count : integer range 0 to ClockDiv;     -- clock divider counter

begin
	-- whenever the slave is not ready it can delay the cycle by pulling SCL low
	-- delay scl_oen
	process (Clk)
	begin
	    if Clk'event and Clk = '1' then
	      dnSCLoe <= inSCLoe;
	    end if;
	end process;

	SlaveWait <= dnSCLoe and not sSCL;

	-- generate clk enable signal
	gen_clken: process(Clk, Reset)
	begin
	    if Reset = '1' then
	      Count <= 0;
	      ClkEn <= '1';
	    elsif Clk'event and Clk = '1' then
	      if Count = 0 or En = '0' then
	        Count <= ClockDiv;
	        ClkEn <= '1';
	      elsif SlaveWait = '1' then
	        Count <= Count;
	        ClkEn <= '0';
	      else
	        Count <= Count - 1;
	        ClkEn <= '0';
	      end if;
	    end if;
	end process gen_clken;


	-- generate bus status controller
	bus_status_ctrl: block
	  signal dSCL, dSDA   : std_logic;  -- delayes sSCL and sSDA
	  signal StaCondition : std_logic;  -- start detected
	  signal StoCondition : std_logic;  -- stop detected
	  signal CmdStop      : std_logic;  -- STOP command
	  signal iBusy        : std_logic;  -- internal busy signal
	begin
	    -- synchronize SCL and SDA inputs
	    synch_scl_sda: process(Clk, Reset)
	    begin
	        if Reset = '1' then
	          sSCL <= '1';
	          sSDA <= '1';

	          dSCL <= '1';
	          dSDA <= '1';
	        elsif Clk'event and Clk = '1' then
	          sSCL <= SCLi;
	          sSDA <= SDAi;

	          dSCL <= sSCL;
	          dSDA <= sSDA;
	        end if;
	    end process synch_SCL_SDA;

	    -- detect start condition => detect falling edge on SDA while SCL is high
	    -- detect stop condition  => detect rising edge on SDA while SCL is high
	    detect_sta_sto: process(Clk, Reset)
	    begin
	        if Reset = '1' then
	          StaCondition <= '0';
	          StoCondition <= '0';
	        elsif Clk'event and Clk = '1' then
            StaCondition <= (not sSDA and dSDA) and sSCL;
            StoCondition <= (sSDA and not dSDA) and sSCL;
	        end if;
	    end process detect_sta_sto;

	    -- generate i2c-bus busy signal
	    gen_busy: process(Clk, Reset)
	    begin
	        if Reset = '1' then
	          iBusy <= '0';
	        elsif Clk'event and Clk = '1' then
            iBusy <= (StaCondition or iBusy) and not StoCondition;
	        end if;
	    end process gen_busy;

	    Busy <= iBusy;

	    -- generate arbitration lost signal
	    -- aribitration lost when:
	    -- 1) master drives SDA high, but the i2c bus is low
	    -- 2) stop detected while not requested (detect during 'idle' state)
	    gen_al: process(Clk, Reset)
	    begin
	      if Reset = '1' then
	        CmdStop  <= '0';
	        iAl       <= '0';
	      elsif Clk'event and Clk = '1' then
	        if ClkEn = '1' then
	          if Cmd = I2C_CMD_STOP then
	            CmdStop <= '1';
	          else
	            CmdStop <= '0';
	          end if;
	        end if;

	        if c_state = idle then
	          iAl <= SDAChk and not sSDA and inSDAoe;
	        else
	          iAl <= (SDAChk and not sSDA and inSDAoe) or (StoCondition and not CmdStop);
	        end if;

	      end if;
	    end process gen_al;

	    Al <= iAl;

	    -- generate dout signal, store dout on rising edge of SCL
	    gen_dout: process(Clk)
	    begin
	      if Clk'event and Clk = '1' then
	        if sSCL = '1' and dSCL = '0' then
	          DOut <= sSDA;
	        end if;
	      end if;
	    end process gen_dout;
	end block bus_status_ctrl;


	-- generate statemachine
	nxt_state_decoder : process (Clk, Reset)
	begin
	    if Reset = '1' then
	      c_state  <= idle;
	      CmdAck  <= '0';
	      inSCLoe <= '1';
	      inSDAoe <= '1';
	      SDAChk  <= '0';
	    elsif (clk'event and clk = '1') then
	      if iAl = '1' then
	        c_state <= idle;
	        CmdAck  <= '0';
	        inSCLoe <= '1';
	        inSDAoe <= '1';
	        SDAChk  <= '0';
	      else
	        CmdAck <= '0'; -- default no acknowledge

	        if ClkEn = '1' then
	          case (c_state) is
	             -- idle
	             when idle =>
	                case cmd is
	                  when I2C_CMD_START => c_state <= start_a;
	                  when I2C_CMD_STOP  => c_state <= stop_a;
	                  when I2C_CMD_WRITE => c_state <= wr_a;
	                  when I2C_CMD_READ  => c_state <= rd_a;
	                  when others        => c_state <= idle; -- NOP command
	                end case;

	                inSCLoe <= inSCLoe; -- keep SCL in same state
	                inSDAoe <= inSDAoe; -- keep SDA in same state
	                SDAChk  <= '0';      -- don't check SDA

	             -- start
	             when start_a =>
	                c_state <= start_b;
	                inSCLoe <= inSCLoe; -- keep SCL in same state (for repeated start)
	                inSDAoe <= '1';      -- set SDA high
	                SDAChk  <= '0';      -- don't check SDA

	             when start_b =>
	                c_state <= start_c;
	                inSCLoe <= '1'; -- set SCL high
	                inSDAoe <= '1'; -- keep SDA high
	                SDAChk  <= '0'; -- don't check SDA

	             when start_c =>
	                c_state <= start_d;
	                inSCLoe <= '1'; -- keep SCL high
	                inSDAoe <= '0'; -- set SDA low
	                SDAChk  <= '0'; -- don't check SDA

	             when start_d =>
	                c_state <= start_e;
	                inSCLoe <= '1'; -- keep SCL high
	                inSDAoe <= '0'; -- keep SDA low
	                SDAChk  <= '0'; -- don't check SDA

	             when start_e =>
	                c_state <= idle;
	                CmdAck  <= '1'; -- command completed
	                inSCLoe <= '0'; -- set SCL low
	                inSDAoe <= '0'; -- keep SDA low
	                SDAChk  <= '0'; -- don't check SDA

	             -- stop
	             when stop_a =>
	                c_state <= stop_b;
	                inSCLoe <= '0'; -- keep SCL low
	                inSDAoe <= '0'; -- set SDA low
	                SDAChk  <= '0'; -- don't check SDA

	             when stop_b =>
	                c_state <= stop_c;
	                inSCLoe <= '1'; -- set SCL high
	                inSDAoe <= '0'; -- keep SDA low
	                SDAChk  <= '0'; -- don't check SDA

	             when stop_c =>
	                c_state <= stop_d;
	                inSCLoe <= '1'; -- keep SCL high
	                inSDAoe <= '0'; -- keep SDA low
	                SDAChk  <= '0'; -- don't check SDA

	             when stop_d =>
	                c_state <= idle;
	                CmdAck  <= '1'; -- command completed
	                inSCLoe <= '1'; -- keep SCL high
	                inSDAoe <= '1'; -- set SDA high
	                SDAChk  <= '0'; -- don't check SDA

	             -- read
	             when rd_a =>
	                c_state <= rd_b;
	                inSCLoe <= '0'; -- keep SCL low
	                inSDAoe <= '1'; -- tri-state SDA
	                SDAChk  <= '0'; -- don't check SDA

	             when rd_b =>
	                c_state <= rd_c;
	                inSCLoe <= '1'; -- set SCL high
	                inSDAoe <= '1'; -- tri-state SDA
	                SDAChk  <= '0'; -- don't check SDA

	             when rd_c =>
	                c_state <= rd_d;
	                inSCLoe <= '1'; -- keep SCL high
	                inSDAoe <= '1'; -- tri-state SDA
	                SDAChk  <= '0'; -- don't check SDA

	             when rd_d =>
	                c_state <= idle;
	                CmdAck  <= '1'; -- command completed
	                inSCLoe <= '0'; -- set SCL low
	                inSDAoe <= '1'; -- tri-state SDA
	                SDAChk  <= '0'; -- don't check SDA

	             -- write
	             when wr_a =>
	                c_state <= wr_b;
	                inSCLoe <= '0'; -- keep SCL low
	                inSDAoe <= din; -- set SDA
	                SDAChk  <= '0'; -- don't check SDA (SCL low)

	             when wr_b =>
	                c_state <= wr_c;
	                inSCLoe <= '1'; -- set SCL high
	                inSDAoe <= din; -- keep SDA
	                SDAChk  <= '1'; -- check SDA

	             when wr_c =>
	                c_state <= wr_d;
	                inSCLoe <= '1'; -- keep SCL high
	                inSDAoe <= din; -- keep SDA
	                SDAChk  <= '1'; -- check SDA

	             when wr_d =>
	                c_state <= idle;
	                CmdAck  <= '1'; -- command completed
	                inSCLoe <= '0'; -- set SCL low
	                inSDAoe <= din; -- keep SDA
	                SDAChk  <= '0'; -- don't check SDA (SCL low)

	             when others =>

	          end case;
	        end if;
	      end if;
	    end if;
	end process nxt_state_decoder;


	-- assign outputs
	SCLo   <= '0';
	nSCLoe <= inSCLoe;
	SDAo   <= '0';
	nSDAoe <= inSDAoe;
end architecture structural;

