Tri-State Buffers and FPGA Hierarchy
If you ever are using a bidirectional interface you know that you need to be using tri-state buffers to control the bidirectional signals. An example of a bidirectional interface is I2C. I2C is a two-wire interface that consists of a clock and a data line. The clock is usually sourced by the host and the data line is defined as type inout in VHDL or Verilog. The data line can be either a transmitter from host or a receiver to the host.
When the host wants to transmit data, it drives its logic on to the shared data line. When the host wants to listen for a response, it changes the driver to a high-impedance state and looks at data coming in on the shared data line. This is known as a tri-state buffer, since it can be three states: 0, 1, Z (high impedance)
Back ten or more years ago, all registers on an FPGA could be tristated. This was often how bus interfaces were accomplished. Now however on the majority of FPGA designs, tristate buffers only exist in the Input/Output Block (IOB).
The issue that I came across occurs when a sub-module tries to infer a tristate buffer. Here was the code in my I2C sub module:
w_I2C_DATA_IN <= io_i2c_data; io_i2c_data <= w_I2C_DATA_OUT when w_I2C_TX_ACTIVE = '1' else 'Z';
This inferred a tri-state buffer, but it was not at the top level of the FPGA. This is bad! The tools recognize that this module is a sub-module, so it cannot have a tristate buffer inside the FPGA. The synthesis tools remove the buffer and now you just have a normal output. This can be seen on the Pinout Report in Xilinx ISE. Instead of showing BIDIR next to your bidirectional inout signal (io_i2c_data in my case) the Pinout Report shows OUTPUT.
The preferred approach to fixing this problem is to bring the inference of the tristate buffer to the top level module of your FPGA design. For me that would mean bringing signals DATA_OUT, TX_ACTIVE, and DATA_IN to the top level of the FPGA. However if this cannot be done there is another solution. Under Synthesis Properties in Xilinx ISE you can set the attribute "keep hierarchy" to either Soft or Yes rather than No. This will allow the tristate buffer to be created at the lower level module and your bidirectional interface will work as intended.
Bidirectional interfaces can be convenient if you need to save every spare pin you can, but they often cause headaches like these.