File Structure
1. Library Declaration (Choosing the resources)
In VHDL, many things like std_logic signals, arithmetic operations, etc. are already pre-defined in libraries.
To use them, we declare libraries at the beginning of the code:
2. Entity Declaration (Outer part of the Black Box)
You can imagine Entity as a Black Box.
​
-
Generics act like parameters (for example: width, size, range).
-
Ports are like input-output pins (wires that connect to the outside world).
The Entity only defines what is visible from outside.
It does not describe what happens inside.
3. Architecture Declaration (Inner logic of the Black Box)
The Architecture describes the internal working of the Black Box.
​
-
Here we write the actual logic of the circuit.
-
We can define signals, constants, state machines, counters, arithmetic, etc. inside the architecture.

When writing VHDL code, we always follow 3 main steps:
Library --> Entity --> Architecture
Let us understand this with our asynchronous up/down counter
VHDL Counter Example (with Load, Enable, Up/Down)
We first include the required libraries for std_logic and arithmetic (unsigned type):
1. Library Declaration
VHDL
LIBRARY IEEE;
USE IEEE.std_logic_1164.all;
USE IEEE.numeric_std.all;
2. Entity Declaration (Black Box Ports)
Entity is like the outer shell of the circuit. Here we declare inputs and outputs:
VHDL
ENTITY counter IS
PORT (
clock : IN std_logic; -- clock signal
rst : IN std_logic; -- reset (active low)
ENABLE : IN std_logic; -- enable counting
LOAD : IN std_logic; -- load counter_in into counter
UP_DOWN : IN std_logic; -- 1 = up, 0 = down
counter_in : IN std_logic_vector(3 DOWNTO 0); -- load value
counter_out : OUT std_logic_vector(3 DOWNTO 0) -- current counter output
);
END counter;
Explanation:
-
clock → controls counting (on rising edge).
-
rst → reset signal (here it works when rst = '0').
-
ENABLE → allows counting only when high.
-
LOAD → puts a given input value into the counter.
-
UP_DOWN → decides counting direction.
-
counter_in → value to load.
-
counter_out → current counter value.
3. Architecture (Internal Logic)
Now we define what happens inside the box.
VHDL
ARCHITECTURE behavioral OF counter IS
SIGNAL counter_out_tmp : unsigned(3 DOWNTO 0); -- unsigned for arithmetic
BEGIN
counter_proc : PROCESS (rst, clock)
BEGIN
IF (rst = '0') THEN
counter_out_tmp <= (OTHERS => '0'); -- reset to zero
ELSIF rising_edge(clock) THEN
IF (LOAD = '1') THEN
counter_out_tmp <= unsigned(counter_in); -- load value
ELSIF (ENABLE = '1') THEN
IF (UP_DOWN = '1') THEN
counter_out_tmp <= counter_out_tmp + 1; -- count up
ELSE
counter_out_tmp <= counter_out_tmp - 1; -- count down
END IF;
END IF;
END IF;
END PROCESS;
counter_out <= std_logic_vector(counter_out_tmp); -- convert back
END behavioral;
Explanation:
-
On reset → counter becomes 0.
-
On each clock rising edge:
-
If LOAD = 1 → put counter_in into counter.
-
Else if ENABLE = 1 → counter changes:
-
UP_DOWN = 1 → increment.
-
UP_DOWN = 0 → decrement.
-
-
So this counter is very flexible: it can reset, load a value, enable/disable counting, and count up or down.
​
Extra component of File:
​
Now we will understand how a package is created from this, let's see
Package Declaration:
In VHDL, creating a package means collecting many related things in one place, such as:
​
-
Constants
-
Types / Subtypes
-
Functions / Procedures
-
Reusable Components (Modules/Entities)
And whatever is necessary for your design, you can use from it.
In simple terms, a package is like a box where you store reusable and essential items, so you don’t have to write them repeatedly in different files.
​
Why do we use a package in VHDL?
-
A package in VHDL is used to reuse common code in multiple design files.
-
Instead of writing the same definitions again and again, we keep them once in a package.
-
Inside a package, we usually put:
-
constants
-
types
-
functions/procedures
-
component declarations (like a blueprint of an entity)
-
Then, in any RTL file, we just write:
use work.my_package.all;
and we can reuse those definitions and components.
​
So, if we want to use the same design in another RTL file, we declare its component in a package and then instantiate it wherever needed.
​
How to reuse a VHDL Counter in multiple files
In VHDL, you cannot put the entire entity + architecture inside a package.
A package is only for:
-
Constants
-
Types
-
Functions/Procedures
-
Component Declarations (like a "blueprint" of the entity)
So the correct way is:
Step 1: Write your counter separately
Put the full counter (entity + architecture) in its own file, e.g. counter.vhd
VHDL
-- counter.vhd
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity counter is
port (
clock : in std_logic;
rst : in std_logic;
ENABLE : in std_logic;
LOAD : in std_logic;
UP_DOWN : in std_logic;
counter_in : in std_logic_vector(3 downto 0);
counter_out : out std_logic_vector(3 downto 0)
);
end counter;
architecture behavioral of counter is
signal counter_out_tmp : unsigned(3 downto 0);
begin
process (rst, clock)
begin
if (rst = '0') then
counter_out_tmp <= (others => '0');
elsif rising_edge(clock) then
if (LOAD = '1') then
counter_out_tmp <= unsigned(counter_in);
elsif (ENABLE = '1') then
if (UP_DOWN = '1') then
counter_out_tmp <= counter_out_tmp + 1;
else
counter_out_tmp <= counter_out_tmp - 1;
end if;
end if;
end if;
end process;
counter_out <= std_logic_vector(counter_out_tmp);
end behavioral;
Step 2: Create a package with component declaration
Now make a package file, e.g. up_down_pkg.vhd
VHDL
-- up_down_pkg.vhd
library ieee;
use ieee.std_logic_1164.all;
package up_down_pkg is
component counter
port (
clock : in std_logic;
rst : in std_logic;
ENABLE : in std_logic;
LOAD : in std_logic;
UP_DOWN : in std_logic;
counter_in : in std_logic_vector(3 downto 0);
counter_out : out std_logic_vector(3 downto 0)
);
end component;
end package up_down_pkg;
This acts like a header file (like in C/C++).
Step 3: Use the package in another design
In your top file, import the package and instantiate the counter: e.g., top.vhd
VHDL
library ieee;
use ieee.std_logic_1164.all;
use work.up_down_pkg.all; -- import the package
entity top is
port (
clk : in std_logic;
rst : in std_logic;
q_out : out std_logic_vector(3 downto 0)
);
end top;
architecture rtl of top is
begin
u1: counter
port map (
clock => clk,
rst => rst,
ENABLE => '1',
LOAD => '0',
UP_DOWN => '1',
counter_in => "0000",
counter_out => q_out
);
end rtl;
Here inside top file up/down counter is being instantiated which is being done by port mapping which I will explain in further lectures, when the design is very large and one component is used in another design then it is port mapped.
A VHDL package can contain as many components as you want, such as constants, types, subtypes, functions, procedures, and reusable component declarations.
There is no strict limit defined by VHDL, but practical limits depend on your tool/compiler and design complexity.
For better readability and reuse, related components should be grouped together, and very large packages can be split into smaller logical packages.
Important for Interview:
Till now you might not have understood what is a library, its uses and work library. Let's understand this
library ieee;
-
This tells VHDL that you want to use a predefined collection of useful code called a library.
-
ieee is a standard library provided for VHDL designs.
use ieee.std_logic_1164.all;
-
From the ieee library, we are using everything (all) from the std_logic_1164 package.
-
This package gives us standard logic types like std_logic and std_logic_vector, which are used to represent signals like 0, 1, unknown (X), high-impedance (Z), etc.
use work.up_down_pkg.all;
-
work is the current project/library where your own code resides.
-
up_down_pkg is a package you or someone created that contains reusable code, like constants, types, or functions.
-
all means you are importing everything inside that package.
This is useful if you want to reuse your counter types, constants, or helper functions in multiple VHDL files without rewriting them.