Skip to content

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:

bus_decl.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 leftmost
  • downto is 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.
  • to counts 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:

std_logic_vector(7 downto 0) bit layout with an index and a slicestd_logic_vector(7downto 0)8 std_logic wiresdata(7)MSB — leftdata(6 .. 1)middle bitsdata(0)LSB — rightdata(3 downto 0)a sub-bus (low nibble)12
A std_logic_vector(7 downto 0) is eight std_logic wires indexed from bit 7 (the MSB, written on the left) down to bit 0 (the LSB). A single index like data(7) selects one wire; a slice like data(3 downto 0) selects a contiguous sub-bus (here the low nibble). The vector is just structure — which wires belong together — and carries no numeric value on its own.

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:

bus_ops.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 (downto with downto).
  • Concatenation a & b joins 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:

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

unsigned(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:

two classic bus errors
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 through unsigned/ signed (numeric_std) first.
  • Mixing to and downto. Slices and assignments must share direction; a stray to on 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_unsigned to "add to a vector." That legacy package fakes arithmetic on std_logic_vector and synthesizes inconsistently — use numeric_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.