Packages and Subprograms
Organize VHDL with reusable packages, functions and procedures.
Why Use a Package
A package groups what must be shared between several VHDL files:
- custom types;
- common constants;
- subtypes;
- function and procedure declarations;
- component declarations, when the project uses them.
Without a package, the same definitions are copied into several architectures. That often creates inconsistencies.
Package Declaration
The package declaration exposes the interface: what other files can use.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
package video_pkg is
subtype t_color is unsigned(7 downto 0);
type t_rgb is record
red : t_color;
green : t_color;
blue : t_color;
valid : std_logic;
end record t_rgb;
constant c_RGB_BLACK : t_rgb := (
red => (others => '0'),
green => (others => '0'),
blue => (others => '0'),
valid => '0'
);
function is_valid_pixel(i_pixel : t_rgb) return boolean;
end package video_pkg;The package does not describe hardware by itself. It provides reusable definitions.
Package Body
The package body contains the implementation of functions and procedures declared in the package.
package body video_pkg is
function is_valid_pixel(i_pixel : t_rgb) return boolean is
begin
return i_pixel.valid = '1';
end function is_valid_pixel;
end package body video_pkg;A constant or type can be fully defined in the package declaration. A function declared in the package must have its implementation in the package body.
Use a Package
In another file:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
library work;
use work.video_pkg.ALL;
entity pixel_gate is
port (
i_pixel : in t_rgb;
o_red : out std_logic_vector(7 downto 0)
);
end entity pixel_gate;
architecture rtl of pixel_gate is
begin
o_red <= std_logic_vector(i_pixel.red) when is_valid_pixel(i_pixel) else x"00";
end architecture rtl;If a type is used in an entity port, it must be visible before the entity declaration. This is one of the most important package use cases.
Function
A function computes and returns a value. It should stay simple, deterministic and free of timing waits.
function parity_even(i_data : std_logic_vector) return std_logic is
variable v_parity : std_logic := '0';
begin
for i in i_data'range loop
v_parity := v_parity xor i_data(i);
end loop;
return v_parity;
end function parity_even;Usage:
w_parity <= parity_even(i_data);In RTL, a function should describe a combinational calculation. It should not contain wait, clock generation or a test scenario.
Procedure
A procedure does not return a single value. It can modify out or inout parameters. In RTL, it is mostly used to factor small combinational sequences. In a testbench, it often applies stimuli.
procedure drive_byte(
signal o_bus : out std_logic_vector(7 downto 0);
constant i_value : in natural
) is
begin
o_bus <= std_logic_vector(to_unsigned(i_value, o_bus'length));
end procedure drive_byte;Testbench usage:
drive_byte(w_data, 16#A5#);
wait until rising_edge(i_clk);Function or Procedure
| Need | Choice |
|---|---|
| Compute one value | Function |
| Factor a testbench check | Procedure |
| Modify several outputs | Procedure |
| Share types and constants | Package |
Best Practices
- Put shared types in a clear and short package.
- Keep RTL functions free of
waitand side effects. - Use explicit names:
video_pkg,spi_pkg,test_utils_pkg. - Avoid catch-all packages that mix project types, testbench helpers, constants and components with no structure.
- Import only the packages needed by each file.
Key Point
A package improves project consistency. A function expresses a calculation. A procedure factors an action. Together, they make VHDL easier to maintain without making the design more complex.
📝 Test your knowledge - Chapter quiz