By the end of this chapter you should be able to:
- Explain why textual description replaced schematic capture for anything above a few hundred gates.
- Write an entity declaration with correctly chosen port modes, and pair it with one or more architectures.
- Describe the same circuit in dataflow, behavioural and structural style, and say which is appropriate when.
- State why std_logic has nine values and what each of 'U', 'X', 'Z', 'W', 'L', 'H' and '-' is for.
- Use processes and sensitivity lists correctly, and explain the difference between signal and variable assignment.
- Write synthesisable descriptions of a full adder, a 4-to-1 multiplexer, a D flip-flop and a mod-10 synchronous counter.
- Outline a test bench, and recognise the incomplete if, case or sensitivity list that produces an unwanted latch.
Every circuit in this course so far has been specified by drawing it. That works while the drawing fits on a page. It stops working at about the scale of Chapter 20's arithmetic logic unit, and it has been hopeless for thirty years at the scale of the devices Chapter 28 described, where a mid-range FPGA holds hundreds of thousands of logic blocks. Nobody draws a hundred thousand gates, and if they did, nobody could review the drawing, keep it under version control, or search it for the one signal that is wrong.
A hardware description language solves that by writing the circuit down as text. The word description is the one to hold on to: VHDL is not a programming language that happens to control hardware, it is a notation for saying what a piece of hardware is, and a synthesiser reads it and builds the corresponding gates and flip-flops. This chapter introduces enough VHDL to describe the circuits of the previous chapters — a full adder, a multiplexer, a flip-flop, a counter — in a form a synthesiser will accept, and spends its last section on the single commonest way of getting it wrong.
a <= b as "copy b into a" rather than as "there is a wire from b to a", the synthesiser's output will start surprising you — and the commonest surprise is a latch you never asked for.
1 Why an HDL Replaced Schematic Capture
A schematic is a fine specification for a circuit of twenty gates and a poor one for a circuit of twenty thousand. The reasons are practical rather than theoretical, and they are worth listing because they explain the shape of the language.
- Scale. Drawing effort grows with the number of symbols; writing effort grows with the number of distinct ideas. A 32-bit adder is one line of text and 160 gate symbols on a page.
- Parameterisation. A text description can be written once for a width \(N\) and elaborated at 4, 8 or 64 bits. A drawing must be redrawn.
- Version control and review. Text diffs. Two revisions of a schematic can only be compared by looking at them, which is how errors survive reviews.
- Portability. A schematic is drawn from a particular vendor's symbol library and dies with it. VHDL is an IEEE standard (1076, first ratified in 1987) and the same source targets a CPLD, an FPGA or an ASIC.
- Simulation and synthesis from one source. The description you verify is the description you build, so the two cannot drift apart.
VHDL — VHSIC Hardware Description Language — came out of a United States Department of Defense programme in the early 1980s whose purpose was documentation: contractors were delivering custom chips with no machine-readable record of what they did. Simulation came next, and synthesis, the automatic translation of a description into gates, arrived only around 1990. That history explains a language feature that surprises beginners: much of VHDL is not synthesisable. Constructs such as wait for 10 ns, floating-point arithmetic and file input describe behaviour a simulator can imitate but no gate can implement. They are perfectly proper VHDL and belong in test benches, never in a design.
The discipline to acquire now is to keep in your head, at every line, the hardware you expect, and to use the synthesis report — so many look-up tables, so many registers — as a check on whether the tool built what you meant. A designer who writes VHDL the way one writes C and then inspects the result to see what appeared will get working silicon occasionally and predictable silicon never.
2 Entity, Architecture, Ports and Modes
A VHDL design unit comes in two parts, and the split is exactly the one an engineer already makes between a block's symbol and its contents. The entity declares the interface: the name of the block and its ports, with the direction and type of each. The architecture describes what is inside. One entity may have several architectures — a behavioural one for early simulation and a structural one for the final build — and because they share the interface, one can be substituted for another without touching anything that uses the block.
library ieee;
use ieee.std_logic_1164.all;
entity full_adder is
port ( a, b, cin : in std_logic;
sum, cout : out std_logic );
end entity full_adder;The two lines at the top matter. std_logic is not built into the language; it lives in the package std_logic_1164 in the library ieee, and without the use clause the compiler will not know the name. The habit of writing both lines at the head of every file saves a great deal of confusion.
Each port has a mode that says which way information flows, and the compiler enforces it:
in— read inside the architecture, never assigned. Assigning to an input is an error, and rightly so: the signal is driven from outside.out— assigned inside, and in VHDL-87 and -93 not readable inside. If you need to read back what you drove, drive an internal signal and assign that signal to the port. VHDL-2008 relaxed this, but the internal-signal habit is still the portable one.inout— genuinely bidirectional, for a real tri-state pin such as a shared data bus of the kind Chapter 13 described. Use it only when the hardware really is bidirectional.buffer— driven inside and readable inside. It looks like the answer to theoutrestriction and causes trouble when the entity is instantiated, so prefer the internal signal.
Inside an architecture, signals are the wires. A signal is declared between the architecture line and the begin, it has a type, and it exists for the life of the circuit. The essential property of a signal — the one that separates VHDL from a programming language — is that assignment to it is scheduled rather than immediate. Writing s <= x; does not change s at that moment; it says that s will take the value of x after a delay, which for synthesis is an infinitesimal one called a delta cycle. That is exactly how a wire driven by a gate behaves, and Section 5 shows what goes wrong when it is forgotten.
3 std_logic and Why It Has Nine Values
VHDL does define a two-valued type, bit, with values '0' and '1'. Nobody uses it, because a real wire has more states than two and a simulation that cannot represent them will report success on circuits that fail on the bench. The standard type is std_logic, defined in std_logic_1164, and it has nine values.
| Value | Name | What it represents |
|---|---|---|
'U' | Uninitialised | The default before anything drives the signal. Seeing 'U' in a waveform means a register was never reset. |
'X' | Forcing unknown | Two strong drivers are fighting, or an unknown has propagated. This is bus contention, and on real silicon it is a short circuit. |
'0' | Forcing 0 | A strong low, an output driving to ground. |
'1' | Forcing 1 | A strong high. |
'Z' | High impedance | A tri-state output turned off, so the wire is driven by somebody else — the modelling of Chapter 13's tri-state bus. |
'W' | Weak unknown | Two weak drivers in conflict, for instance two pull resistors of opposite sense. |
'L' | Weak 0 | A pull-down resistor: it wins against 'Z' and loses against '1'. |
'H' | Weak 1 | A pull-up resistor, the open-collector arrangement of Chapter 13. |
'-' | Don't care | The don't-care of Chapter 8, offered to the synthesiser as freedom to minimise. In simulation it is not a wildcard and never matches anything. |
The list is exactly what a multi-driver wire needs: a strength (forcing, weak, or none at all) crossed with a level (0, 1 or unknown), plus the uninitialised state. The package supplies a resolution function that says what happens when several drivers act on one signal — forcing beats weak, weak beats high impedance, and two conflicting forcing drivers give 'X'. That is a model of the electrical behaviour Chapter 13 derived for wired-AND and tri-state outputs, and it is why bus contention shows up as a screenful of 'X' in simulation instead of silently working.
Three practical points. Only '0', '1', 'Z' and, with care, '-' mean anything to a synthesiser; the rest are simulation values. Comparisons are literal, so if x = '-' asks whether x holds that value and matches neither '0' nor '1'. And std_logic_vector is a bare array of bits with no numeric meaning: to do arithmetic, convert it to unsigned or signed from numeric_std, as the counter in Section 6 does.
4 Three Description Styles for One Circuit
VHDL offers three ways of saying what is inside an architecture. They are not three languages and they are not ranked; the same synthesiser produces the same netlist from all three. What differs is which one lets you say what you mean most directly. The comparison is only convincing when the circuit is held fixed, so all three descriptions below are of the full adder of Chapter 16, with the same entity of Section 2.
Dataflow style writes the Boolean equations as concurrent signal assignments. Each statement is a permanently connected piece of hardware; they all act at once, and reordering them changes nothing.
architecture dataflow of full_adder is
begin
sum <= a xor b xor cin;
cout <= (a and b) or (cin and (a xor b));
end architecture dataflow;These are the expressions Chapter 16 derived, transcribed. Dataflow style is the natural choice whenever you already have the algebra: adders, parity trees, comparators, the code converters of Chapter 20.
Behavioural style describes what the circuit does rather than how it is wired, inside a process. Statements within a process execute in order, like a program, which lets you write if, case and loops; the process as a whole is still one concurrent block of hardware.
architecture behavioural of full_adder is
begin
process (a, b, cin) is -- sensitivity list: every input read
variable v : std_logic_vector(2 downto 0);
begin
v := a & b & cin; -- concatenate into one 3-bit value
case v is
when "000" => sum <= '0'; cout <= '0';
when "001" => sum <= '1'; cout <= '0';
when "010" => sum <= '1'; cout <= '0';
when "011" => sum <= '0'; cout <= '1';
when "100" => sum <= '1'; cout <= '0';
when "101" => sum <= '0'; cout <= '1';
when "110" => sum <= '0'; cout <= '1';
when "111" => sum <= '1'; cout <= '1';
when others => sum <= 'X'; cout <= 'X';
end case;
end process;
end architecture behavioural;This is the truth table of Chapter 16 written out, and it synthesises to the same gates as the dataflow version — the synthesiser minimises it for you, so the K-maps of Chapter 8 are being applied inside the tool rather than on your page. The when others branch covers the metalogical values of Section 3: without it the case would not be exhaustive, and an inexhaustive case infers a latch, which is the subject of Section 7.
Structural style builds the circuit from other components, wiring them together with internal signals. It is the schematic, written down.
architecture structural of full_adder is
component half_adder is
port ( x, y : in std_logic;
s, c : out std_logic );
end component;
signal s1, c1, c2 : std_logic; -- the internal wires
begin
ha1 : half_adder port map (x => a, y => b, s => s1, c => c1);
ha2 : half_adder port map (x => s1, y => cin, s => sum, c => c2);
cout <= c1 or c2;
end architecture structural;Each instantiation has a label (ha1, ha2) and a port map that associates the component's ports with signals in this architecture. Naming the ports explicitly, as here, rather than relying on position, is worth the extra characters: positional association silently connects the wrong wires the day somebody reorders a port list. Structural style is how hierarchy is built — a 4-bit ripple-carry adder is four instances of this entity with the carries chained, exactly the drawing in Chapter 16.
5 Processes, Sensitivity Lists, Signals and Variables
A process is a block of sequential statements that behaves, from the outside, as one concurrent element. It runs, suspends, and runs again when something wakes it. What wakes it is its sensitivity list, the signal names in brackets after the keyword.
For combinational logic the rule is absolute: every signal read inside the process must appear in the sensitivity list. If one is missing, the simulator will not re-evaluate the process when that signal changes, so the simulated output holds its old value — behaving, in simulation, like a latch. The synthesiser, which ignores sensitivity lists and looks only at what is read, builds combinational logic anyway. The result is a design whose simulation and hardware disagree, which is the worst kind of bug because the simulation is the thing you trusted. VHDL-2008 added process (all) precisely to remove this trap; where it is supported, use it.
For sequential logic the sensitivity list is short and deliberate. A synchronous element is sensitive to the clock, and to the asynchronous reset if there is one, and to nothing else — not because the other signals are unimportant but because they are only sampled at the clock edge.
process (clk, rst) is
begin
if rst = '1' then -- asynchronous: outside the edge test
q <= '0';
elsif rising_edge(clk) then -- everything here is edge-triggered
q <= d;
end if;
end process;The shape is a template, and it is worth learning as one. rising_edge(clk) is a function from std_logic_1164 that is true on a '0'-to-'1' transition; every assignment inside that branch becomes the D input of a flip-flop. Move the reset test inside the edge branch and you get a synchronous reset instead — a different circuit, and often the better one, since it uses the flip-flop's data path rather than its asynchronous pin. The setup and hold times of Chapter 21 are what the timing analyser of Chapter 28 will check against this flip-flop after routing.
Inside a process you may also declare variables, and the difference between a variable and a signal is the one point in this chapter that repays real care.
- A signal is assigned with
<=. The assignment is scheduled, so the new value is not visible to later statements in the same process run. A signal models a wire. - A variable is assigned with
:=. The assignment takes effect immediately, so later statements see the new value. A variable models a temporary name for an intermediate result — and, sometimes, a register.
-- (a) signals: b gets the OLD value of s, so two flip-flops in series
process (clk) is
begin
if rising_edge(clk) then
s <= a;
b <= s; -- s is still the previous value here
end if;
end process;
-- (b) variable: b gets the NEW value of v, so only one flip-flop
process (clk) is
variable v : std_logic;
begin
if rising_edge(clk) then
v := a;
b <= v; -- v has already changed
end if;
end process;Version (a) is a two-stage shift register of the kind Chapter 25 built: a reaches b two clock edges later. Version (b) is a single flip-flop: a reaches b after one edge, and v becomes a wire, not a register. Two characters of source, two different circuits. The rule of thumb is to use signals for anything that connects one part of the design to another, and variables only for short-lived intermediate results inside a single process — a running total in a loop, a concatenation like v in Section 4.
6 Synthesisable Descriptions of Earlier Circuits
With the entity, the process and the two assignment operators in hand, the standard blocks of Parts 4 and 5 can be written down. Each of these compiles, simulates and synthesises as it stands.
The 4-to-1 multiplexer of Chapter 19 is a pure selection, so the selected signal assignment says it in one statement with no process at all.
library ieee;
use ieee.std_logic_1164.all;
entity mux4 is
port ( d : in std_logic_vector(3 downto 0);
sel : in std_logic_vector(1 downto 0);
y : out std_logic );
end entity mux4;
architecture rtl of mux4 is
begin
with sel select
y <= d(0) when "00",
d(1) when "01",
d(2) when "10",
d(3) when "11",
'X' when others; -- covers 'U', 'X', 'Z', ... exhaustively
end architecture rtl;The when others is not optional. sel is a two-bit std_logic_vector, so it has \(9^2 = 81\) possible values, not four; without a final catch-all the assignment is incomplete and the tool will infer storage to hold y for the cases you did not mention.
The mod-10 synchronous counter of Chapter 24 needs a register, arithmetic and a terminal count. Arithmetic means converting the bit vector to a numeric type, which is what numeric_std provides.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity counter_mod10 is
port ( clk, rst, en : in std_logic;
q : out std_logic_vector(3 downto 0);
tc : out std_logic );
end entity counter_mod10;
architecture rtl of counter_mod10 is
signal count : unsigned(3 downto 0); -- internal, so it can be read back
begin
process (clk, rst) is
begin
if rst = '1' then
count <= (others => '0');
elsif rising_edge(clk) then
if en = '1' then
if count = 9 then
count <= (others => '0'); -- 9 -> 0, the mod-10 wrap
else
count <= count + 1;
end if;
end if;
end if;
end process;
q <= std_logic_vector(count);
tc <= '1' when (count = 9 and en = '1') else '0';
end architecture rtl;Four questions answer themselves if the description is read as a circuit rather than as a program.
- How many flip-flops? Four, one per bit of
count, becausecountis the only signal assigned inside the edge branch. The sequence 0–9 needs \(\lceil \log_2 10 \rceil = 4\), so nothing is wasted; the six unused states 10–15 are entered only from an illegal power-up value, and thecount = 9test rather thancount > 9is what makes the counter recover — from state 10 it counts up to 15 and rolls to 0. Chapter 24 called this self-starting behaviour. - Why is
counta signal and not the portq? Because the next-state logic must read the present count, and anoutport cannot be read. The last-but-one line copies the internal register to the port and costs no hardware. - Where is the combinational logic? In the comparison against 9 and the incrementer, both of which the synthesiser builds from the adders of Chapter 16 and feeds to the flip-flops' D inputs. This is exactly the excitation logic that Chapter 24's design procedure produced by hand from a state table.
- What does
tccost? One four-input AND-type function ofcountanden, combinational and unregistered. Because it depends onen, it is a Mealy output in the sense of Chapter 26, and it will glitch; registering it would remove the glitch at the price of one clock of delay.
Dividing a 1 MHz clock with this counter gives 100 kHz on the carry, and cascading two of them gives the familiar decade chain.
7 Test Benches, and the Latch You Did Not Ask For
A design is not finished when it compiles. A test bench is a second VHDL file that instantiates the design, drives its inputs and checks its outputs, and it is the one place where the non-synthesisable half of the language belongs. A test bench has no ports at all: it is a closed system, with nothing outside it to connect to.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity tb_full_adder is
end entity tb_full_adder; -- no ports: nothing is outside
architecture sim of tb_full_adder is
signal a, b, cin, sum, cout : std_logic;
begin
uut : entity work.full_adder(dataflow) -- unit under test
port map (a => a, b => b, cin => cin, sum => sum, cout => cout);
stimulus : process is
variable v : std_logic_vector(2 downto 0);
begin
for i in 0 to 7 loop -- all eight input combinations
v := std_logic_vector(to_unsigned(i, 3));
a <= v(2); b <= v(1); cin <= v(0);
wait for 10 ns; -- simulation only, never synthesised
assert sum = (v(2) xor v(1) xor v(0))
report "wrong sum for input " & integer'image(i)
severity error;
end loop;
wait; -- suspend for ever: run complete
end process;
end architecture sim;Three things make it a test bench rather than a design. The wait for statements create time out of nothing, which no circuit can do. The assert statements check the result automatically, so the test either passes or prints a message — far better than staring at waveforms, and the only approach that scales past a handful of vectors. And the final bare wait suspends the process for ever, which is how a simulation stops; without it the loop would restart and run until the simulator was killed. For a sequential design a second process generates the clock, and the stimulus process waits on clock edges rather than on absolute times.
Now the error that catches everyone. VHDL insists that a signal keeps its value until something assigns a new one. If a combinational description leaves any path along which a signal is not assigned, the synthesiser has no choice: it must build something that remembers the old value, and that something is a transparent D latch. Nobody asks for it and the tool rarely refuses; it issues a warning, in a log full of warnings, and carries on.
-- (1) an if with no else
process (sel, a) is
begin
if sel = '1' then
y <= a; -- what is y when sel = '0'? It holds.
end if;
end process;
-- (2) an incomplete case
process (op, a, b) is
begin
case op is
when "00" => y <= a;
when "01" => y <= b; -- "10" and "11" unmentioned: y holds
when others => null; -- 'null' assigns nothing at all
end case;
end process;
-- (3) only some outputs assigned on some branches
process (sel, a, b) is
begin
if sel = '1' then
y <= a; z <= b;
else
y <= b; -- z not assigned here, so z latches
end if;
end process;Why it matters is more than tidiness. A latch is level-sensitive, so it passes any glitch arriving while it is transparent — the static hazards of Chapter 11 go straight through. It is not clocked, so the static timing analysis of Chapter 28 cannot check a path through it against any clock period and will report the path as unconstrained. On an FPGA it wastes a flip-flop that was there anyway. And in a design that was meant to be entirely synchronous, it is a piece of asynchronous logic hiding in the middle of it.
The cures are simple and worth making habits. Give every if an else. End every case with when others, and assign real values there rather than null. Better still, assign default values to every output at the top of the process, before the if, so that every path assigns everything whatever happens afterwards. And read the synthesis report: if it says "inferred latch for signal y" and you did not want a latch, the description is wrong, however well it simulates.
8 Summary and Key Results
| Construct | What it means | What the synthesiser builds |
|---|---|---|
entity … port(…) | The interface: names, directions and types of the ports | The block's pins; nothing inside |
Port modes in / out / inout | Read only / written only / bidirectional | An input pin, an output pin, or a tri-state pin |
architecture | One possible body for an entity; several may exist | Whichever body the configuration selects |
Concurrent assignment <= | A permanent connection; order of statements is irrelevant | Combinational gates |
process (a, b, c) | Sequential statements re-run when a listed signal changes | Combinational logic if every read signal is listed and every path assigns |
rising_edge(clk) in a process | Everything in the branch happens at the clock edge | D flip-flops, one per assigned signal |
Signal <= vs variable := | Scheduled update vs immediate update | Two flip-flops in series vs one — the same two lines give different circuits |
std_logic | Nine values: strength × level, plus 'U' and '-' | Only '0', '1', 'Z' and '-' survive synthesis |
Incomplete if or case | Some path leaves a signal unassigned | A transparent D latch, unclocked and unwanted |
9 Common Mistakes
An if with no else, a case without when others, or a branch that assigns two outputs where another assigns one — each of them means the signal must keep its old value, and the only hardware that keeps a value is a latch. Assign default values to every output at the top of the process, so that whatever the branches do afterwards, no path can leave anything undriven.
Omitting a signal that the process reads makes the simulator hold the output when that signal changes, so the simulation shows a latch that the synthesised hardware does not have — or, worse, hides a genuine error. The synthesiser ignores the list entirely and infers from what is read, so simulation and silicon then disagree. List every signal read, or use process (all) where VHDL-2008 is available.
A signal assignment is scheduled, not immediate, so a statement later in the same process still sees the old value. Writing s <= a; b <= s; inside a clocked process gives two flip-flops in series, not one, because b receives the previous s. If you want the new value immediately, use a variable and :=. This is not a quirk of the language: it is what a wire driven by a gate actually does.
10 Chapter Review
1. Write the entity for a 4-bit magnitude comparator with inputs A and B and outputs gt, eq and lt, and say why the outputs should not be declared with mode
buffereven though the internal logic might want to read them.The entity is
entity comparator4 is port ( a, b : in std_logic_vector(3 downto 0); gt, eq, lt : out std_logic ); end entity comparator4;, preceded bylibrary ieee; use ieee.std_logic_1164.all;. Modebufferwould make the outputs readable inside, but it changes the port's kind, and any entity that instantiates this one must then connect a buffer-mode signal to it, which propagates the restriction upwards and breaks reuse. The portable idiom is to declare internal signals —signal gt_i, eq_i, lt_i : std_logic;— compute and read those freely, and end the architecture withgt <= gt_i; eq <= eq_i; lt <= lt_i;, which costs no hardware.2. Describe a 2-to-4 decoder with an active-high enable in behavioural style, and explain what must be present to keep the description free of latches.
A process sensitive to the address and the enable, with a default assignment first:
process (en, a) is begin y <= "0000"; if en = '1' then case a is when "00" => y <= "0001"; when "01" => y <= "0010"; when "10" => y <= "0100"; when others => y <= "1000"; end case; end if; end process;. Two things keep it combinational. The sensitivity list names both signals the process reads, so the simulation matches the hardware. And the defaulty <= "0000";before theifguarantees thatyis assigned on every path, including the one whereenis '0' — so no branch can require the old value to be remembered, and no latch is inferred. Thewhen othersis also needed, since a two-bitstd_logic_vectorhas 81 possible values, not four.3. What is the difference between the two processes below, in flip-flop count and in behaviour? (a)
if rising_edge(clk) then x <= d; y <= x; end if;(b)variable v : std_logic; … if rising_edge(clk) then v := d; y <= v; end if;In (a) both
xandyare signals, so both assignments are scheduled and neither takes effect until the process suspends. The second statement therefore reads the valuexhad before this clock edge, anddreachesytwo edges after it is applied: two flip-flops in series, a two-stage shift register of the kind Chapter 25 describes. In (b)vis a variable, sov := dtakes effect at once and the next line reads the new value;dreachesyafter one edge andvbecomes a wire rather than a register — one flip-flop in total. The source differs by two characters and the circuits differ by a whole register and a clock cycle of latency.4. A student writes
process (sel) begin if sel = '1' then y <= a; else y <= b; end if; end process;. What is wrong, what does the simulation show, and what does the synthesiser build?The sensitivity list is incomplete: the process reads
aandbas well assel, but onlyselwakes it. In simulation the process re-runs only whenselchanges, so a change inawhileselis '1' does not reachy— the output appears to be latched onsel. The synthesiser ignores sensitivity lists and looks at what the process reads, and since every path assignsyit builds a plain 2-to-1 multiplexer with no storage. Simulation and hardware therefore disagree, which is the dangerous case: the design may well be correct and the verification wrong, or the reverse, and nothing in the flow flags it. The fix isprocess (sel, a, b), orprocess (all)in VHDL-2008.5. Take the mod-10 counter of Section 6 and say what changes are needed to make it a mod-12 up/down counter with a synchronous reset. How many flip-flops does it need, and how would you verify it?
Four changes. The reset test moves inside the edge branch, so it becomes
if rising_edge(clk) then if rst = '1' then count <= (others => '0'); elsif en = '1' then …, andrstleaves the sensitivity list, which becomesprocess (clk)alone. A direction inputupis added to the entity. The counting branch tests both ends: counting up, wrap from 11 to 0; counting down, wrap from 0 to 11. And the terminal count becomestc <= '1' when (up = '1' and count = 11) or (up = '0' and count = 0) else '0';. It still needs four flip-flops, since \(\lceil \log_2 12 \rceil = 4\), with four unused states 12–15 from which the wrap tests must recover. Verification: a test bench with a clock process, a stimulus process that releases reset, counts up through at least fourteen edges to see the wrap at 11, then assertsup = '0'and counts back through zero, withassertstatements comparingqagainst an expected sequence computed in the test bench rather than read off a waveform.