Archive for  February 2016

Home / February 2016
2 Posts

Below I provide the entire source code for a FPGA I2C Slave implementation in VHDL. To learn more about the implementation's design and FSM, see this post here.

This I2C Slave implementation provides basic read, write, and addressing functionality. It also supports repeated start conditions. The address of the slave is configurable through a generic. Note that this implementation does not currently support "advanced" features such as clock stretching. It also does not support 10-bit addressing, although I'd imagine it wouldn't be too hard to implement this.

This implementation has been tested on an Altera Cyclone IV FPGA. A Raspberry Pi 2 was used as the I2C master.

How to use

The I2C Slave FSM provides the following interface:

tx_done goes high when the I2C Slave module has finished transmitting data to the master. In other words, it signals the completion of a READ command (master reads from slave). The data to be sent to the master must be set in tx_byte.

rx_data_rdy goes high when data is received from the master. In other words, it indicates the completion of a WRITE command (master writes to slave). The received data can be found in rx_byte.

clk is the clock signal for the I2C Slave module itself. This should not be confused with SCL, which is the I2C-bus clock line. For best performance, clk should be significantly faster than SCL.

Source Code

This article discusses how to convert between the common VHDL types. The most common VHDL types are std_logic_vector, signed, unsigned, and integer. The std_logic and std_logic_vector types are defined in the standard logic 1164 package of the IEEE library. This package can be imported as follows:

library IEEE;

The signed and unsigned types can be found in the numeric_std package. This package can be imported using the code below. The signed type is represented in two's complement form. The source file for the numeric_std package can be found here. For more information on two's complement, check the Wikipedia page.

use ieee.numeric_std.all

VHDL is a strongly-typed language, and does not have automatic type conversion. As a result, converting between different types is often necessary. The libraries above provide type casts and conversion functions between the common VHDL types. These are depicted by the image below.

How to convert between the most common VHDL types


It is important to note that casting between a std_logic_vector and a signed/unsigned type requires that both signals have the same bit width. Integers do not have a set bit width. Therefore, in the to_signed and to_unsigned conversion functions, note that the first argument is the integer that is being converted and the second argument specifies the bit width of the resulting signed/unsigned type.

It is important to realize that there is no direct conversion between signed and unsigned types. There is also no direct conversion between the std_logic_vector and integer types.

Using these conversions and casts is straightforward. Below is example code illustrating their usage:

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.numeric_std.all;

signal stdlvec : std_logic_vector(31 downto 0);
signal unsgn : unsigned(31 downto 0);
signal sgn : signed(31 downto 0);
signal int : integer;

--Conversion from std_logic_vector to signed and unsigned types
unsgn <= unsigned(stdlvec);
sgn <= signed(stdlvec);

--Conversion from signed/unsigned types back to std_logic_vector
stdlvec <= std_logic_vector(unsgn);
stdlvec <= std_logic_vector(sgn);

--Conversion from signed/unsigned types to integer
int <= to_integer(sgn);
int <= to_integer(unsgn);

--Conversion from integer back to 32-bit signed/unsigned types
unsgn <= to_unsigned(int, 32);
sgn <= to_signed(int, 32);