Skip to content

VHDL · Chapter 1.6 · Foundation

Ports and Port Modes

A port is a wire on the boundary of an entity, the pins of the block and therefore its external contract with the rest of the hardware. Every port carries a mode that fixes the direction data may flow and whether the block may read it, drive it, or both. This lesson covers the four modes, in, out, inout, and buffer, and the read-versus-drive rule each one enforces. You will see which modes you actually use in real RTL and which are legacy, and the single most common beginner trap of reaching for buffer when an ordinary internal signal is the right answer instead.

Foundation13 min readVHDLPortsPort ModesinoutbufferRTL Design

1. Intuition — ports are the pins of the block

In the last lesson the entity was a black box and its ports were the pins on the boundary. A port is exactly that: a named wire that crosses the edge of the block, the only way signals get in or out. Everything a block promises to the outside world is expressed through its ports — they are its contract.

A contract needs a direction. A pin labelled "clock input" must be driven from outside and read inside; a pin labelled "result output" is the opposite. VHDL makes that direction explicit with a port's mode, and then enforces it — try to drive an input from inside the block and the compiler stops you. That enforcement is a feature: it catches a whole class of wiring mistakes before simulation.

2. The four modes

ModeData flowInside the block you may…Real-world use
inoutside → blockread it (never drive it)clocks, resets, data inputs — everywhere
outblock → outsidedrive it (treat as write-only)results, status, data outputs — everywhere
inoutboth directionsread and drive (resolved type)tri-state pads, shared buses — occasional
bufferblock → outside, readable backdrive it and read it backdiscouraged — legacy

Two of these — in and out — are the entire vocabulary of most RTL. inout shows up at true bidirectional boundaries (a chip's external data bus, an I²C/SDA line). buffer is a historical wart you should recognise but rarely write.

3. A multi-mode entity

Here is a small block that uses several modes at once — note the comment on each port declaring who drives it:

port_demo.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
library ieee;
use ieee.std_logic_1164.all;
 
entity port_demo is
  port (
    clk      : in    std_logic;                     -- driven outside, read inside
    rst_n    : in    std_logic;                     -- driven outside, read inside
    data_in  : in    std_logic_vector(7 downto 0);  -- driven outside
    data_out : out   std_logic_vector(7 downto 0);  -- driven inside
    sda      : inout std_logic                       -- bidirectional (tri-state)
  );
end entity port_demo;

The entity says nothing about behaviour — only the shape and direction of each pin. An in port is a value the block is handed; an out port is a value the block must produce; an inout port is a wire the block sometimes drives and sometimes releases (to high-impedance 'Z') so something else can drive it.

4. Directionality as hardware

It helps to see the contract. Inputs arrive on one side, outputs leave on the other, and a bidirectional port is the one wire that does both:

A block boundary showing in, out, and inout port directionsclkin →data_inin →port_demoports = the contractdata_out→ outsda↔ inout12
Ports are the directional boundary of a block. in ports carry values inward (the block reads them); out ports carry values outward (the block drives them); an inout port is a single physical wire the block may drive or release, so data can flow either way. Code inside the block sees only these ports — they are the complete external contract.

5. The read-vs-drive rule, in a waveform

Direction is not bookkeeping — it is what makes a port an input or an output in hardware. A clean way to feel it: the in ports are stimulus (they come from outside) and the out ports are response (the block produces them). For a tiny half-adder — sum <= a xor b; carry <= a and b; — the inputs are driven externally and both outputs are produced inside and flow out:

half-adder — in ports a, b are stimulus; out ports sum, carry are response

6 cycles
half-adder — in ports a, b are stimulus; out ports sum, carry are responsea=1,b=1 → sum=0, carry=1a=1,b=1 → sum=0, carry…a=1,b=1 → sum=0, carry=1a=1,b=1 → sum=0, carry…absumcarryt0t1t2t3t4t5
The in ports change because something outside drives them; the out ports change because the architecture computes them. That cause-and-effect — drive in, read out — is exactly what the modes encode.

6. Why beginners misuse buffer

Here is the trap, almost word for word how it happens. You write a counter whose count is an output, and inside you try to increment it:

the mistake
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
port ( q : out std_logic_vector(3 downto 0); ... );
...
q <= q + 1;   -- ✗ you are READING q, but q is mode `out`

Reading an out port like this is classically illegal (VHDL-2008 relaxed it, but relying on that hurts portability). The compiler complains, and the beginner "fixes" it by changing the mode to buffer — which does let you read the output back. It compiles, so it looks right. It is not: buffer ports carry connection restrictions that bite the moment you instantiate the block, and most teams ban them in code review.

The correct fix is an internal signal: drive it inside, then assign the port from it. The signal is freely readable; the port stays a clean out.

the fix — internal signal, port stays out
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
architecture rtl of counter is
  signal q_i : unsigned(3 downto 0) := (others => '0');  -- internal, readable
begin
  process (clk)
  begin
    if rising_edge(clk) then
      q_i <= q_i + 1;            -- read + drive the internal signal freely
    end if;
  end process;
  q <= std_logic_vector(q_i);    -- drive the `out` port from it
end architecture rtl;

Rule of thumb: if you need to read a value internally, it is an internal signal — an out port is for driving outward only. That single habit makes buffer unnecessary.

7. Common mistakes & what to watch for

  • Driving an in port from inside. Inputs are read-only inside the block; assign one and the compiler rejects it (correctly).
  • Reading an out port. Treat out as write-only; use an internal signal if you need the value back, rather than switching to buffer.
  • Using buffer to dodge a read error. It compiles but creates connection restrictions and review friction — reach for an internal signal instead.
  • Reaching for inout when you do not have a true bidirectional wire. inout implies tri-state drive ('Z') and a resolved type; do not use it just to move data both ways at different times — that is two ports.

8. Summary & next step

A port is a wire on the entity boundary and the block's external contract; its mode fixes direction and the read/drive rule. In practice you live in in and out; inout is for genuine bidirectional wires; buffer is legacy and almost always replaced by an internal signal. Getting modes right means the compiler catches your wiring mistakes for free.

Ports carry typed values — std_logic, std_logic_vector, and the rest — and those types come from libraries. The next lesson covers libraries and the use clause: where std_logic actually comes from, what library and use do, and how ieee and work give your design its types, arithmetic, and shared definitions.