From Hamsterworks Wiki!
This FPGA Project was started in May 2013.
I2C is rapidly becoming the standard way to talk to low data rate sensors and other devices. FPGAs have no problem talking I2C, but designing a custom state machine to drive every device is painful. When more than one device shares the I2C bus this can become quite challenging.
With a clock rate of under 1MHz, I2C is a relatively slow use for using an FPGA's speedy logic, and to make things worse most sensors require delays of maybe a millisecond or longer. It makes little sense to build on-off super complex high speed FSMs to support such needs, making a software driven solution using a soft micro-controller an attractive alternative.
My Intelligent I2C controller is not a full micro-controller, but a simple programmable state machine, using about the same FPGA resources as a Picoblaze processor.
Advantages over more generic controller such as a Picoblaze is that is is designed especially for configuring and accessing I2C devices, where as the Picoblaze will need either a software I2C implementation or an added I2C peripheral.
The controller has 16 binary outputs. The controller can independently set and clear each of the output signals.
The controller has 16 binary inputs. The controller test the status of the input signals.
As a way to supply data from the rest of the FPGA fabric to the an I2C device the upper or lower 8 input signals can be written over the I2C bus. This could be extended by using the binary outputs to drive the select inputs of a mux, allowing the controller to choose which entries from a bank of registers is presented on a set of eight inputs signals.
The controller support up to 1024x9-bit program memory. This fills half an Xilinx Block RAM, allowing two I3C2 instances to share a single BRAM resource.
The only restriction is that the destination of jumps must be aligned to 8-word boundaries - giving only 64 target addresses
The controller can read words from the I2C device into one of thirty two registers. This is achieved through an 8-bit data port, a 5-bit address power and a single-bit write enable port.
The controller's instruction set
|00nnnnnnn||JUMP m||Set PC to 'm' (n = m/8)|
|01000nnnn||SKIPCLEAR n||Skip if input 'n' clear|
|01001nnnn||SKIPSET n||skip if input 'n' set|
|01010nnnn||CLEAR n||Clear output 'n'|
|01011nnnn||SET n||Set output 'n'|
|0110nnnnn||READ n||Read from the I2C bus to register 'n'|
|01110nnnn||DELAY m||Delay 'm' clock cycles (n = log2(m))|
|011110000||SKIPNACK||Skip if NACK is set|
|011110001||SKIPACK||Skip if ACK is set|
|011110010||WRITELOW||Write inputs 7 downto 0 to the I2C bus|
|011110011||WRITEHI||Write inputs 15 downto 8 to the I2C bus|
|011111101||MASTERACK||Send a Master ACK with the next read|
|011111111||STOP||Send Stop on i2C bus|
|1nnnnnnnn||WRITE n||Output 'n' on I2C bus|
An I2C transaction starts with the first WRITE instruction, and is held open until a STOP instruction is executed. Please note - failure to execute the STOP will lock the I2C bus completely!
Here is example code required to read data from a 3D Compass sensor
;============================================================ ; Reading from the HMC5883L Compass (I2C device ID is 3C / 3D ;------------------------------------------------------------ WRITE 0x3C ; Switch to continuous measurement mode WRITE 0x02 WRITE 0x00 STOP reread: WRITE 0x3C ; Start Write transaction WRITE 0x03 ; Pointer to Reg 3 STOP WRITE 0x3D ; Start Read transaction MASTERACK READ 0 ; Reg 3 - X, most significant byte MASTERACK READ 1 ; Reg 4 - X, least significant byte MASTERACK READ 2 ; Reg 5 - Y, most significant byte MASTERACK READ 3 ; Reg 6 - Y, least significant byte MASTERACK READ 4 ; Reg 7 - Z, most significant byte READ 5 ; Reg 8 - Z, Least significant byte (no MACK on last read) STOP SET 0 ; Indicate that new, consistent data is available CLEAR 0 DELAY 32768 ; Wait for approx 1/10th of a second JUMP reread
Save this code into "test_code.i3c2" and then compile it with the i3c2_assembler (code for which is below). This will generate a "test_code.vhd" file that can be added to your project.
Here is the top level VHDL module to go with this design. It captures the data being read from the sensor, and displays eight bits on the dev board's LEDs:
---------------------------------------------------------------------------------- -- Engineer: Mike Field <email@example.com> -- -- Create Date: 20:18:57 05/26/2013 -- Module Name: i3c2_test - Behavioral -- Description: The top_level module for testing the I3C2 processor -- with the HMC5883L 3-axis digital compass IC -- -- Revision 0.01 - File Created ---------------------------------------------------------------------------------- library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity i3c2_test is Port ( clk : in STD_LOGIC; scl : out STD_LOGIC; sda : inout STD_LOGIC; switches : in STD_LOGIC_VECTOR (7 downto 0); btn : in STD_LOGIC_VECTOR (3 downto 0); led : out STD_LOGIC_VECTOR (7 downto 0); i2c_scl : out std_logic := '1'; i2c_sda : inout std_logic := 'Z'; error : out STD_LOGIC); end i3c2_test; architecture Behavioral of i3c2_test is COMPONENT test_code PORT( clk : IN std_logic; address : IN std_logic_vector(9 downto 0); data : OUT std_logic_vector(8 downto 0)); END COMPONENT; COMPONENT i3c2 GENERIC( quarter_bit_length : std_logic_vector(5 downto 0)); PORT( clk : IN std_logic; inst_data : IN std_logic_vector( 8 downto 0); inputs : IN std_logic_vector(15 downto 0); i2c_sda : INOUT std_logic; inst_address : OUT std_logic_vector( 9 downto 0); i2c_scl : OUT std_logic; outputs : OUT std_logic_vector(15 downto 0); reg_addr : OUT std_logic_vector( 4 downto 0); reg_data : OUT std_logic_vector( 7 downto 0); reg_write : OUT std_logic; error : OUT std_logic); END COMPONENT; signal data : std_logic_vector(8 downto 0); signal address : std_logic_vector(9 downto 0); signal inputs, outputs : std_logic_vector(15 downto 0); signal reg_write : std_logic; signal reg_data : std_logic_vector(7 downto 0); signal reg_addr : std_logic_vector(4 downto 0); signal temp_mag_x, mag_x : std_logic_vector(15 downto 0); signal temp_mag_y, mag_y : std_logic_vector(15 downto 0); signal temp_mag_z, mag_z : std_logic_vector(15 downto 0); begin inputs(3 downto 0) <= btn; inputs(15 downto 8) <= switches; led <= mag_x(11 downto 4); process(clK) begin if rising_edge(clk) then -- Save the register values if reg_write = '1' then case reg_addr is when "00000" => temp_mag_x(15 downto 8) <= reg_data; when "00001" => temp_mag_x( 7 downto 0) <= reg_data; when "00010" => temp_mag_y(15 downto 8) <= reg_data; when "00011" => temp_mag_y( 7 downto 0) <= reg_data; when "00100" => temp_mag_z(15 downto 8) <= reg_data; when "00101" => temp_mag_z( 7 downto 0) <= reg_data; when others => null; end case; end if; -- Present the values to the outputs when output(0) is set if outputs(0) = '1' then mag_x <= temp_mag_x; mag_y <= temp_mag_y; mag_z <= temp_mag_z; end if; end if; end process; i3c2_code: test_code PORT MAP ( clk => clk, address => address, data => data ); i3c2_controller: i3c2 GENERIC MAP ( -- Set I2C clock to 50Mhz/63/4 = 198kHz quarter_bit_length => std_logic_vector(to_unsigned(63,6)) ) PORT MAP ( clk => clk, inst_address => address, inst_data => data, i2c_scl => i2c_scl, i2c_sda => i2c_sda, inputs => inputs, outputs => outputs, reg_addr => reg_addr, reg_data => reg_data, reg_write => reg_write, error => open ); end Behavioral;
File:I3c2 assemble.c is a simple assembler, contained in a signal source file and only required C's Standard Library.
Once compiled into an executable programs for the I3C2 can be compiled with "i3c2_assemble filename.i3c2".
This will produce the following files:
- filename.lst - A listing of the compiled code (not yet implemented)
- filename.coe - A '.coe' file for use with Xilinx's memory generator
- filename.vhd - A generic VHDL source file which can be used to infer the program ROM.
This is a work in progress
VHDL source files for the controller
You can find the latest source for the I3C2 here:
And here are the constraints I use with my development board:
NET "I2C_SDA" LOC = "T17" | IOSTANDARD=LVTTL | SLEW=SLOW | PULLUP; NET "I2C_SCL" LOC = "U18" | IOSTANDARD=LVTTL | SLEW=SLOW;