VHDL · Chapter 12.2 · Generics
Width-Generic Design
By far the most common use of generics is parameterizing data-path width. A single width generic sizes every port and internal signal, and the trick is to write the logic width-agnostically so it scales without edits. That means an all-zeros aggregate for reset regardless of width, the range and length attributes for loops and slices, and numeric-standard arithmetic that automatically matches the operand width. Done well, one adder, multiplexer, or register entity serves a 4-bit and a 64-bit instance identically. Related widths can also be derived from one generic, so an address size stays consistent with a depth. This lesson shows how to build width-generic blocks and the idioms that keep the logic independent of any hard-coded width.
Foundation14 min readVHDLGenericsWidthData Pathnumeric_stdReuse
1. Engineering intuition — write the logic, not the width
A good width-generic block is written as if you do not know — and do not care — how wide it is. The reset is "all
zeros" (whatever that means at this width), the loop runs "over the whole vector," the add is "these two numbers."
Each of those phrasings has a width-independent VHDL form, and when you use them, the same source compiles to a
4-bit block or a 64-bit block with only the WIDTH generic changing. The skill is reflexively reaching for the
width-agnostic idiom — (others => '0'), 'range, numeric_std arithmetic — instead of hard-coding a number
that would pin the design to one size.
2. Formal explanation — width-agnostic idioms
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all;
entity adder is
generic ( WIDTH : positive := 8 );
port ( a, b : in std_logic_vector(WIDTH-1 downto 0); -- ports sized by the generic
sum : out std_logic_vector(WIDTH downto 0) ); -- one extra bit for the carry
end entity;
architecture rtl of adder is
begin
-- numeric_std arithmetic scales to WIDTH automatically; resize keeps the carry bit.
sum <= std_logic_vector( resize(unsigned(a), WIDTH+1) + resize(unsigned(b), WIDTH+1) );
end architecture;
-- Width-agnostic idioms used across width-generic code:
-- (others => '0') -- zero/reset at ANY width
-- for i in a'range loop -- iterate the whole vector, whatever its width
-- a'length, a'high, a'low -- query the actual width/bounds
-- clog2(DEPTH) -- DERIVE an address width from a depth genericThe pattern: size ports/signals with WIDTH-1 downto 0, then write logic with width-independent constructs —
(others => '0') for constants, 'range/'length/'high for iteration and bounds, and numeric_std
arithmetic that adapts to the operand width. Derived sizes (clog2) keep related widths consistent with one
generic.
3. Production usage — one block, any width
-- The SAME adder entity, two widths — identical source, different generic.
u_n4 : entity work.adder generic map (WIDTH => 4) port map (a => a4, b => b4, sum => s5);
u_n32 : entity work.adder generic map (WIDTH => 32) port map (a => a32, b => b32, sum => s33);
-- A width-generic register reset width-agnostically:
process (clk) begin
if rising_edge(clk) then
if rst = '1' then q <= (others => '0'); -- correct at WIDTH=4 or 32 — no literal
else q <= d;
end if;
end if;
end process;What hardware does this become? u_n4 elaborates a 4-bit ripple/carry adder with a 5-bit result; u_n32 a
32-bit adder with a 33-bit result — the same description, scaled. (others => '0') becomes a 4- or 32-bit
constant zero as appropriate. Because the arithmetic and constants are width-agnostic, the synthesizer simply
generates more or fewer bits of the identical structure. One verified source covers every width you instantiate.
4. Structural interpretation — the data path scales with WIDTH
5. Simulation interpretation — the same block at two widths
One width-generic adder: WIDTH=4 vs WIDTH=32, carry-out at the top bit either way
4 cycles6. Debugging example — the hard-coded width that did not scale
Expected: a block that works at any width. Observed: it works at 8 bits but breaks at 16/32 — truncated
results, a stuck reset value, or a width-mismatch error. Root cause: a hard-coded width leaked into the
otherwise-generic code: a literal (7 downto 0), an x"00" instead of (others => '0'), a fixed loop bound
0 to 7, or arithmetic sized to a constant rather than to WIDTH. Fix: replace every literal width with the
generic or a width-agnostic idiom — WIDTH-1 downto 0, (others => '0'), a'range, resize(..., WIDTH+1).
Engineering takeaway: a width-generic block must contain no hard-coded widths — use the generic and
(others=>...)/'range/numeric_std so the logic scales; a single literal pins it to one size.
-- BUG: a literal width and a fixed constant defeat genericity.
-- q <= x"00"; -- only correct at 8 bits
-- for i in 0 to 7 loop -- only correct at 8 bits
-- FIX: width-agnostic forms.
q <= (others => '0'); for i in q'range loop -- correct at any WIDTH7. Common mistakes & what to watch for
- Hard-coded widths or constants. A literal
(7 downto 0),x"00", or0 to 7pins the design to one size; useWIDTH-1 downto 0,(others => '0'),'range. - Forgetting the extra result bit. An add/accumulate needs
WIDTH+1(andresize) to keep the carry; otherwise it truncates. - Mixing widths in arithmetic.
numeric_stdrequires operands to match width —resizeto a common width before operating. - Not deriving related widths. Use
clog2(DEPTH)to size addresses/counts from one generic so they stay consistent. - Width-1 / zero-width edge cases. Very small generic values can create degenerate ranges; constrain generics
(e.g.
positive) and consider boundaries.
8. Engineering insight & continuity
Width-generic design is parameterization's flagship use: one WIDTH generic sizes the ports and signals, and
width-agnostic idioms — (others => '0'), 'range/'length, scaling numeric_std arithmetic, derived
clog2 widths — let the same verified source serve every width from 4 to 64 bits. The rule is to write the
logic, never the width. With width-generic blocks in hand, the next lesson focuses on the instantiation side —
Generic Maps and Instantiation — how to pass generic values cleanly (positional, named, defaults) and connect
parameterized components into a larger design.