Lab 03

Introduction

Clock

We have first encounterd the clock in NUS CG2111A! Now we met them again! We can go back to review the notes from CG2111A to get a better view about the clock. Unlike the ATmega328p, our FPGA has a clock built in, which runs at 100 MHz — that is, it completes a full period (on and then off) 100 million times a second (or every 10 nanoseconds).

Registers

Register is something we have learned already from the textbook! Technically speaking, the register we used here is a 1-bit D Flip-Flop. And still remember the very important sentence about the D flip-flop?

A D flip-flop copies D to Q on the rising edge of the clock, and remembers its state at all other times until the next rising edge comes, then it will update its state.

That's essentially the soul of a D flip-flop/1-bit register! Now, let's look at the verilog implementation of register, remember that we have used an always statement.

The keyword always is as the name implies; it means that any behaviour defined inside this block will be repeated till the end of time (or until you turn off/reprogram the FPGA, whichever comes first). This is the same behaviour as combinational logic — once you define a combinational logic circuit, it performs the same function till the end of time.

And this is again the soul of an always statement, both in combinational and sequential logic.

Activity 1: Create a simple counter

Customized Speed Counter

Following the instructions on the lab manual strictly, the code for our simple counter will be as follows,

Counter.sv
module Counter #(
    parameter int OUTPUT_BITS = 8,            // width of main counter
    parameter int CLK_HZ      = 100_000_000,  // FPGA input clock frequency (100 MHz default)
    parameter int TARGET_HZ   = 4             // desired output frequency (Hz)
) (
    input                        clk,
    input                        rst,      // active-low reset
    input                        up_down,  // 0 = count up, 1 = count down
    output reg [OUTPUT_BITS-1:0] count
);

  // Calculate number of input clock cycles per output increment
  localparam int N_COUNTS = CLK_HZ / TARGET_HZ;

  // Calculate number of bits needed for divider
  localparam int N_BITS = $clog2(N_COUNTS);

  // Clock divider counter
  logic [N_BITS-1:0] clk_cnt;

  always @(posedge clk) begin
    // Main counter
    if (!rst) begin
      count   <= 0;
      clk_cnt <= 0;
    end else begin
      // Second counter
      clk_cnt <= (clk_cnt == N_COUNTS - 1) ? 0 : (clk_cnt + 1);

      if (clk_cnt == 0) count <= (up_down == 0) ? (count + 1) : (count - 1);
    end
  end
endmodule

Code Explanation

And follow the requirements for the testbench on the lab manual, we have our testbench to be,

Code Explanation

Activity 2: Display the counter on seven-segment display

To get a preview of what we will design in this activity, please go to the last part.

Taking advantage of persistence of vision

We may have seen from Lab 02 that the seven-segment display can only display one unique digit at any time. However, we can play some tricks on our eyes. This is done by changing the display used very fast so that we can create the illusion of different displays showing different characters.

By Laserlicht, CC BY-SA 4.0, Link

Putting everything together

Once again, before we make everything, make sure we are aware of what each module does, and the inputs and outputs of each module. And most importantly, bare in the golden rule about RTL Coding Steps!

RTL Code

Here, we will use the SevenSegDecoder.sv we have implemented in Lab 02 and the Counter.sv we have implemented in activity 1 in this lab. Besides that, we still need to design our own Top.sv and a SlowCounter.sv (to acheieve the persistence of vision mentioned above)

Constraints

In our constraints file, remember to enable the clk, which is from Line 6-10! This is very important and easy to forget as it is our first time to enable clk on our Nexys 4 FPGA! Not doing so will cause failure to generate the bitstream file!

The fruits of our labour

Last updated