Loading header...

VHDL Entities, Architectures, Signals, and Types

VHDL becomes much easier when you separate a hardware block into its public contract and its internal implementation. The entity says how the block connects to the rest of the design. The architecture says what hardware implements that contract.

Learning Objectives

You will learn to:

  • write complete entity and architecture declarations;
  • choose std_logic, std_logic_vector, unsigned, and signed correctly;
  • declare internal signals and constants;
  • use named association when instantiating another VHDL block;
  • avoid width, direction, and conversion mistakes that cause hardware bugs.

Entity: The Block Boundary

library ieee;
use ieee.std_logic_1164.all;

entity counter8 is
    port (
        clk   : in  std_logic;
        rst   : in  std_logic;
        en    : in  std_logic;
        count : out std_logic_vector(7 downto 0)
    );
end entity counter8;

The entity should be readable without opening the implementation. A reviewer should immediately know which signals are clocks, resets, enables, inputs, outputs, and buses.

Architecture: The Implementation

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

architecture rtl of counter8 is
    signal count_r : unsigned(7 downto 0) := (others => '0');
begin
    count <= std_logic_vector(count_r);
end architecture rtl;

Declarations appear before begin. Concurrent logic, processes, and instantiations appear after begin.

Signals, Variables, and Constants

Item Scope and update behavior Common use
signal connects concurrent logic; updates after process suspension or clock edge wires, registers, internal buses
variable updates immediately inside a process local calculation in a process
constant fixed value after elaboration widths, limits, timing counts
generic configurable value on an entity reusable widths, clock dividers, feature options

Use signals for hardware connections. Use variables sparingly and locally when they make a process easier to read.

Types You Will Use Often

Type Meaning Arithmetic?
std_logic one resolved digital signal with values such as '0', '1', 'Z', 'X' no
std_logic_vector group of bits with no numeric meaning by itself no
unsigned vector interpreted as a non-negative binary number yes
signed vector interpreted as two's-complement number yes
enumerated type named choices such as FSM states not normally

Use ieee.numeric_std.all for arithmetic. Avoid legacy packages such as std_logic_arith, std_logic_unsigned, and std_logic_signed.

Vector Direction and Indexing

Most FPGA VHDL code uses downto for buses:

signal byte_data : std_logic_vector(7 downto 0);
signal low_nib   : std_logic_vector(3 downto 0);

low_nib <= byte_data(3 downto 0);

The direction is part of the type. Mixing 7 downto 0 and 0 to 7 can be valid, but it makes review and slicing easier to get wrong. Pick a project convention and use it consistently.

Type Conversion Example

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

signal raw_count : std_logic_vector(7 downto 0);
signal count_u   : unsigned(7 downto 0);
signal count_out : std_logic_vector(7 downto 0);

count_u   <= unsigned(raw_count);
count_out <= std_logic_vector(count_u + 1);

The cast tells the tools and reviewers when a bit field is being treated as a number.

Generic Width Example

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

entity pulse_counter is
    generic (
        WIDTH : positive := 16
    );
    port (
        clk   : in  std_logic;
        rst   : in  std_logic;
        pulse : in  std_logic;
        count : out std_logic_vector(WIDTH - 1 downto 0)
    );
end entity pulse_counter;

Generics make reusable blocks possible, but every generic value must still be checked against timing, resource use, and interface width.

Clean Instantiation

Prefer direct entity instantiation with named association:

u_filter : entity work.edge_filter
    generic map (
        STABLE_CYCLES => 50000
    )
    port map (
        clk       => clk,
        rst       => rst,
        noisy_in  => button_sync,
        clean_out => button_clean
    );

Named association prevents accidental port swaps. Positional association is compact, but it is easy to break when a port list changes.

Structure Diagram

flowchart TB ENTITY["Entity: ports and generics"] --> ARCH["Architecture rtl"] ARCH --> DECL["Declarations before begin"] DECL --> SIG["Signals"] DECL --> CONST["Constants"] DECL --> TYPES["Types"] ARCH --> BODY["Concurrent body after begin"] BODY --> ASSIGN["Assignments"] BODY --> PROC["Processes"] BODY --> INST["Instantiations"]

Worked Example: Nibble Swap

library ieee;
use ieee.std_logic_1164.all;

entity nibble_swap is
    port (
        din  : in  std_logic_vector(7 downto 0);
        dout : out std_logic_vector(7 downto 0)
    );
end entity nibble_swap;

architecture rtl of nibble_swap is
begin
    dout(7 downto 4) <= din(3 downto 0);
    dout(3 downto 0) <= din(7 downto 4);
end architecture rtl;

If din = x"A5" (1010_0101), then dout = x"5A" (0101_1010).

Exercise

Create an entity named status_pack.

Requirements:

  • inputs: ready, fault, and busy as std_logic;
  • input: error_code as std_logic_vector(4 downto 0);
  • output: status as std_logic_vector(7 downto 0);
  • bit mapping: status(7)=ready, status(6)=fault, status(5)=busy, status(4 downto 0)=error_code.

Also state whether status is numeric data or a packed bit field.

Explained Solution

library ieee;
use ieee.std_logic_1164.all;

entity status_pack is
    port (
        ready      : in  std_logic;
        fault      : in  std_logic;
        busy       : in  std_logic;
        error_code : in  std_logic_vector(4 downto 0);
        status     : out std_logic_vector(7 downto 0)
    );
end entity status_pack;

architecture rtl of status_pack is
begin
    status <= ready & fault & busy & error_code;
end architecture rtl;

status is a packed bit field, not a number. Do not convert it to unsigned unless a later block explicitly treats it as numeric data.

Common Mistakes

  • Performing arithmetic directly on std_logic_vector.
  • Reversing vector order by mixing to and downto.
  • Using positional port maps for blocks with many ports.
  • Reusing a port name for an internal signal.
  • Forgetting that an out port may not be readable in older VHDL styles.
  • Hiding magic numbers instead of naming them as constants or generics.

Summary

An entity defines the interface; an architecture implements it. Signals connect hardware inside the architecture, and explicit types make design intent reviewable. Use std_logic_vector for raw bits, unsigned or signed for arithmetic, and named association for instantiations.

Next: Combinational Logic in VHDL.

Further Reading

Mind Map

mindmap root((VHDL Structure)) Core concept Entity is contract Architecture is implementation Signals connect hardware Types carry intent Applications Reusable IP blocks Top level wrappers Bus packing Parameterized counters Design rules Use named ports Keep vector order consistent Use numeric_std Prefer clear constants Practical checks Check port direction Check bus width Compile after edits Review conversion points Common mistakes Positional port swaps std_logic_vector arithmetic downto mismatch Hidden magic numbers