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