*

i2c_slave.vhd


--------------------------------------------------------------------------

-- FILENAME : i2c_slave.vhd
--
-- This module implements an I2C slave controller.
--
-- AUTHOR : Craig Dunn
-- DATE STARTED : 20 April 2004
-- TAB SETTING : 4
-- RESET : Async (active low)
-- CLOCK : 32MHz (not clock dependent)
-- KNOWN BUGS : None
-- VERSION : 1.0
--
-- All of the design and code in this module is my own work. No design or
-- code has been borrowed or copied from any source.
--------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.STD_LOGIC_ARITH.all;
use IEEE.STD_LOGIC_UNSIGNED.all;

entity i2c_slave is
port ( RST : in std_logic;
MCLK : in std_logic;
SEND : in std_logic;
ACK_IN : in std_logic;
RECV : in std_logic;
DATA_IN : in std_logic_vector(7 downto 0);
HOLDING : out std_logic;
DATA_OUT : out std_logic_vector(7 downto 0);
BYTE_COUNT : out std_logic_vector(9 downto 0);
START : out std_logic;
SCL : inout std_logic;
SDA : inout std_logic
);
end i2c_slave;

architecture i2c_slave_arch of i2c_slave is

component synchronizer
port(
CLK : in std_logic;
CLR : in std_logic;
UNSYNC_IN : in std_logic;
SYNC_OUT : out std_logic
);
end component;

signal old_scl : std_logic;
signal s_scl : std_logic;
signal s_sda : std_logic;
signal tx_out : std_logic;
signal old_clk : std_logic;
signal old_sda : std_logic;
signal sync_scl : std_logic;
signal sync_sda : std_logic;
signal d_scl : std_logic;
signal d_sda : std_logic;
signal start_con : std_logic;
signal nacked : std_logic;
signal bit_cnt : integer range 0 to 9;
signal hold_bit : integer range 0 to 9;
signal byte_cnt : std_logic_vector(9 downto 0);
signal send_byte : std_logic_vector(9 downto 0);

begin

c1 : synchronizer port map(
CLK => MCLK,
CLR => RST,
UNSYNC_IN => SCL,
SYNC_OUT => sync_scl
);


c2 : synchronizer port map(
CLK => MCLK,
CLR => RST,
UNSYNC_in => SDA,
SYNC_out => sync_sda
);

BYTE_COUNT <= byte_cnt;

SDA <= '0' when start_con = '1' and byte_cnt > 0 and nacked = '0' and (
(byte_cnt = send_byte and bit_cnt > 0 and tx_out = '0') or
(byte_cnt /= send_byte and bit_cnt = 0 and ACK_IN = '0'))
else 'Z';


HOLDING <= '1' when hold_bit = bit_cnt else '0';
START <= start_con;


--------------------------------------------------------------------------
-- PROCESS : delay_sda_scl
--
-- Store the current and previous I2C bus status. This is used to detect
-- rising and falling edges.
--------------------------------------------------------------------------
delay_sda_scl : process(MCLK)
begin
if rising_edge(MCLK) then
s_scl <= sync_scl;
s_sda <= sync_sda;

d_scl <= s_scl;
d_sda <= s_sda;
end if;
end process delay_sda_scl;


--------------------------------------------------------------------------
-- PROCESS : scl_hold
--
-- Hold SCL low (clock stretching) while the system decides what to do.
--
-- If receiving a byte hold SCL low before the ACK bit, but if sending
-- a byte hold SCL low after the ACK bit.
--------------------------------------------------------------------------
scl_hold : process(RST, MCLK)
begin
if RST = '0' then
SCL <= 'Z';
elsif rising_edge(MCLK) then
if s_scl = '0' and (bit_cnt = hold_bit) and start_con = '1' then
SCL <= '0';
else
SCL <= 'Z';
end if;
end if;
end process scl_hold;


--------------------------------------------------------------------------
-- PROCESS : start_stop
--
-- Detect START and STOP conditions on the I2C bus.
--------------------------------------------------------------------------
start_stop : process(RST, MCLK)
begin
if RST = '0' then
start_con <= '0';
elsif rising_edge(MCLK) then
if d_scl = '1' and s_scl = '1' then
if d_sda = '1' and s_sda = '0' then
start_con <= '1';
elsif d_sda = '0' and s_sda = '1' then
start_con <= '0';
end if;
end if;
end if;
end process start_stop;


--------------------------------------------------------------------------
-- PROCESS : counter
--
-- Count the bits and bytes on the I2C bus.
--------------------------------------------------------------------------
counter : process(MCLK)
begin
if rising_edge(MCLK) then
if start_con = '0' then
bit_cnt <= 0;
byte_cnt <= (others => '0');
elsif d_scl = '1' and s_scl = '0' then
if bit_cnt = 0 then
bit_cnt <= 8;
if byte_cnt < 1000 then
byte_cnt <= byte_cnt + 1;
end if;
else
bit_cnt <= bit_cnt 1;
end if;
end if;
end if;
end process counter;


--------------------------------------------------------------------------
-- PROCESS : shift_regs
--
-- Shift bits in and out of the I2C bus.
--------------------------------------------------------------------------
shift_regs : process(RST, MCLK)
begin
if RST = '0' then
DATA_OUT <= (others => '0');
elsif rising_edge(MCLK) then

if d_scl = '0' and s_scl = '0' then
if bit_cnt > 0 then
tx_out <= DATA_IN(bit_cnt 1);
end if;
elsif d_scl = '1' and s_scl = '1' then
if bit_cnt > 0 then
DATA_OUT(bit_cnt 1) <= s_sda;
end if;
end if;
end if;
end process shift_regs;


--------------------------------------------------------------------------
-- PROCESS : next_op
--
-- Process the SEND and RECV inputs and calculate the next place to hold
-- SCL low.
--------------------------------------------------------------------------
next_op : process(MCLK)
begin
if rising_edge(MCLK) then
if start_con = '0' then
hold_bit <= 5;
send_byte <= (others => '0');
else
if bit_cnt = 7 then
if byte_cnt = send_byte then -- sending
hold_bit <= 8;
else -- receiving
hold_bit <= 0;
end if;
elsif hold_bit = bit_cnt then
if SEND = '1' then
-- Send a byte out.
if bit_cnt = 8 then
send_byte <= byte_cnt;
else
send_byte <= byte_cnt + '1';
end if;
hold_bit <= 5;
elsif RECV = '1' then
-- Recieve a byte.
hold_bit <= 5;
end if;
end if;
end if;
end if;
end process next_op;


--------------------------------------------------------------------------
-- PROCESS : read_ack
--
-- Detect a NACK and stop everything. NACK's are used to stop the
-- slave from transmitting (see page 10 of the I2C specification).
--------------------------------------------------------------------------
read_ack : process(MCLK)
begin
if rising_edge(MCLK) then
if start_con = '0' then
nacked <= '0';
else
if send_byte = byte_cnt and byte_cnt > 0 and bit_cnt = 0 then
if d_scl = '1' and s_scl = '1' and s_sda = '1' then
nacked <= '1';
end if;
end if;
end if;
end if;
end process read_ack;

end i2c_slave_arch;


BackHome