A bidirectional bus is typically implemented by using a tristate buffer. When the tristate buffer output is "Z" you can read from the inout
port, when the buffer is driving the line, it acts as an output. In VHDL, this can be implemented by directly instanciating a primitive (e.g. IOBUF
for Xilinx device), or by letting your synthesis tool infer tristate buffer by describing logic as described above.
There are 3 signals you are dealing with here:
T
which is your tristate control. This signal will be derived from your synchronization logic, knowing the protocol of ULPI. That is since the bus is shared, there must be some way of knowing when you should be receiving data vs. sending data.
I
this is your input data that you wish to send across the bus, after being registered by the appropriate clock.
O
this is your output data that you are receiving over the bus, prior to any registering/synchronization.
Key: A tristate buffer is not synchronous. It is what you do before/after the tristate buffer that will properly synchronize your signal. In this case, you must synchronize your inputs to the tristate buffer (to be transmitted) on the rising clock edge, and registered data received from tristate buffer/IOBUF on the falling clock edge.
Sample Design.
library ieee;
use ieee.std_logic_1164.all;
library unisim; -- for xilinx IOBUF
use unisim.vcomponents.all;
entity iobuffer_example is
port (
I_CLK : in std_logic; -- synchronized with bidir bus
IO_DATA : inout std_logic; -- data to/from external pin on bidir bus
I_DIR_CTRL : in std_logic; -- from other VHDL logic, controlling bidir bus direction
O_DATA_FROM_EXTERNAL : out std_logic; -- data received over bidir bus
I_DATA_TO_EXTERNAL : in std_logic); -- data to send over bidir bus
end entity iobuffer_example;
architecture io_buffer_arch of iobuffer_example is
signal data_in : std_logic;
signal data_out : std_logic;
begin
IOBUF_Inst : IOBUF
port map (
O => data_in, -- data from bidir bus
IO => IO_DATA, -- data on bidir bus
I => data_out, -- data to bidir bus
T => I_DIR_CTRL); -- 3-state enable input, high=input, low=output
Register_Input : process (I_CLK) is
begin
if (falling_edge(I_CLK)) then
O_DATA_FROM_EXTERNAL <= data_in;
end if;
end process Register_Input;
Register_Output : process (I_CLK) is
begin
if (rising_edge(I_CLK)) then
data_out <= I_DATA_TO_EXTERNAL;
end if;
end process Register_Output;
end architecture io_buffer_arch;
Notes.
Be mindful of cross clock domain crossings. There are many possible crossings here for data going out and coming from the bus, especially if your internal logic is driven on a different clock than the bus clock. I can't make a suggestion without more detail.
If you would like a behavioral representation of the tristate buffer to be inferred by the synthesis tools, can you could do something like this, instead of using unisim library and IOBUF
:
PROCESS (I_DIR_CTRL, IO_DATA)
BEGIN
IF( I_DIR_CTRL = '1') THEN
IO_DATA <= 'Z';
ELSE
IO_DATA <= data_out;
END IF;
data_in <= IO_DATA;
END PROCESS;