FPGA Reaction Timer

Reaction time is the duration of time it takes for the brain to interpret a stimulus and do something in reaction to it. The stimulus may be something visual such as a light turning on, something auditory such as a beep, or a touch cue such as a poke. The time it takes for the brain to interpret a stimulus and respond to it can be used as a basic benchmark to measure and compare mental acuity.

We will be implementing a reaction timer on an FPGA that turns on an LED after a psuedorandom period of time, and uses a pushbutton as a reaction input. There will be 3 input buttons: clear, start, and stop. The system will begin in an idle state waiting for the user to press the start button. When the start button is pressed, a random time interval will elapse before the LED turns on. When the LED turns on a reaction timer will begin counting the number of milliseconds until the user presses the stop button. When the stop button is pressed, the reaction time will be shown on a 4 digit 7-segment display in the format “0.000” seconds, up to a value of 9.999 seconds. The user can then press the clear button to reset the time display and go back to the idle state.

For this project we will be using the Basys 2 FPGA development board to implement the design, as it has the 4 digit display, pushbuttons, and LED that we need onboard.

The first task we will consider is how to generate a random time interval to wait after the start button is pressed and before turning on the stimulus LED. The randomness of the time interval is meant to prevent a user from  being able to get a feel for when the LED will turn on after pressing the start button. We will design the random duration to be between 3 seconds and 10 seconds. The Basys 2 Board uses a default 50 MHz clock, with each clock cycle having a period of 1/50E6 = 20 ns. If we want a time interval between 3 and 10 seconds, we will need to cycle between 1.5E8 and 5E8 clock cycles. We clearly will need a counter that counts down from a randomly selected number of clock cycles between this range, which we will call the “countdown_timer”. To determine where in the countdown range is chosen, we can divide the range by 1E7 and make another counter go from 15 to 50. By having a counter that increments once every clock cycle, and cycles between 15 and 50, the value that it has when the start button is pressed is practically impossible for the user to intentionally select or to know. We will call this counter register “random_counter”. So when the start button is pressed, the value of the random_counter register will be taken and multiplied by 1E7, to yield a value in the range of 1.5E8 and 5E8, and will be stored in the countdown timer’s register for the countdown to begin from.

When the countdown_timer is done counting down and the LED turns on, we will need to begin counting up the number of milliseconds it takes for the user to react and press the stop button. For our system, 1 ms equates to 50,000 clock cycles, so we will create a register called ms_counter that counts up to 50,000 and then resets to 0. We will have a signal called ms_tick that is asserted when the ms_counter register counts up to 50,000. We will then create a register called reaction_timer that will increment every time ms_tick is asserted, and will thus count up the number of milliseconds that have elapsed since the LED has been on. We will limit reaction_timer to count up to 9999, which is the maximum value that can be displayed as 9.999 seconds.

To reaction timer will be implemented as a finite state machine with data path (FSMD), with 4 states, idle, load, timing, and w2c.

  • The idle state will wait for the user to press the start button. When the start button is pressed the state will transition to load, and the countdown_timer will be loaded with the pseudorandom countdown value.
  • In the load state the countdown_timer will turn on and begin counting down until 0. When the countdown is done, the state will transition to timing.
  • In the timing state the LED will turn on, and the ms_timer will be enabled, beginning the reaction time count. The state will only transition once the stop button is pressed, and will transition to the w2c state.
  • The w2c (wait to clear) state does nothing but wait for clear to be pressed. The register of the FSM will be designed such that when the clear button is pressed the state will transition to idle.

To display the reaction time we will use two ready made circuits derived from Pong Chu’s FPGA by Verilog Examples, one to convert the binary value of the reaction time in ms to BCD format, and the other to display the 4 BCD values on the 4 7-segment displays.

Let’s look at the code in chunks:

module reactionTimer

    (
      input wire clk,
      input wire clear, start, stop,
      output wire led,
      output wire [3:0] an,
      output wire [7:0] sseg
    );
	 

    // internal signals
    reg [5:0] random_counter_reg;       // register counts from 15 to 50
    wire [5:0] random_counter_next; 
    reg [15:0] ms_counter_reg;          // register ticks and overflows at 1 ms
    wire [15:0] ms_counter_next;
    reg [13:0] reaction_timer_reg;      // register counts number of elapsed ms up to 9999
    wire [13:0] reaction_timer_next;
    reg [28:0] countdown_timer_reg;     // register counts down from random number of seconds
    wire [28:0] countdown_timer_next;
    wire ms_tick;                       // tick from ms_counter register to reaction_timer register to increment every 1 ms
    reg ms_go;                          // register to start/stop ms_counter
    wire countdown_done;                // register with countdown done status
    reg countdown_go;                   // register to start/stop countdown_timer
    
    reg [1:0] state_reg, state_next;    // FSM register and next state logic
    reg bin2bcd_start;                  // start signal for binary to bcd conversion
    wire [3:0] bcd3, bcd2, bcd1, bcd0;  // signals to route from bin2bcd outputs to displaymux inputs
    reg led_state;                      // output state for reflex led

 

Our Verilog module will be called reactionTimer. It will take an input from the master clock, take inputs clear, start, and stop, have an output line for the reflex LED, and output arrays to drive the 7-segment displays.

We next have four pairs of reg and wire arrays to use for our timing / counting registers. The random_counter register goes from 15 to 50, so it must be 6 bits wide (log(2, 50) = 5.64). The ms_counter goes from 0 to 50,000, so it must be 16 bits wide (log(2, 50,000) = 15.6). The reaction_timer goes from 0 to 9999, so it must be 14 bits wide (log(2, 9999) = 13.28). Finally, the countdown_timer goes from 1.5E8 to 5E8, so it must be 29 bits wide (log(2, 5E8) = 28.89).

The ms_tick is routed to the next state logic of the reaction_timer register to increment its value every millisecond. The ms_go register is asserted in the timing state of the FSM to enable the ms_timer. The countdown_done signal is routed to the FSM to tell the load state to transition to the timing state when the random countdown time has fully elapsed. The countdown_go signal is asserted to enable the countdown_timer.

The state_reg and state_next registers form our FSM state register and next state logic. The bin2bcd_start register is asserted when the reaction time is finalized and the binary value is ready to be converted to 4 Binary Coded Decimal (BCD) values to be displayed. The wire arrays bcd3-bcd0 route the output from the bin2bcd circuit to the 7-segment display multiplexing circuit. Finally the led_state register is asserted to turn on the reflex LED.

	 // register for timers / counters
	 always @(posedge clk, posedge clear)
	     if(clear) // reset to 0
	       begin
	           random_counter_reg   <= 7'b0;
	           ms_counter_reg       <= 16'b0;
	           reaction_timer_reg   <= 14'b0;
	           countdown_timer_reg  <= 30'b0;
	       end
	     else
	       begin
		   random_counter_reg  <= random_counter_next;
		   ms_counter_reg      <= ms_counter_next;
	           reaction_timer_reg  <= reaction_timer_next; 
                   countdown_timer_reg <= countdown_timer_next;
	       end

 

Next we create the registers for the timer / counters. If clear is asserted, each register is reset to 0, else on the positive edge of the clock they are set to the next state determined by the next state logic descriptions below.

	 // random counter next state logic 
	 // count from 15 to 50
	 assign random_counter_next = (random_counter_reg < 7'b0110010) ? random_counter_reg + 1 : 7'b0001111; 
	 
	 // ms counter next state logic 
	 // when ms_go asserted, increment until 50000, then reset.
	 assign ms_counter_next = (ms_go && ms_counter_reg < 50000) ? ms_counter_reg + 1 :  
	                          (ms_counter_reg == 50000) ? 16'b0 : ms_counter_reg;
	 
	 // ms_tick upticks to reaction_timer at 50000 clock cycles, or 1 ms
	 assign ms_tick = (ms_counter_reg == 50000) ? 1'b1 : 1'b0; 
	 
	 // reaction timer next state logic
	 // count up for every 1 ms until 9999
	 assign reaction_timer_next = (ms_tick && reaction_timer_reg < 9999) ? reaction_timer_reg + 1 : reaction_timer_reg; 
	
         // countdown timer next state logic
         // when countdown_go asserted and reg not 0, decrement 1 every clock cycle,
         // if start asserted, load random number to countdown from (1.5E8 to 5E8 clock cycles, or 3s to 10s of elapsed time)	 
	 assign countdown_timer_next = (countdown_go && countdown_timer_reg > 0) ? countdown_timer_reg - 1 :
	                               start ? random_counter_reg * 10000000 : countdown_timer_reg;
	 
	 // when countdown_timer is 0, assert countdown done
	 assign countdown_done = (countdown_timer_reg == 0) ? 1'b1 : 1'b0;

 

The random_counter next state logic increments the register value if it is less than 50, else resets it to 15.

The ms_counter next state logic increments the register value if the ms_go register is asserted (in the FSM timing state) and the value is < 50,000, else if the value is 50,000 it is reset to 0.

We assert the ms_tick line whenever the ms_counter reg hits 50,000, signifying that 1 ms  in clock cycles have elapsed.

The reaction_timer next state logic uses the ms_tick to determine when to increment the register value, so long as the value is below 9999, the max value that can be displayed.

The countdown_timer next state logic increments the register value when the countdown_go register is asserted (in FSM load state). If the start button is pressed, the countdown_timer register is set to the product of the random_counter register and 1E7, thereby setting the countdown value to a number of clock cycles that will take 3-10 seconds to elapse during the countdown process.

Finally we assert the countdown_done line when the countdown_timer register is 0, and the countdown process has completed. This is used to transition from the load to timing state in the FSM.

    // FSM states 
    localparam [1:0] idle   = 2'b00,  // ready to go
	             load   = 2'b01,  // load random time to countdown_reg and countdown
		     timing = 2'b10,  // turn on led, and start timing reaction
		     w2c    = 2'b11;  // led now off, reaction time stored and displayed, waiting to clear

	 
    // FSM state register behavior
    always @(posedge clk, posedge clear)
     if (clear)
        state_reg <= idle;
     else
        state_reg <= state_next;

 

Next we define out FSM state register. We create 4 symbolic constants for our 4 FSM states, and describe the register. When the clear button is pressed, the state register resets the state to idle, else the next state is loaded to the register on a positive clock edge.

    // FSM control
    always @*
    begin
      // defaults
      state_next = state_reg;
      ms_go = 1'b0;
      countdown_go = 1'b0;
      bin2bcd_start = 1'b0;
      led_state = 1'b0;
		
      case (state_reg)
         idle:
	    begin 
	    if (start)              // if start input asserted
	       state_next = load;   // transition to load state
	    end
			
	 load:
	    begin
            countdown_go = 1'b1;       // start countdown_timer counting down
	    if (countdown_done)        // once countdown done,
	       state_next = timing;    // transition to timing state
            end
 
         timing:
            begin 
               ms_go = 1'b1;           // start ms_counter counting up
	       led_state = 1'b1;       // turn on reflex led
               if (stop)               // if stop input asserted
		  begin
	             state_next = w2c;     // transition to w2c state
		     bin2bcd_start = 1'b1; // begin binary to bcd conversion
		  end
	    end
			
	 w2c:      // state does nothing but wait for clear input to be asserted, which in the FSM register sets state to idle
	   begin 
           end
				
      endcase
    end      

 

Next we have the FSM control. The default values are set before the case statement. For example, ms_go will always be 0, unless the FSM is in the begin state in which it is asserted.

In the idle state, a state transition occurs when the start button is pressed, and the state moves to the load state. Keep in mind that when start is asserted, the random countdown value is also loaded into the countdown_timer_reg.  In the load state, the countdown_go signal is asserted, which begins the random countdown of countdown_timer_reg. Once the countdown_done signal is asserted, the state transitions to timing. In the timing state, the ms_go line is asserted which starts the millisecond counting register that upticks to the reaction timer register every millisecond. The LED is turned on by having the led_state register asserted. A state transition occurs when the user presses the stop button in reaction to the led turning on, in which case the state transitions to w2c and the binary to bcd circuit is initiated. In the w2c state, nothing happens, until the user presses the clear button which in the register behavior definition resets the state to idle.

    // instantiation of binary to bcd circuit, with binary output from reaction_timer register holding number of ms elapsed during reaction timer
    bin2bcd b2b_unit (.clk(clk), .reset(clear), .start(bin2bcd_start), .bin(reaction_timer_reg), .ready(), .done_tick(), .bcd3(bcd3), .bcd2(bcd2), .bcd1(bcd1), .bcd0(bcd0));
    
    // instantiation of display multiplexer circuit, routing in bcd output from bin2bcd circuit and displaying on 4 7-seg displays
    displayMuxBasys disp_unit (.clk(clk), .hex3(bcd3), .hex2(bcd2), .hex1(bcd1), .hex0(bcd0), .dp_in(4'b0111), .an(an), .sseg(sseg));	 

    assign led = led_state; // assign output state of led
			  
endmodule 

 

Finally we instantiate the binary to bcd conversion circuit, which takes in the binary value from the reaction_timer_reg and converts it to 4 BCD values. The multiplexing circuit for the 4 7-segment displays has the 4 BCD outputs of bin2bcd routed into each input, such that the contents of reaction_timer_reg display in the decimal format “1.234” seconds.

The displayMuxBasys circuit is detailed briefly in my “Stopwatch with the Basys 2 FPGA” blog, so please reference that page for the detailed code listing.

The algorithm used to convert the binary value to BCD format is derived from the algorithm and code outlined in Pong Chu’s FPGA by Verilog Examples, chapter 6. This algorithm is essentially the complement of the algorithm detailed in my BCD to Binary Conversion on an FPGA blog post. You can find the slightly adjusted code implementation used for this project here.

Finally for completeness, here is the user constraint file I used for the Basys 2 board in this implementation.

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

NET "clear" LOC = "M4";  # Bank = 0, Signal name = BTN2
NET "start" LOC = "C11"; # Bank = 2, Signal name = BTN1
NET "stop" LOC = "G12"; # Bank = 0, Signal name = BTN0
NET "led" LOC = "M5" ;  # Bank = 2, Signal name = LD0


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

Below is a short video of the reaction timer being tested.

Advertisement

One thought on “FPGA Reaction Timer

Leave a Reply

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

WordPress.com Logo

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

Facebook photo

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

Connecting to %s