-- -- Simple I2C controller -- -- 1) No multimaster -- 2) No slave mode -- 3) No fifo's -- -- notes: -- Every command is acknowledged. Do not set a new command before previous is acknowledged. -- Dout is available 1 clock cycle later as cmd_ack -- library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all; package I2C is component simple_i2c is port ( clk : in std_logic; ena : in std_logic; nReset : in std_logic; clk_cnt : in unsigned(7 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; ack_out : out std_logic; Dout : out std_logic_vector(7 downto 0); -- i2c signals SCL : inout std_logic; SDA : inout std_logic ); end component simple_i2c; end package I2C; library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all; entity simple_i2c is port ( clk : in std_logic; ena : in std_logic; nReset : in std_logic; clk_cnt : in unsigned(7 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; ack_out : out std_logic; Dout : out std_logic_vector(7 downto 0); -- i2c signals SCL : inout std_logic; SDA : inout std_logic ); end entity simple_i2c; architecture structural of simple_i2c is component i2c_core is port ( clk : in std_logic; nReset : in std_logic; clk_cnt : in unsigned(7 downto 0); cmd : in std_logic_vector(2 downto 0); cmd_ack : out std_logic; busy : out std_logic; Din : in std_logic; Dout : out std_logic; SCL : inout std_logic; SDA : inout std_logic ); end component i2c_core; -- commands for i2c_core constant CMD_NOP : std_logic_vector(2 downto 0) := "000"; constant CMD_START : std_logic_vector(2 downto 0) := "010"; constant CMD_STOP : std_logic_vector(2 downto 0) := "011"; constant CMD_READ : std_logic_vector(2 downto 0) := "100"; constant CMD_WRITE : std_logic_vector(2 downto 0) := "101"; -- signals for i2c_core signal core_cmd : std_logic_vector(2 downto 0); signal core_ack, core_busy, core_txd, core_rxd : 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; begin -- hookup i2c core u1: i2c_core port map (clk, nReset, clk_cnt, core_cmd, core_ack, core_busy, core_txd, core_rxd, SCL, SDA); -- generate host-command-acknowledge cmd_ack <= host_ack; -- generate go-signal go <= (read or write) and not host_ack; -- assign Dout output to shift-register Dout <= sr; -- assign ack_out output to core_rxd (contains last received bit) ack_out <= core_rxd; -- generate shift register shift_register: process(clk) begin if (clk'event and clk = '1') then if (ld = '1') then sr <= din; elsif (shift = '1') then sr <= (sr(6 downto 0) & core_rxd); end if; end if; end process shift_register; -- -- state machine -- statemachine : block type states is (st_idle, st_start, st_read, st_write, st_ack, st_stop); signal state : states; signal dcnt : unsigned(2 downto 0); begin -- -- command interpreter, translate complex commands into simpler I2C commands -- nxt_state_decoder: process(clk, nReset, state) variable nxt_state : states; variable idcnt : unsigned(2 downto 0); variable ihost_ack : std_logic; variable icore_cmd : std_logic_vector(2 downto 0); variable icore_txd : std_logic; variable ishift, iload : std_logic; begin -- 8 databits (1byte) of data to shift-in/out idcnt := dcnt; -- no acknowledge (until command complete) ihost_ack := '0'; icore_txd := core_txd; -- keep current command to i2c_core icore_cmd := core_cmd; -- no shifting or loading of shift-register ishift := '0'; iload := '0'; -- keep current state; nxt_state := state; case state is when st_idle => if (go = '1') then if (start = '1') then nxt_state := st_start; icore_cmd := CMD_START; elsif (read = '1') then nxt_state := st_read; icore_cmd := CMD_READ; idcnt := "111"; else nxt_state := st_write; icore_cmd := CMD_WRITE; idcnt := "111"; iload := '1'; end if; end if; when st_start => if (core_ack = '1') then if (read = '1') then nxt_state := st_read; icore_cmd := CMD_READ; idcnt := "111"; else nxt_state := st_write; icore_cmd := CMD_WRITE; idcnt := "111"; iload := '1'; end if; end if; when st_write => if (core_ack = '1') then idcnt := dcnt -1; -- count down Data_counter icore_txd := sr(7); if (dcnt = 0) then nxt_state := st_ack; icore_cmd := CMD_READ; else ishift := '1'; -- icore_txd := sr(7); end if; end if; when st_read => if (core_ack = '1') then idcnt := dcnt -1; -- count down Data_counter ishift := '1'; if (dcnt = 0) then nxt_state := st_ack; icore_cmd := CMD_WRITE; icore_txd := ack_in; end if; end if; when st_ack => if (core_ack = '1') then -- generate command acknowledge signal ihost_ack := '1'; -- Perform an additional shift, needed for 'read' (store last received bit in shift register) ishift := '1'; -- check for stop; Should a STOP command be generated ? if (stop = '1') then nxt_state := st_stop; icore_cmd := CMD_STOP; else nxt_state := st_idle; icore_cmd := CMD_NOP; end if; end if; when st_stop => if (core_ack = '1') then nxt_state := st_idle; icore_cmd := CMD_NOP; end if; when others => -- illegal states nxt_state := st_idle; icore_cmd := CMD_NOP; end case; -- generate registers if (nReset = '0') then core_cmd <= CMD_NOP; core_txd <= '0'; shift <= '0'; ld <= '0'; dcnt <= "111"; host_ack <= '0'; state <= st_idle; elsif (clk'event and clk = '1') then if (ena = '1') then state <= nxt_state; dcnt <= idcnt; shift <= ishift; ld <= iload; core_cmd <= icore_cmd; core_txd <= icore_txd; host_ack <= ihost_ack; end if; end if; end process nxt_state_decoder; end block statemachine; end architecture structural; -- -- -- I2C Core -- -- Translate simple commands into SCL/SDA transitions -- Each command has 5 states, A/B/C/D/idle -- -- start: SCL ~~~~~~~~~~\____ -- SDA ~~~~~~~~\______ -- 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 ==X=========X= -- x | A | B | C | D | i -- --- read SCL ____/~~~~\____ -- SDA XXXX=====XXXX -- 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_core is port ( clk : in std_logic; nReset : in std_logic; clk_cnt : in unsigned(7 downto 0); cmd : in std_logic_vector(2 downto 0); cmd_ack : out std_logic; busy : out std_logic; Din : in std_logic; Dout : out std_logic; SCL : inout std_logic; SDA : inout std_logic ); end entity i2c_core; architecture structural of i2c_core is constant CMD_NOP : std_logic_vector(2 downto 0) := "000"; constant CMD_START : std_logic_vector(2 downto 0) := "010"; constant CMD_STOP : std_logic_vector(2 downto 0) := "011"; constant CMD_READ : std_logic_vector(2 downto 0) := "100"; constant CMD_WRITE : std_logic_vector(2 downto 0) := "101"; type cmds is (idle, start_a, start_b, start_c, start_d, stop_a, stop_b, stop_c, rd_a, rd_b, rd_c, rd_d, wr_a, wr_b, wr_c, wr_d); signal state : cmds; signal SDAo, SCLo : std_logic; signal txd : std_logic; signal clk_en, slave_wait :std_logic; signal cnt : unsigned(7 downto 0) := clk_cnt; begin -- whenever the slave is not ready it can delay the cycle by pulling SCL low slave_wait <= '1' when ((SCLo = '1') and (SCL = '0')) else '0'; -- generate clk enable signal gen_clken: process(clk, nReset) begin if (nReset = '0') then cnt <= (others => '0'); clk_en <= '1'; --'0'; elsif (clk'event and clk = '1') then if (cnt = 0) then clk_en <= '1'; cnt <= clk_cnt; else if (slave_wait = '0') then cnt <= cnt -1; end if; clk_en <= '0'; end if; end if; end process gen_clken; -- generate statemachine nxt_state_decoder : process (clk, nReset, state, cmd, SDA) variable nxt_state : cmds; variable icmd_ack, ibusy, store_sda : std_logic; variable itxd : std_logic; begin nxt_state := state; icmd_ack := '0'; -- default no acknowledge ibusy := '1'; -- default busy store_sda := '0'; itxd := txd; case (state) is -- idle when idle => case cmd is when CMD_START => nxt_state := start_a; icmd_ack := '1'; -- command completed when CMD_STOP => nxt_state := stop_a; icmd_ack := '1'; -- command completed when CMD_WRITE => nxt_state := wr_a; icmd_ack := '1'; -- command completed itxd := Din; when CMD_READ => nxt_state := rd_a; icmd_ack := '1'; -- command completed when others => nxt_state := idle; -- don't acknowledge NOP command icmd_ack := '1'; -- command completed ibusy := '0'; end case; -- start when start_a => nxt_state := start_b; when start_b => nxt_state := start_c; when start_c => nxt_state := start_d; when start_d => nxt_state := idle; ibusy := '0'; -- not busy when idle -- stop when stop_a => nxt_state := stop_b; when stop_b => nxt_state := stop_c; when stop_c => -- nxt_state := stop_d; -- when stop_d => nxt_state := idle; ibusy := '0'; -- not busy when idle -- read when rd_a => nxt_state := rd_b; when rd_b => nxt_state := rd_c; when rd_c => nxt_state := rd_d; store_sda := '1'; when rd_d => nxt_state := idle; ibusy := '0'; -- not busy when idle -- write when wr_a => nxt_state := wr_b; when wr_b => nxt_state := wr_c; when wr_c => nxt_state := wr_d; when wr_d => nxt_state := idle; ibusy := '0'; -- not busy when idle end case; -- generate regs if (nReset = '0') then state <= idle; cmd_ack <= '0'; busy <= '0'; txd <= '0'; Dout <= '0'; elsif (clk'event and clk = '1') then if (clk_en = '1') then state <= nxt_state; busy <= ibusy; txd <= itxd; if (store_sda = '1') then Dout <= SDA; end if; end if; cmd_ack <= icmd_ack and clk_en; end if; end process nxt_state_decoder; -- -- convert states to SCL and SDA signals -- output_decoder: process (clk, nReset, state) variable iscl, isda : std_logic; begin case (state) is when idle => iscl := SCLo; -- keep SCL in same state isda := SDA; -- keep SDA in same state -- start when start_a => iscl := SCLo; -- keep SCL in same state (for repeated start) isda := '1'; -- set SDA high when start_b => iscl := '1'; -- set SCL high isda := '1'; -- keep SDA high when start_c => iscl := '1'; -- keep SCL high isda := '0'; -- sel SDA low when start_d => iscl := '0'; -- set SCL low isda := '0'; -- keep SDA low -- stop when stop_a => iscl := '0'; -- keep SCL disabled isda := '0'; -- set SDA low when stop_b => iscl := '1'; -- set SCL high isda := '0'; -- keep SDA low when stop_c => iscl := '1'; -- keep SCL high isda := '1'; -- set SDA high -- write when wr_a => iscl := '0'; -- keep SCL low -- isda := txd; -- set SDA isda := Din; when wr_b => iscl := '1'; -- set SCL high -- isda := txd; -- set SDA isda := Din; when wr_c => iscl := '1'; -- keep SCL high -- isda := txd; -- set SDA isda := Din; when wr_d => iscl := '0'; -- set SCL low -- isda := txd; -- set SDA isda := Din; -- read when rd_a => iscl := '0'; -- keep SCL low isda := '1'; -- tri-state SDA when rd_b => iscl := '1'; -- set SCL high isda := '1'; -- tri-state SDA when rd_c => iscl := '1'; -- keep SCL high isda := '1'; -- tri-state SDA when rd_d => iscl := '0'; -- set SCL low isda := '1'; -- tri-state SDA end case; -- generate registers if (nReset = '0') then SCLo <= '1'; SDAo <= '1'; elsif (clk'event and clk = '1') then if (clk_en = '1') then SCLo <= iscl; SDAo <= isda; end if; end if; end process output_decoder; SCL <= '0' when (SCLo = '0') else 'Z'; -- since SCL is externally pulled-up convert a '1' to a 'Z'(tri-state) SDA <= '0' when (SDAo = '0') else 'Z'; -- since SDA is externally pulled-up convert a '1' to a 'Z'(tri-state) -- SCL <= SCLo; -- SDA <= SDAo; end architecture structural;