Combinational Building Blocks

Multiplexers, decoders, comparators, half and full adders, ripple-carry adders, and simplifying logic with Karnaugh maps.

1. From gates to blocks

Chapter 1 showed that any truth table can be built from NOT, AND and OR gates. In practice, digital designers rarely think in single gates. They think in a handful of standard building blocks, each with a clear job: choose one of several signals, recognise a code, compare two numbers, add them. This chapter derives each of those blocks from its truth table, then looks at two practical questions: how to make the logic smaller (Karnaugh maps), and how long it takes for the answer to appear (propagation delay).

All blocks here are combinational: the outputs depend only on the current inputs. When an input changes, the outputs change shortly afterwards and there is no memory of the past. That "shortly afterwards" turns out to be the most important number in FPGA design, so keep it in mind.

2. The multiplexer

A multiplexer (mux) is a digitally controlled switch. A 2:1 mux has two data inputs \(d_0, d_1\), one select input \(s\) and one output: if \(s = 0\) the output copies \(d_0\), otherwise \(d_1\). Writing a product term for each case gives

\[ y = \overline{s}\,d_0 + s\,d_1 \]

A 4:1 mux needs two select bits \(s_1 s_0\), read as a binary number 0–3, and has one product term per data input. Each term contains the minterm of the select code that picks it:

\[ y = d_0\,\overline{s_1}\,\overline{s_0} + d_1\,\overline{s_1}\,s_0 + d_2\,s_1\,\overline{s_0} + d_3\,s_1\,s_0 \]

In general a \(2^k\):1 mux needs \(k\) select bits. Multiplexers are everywhere in hardware: choosing which register feeds an adder, which source drives a bus, which value is shown on the LEDs (the first_lights design of Chapter 14 ends in a mux controlled by a switch).

MUXd00d11d22d33s[1:0]ys1s0d0selected when s = 00d1selected when s = 01d2selected when s = 10d3selected when s = 11yOR
Left: the 4:1 multiplexer symbol. Right: its gate-level structure. The select code turns on exactly one AND gate (a decoder is hidden inside), and the OR passes that gate's data bit to the output.
Key idea: a mux is a programmable function
Tie the data inputs of a 4:1 mux to constants and drive the select lines with two signals \(x, y\). The output is then whichever constant sits at row \((x, y)\): the constants are a truth table, and changing them changes the function. This is precisely how an FPGA look-up table works (Problem 5 and Chapter 5).

3. Decoders and encoders

A decoder turns a \(k\)-bit binary code into \(2^k\) separate wires, exactly one of which is 1. This is called a one-hot output. Output \(i\) is simply the minterm of code \(i\): for a 2-to-4 decoder, \(y_0 = \overline{a_1}\,\overline{a_0}\), \(y_1 = \overline{a_1}\,a_0\), \(y_2 = a_1\,\overline{a_0}\), \(y_3 = a_1\,a_0\). Decoders select one memory word out of many, one device on a bus, or one digit of a display. Often an enable input is ANDed into every output so that all outputs can be forced to 0.

An encoder does the reverse: from \(2^k\) input wires it produces the \(k\)-bit index of the one that is active. A plain encoder assumes only one input is 1 at a time. A priority encoder handles any input pattern by reporting the highest-numbered active input, plus a valid flag for the case where none is active. For 4 inputs:

r3r2r1r0code q1 q0valid
000000 (don’t care)0
0001001
001x011
01xx101
1xxx111

Here x means "either value", which compresses 16 rows into 5. Reading the table: \(q_1 = r_3 + r_2\), \(q_0 = r_3 + \overline{r_2}\,r_1\), \(\text{valid} = r_3 + r_2 + r_1 + r_0\). Priority encoders arbitrate between several requests, for example deciding which of several interrupt sources is served first.

4. Comparators

Two bits are equal exactly when their XOR is 0, that is when their XNOR is 1. Two \(n\)-bit words are equal when every pair of bits is equal, so an equality comparator is \(n\) XNOR gates followed by one wide AND:

\[ (A = B) \;=\; \prod_{i=0}^{n-1} \overline{a_i \oplus b_i} \]

A magnitude comparator (\(A > B\)) works like comparing two decimal numbers by eye: scan from the most significant digit; the first position where they differ decides. For unsigned numbers, \(A > B\) if at some position \(i\) we have \(a_i = 1,\ b_i = 0\) and all higher bits are equal:

\[ (A > B) = a_{n-1}\overline{b_{n-1}} \;+\; e_{n-1}\,a_{n-2}\overline{b_{n-2}} \;+\; e_{n-1}e_{n-2}\,a_{n-3}\overline{b_{n-3}} \;+\;\cdots, \qquad e_i = \overline{a_i \oplus b_i} \]

In practice, as Section 6 shows, hardware usually computes \(A - B\) with an adder and looks at the sign or the carry: \(A \ge B\) (unsigned) exactly when the subtraction produces a carry out. Comparing against a constant is cheaper still: count == 9 is just a single AND of the counter bits, some of them inverted.

5. Adding bits: the half and full adder

Adding two single bits \(a + b\) gives a result from 0 to 2, which needs two bits: a sum bit \(s\) (weight 1) and a carry bit \(c\) (weight 2). The truth table reveals two gates you already know:

aba + bcarry csum s
00000
01101
10101
11210
\[ \text{half adder:}\qquad s = a \oplus b, \qquad c = a\,b \]

This is called a half adder because it is only half of what we need. To add multi-bit numbers, each column must also add the carry coming in from the column to its right, exactly like the "carry the one" of school arithmetic. A full adder adds three bits \(a, b, c_\text{in}\), and its result 0–3 still fits in two bits:

abcintotalcoutsum
000000
001101
010101
011210
100101
101210
110210
111311

The sum is 1 when an odd number of inputs is 1, which is a chain of XORs. The carry is 1 when at least two of the three inputs are 1: it is the majority function from Chapter 1.

\[ s = a \oplus b \oplus c_\text{in}, \qquad c_\text{out} = ab + a\,c_\text{in} + b\,c_\text{in} = \underbrace{a\,b}_{g} + c_\text{in}\,\underbrace{(a \oplus b)}_{p} \]

The second form of the carry has a useful reading. The column generates a carry by itself when \(g = ab = 1\), and it propagates an incoming carry when \(p = a \oplus b = 1\)(exactly one of \(a, b\) is 1, so adding \(c_\text{in}\) overflows the column). The two forms agree on all eight rows; the difference is the case \(a = b = 1\), already covered by \(g\). Note that \(p\) also appears in the sum, \(s = p \oplus c_\text{in}\), so one XOR gate serves both. A full adder is therefore two half adders plus an OR gate.

6. The ripple-carry adder and its delay

To add two \(N\)-bit numbers, put \(N\) full adders side by side and connect each carry out to the next carry in. This is a ripple-carry adder. It is small (one full adder per bit) and obviously correct, because it is long addition done in hardware.

FA3full addera3b3s3c3FA2full addera2b2s2c2FA1full addera1b1s1c1FA0full addera0b0s0c0 = cincoutcarry ripples right → left: worst-case delay grows linearly with N
A 4-bit ripple-carry adder. All a, b bits arrive together, but bit 3 cannot know its carry until the carry has rippled through every stage to its right. The red path is the critical path.

Its weakness is speed. Each gate needs a small time to respond, its propagation delay (Section 8). The sum bit \(s_{N-1}\) is not final until \(c_{N-1}\) is, and \(c_{N-1}\) may depend on every bit below it. The worst case is something like \(0\text{x}FF + 0\text{x}01\), where every column propagates and a carry born in bit 0 must travel all the way to the top. Counting gate delays in our full adder (with \(c_\text{in} = 0\)): \(p, g\) are ready after 1 delay, \(c_1 = g_0\) after 2, each further carry needs an AND and an OR (+2), and the last sum one XOR (+1):

\[ t_\text{ripple} = 2 + 2(N-2) + 1 = 2N - 1 \text{ gate delays} \qquad\Longrightarrow\qquad t_\text{ripple} \propto N \]

The program below simulates the adder gate by gate, every gate with one unit of delay. Watch each sum bit flip to 1 and then back to 0 as the carry passes: these temporary wrong values are called glitches. The histogram shows that most input changes settle quickly, but a synchronous circuit (Chapter 3) must always wait for the worst case.

Python
script.py77 lines

Click Run to execute the Python code

Code will be executed with Python 3 on the server

The same adder also subtracts. Since \(-B = \overline{B} + 1\) in two's complement (Chapter 1), feed it \(\overline{B}\) instead of \(B\) and set \(c_\text{in} = 1\): \(A - B = A + \overline{B} + 1\). A row of XOR gates controlled by a single sub signal (\(b_i \oplus \text{sub}\), with \(c_\text{in} = \text{sub}\)) turns the adder into an adder/subtractor.

Key idea: FPGAs have a fast lane for carries
Faster adder architectures (carry-lookahead, carry-select) compute carries in parallel at the cost of more logic. FPGAs take a different route: next to the LUTs sits a dedicated carry chain, hard-wired multiplexers and XORs that ripple the carry through very fast, dedicated connections. You write a + b and the tools use it automatically. Chapter 5 opens it up.

7. Simplifying logic with Karnaugh maps

Simplifying sum-of-products expressions with algebra (as for the majority function in Chapter 1) works, but it depends on spotting the right steps. A Karnaugh map (K-map) makes the steps visible. It is the truth table redrawn as a grid, with the rows and columns ordered in Gray code (00, 01, 11, 10) so that any two neighbouring cells differ in exactly one variable. The left and right edges are also neighbours, and so are the top and bottom.

Why it works: two adjacent 1-cells differ in one variable \(x\), so their minterms combine as \(P\,x + P\,\overline{x} = P\) and the variable disappears. A rectangle of \(2^k\) cells removes \(k\) variables. The recipe:

  1. Fill in the 1s from the truth table.
  2. Cover every 1 with rectangles of size 1, 2, 4, 8… cells, as large as possible (wrap-around allowed). Overlap is fine.
  3. Use as few rectangles as possible.
  4. Each rectangle becomes one product term: keep only the variables that are constant across it.

Example: \(F(a,b,c,d) = \sum m(0,1,2,5,8,9,10)\), where \(\sum m(\ldots)\) lists the rows (minterm numbers, \(8a + 4b + 2c + d\)) where \(F = 1\). The direct sum of products has seven 4-input terms.

cdab00011110000111101m01m10m31m20m41m50m70m60m120m130m150m141m81m90m111m10dashed: the four corners → b̄ d̄blue: rows 00 and 10 (adjacentthrough the wrap), cd = 00, 01 → b̄ c̄green: pair m1, m5 → ā c̄ dF = b̄c̄ + b̄d̄ + āc̄d7 four-input terms → 3 small terms
Karnaugh map of F = Σm(0,1,2,5,8,9,10). Small grey labels are minterm indices. Three groups cover all 1s: the four corners (b̄d̄), the top and bottom rows of the first two columns (b̄c̄), and a vertical pair (āc̄d).
\[ F = \overline{b}\,\overline{c} + \overline{b}\,\overline{d} + \overline{a}\,\overline{c}\,d \]

Read each group by asking which variables do not change inside it. In the corner group, \(a\) and \(c\) take both values while \(b = 0\) and \(d = 0\) throughout, giving \(\overline{b}\,\overline{d}\). Cells you do not care about (input codes that can never occur) can be marked x and treated as 1 or 0, whichever makes groups larger.

Common mistake: ordering the map in binary
If you label the columns 00, 01, 10, 11, neighbouring cells 01 and 10 differ in two variables and the grouping rule silently gives wrong answers. Always use Gray order: 00, 01, 11, 10. And remember the wrap-around: the corners of a 4×4 map are all neighbours.

K-maps are practical up to four or five variables. You will not use them to design FPGA logic, because the synthesis tools minimise far better and, in an FPGA, any function of up to six inputs costs one LUT regardless of how simple it is. They are still the best way to understand what minimisation does, and they show up whenever you reason about a small piece of control logic by hand.

8. Propagation delay and the critical path

A real gate does not respond instantly. After an input changes, the output stays at its old value for a short time, may wobble, and then settles. Two numbers describe this:

  • The propagation delay \(t_{pd}\): the longest time from an input change until the output is guaranteed to be stable at its new value.
  • The contamination delay \(t_{cd}\): the shortest time from an input change until the output might start to change. Before that, the old value is still guaranteed.

For a whole combinational circuit, add up the delays along each path from an input to an output. The path with the largest total is the critical path; it sets the circuit's \(t_{pd}\). The shortest path sets its \(t_{cd}\):

\[ t_{pd}^{\text{circuit}} = \max_{\text{paths}} \sum_{\text{gates on path}} t_{pd,i}, \qquad t_{cd}^{\text{circuit}} = \min_{\text{paths}} \sum_{\text{gates on path}} t_{cd,i} \]

In the ripple-carry adder the critical path is the carry chain: from \(a_0\) through every carry to \(s_{N-1}\). Between \(t_{cd}\) and \(t_{pd}\) the outputs may glitch, as the simulation showed. In an FPGA, delay comes as much from the programmable wiring between LUTs as from the LUTs themselves: on the order of 0.1 ns per LUT, and several times that per routing hop (Chapter 5).

Key idea: wait for the slowest path, then look
Combinational logic is only trustworthy after it has settled. The solution adopted by nearly all digital systems is to sample the outputs at regular instants, spaced further apart than the critical-path delay, and ignore everything in between. The circuit that samples is the flip-flop, and the instants are the edges of a clock. That is Chapter 3, and the precise timing rule is Chapter 15.

9. Preview: the same thing in HDL

Here are the 4:1 multiplexer and the full adder written in Verilog and VHDL, followed by a 4-bit ripple-carry adder built from four full adders. The syntax is the subject of Part III (Chapters 7–9); for now, compare each line with the equations above. The mux is the sum-of-products equation of Section 2, the full adder is the \(s\) and \(c_\text{out}\) equations of Section 5, and the ripple adder wires the carries together as in the figure of Section 6.

mux4 — the 4:1 multiplexer equation
// 4:1 multiplexer: the select code s picks one of four data inputs.
// Sum-of-products form, one product term per input.
module mux4 (
    input  wire [3:0] d,     // data inputs d[0]..d[3]
    input  wire [1:0] s,     // select
    output wire       y
);
    assign y = (d[0] & ~s[1] & ~s[0])
             | (d[1] & ~s[1] &  s[0])
             | (d[2] &  s[1] & ~s[0])
             | (d[3] &  s[1] &  s[0]);
endmodule
full_adder — sum and carry
// One-bit full adder: a + b + cin = 2*cout + sum
module full_adder (
    input  wire a,
    input  wire b,
    input  wire cin,
    output wire sum,
    output wire cout
);
    assign sum  = a ^ b ^ cin;                  // odd number of 1s
    assign cout = (a & b) | (cin & (a ^ b));    // generate, or propagate a carry
endmodule
ripple4 — four full adders in a chain
// 4-bit ripple-carry adder: four full adders, each carry feeding the next.
module ripple4 (
    input  wire [3:0] a,
    input  wire [3:0] b,
    input  wire       cin,
    output wire [3:0] sum,
    output wire       cout
);
    wire c1, c2, c3;

    full_adder fa0 (.a(a[0]), .b(b[0]), .cin(cin), .sum(sum[0]), .cout(c1));
    full_adder fa1 (.a(a[1]), .b(b[1]), .cin(c1),  .sum(sum[1]), .cout(c2));
    full_adder fa2 (.a(a[2]), .b(b[2]), .cin(c2),  .sum(sum[2]), .cout(c3));
    full_adder fa3 (.a(a[3]), .b(b[3]), .cin(c3),  .sum(sum[3]), .cout(cout));
endmodule

A testbench (Chapter 10) applied all 512 combinations of \(a, b, c_\text{in}\) to ripple4 and all 64 combinations of data and select to mux4, comparing each result with ordinary arithmetic. Both the Verilog and the VHDL version print PASS: 512 additions and 64 mux selections correct. In real designs you will simply write a + b; the synthesiser builds the adder on the FPGA's carry chain.

Practice Problems

Progress: 0/5 (0%)
1
⭐ Easy
(a) How many select bits does a 32:1 multiplexer need? (b) How many outputs does a decoder with 5 inputs have, and how many of them are 1 at any time?
2
⭐ Easy
Write a Boolean expression for a 4-bit equality comparator eq that is 1 when \(a_3a_2a_1a_0 = b_3b_2b_1b_0\). Which gate type do you need four of?
3
⭐⭐ Medium
Use a Karnaugh map to minimise \(F(a,b,c) = \sum m(0, 2, 4, 5, 6)\), where the minterm index is \(4a + 2b + c\). Check your answer on minterm 5.
4
⭐⭐ Medium
In some technology an AND or OR gate has a propagation delay of 60 ps and an XOR 80 ps. Using the full adder of this chapter, estimate the worst-case delay of a 32-bit ripple-carry adder (carry-in tied to 0). What is the highest rate at which it could produce new results?
5
⭐⭐⭐ Hard
(a) Show that a 4:1 multiplexer whose data inputs are tied to constants \(d_0, d_1, d_2, d_3\) and whose select inputs are driven by two signals \(x, y\) can implement any Boolean function of \(x\) and \(y\). Give the constants for XOR. (b) Generalise: what is the relationship between a \(2^n\):1 multiplexer and an \(n\)-input truth table? (c) Show how to build a 4:1 mux from three 2:1 muxes.

Problem Set Statistics

Total Problems
5
Attempted
0
Completion
0%
Share:XRedditLinkedIn
Rate this chapter: