7. Stopwatch

This lab implements a stopwatch that displays elapsed time from when a switch is activated. We’ll split the task of designing our stopwatch into three steps. First, in Section 7.1 we’ll design a modulo-k counter that produces a tick every \(k\) cycles of an input clock signal. Second, in Section 7.2 we’ll implement our own clock signals which have frequencies of interest to us. Third, in Section 7.3 we’ll use our custom clock signals and count the number of elapsed time units which the final stopwatch displays.

7.1. Counters

Our stopwatch will display time units measured from the last reset, i.e., tenths of a second, seconds, tens of seconds, minutes, and tens of minutes. The clock signals available on the MAX 10 FPGA run at much higher frequencies. This means that we need a way to generate lower-frequency clock signals from those available.

../_images/clocks_4.svg

Fig. 7.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. clk0’s frequency are shown: clk1 (half), clk2 (third), clk3 (fourth) and clk4 (eighth).

Fig. 7.1.1 illustrates the challenge. Here, clk0 has the highest frequency and we wish to derive lower-frequency signals from it. For example, clk3 runs at one-quarter the frequency of clk0. If clk0 runs at 100 MHz, it delivers \(100 \cdot 10^6\) cycles per second. We therefore target a 25 MHz clk3, which produces \(25 \cdot 10^6\) cycles per second. How can we design a circuit that takes clk0 as input and outputs clk3?

The conceptual solution is straightforward: we count the elapsed cycles of the high-frequency clock and generate a new clock signal from that counter.

../_images/ctr3.svg

Fig. 7.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. 7.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. 7.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. 7.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. 7.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 7.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] 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          count <= {N{1'b0}};
23        end
24      else
25        begin
26          if( count == i_k-1 )
27            begin
28              count <= {N{1'b0}};
29            end
30          else
31            begin
32              count <= count + 1;
33            end
34        end
35    end
36
37  // assign output bus
38  assign o_count = count;
39endmodule

An implementation of a SystemVerilog module for a modulo-k counter is provided in Listing 7.1.1. We see that the module modifies the counter’s value 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 count is set to zero (lines 20 - 23). Additionally, if the reset signal is low, count is also set to zero if the counter’s 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. Rather than outputting the counter’s value, we now emit a one-bit rollover signal. We set this rollover signal high only in the clock cycle immediately following a rollover, i.e., when 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. 7.1.2 and ctr4 in Fig. 7.1.3 show the rollover. A template of the targeted module counter_mod_k_ro is given in Listing 7.1.2. To simplify your initial efforts, a code frame for the stopwatch is provided as a starting point.

Listing 7.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] count;
22
23  // TODO: finish implementation of modulo-k counter which outputs a rollover signal
24endmodule

Tasks

  1. Finish implementing module counter_mod_k_ro and testbench counter_mod_k_ro_tb in the stopwatch code frame. Use \(k=4\) in the testbench and verify the counter’s output over 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., count, and the output o_roll_over.

7.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 that takes a rollover signal as input and outputs a clock signal. Specifically, on each rising edge of the rollover signal, our clock toggles its state.

We see in Fig. 7.1.2 that we may obtain the clock signal clk3 from ctr3’s rollover output. Here, the state of clk3 first changes from high to low when clk0 transitions from cycle 1 to cycle 2. This coincides with the first rising edge of ctr3’s rollover signal. A full clock cycle requires two state changes, so the clock produced by ctr3 runs at one-quarter the frequency of clk0. The clock clk4 in Fig. 7.1.3 is derived analogously from the rollover output signal of ctr4.

Listing 7.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 clk;
14
15  // TODO: finish implementation
16
17  assign o_clk = clk;
18endmodule

A template for the clock is given in Listing 7.2.1. The module must 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 internal state clk to high. Otherwise it should simply change its state: if clk is high it should change to low and if 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. Combining both modules allows us to lower the 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 10 Hz clock signal, an 1 Hz signal and a 0.1 Hz one from MAX10_CLK1_50. We’ll display the 10 Hz clock signal using LEDR5, the 1 Hz signal using LEDR6, and the 0.1 Hz one using LEDR7. The signal of the first switch SW0 will drive the reset signal of all modules. The reset signal must be asserted when SW0 is 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 implementing module clock and 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 the clock input for your modulo-k counter counter_mod_k_ro followed by the clock module clock to generate clock signals with 10 Hz, 1 Hz, and 0.1 Hz.

  5. Finish implementing the top-level module clocks_de10_lite in the stopwatch code frame. Use the smallest feasible values for the parameter N in the instantiations of 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.

7.3. Elapsed Time

This final part of the lab combines the pieces of Section 7.1 and Section 7.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 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 10 Hz clock signal which drives a tenth-of-a-second counter.

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

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

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

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

  • HEX2 displays the output of the tens-of-seconds counter.

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

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

  • HEX4 displays the output of the minutes counter.

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

  • HEX5 displays the output of the tens-of-minutes counter.

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

  1. A modulo-k counter that generates a suitable rollover signal from MAX10_CLK1_50. In Section 7.2 we already derived proper values for \(k\) for the seconds-related parts. Respective values of \(k\) for minutes and tens of minutes still must be derived.

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

  3. Another modulo-k counter that 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 within the file stopwatch_de10_lite.sv. As in Section 7.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.