VHDL · Chapter 2.4 · Data Types
std_logic_vector and Buses
A single standard logic signal is one wire, but real designs move data on buses, many wires at once. A standard logic vector is exactly that: an array of standard logic bits, the type every address, data path, and register in VHDL is built from. Each element is a full nine-valued bit, so a bus can carry data, be released bit by bit, or show conflict only on the bits that disagree. This lesson covers how a bus is declared and indexed with the downto and to directions, how to slice and concatenate it, and the rule that catches everyone: a vector is a bag of bits with no numeric meaning, so arithmetic lives in the numeric packages, not on the vector itself. Keeping those two ideas separate is the point of strong typing.
Foundation14 min readVHDLstd_logic_vectorBusesSlicingnumeric_stdTypes
1. Intuition — a bus is wires bundled together
Hardware rarely carries one bit. An address is 16 wires, a data word is 32, a register is 8 — and treating each as a separate signal would be unmanageable. A bus bundles those wires into one named signal you can route, slice, and connect as a unit.
std_logic_vector is that bundle: an array of std_logic. Every element is a full
nine-valued std_logic (lesson 2.2), so a bus can carry data, be released to 'Z'
bit-by-bit, or show 'X' on exactly the bits in conflict. It is the single most-used type
in RTL.
2. Declaring and indexing — downto vs to
A vector declaration gives a range, and the direction matters:
signal data : std_logic_vector(7 downto 0); -- 8 bits, bit 7 is the MSB (left)
signal asc : std_logic_vector(0 to 7); -- 8 bits, bit 0 is the leftmostdowntois the RTL convention for numeric buses: the MSB is on the left, the LSB (bit 0) on the right — matching how you write binary.data(7)is the top bit,data(0)the bottom.tocounts the other way (ascending). It is legal and useful for things like byte arrays, but using it for a numeric bus inverts bit significance and is a classic source of reversed-bit bugs.
Pick downto for anything that represents a number, and stay consistent.
3. The bit layout
It helps to picture the vector as indexed wires, with slices as sub-buses:
There is no waveform here on purpose: a vector's structure — which wires it holds and how they are indexed — is a layout fact, not a timing one. The bit-layout diagram, not a waveform, is what teaches it.
4. Slicing, concatenation, and aggregates
These three operations are how you take buses apart and put them together — all pure wiring, no logic:
signal data : std_logic_vector(7 downto 0);
signal hi : std_logic_vector(3 downto 0);
signal byte : std_logic_vector(7 downto 0);
hi <= data(7 downto 4); -- SLICE: the high nibble (a 4-bit sub-bus)
byte <= data(3 downto 0) & data(7 downto 4); -- CONCAT (&): swap the nibbles
data <= (others => '0'); -- AGGREGATE: every bit to '0'
data <= (7 => '1', others => '0'); -- AGGREGATE: bit 7 high, the rest low- Slicing
data(7 downto 4)selects a contiguous sub-bus. The slice direction must match the signal's direction (downtowithdownto). - Concatenation
a & bjoins buses into a wider one — bus assembly, byte swaps, sign extension all use it. - Aggregates
(others => '0')set every bit at once;(7 => '1', others => '0')set named bits. They are the clean way to reset or build a constant bus of any width.
5. Arithmetic needs numeric_std — not the vector
The rule that produces the most beginner errors: std_logic_vector has no arithmetic.
It is bits, not a number, so data + 1 is illegal. To do math, interpret the bits as a
number with numeric_std, compute, then convert back:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
signal data : std_logic_vector(7 downto 0);
signal sum : std_logic_vector(7 downto 0);
-- sum <= data + 1; -- ILLEGAL: std_logic_vector has no '+'
sum <= std_logic_vector(unsigned(data) + 1); -- read as a number, add, convert backunsigned(data) says "treat these bits as a non-negative number"; the + 1 is then legal;
std_logic_vector(...) hands the resulting bits back to the vector. The vector stays a bus;
the meaning lives in numeric_std.
6. Debugging example — width and direction faults
Two failures dominate bus work, and the compiler catches both:
signal a : std_logic_vector(7 downto 0);
signal b : std_logic_vector(3 downto 0);
-- a <= b; -- error: width mismatch (8 bits <= 4 bits)
a <= "0000" & b; -- fix: pad to 8 bits with concatenation
-- a <= b(0 to 3); -- error: slice direction (to) ≠ signal direction (downto)A width mismatch ("target has 8 elements, source has 4") means you assigned buses of
different widths — pad or slice to match. A direction mismatch on a slice means you
wrote to where the signal is downto (or vice versa) — the fix is to slice in the
signal's own direction. Read both as the compiler protecting you from losing or reversing
bits.
7. Common mistakes & what to watch for
- Doing arithmetic on
std_logic_vector. It has no+; convert throughunsigned/signed(numeric_std) first. - Mixing
toanddownto. Slices and assignments must share direction; a straytoon a numeric bus silently reverses significance. - Width mismatches. Assigning buses of different widths is an error — concatenate or slice to the right width; never assume padding.
- Reaching for
std_logic_unsignedto "add to a vector." That legacy package fakes arithmetic onstd_logic_vectorand synthesizes inconsistently — usenumeric_std.
8. Engineering insight
std_logic_vector is the workhorse type precisely because it means so little: it says
only "these wires travel together," which is exactly what a bus is. All the structure —
slices, concatenations, aggregates — is wiring you can reason about as connections, not
computation. The moment a bus needs to mean a number, you cross deliberately into
numeric_std. Keeping "bundle of wires" and "a number" as separate ideas is what makes
wide RTL data paths predictable.
9. Summary & next step
std_logic_vector is an array of std_logic — a bus. Declare it downto for numeric
buses (MSB left), index and slice it as sub-buses, build it with concatenation and
aggregates, and remember it carries no numeric meaning: arithmetic goes through
numeric_std. It is structure, which is why a layout diagram, not a waveform, is its
natural picture.
A bus raises a question the single-wire lessons skipped: what happens when two sources
drive the same vector? The next lesson answers it precisely — resolved vs unresolved
types, and how the choice decides whether a double-drive is a silent 'X' or a compile
error.