library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity slaveOut is port( scl_i, sda_i : in std_logic; -- I2C SCL, SDA input scl_o, sda_o : out std_logic; -- I2C SCL, SDA output devAddr_i : in std_logic_vector (6 downto 0); -- I2C address for this device is16Bits_i : in std_logic; -- register size, 1: 16 bits, 0: 8 bits data_i : in unsigned(15 downto 0)); -- register data input ( (7,0) for 8-bit register ) -- dbg: out std_logic_vector (7 downto 0); -- for testing only -- dbg_start: out std_logic; -- for testing only -- dbg_start2: out std_logic_vector (3 downto 0)); -- for testing only end slaveOut; architecture slaveOut_impl of slaveOut is -- I2C communication states: -- idle: before receiving a S bit or after received a P bit -- start: receiving device address -- ack_start: Ack. master if the received address equal devAddr_i, otherwise, go to 'hold' state. -- write_a: writing first byte -- ack_a: ack. from master -- write_b: write second byte -- (master needn't ack. the last byte) -- hold: Wait until received a P bit. -- type states is (idle,start,ack_start,write_a,ack_a,write_b,hold); -- c_state represents current state signal c_state : states := idle; -- internal buffer for receiving bits from master signal reg : std_logic_vector ( 7 downto 0) := "00000000"; signal regBit : std_logic; -- counter for shifting reg signal shiftCnt : unsigned(3 downto 0) := "0000"; -- signals for reg signal ldReg, rstReg : std_logic := '0'; -- I2C SDA output signal signal isda : std_logic := '1'; -- a register for sending out data signal sendReg : std_logic_vector(7 downto 0); -- counter for shifting sendReg signal sendCnt : unsigned(3 downto 0); -- S bit, P bit signal signal started, doStop : std_logic := '0'; begin -- preserve input across low scl_i regBit <= sda_i when scl_i = '1'; -- manipulate internal buffer -- write always happening in scl_i's falling edge -- loadReg: process(scl_i, rstReg, regBit,reg) begin if(scl_i = '0' and scl_i'event) then if(rstReg = '1') then -- clear reg and shiftCnt reg <= "00000000"; shiftCnt <= "0000"; elsif(ldReg = '1') then -- shift reg and load regBit into reg(0) reg <= reg(6 downto 0) & regBit; shiftCnt <= shiftCnt + 1; end if; end if; end process loadReg; -- detect S bit and set started signal -- S bit: when sda_i is falling and scl_i is high -- -- note: sda_i never changes when scl_i is rising or high -- except the master transmiting S or P bit detectStart: process(scl_i, sda_i, doStop) begin if(doStop = '1') then started <= '0'; elsif(sda_i = '0' and sda_i'event) then if(scl_i = '1' and started = '0') then started <='1'; end if; end if; end process detectStart; -- detect P bit and set doStop signal -- P bit: when sda_i is rising and scl_i is high detectStop: process(scl_i, sda_i, started) begin if(started = '0') then doStop <= '0'; elsif(sda_i = '1' and sda_i'event) then if(scl_i = '1' and started = '1') then doStop <='1'; end if; end if; end process detectStop; -- send ack. bit or data -- -- When sending ack. bit, -- isda must be low before next rising scl_i -- and hold low when scl_i is high. -- -- When sending data, -- the bit to be sent is always ready in sendReg(7). -- The preparation is done by the process loadOrShiftData below. sendAckOrData: process(scl_i, c_state, sendReg) begin if(c_state = idle or (c_state = hold and scl_i = '0')) then isda <= '1'; elsif(scl_i = '0' and scl_i'event) then case(c_state) is when ack_start => if(reg(6 downto 0) /= devAddr_i or regBit /= '1') then isda <= '1'; -- not for this device => don't ack. else isda <= '0'; end if; when write_a => isda <= sendReg(7); when write_b => isda <= sendReg(7); when others => isda <= '1'; end case; end if; end process sendAckOrData; -- This process fills or shifts sendReg to make -- sendReg(7) the next bit to be sent. loadOrShiftData: process(scl_i) begin if scl_i = '1' and scl_i'event then case c_state is when write_a => -- We are transferring the first byte. -- Left-shift the register and increase sencCnt both by one. sendReg <= sendReg(6 downto 0) & '0'; sendCnt <= sendCnt + 1; when ack_a => -- Transfer of the first byte has been completed, and -- We are currently waiting for ack. bit from master. -- At the same time we fill sendReg with the second byte -- and initialize sendCnt to zero. for i in 7 downto 0 loop sendReg(i) <= data_i(i); end loop; sendCnt <= X"0"; when write_b => -- We are transferring the second byte. -- Left-shift the register and increase sencCnt both by one. sendReg <= sendReg(6 downto 0) & '0'; sendCnt <= sendCnt + 1; when others => -- When in other states, we prepare for transferring -- of the first byte. if is16Bits_i = '1' then for i in 15 downto 8 loop sendReg(i - 8) <= data_i(i); end loop; else for i in 7 downto 0 loop sendReg(i) <= data_i(i); end loop; end if; sendCnt <= X"0"; end case; end if; end process loadOrShiftData; -- state machine stateMachine: process(scl_i, c_state, started) begin if(started = '0') then c_state <= idle; elsif(started = '1' and c_state=idle) then c_state <= start; elsif(scl_i = '1' and scl_i'event) then case(c_state) is when idle => rstReg <= '1'; ldReg <= '0'; c_state <= idle; when start => rstReg <= '0'; ldReg <= '1'; if(shiftCnt = "0111") then -- receiving 8 bits before going to next state c_state <= ack_start; else c_state <= start; end if; when ack_start => ldReg <= '0'; rstReg <= '1'; if(reg(7 downto 1) /= devAddr_i or reg(0) /= '1') then c_state <= hold; -- hold until receiving P bit else if(is16Bits_i = '1') then c_state <= write_a; else c_state <= write_b; end if; end if; when write_a => rstReg <= '0'; ldReg <= '1'; if(sendCnt = "0111") then c_state <= ack_a; else c_state <= write_a; end if; when ack_a => ldReg <= '0'; rstReg <= '1'; if sda_i = '0' then -- master ack.ed c_state <= write_b; else c_state <= ack_a; end if; when write_b => rstReg <= '0'; ldReg <= '1'; if(sendCnt = "0111") then c_state <= hold; else c_state <= write_b; end if; when hold => -- do nothing ldReg <= '0'; rstReg <= '1'; c_state <= hold; when others => -- do nothing ldReg <= '0'; rstReg <= '0'; c_state <= c_state; end case; end if; end process stateMachine; -- assign output signals sda_o <= isda; scl_o <= '1'; -- this device never pulling down SCL -- assign debug signals -- dbg <= reg; -- for testing only -- dbg_start <= started; -- for testing only -- dbg_start2 <= sendCnt; -- for testing only end slaveOut_impl;