Loading header...

Combinational Logic in VHDL

Combinational logic has no memory. Its outputs depend only on the current inputs. In an FPGA, combinational VHDL usually becomes lookup tables, mux trees, comparators, decoders, adders, and routing between registers.

Learning Objectives

By the end of this lesson, you should be able to:

  • describe Boolean functions with concurrent assignments;
  • write muxes and decoders using selected and conditional assignments;
  • use process(all) for readable combinational logic;
  • prevent accidental latch inference with defaults and complete branches;
  • estimate why large combinational paths can hurt timing.

Concurrent Assignment

Concurrent assignments are active all the time.

library ieee;
use ieee.std_logic_1164.all;

entity majority3 is
    port (
        a : in  std_logic;
        b : in  std_logic;
        c : in  std_logic;
        y : out std_logic
    );
end entity majority3;

architecture rtl of majority3 is
begin
    y <= (a and b) or (a and c) or (b and c);
end architecture rtl;

This majority function outputs 1 when at least two inputs are 1.

a b c y
0 0 0 0
0 0 1 0
0 1 0 0
0 1 1 1
1 0 0 0
1 0 1 1
1 1 0 1
1 1 1 1

Conditional Assignment

A two-input mux can be written compactly:

y <= b when sel = '1' else a;

Hardware meaning: sel controls which input reaches y. It does not mean the FPGA executes an if statement in time.

Selected Assignment

For multi-way selection, with select is often clearer:

with sel select
    y <= d0 when "00",
         d1 when "01",
         d2 when "10",
         d3 when others;

Use others when the type permits values beyond the listed patterns. With std_logic_vector, simulation can contain values such as X, U, or Z.

Combinational Process

Use process(all) when the decision logic is easier to read procedurally.

process(all)
begin
    y <= '0';

    if enable = '1' then
        y <= a xor b;
    end if;
end process;

The default assignment is the key. It tells synthesis what y should be when enable is not 1.

Latch Inference

A latch is inferred when a combinational process leaves an output unassigned on some path.

process(all)
begin
    if enable = '1' then
        y <= a;
    end if;
end process;

When enable = '0', this code says y must remember its old value. That is storage, not pure combinational logic. If storage was not intended, add a default:

process(all)
begin
    y <= '0';

    if enable = '1' then
        y <= a;
    end if;
end process;

Worked Example: 2-to-4 Decoder

library ieee;
use ieee.std_logic_1164.all;

entity decoder2to4 is
    port (
        sel : in  std_logic_vector(1 downto 0);
        y   : out std_logic_vector(3 downto 0)
    );
end entity decoder2to4;

architecture rtl of decoder2to4 is
begin
    process(all)
    begin
        y <= "0000";

        case sel is
            when "00" => y <= "0001";
            when "01" => y <= "0010";
            when "10" => y <= "0100";
            when "11" => y <= "1000";
            when others => y <= "0000";
        end case;
    end process;
end architecture rtl;

Every path assigns y. The default also makes the intended inactive output obvious.

Hardware View

flowchart LR A["Inputs a, b, c"] --> LUT["FPGA LUT logic"] SEL["Select lines"] --> MUX["Mux tree"] LUT --> OUT["Output y"] MUX --> OUT OUT --> REG["Optional next register"]

Large combinational paths between registers reduce maximum clock frequency. For a synchronous path:

Tclk >= Tco + Tlogic + Trouting + Tsetup + Tskew

where Tlogic is the delay through LUT levels and carry/mux logic, and Trouting is the FPGA interconnect delay.

Exercise

Write a 4-to-1 mux named mux4.

Requirements:

  • inputs: d0, d1, d2, d3 as std_logic;
  • select: sel : std_logic_vector(1 downto 0);
  • output: y;
  • implement it once using with select;
  • implement it once using process(all) and case.

Explained Solution

Selected assignment version:

library ieee;
use ieee.std_logic_1164.all;

entity mux4 is
    port (
        d0  : in  std_logic;
        d1  : in  std_logic;
        d2  : in  std_logic;
        d3  : in  std_logic;
        sel : in  std_logic_vector(1 downto 0);
        y   : out std_logic
    );
end entity mux4;

architecture with_select of mux4 is
begin
    with sel select
        y <= d0 when "00",
             d1 when "01",
             d2 when "10",
             d3 when others;
end architecture with_select;

Process version:

architecture proc_case of mux4 is
begin
    process(all)
    begin
        y <= d0;

        case sel is
            when "00" => y <= d0;
            when "01" => y <= d1;
            when "10" => y <= d2;
            when "11" => y <= d3;
            when others => y <= d0;
        end case;
    end process;
end architecture proc_case;

Both versions describe a mux. The process version uses a default so y is never left unassigned.

Common Mistakes

  • Missing a default assignment in a combinational process.
  • Forgetting when others for vectors with unknown simulation values.
  • Assuming statement order creates hardware delay.
  • Using incomplete sensitivity lists instead of process(all).
  • Building a very deep combinational path and then blaming the clock.
  • Mixing arithmetic and bit-field types without conversion.

Summary

Use concurrent assignments for simple Boolean logic, selected assignments for clean muxes, and process(all) for decision logic. In every combinational process, assign every output for every possible path unless you intentionally want storage.

Next: Sequential Logic in VHDL.

Further Reading

Mind Map

mindmap root((VHDL Combinational)) Core concept No memory Outputs follow inputs LUTs and muxes Complete assignments Applications Boolean logic Decoders Comparators Multiplexers Formulas Tclk covers Tco logic route setup skew Longer logic lowers Fmax Design rules Use process all Set defaults first Add when others Keep paths short Practical checks Simulate truth table Read latch warnings Review timing path Check unknown inputs Common mistakes Inferred latch Missing branch Time delay thinking Width mismatch