Newer
Older

Dimitris Lampridis
committed
-------------------------------------------------------------------------------
-- Title : I2C Slave Core
-- Project : OHWR General Cores
-- URL : http://www.ohwr.org/projects/general-cores
-------------------------------------------------------------------------------
-- File : gc_i2c_slave.vhd
-- Author(s) : Theodor Stana <t.stana@cern.ch>
-- Dimitrios Lampridis <dimitrios.lampridis@cern.ch>
-- Company : CERN (BE-CO-HT)
-- Created : 2013-03-13
-- Last update: 2016-11-25
-- Standard : VHDL'93
-------------------------------------------------------------------------------
-- Description: Simple I2C slave interface, providing the basic low-level
-- functionality of the I2C protocol.

Dimitris Lampridis
committed
-- The gc_i2c_slave module waits for a master to initiate a transfer via
-- a start condition. The address is sent next and if the address matches
-- the slave address set via the i2c_addr_i input, the addr_good_p_o output
-- is set. Based on the eighth bit of the first I2C transfer byte, the module
-- then starts shifting in or out each byte in the transfer, setting the
-- r/w_done_p_o output after each received/sent byte.

Dimitris Lampridis
committed
-- For master write (slave read) transfers, the received byte can be read at
-- the rx_byte_o output when the r_done_p_o pin is high. For master read (slave
-- write) transfers, the slave sends the byte at the tx_byte_i input, which
-- should be set when the w_done_p_o output is high, either after I2C address
-- reception, or a successful send of a previous byte.
Theodor-Adrian Stana
committed
-- OHWR general-cores library
--
-- references:
-- [1] The I2C bus specification, version 2.1, NXP Semiconductor, Jan. 2000
-- http://www.nxp.com/documents/other/39340011.pdf

Dimitris Lampridis
committed
-------------------------------------------------------------------------------
-- Copyright (c) 2013-2016 CERN
-------------------------------------------------------------------------------

Dimitris Lampridis
committed
--
-- 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;

Dimitris Lampridis
committed
library work;
use work.gencores_pkg.all;
entity gc_i2c_slave is
generic
(
-- Length of glitch filter
-- 0 - SCL and SDA lines are passed only through synchronizer
-- 1 - one clk_i glitches filtered
-- 2 - two clk_i glitches filtered

Dimitris Lampridis
committed
g_gf_len : natural := 0;
-- Automatically ACK reception upon address match.
g_auto_addr_ack : boolean := FALSE
clk_i : in std_logic;
rst_n_i : in std_logic;
scl_i : in std_logic;
scl_o : out std_logic;
scl_en_o : out std_logic;
sda_i : in std_logic;
sda_o : out std_logic;
sda_en_o : out std_logic;
Theodor-Adrian Stana
committed
i2c_addr_i : in std_logic_vector(6 downto 0);
-- ACK input, should be set after done_p_o = '1'
-- (note that the bit is reversed wrt I2C ACK bit)
-- '1' - ACK
-- '0' - NACK
-- Byte to send, should be loaded while done_p_o = '1'
tx_byte_i : in std_logic_vector(7 downto 0);
-- Received byte, valid after done_p_o = '1'
rx_byte_o : out std_logic_vector(7 downto 0);
-- Pulse outputs signaling various I2C actions
-- Start and stop conditions
Theodor-Adrian Stana
committed
i2c_sta_p_o : out std_logic;
i2c_sto_p_o : out std_logic;
-- Received address corresponds i2c_addr_i
addr_good_p_o : out std_logic;
-- Read and write done
r_done_p_o : out std_logic;
w_done_p_o : out std_logic;
-- I2C bus operation, set after address detection
-- '0' - write
-- '1' - read
op_o : out std_logic
);
end entity gc_i2c_slave;
architecture behav of gc_i2c_slave is
--============================================================================
-- Type declarations
--============================================================================
type t_state is
(
IDLE, -- idle
ADDR, -- shift in I2C address bits
ADDR_ACK, -- ACK/NACK to I2C address
RD, -- shift in byte to read
RD_ACK, -- ACK/NACK to received byte
WR_LOAD_TXSR, -- load byte to send via I2C
WR, -- shift out byte
WR_ACK -- get ACK/NACK from master
);
--============================================================================
-- Signal declarations
--============================================================================
-- Deglitched signals and delays for SCL and SDA lines
signal scl_synced : std_logic;
signal scl_deglitched : std_logic;
signal scl_deglitched_d0 : std_logic;
signal sda_synced : std_logic;
signal sda_deglitched : std_logic;
signal sda_deglitched_d0 : std_logic;
signal scl_r_edge_p : std_logic;
signal scl_f_edge_p : std_logic;
signal sda_f_edge_p : std_logic;
signal sda_r_edge_p : std_logic;
-- FSM signals
signal state : t_state;
signal inhibit : std_logic;
signal txsr : std_logic_vector(7 downto 0);
signal rxsr : std_logic_vector(7 downto 0);
signal bit_cnt : unsigned(2 downto 0);
-- Start and stop condition pulse signals
signal sta_p, sto_p : std_logic;
-- Master ACKed after it has read a byte from the slave
signal mst_acked : std_logic;
--==============================================================================
-- architecture begin
--==============================================================================
begin
--============================================================================
-- I/O logic
--============================================================================
-- No clock stretching implemented, always disable SCL line
scl_o <= '0';
scl_en_o <= '0';
-- SDA line driven low; SDA_EN line controls when the tristate buffer is enabled
sda_o <= '0';
-- Assign RX byte output
rx_byte_o <= rxsr;
--============================================================================
-- Deglitching logic
--============================================================================
-- First, synchronize the SCL signal in the clk_i domain
cmp_sync_scl : gc_sync_ffs
generic map
(
g_sync_edge => "positive"
)
port map
(
clk_i => clk_i,
rst_n_i => rst_n_i,
data_i => scl_i,
synced_o => scl_synced
);
-- Generate deglitched SCL signal
cmp_scl_deglitch : gc_glitch_filt
generic map
(
)
port map
(
clk_i => clk_i,
rst_n_i => rst_n_i,
dat_i => scl_synced,
dat_o => scl_deglitched
);
-- and create a delayed version of this signal, together with one-tick-long
-- falling-edge detection signal
p_scl_degl_d0 : process(clk_i) is
begin
if rising_edge(clk_i) then
if (rst_n_i = '0') then
scl_deglitched_d0 <= '0';
scl_f_edge_p <= '0';
scl_r_edge_p <= '0';
else
scl_deglitched_d0 <= scl_deglitched;
scl_f_edge_p <= (not scl_deglitched) and scl_deglitched_d0;
scl_r_edge_p <= scl_deglitched and (not scl_deglitched_d0);
end if;
end if;
end process p_scl_degl_d0;
-- Synchronize SDA signal in clk_i domain
cmp_sda_sync : gc_sync_ffs
generic map
(
g_sync_edge => "positive"
)
port map
(
clk_i => clk_i,
rst_n_i => rst_n_i,
data_i => sda_i,
synced_o => sda_synced
);
-- Generate deglitched SDA signal
cmp_sda_deglitch : gc_glitch_filt
generic map
(
)
port map
(
clk_i => clk_i,
rst_n_i => rst_n_i,
dat_i => sda_synced,
dat_o => sda_deglitched
);
-- and create a delayed version of this signal, together with one-tick-long
-- falling- and rising-edge detection signals
p_sda_deglitched_d0 : process(clk_i) is
begin
if rising_edge(clk_i) then
if (rst_n_i = '0') then
sda_deglitched_d0 <= '0';
sda_f_edge_p <= '0';
sda_r_edge_p <= '0';
else
sda_deglitched_d0 <= sda_deglitched;
sda_f_edge_p <= (not sda_deglitched) and sda_deglitched_d0;
sda_r_edge_p <= sda_deglitched and (not sda_deglitched_d0);
end if;
end if;
end process p_sda_deglitched_d0;
--============================================================================
-- Start and stop condition outputs
--============================================================================
Theodor-Adrian Stana
committed
-- First the process to set the start and stop conditions as per I2C standard
p_sta_sto : process (clk_i) is
begin
if rising_edge(clk_i) then
if (rst_n_i = '0') then
sta_p <= '0';
sto_p <= '0';
else
sta_p <= sda_f_edge_p and scl_deglitched;
sto_p <= sda_r_edge_p and scl_deglitched;
end if;
end if;
end process p_sta_sto;
Theodor-Adrian Stana
committed
-- Finally, set the outputs
i2c_sta_p_o <= sta_p;
i2c_sto_p_o <= sto_p;
--============================================================================
-- FSM logic
--============================================================================
p_fsm: process (clk_i) is
begin
if rising_edge(clk_i) then
if (rst_n_i = '0') then
state <= IDLE;
inhibit <= '0';
bit_cnt <= (others => '0');
rxsr <= (others => '0');
txsr <= (others => '0');
mst_acked <= '0';
r_done_p_o <= '0';
w_done_p_o <= '0';
addr_good_p_o <= '0';
op_o <= '0';
Theodor-Adrian Stana
committed
-- start and stop conditions are followed by I2C address, so any byte
-- following would be an address byte; therefore, it is safe to deinhibit
-- the FSM
elsif (sta_p = '1') or (sto_p = '1') then
state <= IDLE;
inhibit <= '0';
-- state machine logic
else
case state is
---------------------------------------------------------------------
-- IDLE
---------------------------------------------------------------------
when IDLE =>
Theodor-Adrian Stana
committed
-- clear outputs and bit counter
bit_cnt <= (others => '0');
Theodor-Adrian Stana
committed
sda_en_o <= '0';
mst_acked <= '0';
r_done_p_o <= '0';
w_done_p_o <= '0';
addr_good_p_o <= '0';
if (scl_f_edge_p = '1') and (inhibit = '0') then
state <= ADDR;
end if;
---------------------------------------------------------------------
-- ADDR
---------------------------------------------------------------------
when ADDR =>
-- Shifting in is done on rising edge of SCL
if (scl_r_edge_p = '1') then
rxsr <= rxsr(6 downto 0) & sda_deglitched;
bit_cnt <= bit_cnt + 1;
--
-- Checking the bit counter is done on the falling edge of SCL
--
-- If 8 bits have been shifted in, the received address is checked
-- and the slave goes in the ADDR_ACK state.
--
-- If the address is not ours, go back to IDLE and set inhibit bits
-- so bytes sent to or received from another slave that happen to
-- coincide to the address of this slave don't get interpreted
-- as accesses to this slave.
--
if (scl_f_edge_p = '1') then
if (bit_cnt = 0) then
if (rxsr(7 downto 1) = i2c_addr_i) then
op_o <= rxsr(0);
addr_good_p_o <= '1';
state <= ADDR_ACK;
else
inhibit <= '1';
state <= IDLE;
end if;
---------------------------------------------------------------------
-- ADDR_ACK
---------------------------------------------------------------------
when ADDR_ACK =>
Theodor-Adrian Stana
committed
-- clear addr_good pulse
Theodor-Adrian Stana
committed
-- send ACK from input, check the ACK on falling edge and go to
-- loading of the TXSR if the OP bit is a write, or read otherwise

Dimitris Lampridis
committed
if g_auto_addr_ack = TRUE then
sda_en_o <= '1';
else
sda_en_o <= ack_i;
end if;
if (scl_f_edge_p = '1') then

Dimitris Lampridis
committed
if (g_auto_addr_ack = TRUE) or (ack_i = '1') then
if (rxsr(0) = '0') then
state <= RD;
else
state <= WR_LOAD_TXSR;
end if;
end if;
end if;
---------------------------------------------------------------------
-- RD
---------------------------------------------------------------------
-- Shift in bits sent by the master
---------------------------------------------------------------------
when RD =>
Theodor-Adrian Stana
committed
-- not controlling SDA, clear enable signal
sda_en_o <= '0';
-- shift in on rising-edge
if (scl_r_edge_p = '1') then
rxsr <= rxsr(6 downto 0) & sda_deglitched;
bit_cnt <= bit_cnt + 1;
if (scl_f_edge_p = '1') then
-- Received 8 bits, go to RD_ACK and signal external module
if (bit_cnt = 0) then
state <= RD_ACK;
r_done_p_o <= '1';
end if;
end if;
---------------------------------------------------------------------
-- RD_ACK
---------------------------------------------------------------------
when RD_ACK =>
-- Clear done pulse
Theodor-Adrian Stana
committed
-- we write the ACK bit, so control sda_en_o signal to send ACK/NACK
sda_en_o <= ack_i;
-- based on the ACK received by external command, we read the next
-- bit (ACK) or go back to idle state (NACK)
if (scl_f_edge_p = '1') then
state <= RD;
else
state <= IDLE;
end if;
end if;
---------------------------------------------------------------------
-- WR_LOAD_TXSR
---------------------------------------------------------------------
when WR_LOAD_TXSR =>
txsr <= tx_byte_i;
state <= WR;
---------------------------------------------------------------------
-- WR
---------------------------------------------------------------------
when WR =>
-- slave writes, SDA output enable is the negated value of the bit
-- to send (since on I2C, '1' is a release of the bus)
Theodor-Adrian Stana
committed
sda_en_o <= not txsr(7);
-- increment bit counter on rising edge
if (scl_r_edge_p = '1') then
Theodor-Adrian Stana
committed
-- Shift TXSR on falling edge of SCL
if (scl_f_edge_p = '1') then
Theodor-Adrian Stana
committed
txsr <= txsr(6 downto 0) & '0';
-- Eight bits sent, disable SDA and go to WR_ACK
if (bit_cnt = 0) then
state <= WR_ACK;
w_done_p_o <= '1';
end if;
end if;
---------------------------------------------------------------------
-- WR_ACK
---------------------------------------------------------------------
when WR_ACK =>
Theodor-Adrian Stana
committed
-- master controls SDA, clear sda_en_o
sda_en_o <= '0';
-- clear done pulse
Theodor-Adrian Stana
committed
-- sample in ACK from master on rising edge
if (scl_r_edge_p = '1') then
mst_acked <= '1';
else
mst_acked <= '0';
end if;
end if;
Theodor-Adrian Stana
committed
-- and check it on falling edge
if (scl_f_edge_p = '1') then
if (mst_acked = '1') then
state <= WR_LOAD_TXSR;
else
state <= IDLE;
end if;
end if;
---------------------------------------------------------------------
-- Any other state: go back to IDLE
---------------------------------------------------------------------
when others =>
state <= IDLE;
end case;
end if;
end if;
end process p_fsm;
end architecture behav;
--==============================================================================
-- architecture end
--==============================================================================