verilog-descodificador-7-segmentos

7-Segment Decoder in Verilog

  • 4 min

A 7-segment display is a set of bar-shaped LEDs that allows representing digits through combinations of on and off states.

In this article, we will apply what we learned about the case statement to build a Hexadecimal Decoder.

The goal is simple: we have a 4-bit number inside the FPGA (from 0 to 15) and we want it to be visually displayed on the display.

The hardware: seven LEDs

A 7-segment display is straightforward; it is literally 7 packaged LEDs with a specific shape (plus an eighth for the decimal point).

Each segment has a standard letter assigned, from a to g, starting at the top and going clockwise, ending with the middle bar (g).

To draw a number, we simply need to turn on the correct combination of LEDs.

  • For “1”: Turn on b and c.
  • For “8”: Turn on all (a, b, c, d, e, f, g).

Common Anode and Common Cathode

Here comes the first common stumbling block. Depending on how the LEDs are internally connected, there are two types of displays:

  1. Common Cathode: All negatives are tied to Ground (GND). To light a segment, we put a 1 (Positive Voltage) on its pin. (Positive Logic).
  2. Common Anode: All positives are tied to VCC (3.3V). To light a segment, we put a 0 (Ground) on its pin to allow current to flow. (Negative Logic).

The vast majority of FPGA development boards (like the IceStick, Go Board, or Alhambra) use Common Anode displays or inverting drivers. This means 0 turns on and 1 turns off.

Decoder Design

We will design a module that takes a 4-bit number (data) and returns a 7-bit bus (segments) where each bit corresponds to a letter: gfe_dcba.

In classic university courses, they would have you draw a giant truth table and solve 7 Karnaugh Maps to find the Boolean equation for each LED.

In Verilog, thanks to abstraction, we simply describe the truth table using a case. The synthesizer will take care of doing the Karnaugh Maps for us.

Verilog Code for Common Cathode

First, we’ll do it thinking “1 turns on”, which is more natural for the brain.

module decoder_7seg (
    input wire [3:0] number,  // Value from 0 to 15 (Hexadecimal)
    output reg [6:0] seg      // Output gfe_dcba
);

    // Define which segments to light for each number
    // Format: gfe_dcba
    // 1 means ON
    
    always @(*) begin
        case(number)
            //                  gfedcba
            4'h0: seg = 7'b0111111; // 0: Everything except g
            4'h1: seg = 7'b0000110; // 1: Only b and c
            4'h2: seg = 7'b1011011; // 2
            4'h3: seg = 7'b1001111; // 3
            4'h4: seg = 7'b1100110; // 4
            4'h5: seg = 7'b1101101; // 5
            4'h6: seg = 7'b1111101; // 6
            4'h7: seg = 7'b0000111; // 7
            4'h8: seg = 7'b1111111; // 8: All
            4'h9: seg = 7'b1101111; // 9
            
            // Hexadecimal (A-F)
            4'hA: seg = 7'b1110111; // A
            4'hB: seg = 7'b1111100; // b (lowercase)
            4'hC: seg = 7'b0111001; // C
            4'hD: seg = 7'b1011110; // d (lowercase)
            4'hE: seg = 7'b1111001; // E
            4'hF: seg = 7'b1110001; // F
            
            default: seg = 7'b0000000; // Off for safety
        endcase
    end

endmodule
Copied!

Adaptation to Common Anode

If you load the previous code onto a typical board, you will see the numbers “in negative” (the LEDs that should be off will turn on and vice versa).

To fix it, you don’t need to rewrite the entire case with zeros. We simply invert the output.

We can create a wrapper module or modify the output:

module top_display (
    input wire [3:0] switches,
    output wire [6:0] display_leds // Connected to physical pins
);

    wire [6:0] positive_logic;

    // 1. Instantiate our pure decoder
    decoder_7seg my_deco (
        .number(switches),
        .seg(positive_logic)
    );

    // 2. Invert the bits before going out to the physical world
    // If positive_logic is 1 (On), we send 0 to the pin (Ground) to turn on.
    assign display_leds = ~positive_logic;

endmodule
Copied!

Configuration via Parameters

If we want to make portable code that works on any board, we can add a configuration parameter.

module display_driver #(
    parameter COMMON_ANODE = 1 // 1 to invert, 0 for normal
) (
    input wire [3:0] num,
    output wire [6:0] pin_output
);
    
    reg [6:0] internal_segments;
    
    always @(*) begin
        case(num)
            // ... (case table) ...
        endcase
    end

    // Conditional XOR operation:
    // If COMMON_ANODE is 1, invert (segments ^ 111... = ~segments)
    // If COMMON_ANODE is 0, leave as is (segments ^ 000... = segments)
    assign pin_output = internal_segments ^ {7{COMMON_ANODE}};

endmodule
Copied!