Skip to content

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

shared_types_pkg.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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

record_interface.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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

shared record bus type used by master, slave, and monitoruseuseusebus_pkg: bus_req_tcmd, addr, data, validmasterdrives bus_req_tslaveconsumes bus_req_tmonitorobserves bus_req_t12
A shared record type bundles an interface into one definition that every participant uses. The package declares bus_req_t (cmd, addr, data, valid); the master drives it, the slave consumes it, and the monitor observes it — all through the same type imported from the package. Because there is one definition, the modules cannot disagree about the interface, and a change (adding a field) propagates to all of them consistently. The record is a source-level grouping; it synthesises to the same individual wires, so a structural diagram, not a waveform, captures it.

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.

share_the_interface_type.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- 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 construction

7. 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.