7-Segment Display Verilog Code (BCD & Hex Decoder)
A seven-segment display is the little digit you see on a calculator, a microwave clock, or an FPGA dev board. Each digit is built from seven LEDs arranged in a figure-8, labelled a through g. To show a number you light the right combination of those seven segments. A decoder is the logic that converts a 4-bit value into that seven-wire pattern. On this page you get complete, runnable 7 segment display Verilog code: a BCD (0-9) decoder, a full hex (0-F) decoder, and a parameterised module that drives both common-anode and common-cathode displays. Every example ships with a testbench that prints the segment pattern so you can check the output without any hardware.
All three files are Verilog-2005 and build with the open-source Icarus Verilog toolchain:
iverilog -g2005 -o sim seven_seg_bcd.v && vvp sim
Segment mapping: what each bit means
Before any code, fix a convention and stick to it. In every example below, the output bus is seg[6:0] = {a, b, c, d, e, f, g}, where the most significant bit is segment a (top bar) and the least significant bit is segment g (middle bar). The patterns here are written for a common-cathode display, so a bit value of 1 means “that segment is lit”. Here are the digits 0 through 9:
| Digit | Lit segments | seg[6:0] (abcdefg) |
|---|---|---|
| 0 | a b c d e f | 1111110 |
| 1 | b c | 0110000 |
| 2 | a b d e g | 1101101 |
| 3 | a b c d g | 1111001 |
| 4 | b c f g | 0110011 |
| 5 | a c d f g | 1011011 |
| 6 | a c d e f g | 1011111 |
| 7 | a b c | 1110000 |
| 8 | all seven | 1111111 |
| 9 | a b c d f g | 1111011 |
Example 1: BCD (0-9) decoder with testbench
This is the classic starting point. The input bcd is a 4-bit value expected to hold 0 through 9. A combinational always @(*) block with a case statement selects the segment pattern. Anything outside 0-9 falls through to the default, which blanks the display instead of leaving the output unknown. The testbench sweeps 0 through 11 so you can confirm both the valid digits and the blanking behaviour.
// seven_seg_bcd.v -- BCD (0-9) to 7-segment decoder, common cathode
// Segment order: seg = {a,b,c,d,e,f,g}, a segment lights when its bit is 1.
module seven_seg_bcd (
input [3:0] bcd, // binary-coded decimal 0..9
output reg [6:0] seg // {a,b,c,d,e,f,g}, active-high
);
always @(*) begin
case (bcd)
4'd0: seg = 7'b1111110;
4'd1: seg = 7'b0110000;
4'd2: seg = 7'b1101101;
4'd3: seg = 7'b1111001;
4'd4: seg = 7'b0110011;
4'd5: seg = 7'b1011011;
4'd6: seg = 7'b1011111;
4'd7: seg = 7'b1110000;
4'd8: seg = 7'b1111111;
4'd9: seg = 7'b1111011;
default: seg = 7'b0000000; // blank for invalid BCD
endcase
end
endmodule
module tb_seven_seg_bcd;
reg [3:0] bcd;
wire [6:0] seg;
integer i;
seven_seg_bcd dut (.bcd(bcd), .seg(seg));
initial begin
$display("digit | abcdefg");
$display("------+--------");
for (i = 0; i <= 11; i = i + 1) begin
bcd = i[3:0];
#5;
$display(" %2d | %b", bcd, seg);
end
$finish;
end
endmoduledigit | abcdefg ------+-------- 0 | 1111110 1 | 0110000 2 | 1101101 3 | 1111001 4 | 0110011 5 | 1011011 6 | 1011111 7 | 1110000 8 | 1111111 9 | 1111011 10 | 0000000 11 | 0000000 /var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpm60qp07v/seven_seg_bcd.v:39: $finish called at 60 (1s)
How the decoder works
The whole decoder is pure combinational logic, so three details matter. First, the sensitivity list is always @(*), which tells the synthesiser to react to any input change. Second, seg is declared output reg: in Verilog, a signal assigned inside an always block must be a reg, even though this produces gates, not a flip-flop. There is no clock here, so nothing is actually stored. Third, the default branch is not optional busywork. Without it, an input the case does not list would make seg hold its previous value, and the synthesiser would infer an unwanted latch. Covering every case keeps the block purely combinational.
In the testbench, #5 waits five time units after driving a new input so the combinational logic settles before $display prints. The %b format prints the seven-bit pattern, and you can read it straight against the mapping table above.
Example 2: Full hexadecimal (0-F) decoder
Hardware designers usually want all sixteen values 0-F, because a 4-bit nibble naturally spans the full hex range. The structure is identical to the BCD version; we just add rows for A, b, C, d, E, and F. Note the lowercase b and d patterns: on a seven-segment display an uppercase B is indistinguishable from 8, and uppercase D is indistinguishable from 0, so the convention is to render those two glyphs in lowercase.
// seven_seg_hex.v -- Hexadecimal (0-F) to 7-segment decoder, common cathode
// Segment order: seg = {a,b,c,d,e,f,g}, a segment lights when its bit is 1.
module seven_seg_hex (
input [3:0] hex, // 0x0 .. 0xF
output reg [6:0] seg // {a,b,c,d,e,f,g}, active-high
);
always @(*) begin
case (hex)
4'h0: seg = 7'b1111110;
4'h1: seg = 7'b0110000;
4'h2: seg = 7'b1101101;
4'h3: seg = 7'b1111001;
4'h4: seg = 7'b0110011;
4'h5: seg = 7'b1011011;
4'h6: seg = 7'b1011111;
4'h7: seg = 7'b1110000;
4'h8: seg = 7'b1111111;
4'h9: seg = 7'b1111011;
4'hA: seg = 7'b1110111;
4'hB: seg = 7'b0011111;
4'hC: seg = 7'b1001110;
4'hD: seg = 7'b0111101;
4'hE: seg = 7'b1001111;
4'hF: seg = 7'b1000111;
default: seg = 7'b0000000;
endcase
end
endmodule
module tb_seven_seg_hex;
reg [3:0] hex;
wire [6:0] seg;
integer i;
seven_seg_hex dut (.hex(hex), .seg(seg));
initial begin
$display("hex | abcdefg");
$display("----+--------");
for (i = 0; i < 16; i = i + 1) begin
hex = i[3:0];
#5;
$display(" %h | %b", hex, seg);
end
$finish;
end
endmodulehex | abcdefg ----+-------- 0 | 1111110 1 | 0110000 2 | 1101101 3 | 1111001 4 | 0110011 5 | 1011011 6 | 1011111 7 | 1110000 8 | 1111111 9 | 1111011 a | 1110111 b | 0011111 c | 1001110 d | 0111101 e | 1001111 f | 1000111 /var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpkcbm6th_/seven_seg_hex.v:45: $finish called at 80 (1s)
Example 3: Common-anode vs common-cathode in one module
Physical displays come in two wirings. In a common-cathode display all seven LED cathodes join to ground, so you drive a segment HIGH to light it, exactly what the patterns above assume. In a common-anode display all anodes join to the supply, so the logic must pull a segment LOW to light it. The pattern is simply inverted.
Rather than maintaining two copies of the table, parameterise it. The module below computes the active-high pattern once, then conditionally inverts it with ~ based on the COMMON_ANODE parameter. The testbench instantiates both variants and prints them side by side so you can see that the anode column is the bitwise complement of the cathode column.
// seven_seg_anode.v -- One decoder, both display types via a parameter.
// COMMON_ANODE = 0 -> common-cathode (segment ON = 1)
// COMMON_ANODE = 1 -> common-anode (segment ON = 0, pattern inverted)
module seven_seg #(
parameter COMMON_ANODE = 0
) (
input [3:0] hex,
output reg [6:0] seg // {a,b,c,d,e,f,g}
);
reg [6:0] pat; // common-cathode (active-high) base pattern
always @(*) begin
case (hex)
4'h0: pat = 7'b1111110;
4'h1: pat = 7'b0110000;
4'h2: pat = 7'b1101101;
4'h3: pat = 7'b1111001;
4'h4: pat = 7'b0110011;
4'h5: pat = 7'b1011011;
4'h6: pat = 7'b1011111;
4'h7: pat = 7'b1110000;
4'h8: pat = 7'b1111111;
4'h9: pat = 7'b1111011;
4'hA: pat = 7'b1110111;
4'hB: pat = 7'b0011111;
4'hC: pat = 7'b1001110;
4'hD: pat = 7'b0111101;
4'hE: pat = 7'b1001111;
4'hF: pat = 7'b1000111;
default: pat = 7'b0000000;
endcase
// Common-anode hardware sinks current, so it needs the inverse drive.
seg = (COMMON_ANODE != 0) ? ~pat : pat;
end
endmodule
module tb_seven_seg;
reg [3:0] hex;
wire [6:0] seg_cc; // common cathode
wire [6:0] seg_ca; // common anode
integer i;
seven_seg #(.COMMON_ANODE(0)) u_cc (.hex(hex), .seg(seg_cc));
seven_seg #(.COMMON_ANODE(1)) u_ca (.hex(hex), .seg(seg_ca));
initial begin
$display("hex | cathode | anode");
$display("----+---------+--------");
for (i = 0; i < 16; i = i + 1) begin
hex = i[3:0];
#5;
$display(" %h | %b | %b", hex, seg_cc, seg_ca);
end
$finish;
end
endmodulehex | cathode | anode ----+---------+-------- 0 | 1111110 | 0000001 1 | 0110000 | 1001111 2 | 1101101 | 0010010 3 | 1111001 | 0000110 4 | 0110011 | 1001100 5 | 1011011 | 0100100 6 | 1011111 | 0100000 7 | 1110000 | 0001111 8 | 1111111 | 0000000 9 | 1111011 | 0000100 a | 1110111 | 0001000 b | 0011111 | 1100000 c | 1001110 | 0110001 d | 0111101 | 1000010 e | 1001111 | 0110000 f | 1000111 | 0111000 /var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpwladi397/seven_seg_anode.v:53: $finish called at 80 (1s)
Common mistakes
- Forgetting the
defaultbranch. An incompletecaseon a combinational block infers a latch. Always add adefault, even if it just blanks the display. - Mixing up segment order. There is no universal standard for which bit is
a. If your board lights the wrong bars, your bus order probably does not match the dev board’s pin assignment. Decide on{a,...,g}and document it, as these examples do. - Driving a common-anode display with active-high patterns. You will see every segment that should be off, and nothing that should be on. Invert the pattern or use the parameter from Example 3.
- Declaring
segaswire. A signal assigned insidealwaysmust bereg. Compilers reject thewireversion. Theregkeyword here does not create storage; it is just an assignment-target rule. - Using
always @(bcd)instead ofalways @(*). An explicit sensitivity list that misses a signal causes simulation to diverge from synthesised hardware. Prefer@(*)for combinational logic.
Quick check
Why is the output seg declared as reg when the decoder has no clock?
In Verilog, any signal assigned inside an always block must be declared reg. Because this block is combinational (always @(*) with no clock edge), the reg keyword is only a syntax requirement for assignment targets. It synthesises to gates, not to a flip-flop, so no value is actually stored.
What happens if you remove the default line from the case statement?
Any input value not explicitly listed would leave seg unchanged, so the tool infers a latch to hold the previous value. That turns your intended combinational decoder into sequential logic with a hidden memory element. The default branch guarantees every input assigns an output.
How do you convert a common-cathode pattern to a common-anode one?
Invert it bitwise with the ~ operator. A common-cathode display lights a segment on logic 1, while a common-anode display lights it on logic 0, so the anode pattern is the exact complement of the cathode pattern. Example 3 does this with a parameter so one module serves both.
Why are hex digits B and D usually shown in lowercase on a seven-segment display?
An uppercase B uses the same segments as 8, and an uppercase D uses the same segments as 0, so they would be unreadable. Lowercase b (segments c d e f g) and d (segments b c d e g) produce distinct glyphs, which is why the hex table uses patterns 0011111 and 0111101 for them.
Share your question in comments or talk to our mentor team for batch guidance.
Open this design in the free in-browser simulator — edit, compile and see the waveform/log instantly.
Learn Verilog with mentors, labs and placement support
This free series is maintained by the ChipXpert training team in Hyderabad & Bengaluru. Go deeper with instructor-led courses on real EDA tools.
Explore VLSI Courses