Correctly Interfacing to a Bidirectional Signal
Tristate is what you want.  Or, as you have an external pull-up resistor, an open-drain drive. You'll also see open-collector meaning the same thing - it depends on the style of transistors used, but the fundamental idea is the same.  ISO9141 (automotive diagnostics) operates this way for example.

Open-drain just means that the driver pin can only pull the external signal low, but cannot drive it high - the resistor will allow the line to "float" gently back up.  It also means multiple devices can drive the communication line, so you have to ensure (eg. through higher level protocol or a sidechannel) that only one device chats at a time.

To implement this in VHDL:

In your entity you need a pin:

TxRx_pin : inout std_logic;


and in the architecture two signals:

signal tx_sig, rx_sig : std_logic;


Wire them up like this:

TxRx_pin &lt;= '0' when tx_sig = '0' else 'Z';
rx_sig &lt;= to_X01(TxRx_pin);


The first line copies 0s from the transmitter onto the pin, but doesn't drive 1s, instead using the Z state which means "disconnected", allowing the external resistor to pull the line up.

The second line ensures that whatever appears on the pin is copied onto rx_sig.  The reason for the to_X01 call is to make the simulations work.  In your testbench, the pullup resistor is modelled by driving an H (a so-called weak pullup) onto the TxRx signal:

TxRx_sig &lt;= 'H';


Your FPGA logic will not be looking for Hs (in simulation) so will not work correctly - the to_X01 function converts H to 1 (amongst other things not relevant to this situation).

BTW, there is no need for a mux to turn off the reception in order to have a functional design, but you may want to keep it so you don't see the data you are transmitting.