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:
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.
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.
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.
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.
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.
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.
Below is a short video of the reaction timer being tested.