+ Post New Thread
Page 3 of 3 FirstFirst 1 2 3
Results 41 to 56 of 56
  1. #41
    Advanced Member level 3
    Points: 5,683, Level: 17

    Join Date
    Feb 2015
    Posts
    939
    Helped
    269 / 269
    Points
    5,683
    Level
    17

    Re: Implement I2C in VHDL

    Quote Originally Posted by barry View Post
    There is nothing intrinsically better in the 2-process style. Why is the one process a failing?? And "aren't designed for HW development"?? That's EXACTLY what they were designed for; the H in HDL stands for hardware.
    You have next state and next value logic directly in two process style. The one process style make combinatorial outputs -- even used within the same module -- difficult. My point is that the HDLs could support two process but also get rid of most of the boilerplate automatically. It might mean a special class of signals that don't support delays or multiple drivers, but those are mostly for simulation and not used internally.



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

    Re: Implement I2C in VHDL

    You have next state and next value logic directly in two process style. The one process style make combinatorial outputs -- even used within the same module -- difficult. My point is that the HDLs could support two process but also get rid of most of the boilerplate automatically. It might mean a special class of signals that don't support delays or multiple drivers, but those are mostly for simulation and not used internally.
    It would be nice and meaningful if we can stick to the OPs problem and related stuff.
    FPGA enthusiast!



  3. #43
    Super Moderator
    Points: 249,762, Level: 100
    Awards:
    1st Helpful Member

    Join Date
    Jan 2008
    Location
    Bochum, Germany
    Posts
    43,481
    Helped
    13214 / 13214
    Points
    249,762
    Level
    100

    Re: Implement I2C in VHDL

    I don't see a problem of HDL capabilities, you can write whatever you consider readable, e.g. have combinational and registered signal assignments, also different clock edges (using different signals, of course) in a single process. The question is, which hardware structure is appropriate and how can it be efficiently described.



  4. #44
    Junior Member level 3
    Points: 150, Level: 1

    Join Date
    Oct 2018
    Posts
    29
    Helped
    0 / 0
    Points
    150
    Level
    1

    Re: Implement I2C in VHDL

    OP here again bringing some news.

    Yesterday I achieved to send some info to the I2C GPIO Expander. The problem was I had the "repeated start" continuosly, but I made the communication and it was outputing voltage through the GPIO pins, so the ports were configurated and the value too.

    Today I fixed that and I have a working Stop Condition, but I see some glitches I will try to correct.
    From now I will implement the read function, but at least I continue advancing on the project, so great. I will come back for more doubts or to post my work.

    Cya soon and thanks for your help and support.

    Click image for larger version. 

Name:	CapturaI2C_Envio.PNG 
Views:	7 
Size:	21.5 KB 
ID:	150034



    •   AltAdvertisment

        
       

  5. #45
    Junior Member level 3
    Points: 150, Level: 1

    Join Date
    Oct 2018
    Posts
    29
    Helped
    0 / 0
    Points
    150
    Level
    1

    Re: Implement I2C in VHDL

    OP here again. I'm messing it with the tempos and I don't know why. Could anybody throw some light? I have been working on it but I don't realize what am I doing wrong.
    From the last time I posted here I have generated a reader and mixed both modules into one, to create the I2C-master. Now I can write and read from the board in the same module, but my algorithm is not well implemented.
    Here I post two chronograms I have taken.
    Click image for larger version. 

Name:	1_.PNG 
Views:	4 
Size:	35.8 KB 
ID:	150175
    On this picture I marked those black edges. It is part of the ACK from the board. But it should be on more places, I marked it with an interrogation symbol (?).
    Click image for larger version. 

Name:	2_.PNG 
Views:	2 
Size:	26.9 KB 
ID:	150176
    On this one I see the clock desynchronizes, I still don't know why.
    I uploaded my code to github, just in case you want to see it.
    Moderator action: deleted links to external severs
    Thanks in advance.
    Last edited by KlausST; 21st November 2018 at 11:00. Reason: deleted links to external servers



  6. #46
    Junior Member level 3
    Points: 150, Level: 1

    Join Date
    Oct 2018
    Posts
    29
    Helped
    0 / 0
    Points
    150
    Level
    1

    Re: Implement I2C in VHDL

    I posted the code on GitHub because I thought it could help the list of modifications, anyway, if it is forbidden to post links to external sites, I will respect that.
    This is my code:

    Master
    Code:
    library IEEE;
    use IEEE.STD_LOGIC_1164.ALL;
    use IEEE.STD_LOGIC_UNSIGNED.ALL;
    use ieee.numeric_std.all;
    
    
    entity masterI2C_RW is
        Port( 
    		CLK_50: IN  STD_LOGIC;								--Reloj a 50Mhz
    		RST 	: IN  STD_LOGIC;
    		ADD	: IN  STD_LOGIC_VECTOR (6 DOWNTO 0); 	--Address: Dirección del dispositivo.
    		COM	: IN  STD_LOGIC_VECTOR (7 DOWNTO 0); 	--Command: Tipo de orden a enviar.
    		DAT	: IN  STD_LOGIC_VECTOR (7 DOWNTO 0); 	--Data: Información a enviar.
    		GO		: IN  STD_LOGIC;								--Inicio.
    		RW		: IN  STD_LOGIC;								--Bit de L/E. 0=Write; 1=Read;
    		SPEED : IN  STD_LOGIC;								--Velocidad de reloj. 0=100kHz; 1=400kHz.
    		RDNUM : IN  INTEGER;									--Numero de registros a leer.
    		BUSY	: OUT STD_LOGIC;								--Ocupado. No puede atender nuevas peticiones.
    		DOUT	: OUT STD_LOGIC_VECTOR (7 DOWNTO 0);	--Salida de datos. Cuando hay operación de lectura saca ese resultado.
    		SCLK	: OUT STD_LOGIC;								--Señal de reloj para la sincronización del I2C. 100kHz.
    		SDAT	: INOUT STD_LOGIC);							--Señal de datos generada. Trama a enviar al dispositivo.
    end masterI2C_RW;
    
    
    architecture a of masterI2C_RW is
    
    	--Estados
    	TYPE estados is (e0, e1, e2, e3, e4, e5, e6, e7, e8);
    	SIGNAL ep : estados :=e0; 	--Estado Presente
    	SIGNAL es : estados; 		--Estado Siguiente
    	
    	--Señales de control de la máquina de estados
    	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_tx	: std_logic := '0';				--Acknowledge transmission. Estado de ACK en el que el MAESTRO tiene que responder.
    	signal ack_rx	: std_logic := '0';				--Acknowledge reception. Estado de ACK en el que el ESCLAVO tiene que responder.
    	signal cBits	: integer range -1 to 7 :=7; 	--Contador de bits.
    	signal sec		: std_logic := '0';				--Senal para saber si es la segunda vez que pasa(RS-Repeated Start)
    	
    
    	--Generación de relojes
    	signal scl2x 	: std_logic :='0'; 									--Reloj al doble de la frecuencia de scl1x. Se usa para hacer las condiciones de inicio/parada y trama.
    	signal scl1x	: std_logic :='0'; 									--Reloj de 100 o 400 kHz. Para la sincronización. Señal SCL.
    	signal cont		: std_logic_vector(7 downto 0) :="00000000";	--Lo uso para contar ciclos a la hora de hacer el reloj de 2x.
    	signal halfCont: std_logic_vector(7 downto 0) :="01111101"; --Mitad del contador. Ciclo a '1'. Inicializado a 125.
    	signal maxCont : std_logic_vector(7 downto 0) :="11111001"; --Máximo del contador. Indica cuando reiniciar. Inicializado a 249.
    	
    	--Señales de datos para la placa
    	signal data_addr	: std_logic_vector(7 downto 0); --Dirección de la placa y bit de escritura.
    	signal data_cmd	: std_logic_vector(7 downto 0); --Comando.
    	signal data_info	: std_logic_vector(7 downto 0); --Datos asociados al comando.
    	signal data_reg	: std_logic_vector(7 downto 0); --Datos que recibo del dispositivo.
    	signal le			: std_logic;						  --Lectura/Escritura.
    	
    	--Otras señales
    	signal reset 	 : std_logic;
    	signal sdat_gen : std_logic:='1'; --Trama generada para SDAT.
    	signal hold 	 : std_logic:='1'; --Para ir al ritmo de SCLK y no del CLK. Mantiene el valor en el canal SDAT lo que dure el ciclo de SCLK.
    	signal stopped  : std_logic:='0';
    	signal started  : std_logic:='0';
    	signal terminate: std_logic:='0';
    	signal ack_rdy  : std_logic:='0'; --Para detener el ack durante un ciclo.
    	signal count100 : std_logic_vector(8 downto 0):="000000000";
    	signal fullCont : std_logic_vector(8 downto 0):="111110011"; --499. El ciclo completo de 100kHz
    	signal rdnReg	 : integer range 0 to 9:=0;		--"Readen Registers". Numero de registros leidos.
    
    begin
    
    	--IDEA: Igual estas senales tienen que cambiar solo cuando la maquina este disponible, para que no se modifiquen en mitad de una operacion.
    	reset	  	 <= RST;
    	--data_addr <= ADD; Esto va mas abajo, tengo que cambiar el ultimo bit.
    	data_cmd  <= COM;
    	data_info <= DAT;
    	le <= RW;
    
    	--Maquina de estados
    	PROCESS(ep, reset, GO, conf_rdy, cmd_rdy, data_rdy, ack, stopped, sec, started, ack_rdy, le)--He añadido ACK a la lista de sensibilidad.
    	BEGIN
    		IF(reset='1')then
    			es<=e0;
    		ELSE
    			CASE ep IS
    				WHEN e0 =>				--Estado de espera
    					IF(GO='1')THEN
    						es<=e1;
    					ELSE
    						es<=e0;
    					END IF;
    				WHEN e1=>					--Llamada para generar la condición de inicio
    					IF(started='1')THEN	--Started se pone a '1' cuando ha hecho la condicion de inicio.
    						es<=e2;
    					ELSE
    						es<=e1;
    					END IF;
    				WHEN e2=>				--Address+RW. Envio la direccion y la operacion a realizar (leer o escribir).
    					IF(conf_rdy='1')THEN
    						es<=e3;
    					ELSE
    						es<=e2;
    					END IF;
    				WHEN e3 =>				--ACK
    					IF(ack_rdy='1')THEN
    						--IF(ACK='0')THEN
    							IF(le='0')THEN --Escritura
    								es<=e4;
    							ELSE				--Lectura
    								IF(sec='0')THEN
    									es<=e4;
    								ELSE
    									es<=e6;
    								END IF;
    							END IF;
    						--ELSE
    							--es<=e8;
    						--END IF;
    					ELSE
    						es<=e3;
    					END IF;
    				WHEN e4=>				--CMD (Mas que comando, es el registro al que accedo). Convendria cambiar el nombre de estas variables.
    					IF(cmd_rdy='1')THEN
    						es<=e5;
    					ELSE
    						es<=e4;
    					END IF;
    				WHEN e5=>				--ACK
    					--IF(ACK='0')THEN
    						IF(ack_rdy='1')THEN
    							IF(le='0')THEN --Escritura
    								es<=e6;
    							ELSE
    								es<=e1;
    							END IF;
    						ELSE
    							es<=e5;
    						END IF;
    					--ELSE
    						--es<=e8;
    					--END IF;
    				WHEN e6=>				--DATA
    					IF(data_rdy='1')THEN
    						es<=e7;
    					ELSE
    						es<=e6;
    					END IF;
    				WHEN e7=>				--ACK
    					--OJO. Hay que comprobar si quiero mas datos o si solo quiero recibir un Byte.
    					IF(ack_rdy='1')THEN
    						IF(le='0')THEN
    							es<=e8;
    						ELSE
    							IF(ack='0')THEN--Quiero que siga devolviendome mas datos. Me pasa el siguiente registro.
    								es<=e6;
    							ELSE				--No quiero mas datos, quiero terminar la trama.
    								es<=e8;
    							END IF;
    						END IF;
    					ELSE
    						es<=e7;
    					END IF;
    				WHEN e8=>				--Llamada para generar la condición de parada.
    					if (stopped='1')then
    						es<=e0;
    					else
    						es<=e8;
    					end if;
    			END CASE;
    		END IF;
    	END PROCESS;
    	
    	--Cambio de estados.
    	PROCESS(scl2x)
    	BEGIN
    		if(rising_edge(scl2x))then
    			ep<=es;
    		end if;
    	END PROCESS;
    	
    	--Señales de control
    	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_tx	<='1' when ep=e7 else '0';
    	ack_rx	<='1' when ep=e3 or ep=e5 else '0';
    	stop		<='1' when ep=e8 else '0';
    
    	--Reloj de 200kHz
    	process (CLK_50)
    	begin
    		if (rising_edge(CLK_50)) then
    			if (cont<halfCont)then --Si es menor que la mitad.
    				scl2x<='0';
    				cont<=cont+'1';
    			else
    				scl2x<='1';
    				cont<=cont+'1';
    			end if;
    			if(cont=maxCont)then 
    				cont<="00000000";
    			end if;
    		end if;
    	end process;
    
    	--Reloj de 100kHz (Va a enviarse a SCLK)
    	process(scl2x)
    	begin
    		if(rising_edge(scl2x))then
    			scl1x<=not(scl1x);
    		end if;
    	end process;
    	
    	--Unidad de Control (UC)
    	process (CLK_50)	--En este proceso voy a manipular exclusivamente la señal SDAT.
    	begin
    		if(rising_edge(CLK_50))then
    			if(idle='1')then
    				sdat_gen<='1';
    				stopped<='0';
    				terminate<='0';
    			elsif(start='1')then					--Comienzo de la transmision: SDAT a 0 antes que SCLK
    				if(SPEED='0')then
    					halfCont<="01111101";	--125.
    					maxCont<="11111001";		--249.
    					fullCont<="111110011";	--499. Ciclo completo de 100kHz
    				else
    					halfCont<="00100000";	--32.
    					maxCont<="00111111";		--63.
    					fullCont<="001111110";	--126. Ciclo completo de "400kHz". 390kHz reales.
    				end if;
    				ack_rdy<='0';
    				--Preparo el siguiente dato a enviar.
    				if(le='0')then
    					data_addr<=ADD & le;
    				else
    					if(sec='0')then 					--Si es la segunda vez que pasa, le pongo el bit de escritura.
    						data_addr<=ADD & '0';
    					else
    						conf_rdy<='0'; --Tengo que volver a ponerlo a 0 para que vuelva a enviar el address
    						data_addr<=ADD & le;
    					end if;
    				end if;
    				--Hago la condicion de inicio.
    				if(scl1x='1')then
    					if(scl2x='0')then
    						sdat_gen<='0';
    						started<='1';
    					else
    						sdat_gen<='1';
    					end if;
    				else
    					sdat_gen<='1';
    				end if;
    				--Hay que inicializar las variables de rdy a 0.
    			elsif(config='1')then
    				ack_rdy<='0';
    				if(scl1x='0')then
    					if(scl2x='0')then
    						if(cBits=-1)then--aqui he metido el clk,  de lo contrario el ultimo bit dura menos y cambia los tiempos de la trama
    							cBits<=7;
    							conf_rdy<='1';
    						else
    							sdat_gen<=data_addr(cBits);
    							hold<='0';
    						end if;
    					end if;
    				elsif(scl1x='1' and cBits>-1 and hold='0')then
    					cBits<=cBits-1;
    					hold<='1';
    				end if;
    --				if(scl1x='0' and cBits=-1)then--aqui he metido el clk,  de lo contrario el ultimo bit dura menos y cambia los tiempos de la trama
    --					cBits<=7;
    --					conf_rdy<='1';
    --				end if;
    			elsif(cmd='1')then
    			ack_rdy<='0';
    				if(scl1x='0')then
    					if(scl2x='0')then
    						if(cBits=-1)then
    							cBits<=7;
    							if(le='1')then
    								sec<='1';	--Activo sec para decir que ya ha pasado. En el siguiente estado e5 le toca ir al e0.
    							else
    								sec<='0';
    							end if;
    							cmd_rdy<='1';
    						else
    							sdat_gen<=data_cmd(cBits);
    							hold<='0';
    						end if;
    					end if;
    				elsif(scl1x='1' and cBits>-1 and hold='0')then
    					cBits<=cBits-1;
    					hold<='1';
    				end if;
    --				if(scl1x='0'and cBits=-1)then--aqui he metido el clk, de lo contrario el ultimo bit dura menos y cambia los tiempos de la trama
    --					cBits<=7;
    --					--Solo es necesario que cambie sec para las lecturas.
    --					if(le='1')then
    --						sec<='1';	--Activo sec para decir que ya ha pasado. En el siguiente estado e5 le toca ir al e0.
    --					else
    --						sec<='0';
    --					end if;
    --					started<='0';
    --					cmd_rdy<='1';
    --				end if;
    			elsif(data='1')then
    				ack_rdy<='0';
    				if(scl1x='0')then
    					if(scl2x='0')then
    						if(cBits=-1)then
    							cBits<=7;
    							data_rdy<='1';
    						else
    							if (le='0')then	--Si escribo, lo mando hacia sdat
    								sdat_gen<=data_info(cBits);
    							else			--Si leo, "pincho" la linea SDAT y lo almaceno en mi vector.
    								sdat_gen<='1';
    								data_reg(cBits)<=SDAT;
    							end if;
    							hold<='0';
    						end if;	
    					end if;
    				elsif(scl1x='1' and cBits>-1 and hold='0')then
    					cBits<=cBits-1;
    					hold<='1';
    				end if;
    
    			elsif(ack_rx='1')then
    
    				--Cuestion de tiempos, tienes que quedarte un ciclo. (Funciona, pero revisalo bien)
    				sdat_gen<='1';
    				--if(count100<('0' & fullCont(7 downto 1)))then --Espero un ciclo completo de reloj SCL.
    				--	count100<=count100+'1';
    				--else
    					--count100<="000000000";
    					--rdnReg<=rdnReg+1;
    					--data_rdy<='0';--Tengo que coger nuevos datos, asi que no puede estar este flag a '1'
    					ack_rdy<='1';
    				--end if;
    			elsif(ack_tx='1')then
    				--Me estoy quedando un ciclo de 100kHz a '1'. El tx es solo para leer, y estoy considerando un unico caso, hacer solo una lectura.
    				if(le='1')then
    					if(rdnReg<RDNUM)then
    							ack<='0';
    					else
    							ack<='1';
    					end if;
    					sdat_gen<=ack;
    					if(count100<fullCont)then --Espero un ciclo completo de reloj SCL.
    						count100<=count100+'1';
    					else
    						count100<="000000000";
    						rdnReg<=rdnReg+1;
    						data_rdy<='0';--Tengo que coger nuevos datos, asi que no puede estar este flag a '1'
    						ack_rdy<='1';
    					end if;
    				else
    					sdat_gen<='1';
    					ack_rdy<='1';
    				end if;
    			elsif(stop='1')then				--Fin de la transmision: SDAT a 1 despues de SCLK
    				--Pongo las variables de control a 0 para la proxima trama.
    				conf_rdy<='0';
    				cmd_rdy<='0';
    				data_rdy<='0';
    				ack_rdy<='0';
    				sec<='0';
    				sdat_gen<='0';--Lo pongo aqui para que despues del ack se quede a 0.
    				--He cambiado de estado, pero estoy a la espera de que el ACK del dispositivo termine, espero al siguiente ciclo para poner SDA a 0.
    				if(scl1x='0')then
    					terminate<='1';
    					sdat_gen<='0';
    				end if;
    				--Si todavía sigue en el ciclo anterior, el del ACK, lo mantengo a 1 para que haya alta impedancia y pueda escribir 0 el dispositivo.
    				if(scl1x='1' and terminate='0')then
    					--sdat_gen<='1';
    					sdat_gen<='0';
    				end if;
    				--Si ya estoy en el siguiente ciclo, durante la bajada estaba 0, por lo que ahora hago la condición de parada subiendo SDA en el momento adecuado.
    				if(scl1x='1' and scl2x='0' and terminate='1')then --Si estoy en el siguiente ciclo y en medio del bit de SCL que esta a 1, ya puedo subir SDA
    					sdat_gen<='1';
    					stopped<='1';
    					started<='0';
    				end if;
    			end if;
    		end if;
    	end process;
    		
    	--Salidas
    	SDAT <= 'Z' when sdat_gen = '1' else '0'; --En el bus I2C los '1' se representa con alta impedancia.
    	SCLK 	<= '1' when ep=e0 else scl1x;
    	BUSY	<= not(idle);
    	DOUT <= data_reg;
    
    end a;
    Test bench
    Code:
    library IEEE;
    use IEEE.STD_LOGIC_1164.ALL;
    use IEEE.STD_LOGIC_UNSIGNED.ALL;
    use ieee.numeric_std.all;
    
    ENTITY pruebaI2C_RW is
    	PORT(
    			FPGA_CLK1_50: IN STD_LOGIC;
    			KEY			: IN STD_LOGIC_VECTOR(1 DOWNTO 0);
    			LED			: OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
    			SCL			: OUT STD_LOGIC;
    			SDA			: INOUT STD_LOGIC);	
    END pruebaI2C_RW;
    
    architecture a of pruebaI2C_RW is
    
    	COMPONENT masterI2C_RW is
       	 	Port( 
    			CLK_50: IN  STD_LOGIC;
    			RST 	: IN  STD_LOGIC;
    			ADD	: IN  STD_LOGIC_VECTOR (6 DOWNTO 0);	--Address: Direccion del dispositivo.
    			COM	: IN  STD_LOGIC_VECTOR (7 DOWNTO 0);	--Command: Tipo de orden a enviar.
    			DAT	: IN  STD_LOGIC_VECTOR (7 DOWNTO 0);	--Data: Informacion a enviar.
    			GO		: IN  STD_LOGIC;
    			RW		: IN  STD_LOGIC;							 	--Bit de L/E. 0=Write; 1=Read;
    			SPEED : IN  STD_LOGIC;							 	--Velocidad de reloj. 0=100kHz; 1=400kHz.
    			RDNUM : IN  INTEGER;									--Numero de registros a leer.
    			BUSY	: OUT STD_LOGIC;
    			DOUT	: OUT STD_LOGIC_VECTOR (7 DOWNTO 0);	--Salida de datos. Cuando hay operación de lectura saca ese resultado.
    			SCLK	: OUT STD_LOGIC;
    			SDAT	: INOUT STD_LOGIC
    			);
    	end COMPONENT;
    
    
    	--Estados
    	TYPE estados is (e0, e1, e2, e3);
    	SIGNAL ep : estados :=e0; 	--Estado Presente
    	SIGNAL es : estados; 		--Estado Siguiente
    	
    
    	signal command, data : std_logic_vector(7 downto 0);
    	
    	--Senales de datos para la placa
    	constant cmd_config	: std_logic_vector(7 downto 0) := "00000011"; 	--Configura los pines de la placa. Cuales son de entrada y cuales de salida. (03h)
    	constant cmd_write	: std_logic_vector(7 downto 0) := "00000001"; 	--Dice que el dato siguiente es para escribir en la salida.
    	constant data_addr	: std_logic_vector(6 downto 0) := "1110010"; 	--Direccion de la placa.
    	constant data_ports	: std_logic_vector(7 downto 0) := "00001111"; 	--Digo los puertos que son entradas y los que son salidas. (0=out; 1=in;)
    	constant data_out		: std_logic_vector(7 downto 0) := "10101010"; 	--Digo donde quiero escribir un uno. Solo lo hara en las que se configuren como salida.
    	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 	cont			: std_logic_vector (6 downto 0):="0000000";
    	signal 	clk100k		: std_logic;
    	signal 	clk100k_z	: std_logic;
    	signal	le				: std_logic:='0';
    	signal	speed			: std_logic:='0';
    	signal	rdnum			:integer range 0 to 9:=6; 		--OJO! Que he puesto 2 en la definicion solo para la prueba.
    
    BEGIN
    
    	reset<=NOT(KEY(0));
    
    	PROCESS(clk100k, ep, reset)
    	BEGIN
    		IF(rising_edge(clk100k))THEN
    			IF(reset='1')then
    				es<=e0;
    			ELSE
    				CASE ep IS
    					WHEN e0 =>				--Estado de conf
    						command<=cmd_config;
    						data<=data_ports;
    						le<='0';
    						es<=e1;
    					WHEN e1 =>
    						command<=cmd_write;
    						data<=data_out;
    						le<='0';
    						es<=e2;
    					WHEN e2 =>
    						command<=cmd_write;
    						le<='1';
    						es<=e3;
    						--speed<='0';
    					WHEN e3 =>
    						es<=e3;
    				END CASE;
    			END IF;
    		END IF;
    	END PROCESS;
    	go			<='1' 	when (ocupado='0' and (ep=e0 or ep=e1 or ep=e2)) else '0'; --OJO! Esto es una PRUEBA. Solo queria transmitir una trama!!!
    	speed		<='1'		when ep=e1 or ep=e2 else '0';
    	
    	PROCESS(clk100k, ocupado, es)
    	BEGIN
    		if(ocupado='0')then
    			ep<=es;
    		end if;
    	END PROCESS;
    
    
    	inst1: masterI2C_RW
        	Port Map( 
    		CLK_50 	=> FPGA_CLK1_50,
    		RST 	=> reset,
    		ADD 	=> data_addr,
    		COM	=> command,
    		DAT 	=> data,
    		GO	=> go,
    		RW => le,
    		SPEED => '0',
    		RDNUM => rdnum,
    		BUSY	=> ocupado,
    		DOUT => LED,
    		SCLK	=> clk100k,
    		SDAT	=> SDA
    	);
    	clk100k_Z <= 'Z' when clk100k = '1' else '0';
    	SCL <= clk100k_Z;
    END a;
    Simulating on ModelSim I see this glitch, but I can't find the trace. Both states are practically equal (e2 and e4), and they share the same ACK status on the "control unit". BTW, the FPGA_CLOCK is 50Mhz, so I force it to 20ns on modelsim
    Click image for larger version. 

Name:	3_.PNG 
Views:	4 
Size:	55.1 KB 
ID:	150177



  7. #47
    Super Moderator
    Points: 29,664, Level: 42
    ads-ee's Avatar
    Join Date
    Sep 2013
    Location
    USA
    Posts
    6,843
    Helped
    1628 / 1628
    Points
    29,664
    Level
    42

    Re: Implement I2C in VHDL

    The glitches are a result of generating "clock" with flip-flops and then using both of these new clocks in the 50MHz domain to detect edges of them. Doing this results in all the glitches due to the combinational logic that produces the signals.

    Instead of making everything synchronous to 50 MHz and creating an enable signal that pulses 1 50 MHz clock cycle high at the sclk2x rate you've made your design much more difficult by introducing multiple clock domains with each new clock generated with more skew. There is a chance that the new FF clocks aren't even routed on clock networks in the device. Which will cause even more timing variation and potential functional failures.

    The entire design approach is bad, using 1, 2, or 3 process style FSMs has no bearing on the root problem...a bad architecture for an FPGA design.



    •   AltAdvertisment

        
       

  8. #48
    Advanced Member level 5
    Points: 22,270, Level: 36
    barry's Avatar
    Join Date
    Mar 2005
    Location
    California, USA
    Posts
    4,261
    Helped
    943 / 943
    Points
    22,270
    Level
    36

    Re: Implement I2C in VHDL

    Quote Originally Posted by dpaul View Post
    It would be nice and meaningful if we can stick to the OPs problem and related stuff.
    The OPs original problem was how to code I2C. FSM coding style, I think, is relevant.



  9. #49
    Junior Member level 3
    Points: 150, Level: 1

    Join Date
    Oct 2018
    Posts
    29
    Helped
    0 / 0
    Points
    150
    Level
    1

    Re: Implement I2C in VHDL

    Sorry, but I'm getting quite desperated with this.
    I am just looking at the chronogram and I can't comprehend what am I doing wrong. Thats why I asking for some light.
    On this thread there's an evolution from a copy-pasted code to a semi-working I2C protocol, I learned a little bit more to program in VHDL.
    Some of you are telling me that the way I'm doing it is wrong, but I still don't know how should I do it. I'm learning and it seems I am doing all this way alone. So please, if someone could just tell me in a few words which mistake I'm doing and not seeing I would be really glad.

    As always, thanks in advance.



  10. #50
    Super Moderator
    Points: 29,664, Level: 42
    ads-ee's Avatar
    Join Date
    Sep 2013
    Location
    USA
    Posts
    6,843
    Helped
    1628 / 1628
    Points
    29,664
    Level
    42

    Re: Implement I2C in VHDL

    I already told you use a clock enable that is generated off the 50 MHz and pulses high once every half period of the I2C interface bit period. Your FSM then just bit bangs the interface signals out (both SCL and SDA, i.e. SCL <= '1' on one enable at the 50 MHz clock then SCL <= '0' on the next enable at the 50 MHz clock). This way the entire design is synchronous and there are no skew problems causing a bunch of glitches and all the I2C outputs can be generated from the outputs of flip-flops. If you can't draw up a detailed block diagram of this and/or a schematic of such a implementation then you should probably stick to simpler circuits. I2C isn't all that complicated, but it is also not entirely trivial.

    IMO you should have grabbed the design from Opencores and studied that and ran simulations of that to see how it works.
    The problem with the copy paste comments and the lack of effort shown from before, was because you made no discernible effort to attempt to understand the design by writing a testbench and showing us waveforms of the simulation that didn't seem to be correct. Posting code snippets and such and saying you don't understand how it works isn't going to get any help without explaining why you can't understand how it works. e.g. I ran the following simulation and signal x doesn't go high like I think it should when y goes high... A description that allows us to see both what the problem is and if it is caused by misunderstanding how the code behaves or that you don't understand the logic involved. That is the kind of effort I want to see before I help, otherwise it's just another forum user that just wants to leach off the board and get free help on the simplest stuff, because they are too lazy to make any effort to learn.

    The original design you posted used a clock enable and a single 50 MHz clock domain, you should have stuck with that and ran simulations.



  11. #51
    Advanced Member level 3
    Points: 5,683, Level: 17

    Join Date
    Feb 2015
    Posts
    939
    Helped
    269 / 269
    Points
    5,683
    Level
    17

    Re: Implement I2C in VHDL

    sclk2x and sclk1x basically start with 2x and 1x going high. 2x goes low, then 1x high and 2x low, then the scl cycles ends with both low. The condition for changing the data is both clocks low. This is the last quarter of the scl cycle.

    That said, the code should be rewritten with clk100k_rise/clk100_fall and any other relevant strobes. only rising_edge(clk50) should be used inside the synthesizable logic.

    This type of project can also teach some bad practices because the delays often don't matter.



  12. #52
    Super Moderator
    Points: 67,706, Level: 63
    Achievements:
    7 years registered
    Awards:
    Most Frequent Poster 3rd Helpful Member

    Join Date
    Apr 2014
    Posts
    13,843
    Helped
    3158 / 3158
    Points
    67,706
    Level
    63

    Re: Implement I2C in VHDL

    Hi,

    I recommend to go through some good tutorials. Maybe one of the FPGA or CPLD manufacturers.
    Especially focus on clock generation...

    As ads-ee mentioned:
    I already told you use a clock enable ...
    I want to emphasize "enable".
    You don't generate "new clock" signal that are use to control parts of the logic... you generate "enable" signals, but use always your high speed system clock.
    ..this is a general rule ... and for sure there are exceptions...

    I'm no expert in writing HDL, but this was one of the first rule that I was told...

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



    •   AltAdvertisment

        
       

  13. #53
    Junior Member level 3
    Points: 150, Level: 1

    Join Date
    Oct 2018
    Posts
    29
    Helped
    0 / 0
    Points
    150
    Level
    1

    Re: Implement I2C in VHDL

    Hello again, I fixed the mistakes with the tempo. But I have a doubt with reading the values from the registers.
    My board has sequential read, so if I start reading in register 01h it should keep reading the 02h, 03h, etc.
    I have configurated it writing on the registers and I tried to read them later separately, it works fine. But when I try to do the sequential read it returns me the same value all the time.
    On the simulations the timings are right, you can see how it keeps the same stroke and the SDA line is on high-impedance in order to read, here's a picture:
    Click image for larger version. 

Name:	sim.PNG 
Views:	3 
Size:	8.9 KB 
ID:	150199
    But when I connect the logic analyzer I get other kind of data. I should get AB, 0, F on the registers, but I only see AA all the time until de NACK and Stop condition. Why can this be happening?
    Click image for larger version. 

Name:	real.PNG 
Views:	2 
Size:	28.7 KB 
ID:	150200

    The simulation goes on from the Repeated Start, and the real signal on logic simulator goes from start.

    Thanks in advance, I update my code here so if anyone wants to run it.
    Master
    Code:
    library IEEE;
    use IEEE.STD_LOGIC_1164.ALL;
    use IEEE.STD_LOGIC_UNSIGNED.ALL;
    use ieee.numeric_std.all;
    
    
    entity masterI2C_RW is
        Port( 
    		CLK_50: IN  STD_LOGIC;								--Reloj a 50Mhz
    		RST 	: IN  STD_LOGIC;
    		ADD	: IN  STD_LOGIC_VECTOR (6 DOWNTO 0); 	--Address: Dirección del dispositivo.
    		COM	: IN  STD_LOGIC_VECTOR (7 DOWNTO 0); 	--Command: Tipo de orden a enviar.
    		DAT	: IN  STD_LOGIC_VECTOR (7 DOWNTO 0); 	--Data: Información a enviar.
    		GO		: IN  STD_LOGIC;								--Inicio.
    		RW		: IN  STD_LOGIC;								--Bit de L/E. 0=Write; 1=Read;
    		SPEED : IN  STD_LOGIC;								--Velocidad de reloj. 0=100kHz; 1=400kHz.
    		RDNUM : IN  INTEGER;									--Numero de registros a leer.
    		BUSY	: OUT STD_LOGIC;								--Ocupado. No puede atender nuevas peticiones.
    		DOUT	: OUT STD_LOGIC_VECTOR (7 DOWNTO 0);	--Salida de datos. Cuando hay operación de lectura saca ese resultado.
    		SCLK	: OUT STD_LOGIC;								--Señal de reloj para la sincronización del I2C. 100kHz.
    		SDAT	: INOUT STD_LOGIC);							--Señal de datos generada. Trama a enviar al dispositivo.
    end masterI2C_RW;
    
    
    architecture a of masterI2C_RW is
    
    	--Estados
    	TYPE estados is (e0, e1, e2, e3, e4, e5, e6, e7, e8);
    	SIGNAL ep : estados :=e0; 	--Estado Presente
    	SIGNAL es : estados; 		--Estado Siguiente
    	
    	--Señales de control de la máquina de estados
    	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_tx	: std_logic := '0';				--Acknowledge transmission. Estado de ACK en el que el MAESTRO tiene que responder.
    	signal ack_rx	: std_logic := '0';				--Acknowledge reception. Estado de ACK en el que el ESCLAVO tiene que responder.
    	signal ack_txrx	: std_logic := '0';
    	signal cBits	: integer range -1 to 7 :=7; 	--Contador de bits.
    	signal sec		: std_logic := '0';				--Senal para saber si es la segunda vez que pasa(RS-Repeated Start)
    	
    
    	--Generación de relojes
    	signal scl2x 	: std_logic :='0'; 									--Reloj al doble de la frecuencia de scl1x. Se usa para hacer las condiciones de inicio/parada y trama.
    	signal scl1x	: std_logic :='0'; 									--Reloj de 100 o 400 kHz. Para la sincronización. Señal SCL.
    	signal cont		: std_logic_vector(7 downto 0) :="00000000";	--Lo uso para contar ciclos a la hora de hacer el reloj de 2x.
    	signal halfCont: std_logic_vector(7 downto 0) :="01111101"; --Mitad del contador. Ciclo a '1'. Inicializado a 125.
    	signal maxCont : std_logic_vector(7 downto 0) :="11111001"; --Máximo del contador. Indica cuando reiniciar. Inicializado a 249.
    	
    	--Señales de datos para la placa
    	signal data_addr	: std_logic_vector(7 downto 0); --Dirección de la placa y bit de escritura.
    	signal data_cmd	: std_logic_vector(7 downto 0); --Comando.
    	signal data_info	: std_logic_vector(7 downto 0); --Datos asociados al comando.
    	signal data_reg	: std_logic_vector(7 downto 0); --Datos que recibo del dispositivo.
    	signal le			: std_logic;						  --Lectura/Escritura.
    	
    	--Otras señales
    	signal reset 	 : std_logic;
    	signal sdat_gen : std_logic:='1'; --Trama generada para SDAT.
    	signal hold 	 : std_logic:='1'; --Para ir al ritmo de SCLK y no del CLK. Mantiene el valor en el canal SDAT lo que dure el ciclo de SCLK.
    	signal stopped  : std_logic:='0';
    	signal started  : std_logic:='0';
    	signal terminate: std_logic:='0';
    	signal terminated: std_logic:='0';
    	signal ack_rdy  : std_logic:='0'; --Para detener el ack durante un ciclo.
    	signal count100 : std_logic_vector(8 downto 0):="000000000";
    	signal fullCont : std_logic_vector(8 downto 0):="111110011"; --499. El ciclo completo de 100kHz
    	signal rdnReg	 : integer range 0 to 9:=0;		--"Readen Registers". Numero de registros leidos.
    
    begin
    
    	--IDEA: Igual estas senales tienen que cambiar solo cuando la maquina este disponible, para que no se modifiquen en mitad de una operacion.
    	reset	  	 <= RST;
    	--data_addr <= ADD; Esto va mas abajo, tengo que cambiar el ultimo bit.
    	data_cmd  <= COM;
    	data_info <= DAT;
    	le <= RW;
    
    	--Maquina de estados
    	PROCESS(ep, reset, GO, conf_rdy, cmd_rdy, data_rdy, ack, stopped, sec, started, ack_rdy, le)--He añadido ACK a la lista de sensibilidad.
    	BEGIN
    		IF(reset='1')then
    			es<=e0;
    		ELSE
    			CASE ep IS
    				WHEN e0 =>				--Estado de espera
    					IF(GO='1')THEN
    						es<=e1;
    					ELSE
    						es<=e0;
    					END IF;
    				WHEN e1=>					--Llamada para generar la condición de inicio
    					IF(started='1')THEN	--Started se pone a '1' cuando ha hecho la condicion de inicio.
    						es<=e2;
    					ELSE
    						es<=e1;
    					END IF;
    				WHEN e2=>				--Address+RW. Envio la direccion y la operacion a realizar (leer o escribir).
    					IF(conf_rdy='1')THEN
    						es<=e3;
    					ELSE
    						es<=e2;
    					END IF;
    				WHEN e3 =>				--ACK
    					IF(ack_rdy='1')THEN
    						--IF(ACK='0')THEN
    							IF(le='0')THEN --Escritura
    								es<=e4;
    							ELSE				--Lectura
    								IF(sec='0')THEN
    									es<=e4;
    								ELSE
    									es<=e6;
    								END IF;
    							END IF;
    						--ELSE
    							--es<=e8;
    						--END IF;
    					ELSE
    						es<=e3;
    					END IF;
    				WHEN e4=>				--CMD (Mas que comando, es el registro al que accedo). Convendria cambiar el nombre de estas variables.
    					IF(cmd_rdy='1')THEN
    						es<=e5;
    					ELSE
    						es<=e4;
    					END IF;
    				WHEN e5=>				--ACK
    					--IF(ACK='0')THEN
    						IF(ack_rdy='1')THEN
    							IF(le='0')THEN --Escritura
    								es<=e6;
    							ELSE
    								es<=e1;
    							END IF;
    						ELSE
    							es<=e5;
    						END IF;
    					--ELSE
    						--es<=e8;
    					--END IF;
    				WHEN e6=>				--DATA
    					IF(data_rdy='1')THEN
    						es<=e7;
    					ELSE
    						es<=e6;
    					END IF;
    				WHEN e7=>				--ACK
    					--OJO. Hay que comprobar si quiero mas datos o si solo quiero recibir un Byte.
    					IF(ack_rdy='1')THEN
    						IF(le='0')THEN
    							es<=e8;
    						ELSE
    							IF(ack='0')THEN--Quiero que siga devolviendome mas datos. Me pasa el siguiente registro.
    								es<=e6;
    							ELSE				--No quiero mas datos, quiero terminar la trama.
    								es<=e8;
    							END IF;
    						END IF;
    					ELSE
    						es<=e7;
    					END IF;
    				WHEN e8=>				--Llamada para generar la condición de parada.
    					if (stopped='1')then
    						es<=e0;
    					else
    						es<=e8;
    					end if;
    			END CASE;
    		END IF;
    	END PROCESS;
    	
    	--Cambio de estados.
    	PROCESS(CLK_50)
    	BEGIN
    		if(rising_edge(CLK_50))then
    			ep<=es;
    		end if;
    	END PROCESS;
    	
    	--Señales de control
    	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_txrx	<='1' when ep=e3 or ep=e5 or ep=e7 else '0';
    	stop		<='1' when ep=e8 else '0';
    
    	--Reloj de 200kHz
    	process (CLK_50)
    	begin
    		if (rising_edge(CLK_50)) then
    			if (cont<halfCont)then --Si es menor que la mitad.
    				scl2x<='0';
    				cont<=cont+'1';
    			else
    				scl2x<='1';
    				cont<=cont+'1';
    			end if;
    			if(cont=maxCont)then 
    				cont<="00000000";
    			end if;
    		end if;
    	end process;
    
    	--Reloj de 100kHz (Va a enviarse a SCLK)
    	process(scl2x)
    	begin
    		if(rising_edge(scl2x))then
    			scl1x<=not(scl1x);
    		end if;
    	end process;
    	
    	--Unidad de Control (UC)
    	process (CLK_50)	--En este proceso voy a manipular exclusivamente la señal SDAT.
    	begin
    		if(rising_edge(CLK_50))then
    			if(idle='1')then
    				sdat_gen<='1';
    				stopped<='0';
    				terminate<='0';
    				ack<='0';
    				rdnReg<=0;
    			elsif(start='1')then					--Comienzo de la transmision: SDAT a 0 antes que SCLK
    				if(SPEED='0')then
    					halfCont<="01111101";	--125.
    					maxCont<="11111001";		--249.
    					fullCont<="111110011";	--499. Ciclo completo de 100kHz
    				else
    					halfCont<="00100000";	--32.
    					maxCont<="00111111";		--63.
    					fullCont<="001111110";	--126. Ciclo completo de "400kHz". 390kHz reales.
    				end if;
    				ack_rdy<='0';
    				--Preparo el siguiente dato a enviar.
    				if(le='0')then
    					data_addr<=ADD & le;
    				else
    					if(sec='0')then 					--Si es la segunda vez que pasa, le pongo el bit de escritura.
    						data_addr<=ADD & '0';
    					else
    						conf_rdy<='0'; --Tengo que volver a ponerlo a 0 para que vuelva a enviar el address
    						data_addr<=ADD & le;
    					end if;
    				end if;
    				--Hago la condicion de inicio.
    				if(scl1x='1')then
    					if(scl2x='0')then
    						sdat_gen<='0';
    						terminated<='1';
    					else
    						sdat_gen<='1';
    					end if;
    				else
    					if(terminated='1')then
    						if(scl2x='0')then
    							started<='1';
    							terminated<='0';
    						end if;
    					else
    						sdat_gen<='1';
    					end if;
    				end if;
    				--Hay que inicializar las variables de rdy a 0.
    			elsif(config='1')then
    				terminated<='0';
    				ack_rdy<='0';
    				ack<='0';
    				if(scl1x='0')then
    					if(scl2x='1')then
    						if(cBits=-1)then--aqui he metido el clk,  de lo contrario el ultimo bit dura menos y cambia los tiempos de la trama
    							cBits<=7;
    							conf_rdy<='1';
    						end if;
    							--sdat_gen<='1';
    					else
    						sdat_gen<=data_addr(cBits);
    						hold<='0';
    					end if;
    				elsif(scl1x='1' and cBits>-1 and hold='0')then
    					cBits<=cBits-1;
    					hold<='1';
    				end if;
    --				if(scl1x='0' and cBits=-1)then--aqui he metido el clk,  de lo contrario el ultimo bit dura menos y cambia los tiempos de la trama
    --					cBits<=7;
    --					conf_rdy<='1';
    --				end if;
    			elsif(cmd='1')then
    			ack_rdy<='0';
    			ack<='0';
    				if(scl1x='0')then
    					if(scl2x='1')then
    						if(cBits=-1)then
    							cBits<=7;
    							--sdat_gen<='1';
    							if(le='1')then
    								sec<='1';	--Activo sec para decir que ya ha pasado. En el siguiente estado e5 le toca ir al e0.
    							else
    								sec<='0';
    							end if;
    							cmd_rdy<='1';
    						end if;
    					else
    						sdat_gen<=data_cmd(cBits);
    						hold<='0';
    					end if;
    				elsif(scl1x='1' and cBits>-1 and hold='0')then
    					cBits<=cBits-1;
    					hold<='1';
    				end if;
    --				if(scl1x='0'and cBits=-1)then--aqui he metido el clk, de lo contrario el ultimo bit dura menos y cambia los tiempos de la trama
    --					cBits<=7;
    --					--Solo es necesario que cambie sec para las lecturas.
    --					if(le='1')then
    --						sec<='1';	--Activo sec para decir que ya ha pasado. En el siguiente estado e5 le toca ir al e0.
    --					else
    --						sec<='0';
    --					end if;
    --					started<='0';
    --					cmd_rdy<='1';
    --				end if;
    			elsif(data='1')then
    				ack_rdy<='0';
    				ack<='0';
    				if(scl1x='0')then
    					if(scl2x='1')then
    						if(cBits=-1)then
    							cBits<=7;
    							data_rdy<='1';
    							--sdat_gen<='1';
    						end if;
    					else
    						if (le='0')then	--Si escribo, lo mando hacia sdat
    							sdat_gen<=data_info(cBits);
    						else			--Si leo, "pincho" la linea SDAT y lo almaceno en mi vector.
    							sdat_gen<='1';
    							data_reg(cBits)<=SDAT;
    						end if;
    						hold<='0';
    					end if;
    				elsif(scl1x='1' and cBits>-1 and hold='0')then
    					cBits<=cBits-1;
    					hold<='1';
    				end if;
    			elsif(ack_txrx='1')then
    				started<='0';
    				if(scl1x='0')then
    					if(scl2x='1')then
    						if(cBits=6)then
    							cBits<=7;
    							ack_rdy<='1';
    							if(ep=e7 and le='1')then
    								rdnReg<=rdnReg+1;
    							end if;
    						end if;
    					else
    						if(ep=e7)then
    							data_rdy<='0';
    							if (le='0')then	--Si escribo, lo mando hacia sdat
    								ack<='1';
    							else			--Si leo, "pincho" la linea SDAT y lo almaceno en mi vector.
    								if(rdnReg<RDNUM)then
    									ack<='0';
    								else
    									ack<='1';
    								end if;
    							end if;
    						else
    							ack<='1';
    						end if;
    						hold<='0';
    					end if;
    				elsif(scl1x='1' and cBits>-1 and hold='0')then
    					cBits<=cBits-1;
    					hold<='1';
    				end if;
    				if(config='0' and ack='0' and ep=e3)then
    					sdat_gen<=data_addr(0);
    				elsif(cmd='0' and ack='0' and ep=e5)then
    					sdat_gen<=data_cmd(0);
    				elsif(le='0' and data='0' and ack='0' and ep=e7)then
    					sdat_gen<=data_info(0);
    				elsif(le='1' and data_rdy='1' and ep=e7)then
    					sdat_gen<='1';
    				else
    					sdat_gen<=ack;	--Antes solo esto sin condiciones.
    				end if;		
    			elsif(stop='1')then				--Fin de la transmision: SDAT a 1 despues de SCLK
    				--Pongo las variables de control a 0 para la proxima trama.
    				conf_rdy<='0';
    				cmd_rdy<='0';
    				data_rdy<='0';
    				ack_rdy<='0';
    				ack<='0';
    				sec<='0';
    				if(scl1x='0')then
    					if(terminate='1')then
    						stopped<='1';
    						sdat_gen<='1';
    					elsif(scl2x='1')then
    						sdat_gen<='1';
    					elsif(scl2x='0')then
    						sdat_gen<='0';--Lo pongo aqui para que despues del ack se quede a 0.
    					end if;
    				else
    					if(scl2x='0')then
    						sdat_gen<='1';
    						terminate<='1';
    					end if;
    				end if;
    			end if;
    		end if;
    	end process;
    		
    	--Salidas
    	SDAT <= 'Z' when sdat_gen = '1' or terminate='1' else '0'; --En el bus I2C los '1' se representa con alta impedancia.
    	SCLK 	<= '1' when ep=e0 or terminate='1' else scl1x;
    	BUSY	<= not(idle);
    	DOUT <= data_reg;
    
    end a;
    Slave:
    Code:
    library IEEE;
    use IEEE.STD_LOGIC_1164.ALL;
    use IEEE.STD_LOGIC_UNSIGNED.ALL;
    use ieee.numeric_std.all;
    
    ENTITY pruebaI2C_RW is
    	PORT(
    			FPGA_CLK1_50: IN STD_LOGIC;
    			KEY			: IN STD_LOGIC_VECTOR(1 DOWNTO 0);
    			LED			: OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
    			SCL			: OUT STD_LOGIC;
    			SDA			: INOUT STD_LOGIC);	
    END pruebaI2C_RW;
    
    architecture a of pruebaI2C_RW is
    
    	COMPONENT masterI2C_RW is
       	 	Port( 
    			CLK_50: IN  STD_LOGIC;
    			RST 	: IN  STD_LOGIC;
    			ADD	: IN  STD_LOGIC_VECTOR (6 DOWNTO 0);	--Address: Direccion del dispositivo.
    			COM	: IN  STD_LOGIC_VECTOR (7 DOWNTO 0);	--Command: Tipo de orden a enviar.
    			DAT	: IN  STD_LOGIC_VECTOR (7 DOWNTO 0);	--Data: Informacion a enviar.
    			GO		: IN  STD_LOGIC;
    			RW		: IN  STD_LOGIC;							 	--Bit de L/E. 0=Write; 1=Read;
    			SPEED : IN  STD_LOGIC;							 	--Velocidad de reloj. 0=100kHz; 1=400kHz.
    			RDNUM : IN  INTEGER;									--Numero de registros a leer.
    			BUSY	: OUT STD_LOGIC;
    			DOUT	: OUT STD_LOGIC_VECTOR (7 DOWNTO 0);	--Salida de datos. Cuando hay operación de lectura saca ese resultado.
    			SCLK	: OUT STD_LOGIC;
    			SDAT	: INOUT STD_LOGIC
    			);
    	end COMPONENT;
    
    
    	--Estados
    	TYPE estados is (e0, e1, e2, e3, e4, e5, e6);
    	SIGNAL ep : estados :=e0; 	--Estado Presente
    	SIGNAL es : estados; 		--Estado Siguiente
    	
    
    	signal command, data : std_logic_vector(7 downto 0);
    	
    	--Senales de datos para la placa
    	constant cmd_config	: std_logic_vector(7 downto 0) := "00000011"; 	--Configura los pines de la placa. Cuales son de entrada y cuales de salida. (03h)
    	constant cmd_write	: std_logic_vector(7 downto 0) := "00000001"; 	--Dice que el dato siguiente es para escribir en la salida.
    	constant data_addr	: std_logic_vector(6 downto 0) := "1110010"; 	--Direccion de la placa.
    	constant data_ports	: std_logic_vector(7 downto 0) := "00001111"; 	--Digo los puertos que son entradas y los que son salidas. (0=out; 1=in;)
    	constant data_out		: std_logic_vector(7 downto 0) := "10101010"; 	--Digo donde quiero escribir un uno. Solo lo hara en las que se configuren como salida.
    	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 	cont			: std_logic_vector (6 downto 0):="0000000";
    	signal 	clk100k		: std_logic;
    	signal 	clk100k_z	: std_logic;
    	signal	le				: std_logic:='0';
    	signal	speed			: std_logic:='0';
    	signal	rdnum			:integer range 0 to 9:=6; 		--OJO! Que he puesto 2 en la definicion solo para la prueba.
    
    BEGIN
    
    	reset<=NOT(KEY(0));
    
    	PROCESS(clk100k, ep, reset)
    	BEGIN
    		IF(rising_edge(clk100k))THEN
    			IF(reset='1')then
    				es<=e0;
    			ELSE
    				CASE ep IS
    					WHEN e0 =>				--Estado de conf
    						command<=cmd_config;
    						data<=data_ports;
    						le<='0';
    						es<=e1;
    					WHEN e1 =>
    						command<=cmd_write;
    						data<="10101011";
    						le<='0';
    						es<=e2;
    					WHEN e2 =>
    						command<=cmd_write;
    						le<='1';
    						es<=e3;
    						rdnum<=0;
    						--speed<='0';
    					WHEN e3 =>
    						command<="00000010";
    						le<='1';
    						rdnum<=0;
    						--speed<='0';
    						es<=e4;
    					WHEN e4 =>
    						command<="00000011";
    						le<='1';
    						es<=e5;
    						rdnum<=0;
    						--speed<='0';
    					WHEN e5 =>
    						command<="00000001";
    						le<='1';
    						es<=e6;
    						rdnum<=3;
    						--speed<='0';
    					WHEN e6 =>
    						es<=e6;
    				END CASE;
    			END IF;
    		END IF;
    	END PROCESS;
    	go			<='1' 	when (ocupado='0' and (ep=e0 or ep=e1 or ep=e2 or ep=e3 or ep=e4 or ep=e5)) else '0'; --OJO! Esto es una PRUEBA. Solo queria transmitir una trama!!!
    	speed		<='1'		when ep=e1 or ep=e2 or ep=e3 else '0';
    	
    	PROCESS(clk100k, ocupado, es)
    	BEGIN
    		if(ocupado='0')then
    			ep<=es;
    		end if;
    	END PROCESS;
    
    
    	inst1: masterI2C_RW
        	Port Map( 
    		CLK_50 	=> FPGA_CLK1_50,
    		RST 	=> reset,
    		ADD 	=> data_addr,
    		COM	=> command,
    		DAT 	=> data,
    		GO	=> go,
    		RW => le,
    		SPEED => '0',
    		RDNUM => rdnum,
    		BUSY	=> ocupado,
    		DOUT => LED,
    		SCLK	=> clk100k,
    		SDAT	=> SDA
    	);
    	clk100k_Z <= 'Z' when clk100k = '1' else '0';
    	SCL <= clk100k_Z;
    END a;



  14. #54
    Super Moderator
    Points: 67,706, Level: 63
    Achievements:
    7 years registered
    Awards:
    Most Frequent Poster 3rd Helpful Member

    Join Date
    Apr 2014
    Posts
    13,843
    Helped
    3158 / 3158
    Points
    67,706
    Level
    63

    Re: Implement I2C in VHDL

    Hello,

    I´m no specialist with HDL,

    But I assume you didn´t get the clock concept yet.
    Example @ Slave:
    Code:
    IF(rising_edge(clk100k))THEN
    I assume this is the I2C communication clock, but not your system clock. If so, then you can´t rely on this clok. It is neither continous, nor can it be expected to be reliable.
    Further I assume "FPGA_CLK1_50" is your FPGA system clock. If so, thewn synchronize aa inputs (and outputs) to this clock (domain). This should be your continous and reliable clock.

    Example @ Master:
    Code:
    	if(rising_edge(scl2x))then
    But scl2x is a generated signal. It is no "clock".

    ***
    There should be experts .. they can better verify whether this may cause problems. (I even may be wrong with my assumptions)

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



  15. #55
    Junior Member level 3
    Points: 150, Level: 1

    Join Date
    Oct 2018
    Posts
    29
    Helped
    0 / 0
    Points
    150
    Level
    1

    Re: Implement I2C in VHDL

    No, I still don't comprehend the clock concept you are talking me about.
    What I assume I'm doing (may I be wrong) is generating another clock signal from an original 50Mhz signal. FPGA_CLK1_50 is the main FPGA Clock.
    In the simulation it seems that the time I let for the ACK bit is OK. But in the simulation it seems as it doesn't read the ACK or that it doesn't want to read. What surprisees me the most is that it gets the NACK signal right.



  16. #56
    Super Moderator
    Points: 67,706, Level: 63
    Achievements:
    7 years registered
    Awards:
    Most Frequent Poster 3rd Helpful Member

    Join Date
    Apr 2014
    Posts
    13,843
    Helped
    3158 / 3158
    Points
    67,706
    Level
    63

    Re: Implement I2C in VHDL

    Hi,

    Clock generation:
    This is basic and important. Usually every FPGA manufacturer gives related documentation. Look for it.
    Hopefully a more experiecnced user could give you the link to a good tutorial.
    What surprisees me the most is that it gets the NACK signal right.
    Nothing to be surprised. NACK is the default, because it is just a HIGH on SDA. And for a HIGH on SDA no bus member has to do anything (idle state) because of the pullup. SDA is HIGH when all members release the bus.

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



--[[ ]]--