Sign in

Learn

0/8
0/6
0/6
0/6

Practice

Numeric Types and Constants

Two's Complement left a promise hanging: the pattern 1011 reads as 11 unsigned or as −5 signed. The choice of reading "lives in your head and, later, in the types you give your signals." Later is now. A std_logic_vector is deliberately meaning-free: wires, in order, nothing more. The moment a design needs to treat a bundle as a number (compare it against a limit, count something), you declare the reading explicitly, with a type.

unsigned and signed

VHDL keeps its number-reading vector types in a second ieee package, numeric_std. Anatomy of a Component promised you'd add this line when the number types appeared. Add it now:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;   -- unsigned and signed live here

The declarations look exactly like vectors because they are vectors (same wires, same indexing and slices), plus a declared reading:

signal level : unsigned(6 downto 0);  -- 7 wires read as 0 to 127
signal temp  : signed(7 downto 0);    -- 8 wires read as -128 to +127

VHDL has three distinct types, and the distinction matters. std_logic_vector says "no numeric meaning"; unsigned and signed each fix one reading, and the tools refuse numeric operations on the meaning-free one.

The Integer Family

Not every number in a design rides on wires. The limits you compare against, the widths in declarations, the values a testbench feeds in: those are numbers in the source text, with no bundle behind them.

VHDL's type for them is integer, with two built-in subtypes that document intent: natural (0 and up) and positive (1 and up). Declaring a limit as natural is a statement to the reader and the compiler; a negative value in that spot becomes an error.

Can a signal be an integer? It's legal, and code that does it constrains the range (signal count : natural range 0 to 100) so the synthesizer knows how many wires to spend. You'll meet that form in Counting to N. For ports and datapaths, prefer unsigned/signed, where you chose the exact wire count yourself.

Constants: Naming Your Numbers

A limit that appears as a bare number is a question every reader has to answer again ("why 80?"). Name it once, next to your signal declarations:

constant LOW_MARK  : natural := 20;
constant HIGH_MARK : natural := 80;

Constants are written ALL_CAPS by convention. A constant has no wires and no driver: at synthesis it dissolves into the logic that uses it. That gives "change the value" a hardware meaning: edit one line and a different circuit gets built.

A constant is private to the file that declares it. Its overridable cousin, the generic, can be set from outside per instance; generics get their own treatment in Hierarchy and Reuse.

Putting Numbers to Work

The payoff of a declared reading: the tools can now compare. Comparison operators get their formal lesson in Arithmetic and Relational Operators, but you need their everyday shape now. Like the tri-state recipe in Port Directions, take this one as-is:

low <= '1' when level < LOW_MARK else '0';

A comparison produces a true/false answer, not a wire level, so the when/else wrapper (the same shape as the tri-state recipe) turns "true" into '1'. The mechanism behind it arrives in Conditional Assignment.

The comparison reads the signal against the constant under the declared reading. < is strict: a level of 19 trips the low flag, exactly 20 does not.

CAUTION

Common Mistake: comparing a meaning-free vector. std_logic_vector has no numeric reading, and comparing one against a number refuses to compile; declare the signal unsigned or signed instead.

In the Example

The example panel on the right (Tank Gauge) is the level monitor this lesson has been building. A 7-wire unsigned fill level is checked against LOW_MARK (20) and HIGH_MARK (80). Step through the waveform. At 19 the low flag is up. At exactly 20, the mark itself, both flags are calm, because the comparisons are strict. high first rises at 81, one past the mark. One thing you will not find in the waveform is LOW_MARK itself: a constant is not a wire. It dissolved into the comparator at build time.

Key Takeaways

  • Wires carry no meaning by themselves: unsigned and signed, from numeric_std, declare how a vector is read as a number.
  • integer and its subtypes natural and positive are wire-less numbers: the types for limits, literals, and counts in the source text.
  • Name every limit as an ALL_CAPS constant: one definition, and at synthesis it dissolves into the circuit that uses it.
Loading editor...
Waveform not yet available. Run sync-content --examples to generate.