FPGA Digital Stopwatch Implementation with Verilog HDL

Hardware Platform: DE2-115 Development Board

Software Environment: Quartus II 15.1

This implemantation presents a modular approach to creating a digital stopwatch using FPGA. The design consists of three main modules: a timing controller module (timer.v), a seven-segment display driver (segment_driver.v), and a top-level integration module (stopwatch.v). The system includes reset functionality via push button and pause capability through a DIP switch.

Top-Level Module Integration:

module stopwatch_fpga(
    system_clock,
    reset_signal,
    hold_state,
    
    segment_0,
    segment_1,
    segment_2,
    segment_3,
    segment_4,
    segment_5
);

input system_clock;
input reset_signal;
input hold_state;

output reg[6:0] segment_0;
output reg[6:0] segment_1;
output reg[6:0] segment_2;
output reg[6:0] segment_3;
output reg[6:0] segment_4;
output reg[6:0] segment_5;

wire[3:0] centisecond_high;
wire[3:0] centisecond_low;

wire[3:0] second_tens;
wire[3:0] second_units;

wire[3:0] minute_tens;
wire[3:0] minute_units;

timer_module timing_unit(
    .clock_input(system_clock),
    .reset_input(reset_signal),
    .pause_input(hold_state),
    
    .cs_high_out(centisecond_high),
    .cs_low_out(centisecond_low),
    
    .sec_tens_out(second_tens),
    .sec_units_out(second_units),
    
    .min_tens_out(minute_tens),
    .min_units_out(minute_units)
);

display_module display_unit(
    .cs_high_in(centisecond_high),
    .cs_low_in(centisecond_low),
    
    .sec_tens_in(second_tens),
    .sec_units_in(second_units),
    
    .min_tens_in(minute_tens),
    .min_units_in(minute_units),
    
    .hex_0_out(segment_0),
    .hex_1_out(segment_1),
    .hex_2_out(segment_2),
    .hex_3_out(segment_3),
    .hex_4_out(segment_4),
    .hex_5_out(segment_5)
);

endmodule

Timing Controller Module:

module timer_module(
    clock_input,
    reset_input,
    pause_input,
    
    cs_high_out,
    cs_low_out,
    
    sec_tens_out,
    sec_units_out,
    
    min_tens_out,
    min_units_out
);

input clock_input;
input reset_input;
input pause_input;

output reg[3:0] cs_high_out;
output reg[3:0] cs_low_out;

output reg[3:0] sec_tens_out;
output reg[3:0] sec_units_out;

output reg[3:0] min_tens_out;
output reg[3:0] min_units_out;

reg carry_to_seconds;
reg carry_to_minutes;
reg[26:0] frequency_counter;
reg clock_divided;

/* Clock division to 100Hz */
always@(posedge clock_input or negedge reset_input)
    if(!reset_input)
        frequency_counter <= 27'd0;
    else if(frequency_counter == 249999)
        frequency_counter <= 27'd0;
    else
        frequency_counter <= frequency_counter + 1'b1;
        
always@(posedge clock_input or negedge reset_input)
    if(!reset_input)
        clock_divided <= 1'b0;
    else if(frequency_counter == 249999)
        clock_divided <= !clock_divided;
    else
        clock_divided <= clock_divided;

/* Centisecond counting process */
always@(posedge clock_divided or negedge reset_input)
    begin
        if(!reset_input) begin  
            {cs_high_out, cs_low_out} <= 8'h00;
            carry_to_seconds <= 1'b0;
        end
        else if(!pause_input) begin      
            if(cs_low_out == 9) begin
                cs_low_out <= 4'd0;
                if(cs_high_out == 9) begin
                    cs_high_out <= 4'd0;
                    carry_to_seconds <= 1'b1;
                end
                else
                    cs_high_out <= cs_high_out + 1'b1;
            end
            else begin
                cs_low_out <= cs_low_out + 1'b1;
                carry_to_seconds <= 1'b0;
            end
        end
    end
    
/* Second counting process */
always@(posedge carry_to_seconds or negedge reset_input)
    begin
        if(!reset_input) begin
            {sec_tens_out, sec_units_out} <= 8'h00;
            carry_to_minutes <= 1'b0;
        end
        else if(sec_units_out == 9) begin
            sec_units_out <= 4'd0;
            if(sec_tens_out == 5) begin
                sec_tens_out <= 4'd0;
                carry_to_minutes <= 1'b1;
            end
            else
                sec_tens_out <= sec_tens_out + 1'b1;
        end
        else begin
            sec_units_out <= sec_units_out + 1'b1;
            carry_to_minutes <= 1'b0;
        end
    end
    
/* Minute counting process */
always@(posedge carry_to_minutes or negedge reset_input)
    begin
        if(!reset_input) begin
            {min_tens_out, min_units_out} <= 8'h00;
        end
        else if(min_units_out == 9) begin
            min_units_out <= 4'd0;
            if(min_tens_out == 5)
                min_tens_out <= 4'd0;
            else
                min_tens_out <= min_tens_out + 1'b1;
        end
        else
            min_units_out <= min_units_out + 1'b1;
    end
endmodule

Display Driverr Module:

module display_module(
    cs_high_in,
    cs_low_in,
    
    sec_tens_in,
    sec_units_in,
    
    min_tens_in,
    min_units_in,
    
    hex_0_out,
    hex_1_out,
    hex_2_out,
    hex_3_out,
    hex_4_out,
    hex_5_out
);

input[3:0] cs_high_in;
input[3:0] cs_low_in;

input[3:0] sec_tens_in;
input[3:0] sec_units_in;

input[3:0] min_tens_in;
input[3:0] min_units_in;

output reg[6:0] hex_0_out;
output reg[6:0] hex_1_out;
output reg[6:0] hex_2_out;
output reg[6:0] hex_3_out;
output reg[6:0] hex_4_out;
output reg[6:0] hex_5_out;

function [6:0] seven_segment_decode;
    input [3:0] digit;
    begin
        case(digit)
            0: seven_segment_decode = 7'b1000000;
            1: seven_segment_decode = 7'b1111001;
            2: seven_segment_decode = 7'b0100100;
            3: seven_segment_decode = 7'b0110000;
            4: seven_segment_decode = 7'b0011001;
            5: seven_segment_decode = 7'b0010010;
            6: seven_segment_decode = 7'b0000010;
            7: seven_segment_decode = 7'b1111000;
            8: seven_segment_decode = 7'b0000000;
            9: seven_segment_decode = 7'b0010000;
            default: seven_segment_decode = 7'b1000000;
        endcase
    end
endfunction

always@(*) 
    hex_0_out = seven_segment_decode(cs_low_in);

always@(*) 
    hex_1_out = seven_segment_decode(cs_high_in);
    
always@(*) 
    hex_2_out = seven_segment_decode(sec_units_in);

always@(*) 
    hex_3_out = seven_segment_decode(sec_tens_in);

always@(*) 
    hex_4_out = seven_segment_decode(min_units_in);

always@(*) 
    hex_5_out = seven_segment_decode(min_tens_in);
    
endmodule

Simulation Environment: ModelSim SE-64 10.4

For verification purposes, a testbench was created specifically for the timing controller module. Due to the 100Hz clock division requiring extensive simulation time, individual modules were tested separately for efficient debugging and validation.

Testbench for Timing Module:

`timescale 1ns/1ns
`define clock_cycle 20
module timer_module_tb;
    reg clock_signal;
    reg reset_active;
    reg pause_active;
    wire[3:0] cs_high;
    wire[3:0] cs_low;
    wire[3:0] sec_units;
    wire[3:0] sec_tens;
    wire[3:0] min_units;
    wire[3:0] min_tens;
    
timer_module timer_instance(
    .clock_input(clock_signal),
    .reset_input(reset_active),
    .pause_input(pause_active),
    .cs_high_out(cs_high),
    .cs_low_out(cs_low),
    .sec_tens_out(sec_tens),
    .sec_units_out(sec_units),
    .min_tens_out(min_tens),
    .min_units_out(min_units)
);

initial
    clock_signal = 0;
    always#(`clock_cycle/2) clock_signal = ~clock_signal;
    
initial
    begin
        reset_active = 1'b0;
        #(`clock_cycle);
        reset_active = 1'b1;
        pause_active = 1'b1;
        #(`clock_cycle*5);
        pause_active = 1'b0;
        #(`clock_cycle*1000000);
        $stop;
    end
endmodule


Alternative Implementation: Enhanced Precision Version

Hardware Platform: Milianker MA703FA

Software Environment: Vivado 2019.2

This enhanced version offers improevd timing precision with 0.01-second resolution and a measurement range of 0 to 99.99 seconds. The design utilizes three control buttons for reset, start, and stop functions. To optimize pin usage, two 74HC595 shift register chips are employed for driving the seven-segment displays.

Data Generation Module:

module precision_timer(
    input system_clock,
    input system_reset,
    input trigger_start,
    input trigger_stop,
    output reg[15:0] timing_value
);

parameter MILLISECOND_COUNT = 499999;
reg [23:0] millisecond_counter = 24'd0;
reg counting_enable = 1'b0;

always @(posedge system_clock) begin
    if(!system_reset)
        counting_enable <= 1'b0;
    else if(trigger_start == 1'b0)
        counting_enable <= 1'b1;
    else if(trigger_stop == 1'b0)
        counting_enable <= 1'b0;
    else
        counting_enable <= counting_enable;
end

always @(posedge system_clock) begin
    if(!system_reset)
        millisecond_counter <= 24'd0;
    else if(counting_enable == 1'b1) begin
        if(millisecond_counter == MILLISECOND_COUNT)
            millisecond_counter <= 24'd0;
        else
            millisecond_counter <= millisecond_counter + 1'b1;
    end
    else
        millisecond_counter <= millisecond_counter;
end

always @(posedge system_clock) begin
    if(!system_reset)
        timing_value <= 16'd0;
    else if(counting_enable == 1'b1) begin
        if(timing_value == 16'd9999)
            timing_value <= 16'd0;
        else if(millisecond_counter == MILLISECOND_COUNT)
            timing_value <= timing_value + 1'b1;
        else
            timing_value <= timing_value;
    end
    else
        timing_value <= timing_value;
end

endmodule

BCD Conversion Module:

module binary_to_bcd(
    input system_clock,
    input system_reset,
    input [15:0] binary_data,
    output reg[3:0] ones_digit,
    output reg[3:0] tens_digit,
    output reg[3:0] hundreds_digit,
    output reg[3:0] thousands_digit
);

reg [4:0] shift_counter;
reg [31:0] shift_register;
reg conversion_flag;

always @(posedge system_clock or negedge system_reset) begin
    if(!system_reset)
        shift_counter <= 5'd0;
    else if((shift_counter == 5'd17) && (conversion_flag == 1'b1))
        shift_counter <= 5'd0;
    else if(conversion_flag == 1'b1)
        shift_counter <= shift_counter + 1'b1;
    else
        shift_counter <= shift_counter;
end

always @(posedge system_clock or negedge system_reset) begin
    if(!system_reset)
        shift_register <= 32'd0;
    else if(shift_counter == 5'd0)
        shift_register <= {16'b0, binary_data};
    else if((shift_counter <= 16) && (conversion_flag == 1'b0)) begin
        shift_register[19:16] <= (shift_register[19:16] > 4) ? (shift_register[19:16] + 2'd3) : (shift_register[19:16]);
        shift_register[23:20] <= (shift_register[23:20] > 4) ? (shift_register[23:20] + 2'd3) : (shift_register[23:20]);
        shift_register[27:24] <= (shift_register[27:24] > 4) ? (shift_register[27:24] + 2'd3) : (shift_register[27:24]);
        shift_register[31:28] <= (shift_register[31:28] > 4) ? (shift_register[31:28] + 2'd3) : (shift_register[31:28]);
    end
    else if((shift_counter <= 16) && (conversion_flag == 1'b1))
        shift_register <= shift_register << 1;
    else
        shift_register <= shift_register;    
end

always @ (posedge system_clock or negedge system_reset) begin
    if(system_reset == 1'b0) 
        conversion_flag <= 1'b0;
    else    
        conversion_flag <= ~conversion_flag;
end

always @(posedge system_clock or negedge system_reset) begin
    if(!system_reset) begin
        ones_digit <= 4'b0;
        tens_digit <= 4'b0;
        hundreds_digit <= 4'b0;
        thousands_digit <= 4'b0;
    end    
    else if(shift_counter == 5'd17) begin
        ones_digit <= shift_register[19:16];
        tens_digit <= shift_register[23:20];
        hundreds_digit <= shift_register[27:24];
        thousands_digit <= shift_register[31:28];
    end
end

endmodule


Implementation Notes:

  • The code can be ported to other FPGA platforms with minimal modifications
  • Button debounce logic is not included in this implementation for simplicity
  • For production use, consider adding metastability protection and proper synchronization for asynchronous inputs

Tags: FPGA Verilog HDL Digital Design Stopwatch Seven-Segment Display

Posted on Sat, 08 Aug 2026 16:14:24 +0000 by virken