Stopwatch with the Basys 2 FPGA

A stopwatch is a good FPGA project that covers many basic, yet interesting areas of FPGA design. We will need display multiplexing for the multi-digit display, synchronous cascaded counter circuits to increment time registers for seconds and minutes, and a finite state machine to give us start, stop, and reset functionality.

I recently acquired a Basys 2 FPGA development board, which has an on-board 4 digit seven-segment display that lends itself nicely to keeping track of time in MM.SS format. Let’s build a stopwatch.

Before we begin to tackle the counting part of the stopwatch project let’s first consider how to control the 4-digit seven-segment display.

There are only 8 shared pins to control the 7 segments and dot for all 4 digits on the display, while 4 pins enable digits 0-3 at any time. By sequentially turning one digit on at a time while the other digits are off, at a high enough refresh rate, we can display different numbers for each digit at the “same time”. Multiplexing, as this is called, saves us on the number of pins needed to drive a device, but comes at the cost of having to design a multiplexing circuit.

The multiplexing code we will use for this part is referenced from FPGA Prototyping by Verilog Examples by Pong Chu.

module displayMux

	  input wire clk,                             // clock and reset input lines
	  input wire [3:0] hex3, hex2, hex1, hex0,    // hex digits
	  input wire [3:0] dp_in,                     // 4 dec pts
	  output reg [3:0] an,                        // enable for 4 displays
	  output reg [7:0] sseg                       // led segments

	// constant for refresh rate: 50 Mhz / 2^16 = 763 Hz
	// we will use 2 of the 2 MSB states to multiplex 3 displays
	localparam N = 18;
	// internal signals
	reg [N-1:0] q_reg;
	wire [N-1:0] q_next;
	reg [3:0] hex_in;
	reg dp;
	// counter register 
	always @(posedge clk)
              q_reg <= q_next; 
	// next state logic 
	assign q_next = q_reg + 1;
	// 2 MSBs control 4-to-1 multiplexing (active low)
	always @*
	   case (q_reg[N-1:N-2])
		   an = 4'b1110;
                   hex_in = hex0;
		   dp = dp_in[0];
		   an = 4'b1101;
                   hex_in = hex1;
		   dp = dp_in[1];
		   an = 4'b1011;
                   hex_in = hex2;
		   dp = dp_in[2];

		   an = 4'b0111;
                   hex_in = hex3;
		   dp = dp_in[3];
                   an = 4'b1111;
		   dp = 1'b1;
	// hex to seven-segment circuit 
	always @*
	       4'h0: sseg[6:0] = 7'b0000001;
	       4'h1: sseg[6:0] = 7'b1001111;
	       4'h2: sseg[6:0] = 7'b0010010;
	       4'h3: sseg[6:0] = 7'b0000110;
	       4'h4: sseg[6:0] = 7'b1001100;
	       4'h5: sseg[6:0] = 7'b0100100;
	       4'h6: sseg[6:0] = 7'b0100000;
	       4'h7: sseg[6:0] = 7'b0001111;
	       4'h8: sseg[6:0] = 7'b0000000;
	       4'h9: sseg[6:0] = 7'b0000100;
	       4'ha: sseg[6:0] = 7'b0001000;
	       4'hb: sseg[6:0] = 7'b1100000;
	       4'hc: sseg[6:0] = 7'b0110001;
	       4'hd: sseg[6:0] = 7'b1000010;
	       4'he: sseg[6:0] = 7'b0110000;
	       default: sseg[6:0] = 7'b0111000; // 4'hf
	  sseg[7] = dp;


The circuit we will use lights up digits 0-F on the four digits of the display.

A decoder circuit takes the input of a 4 bit register and outputs the appropriate states of the common segment pins to represent the hexadecimal number on a seven-segment display.

An 18 bit counter register “q_reg” increments each positive edge of the clock, much like the counter we covered last time. The upper two bits of q_reg count from 0 to 3 with a frequency of 763 Hz, fast enough to multiplex the four digits of our display without any noticeable flickering.

A case statement uses the upper 2 bits of q_reg to turn on (enabled low) the appropriate digit enable pin and multiplex the routing of the separate digit registers to the common segment pins of the display. This separately routes numbers “hex0” to “hex3” to digits 0-3 of the display, sequentially with a frequency of 764 Hz. Thus we see each number being displayed on the appropriate digit.

Now that we have the ability to easily display numbers 0-9 on each digit of our display we can move on to the stopwatch circuit.

module stopwatch
	  input wire clk,
	  input wire go, stop, clr, 
	  output wire [3:0] d3, d2, d1, d0


The stopwatch circuit has 3 inputs that we will route to pushbuttons, which tell the stopwatch to go, stop, and clear. The output arrays d3-d0 will be used to route counter registers for the MM.SS time to the multiplexing display to show the elapsed time.

First lets consider the finite state machine that will control the user input portion of the stopwatch.

	// declarations for FSM circuit
	reg state_reg, state_next;           // register for current state and next state     
	localparam off = 1'b0,               // states 
	           on = 1'b1;


For the FSM there is a state register and next state logic register. We use two separate bits to represent the state of the FSM; off or on.

   // state register
   always @(posedge clk, posedge clr)
	 state_reg <= 0;
         state_reg <= state_next;

   // FSM next state logic
   always @*
                state_next = on;
		state_next = off;
         on: begin
               state_next = off;
	       state_next = on;


The state register updates on each positive edge of the clock, and is reset to 0 (off) when the clr input is asserted. The FSM next state logic controls the transition between on and off states. In the off state, if the go input is asserted, the next state is on, else the off state is retained. In the on state, if the stop input is asserted, the next state is off, else the on state is retained. We use the status of the state register to enable or disable the counting circuit.

The counting circuit uses synchronous cascaded counter registers to display incrementing time.

	// declarations for counter circuit
	localparam divisor = 50000000;                  // number of clock cycles in 1 s, for mod-50M counter
	reg [26:0] sec_reg;                             // register for second counter
	wire [26:0] sec_next;                           // next state connection for second counter
	reg [3:0] d3_reg, d2_reg, d1_reg, d0_reg;       // registers for decimal values displayed on 4 digit displays
	wire [3:0] d3_next, d2_next, d1_next, d0_next;  // next state wiring for 4 digit displays
	wire d3_en, d2_en, d1_en, d0_en;                // enable signals for display multiplexing 
	wire sec_tick, d0_tick, d1_tick, d2_tick;       // signals to enable next stage of counting

        // counter register 
	always @(posedge clk)
	       sec_reg <= sec_next;
	       d0_reg  <= d0_next;
	       d1_reg  <= d1_next;
	       d2_reg  <= d2_next; 
	       d3_reg  <= d3_next;
	// next state logic 
	// 1 second tick generator : mod-50M
	assign sec_next = (clr || sec_reg == divisor && (state_reg == on)) ? 4'b0 : 
	                  (state_reg == on) ? sec_reg + 1 : sec_reg;
	assign sec_tick = (sec_reg == divisor) ? 1'b1 : 1'b0;
	// second ones counter 
	assign d0_en   = sec_tick; 
	assign d0_next = (clr || (d0_en && d0_reg == 9)) ? 4'b0 : 
	                  d0_en ? d0_reg + 1 : d0_reg; 
	assign d0_tick = (d0_reg == 9) ? 1'b1 : 1'b0;
	// second tenths counter 
	assign d1_en = sec_tick & d0_tick; 
	assign d1_next = (clr || (d1_en && d1_reg == 5)) ? 4'b0 : 
	                  d1_en ? d1_reg + 1 : d1_reg; 	
	assign d1_tick = (d1_reg == 5) ? 1'b1 : 1'b0;
	// minute ones counter 
	assign d2_en = sec_tick & d0_tick & d1_tick; 
	assign d2_next = (clr || (d2_en && d2_reg == 9)) ? 4'b0 : 
	                  d2_en ? d2_reg + 1 : d2_reg;
        assign d2_tick = (d2_reg == 9) ? 1'b1 : 1'b0;	
	// minute tenths counter 
	assign d3_en = sec_tick & d0_tick & d1_tick & d2_tick; 
	assign d3_next = (clr || (d3_en && d3_reg == 9)) ? 4'b0 : 
	                  d3_en ? d3_reg + 1 : d3_reg;
       // route digit registers to output 
       assign d0 = d0_reg; 
       assign d1 = d1_reg; 
       assign d2 = d2_reg;
       assign d3 = d3_reg;



There is a base register, sec_reg, that increments with the positive edge of the clock. This register resets and sets a tick line high when the counter counts 1 second worth of clock cycles (sec_reg == divisor). This tick increments a register keeping a value of seconds “ones” for the display (MM.S<S>).

When the seconds “ones” registers reaches 9 and the base register ticks, the “ones” register ticks up, which enables a seconds “tenths” register (MM.<S>S) to go to its next state, signifying that ten seconds have elapsed.

When the “tenths” register reaches 5 and the “ones” and base registers tick, the “tenths” register ticks up to the minutes “ones” register (M<M>.SS), signifying that 60 seconds have elapsed.

When the minutes “ones” register reaches 9, and both seconds “tenths” and “ones” as well as base register tick, the minute “ones” register ticks up to the minutes “tenths” register (<M>M.SS), signifying that ten minutes have elapsed. The minute “tenths” register increments up to 9, where the next up tick it receives will effectively roll over the stopwatch. Thus this stopwatch can count up to 99 minutes and 59 seconds.

module stopwatch_unit

	  input wire clk, go, stop, clr,      // clock and reset input lines
	  output wire [3:0] an,               // enable for 4 displays
	  output wire [7:0] sseg              // led segments
	wire [3:0] d3, d2, d1, d0;
	stopwatch timer (.clk(clk), .go(go), .stop(stop), .clr(clr), .d3(d3), .d2(d2), .d1(d1), .d0(d0));
	displayMux display_unit (.clk(clk), .hex3(d3), .hex2(d2), .hex1(d1), .hex0(d0), .dp_in(4'b1011), .an(an), .sseg(sseg));


Finally we instantiate both of these circuits, routing the output of the stopwatch circuit to the input of the display multiplexing circuit.

# clock pins for Basys2 Board
NET "clk" LOC = "B8"; # Bank = 0, Signal name = MCLK

NET "clr" LOC = "M4";  # Bank = 0, Signal name = BTN2
NET "stop" LOC = "C11"; # Bank = 2, Signal name = BTN1
NET "go" LOC = "G12"; # Bank = 0, Signal name = BTN0

NET "an[3]" LOC = "K14"; # Bank = 1, Signal name = AN3
NET "an[2]" LOC = "M13"; # Bank = 1, Signal name = AN2
NET "an[1]" LOC = "J12"; # Bank = 1, Signal name = AN1
NET "an[0]" LOC = "F12"; # Bank = 1, Signal name = AN0

NET "sseg[6]" LOC = "L14"; # Bank = 1, Signal name = CA
NET "sseg[5]" LOC = "H12"; # Bank = 1, Signal name = CB
NET "sseg[4]" LOC = "N14"; # Bank = 1, Signal name = CC
NET "sseg[3]" LOC = "N11"; # Bank = 2, Signal name = CD
NET "sseg[2]" LOC = "P12"; # Bank = 2, Signal name = CE
NET "sseg[1]" LOC = "L13"; # Bank = 1, Signal name = CF
NET "sseg[0]" LOC = "M12"; # Bank = 1, Signal name = CG
NET "sseg[7]" LOC = "N13"; # Bank = 1, Signal name = DP


For completeness,  here is the user constraint file used for this circuit on the Basys 2 board.

If you are impatient and wish to test the circuit over the entire range of functional time output, you can make the divisor value smaller.


One thought on “Stopwatch with the Basys 2 FPGA

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s