Skip to content

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

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

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

conversion map among std_logic_vector, unsigned, signed, and integercast (free)to_integer /to_unsigned(i,n)width changestd_logic_vectorbits, NO numeric meaningunsigned / signedsame bits + numeric meaningintegerabstract number, no widthresize(arg, n)zero/sign-extend ortruncate12
The conversion map between the four common types. std_logic_vector, unsigned, and signed share the same bits, so casts between them are free reinterpretations (relabel the wires, no logic). Crossing to or from integer is a value conversion that preserves the number: to_integer goes to integer, and to_unsigned(i,n) / to_signed(i,n) come back with an explicit width because integer has none. resize(arg,n) changes width by zero-extending (unsigned), sign-extending (signed), or truncating. Choosing the right crossing keeps numeric meaning correct. This is a type-relationship structure, captured by a map rather than a waveform.

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.

match_meaning_and_width.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- 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 signed for two's-complement data, unsigned for 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_unsigned with 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_vector come from std_logic_arith; use numeric_std's to_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.