6 Digit 7 Segment Display Driver

This short bit of Verilog code displays a 6 digit wide hex number on a group of 7-segment displays. I think the test code is actually longer than the code it’s testing. 🙂

Background

This is a good beginner project and a useful piece of code to have when your dev board has six 7 segment displays, such as the Terasic DE10-Lite board. Adding this module to other (larger) designs can be a useful aid for debugging. Outside of the I/O mapping, nothing is really specific to this particular board, and the code can be readily adapted to other hardware.

If you’re new to Verilog, this project may be worth taking a quick look at. It has always blocks for both sequential and combinational code, case statements, module instantiation “by name”, a function, and a synthesizable test module. I didn’t bother with a test bench to run in simulation too since this is really straightforward (and more intuitive) to test in hardware.

Verilog Resources

For more info on Verilog, lots of helpful online resources exist including:

Quartus Setup

Since I used the DE10-Lite board which an Altera MAX10 part, I wrote this with the Intel/Altera toolchain – Quartus Prime 20.1, Light Edition. The Light Edition is nice since it’s free and supports quite a few parts in their product line.

My top-level entity is a graphical schematic page which Quartus calls a “Block Diagram/Schematic File” (a .bdf). Whether you use a VHDL/Verilog file for your top-level entity or a schematic file is personal preference and driven by the complexity of your design and what sort of readability you prefer. Source code like VHDL or Verilog is often easier to integrate with version control systems than a schematic file since it can be more straightforward to understand the change history. Either way, personal preference.

On the schematic, you can clearly see the I/O pin assignments (in the boxes) and that the only module at this level is the test_seven_segments.v Verilog file. That file instantiates the module in drive_6digit_7segs.v which has the actual Verilog code that performs the conversion from a 24 bit value (6 hex digits) to the 7 segment display pins.

I/O Interface

I/O to the test module is straightforward:

  • 5 inputs: a clock, a switch to enable counting, and 4 slide switches for counting frequency.
  • 42 outputs: for the 42 LEDs that comprise the segments of the six 7 segment displays.

Note:

For the purists out there, note that the 42 signals going to the 7 segment display segments are not guaranteed to be glitch free. They are purely combinatorial with no registers or clocked logic. Since the purpose is showing data on the LEDs for human consumption, I found this reasonable. See comments in the header of drive_6digit_7segs.v for potential improvements.

The full Quartus project is up on my GitHub site and the two relevant Verilog source files are also included below.

test_seven_segments.v – synthesizable test code wrapper

//  ------------------------------------------------------------------------------
//
//  test_seven_segments.v -- test module for drive_6_7segs.v
//
//  Copyright (C) 2020 Michael Gansler
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
//
//  ------------------------------------------------------------------------------
//
//  Module:       test_seven_segments.v
//
//  Objective:    test module for drive_6_7segs.v
//
//  Assumptions:  50 MHz input clock
//                DE10-Lite board
//
//  Notes:
//
//    This is a lot like a test bench, but this is intended for testing on 
//    actual h/w, so it is synthesizable, and easily interfaces with switches 
//    and the six 7 segment displays on a Terasic DE10-Lite board.
// 
//    Counter value is displayed on the six 7 segment displays in hex.
//
//    Four slide switches set rate at which the counter is incremented.  
//    The 16 possible speeds can be set from 1 Hz to 100 KHz, logarithmically spaced.
//    This allows for a quick test over the full range of values, testing all 6
//    digits quickly, and it was amusing, so why not.
//
//    This could have been done as a testbench and run in simulation, but it was 
//    easier to verify the shapes of all the letters/numbers on the actual 
//    hardware displays.
//
//
    
    
`default_nettype none                        // Require all nets to be declared before used.
                                             // --> guarantees that typo'd net names get trapped

module test_seven_segments
(
   input             clk,                    // high speed clock from PLL, 50 MHz
   
   input             count_en,               // allow/inhibit counting
   input    [3:0]    switches,               // 4 slide switches to set display value increment rate
   
   output   [6:0]    digit5,                 // active low bits for 7 segments of digit 5 (left-most digit) 
   output   [6:0]    digit4,                 // active low bits for 7 segments of digit 4
   output   [6:0]    digit3,                 // active low bits for 7 segments of digit 3 
   output   [6:0]    digit2,                 // active low bits for 7 segments of digit 2  
   output   [6:0]    digit1,                 // active low bits for 7 segments of digit 1 
   output   [6:0]    digit0                  // active low bits for 7 segments of digit 0 (right-most digit) 
);


reg        [27:0]    ctr      = 1;           // high speed counter, counts at clk freq

reg        [27:0]    thresh   = 50_000_000;  // thresh for reseting high speed counter, in clk cycles
  
reg        [23:0]    test_val = 24'hab_cdef; // value that is incremented and sent to the six digit 7 segment display


drive_6dig_7segs drive_6dig_7segs_inst1      // instantiate DUT (Device Under Test)
(
   .clk              ( clk ),
   .hex_mode         ( 1'b1 ),
   .disp_value       ( test_val ),
   
   .digit5           ( digit5 ),
   .digit4           ( digit4 ),
   .digit3           ( digit3 ),
   .digit2           ( digit2 ),
   .digit1           ( digit1 ),
   .digit0           ( digit0 )
);


//
// Increment test_val sent to 7 segment displays at user-selectable frequency.
//
// Threshold sets number of clk cycles between increments of test_val.
//
always @(posedge clk) begin

   if (count_en) begin
   
      if (ctr<thresh) begin
      
         ctr <= ctr + 28'd1;
         
      end else begin
      
         ctr <= 28'd0;
         
         test_val <= test_val + 24'd1;   // increment value sent to display.
      
      end
      
   end
      
end


//
// Select counting frequency via 4 slide switches.
//
// Assumption - 50 MHz input clk
//
// Logarithmically spaced threshold values allows counting at
// frequencies between 1 Hz and 100 KHz.  
//
// thresh is in clk cycles, e.g. 
//
//   50,000,000 --> 1 Hz
//   10,000,000 --> 5 Hz
//           .
//           .
//           .
//        1,000 -->  50 KHz
//          500 --> 100 KHz
//
// 'default' case not necessary since list is exhaustive,
// but good practice to include to ensure avoiding unintentional
// inferred latch.  Note that this 'always' block is 
// _combinational_ logic.
//
always @(*) begin

   case (switches)
      4'h0   : thresh = 50_000_000;
      4'h1   : thresh = 25_000_000;
      4'h2   : thresh = 10_000_000;
      4'h3   : thresh =  5_000_000;
      4'h4   : thresh =  2_500_000;
      4'h5   : thresh =  1_000_000;
      4'h6   : thresh =    500_000;
      4'h7   : thresh =    250_000;
      4'h8   : thresh =    100_000;
      4'h9   : thresh =     50_000;
      4'hA   : thresh =     25_000;
      4'hB   : thresh =     10_000;
      4'hC   : thresh =      5_000;
      4'hD   : thresh =      2_500;
      4'hE   : thresh =      1_000;
      4'hF   : thresh =        500;
      default: thresh = 50_000_000;
   endcase
   
end


endmodule

drive_6digit_7seg.v – converts 24 bit hex value to 7 segment display outputs

//  ------------------------------------------------------------------------------
//
//  drive_6dig_7seg.v -- drives 6 digit 7 segment displays on DE10-Lite board
//
//  Copyright (C) 2020 Michael Gansler
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
//
//  ------------------------------------------------------------------------------
//
//  Module:       drive_6dig_7seg.v
//
//  Objective:    drives 6 digit 7 segment displays on DE10-Lite board
//
//  Assumptions:  DE10-Lite board
//
//  Notes:
//
//  Displays a 24 bit value in hex on the six seven segment displays
//  on a DE10-Lite board.  Nothing really too board-specific about this,
//  other than the segments on this board are active low.  Easy to 
//  adapt to any other board.
//
//  Nomenclature for the segments on this board is:
//
//          0
//        -----
//       |     |
//      5|     | 1
//       |  6  |
//        -----
//       |     |
//      4|     | 2
//       |     |
//        ----- 
//          3
//
//  The above is used to define the ordering of the bits returned
//  by the function 'segments' below.
//
//  A bit surprisingly, the DE10-Lite's six 7 segment displays are not
//  multiplexed.  Each segment on each LED has its own dedicated FPGA
//  pin.  I guess the board designers thought that they have so many pins 
//  on the FPGA, that it wasn't worth bothering to save pins.  (The 
//  Altera Max 10 on this board does have over 300 pins.)
//
//  As a result, no sequential muxing logic is necessary to strobe 
//  the displays.  This module is therefore really straightforward and
//  purely combinatorial, so it actually doesn't even need a clock.
//
//  No input data_valid trigger, so display is constantly updated
//  as disp_value is changed.  Certainly not glitch-free outputs
//  to the LEDs, but only envisioned for visual display, so saw
//  no real need to overcomplicate.  Potential improvements would be
//  to add a data_valid input to trigger an update, and output latches 
//  for the display segment bits.
//
//  Note use of a function for converting a hex nibble to the segment
//  bits.  Seemed like a good simple example of how to use a function
//  in Verilog.  Other implementations are of course possible.
//

`default_nettype none                // Require all nets to be declared before used.
                                     // --> guarantees that typo'd net names get trapped


module drive_6dig_7segs
(
   input              clk,           // actually unused high speed input clock (for future features)
   input              hex_mode,      // flag for future use once BCD implemented
   input    [23:0]    disp_value,    // value to display on digits
   
   output    [6:0]    digit5,        // segments to light on left-most digit
   output    [6:0]    digit4,        //   ...
   output    [6:0]    digit3,        //   ...
   output    [6:0]    digit2,        //   ...
   output    [6:0]    digit1,        //   ...
   output    [6:0]    digit0         // segments to light on right-most digit
);


//
// Extract each 4 bit nibble and convert to 7 bits of segment
// light/no-light bits.
//
assign digit5 = segments( disp_value[23:20] );
assign digit4 = segments( disp_value[19:16] );
assign digit3 = segments( disp_value[15:12] );
assign digit2 = segments( disp_value[11: 8] );
assign digit1 = segments( disp_value[ 7: 4] );
assign digit0 = segments( disp_value[ 3: 0] );


//
// Convert a hex nibble, i.e. 0 through F, to a 7 bit variable
// representing which segments on the 7 segment display should 
// be lit.
//
function automatic [6:0] segments ( input [3:0] i_nibble );

   begin
      
      //
      // Since DE10-Lite board 7 segment displays LEDs
      // are wired active low, the bit patterns below 
      // are negated.  
      //
      // Each 1 in the raw literal value represents 
      // a lit segment.  
      //
      // 'default' case not necessary since list is exhaustive,
      // but good practice to include to ensure avoiding unintentional
      // inferred latch.  Note that this is _combinational_ logic.
      //
      
      case (i_nibble)         // 654 3210 <----- Bit positions based on
         4'h0   : segments = ~7'b011_1111;   //  numbering in comments at
         4'h1   : segments = ~7'b000_0110;   //  top of this module.
         4'h2   : segments = ~7'b101_1011;
         4'h3   : segments = ~7'b100_1111;
         4'h4   : segments = ~7'b110_0110;
         4'h5   : segments = ~7'b110_1101;
         4'h6   : segments = ~7'b111_1101;
         4'h7   : segments = ~7'b000_0111;
         4'h8   : segments = ~7'b111_1111;
         4'h9   : segments = ~7'b110_1111;
         4'hA   : segments = ~7'b111_0111;
         4'hB   : segments = ~7'b111_1100;
         4'hC   : segments = ~7'b011_1001;
         4'hD   : segments = ~7'b101_1110;
         4'hE   : segments = ~7'b111_1001;
         4'hF   : segments = ~7'b111_0001;
         default: segments = ~7'b100_0000;
      endcase
      
   end

endfunction


endmodule