Skip to content

Hierarchy, generics, and packages

Hierarchy makes large designs understandable. Each entity should have one clear responsibility, an explicit timing contract, and a test strategy.

Direct entity instantiation

u_counter : entity work.counter(rtl)
  generic map (
    G_WIDTH => 24
  )
  port map (
    clk_i    => clk_i,
    reset_i  => reset_i,
    enable_i => tick_s,
    count_o  => count_s
  );

Use named association. It is safer when ports are reordered or added.

work means the current compilation library, not a folder name.

Generics

Generics configure hardware during elaboration:

entity counter is
  generic (
    G_WIDTH : positive := 8
  );
  port (
    clk_i    : in  std_logic;
    reset_i  : in  std_logic;
    enable_i : in  std_logic;
    count_o  : out unsigned(G_WIDTH - 1 downto 0)
  );
end entity;

Derived widths

For a counter from zero through G_MAX_COUNT, compute the required width with a package function rather than guessing.

function clog2(value : positive) return natural is
  variable result : natural := 0;
  variable v      : natural := value - 1;
begin
  while v > 0 loop
    result := result + 1;
    v := v / 2;
  end loop;
  return result;
end function;

Be explicit about edge cases. If a zero-width vector could result, constrain the generic or return at least one.

Packages

Package declaration:

library ieee;
use ieee.std_logic_1164.all;

package display_pkg is
  subtype digit_t is std_logic_vector(3 downto 0);
  type digit_array_t is array (natural range <>) of digit_t;

  function hex_to_segments(digit : digit_t)
    return std_logic_vector;
end package;

Package body:

package body display_pkg is
  function hex_to_segments(digit : digit_t)
    return std_logic_vector is
  begin
    case digit is
      when x"0"   => return "1000000";
      when x"1"   => return "1111001";
      -- Add remaining digits.
      when others => return "1111111";
    end case;
  end function;
end package body;

Consumer:

use work.display_pkg.all;

Packages are appropriate for:

  • Shared types and subtypes.
  • Physical and protocol constants.
  • Pure conversion/reference functions.
  • Component declarations when an older binding style requires them.
  • Testbench procedures and scoreboards.

Avoid a single project-wide package that imports every dependency everywhere.

Functions and procedures

Pure function

A pure function depends only on its inputs and returns one result. It maps naturally to combinational logic and is excellent for reference models.

function parity(data : std_logic_vector) return std_logic is
  variable result : std_logic := '0';
begin
  for index in data'range loop
    result := result xor data(index);
  end loop;
  return result;
end function;

Procedure

A procedure may have multiple in, out, and inout parameters.

procedure pulse(
  signal target : out std_logic;
  constant width : time
) is
begin
  target <= '1';
  wait for width;
  target <= '0';
end procedure;

This waiting procedure is testbench-only.

Structural top-level pattern

Keep the FPGA top level mostly structural:

  1. Name physical pins with readable logical names.
  2. Instantiate synchronization/debounce at the input boundary.
  3. Instantiate functional blocks.
  4. Connect outputs.
  5. Keep algorithmic behavior in testable sub-entities.

This makes the board wrapper replaceable and avoids mixing pin polarity with core behavior.

Generic verification

A parameterized component should be tested at:

  • The smallest legal generic value.
  • A typical value.
  • A non-power-of-two value where applicable.
  • A boundary value large enough to expose width errors.

Do not assume testing only the default proves the generic implementation.

Compile order

VHDL analysis requires dependencies to be compiled first:

  1. Packages.
  2. Package bodies.
  3. Leaf entities.
  4. Entities that instantiate them.
  5. Testbench packages.
  6. Testbench tops.

Vivado usually manages order in projects. In command-line automation, list sources deliberately or use a project file.

Hierarchy checklist

  • Does each entity have one sentence describing its responsibility?
  • Are clocks, resets, enables, and handshakes obvious at the boundary?
  • Are generics typed and range-constrained?
  • Are derived constants centralized?
  • Are packages cohesive rather than global dumping grounds?
  • Can the functional core be simulated without FPGA pins?