VHDL type conversions
VHDL does not implicitly reinterpret a std_logic_vector as a number.
With numeric_std, arithmetic on std_logic_vector, indexing an array with
a vector, and assigning an integer to a vector all require an explicit
conversion. This page explains why, lists the numeric_std conversions,
and shows what each costs in hardware. For the types themselves, see
VHDL data types.
Why conversions are required
- Bits have no inherent meaning.
"1000"is 8 asunsignedand −8 assigned. Comparison, resizing and right shifts give different results for each, so the interpretation must be stated before arithmetic. - An integer does not carry a vector width. Converting an integer to a vector requires a width, and a rule for values that do not fit.
- Vector interpretation is explicit. VHDL does not silently treat a
std_logic_vectoras signed or unsigned. Verilog extends narrow operands, truncates wide results and mixes signed and unsigned operands implicitly, which is a common source of bugs.numeric_stddoes provide deliberate mixed-type overloads, such asunsigned + natural(see below).
Conversion table
| From ↓ / To → | std_logic_vector | unsigned | signed | integer |
|---|---|---|---|---|
std_logic_vector | unsigned(v) | signed(v) | to_integer(unsigned(v)) or to_integer(signed(v)) | |
unsigned | std_logic_vector(u) | signed(u) | to_integer(u) | |
signed | std_logic_vector(s) | unsigned(s) | to_integer(s) | |
integer | std_logic_vector(to_unsigned(i, N)) or std_logic_vector(to_signed(i, N)) | to_unsigned(i, N) | to_signed(i, N) |
All entries require ieee.numeric_std. There are two kinds:
- Type casts (
unsigned(v),signed(v),std_logic_vector(u)) convert between closely related array types. The bits are unchanged; only the type changes. - Conversion functions (
to_integer,to_unsigned,to_signed,resize) convert between a number and a vector, or between widths.
Notes:
- There is no direct
std_logic_vectortointegerconversion. Cast tounsignedorsignedfirst, because the result depends on the interpretation. - A cast between
signedandunsignedkeeps the bits but changes the value they represent:signed(u)for an 8-bit unsigned 200 gives −56.
Casts generate no hardware
This adder has std_logic_vector ports and casts to unsigned for the
addition:
Interactive schematic by RapidRTL
The schematic contains only the adder.
Exercise: change both unsigned casts to signed and click Run. The
circuit is unchanged, because at equal widths two’s complement addition
produces the same bits as unsigned addition.
resize
resize(arg, new_size) changes the width according to the type:
| Type | Widening | Narrowing |
|---|---|---|
unsigned | fills the new MSBs with '0' | keeps the rightmost bits |
signed | copies the sign bit into the new MSBs (sign extension) | keeps the sign bit and the rightmost bits |
y <= resize(a, 8); -- a: 4 bits, y: 8 bits
With a and y declared signed, bit 3 (the sign) drives bits 4 to 7:
Interactive schematic by RapidRTL
With a and y declared unsigned, bits 4 to 7 are constant 0:
Interactive schematic by RapidRTL
Neither version contains logic; the type alone selects the wiring.
shift_right follows the same rule: it shifts in the sign bit for signed
and '0' for unsigned.
Narrowing cannot preserve a value that does not fit. Resizing the 4-bit
signed −6 ("1010") to 3 bits gives −2 ("110"). Check the range before
narrowing.
Preserving the carry
+ returns the width of the wider operand, so the carry of an 8-bit
addition is discarded. Widen an operand before the addition:
signal a, b : unsigned(7 downto 0);
signal sum : unsigned(8 downto 0);
sum <= resize(a, 9) + b; -- 9-bit addition; the carry is sum(8)
resize(a + b, 9) is incorrect: the addition is performed at 8 bits, and
the carry is lost before the resize.
to_integer
Arrays are indexed by integers, so indexing with a vector requires
to_integer:
y <= data(to_integer(unsigned(sel)));
Interactive schematic by RapidRTL
In this example, to_integer adds no conversion logic; the indexing
synthesizes to the multiplexer.
- The result must fit the return type:
naturalforunsigned,integerforsigned. With a 32-bit integer, the largest unsigned result isinteger'high(231 − 1); a larger value causes a simulation error. 'L'and'H'bits are read as 0 and 1. If any bit is'U','X','Z','W'or'-', the referencenumeric_stdpackage returns 0 and reportsNUMERIC_STD.TO_INTEGER: metavalue detected, returning 0.
to_unsigned and to_signed
Both take the target width as the second argument:
count <= to_unsigned(0, count'length); -- width taken from the declaration
coeff <= to_signed(-75, 8);
- A value that does not fit is represented by its low-order bits:
to_unsigned(300, 8)returns 44. The referencenumeric_stdpackage also reportsNUMERIC_STD.TO_UNSIGNED: vector truncated. to_unsignedtakes anatural. A negative argument, such asto_unsigned(-1, 8), is a range error. Useto_signedfor negative values.- A conversion is often unnecessary in expressions.
numeric_stddefines its operators for anunsignedwith anatural, and asignedwith aninteger, socount + 1andcount = 9are legal. Assignment still requires matching types.
VHDL-2008 also provides overloads that take a vector instead of a width;
the result takes that vector’s length. They exist for to_unsigned,
to_signed and resize:
count <= to_unsigned(0, count); -- same as to_unsigned(0, count'length)
wide <= resize(narrow, wide); -- same as resize(narrow, wide'length)
Common errors
Example messages from GHDL. Wording varies by tool, version and language standard.
| Error | Cause | Fix |
|---|---|---|
no function declarations for operator "+" | arithmetic on std_logic_vector | cast to unsigned or signed |
can't match integer literal with type array type "UNRESOLVED_UNSIGNED" | integer assigned to a vector, e.g. u <= 5; | u <= to_unsigned(5, u'length); |
no overloaded function found matching "to_integer" | to_integer on a std_logic_vector | to_integer(unsigned(v)) or to_integer(signed(v)) |
no declaration for "unsigned" (due to conflicts) | numeric_std and std_logic_arith in one file | remove std_logic_arith (details) |
bound check failure (simulation) | value outside a range: to_integer above 31 bits, negative to_unsigned argument, constrained integer overflow | widen the type, use to_signed, or correct the logic |
Rules
- Keep top-level ports
std_logic_vector. Cast tounsignedorsignedfor arithmetic, and back when driving the port. - Casts between
std_logic_vector,unsignedandsignedcost no hardware. - Widen with
resizebefore an operation that can overflow, not after. - Pass the target’s
'lengthas the width toto_unsignedandto_signed. - Do not cast between
signedandunsignedto convert a value. A cast keeps the bits; the number they represent changes.
References
- IEEE Std 1076-2019, IEEE Standard for VHDL Language Reference Manual (IEEE), the current standard.
- IEEE Std 1076-2008, IEEE Standard VHDL Language Reference Manual
(IEEE), the previous
edition: §9.3.6 type conversions and closely related types; Clause 16
incorporates
numeric_std, which definesto_integer,to_unsigned,to_signedandresize. - IEEE Std 1076.3-1997, IEEE Standard VHDL Synthesis Packages
(IEEE): historical origin
of
numeric_std. - IEEE package sources, VHDL Analysis and Standardization Group:
opensource.ieee.org/vasg/Packages.
The package declaration (
numeric_std.vhdl) documents signatures, result widths andresizebehaviour; the package body (numeric_std-body.vhdl) shows the truncation and metavalue warnings. - VHDL data types and their ranges.