VHDL · Chapter 11.6 · Functions and Procedures
Conversion Functions
A standard logic vector is just a bag of bits with no numeric meaning, unsigned and signed carry a number, and integer is an abstract value. Moving between them is the daily friction of VHDL, and the standard library gives you a precise toolkit. Type casts reinterpret the same bits between closely related array types and cost nothing in hardware. Value conversions such as to-integer, to-unsigned, and to-signed cross to and from integer, preserving the number and taking an explicit width where one is needed. The resize function changes width by zero-extending, sign-extending, or truncating. This lesson maps every crossing and which function to use, so your conversions are correct and intentional rather than guesswork.
Foundation14 min readVHDLConversionnumeric_stdunsignedsignedresize
1. Engineering intuition — bits versus numbers
The reason VHDL makes you convert is that its types mean different things. A std_logic_vector is a row of
bits with no agreed numeric interpretation — the tool will not assume it is a number. unsigned and signed are
the same bits but tagged with a meaning (plain binary, or two's complement). integer is an abstract whole
number with no fixed width. So crossing between them is either a reinterpretation (same bits, new label — a
cast) or a translation of value (number in, bits out, or vice versa — needing a width). Knowing which crossing
you are making tells you which function to reach for and whether it costs hardware.
2. Formal explanation — three families of conversion
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all;
signal slv : std_logic_vector(7 downto 0);
signal u : unsigned(7 downto 0);
signal s : signed(7 downto 0);
signal i : integer;
-- (1) TYPE CASTS: same bits, related array types, FREE (just relabel the wires).
u <= unsigned(slv); -- view the bits as unsigned
s <= signed(slv); -- view the bits as signed
slv <= std_logic_vector(u); -- view unsigned bits as a plain vector
-- (2) VALUE conversions to/from INTEGER: preserve the NUMBER; width given where needed.
i <= to_integer(u); -- unsigned/signed → integer
u <= to_unsigned(i, 8); -- integer → unsigned, width 8 (explicit)
s <= to_signed(i, 8); -- integer → signed, width 8 (explicit)
-- (3) RESIZE: change WIDTH, zero-extend (unsigned) / sign-extend (signed) / truncate.
u <= resize(u_4bit, 8); -- 4 → 8 bits (zero-extended)Three families: casts between std_logic_vector/unsigned/signed (same bits, free); value conversions
to/from integer (to_integer, to_unsigned(i,n), to_signed(i,n) — the integer side has no width, so the
vector side states one); and resize to change width with proper zero-/sign-extension or truncation.
3. Production usage — the canonical port-to-arithmetic-to-port flow
-- Ports are std_logic_vector (no numeric meaning); do arithmetic in numeric_std; convert back.
entity acc is port ( a, b : in std_logic_vector(7 downto 0); y : out std_logic_vector(8 downto 0) ); end;
architecture rtl of acc is
begin
-- cast inputs to a numeric type → add (resize so the sum keeps the carry) → cast back to the port.
y <= std_logic_vector( resize(unsigned(a), 9) + resize(unsigned(b), 9) );
end architecture;What hardware does this become? The casts (unsigned(a), std_logic_vector(...)) are free — they only
relabel wires, no gates. resize(..., 9) to a wider width is zero-extension (just a tied-'0' wire on the
top bit), and the + is the adder. So the whole expression is one 9-bit adder; the conversions are wiring, not
logic. (Truncating resize drops top bits — real, sometimes lossy; widening is free extension.) This
cast-arithmetic-cast pattern is the standard way to keep std_logic_vector ports while computing in numeric_std.
4. Structural interpretation — the conversion map
5. Why this is structural, not timing
Conversions are about type relationships, not behaviour over time, so the map above tells the story better than
a waveform. Their hardware cost is structural and known: casts are free rewiring; widening resize is
extension (tied bits); truncating resize drops bits; to_integer/to_unsigned are encodings that, between
numeric_std types and integer, also reduce to wiring for synthesis. None of these introduce state or delay —
they reshape or relabel a value in the same instant. The thing to get right is meaning (is this unsigned or
signed? what width?), which is exactly what choosing the correct conversion guarantees.
6. Debugging example — the cast that lied about sign (or width)
Expected: correct numeric result. Observed: a negative number read as a huge positive (or vice versa), or
upper bits lost. Root cause: the wrong conversion — casting a std_logic_vector to unsigned when it
represents a signed value (so the sign is misread), or assigning a wider result to a narrower target so resize/
assignment truncated the top bits, or feeding to_unsigned a negative integer. Fix: cast to the type
whose meaning matches the data (signed for two's-complement), resize to a width that holds the full result
(e.g. one extra bit for an add's carry) before narrowing, and keep value conversions within range. Engineering
takeaway: a cast changes only the label on the bits — pick unsigned vs signed to match the real meaning,
and size with resize so no significant bit is dropped.
-- BUG: signed data cast as unsigned → sign misread; sum truncated into 8 bits.
-- y <= std_logic_vector(unsigned(a) + unsigned(b)); -- a,b two's-complement; 8-bit result loses carry
-- FIX: signed meaning + a 9-bit result to keep the carry/sign.
y <= std_logic_vector( resize(signed(a),9) + resize(signed(b),9) );7. Common mistakes & what to watch for
- Casting to the wrong meaning. Use
signedfor two's-complement data,unsignedfor plain binary — the cast does not change bits, only interpretation. - Dropping bits by under-sizing.
resize/assign to a width that holds the full result (add needs +1 bit); narrowing truncates. - Confusing cast with value conversion.
unsigned(slv)reinterprets bits;to_unsigned(int, n)converts a number — they are not interchangeable. to_unsignedwith a negative / out-of-range integer. Out-of-range integer conversions are illegal/wrong; keep values in range.- Using the obsolete
conv_*functions.conv_integer/conv_std_logic_vectorcome fromstd_logic_arith; usenumeric_std'sto_integer/to_unsigned/casts.
8. Engineering insight & continuity
Conversion functions are how you cross VHDL's deliberate type boundaries correctly: casts reinterpret the same
bits for free between std_logic_vector/unsigned/signed, value conversions translate numbers to and from
integer with an explicit width, and resize changes width with proper extension or truncation — the
cast-arithmetic-cast pattern being the everyday flow for std_logic_vector ports. With overloading and
conversions covered, the module turns to the rule that governs all of this in practice: Synthesizable
Subprograms — exactly which subprogram constructs map to hardware and which are simulation-only.