---------------------------------------------------------------------
----                                                             ----
----  WISHBONE revB2 compl. I2C Master Core; byte-controller     ----
----                                                             ----
----                                                             ----
----  Author: Richard Herveille                                  ----
----          richard@asics.ws                                   ----
----          www.asics.ws                                       ----
----                                                             ----
----  Downloaded from: http://www.opencores.org/projects/i2c/    ----
----                                                             ----
---------------------------------------------------------------------
----                                                             ----
---- Copyright (C) 2000 Richard Herveille                        ----
----                    richard@asics.ws                         ----
----                                                             ----
---- This source file may be used and distributed without        ----
---- restriction provided that this copyright statement is not   ----
---- removed from the file and that any derivative work contains ----
---- the original copyright notice and the associated disclaimer.----
----                                                             ----
----     THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY     ----
---- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED   ----
---- TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS   ----
---- FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL THE AUTHOR      ----
---- OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,         ----
---- INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES    ----
---- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE   ----
---- GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR        ----
---- BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF  ----
---- LIABILITY, WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT  ----
---- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT  ----
---- OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE         ----
---- POSSIBILITY OF SUCH DAMAGE.                                 ----
----                                                             ----
---------------------------------------------------------------------

--  CVS Log
--
--  $Id: i2c_master_byte_ctrl.vhd,v 1.5 2004/02/18 11:41:48 rherveille Exp $
--
--  $Date: 2004/02/18 11:41:48 $
--  $Revision: 1.5 $
--  $Author: rherveille $
--  $Locker:  $
--  $State: Exp $
--
-- Change History:
--               $Log: i2c_master_byte_ctrl.vhd,v $
--               Revision 1.5  2004/02/18 11:41:48  rherveille
--               Fixed a potential bug in the statemachine. During a 'stop' 2 cmd_ack signals were generated. Possibly canceling a new start command.
--
--               Revision 1.4  2003/08/09 07:01:13  rherveille
--               Fixed a bug in the Arbitration Lost generation caused by delay on the (external) sda line.
--               Fixed a potential bug in the byte controller's host-acknowledge generation.
--
--               Revision 1.3  2002/12/26 16:05:47  rherveille
--               Core is now a Multimaster I2C controller.
--
--               Revision 1.2  2002/11/30 22:24:37  rherveille
--               Cleaned up code
--
--               Revision 1.1  2001/11/05 12:02:33  rherveille
--               Split i2c_master_core.vhd into separate files for each entity; same layout as verilog version.
--               Code updated, is now up-to-date to doc. rev.0.4.
--               Added headers.
--

-- Modified by Jan Andersson (jan@gaisler.com). Changed std_logic_arith to numeric_std.


--
------------------------------------------
-- Byte controller section
------------------------------------------
--
library ieee;
use ieee.std_logic_1164.all;
library grlib;
use grlib.stdlib.all;

entity i2c_master_byte_ctrl is
	port (
		clk    : in std_logic;
		rst    : in std_logic; -- synchronous active high reset (WISHBONE compatible)
		nReset : in std_logic;	-- asynchornous active low reset (FPGA compatible)
		ena    : in std_logic; -- core enable signal

		clk_cnt : in std_logic_vector(15 downto 0);	-- 4x SCL

		-- input signals
		start,
		stop,
		read,
		write,
		ack_in : std_logic;
		din    : in std_logic_vector(7 downto 0);

		-- output signals
		cmd_ack  : out std_logic; -- command done
		ack_out  : out std_logic;
		i2c_busy : out std_logic; -- arbitration lost
		i2c_al   : out std_logic; -- i2c bus busy
		dout     : out std_logic_vector(7 downto 0);

		-- i2c lines
		scl_i   : in std_logic;  -- i2c clock line input
		scl_o   : out std_logic; -- i2c clock line output
		scl_oen : out std_logic; -- i2c clock line output enable, active low
		sda_i   : in std_logic;  -- i2c data line input
		sda_o   : out std_logic; -- i2c data line output
		sda_oen : out std_logic  -- i2c data line output enable, active low
	);
end entity i2c_master_byte_ctrl;

architecture structural of i2c_master_byte_ctrl is
	component i2c_master_bit_ctrl is
	port (
		clk    : in std_logic;
		rst    : in std_logic;
		nReset : in std_logic;
		ena    : in std_logic;				-- core enable signal

		clk_cnt : in std_logic_vector(15 downto 0);		-- clock prescale value

		cmd     : in std_logic_vector(3 downto 0);
		cmd_ack : 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
		scl_i   : in std_logic;  -- i2c clock line input
		scl_o   : out std_logic; -- i2c clock line output
		scl_oen : out std_logic; -- i2c clock line output enable, active low
		sda_i   : in std_logic;  -- i2c data line input
		sda_o   : out std_logic; -- i2c data line output
		sda_oen : 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 core_cmd : std_logic_vector(3 downto 0);
	signal core_ack, core_txd, core_rxd : std_logic;
	signal al : std_logic;

	-- signals for shift register
	signal sr : std_logic_vector(7 downto 0); -- 8bit shift register
	signal shift, ld : std_logic;

	-- signals for state machine
	signal go, host_ack : std_logic;
        -- Added init value to dcnt to prevent simulation meta-value
        -- - jan@gaisler.com
        signal dcnt : std_logic_vector(2 downto 0) := (others => '0'); -- data counter
	signal cnt_done : std_logic;

begin
	-- hookup bit_controller
	bit_ctrl: i2c_master_bit_ctrl port map(
		clk     => clk,
		rst     => rst,
		nReset  => nReset,
		ena     => ena,
		clk_cnt => clk_cnt,
		cmd     => core_cmd,
		cmd_ack => core_ack,
		busy    => i2c_busy,
		al      => al,
		din     => core_txd,
		dout    => core_rxd,
		scl_i   => scl_i,
		scl_o   => scl_o,
		scl_oen => scl_oen,
		sda_i   => sda_i,
		sda_o   => sda_o,
		sda_oen => sda_oen
	);
	i2c_al <= al;

	-- generate host-command-acknowledge
	cmd_ack <= host_ack;

	-- generate go-signal
	go <= (read or write or stop) and not host_ack;

	-- assign Dout output to shift-register
	dout <= sr;

	-- generate shift register
	shift_register: process(clk, nReset)
	begin
	    if (nReset = '0') then
	      sr <= (others => '0');
	    elsif (clk'event and clk = '1') then
	      if (rst = '1') then
	        sr <= (others => '0');
	      elsif (ld = '1') then
	        sr <= din;
	      elsif (shift = '1') then
	        sr <= (sr(6 downto 0) & core_rxd);
	      end if;
	    end if;
	end process shift_register;

	-- generate data-counter
	data_cnt: process(clk, nReset)
	begin
	    if (nReset = '0') then
	      dcnt <= (others => '0');
	    elsif (clk'event and clk = '1') then
	      if (rst = '1') then
	        dcnt <= (others => '0');
	      elsif (ld = '1') then
	        dcnt <= (others => '1');  -- load counter with 7
	      elsif (shift = '1') then
	        dcnt <= dcnt -1;
	      end if;
	    end if;
	end process data_cnt;

	cnt_done <= '1' when (dcnt = "000") 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, nReset)
	    begin
	        if (nReset = '0') then
	          core_cmd <= I2C_CMD_NOP;
	          core_txd <= '0';
	          shift    <= '0';
	          ld       <= '0';
	          host_ack <= '0';
	          c_state  <= st_idle;
	          ack_out  <= '0';
	        elsif (clk'event and clk = '1') then
	          if (rst = '1' or al = '1') then
	            core_cmd <= I2C_CMD_NOP;
	            core_txd <= '0';
	            shift    <= '0';
	            ld       <= '0';
	            host_ack <= '0';
	            c_state  <= st_idle;
	            ack_out  <= '0';
	          else
	            -- initialy reset all signal
	            core_txd <= sr(7);
	            shift    <= '0';
	            ld       <= '0';
	            host_ack <= '0';

	            case c_state is
	              when st_idle =>
	                 if (go = '1') then
	                   if (start = '1') then
	                     c_state  <= st_start;
	                     core_cmd <= I2C_CMD_START;
	                   elsif (read = '1') then
	                     c_state  <= st_read;
	                     core_cmd <= I2C_CMD_READ;
	                   elsif (write = '1') then
	                     c_state  <= st_write;
	                     core_cmd <= I2C_CMD_WRITE;
	                   else -- stop
	                     c_state  <= st_stop;
	                     core_cmd <= I2C_CMD_STOP;
	                   end if;

	                   ld <= '1';
	                 end if;

	              when st_start =>
	                 if (core_ack = '1') then
	                   if (read = '1') then
	                     c_state  <= st_read;
	                     core_cmd <= I2C_CMD_READ;
	                   else
	                     c_state  <= st_write;
	                     core_cmd <= I2C_CMD_WRITE;
	                   end if;

	                   ld <= '1';
	                 end if;

	              when st_write =>
	                 if (core_ack = '1') then
	                   if (cnt_done = '1') then
	                     c_state  <= st_ack;
	                     core_cmd <= I2C_CMD_READ;
	                   else
	                     c_state  <= st_write;       -- stay in same state
	                     core_cmd <= I2C_CMD_WRITE;  -- write next bit
	                     shift    <= '1';
	                   end if;
	                 end if;

	              when st_read =>
	                 if (core_ack = '1') then
	                   if (cnt_done = '1') then
	                     c_state  <= st_ack;
	                     core_cmd <= I2C_CMD_WRITE;
	                   else
	                     c_state  <= st_read;      -- stay in same state
	                     core_cmd <= I2C_CMD_READ; -- read next bit
	                   end if;

	                   shift    <= '1';
	                   core_txd <= ack_in;
	                 end if;

	              when st_ack =>
	                 if (core_ack = '1') then
	                   -- check for stop; Should a STOP command be generated ?
	                   if (stop = '1') then
	                     c_state  <= st_stop;
	                     core_cmd <= I2C_CMD_STOP;
	                   else
	                     c_state  <= st_idle;
	                     core_cmd <= I2C_CMD_NOP;

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

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

	                   core_txd <= '1';
	                 else
	                   core_txd <= ack_in;
	                 end if;

	              when st_stop =>
	                 if (core_ack = '1') then
	                   c_state  <= st_idle;
	                   core_cmd <= I2C_CMD_NOP;

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

	              when others => -- illegal states
	                 c_state  <= st_idle;
	                 core_cmd <= 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 structural;

