VHDL · Chapter 6.7 · Combinational Logic Design
Arithmetic Circuits
Datapaths compute, and in VHDL that computation rides on the standard numeric package. The unsigned and signed types give you add, subtract, multiply, and comparison operators that synthesize to real adders, subtractors, and comparators. The care is in the details. You capture carry and overflow by resizing to one extra bit, keep operand widths consistent, and use relational operators to produce the booleans that steer multiplexers. This lesson builds the standard combinational arithmetic blocks on those numeric types, shows the hardware each operation becomes, and reinforces the width and signedness discipline that keeps arithmetic correct. By the end you can add, compare, and detect overflow with confidence, and you understand why signed and unsigned magnitudes lead to different circuits from the same operators.
Foundation14 min readVHDLArithmeticnumeric_stdAdderComparatorOverflow
1. Engineering intuition — numbers become adders
Arithmetic in hardware is just more combinational logic: an adder is a cone of gates that turns two
input numbers into their sum; a comparator turns two numbers into a boolean. The only thing that makes
arithmetic special is that you must tell VHDL the bits mean numbers — which is what unsigned and
signed from numeric_std do (lesson 2.8). Once the types carry that meaning, +, -, and < build
the obvious hardware. The recurring concerns are width (results can need an extra bit) and signedness
(unsigned magnitude vs signed two's-complement give different circuits).
2. Formal explanation — operations, width, carry, and overflow
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all;
signal a, b : unsigned(7 downto 0);
sum <= std_logic_vector(a + b); -- 8-bit add, result wraps mod 256
diff <= std_logic_vector(a - b); -- subtract
incr <= std_logic_vector(a + 1); -- incrementer
-- CARRY: resize to 9 bits so the top bit is the carry-out.
signal s9 : unsigned(8 downto 0);
s9 <= resize(a, 9) + resize(b, 9); -- 9-bit add
sum8 <= std_logic_vector(s9(7 downto 0));
carry <= s9(8); -- carry-out
-- COMPARISON: relational operators produce a boolean (drives selection).
ge <= '1' when a >= b else '0';numeric_std arithmetic on equal-width operands wraps at the width boundary; to keep the carry (or
detect overflow), resize the operands to one extra bit and read the top bit of the wider result.
Relational operators (<, >=, =, …) return a boolean, which feeds conditions and muxes. Width
discipline is essential: operands of a binary operator generally must match width (resize as needed).
3. Production RTL — an ALU slice with flags
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all;
alu : process (all)
variable wide : unsigned(8 downto 0); -- 9-bit intermediate for carry
begin
result <= (others => '0'); carry_out <= '0'; zero <= '0'; -- defaults → latch-free
case op is
when "00" => wide := resize(unsigned(a), 9) + resize(unsigned(b), 9);
result <= std_logic_vector(wide(7 downto 0));
carry_out <= wide(8); -- carry from the 9th bit
when "01" => result <= std_logic_vector(unsigned(a) - unsigned(b));
when "10" => result <= a and b;
when others => result <= a or b;
end case;
if result = x"00" then zero <= '1'; end if; -- zero flag
end process;What hardware does this become? A combinational ALU: an adder/subtractor (the add uses a 9-bit
intermediate so bit 8 is the carry), bitwise gates, a result mux over the opcode, and a zero-detect
comparator. Defaults keep all outputs latch-free; numeric_std makes the arithmetic well-defined and
portable (never the obsolete std_logic_unsigned).
4. Hardware interpretation — adder and comparator datapaths
5. Simulation interpretation — results track operands
An 8-bit adder with carry: sum and carry follow the operands
8 cycles6. Debugging example — the missing carry and the width mismatch
Expected: an add that detects overflow. Observed: the carry is always 0 (overflow lost), or the
tool errors on a length mismatch. Root cause: the add was done at the operands' native width, so the
carry simply wrapped away with no extra bit to hold it; or two different-width operands were added
without resizing, which numeric_std rejects (no silent extension). Fix: resize both operands to
N+1 bits and read the top bit for carry; resize operands to a common width before any binary operation.
Engineering takeaway: carry/overflow lives in the bit above the operands — make room for it with
resize, and keep operand widths equal.
-- BUG: native-width add loses the carry; mismatched widths are illegal.
-- carry <= ??? ; sum <= std_logic_vector(a8 + b4); -- length mismatch + no carry
-- FIX: common width, and one extra bit for carry.
wide <= resize(a8, 9) + resize(b4, 9); -- equalise width, +1 for carry
sum <= std_logic_vector(wide(7 downto 0));
carry <= wide(8);7. Common mistakes & what to watch for
- Losing carry/overflow. A native-width add wraps; resize to N+1 and read the top bit (unsigned
carry); for
signed, detect overflow from the sign bits. - Width mismatches.
numeric_stdnever auto-extends;resizeoperands to a common width. - Mixing
signedandunsigned. They are different circuits; convert deliberately (lesson 2.9). - Using
std_logic_unsigned. Obsolete and non-portable; alwaysnumeric_stdonunsigned/signed. - Doing arithmetic on
std_logic_vector. Cast tounsigned/signedfirst; the vector has no+.
8. Engineering insight & continuity
Combinational arithmetic is "ordinary logic that happens to mean numbers," and numeric_std is what
gives the bits that meaning so +, -, and < build the right datapath. The two habits that keep it
correct are width discipline (resize to a common width, add a bit for carry/overflow) and signedness
discipline (choose unsigned or signed deliberately). With routing, encoding, and now arithmetic
covered, only one combinational topic remains — the one structure you must not build. The next
lesson, Combinational Loops and Why to Avoid Them, shows how a signal that feeds back on itself with
no register creates an undefined, unsynthesizable circuit, and why the fix — a register — is the doorway
into sequential logic.