+ Post New Thread
Page 1 of 3 1 2 3 LastLast
Results 1 to 20 of 44
  1. #1
    Junior Member level 1
    Points: 97, Level: 1

    Join Date
    Oct 2018
    Posts
    18
    Helped
    0 / 0
    Points
    97
    Level
    1

    Implement I2C in VHDL

    Hello.

    I want to connect a GPIO Expander to my FPGA using I2C. I have seen this thread and I was using the same code, but I have the same problem and it is not solved.
    https://www.edaboard.com/showthread....l-example-code

    My development board is a Terasic DE10-Nano, with an Altera Cyclone V FPGA. The GPIO Expander is a PA9538A, the address of the device is "1110010.

    I will show you the code I'm using and the code I generated.

    Code:
    --------------------------------------------------------------------------------
    --
    --   FileName:         i2c_master.vhd
    --
    --   Version History
    --   Version 1.0 11/1/2012 Scott Larson
    --     Initial Public Release
    --   Version 2.0 06/20/2014 Scott Larson
    --     Added ability to interface with different slaves in the same transaction
    --     Corrected ack_error bug where ack_error went 'Z' instead of '1' on error
    --     Corrected timing of when ack_error signal clears
    --   Version 2.1 10/21/2014 Scott Larson
    --     Replaced gated clock with clock enable
    --     Adjusted timing of SCL during start and stop conditions
    -- 
    --------------------------------------------------------------------------------
    
    LIBRARY ieee;
    USE ieee.std_logic_1164.all;
    USE ieee.std_logic_unsigned.all;
    
    ENTITY i2c_master IS
      GENERIC(
        input_clk : INTEGER := 50_000_000; --input clock speed from user logic in Hz
        bus_clk   : INTEGER := 400_000);   --speed the i2c bus (scl) will run at in Hz
      PORT(
        clk       : IN     STD_LOGIC;                    --system clock
        reset_n   : IN     STD_LOGIC;                    --active low reset
        ena       : IN     STD_LOGIC;                    --latch in command
        addr      : IN     STD_LOGIC_VECTOR(6 DOWNTO 0); --address of target slave
        rw        : IN     STD_LOGIC;                    --'0' is write, '1' is read
        data_wr   : IN     STD_LOGIC_VECTOR(7 DOWNTO 0); --data to write to slave
        busy      : OUT    STD_LOGIC;                    --indicates transaction in progress
        data_rd   : OUT    STD_LOGIC_VECTOR(7 DOWNTO 0); --data read from slave
        ack_error : BUFFER STD_LOGIC;                    --flag if improper acknowledge from slave
        sda       : INOUT  STD_LOGIC;                    --serial data output of i2c bus
        scl       : INOUT  STD_LOGIC);                   --serial clock output of i2c bus
    END i2c_master;
    
    ARCHITECTURE logic OF i2c_master IS
      CONSTANT divider  :  INTEGER := (input_clk/bus_clk)/4; --number of clocks in 1/4 cycle of scl
      TYPE machine IS(ready, start, command, slv_ack1, wr, rd, slv_ack2, mstr_ack, stop); --needed states
      SIGNAL state         : machine;                        --state machine
      SIGNAL data_clk      : STD_LOGIC;                      --data clock for sda
      SIGNAL data_clk_prev : STD_LOGIC;                      --data clock during previous system clock
      SIGNAL scl_clk       : STD_LOGIC;                      --constantly running internal scl
      SIGNAL scl_ena       : STD_LOGIC := '0';               --enables internal scl to output
      SIGNAL sda_int       : STD_LOGIC := '1';               --internal sda
      SIGNAL sda_ena_n     : STD_LOGIC;                      --enables internal sda to output
      SIGNAL addr_rw       : STD_LOGIC_VECTOR(7 DOWNTO 0);   --latched in address and read/write
      SIGNAL data_tx       : STD_LOGIC_VECTOR(7 DOWNTO 0);   --latched in data to write to slave
      SIGNAL data_rx       : STD_LOGIC_VECTOR(7 DOWNTO 0);   --data received from slave
      SIGNAL bit_cnt       : INTEGER RANGE 0 TO 7 := 7;      --tracks bit number in transaction
      SIGNAL stretch       : STD_LOGIC := '0';               --identifies if slave is stretching scl
      signal data_clk_m : std_logic;
    BEGIN
    
      --generate the timing for the bus clock (scl_clk) and the data clock (data_clk)
      PROCESS(clk, reset_n)
        VARIABLE count  :  INTEGER RANGE 0 TO divider*4;  --timing for clock generation
      BEGIN
        IF(reset_n = '0') THEN                --reset asserted
          stretch <= '0';
          count := 0;
        ELSIF(clk'EVENT AND clk = '1') THEN
          data_clk_prev <= data_clk;          --store previous value of data clock
          IF(count = divider*4-1) THEN        --end of timing cycle
            count := 0;                       --reset timer
          ELSIF(stretch = '0') THEN           --clock stretching from slave not detected
            count := count + 1;               --continue clock generation timing
          END IF;
          CASE count IS
            WHEN 0 TO divider-1 =>            --first 1/4 cycle of clocking
              scl_clk <= '0';
              data_clk <= '0';
            WHEN divider TO divider*2-1 =>    --second 1/4 cycle of clocking
              scl_clk <= '0';
              data_clk <= '1';
            WHEN divider*2 TO divider*3-1 =>  --third 1/4 cycle of clocking
              scl_clk <= '1';                 --release scl
              IF(scl = '0') THEN              --detect if slave is stretching clock
                stretch <= '1';
              ELSE
                stretch <= '0';
              END IF;
              data_clk <= '1';
            WHEN OTHERS =>                    --last 1/4 cycle of clocking
              scl_clk <= '1';
              data_clk <= '0';
          END CASE;
        END IF;
      END PROCESS;
    
      --state machine and writing to sda during scl low (data_clk rising edge)
      PROCESS(clk, reset_n)
      BEGIN
        IF(reset_n = '0') THEN                 --reset asserted
          state <= ready;                      --return to initial state
          busy <= '1';                         --indicate not available
          scl_ena <= '0';                      --sets scl high impedance
          sda_int <= '1';                      --sets sda high impedance
          ack_error <= '0';                    --clear acknowledge error flag
          bit_cnt <= 7;                        --restarts data bit counter
          data_rd <= "00000000";               --clear data read port
        ELSIF(clk'EVENT AND clk = '1') THEN
          IF(data_clk = '1' AND data_clk_prev = '0') THEN  --data clock rising edge
            CASE state IS
              WHEN ready =>                      --idle state
                IF(ena = '1') THEN               --transaction requested
                  busy <= '1';                   --flag busy
                  addr_rw <= addr & rw;          --collect requested slave address and command
                  data_tx <= data_wr;            --collect requested data to write
                  state <= start;                --go to start bit
                ELSE                             --remain idle
                  busy <= '0';                   --unflag busy
                  state <= ready;                --remain idle
                END IF;
              WHEN start =>                      --start bit of transaction
                busy <= '1';                     --resume busy if continuous mode
                sda_int <= addr_rw(bit_cnt);     --set first address bit to bus
                state <= command;                --go to command
              WHEN command =>                    --address and command byte of transaction
                IF(bit_cnt = 0) THEN             --command transmit finished
                  sda_int <= '1';                --release sda for slave acknowledge
                  bit_cnt <= 7;                  --reset bit counter for "byte" states
                  state <= slv_ack1;             --go to slave acknowledge (command)
                ELSE                             --next clock cycle of command state
                  bit_cnt <= bit_cnt - 1;        --keep track of transaction bits
                  sda_int <= addr_rw(bit_cnt-1); --write address/command bit to bus
                  state <= command;              --continue with command
                END IF;
              WHEN slv_ack1 =>                   --slave acknowledge bit (command)
                IF(addr_rw(0) = '0') THEN        --write command
                  sda_int <= data_tx(bit_cnt);   --write first bit of data
                  state <= wr;                   --go to write byte
                ELSE                             --read command
                  sda_int <= '1';                --release sda from incoming data
                  state <= rd;                   --go to read byte
                END IF;
              WHEN wr =>                         --write byte of transaction
                busy <= '1';                     --resume busy if continuous mode
                IF(bit_cnt = 0) THEN             --write byte transmit finished
                  sda_int <= '1';                --release sda for slave acknowledge
                  bit_cnt <= 7;                  --reset bit counter for "byte" states
    			  busy<='0'; 						--modified
                  state <= slv_ack2;             --go to slave acknowledge (write)
                ELSE                             --next clock cycle of write state
                  bit_cnt <= bit_cnt - 1;        --keep track of transaction bits
                  sda_int <= data_tx(bit_cnt-1); --write next bit to bus
                  state <= wr;                   --continue writing
                END IF;
              WHEN rd =>                         --read byte of transaction
                busy <= '1';                     --resume busy if continuous mode
                IF(bit_cnt = 0) THEN             --read byte receive finished
                  IF(ena = '1' AND addr_rw = addr & rw) THEN  --continuing with another read at same address
                    sda_int <= '0';              --acknowledge the byte has been received
                  ELSE                           --stopping or continuing with a write
                    sda_int <= '1';              --send a no-acknowledge (before stop or repeated start)
                  END IF;
                  bit_cnt <= 7;                  --reset bit counter for "byte" states
                  data_rd <= data_rx;            --output received data
                  state <= mstr_ack;             --go to master acknowledge
                ELSE                             --next clock cycle of read state
                  bit_cnt <= bit_cnt - 1;        --keep track of transaction bits
                  state <= rd;                   --continue reading
                END IF;
              WHEN slv_ack2 =>                   --slave acknowledge bit (write)
                IF(ena = '1') THEN               --continue transaction
                 -- busy <= '0';                   --continue is accepted
                  addr_rw <= addr & rw;          --collect requested slave address and command
                  data_tx <= data_wr;            --collect requested data to write
                  IF(addr_rw = addr & rw) THEN   --continue transaction with another write
    			  busy <= '1';
                    sda_int <= data_wr(bit_cnt); --write first bit of data
                    state <= wr;                 --go to write byte
                  ELSE                           --continue transaction with a read or new slave
                    state <= start;              --go to repeated start
                  END IF;
                ELSE                             --complete transaction
                  state <= stop;                 --go to stop bit
                END IF;
              WHEN mstr_ack =>                   --master acknowledge bit after a read
                IF(ena = '1') THEN               --continue transaction
                  busy <= '0';                   --continue is accepted and data received is available on bus
                  addr_rw <= addr & rw;          --collect requested slave address and command
                  data_tx <= data_wr;            --collect requested data to write
                  IF(addr_rw = addr & rw) THEN   --continue transaction with another read
                    sda_int <= '1';              --release sda from incoming data
                    state <= rd;                 --go to read byte
                  ELSE                           --continue transaction with a write or new slave
                    state <= start;              --repeated start
                  END IF;    
                ELSE                             --complete transaction
                  state <= stop;                 --go to stop bit
                END IF;
              WHEN stop =>                       --stop bit of transaction
                busy <= '0';                     --unflag busy
                state <= ready;                  --go to idle state
            END CASE;    
          ELSIF(data_clk = '0' AND data_clk_prev = '1') THEN  --data clock falling edge
            CASE state IS
              WHEN start =>                  
                IF(scl_ena = '0') THEN                  --starting new transaction
                  scl_ena <= '1';                       --enable scl output
                  ack_error <= '0';                     --reset acknowledge error output
                END IF;
              WHEN slv_ack1 =>                          --receiving slave acknowledge (command)
                IF(sda /= '0' OR ack_error = '1') THEN  --no-acknowledge or previous no-acknowledge
                  ack_error <= '1';                     --set error output if no-acknowledge
                END IF;
              WHEN rd =>                                --receiving slave data
                data_rx(bit_cnt) <= sda;                --receive current slave data bit
              WHEN slv_ack2 =>                          --receiving slave acknowledge (write)
                IF(sda /= '0' OR ack_error = '1') THEN  --no-acknowledge or previous no-acknowledge
                  ack_error <= '1';                     --set error output if no-acknowledge
                END IF;
              WHEN stop =>
                scl_ena <= '0';                         --disable scl
              WHEN OTHERS =>
                NULL;
            END CASE;
          END IF;
        END IF;
      END PROCESS;  
    
      --set sda output
      data_clk_m<=data_clk_prev and data_clk;
      WITH state SELECT
        sda_ena_n <= data_clk WHEN start,     --generate start condition
                     NOT data_clk_m WHEN stop,  --generate stop condition
                     sda_int WHEN OTHERS;     --set to internal sda signal    
          
      --set scl and sda outputs
      scl <= '0' WHEN (scl_ena = '1' AND scl_clk = '0') ELSE 'Z';
      sda <= '0' WHEN sda_ena_n = '0' ELSE 'Z';
      
    END logic;
    Code:
    ----------------------------------------------------------------------------------
    --   FileName:         i2c_controller.vhd
    ----------------------------------------------------------------------------------
    
    library IEEE;
    use IEEE.STD_LOGIC_1164.ALL;
    use IEEE.std_logic_unsigned.all;
    
    entity i2c_controller is
    	Generic (slave_addr : std_logic_vector(6 downto 0) := "1110010");
        Port ( Clock : in STD_LOGIC;
               dataIn : in STD_LOGIC_VECTOR (15 downto 0);
               oSDA : inout STD_LOGIC;
               oSCL : inout STD_LOGIC);
    end i2c_controller;
    
    architecture Behavioral of i2c_controller is
    
    component i2c_master IS
      GENERIC(
        input_clk : INTEGER := 50_000_000; --input clock speed from user logic in Hz
        bus_clk   : INTEGER := 400_000);   --speed the i2c bus (scl) will run at in Hz
      PORT(
        clk       : IN     STD_LOGIC;                    --system clock
        reset_n   : IN     STD_LOGIC;                    --active low reset
        ena       : IN     STD_LOGIC;                    --latch in command
        addr      : IN     STD_LOGIC_VECTOR(6 DOWNTO 0); --address of target slave
        rw        : IN     STD_LOGIC;                    --'0' is write, '1' is read
        data_wr   : IN     STD_LOGIC_VECTOR(7 DOWNTO 0); --data to write to slave
        busy      : OUT    STD_LOGIC;                    --indicates transaction in progress
        data_rd   : OUT    STD_LOGIC_VECTOR(7 DOWNTO 0); --data read from slave
        ack_error : BUFFER STD_LOGIC;                    --flag if improper acknowledge from slave
        sda       : INOUT  STD_LOGIC;                    --serial data output of i2c bus
        scl       : INOUT  STD_LOGIC);                   --serial clock output of i2c bus
    END component i2c_master;
    
    signal regBusy,sigBusy,reset,enable,readwrite,nack : std_logic;
    signal regData : std_logic_vector(15 downto 0);
    signal dataOut : std_logic_vector(7 downto 0);
    signal byteChoice : integer := 1;
    signal byteChoiceMax : integer := 13; 
    signal initialCount : integer := 0;
    type state_type is (start,write,stop);
    signal State : state_type := start;
    signal address : std_logic_vector(6 downto 0);
    signal Cnt : integer := 16383;
    
    begin
    
    output: i2c_master
    port map (
        clk=>Clock,
        reset_n=>reset,
        ena=>enable,
        addr=>address,
        rw=>readwrite,
        data_wr=>dataOut,
        busy=>sigBusy,
        data_rd=>OPEN,
        ack_error=>nack,
        sda=>oSDA,
        scl=>oSCL);
    	
    
    StateChange: process (Clock)
    begin
    	if rising_edge(Clock) then
    		case State is
    			when start =>
    			if Cnt /= 0 then
    				Cnt<=Cnt-1;
    				reset<='0';
    				State<=start;
    				enable<='0';
    			else
    				reset<='1';
    				enable<='1';
    				address<=slave_addr;
    				readwrite<='0';
    				State<=write;
    			end if;
    			
    			when write=>
    			regBusy<=sigBusy;
    			regData<=dataIn;
    			if regBusy/=sigBusy and sigBusy='0' then
    				if byteChoice /= byteChoiceMax then
    					byteChoice<=byteChoice+1;
    					State<=write;
    				else
    					byteChoice<=8;
    					State<=stop;
    				end if;
    			end if;
    			
    			when stop=>
    			enable<='0';
    			if regData/=dataIn then
    				State<=start;
    			else
    				State<=stop;
    			end if;
    		end case;
    	end if;
    end process;
    
    process(byteChoice,Clock)
    begin
        case byteChoice is
            when 1 => dataOut <= x"76";
            when 2 => dataOut <= x"76";
            when 3 => dataOut <= x"76";
            when 4 => dataOut <= x"7A";
            when 5 => dataOut <= x"FF";
            when 6 => dataOut <= x"77";
            when 7 => dataOut <= x"00";
            when 8 => dataOut <= x"79";
            when 9 => dataOut <= x"00";
            when 10 => dataOut <= x"0" & dataIn(15 downto 12);
            when 11 => dataOut <= x"0" & dataIn(11 downto 8);
            when 12 => dataOut <= x"0" & dataIn(7 downto 4);
            when 13 => dataOut <= x"0" & dataIn(3 downto 0);
            when others => dataOut <= x"76";
        end case;
    end process;
    
    end Behavioral;
    And this last code is which is my top-level entity.
    Code:
    LIBRARY ieee;
    USE ieee.std_logic_1164.all;
    USE ieee.std_logic_unsigned.all;
    
    
    entity pruebaI2C is
    	port(
    		FPGA_CLK1_50: in  	std_logic; 
    		SDA 			: inout 	std_logic;
    		SCL			: inout 	std_logic
    	);
    end pruebaI2C;
    
    architecture a of pruebaI2C is
    
    component i2c_controller is
    	Generic (slave_addr : std_logic_vector(6 downto 0) := "1110010");
        Port ( Clock : in STD_LOGIC;
               dataIn : in STD_LOGIC_VECTOR (15 downto 0);
               oSDA : inout STD_LOGIC;
               oSCL : inout STD_LOGIC);
    end component;
    
    	signal Clock, oSDA, oSCL : std_logic;
    	signal slave_addr : std_logic_vector(6 downto 0);
    	signal dataIn : std_logic_vector(15 downto 0);
    	signal cont : std_logic_vector(31 downto 0);
    
    
    begin
    
    	inst1 : i2c_controller
    		--GENERIC MAP (slave_addr => slave_addr)
    		PORT MAP(
    			Clock => Clock,
    			dataIn => dataIn,
    			oSDA => oSDA,
    			oSCL => oSCL
    		);
    		
    		process (FPGA_CLK1_50)
    		begin
    			if (cont < "11111111111111111111111100000000") then
    				dataIn<="0000001101010101";
    			else 
    				dataIn<="0000000111110000";
    			end if;
    			if (cont = "11111111111111111111111111111111") then
    				cont <= "00000000000000000000000000000000";
    			end if;
    		end process;
    
    
    end a;
    As I have readed in the thread I commented before, this code doesn't generate de clock signal, but I don't know how to do this right.

    Thank you in advance. I’m looking forward to your replies.

  2. #2
    Super Moderator
    Points: 66,847, Level: 63
    Achievements:
    7 years registered
    Awards:
    Most Frequent Poster 3rd Helpful Member

    Join Date
    Apr 2014
    Posts
    13,644
    Helped
    3122 / 3122
    Points
    66,847
    Level
    63

    Re: Implement I2C in VHDL

    Hi,

    At first you should be aware of hardware, wiring and timing.
    Draw a timing chart... how you expect the signals to be.
    I've seen you copied code, but I don't see any effort on your own.

    At least you should use a scope to see the current signals, their voltage levels and their timing.
    Then describe what detail (only one detail, step by step) what you want to modify.

    Then try to modify the existign code and use a scope to see what happens.

    Klaus
    Last edited by KlausST; 8th November 2018 at 16:22. Reason: spelling
    Please donīt contact me via PM, because there is no time to respond to them. No friend requests. Thank you.



    •   AltAdvertisment

        
       

  3. #3
    Junior Member level 1
    Points: 97, Level: 1

    Join Date
    Oct 2018
    Posts
    18
    Helped
    0 / 0
    Points
    97
    Level
    1

    Re: Implement I2C in VHDL

    I have been thinking about it for 1 week and I still have no idea why it is not working.

    I want to work with the 50Mhz clock of the FPGA and generate a 100kHz pulse for the SCL. When I connect the oscilloscope I see a clock signal with the duty cycle in the "edge", close to 0% of duty. And the frequency uses to be about 30Mhz, I still don't know why does this happen. The voltage is around 1.3V.

    For my application I know what I want to do and receive.
    Start signal -> address+write bite->ACK->register to config the GPIO Pins (03h)->ACK->DATA(8bits)->ACK->stop (This configure the pins I/O of the expander)
    Start signal -> address+write bite->ACK->register to config the write (01h)->ACK->DATA(8bits)->ACK->stop (This puts the pins to 1 or 0)

    If I'm asking is because I don't know where to coninue and I am stucked.



  4. #4
    Super Moderator
    Points: 66,847, Level: 63
    Achievements:
    7 years registered
    Awards:
    Most Frequent Poster 3rd Helpful Member

    Join Date
    Apr 2014
    Posts
    13,644
    Helped
    3122 / 3122
    Points
    66,847
    Level
    63

    Re: Implement I2C in VHDL

    Hi,

    "Thinking for a week" wonīt help.
    You need to systematically analyze it.

    You work on FPGa, thus I assume - and strongly recommend - to use the simulator.

    ***

    Real world:
    --> show a scope picture. It says more than all the words written above.

    Then generate extra signals just to analyze your internal signal flow. Feed them to pins and take extra scope pictures.

    ***
    The voltage is around 1.3V.
    What voltage at whick signal.
    Surely 1.3V is no valid HIGH level for I2C signals. If you didnīt know this before you need to read I2C specifications.
    If this "1.3V" is not the HIGH level, then what else? An average value measured with a DVM? --> useless

    For my application I know what I want to do and receive.
    Yes, thatīs the target task.
    But you need to divide the problem in smaller parts. Then these smaller parts in even smaller parts.

    *****
    Generting clock basics:
    If you want to generate 400kHz from a 50MHz clock:
    50MHz means a period time of 20ns
    400kHz means a priod time of 2500ns, but you need to generate a clock edge every 1250ns.

    1250ns / 20ns = 62.5
    I recommend to use the next highest integer value = 63 (itīs not exactly 400kHz but itīs not important. You may run the I2C with slower clock frequency, 400kHz is the usual upper limit)
    Use a binary counter to count from 0 to 62 (this are 63 steps).
    On "62": toggle the output clock signal, and prepare the "synchronous reset" of the counter on the next incoming clock.

    Klaus
    Please donīt contact me via PM, because there is no time to respond to them. No friend requests. Thank you.



  5. #5
    Junior Member level 1
    Points: 97, Level: 1

    Join Date
    Oct 2018
    Posts
    18
    Helped
    0 / 0
    Points
    97
    Level
    1

    Re: Implement I2C in VHDL

    I know that theory, but...

    The CLK_50 I generate it on my top-level entity. Must I generate also the SCL @ 400kHz on the top-level entity or does it go on another component?
    I thought with that IP core I just had to put my clk and it would calculate the parameters to generate the SCL and to send the data on time.



  6. #6
    Super Moderator
    Points: 66,847, Level: 63
    Achievements:
    7 years registered
    Awards:
    Most Frequent Poster 3rd Helpful Member

    Join Date
    Apr 2014
    Posts
    13,644
    Helped
    3122 / 3122
    Points
    66,847
    Level
    63

    Re: Implement I2C in VHDL

    Hi,

    does it go on another component?
    It will work on any other component, too.
    this 400kHz clock does not need to be treated like the 50MHz clock (with FPGA clock dsitribution...) you may treat it is any other logic signal.
    But it should be synchronized to the 50MHz clock with the use of a FF, not generated from a combinatorial logic. This ensures glitch free signal with predictable alignment to the 50MHz clock.

    But if you need the clock on several components, there may be a benefit to distribute the "enable" for the 400kHz lock instead of the 400kHz clock itself.
    Not always. In detail it depends on the requirements..

    Klaus
    Please donīt contact me via PM, because there is no time to respond to them. No friend requests. Thank you.



  7. #7
    Advanced Member level 4
    Points: 7,808, Level: 21
    Achievements:
    7 years registered Created Blog entry
    dpaul's Avatar
    Join Date
    Jan 2008
    Location
    Germay
    Posts
    1,161
    Helped
    252 / 252
    Points
    7,808
    Level
    21
    Blog Entries
    1

    Re: Implement I2C in VHDL

    If I'm asking is because I don't know where to coninue and I am stucked
    Have you simulated your complete design (I2C master is there and a slave model exists) using the exact clocks?
    If not first focus there!

    Connect this https://github.com/oetr/FPGA-I2C-Slave to you I2C controller and observe the read/writes.
    Last edited by dpaul; 16th October 2018 at 13:29.
    FPGA enthusiast!



    •   AltAdvertisment

        
       

  8. #8
    Junior Member level 1
    Points: 97, Level: 1

    Join Date
    Oct 2018
    Posts
    18
    Helped
    0 / 0
    Points
    97
    Level
    1

    Re: Implement I2C in VHDL

    Quote Originally Posted by dpaul View Post
    Have you simulated your complete design (I2C master is there and a slave model exists) using the exact clocks?
    If not first focus there!

    Connect this https://github.com/oetr/FPGA-I2C-Slave to you I2C controller and observe the read/writes.
    I have not simulated it with an slave, but I did with a testbench of my own. I will try your way.

    Also, if anybody has a working IP Core for I2C FPGA I'd be glad if you post it. Thanks.

    - - - Updated - - -

    i2c_master_slave_tb.zip
    This is what I have tried, but I'm getting errors and can't run my TB. Any help?



    •   AltAdvertisment

        
       

  9. #9
    Junior Member level 1
    Points: 97, Level: 1

    Join Date
    Oct 2018
    Posts
    18
    Helped
    0 / 0
    Points
    97
    Level
    1

    Re: Implement I2C in VHDL

    After no finding solution with the code I shared here, I have made all the process on my own and I have created my implementation.
    On this test I just want to write a value on the output, I still have to do the reading task for I2C, but it will be after finishing the writting part.

    The problem is that on my simulation it seems to work fine, but when I program it on the FPGA, it doesn't works at all. Anyway, I can see the signals on the oscilloscope.

    Code:
    library IEEE;
    use IEEE.STD_LOGIC_1164.ALL;
    use IEEE.STD_LOGIC_UNSIGNED.ALL;
    use ieee.numeric_std.all;
    
    
    entity masterI2C is
        Port( 
    		CLK_50 	: IN  STD_LOGIC;					--50MHz Clock
    		RST 	: IN  STD_LOGIC;
    		ADD	: IN  STD_LOGIC_VECTOR (7 DOWNTO 0); 	--Address
    		COM	: IN  STD_LOGIC_VECTOR (7 DOWNTO 0); 	--Command
    		DAT	: IN  STD_LOGIC_VECTOR (7 DOWNTO 0); 	--Data
    		GO	: IN  STD_LOGIC;						--Start signal.
    		BUSY	: OUT STD_LOGIC;					--Working. Can't attend new queries.
    		SCLK	: OUT STD_LOGIC;					--CLK Signal for sync. 100kHz.
    		SDAT	: INOUT STD_LOGIC);					--Generated trace.
    end masterI2C;
    
    
    architecture a of masterI2C is
    
    	--Status
    	TYPE estados is (e0, e1, e2, e3, e4, e5, e6, e7, e8);
    	SIGNAL ep : estados :=e0; 	--Actual Status.
    	SIGNAL es : estados; 		--Next Status.
    	
    	--State Machine control signals
    	signal idle 	: std_logic := '0';
    	signal start 	: std_logic := '0';
    	signal config	: std_logic := '0';
    	signal cmd	: std_logic := '0';
    	signal data	: std_logic := '0';
    	signal ack 	: std_logic := '0';
    	signal stop	: std_logic := '0';
    	signal conf_rdy : std_logic := '0';
    	signal cmd_rdy	: std_logic := '0';
    	signal data_rdy : std_logic := '0';
    	signal ack_st	: std_logic := '0';				--Acknowledge status.
    	signal cBits	: integer range -1 to 7 :=7; 	--Bit counter.
    	
    
    	--Clk generation
    	signal clk200k 	: std_logic :='0'; --200kHz clock. Used to create the start/stop conditions.
    	signal clk100k	: std_logic :='0'; --100kHz clock. SCL signal for I2C protocol.
    	signal cont	: std_logic_vector(7 downto 0) :="00000000";
    	
    	--Seņales de datos para la placa
    	signal data_addr	: std_logic_vector(7 downto 0); --Device Address(7 bits) + "Write" flag.
    	signal data_cmd		: std_logic_vector(7 downto 0); --Command.
    	signal data_info	: std_logic_vector(7 downto 0); --Command associated data.
    	
    	--Otras seņales
    	signal reset 	: std_logic;
    	signal sdat_gen : std_logic:='1'; --Generated trace for SDAT.
    	signal hold 	: std_logic:='1'; --Keeps the value in SDAT line during a cycle of SCLK.
    
    
    begin
    
    	reset	  <= RST;
    	data_addr <= ADD;
    	data_cmd  <= COM;
    	data_info <= DAT;
    
    	--Maquina de estados
    	PROCESS(ep, reset, GO, conf_rdy, cmd_rdy, data_rdy)
    	BEGIN
    		IF(reset='1')then
    			es<=e0;
    		ELSE
    			CASE ep IS
    				WHEN e0 =>				--Wait status.
    					IF(GO='1')THEN
    						es<=e1;
    					ELSE
    						es<=e0;
    					END IF;
    				WHEN e1=>				--Start condition call.
    					es<=e2;
    				WHEN e2=>				--Configurate
    					IF(conf_rdy='1')THEN
    						es<=e3;
    					ELSE
    						es<=e2;
    					END IF;
    				WHEN e3 =>				--ACK
    					--I'm ignoring the ACK.
    					IF(ack='1')THEN
    						es<=e4;
    					ELSE
    						es<=e4;
    					END IF;
    				WHEN e4=>				--CMD
    					IF(cmd_rdy='1')THEN
    						es<=e5;
    					ELSE
    						es<=e4;
    					END IF;
    				WHEN e5=>				--ACK
    					IF(ack='1')THEN
    						--Same, ignoring the ACK.
    						es<=e6;
    					ELSE
    						es<=e6;
    					END IF;
    				WHEN e6=>				--DATA
    					IF(data_rdy='1')THEN
    						es<=e7;
    					ELSE
    						es<=e6;
    					END IF;
    				WHEN e7=>				--ACK
    					IF(ack='1')THEN
    						es<=e8;
    					ELSE
    						es<=e8;
    					END IF;
    				WHEN e8=>				--Stop condition call.
    					es<=e0;
    			END CASE;
    		END IF;
    	END PROCESS;
    	
    	--Status change
    	PROCESS(clk100k)
    	BEGIN
    		if(clk100k='1')then
    			ep<=es;
    		end if;
    	END PROCESS;
    	
    	--Control signals
    	idle		<='1' when ep=e0 else '0';
    	start		<='1' when ep=e1 else '0';
    	config 		<='1' when ep=e2 else '0';
    	cmd		<='1' when ep=e4 else '0';
    	data	 	<='1' when ep=e6 else '0';
    	ack_st		<='1' when ep=e3 or ep=e5 or ep=e7 else '0';
    	stop		<='1' when ep=e8 else '0';
    
    	--200kHz clock generation from 50 MHz
    	process (CLK_50)
    	begin
    		if (rising_edge(CLK_50)) then
    			if (cont<"1111101")then --Si es menor que 125
    				clk200k<='0';
    				cont<=cont+'1';
    			else
    				clk200k<='1';
    				cont<=cont+'1';
    			end if;
    			if(cont="11111001")then 
    				cont<="00000000";
    			end if;
    		end if;
    	end process;
    
    	--100kHz clock (SCLK)
    	process(clk200k)
    	begin
    		if(rising_edge(clk200k))then
    			clk100k<=not(clk100k);
    		end if;
    	end process;
    	
    	--Control Unit
    	process (CLK_50)	--In this process I generate SDAT signal.
    	begin
    		if(rising_edge(CLK_50))then
    			if(start='1')then					--Start condition.
    				if(clk100k='1')then
    					if(clk200k='0')then
    						sdat_gen<='0';
    					end if;
    				end if;
    			elsif(config='1')then				--vector "address" to sda_gen std_logic.
    				if(clk100k='1')then
    					if(clk200k='0')then
    						sdat_gen<=data_addr(cBits);
    						hold<='0';
    					end if;
    				elsif(clk100k='0' and cBits>-1 and hold='0')then
    					cBits<=cBits-1;
    					hold<='1';
    				end if;
    				if(cBits=-1)then
    					cBits<=7;
    					conf_rdy<='1';
    				end if;
    			elsif(cmd='1')then
    				if(clk100k='1')then
    					if(clk200k='0')then
    						sdat_gen<=data_cmd(cBits);
    						hold<='0';
    					end if;
    				elsif(clk100k='0' and cBits>-1 and hold='0')then
    					cBits<=cBits-1;
    					hold<='1';
    				end if;
    				if(cBits=-1)then
    					cBits<=7;
    					cmd_rdy<='1';
    				end if;
    			elsif(data='1')then
    				if(clk100k='1')then
    					if(clk200k='0')then
    						sdat_gen<=data_info(cBits);
    						hold<='0';
    					end if;
    				elsif(clk100k='0' and cBits>-1 and hold='0')then
    					cBits<=cBits-1;
    					hold<='1';
    				end if;
    				if(cBits=-1)then
    					cBits<=7;
    					data_rdy<='1';
    				end if;
    			elsif(ack_st='1')then
    				if(clk200k='0')then
    					sdat_gen<='0';
    				end if;
    				--QUESTION: ACK comes from device, which value should be sdat_gen here?
    			elsif(stop='1')then				--Stop condition
    				conf_rdy<='0';
    				cmd_rdy<='0';
    				data_rdy<='0';
    				if(clk100k='1')then
    					if(clk200k='0')then
    						sdat_gen<='1';
    					end if;
    				end if;
    			end if;
    		end if;
    	end process;
    		
    	--Outputs
    	SDAT 	<= sdat_gen;
    	SCLK 	<= clk100k;
    	BUSY	<= not(idle);
    
    end a;
    This is my master. I commented it and there is a doubt about the ACK, I don't know what to do with it. I'm not even trying to caught the information from the device, I just wanna write on it.

    And here is the code which invokes the master:

    Code:
    library IEEE;
    use IEEE.STD_LOGIC_1164.ALL;
    use IEEE.STD_LOGIC_UNSIGNED.ALL;
    use ieee.numeric_std.all;
    
    ENTITY pruebaI2C is
    	PORT(
    			FPGA_CLK1_50 	: IN STD_LOGIC;
    			KEY		: IN STD_LOGIC_VECTOR(0 DOWNTO 0);
    			SCL		: OUT STD_LOGIC;
    			SDA		: INOUT STD_LOGIC);	
    END pruebaI2C;
    
    architecture a of pruebaI2C is
    
    	COMPONENT masterI2C is
       	 	Port( 
    			CLK_50 	: IN  STD_LOGIC;
    			RST 	: IN  STD_LOGIC;
    			ADD		: IN  STD_LOGIC_VECTOR (7 DOWNTO 0); --Address
    			COM		: IN  STD_LOGIC_VECTOR (7 DOWNTO 0); --Command
    			DAT		: IN  STD_LOGIC_VECTOR (7 DOWNTO 0); --Data
    			GO		: IN  STD_LOGIC;
    			BUSY	: OUT STD_LOGIC;
    			SCLK	: OUT STD_LOGIC;
    			SDAT	: INOUT STD_LOGIC
    			);
    	end COMPONENT;
    
    
    	--Estados
    	TYPE estados is (e0, e1, e2);
    	SIGNAL ep : estados :=e0;
    	SIGNAL es : estados; 
    	
    
    	signal command, data : std_logic_vector(7 downto 0);
    	
    	--Senales de datos para la placa
    	signal cmd_config	: std_logic_vector(7 downto 0) := "00000011"; 	--Device configuration command (03h)
    	signal cmd_write	: std_logic_vector(7 downto 0) := "00000001"; 	--Device write command (01h)
    	signal data_addr	: std_logic_vector(7 downto 0) := "11100100"; 	--Device Address + write flag
    	signal data_ports	: std_logic_vector(7 downto 0) := "00001111"; 	--Definition of ports on device to be output and input. (0=out; 1=in;) Device is GPIO expander.
    	signal data_out		: std_logic_vector(7 downto 0) := "10101010"; 	--Ports to write value.
    	signal reset		: std_logic:='0';
    	signal wait_count 	: std_logic_vector(13 downto 0):="00000000000000";
    	signal change		: std_logic;
    	SIGNAL go			: std_logic;
    	signal ocupado		: std_logic;
    	signal espera		: std_logic;
    	signal cont			: std_logic_vector (6 downto 0):="0000000";
    	signal clk100k		: std_logic;
    
    BEGIN
    
    	reset <= NOT(KEY(0));
    
    	PROCESS(ep, reset, change)
    	BEGIN
    		IF(reset='1')then
    			es<=e0;
    		ELSE
    			CASE ep IS
    				WHEN e0 =>
    					command<=cmd_config;
    					data<=data_ports;
    					es<=e1;
    				WHEN e1 =>
    					if(change='1')then
    						es<=e2;
    					end if;
    				WHEN e2 =>
    					command<=cmd_write;
    					data<=data_out;
    					es<=e2;
    			END CASE;
    		END IF;
    	END PROCESS;
    	espera<='1' when ep=e1 else '0';
    	go<='1' when ocupado='0' else '0';
    
    	PROCESS (FPGA_CLK1_50)
    	BEGIN
    		--Wait while data is sending
    		IF(rising_edge(FPGA_CLK1_50))THEN
    			IF (espera='1')THEN
    				IF(wait_count<"11110010001011")THEN
    					wait_count<=wait_count+'1';
    					change<='0';
    				ELSE
    					wait_count<="00000000000000";
    					change<='1';
    				END IF;
    			END IF;
    		END IF;
    	END PROCESS;
    
    
    	PROCESS(clk100k)
    	BEGIN
    		if(clk100k='1')then
    			ep<=es;
    		end if;
    	END PROCESS;
    
    
    	inst1: masterI2C
        	Port Map( 
    		CLK_50 	=> FPGA_CLK1_50,
    		RST 	=> reset,
    		ADD 	=> data_addr,
    		COM	=> command,
    		DAT 	=> data,
    		GO	=> go,
    		BUSY	=> ocupado,
    		SCLK	=> clk100k,
    		SDAT	=> SDA
    	);
    
    	SCL <= clk100k;
    
    END a;
    I have also been awared of the timings, and I am sending it with an offset in respect the SCL so the device reads on the middle of the cycle.
    On the simulation I see the following:

    Click image for larger version. 

Name:	chronogram.PNG 
Views:	3 
Size:	59.6 KB 
ID:	149947

    I think the chronogram is right and my device should be working, but it isn't. Can anybody throw some light?

    Thx in advance one more time.



  10. #10
    Junior Member level 1
    Points: 97, Level: 1

    Join Date
    Oct 2018
    Posts
    18
    Helped
    0 / 0
    Points
    97
    Level
    1

    Re: Implement I2C in VHDL

    Quote Originally Posted by KlausST View Post
    I see SDA is LOW and HIGH, but in simulation it should be LOW and OPEN (HIGH-Z), never HIGH
    So you mean '1' is 'Z'?
    Is high-impedance synthesizable?



  11. #11
    Super Moderator
    Points: 66,847, Level: 63
    Achievements:
    7 years registered
    Awards:
    Most Frequent Poster 3rd Helpful Member

    Join Date
    Apr 2014
    Posts
    13,644
    Helped
    3122 / 3122
    Points
    66,847
    Level
    63

    Re: Implement I2C in VHDL

    Hi,

    I didnīt go through your code, but I had a look at your simulation picture.

    I see SDA is LOW and HIGH, but in simulation it should be LOW and OPEN (HIGH-Z), never HIGH
    I expect to that the simulation can show HIGH-Z state.

    Klaus

    - - - Updated - - -


    **************
    Sorry for the wrong order of posts....
    **************


    Hi,

    Iīm not familiar with VHDL...
    But used an internet search...

    What I found is this:

    https://forums.xilinx.com/t5/General...DL/td-p/301275

    and within this discussion there is code with this line:

    Code:
    sda <= 'Z' when sda01 = '1' else '0'; -- converts sda01 from 0 or 1 to 0 or Z
    												  -- Z being high impedence
    How long did it take to find this...

    Klaus
    Please donīt contact me via PM, because there is no time to respond to them. No friend requests. Thank you.



  12. #12
    Junior Member level 1
    Points: 97, Level: 1

    Join Date
    Oct 2018
    Posts
    18
    Helped
    0 / 0
    Points
    97
    Level
    1

    Re: Implement I2C in VHDL

    Yes, I have already done this after posting, probably I shuld have done it before :/

    Anyway, on the simulation I get what you say, but it still not working at all. I still don't know what I'm doing wrong.

    The message is clear, I do the start condition and I send the adress "1110010"+write bit '0'. Then I wait for ACK in 'Z' but I ignore it, I'm not implementing error comprobations yet.
    After this I send the comand "00000011", which means configure the pins output. It is a GPIO expansor Board, then I send the data to decide hich of them are outputs and inputs.

    In the second row I do the same but with the write command, so I should read 3.3V on my polimeter on the specified outputs which I set to 1 and are active.


    Click image for larger version. 

Name:	chronogram2.PNG 
Views:	3 
Size:	49.3 KB 
ID:	149949



  13. #13
    Super Moderator
    Points: 66,847, Level: 63
    Achievements:
    7 years registered
    Awards:
    Most Frequent Poster 3rd Helpful Member

    Join Date
    Apr 2014
    Posts
    13,644
    Helped
    3122 / 3122
    Points
    66,847
    Level
    63

    Re: Implement I2C in VHDL

    Hi,

    do you use the I2C mandatory pullup resistors at SDA as well as SCL?

    Then I wait for ACK in 'Z'
    You donīt have to "wait", (unless the slave uses clock stretching, which needs additional modification to your code). Itīs just outputting a 9th bit as "Z" while reading in ACK.

    I donīt see this 9th bit to be 'Z' in your simulation.

    Klaus

    added:
    Your aim should be to get ACK=LOW by the slave.
    If you don' t get it then maybe
    * wrong I2C_Start
    * wrong voltage levels
    * wrong timing
    * wrong slave_address
    * wrong/no supply voltag efor the slave
    * defective slave

    Klaus
    Please donīt contact me via PM, because there is no time to respond to them. No friend requests. Thank you.



  14. #14
    Super Moderator
    Points: 249,046, Level: 100
    Awards:
    1st Helpful Member

    Join Date
    Jan 2008
    Location
    Bochum, Germany
    Posts
    43,337
    Helped
    13177 / 13177
    Points
    249,046
    Level
    100

    Re: Implement I2C in VHDL

    I see SDA is LOW and HIGH, but in simulation it should be LOW and OPEN (HIGH-Z), never HIGH
    I expect to that the simulation can show HIGH-Z state.
    You can drive std_logic 'H' value (weak '1') to SDA in the test bench. It overrides 'Z' and will be itself overridden by '0'.



  15. #15
    Super Moderator
    Points: 29,529, Level: 41
    ads-ee's Avatar
    Join Date
    Sep 2013
    Location
    USA
    Posts
    6,819
    Helped
    1621 / 1621
    Points
    29,529
    Level
    41

    Re: Implement I2C in VHDL

    Code VHDL - [expand]
    1
    2
    3
    4
    5
    6
    7
    
    --Status change
        PROCESS(clk100k)
        BEGIN
            if(clk100k='1')then
                ep<=es;
            end if;
        END PROCESS;
    what the heck is this supposed to be? I really doubt a synthesis tool is going to produce a state regisiter with this code.



  16. #16
    Super Moderator
    Points: 249,046, Level: 100
    Awards:
    1st Helpful Member

    Join Date
    Jan 2008
    Location
    Bochum, Germany
    Posts
    43,337
    Helped
    13177 / 13177
    Points
    249,046
    Level
    100

    Re: Implement I2C in VHDL

    Yes, a sufficient explanation why the code doesn't work in hardware. Unfortunately it fakes a register in simulation due to the sensitivity list behavior.

    You may want to run a gate level simulation to see that the register isn't real. Or pay just a little bit of attention to the synthesis warnings that surely come up when you run the FPGA tool.



    •   AltAdvertisment

        
       

  17. #17
    Junior Member level 1
    Points: 97, Level: 1

    Join Date
    Oct 2018
    Posts
    18
    Helped
    0 / 0
    Points
    97
    Level
    1

    Re: Implement I2C in VHDL

    Quote Originally Posted by ads-ee View Post
    Code VHDL - [expand]
    1
    2
    3
    4
    5
    6
    7
    
    --Status change
        PROCESS(clk100k)
        BEGIN
            if(clk100k='1')then
                ep<=es;
            end if;
        END PROCESS;
    what the heck is this supposed to be? I really doubt a synthesis tool is going to produce a state regisiter with this code.
    That piece of code is to change the present status (ep) value. That's the way I learned to do it, what's wrong with it? Is there another way of doing so?
    Be aware that I'm still learning VHDL, so probably I make mistakes.
    Thx.
    Last edited by Ironlord; 9th November 2018 at 08:28.



  18. #18
    Super Moderator
    Points: 249,046, Level: 100
    Awards:
    1st Helpful Member

    Join Date
    Jan 2008
    Location
    Bochum, Germany
    Posts
    43,337
    Helped
    13177 / 13177
    Points
    249,046
    Level
    100

    Re: Implement I2C in VHDL

    That's the way I learned to do it
    I hope not. A register hat to be described by
    Code VHDL - [expand]
    1
    2
    3
    
    if rising_edge(clk) then
      reg <= val;
    end if;



  19. #19
    Junior Member level 1
    Points: 97, Level: 1

    Join Date
    Oct 2018
    Posts
    18
    Helped
    0 / 0
    Points
    97
    Level
    1

    Re: Implement I2C in VHDL

    Code:
    	PROCESS(ep, reset, change, command, data)--I added "when others" statement and "command" and "data" to the sensitive list.
    	BEGIN
    		IF(reset='1')then
    			es<=e0;
    		ELSE
    			CASE ep IS
    				WHEN e0 =>
    					command<=cmd_config;
    					data<=data_ports;
    					es<=e1;
    				WHEN e1 =>
    					if(change='1')then
    						es<=e2;
    					end if;
    				WHEN e2 =>
    					command<=cmd_write;
    					data<=data_out;
    					es<=e2;
    				WHEN OTHERS =>
    					command<=command;
    					data<=data;
    					es<=e1;
    			END CASE;
    		END IF;
    	END PROCESS;
    I changed the code trying to avoid latches, but these warnings appear on my compiler:

    Warning (10631): VHDL Process Statement warning at pruebaI2C.vhd(58): inferring latch(es) for signal or variable "es", which holds its previous value in one or more paths through the process
    Warning (10631): VHDL Process Statement warning at pruebaI2C.vhd(58): inferring latch(es) for signal or variable "command", which holds its previous value in one or more paths through the process
    Warning (10631): VHDL Process Statement warning at pruebaI2C.vhd(58): inferring latch(es) for signal or variable "data", which holds its previous value in one or more paths through the process
    Warning (10540): VHDL Signal Declaration warning at masterI2C.vhd(34): used explicit default value for signal "ack" because signal was never assigned a value
    I try to avoid them and fix the errors, but I don't know what more can I do.
    I also post the capture I got from the oscilloscope. I don't know about which voltage must have nor the SCL nor SDA of I2C protocol. I insist, I'm noob and I'm trying to learn.
    Click image for larger version. 

Name:	hfwytf.jpg 
Views:	2 
Size:	130.1 KB 
ID:	149964



  20. #20
    Advanced Member level 4
    Points: 7,808, Level: 21
    Achievements:
    7 years registered Created Blog entry
    dpaul's Avatar
    Join Date
    Jan 2008
    Location
    Germay
    Posts
    1,161
    Helped
    252 / 252
    Points
    7,808
    Level
    21
    Blog Entries
    1

    Re: Implement I2C in VHDL

    I changed the code trying to avoid latches, but these warnings appear on my compiler:
    Yes because they are not being driven within a clocked process. A clock is missing from the sensitivity list (not sure if it must be there as per your design).

    One question, do you know the difference between writing sequential and combinatorial VHDL code in general?
    Do you know when a register is inferred from a piece of VHDL code in general?
    In the above is not clear to you, I would suggest you to return to these fundamentals first, try working with a simpler design and then work with I2C.
    FPGA enthusiast!



--[[ ]]--