Topic 2 · Combinational Building Blocks

The 7-Segment Display

Video 4 of 4 · ~12 minutes

Dr. Mike Borowczak · Electrical & Computer Engineering · CECS · UCF

Data TypesOperatorsSized Literals7-Seg Display

🌍 Where This Lives

In Industry

The hex-to-7seg decoder is the canonical “Hello, World!” of digital design. It shows up in interview whiteboard problems, FPGA vendor demos, and the first chapter of every HDL textbook. Master it and you've crossed the threshold from theory to real hardware.

In This Course

Active-low logic, case statements, pin mapping — all three concepts land today. The case pattern shows up again in Topic 3 (ALU), Topic 7 (FSMs), Topic 11 (UART state logic). Pin mapping with .pcf files is every lab from now on.

First real peripheral: By the end of today's lab, your Verilog code will drive pins that drive LEDs that form a digit that a human can read. That's the full stack: RTL → synthesis → place-and-route → bitstream → flash → photons.

⚠️ Active-Low: When 0 Means ON

❌ Wrong Model

“Of course 1 means ON. Drive the pin high to turn the segment on.”

✓ Right Model

On the Go Board, segments are wired as common-anode active-low. The LED's anode is tied to VCC; your FPGA pin pulls the cathode low to turn it on. 0 = ON. 1 = OFF. The comment block in every decoder file reminds you of this. Read it before you code.

The failure mode: You write the logical (active-high) version, flash the board, and see an “inverted” or “solid” display. The fix is always to flip every bit. Adding ~ once is better than rewriting every case.

Simplified Circuits: One Segment, Both Polarities

Active-high (common-cathode) one-segment circuit
// Active-high: drive 1 to light segment a
assign o_seg_a = 1'b1;   // ON
// assign o_seg_a = 1'b0; // OFF
Active-low (common-anode) one-segment circuit
// Active-low: drive 0 to light segment a (Go Board)
assign o_seg_a = 1'b0;   // ON
// assign o_seg_a = 1'b1; // OFF
Same LED, opposite wiring: the only difference is which rail the common terminal ties to. Active-high puts the common pin on GND and the FPGA sources current up through the LED; active-low puts the common pin on VCC and the FPGA sinks current down through it. Your HDL bit must match the wiring, or every segment is inverted.
Why active-low often wins: CMOS output drivers usually sink more current than they source, so pulling a pin low to light an LED is electrically easier than pushing it high. Tying the common LED terminal to VCC also lets a single shared rail feed every segment — fewer high-current source paths through the FPGA, lower IOH stress, and a smaller IR drop on the chip's power network. For idle/standby power, an active-low bus held high (1) draws essentially zero LED current — the default state is “off” with no current flowing through any segment. The same logic explains why resets, chip-selects, and button inputs are typically active-low: a pull-up to VCC plus a switch to GND is the cheapest, lowest-leakage default state.

🔍 How to Tell: Active-Low vs Active-High

Active-High (1 = ON)

Typical for common-cathode displays: cathodes tied to GND, FPGA pin sources current up through the LED. Logic mirrors light: 1 bit → segment glows.

Active-Low (0 = ON)

Typical for common-anode displays (and the Go Board): anodes tied to VCC, FPGA pin sinks current down through the LED. Logic is inverted: 0 bit → segment glows.

Five ways to figure out which one you have — in order of reliability:
  1. Read the schematic. Trace one segment pin. If it goes through a resistor to the LED's cathode and the anode is on VCC → active-low. If anode-side and cathode is on GND → active-high.
  2. Read the constraints / pin file. Good .pcf / .xdc files annotate it (ours does: # 7-Segment Display 1 (active low segments)).
  3. Read the vendor's example code. If their top-level wraps the decoder with ~ or ! (e.g. Nandland's assign o_Segment1_A = !w_Seg_A;), the board is active-low.
  4. Read the part datasheet. Common-anode vs common-cathode is right on page 1.
  5. Empirical test. Drive one pin constant 0; if the segment lights, it's active-low. (Last resort — the schematic should never be a mystery.)
Why both exist: FPGA I/O typically sinks more current than it sources, so common-anode (active-low) is mechanically friendlier for LED arrays. Buttons and resets are often active-low for the same reason: a pull-up to VCC plus a switch to GND is the cheapest reliable circuit.
Same board, both polarities: The Go Board's 4 discrete LEDs and 4 push-buttons are active-high (1 = on, 1 = pressed) — see Topic 1's buttons_to_leds. The 7-segment segments on the very same FPGA are active-low. Polarity is per-peripheral, not per-board.

7-Segment Display: Segment Map

7-segment display with labeled segments a-g

Convention: {a,b,c,d,e,f,g} MSB→LSB

a = top, then clockwise: b, c, d, e, f, g = middle

Go Board wiring: 0 = ON, 1 = OFF

So “0” digit = 7'b0000001 (all segments on except g)

👁️ I Do — Derive “3” From Scratch

Signals: i_hex is the 4-bit input nibble (the value 0F we want to display); o_seg is the 7-bit output bus {a,b,c,d,e,f,g} driving the segments active-low. Full module declaration appears two slides ahead.

7-segment showing digit 3 with segments e,f greyed out

{a,b,c,d,e,f,g} — MSB → LSB

Digit “3” lights segments: a, b, c, d, g.

Segments OFF: e, f.

Order:  a b c d e f g
Active: 1 1 1 1 0 0 1   ← logical
Pins:   0 0 0 0 1 1 0   ← active-low (flipped)
case (i_hex)
    // ...
    4'h3: o_seg = 7'b0000110;
    //             abcdefg
    //             0000110 = ON:abcd,g OFF:e,f
    // ...
endcase
My process: For each digit I (1) draw the digit on paper, (2) mark which segments glow, (3) write the logical pattern, (4) invert every bit for active-low. Doing step 3 first prevents bugs — it's easier to think positively then flip once.

🤝 We Do — Fill in “5” and “A”

case (i_hex)
    4'h5: o_seg = 7'b???????;  // segments: a, c, d, f, g (ON)
    4'hA: o_seg = 7'b???????;  // segments: a, b, c, e, f, g (ON)
endcase
7-segment showing digit 5 with segments b,e greyed out 7-segment showing digit A with segment d greyed out
Answers: 4'h5 = 7'b0100100 (ON:a,c,d,f,g → logical 1011011 → active-low 0100100). 4'hA = 7'b0001000 (ON:a,b,c,e,f,g → logical 1110111 → active-low 0001000).
Common bug: forgetting default:. If any bit of i_hex is X (unknown), no 4'h04'hF literal matches, o_seg is left unassigned in always @(*), and the synthesizer infers a latch. Always include default: o_seg = 7'b1111111; (all off).
7-segment segment map a-g

{a,b,c,d,e,f,g} — MSB → LSB

The Full Decoder

module hex_to_7seg (
    input  wire [3:0] i_hex,
    output reg  [6:0] o_seg     // {a,b,c,d,e,f,g} active-low
);
always @(*) begin
    case (i_hex)             //     abcdefg
        4'h0: o_seg = 7'b0000001;  // 0
        4'h1: o_seg = 7'b1001111;  // 1
        4'h2: o_seg = 7'b0010010;  // 2
        4'h3: o_seg = 7'b0000110;  // 3
        4'h4: o_seg = 7'b1001100;  // 4
        4'h5: o_seg = 7'b0100100;  // 5
        4'h6: o_seg = 7'b0100000;  // 6
        4'h7: o_seg = 7'b0001111;  // 7
        4'h8: o_seg = 7'b0000000;  // 8 (all ON)
        4'h9: o_seg = 7'b0000100;  // 9
        4'hA: o_seg = 7'b0001000;  // A
        4'hB: o_seg = 7'b1100000;  // b (lower)
        4'hC: o_seg = 7'b0110001;  // C
        4'hD: o_seg = 7'b1000010;  // d (lower)
        4'hE: o_seg = 7'b0110000;  // E
        4'hF: o_seg = 7'b0111000;  // F
        default: o_seg = 7'b1111111;  // all OFF (safety)
    endcase
end
endmodule

🧪 You Do — Before the Demo

Predict (don't peek at the table):

  1. Does this module use any flip-flops?
  2. Approximately how many LUTs does Yosys build?
  3. If you accidentally write o_seg = 7'b1111111 for 4'h0, what will you see?
Answers: (1) No flops — pure combinational (no clock). (2) ~7 LUTs — one per output bit, each implementing a 4-input boolean function. (3) Press the “0” switch combo, see nothing light up — display stays blank. Easy visual diagnostic for active-low confusion.
7-segment segment map a-g

{a,b,c,d,e,f,g} — MSB → LSB

▶ LIVE DEMO

Flash the Decoder to the Go Board

~5 minutes — real hardware

▸ COMMANDS

cd lecture_examples/week1_day02/d02_s4_ex3/
make sim      # iverilog testbench
make wave     # GTKWave — confirm all 16 digits
make prog     # yosys → nextpnr → iceprog
# Board flashes: you're on hardware now.

▸ EXPECTED RESULT

PASS: all 16 digits verified
=== 16 passed, 0 failed ===

[make prog]
Yosys 0.36 → synth_ice40
nextpnr-ice40 → hx1k vq100
icepack → iceprog
FPGA programmed OK.

▸ ON THE BOARD

Set switches SW1-SW4 to encode a hex digit (binary, LSB=SW1). Watch the leftmost 7-segment display change. Try: 0000 shows “0,” 1111 shows “F,” 1010 shows “A.”

🔧 What Did the Tool Build?

$ yosys -p "read_verilog hex_to_7seg.v; \
    synth_ice40 -top hex_to_7seg; stat" -q

=== hex_to_7seg ===

   Number of wires:                  8
   Number of cells:                  7
     SB_DFF                          0
     SB_LUT4                         7

7 LUT4s — one per output bit:

  • Each output is a 4-input boolean function (the 16-entry case)
  • LUT4 has exactly 4 inputs — perfect match
  • No flops: combinational
  • Total area: 0.5% of the iCE40 HX1K

This is the optimal implementation. The LUT4 was practically designed for this problem.

Historical footnote: 4-input LUTs became standard because real-world combinational logic (like this decoder) rarely needs more than 4 input variables per output bit. Your decoder just proved the design heuristic.

🤖 Check the Machine

Ask AI: “Generate a hex-to-7-segment decoder in Verilog for the Nandland Go Board.”

TASK

Ask AI for a complete decoder module.

BEFORE

Predict: AI will produce a case statement. Key question: will it be active-low?

AFTER

Many AIs produce the active-high version silently — ignoring the “Go Board” hint. Test: flash it.

TAKEAWAY

AI-generated HDL needs hardware-specific validation. Always flash-test.

Teaching moment: AI is confident and often right on textbook problems. But “active-low for Go Board” is the kind of detail it may miss. This is the board-specific knowledge you bring that AI doesn't.

Key Takeaways

 7-segment: 4-bit hex in, 7-bit segment pattern out.

 Go Board is active-low: 0 = ON, 1 = OFF.

 Each output bit is a 4-input function → maps to one SB_LUT4.

 Always include default. Always flash-test, don't just sim.

Simulation is confidence. Hardware is truth. You're on hardware now.

Pre-Class Self-Check

Pause and try each before revealing.

Q1: Does reg always synthesize to a register?

No. reg in always @(*) = combinational. Only reg in always @(posedge clk) becomes a flip-flop.

Q2: What is 4'hF in binary, and why can't you just write 15?

4'b1111. Writing bare 15 makes it a 32-bit value, causing silent width mismatches on assignment.

Pre-Class Self-Check (cont.)

Q3: On the Go Board, how do you turn segment a ON?

Drive the corresponding pin to 0. Active-low: 0 = ON, 1 = OFF.

Q4: If your decoder's case is missing default, what happens?

For a 4-bit input all 16 codes are covered, so no latch inferred in practice. But the habit of including default protects you in larger designs — always include it.

🔗 End of Topic 2

Tomorrow: Procedural Combinational Logic

Topic 3 · always @(*), if/else, case, and the latch trap

▸ WHY THIS MATTERS NEXT

You just used always @(*) and case without really understanding them. Topic 3 is the formal treatment — including the latch problem, the #1 silent bug in combinational RTL. By tomorrow's end you'll recognize latch-inducing patterns on sight, and know three independent ways to prevent them.