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" LOC = "K14"; # Bank = 1, Signal name = AN3 NET "an" LOC = "M13"; # Bank = 1, Signal name = AN2 NET "an" LOC = "J12"; # Bank = 1, Signal name = AN1 NET "an" LOC = "F12"; # Bank = 1, Signal name = AN0 NET "sseg" LOC = "L14"; # Bank = 1, Signal name = CA NET "sseg" LOC = "H12"; # Bank = 1, Signal name = CB NET "sseg" LOC = "N14"; # Bank = 1, Signal name = CC NET "sseg" LOC = "N11"; # Bank = 2, Signal name = CD NET "sseg" LOC = "P12"; # Bank = 2, Signal name = CE NET "sseg" LOC = "L13"; # Bank = 1, Signal name = CF NET "sseg" LOC = "M12"; # Bank = 1, Signal name = CG NET "sseg" LOC = "N13"; # Bank = 1, Signal name = DP
Below is a short video of the reaction timer being tested.
One thought on “FPGA Reaction Timer”
hi sir, where can I find code for displayMuxBasys circuit in timer reaction project