8. Stopwatch

This lab implements a stopwatch which displays the elapsed time since we activated a switch button. We’ll split the task of designing our stopwatch into three steps. First, in Section 8.1 we’ll design a modulo-k counter which produces a tick every k cycles of an input clock signal. Second, in Section 8.2 we’ll implement our own clock signals which have frequencies of interest to us. Third, in Section 8.3 we’ll use our custom clock signals and count the number of elapsed time units which the final stopwatch displays.

8.1. Counters

Our stopwatch will show typical time units since the last reset, i.e., tenths of a second, seconds, tens of seconds, minutes and tens of minutes. The clock signals available in the MAX 10 FPGA have much higher frequencies. This means that we require a way to generate clock signals with (much) lower frequencies from the provides ones.

../_images/clocks_4.svg

Fig. 8.1.1 Illustration of different clock signals. Clock signal clk0 has the highest frequency and a total of 12 cycles of clk0 are illustrated. Four additional clock signals with lower frequencies w.r.t. to clk0’s frequency are shown: clk1 (half), clk2 (third), clk3 (fourth) and clk4 (eighth).

Fig. 8.1.1 illustrates the challenge. Here, clk0 has the highest frequency and we are interested in deriving new clock signals from clk0. For example, we could be interested in generating the signal clk3 which has a four times lower frequency than clk0. Putting some numbers behind this, assume that clk0 has a frequency of 100MHz. This means that we have 100 \cdot 10^6 cycles per second as input. Now, we’d like to generate the 25MHz signal clk3 which therefore has 25 \cdot 10^6 cycles per second. How can we design a circuit which takes clk0 as input and outputs clk3?

The conceptual answer to this is a rather simple one: We simply count the number of elapsed cycles of the higher-frequency clock and generate our new clock signal based on that counter.

../_images/ctr3.svg

Fig. 8.1.2 Illustration of an input clock signal clk0 which is used as input for the modulo-2 counter ctr3. The counter outputs the visualized rollover signal which can be used to generate the clock signal clk3.

Fig. 8.1.2 illustrates the counter-based generation of the clock signal clk3 from signal clk0. The used counter ctr3 is an instantiation of a modulo-k counter where k=2 for ctr3. Our targeted modulo-k counter counts from 0 to k-1. When the counter has the value k-1 in a clock cycle, it will have the value 0 in the next clock cycle.

As shown in Fig. 8.1.2 ctr3 has the initial value 0 during the first clock cycle of clk0 with id 0. Then, in the second clock cycle of clk0 with id 1, the value of ctr3 is 1. This means that ctr3 now has the value k-1. Thus, in the next clock cycle of clk0 which has id 2, the value of ctr3 is 0. Similarly, ctr3 has the value zero in clk0’s clock cycles with ids 4, 6, 8 and 10.

../_images/ctr4.svg

Fig. 8.1.3 Illustration of an input clock signal clk0 which is used as input for the modulo-4 counter ctr4. The counter outputs the visualized rollover signal which can be used to generate the clock signal clk4.

Another modulo-k counter with k=4 is illustrated in Fig. 8.1.3. Here, ctr4 counts from 0 to 3. Respectively, ctr4’s value is 0 in clk0’s clock cycles with ids 0, 4 and 8.

Listing 8.1.1 Implementation of the module counter_mod_k_count.
 1/**
 2 * Counter which has the number of elapsed clock cycles modulo k as output.
 3 *
 4 * @param i_clk input clock.
 5 * @param i_reset reset signal which resets the counter's value to zero.
 6 * @param i_k value k which is used for the modulo computation.
 7 * @param o_count set to number of elapsed clock cycles modulo k.
 8 **/
 9module counter_mod_k_count #(parameter N=8) ( input  logic         i_clk,
10                                              input  logic         i_reset,
11                                              input  logic [N-1:0] i_k,
12                                              output logic [N-1:0] o_count );
13  // current value of the counter
14  logic [N-1:0] m_count;
15
16  // on positive edge of input clock or reset
17  // either reset counter or assign next value
18  always_ff @(posedge i_clk, posedge i_reset)
19    begin
20      if( i_reset == 1'b1 )
21        begin
22          m_count <= {N{1'b0}};
23        end
24      else
25        begin
26          if( m_count == i_k-1 )
27            begin
28              m_count <= {N{1'b0}};
29            end
30          else
31            begin
32              m_count <= m_count + 1;
33            end
34        end
35    end
36
37  // assign output bus
38  assign o_count = m_count;
39endmodule

An implementation of a SystemVerilog module which implements a modulo-k counter is given Listing 8.1.1. We see that the module modifies the counter’s value m_count only on a rising edge of the input clock signal i_clk or the reset signal i_reset (line 18 - 35). If the reset signal is high, the counter’s value m_count is set to zero (lines 20 - 23). Additionally, if the reset signal is low, m_count is also set to zero if the counters value is k-1 (lines 26-29). Lastly, if neither of the previous cases occurred, the counter is simply incremented (lines 30-33). The output of the counter is its value (line 38).

In this part of the lab, we’ll implement and test a slightly different module. Instead of outputting the value of the counter, we output a 1-bit rollover signal. We set this rollover signal to high in the clock cycle of the input clock after a rollover ocurred, i.e., the counter’s value changed from k-1 to 0. In all other cases the rollover signal is low. The illustrated signals of ctr3 in Fig. 8.1.2 and ctr4 in Fig. 8.1.3 show the rollover. A template of the targeted module counter_mod_k_ro is given in Listing 8.1.2. To simplify your initial efforts, a code frame for the stopwatch is provided as starting point.

Listing 8.1.2 Template of the module counter_mod_k_ro.
 1/**
 2 * Counter which has the rollover signal as output.
 3 * The signal o_roll_over is high in every clock cycle after a rollover occurred and low in all other cycles.
 4 *
 5 * Assume k=3, then:
 6 * cycle     | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ...
 7 * mod k     | 0 | 1 | 2 | 0 | 1 | 2 | 0 | 1 | 2 | 0 |  1 | ...
 8 * rollover: | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 1 |  0 | ...
 9 *
10 *
11 * @param i_clk input clock.
12 * @param i_reset reset signal which resets the counter's value to zero.
13 * @param i_k value k which is used for the modulo computation.
14 * @param o_roll_over set to rollover signal which is high in every clock cycle after a rollover occurred; low else.
15 **/
16module counter_mod_k_ro #(parameter N=8) ( input  logic         i_clk,
17                                           input  logic         i_reset,
18                                           input  logic [N-1:0] i_k,
19                                           output logic         o_roll_over );
20  // value of the counter
21  logic [N-1:0] m_count;
22
23  // TODO: finish implementation of modulo-k counter which outputs a rollover signal
24endmodule

Tasks

  1. Finish the implementation of the module counter_mod_k_ro and the testbench counter_mod_k_ro_tb in the stopwatch code frame. Use k=4 in the testbench and check the output of the counter for at least ten clock cycles.

  2. Generate a waveform plot illustrating the behavior of counter_mod_k_ro with k=4 in the first ten clock cycles of the input clock i_clk. The plot shall show all inputs, i.e., i_clk, i_reset, i_k, the value of the counter, i.e., m_count, and the output o_roll_over.

8.2. Clock Signals

In this task we use our modulo-k counter with rollover output to derive custom clock signals. For this, we’ll write a SystemVerilog module which takes a rollover signal as an input and outputs a clock signal. Specifically, on each rising edge of the rollover signal our clock changes its state.

We see in Fig. 8.1.2 that we may obtain the clock signal clk3 from ct3’s rollover output. Here, the state of clk3 changes for the first time from high to low when clk0 transitions from cycle 1 to cycle 2. This coincides with the first shown rising edge of ctr3’s rollover signal. Since a full clock cycle implies two state changes, the resulting clock signal of ctr3 has a four times lower frequency when compared to clk0. The clock clk4 in Fig. 8.1.2 is derived analogously from the rollover output signal of ctr4.

Listing 8.2.1 Template of the module clock.
 1/**
 2 * Outputs a clock signal based on the given rollover signal.
 3 * The clock signal changes on every positive edge of the rollover signal.
 4 * On a positive edge of the reset signal, the clock is reset to high.
 5 *
 6 * @param i_roll_over rollover signal.
 7 * @param i_reset reset signal.
 8 * @param o_clk set to generated clock signal.
 9 **/
10module clock( input  logic i_roll_over,
11              input  logic i_reset,
12              output logic o_clk );
13  logic m_clk;
14
15  // TODO: finish implementation
16
17  assign o_clk = m_clk;
18endmodule

A template for the clock is given in Listing 8.2.1. The module shall only change its state on a rising edge of input i_roll_over or i_reset. If i_reset is high, the clock should set its state m_clk to high. Otherwise it should simply change its state, i.e., if m_clk is high it should change to low and if m_clk is low it should change to high.

Once our custom clock is implemented and tested, we can combine it with the modulo-k counter given through the module counter_mod_k_ro. Combined both modules allow us to lower frequency of the clock signal MAX10_CLK1_50 which can be accessed in the MAX 10 FPGA. In the last part of this task, we’ll generate a 10Hz clock signal, an 1Hz signal and an 0.1Hz one from MAX10_CLK1_50. We’ll display the 10Hz clock signal using LEDR5, the 1Hz signal using LEDR6, and the 0.1Hz one using LEDR7. The signal of the first switch button SW0 will drive the reset signal of all modules (reset on low). An exemplary video of the targeted functionality is shown at the top of this task.

Hint

Base all your custom clocks on the MAX10_CLK1_50 clock signal. In other words: Do not chain your clocks!

Tasks

  1. Finish the implementation of the module clock and the testbench clock_tb in the stopwatch code frame.

  2. Generate a waveform plot illustrating the behavior of the module clock. Manually provide the rollover signal when generating the plot and show three full cycles of the clock. The plot shall show all inputs, i.e., i_roll_over, i_reset and the output o_clk.

  3. Look up the frequency of the clock signal MAX10_CLK1_50 in the DE10-Lite User Manual.

  4. Derive appropriate values for k such that you can use MAX10_CLK1_50 as clock input for your modulo-k counter counter_mod_k_ro followed by the clock module clock to generate clock signals with 10Hz, 1Hz and 0.1Hz.

  5. Finish the top-level module clocks_de10_lite of the stopwatch code frame. Use the smallest possible values for the parameter N in the instantiations of the module counter_mod_k_ro.

  6. Program the FPGA of a DE10-Lite board. Explain and illustrate your programmed FPGA to the teaching personnel. In your submission include a scan of this week’s tailored coversheet which represents the deliverable of this subtask.

8.3. Elapsed Time

This final part of the lab combines the pieces of Section 8.1 and Section 8.2 into an actual stopwatch. For this we write the top-level module stopwatch_de10_lite. The short video above shows the deployed stopwatch. The stopwatch uses switch button SW0 as input. If SW0 is high the watch runs and measures the elapsed time, if it is low the stopwatch holds and resets to zero. The outputs of the stopwatch are as follows:

  • LEDR5 displays the 10Hz clock signal which drives a tenth of a second counter

  • HEX0 displays the output of the tenth of a second counter

  • LEDR6 displays the 1Hz clock signal which drives a seconds counter

  • HEX1 displays the output of the seconds counter. Further, HEX1 has an always-on decimal dot. Check the DE10-Lite User Manual on how to activate the dot.

  • LEDR7 displays the 0.1Hz clock signal which drives a tens of seconds counter

  • HEX2 displays the output of the tens of seconds counter

  • HEX3 displays a blinking dash which separates the displays showing seconds from the ones showing minutes

  • LEDR8 displays the 1/60Hz clock signal which drives a minutes counter

  • HEX4 displays the output of the minutes counter

  • LEDR9 displays the 1/600Hz clock signal which drives a tens of minutes counter

  • HEX5 displays the output of the tens of minutes counter

In general, one of the time-showing HEX displays can be programmed by combining four modules:

  1. A modulo-k counter which generates a suitable rollover signal from MAX10_CLK1_50. In Section 8.2 we already derived proper values for k for the seconds-related parts. Respective values of k for minutes and tens of minutes still have to be derived.

  2. A clock which takes the rollover output of the modulo-k counter as input and generates a respective clock signal.

  3. Another modulo-k counter which has the generated clock signal as input and outputs the counter’s value. Hint: For this counter k is either 6 or 10.

  4. A decoder which wires the output bits of the second counter to the respective HEX display.

Tasks

  1. Implement the top-level module stopwatch_de10_lite in the file stopwatch_de10_lite.sv. As in Section 8.2 use minimal values for the parameter N in all instantiations of the counters counter_mod_k_count and counter_mod_k_ro.

  2. Program the FPGA of a DE10-Lite board. Explain and illustrate your programmed FPGA to the teaching personnel. In your submission include a scan of this week’s tailored coversheet which represents the deliverable of this subtask.