Teach lesson
Digital Logic on the DE1-SoC (2/6): Truth tables, SOP/POS, K-maps, and a multiplexer
Students connect truth tables, Boolean forms, Karnaugh maps, and multiplexers, then test minimized combinational logic on DE1-SoC hardware.
Learning Outcomes
Derive the sum-of-products (SOP) and product-of-sums (POS) forms from a truth table.
Minimize a Boolean function with a Karnaugh map and read off the simplified expression.
Compare a canonical form with its minimal form on real hardware and confirm they match.
Build and verify a 4-to-1 multiplexer that routes one of four inputs to the output.
Student activity preview
Activity Content
Preview only. In a class session, students can fill in responses and submit their work to the teacher.
From truth table to circuit
8 min
In Lesson 1 you wired single gates to switches. Real designs start one level up: you decide what a circuit should do — usually as a truth table — and then find a gate network that produces it. This lesson works one function from both directions and then makes it smaller.
The function for the first two exercises is F(C,B,A), a function of three inputs. You will wire SW[0]=A, SW[1]=B, SW[2]=C, and show the result on LEDR[0]. Here is its truth table. The rows are listed in binary counting order C B A from 000 to 111:
Truth table for F(C,B,A), in binary counting order:
- Row 0: C B A = 0 0 0 -> F=0
- Row 1: C B A = 0 0 1 -> F=1
- Row 2: C B A = 0 1 0 -> F=0
- Row 3: C B A = 0 1 1 -> F=1
- Row 4: C B A = 1 0 0 -> F=1
- Row 5: C B A = 1 0 1 -> F=0
- Row 6: C B A = 1 1 0 -> F=1
- Row 7: C B A = 1 1 1 -> F=0
F is 1 on four of the eight rows and 0 on the other four — read them off the table yourself before you answer below. This split drives the whole lesson: the F=1 rows give you the sum of products, and the F=0 rows give you the product of sums. Keep this table in front of you for both exercises — everything below refers back to it.
Recall the board conventions you used in Lesson 1: a switch up is logic 1, and LEDR is active-high, so the LED lights when the output is 1. These exercises are purely combinational — the output depends only on the current switch positions, so there is no clock to declare.
On how many of the eight rows does F equal 1, and which rows are they?
Sum of products and the K-map
14 min
Minterm. A *minterm* is a single AND (product) term built from all the inputs — each input appears once, plain or negated — that is 1 for exactly one row of the truth table. For three inputs there are eight minterms, one per row. Row 1 is C=0, B=0, A=1, so its minterm is (~C & ~B & A): this product is 1 only when C=0, B=0, and A=1, and 0 everywhere else.
Canonical sum of products (SOP). To build F, OR together the minterm of every row where F=1. Because each minterm is 1 for exactly its own row, the OR is 1 on precisely the F=1 rows and 0 elsewhere — which is exactly F. The four F=1 rows (1, 3, 4, 6) give:
- Row 1 C=0,B=0,A=1 → (~C & ~B & A)
- Row 3 C=0,B=1,A=1 → (~C & B & A)
- Row 4 C=1,B=0,A=0 → (C & ~B & ~A)
- Row 6 C=1,B=1,A=0 → (C & B & ~A)
So the canonical SOP is F = (~C & ~B & A) | (~C & B & A) | (C & ~B & ~A) | (C & B & ~A). That is correct but wasteful: four 3-input AND terms feeding a 4-input OR.
Minimizing with a Karnaugh map. A *K-map* is the truth table redrawn so that physically adjacent cells differ in exactly one input. When two adjacent 1s sit next to each other, the variable that *changes* between them does not matter for that pair and can be dropped, leaving a smaller term. Group the four F=1 minterms into two pairs:
- Group minterms 1 and 3. Row 1 is C=0, B=0, A=1; row 3 is C=0, B=1, A=1. Both have C=0 and A=1; only B differs (0 vs 1). Because B changes while the term stays 1, B is eliminated, and the pair collapses to (~C & A).
- Group minterms 4 and 6. Row 4 is C=1, B=0, A=0; row 6 is C=1, B=1, A=0. Both have C=1 and A=0; again only B differs, so B is eliminated and the pair collapses to (C & ~A).
OR the two reduced terms: F = (~C & A) | (C & ~A). That is the definition of exclusive-OR between A and C, so:
> F = A ^ C
Notice what happened: B was eliminated from *both* groups, so B does not appear in the minimal expression at all. That is the K-map telling you something real about the function — F does not depend on B. You can confirm it directly in the truth table: find two rows that differ only in B and check that F is the same in both.
The K-map for F. Each group of two adjacent 1s eliminates the one variable that changes across it. Both groups here eliminate B, leaving (~C & A) | (C & ~A) = A ^ C. Because B vanishes from every group, F is independent of B.
Apply the same minterm method to a *different* function. A function G(C,B,A) is 1 on exactly rows 0, 2, 5, 7 (and 0 on rows 1, 3, 4, 6). Write the full canonical sum-of-products expression for G — the OR of one minterm per G=1 row. Use ~ for NOT, & for AND, and | for OR.
Does B appear in the minimal expression F = A ^ C? Explain what its absence tells you about how F depends on its three inputs. Then find — on your own — two truth-table rows that differ only in B, and give their row numbers and F values to confirm it.
Build it: canonical SOP, then the minimal form
14 min
Now put the function on the real board. You will run the canonical SOP first, verify all eight rows, then switch to the minimal F = A ^ C and confirm you get the identical outputs.
The module below ships with the minimal form active and the canonical SOP on a commented line. For the first pass, comment out the minimal assign and uncomment the canonical SOP line so you are testing the four-term version; then reverse it for the second pass.
// Lesson 2 - Exercise 1-B: Sum of Products (SOP)
// F(C,B,A): minterms 1,3,4,6. SW[0]=A, SW[1]=B, SW[2]=C. Output on LEDR[0].
// Replace ALL contents of leds_mirror.v with this module.
module leds_mirror(SW, LEDR);
input [9:0] SW; // SW[0]=A, SW[1]=B, SW[2]=C
output [9:0] LEDR;
wire A = SW[0];
wire B = SW[1];
wire C = SW[2];
// Canonical SOP (one product term per F=1 row). Uncomment to test it first:
// assign LEDR[0] = (~C & ~B & A) | (~C & B & A) | (C & ~B & ~A) | (C & B & ~A);
// Minimal form from the K-map: F = A XOR C
assign LEDR[0] = A ^ C;
assign LEDR[9:1] = 9'b0; // unused LEDs off
endmoduleOpen the DE1-SoC Verilog lab. The editor shows a file
leds_mirror.v.Select all of the contents of
leds_mirror.vand replace them with the SOP module above. Keep the module namedleds_mirror.For the first pass, test the canonical SOP: comment out the line
assign LEDR[0] = A ^ C;and remove the//in front of the canonical SOP line so it becomes active. Exactly one of the twoassign LEDR[0]lines must be uncommented.Click Synthesize. If a guided tour covers the button, close or skip the tour first. Wait for it to finish (about 1-3 minutes). If it reports an error, check for a missing semicolon or mismatched parenthesis, fix it, and Synthesize again. Continue only when the final status says the build succeeded with 0 errors.
Click Upload to FPGA and wait for the live board session and camera.
Step
SW[2:0](C B A) through all eight combinations000, 001, 010, 011, 100, 101, 110, 111, readingLEDR[0]on the camera each time. Record each result in the table below. (LeaveSW[1]=Btoggling as part of the count even though you expect it not to matter.)For the second pass, edit the file again: re-comment the canonical SOP line and uncomment
assign LEDR[0] = A ^ C;. Synthesize and Upload again, then spot-check a few rows (for example001,100,111) and confirmLEDR[0]is identical to the canonical version.
Set SW[2:0] = C B A for each row and record the LEDR[0] value you read on the camera (1 = lit, 0 = off). The expected F column is the function from the truth table; your observed column should match it for both the canonical SOP and the minimal A^C version.
| C = SW2 | B = SW1 | A = SW0 | F = LEDR0 (observed) |
|---|---|---|---|
Did the canonical four-term SOP and the minimal F = A ^ C produce the same LEDR[0] for the rows you checked? In one sentence, say what that confirms about the two expressions.
Upload your evidence. Capture a camera screenshot of the minimal F = A ^ C running with the switches set to a row where F = 1 (for example C B A = 100: SW[2] up, SW[1] and SW[0] down, so LEDR[0] is lit), and attach it below.
> Optional bonus (no upload required). Quartus reports how many logic elements a design uses. If your lab surfaces that resource report, synthesize the four-term canonical SOP and the one-operator A ^ C and compare their logic-element counts — the minimal form should use fewer. Treat this as exploration only; the in-IDE resource report is not guaranteed on every lab session, so it is not part of the required evidence.
The same function as a product of sums
14 min
The SOP built F from its 1 rows. The product of sums (POS) builds the same F from its 0 rows instead.
Maxterm. A *maxterm* is a single OR (sum) term using all the inputs — each plain or negated — that is 0 for exactly one row. It is the mirror image of a minterm. For row 0 (C=0, B=0, A=0) the maxterm is (A | B | C): this OR is 0 only when A, B, and C are all 0, and 1 on every other row.
Canonical product of sums. AND together the maxterm of every row where F=0. Each maxterm forces the output to 0 on its own row and leaves it 1 elsewhere, so the AND of all of them is 0 on exactly the F=0 rows — again exactly F. The four F=0 rows are 0, 2, 5, 7:
- Row 0 C=0,B=0,A=0 → (A | B | C)
- Row 2 C=0,B=1,A=0 → (A | ~B | C)
- Row 5 C=1,B=0,A=1 → (~A | B | ~C)
- Row 7 C=1,B=1,A=1 → (~A | ~B | ~C)
In each maxterm, a variable appears *negated* when it is 1 in that F=0 row (so the term goes to 0 there). The module below implements this canonical POS directly. It describes the very same function, so its eight outputs must match what you already recorded: rows 0..7 give 0, 1, 0, 1, 1, 0, 1, 0.
// Lesson 2 - Exercise 1-C: Product of Sums (POS)
// Same F(C,B,A). Maxterms (F=0 rows): 0, 2, 5, 7. SW[0]=A, SW[1]=B, SW[2]=C. Output LEDR[0].
// Expected outputs for rows 0..7 (C,B,A = 000..111): 0,1,0,1,1,0,1,0 (identical to the SOP version).
module leds_mirror(SW, LEDR);
input [9:0] SW; // SW[0]=A, SW[1]=B, SW[2]=C
output [9:0] LEDR;
wire A = SW[0];
wire B = SW[1];
wire C = SW[2];
// Canonical POS: one sum term per F=0 row.
// Row 0 (C=0,B=0,A=0): (A | B | C)
// Row 2 (C=0,B=1,A=0): (A | ~B | C)
// Row 5 (C=1,B=0,A=1): (~A | B | ~C)
// Row 7 (C=1,B=1,A=1): (~A | ~B | ~C)
assign LEDR[0] = (A | B | C) & (A | ~B | C) & (~A | B | ~C) & (~A | ~B | ~C);
assign LEDR[9:1] = 9'b0;
endmoduleIn the DE1-SoC Verilog lab, select all of the contents of
leds_mirror.vand replace them with the POS module above. Keep the module namedleds_mirror.Click Synthesize and wait for it to finish. Fix any reported syntax error and Synthesize again.
Click Upload to FPGA and wait for the live camera.
Step
SW[2:0](C B A) through all eight combinations000 … 111, readingLEDR[0]each time. Confirm the sequence of outputs is0, 1, 0, 1, 1, 0, 1, 0, matching the SOP results you recorded earlier.If any row disagrees with
0, 1, 0, 1, 1, 0, 1, 0, the mismatch is almost certainly a switch-setting slip rather than a logic error — recheck which switch isC,B, andAand re-read that row.
For this particular F, how does the canonical POS compare in size with the canonical SOP?
A 4-to-1 multiplexer
12 min
A multiplexer (MUX) is a selector: it routes one of several data inputs to a single output, chosen by a set of select lines. A 4-to-1 MUX has four data inputs and needs two select lines (because two bits choose among 2^2 = 4 inputs). It behaves like a rotary switch: the select value picks which data line is connected to the output.
The pin assignment for this exercise — stated explicitly so the bit order is unambiguous:
- Data inputs: SW[3]=D3, SW[2]=D2, SW[1]=D1, SW[0]=D0
- Select lines: SW[5]=S1, SW[4]=S0
- Output: LEDR[0] = Y
The select value {S1, S0} chooses the data line: 00 → D0, 01 → D1, 10 → D2, 11 → D3. The module uses a case statement inside always @(*). The default branch is not decorative — in a combinational always block, if some input combination left Y unassigned, the synthesizer would infer a latch to "remember" the old value, turning your intended pure-combinational logic into accidental memory. Assigning Y in every branch (here via default) guarantees Y always has a defined value and no latch is created.
// Lesson 2 - Exercise 1-D: 4-to-1 multiplexer (case-statement style)
// Data inputs: SW[3]=D3, SW[2]=D2, SW[1]=D1, SW[0]=D0
// Select lines: SW[5]=S1, SW[4]=S0
// Output: LEDR[0] = Y
// For SW[3:0]=1010, Y for S1:S0 = 00,01,10,11 is 0,1,0,1.
module leds_mirror(SW, LEDR);
input [9:0] SW;
output [9:0] LEDR;
wire D0 = SW[0], D1 = SW[1], D2 = SW[2], D3 = SW[3];
wire S0 = SW[4], S1 = SW[5];
reg Y;
always @(*) begin
case ({S1, S0})
2'b00: Y = D0;
2'b01: Y = D1;
2'b10: Y = D2;
2'b11: Y = D3;
default: Y = 1'b0; // default avoids an inferred latch
endcase
end
assign LEDR[0] = Y;
assign LEDR[9:1] = 9'b0;
endmoduleIn the DE1-SoC Verilog lab, select all of the contents of
leds_mirror.vand replace them with the multiplexer module above. Keep the module namedleds_mirror.Click Synthesize and wait for it to finish. Fix any reported syntax error and Synthesize again.
Click Upload to FPGA and wait for the live camera.
Set the four data switches to the pattern
SW[3:0] = 1010— that isD3=1(SW[3]up),D2=0(SW[2]down),D1=1(SW[1]up),D0=0(SW[0]down).Now cycle the two select switches through
S1:S0 = 00, 01, 10, 11usingSW[5](S1) andSW[4](S0). For each select value, readLEDR[0](Y) and record it in the table below. With data1010, the output should follow the selected data line:0, 1, 0, 1.As a sanity check, pick one select value, change the corresponding data switch, and confirm
Ytracks that one data line while the others have no effect.
Keep the data switches at SW[3:0] = 1010 (D3 D2 D1 D0 = 1 0 1 0). For each select value, set SW[5]=S1 and SW[4]=S0, note which data input is selected, and record the observed LEDR[0] (Y). With this data pattern the expected Y is 0, 1, 0, 1.
| S1 = SW5 | S0 = SW4 | Selected input (D0..D3) | Y = LEDR0 (observed) |
|---|---|---|---|
Upload your evidence. Capture a camera screenshot of the multiplexer with data SW[3:0] = 1010 and select S1:S0 = 01 (SW[4] up, SW[5] down) — this selects D1 = 1, so LEDR[0] is lit — and attach it below.
Imagine you deleted the default: Y = 1'b0; line so that some input combination left Y unassigned. What unwanted piece of hardware would the synthesizer infer, and why does that conflict with the pure-combinational multiplexer you intended?
Bonus: a two-digit decimal 7-segment decoder
4 min
This exercise is optional. If you have time, it is a satisfying jump from a single LED to formatted output on the seven-segment displays.
A seven-segment display has seven bars — labelled a through g — that light in combinations to draw a digit. On the DE1-SoC each HEX display is active-low and the bit order is {g, f, e, d, c, b, a}: a segment lights when you drive its bit to 0. So the pattern for the digit 0 (segments a,b,c,d,e,f on, g off) is 7'b1000000 — six 0s for the lit segments and a 1 for the unlit g.
Segment naming and the active-low {g,f,e,d,c,b,a} bit order on the DE1-SoC HEX displays. A bit of 0 lights its segment; a 1 turns it off. The displayed digit 0 lights every segment except g, giving the encoding 7'b1000000.
The design below reads SW[3:0] as a value 0..15, shows the ones digit on HEX0 and the tens digit (0 or 1) on HEX1. Note one thing the default project does not give you for free: to drive HEX1 you must add output [6:0] HEX1 to the module's port list — the starter leds_mirror exposes only HEX0. The module below already declares both HEX0 and HEX1, so this is the example that makes the extra port concrete.
// Lesson 2 - Exercise 1-E (optional): two-digit decimal decoder
// SW[3:0] selects a value 0..15. HEX0 shows the ones digit, HEX1 shows the tens digit.
// 7-segment is active-low, segment order {g,f,e,d,c,b,a}: a segment lights when driven 0.
module leds_mirror(SW, HEX0, HEX1);
input [9:0] SW; // SW[3:0] = value 0..15
output [6:0] HEX0; // ones digit
output [6:0] HEX1; // tens digit (0 or 1)
wire [4:0] v = {1'b0, SW[3:0]}; // 0..15
function [6:0] seg; // decimal digit 0..9 -> active-low segments
input [3:0] d;
case (d)
4'd0: seg = 7'b1000000; 4'd1: seg = 7'b1111001;
4'd2: seg = 7'b0100100; 4'd3: seg = 7'b0110000;
4'd4: seg = 7'b0011001; 4'd5: seg = 7'b0010010;
4'd6: seg = 7'b0000010; 4'd7: seg = 7'b1111000;
4'd8: seg = 7'b0000000; 4'd9: seg = 7'b0010000;
default: seg = 7'b1111111;
endcase
endfunction
wire [3:0] tens = (v >= 5'd10) ? 4'd1 : 4'd0;
wire [3:0] ones = (v >= 5'd10) ? (v[3:0] - 4'd10) : v[3:0];
assign HEX0 = seg(ones);
assign HEX1 = seg(tens);
endmoduleOptional. In the DE1-SoC Verilog lab, replace all of the contents of
leds_mirror.vwith the decoder module above. Keep the module namedleds_mirror. Confirm the port list readsmodule leds_mirror(SW, HEX0, HEX1);— the addedoutput [6:0] HEX1is what lets you drive the tens display.Click Synthesize, fix any reported error, and Synthesize again.
Click Upload to FPGA and wait for the live camera.
Set
SW[3:0]to a few values and read both displays:0000should show0onHEX0and0onHEX1;1001(decimal 9) shows9and0;1111(decimal 15) shows5onHEX0and1onHEX1.
What one change to the module's port list was required before this design could light HEX1, and why was it necessary?
What you built
4 min
In a short paragraph, summarize what you did with F(C,B,A): how the canonical SOP and POS each came from the truth table, how the K-map reduced F to A ^ C, and what the disappearance of B told you. Mention that you confirmed the canonical and minimal forms gave identical outputs on the board.
For the multiplexer with data SW[3:0] = 1010, what sequence of Y values did you observe as S1:S0 went 00, 01, 10, 11, and why is that sequence exactly the bits of 1010 read in that order?