VHDL · Chapter 10.3 · Packages and Reuse
Shared Types and Subtypes in Packages
Constants let a design share its numbers, and types let it share its structure. Putting types and subtypes in a package means the modules that must talk to each other agree on their interface definitions by reference, rather than by hoping their separate declarations happen to match. Shared enumerations give every unit the same state or command set, shared subtypes give every port the same width, and shared records bundle an entire interface, a whole bus with all its signals, into one type, so a master, a slave, and a monitor all connect through the identical definition. This lesson shows how shared types eliminate interface mismatch and shrink sprawling port lists into a single, consistent bundle.
Foundation13 min readVHDLTypesRecordsPackagesInterfacesReuse
1. Engineering intuition — agree on structure, not just numbers
When two modules exchange data, they must agree not only on widths but on structure: what fields an interface
has, what a command can be, how a transaction is shaped. If each module declares its own version of that
structure, the two can silently diverge — one adds a field, the other does not, and the interface breaks. A
shared type in a package fixes the structure in one place: the master, the slave, and the monitor all refer to
the same bus_t record or cmd_t enum, so they cannot disagree about the interface. Sharing types is how you
make connectivity correct by construction.
2. Formal explanation — enums, subtypes, and records in a package
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all;
package bus_pkg is
constant ADDR_W : integer := 16;
constant DATA_W : integer := 32;
-- shared SUBTYPES: every port that carries an address/data uses these.
subtype addr_t is std_logic_vector(ADDR_W-1 downto 0);
subtype data_t is std_logic_vector(DATA_W-1 downto 0);
-- shared ENUM: one command set for the whole design.
type cmd_t is (NOP, READ, WRITE);
-- shared RECORD: a whole bus bundled into one type (the classic reuse pattern).
type bus_req_t is record
cmd : cmd_t;
addr : addr_t;
data : data_t;
valid: std_logic;
end record;
end package;A package can declare enumerations (shared state/command sets), subtypes (shared widths), array types (shared memory/word types), and records (bundled interfaces). The record is the high-value one: it gathers many related signals into a single named type, so an interface is passed as one object instead of a dozen separate ports.
3. Production usage — one record interface, many components
library work; use work.bus_pkg.all;
library ieee; use ieee.std_logic_1164.all;
-- A master, a slave, and a monitor all use the SAME bus_req_t — no mismatch possible.
entity master is port ( clk : in std_logic; req : out bus_req_t ); end entity;
entity slave is port ( clk : in std_logic; req : in bus_req_t ); end entity;
entity monitor is port ( clk : in std_logic; req : in bus_req_t ); end entity;
-- Top level wires them through one signal of the shared record type.
architecture top of system is
signal bus_req : bus_req_t;
begin
u_m : entity work.master port map (clk => clk, req => bus_req);
u_s : entity work.slave port map (clk => clk, req => bus_req);
u_v : entity work.monitor port map (clk => clk, req => bus_req);
end architecture;What hardware does this become? The record is just a grouping — it synthesises to the same wires as the
individual signals (cmd, addr, data, valid), but in the source it is one port and one signal. Because
master, slave, and monitor all use bus_req_t from the package, they are guaranteed to agree on the interface;
adding a field changes all three consistently. The reuse and consistency are at the source level; the netlist is
identical to hand-listing the signals.
4. Structural interpretation — one type, all participants
5. Why this is structural, not timing — and the record on the bus
This lesson is about type sharing, a source-level structure, so a structural diagram (above) communicates it
better than a waveform — the record carries the same signals it always did, just bundled. The run-time behaviour
is simply that the record's fields (cmd, addr, data, valid) move together as one interface. The payoff is
entirely at design time: fewer ports to wire, no per-module type declarations to drift, and a single place to
evolve the interface. (Records also interact with how the bus is driven — each field is assigned like any signal;
the record just groups them.)
6. Debugging example — the interface that drifted (or the wrong field)
Expected: master and slave interoperate cleanly. Observed: a type mismatch at the port map, or fields
connected wrongly (data where address was expected). Root cause: the two modules declared their own,
slightly different interface types (one added a field, or ordered them differently), or a hand-wired interface
connected the individual signals in the wrong order — exactly the drift a shared type prevents. Fix: declare
the interface once as a record in a package and have every module use bus_req_t; pass the whole record
rather than wiring loose signals by hand. Engineering takeaway: interface mismatches and mis-wirings between
modules usually mean the interface type was duplicated or hand-wired — share one record type and connect it as a
unit.
-- BUG: each module declares its own 'bus' type → they drift; or signals wired individually (misordered).
-- FIX: one shared record; pass it whole.
port ( req : in bus_req_t ); -- both sides use work.bus_pkg.bus_req_t → consistent by construction7. Common mistakes & what to watch for
- Duplicating interface types per module. They drift; declare the interface once as a package type and use it everywhere.
- Hand-wiring bundled signals individually. Error-prone ordering; pass the record as one port.
- Forgetting that records group, not transform. A record is the same wires bundled; field access is ordinary signal access.
- Over-bundling unrelated signals. Group signals that genuinely belong to one interface; do not stuff unrelated things into a record.
- Mixing directions in one record carelessly. A simple record has one direction per port; bidirectional interfaces may need two records (request/response) or careful mode handling.
8. Engineering insight & continuity
Shared types make interoperating modules agree on structure by reference: shared enums for command/state sets, shared subtypes for widths, and especially shared records that bundle a whole interface into one type so a master, slave, and monitor connect through the identical definition — eliminating mismatch and collapsing long port lists. Types and constants together let a package be the single source of truth for both a design's numbers and its structure. For all this to compile, the package must be analysed before its users — which is exactly the subject of the next lesson, Libraries, work, and Compilation Order: how VHDL libraries work and why dependency order matters.