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:
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:
- Name physical pins with readable logical names.
- Instantiate synchronization/debounce at the input boundary.
- Instantiate functional blocks.
- Connect outputs.
- 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:
- Packages.
- Package bodies.
- Leaf entities.
- Entities that instantiate them.
- Testbench packages.
- 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?