Arty FPGA 02: Clocks, Counting, & Colour

Introduction

Welcome back to my FPGA tutorial series with Verilog and the Digilent Arty board. In this second part, we're going to control LEDs using the clock. We can evidently use a clock to flash the LEDs on and off, but a clock also allows us to control the brightness of the LEDs using a technique called pulse width modulation (PWM). PWN is commonly used in digital circuits to control the speed of motors as well as the brightness of lights (for example in the backlight of LCD screens). Finally, we'll make use of PWM to control the colour of the RGB LEDs.

If you've not already completed part 1 you should start there. In the following part we will look at button debouncing and explore some more Verilog features, such as wire and reg.

Feedback to @WillFlux is most welcome. Last updated February 2018.

Requirements

The requirements for this part are the same as part 1:

  1. Digilent Arty Board
  2. Micro USB cable to program and power the Arty board
  3. Xilinx Vivado installed
  4. Arty board file installed, so Vivado knows your board specification

Learning to Count

We're going to start by using a simple counter to blink an LED on and off. The Arty board has a built-in 100 MHz oscillator. The oscillator provides a clock source we make use of throughout our designs. The constraints file includes the name and configuration of the clock (see below). A 100 MHz clock has a cycle time of 10 nanoseconds (ns): this will be the basic time unit for our designs.

Create a new project in Vivado called tut02 using the same settings as in part 1: an RTL Project with the Arty board.

Add a new design source to your project called top.v with the following content:

module top(
    input CLK,
    output reg [3:0] led
    );

    reg [32:0] counter;

    always @ (posedge CLK) 
    begin
        counter <= counter + 1;
        led[0] <= counter[26];
    end
endmodule

The always block has the positive edge (posedge) of the clock (CLK) on its sensitivity list: the contents of the always block are run on every rising clock. The clock is 100MHz, so that's 100 million times a second.

Add a constraints file to your project called arty.xdc with the following content:

## Arty Board Constraints
## Based on https://github.com/Digilent/digilent-xdc/blob/master/Arty-Master.xdc

## Clock
set_property -dict {PACKAGE_PIN E3  IOSTANDARD LVCMOS33} [get_ports {CLK}];
create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports {CLK}];

## LEDs
set_property -dict {PACKAGE_PIN H5  IOSTANDARD LVCMOS33} [get_ports {led[0]}]; 
set_property -dict {PACKAGE_PIN J5  IOSTANDARD LVCMOS33} [get_ports {led[1]}];
set_property -dict {PACKAGE_PIN T9  IOSTANDARD LVCMOS33} [get_ports {led[2]}];
set_property -dict {PACKAGE_PIN T10 IOSTANDARD LVCMOS33} [get_ports {led[3]}];

Run synthesis, implementation, bitstream generation, and program device using tut02/tut02.runs/impl_1/top.bit. See part 1 if you need a reminder on how to do this.

Tip: You can choose "Generate Bitstream" to run the first three steps in one go. You can find this on the left-hand-side of the Vivado window.

You should see the green LED LD4 blinking on your Arty board.

The clock cycle time is 10 ns, and we change the state of the LED based on the 26th bit of counter. Therefore our LED switches every 10 ns x 226 = 0.67 seconds. That's one blink every 1.34 seconds.

Try replacing the always block in top.v with the following:

always @ (posedge CLK) 
begin
    counter <= counter + 1;
    led[0] <= counter[26];
    led[1] <= counter[24];
    led[2] <= counter[22];
    led[3] <= counter[20];
end

Rerun synthesis, implementation, bitstream generation and program device. How often are the different LEDs blinking?

LD7 on our board (led[3] in Verilog) looks like it's on all time, but is less bright. It's blinking fifty times per second, so fast that your eyes don't see the flashes. We can extend this approach to control the brightness of our LEDs.

Serial vs Parallel

If you're familiar with software development, you might imagine that the statements inside the always block execute in sequence: increment counter, then changes the value of led[0], then led1[1] etc. This isn't how it works when using <= in Verilog: statements execute in parallel. We'll learn more about this in later tutorials.

Brighter, Dimmer

In the previous example LED LD7 spent an equal time switched off and on, so it was about half as bright. By altering the proportion of time the signal is high, and hence the LED is illuminated, we can control the perceived brightness. The is known as pulse width modulation or PWM.

The proportion of time the signal is high is known as the duty cycle. An 8-bit value, 0 to 255, is commonly used for the duty cycle. The brightness of the LED is proportional to the duty cycle. For example, if the duty cycle is 64/255, the LED will be roughly a quarter of maximum brightness.

In the following example, we reduce the brightness of all four LEDs using a small duty cycle of 5/255. Replace the existing module in top.v with the following:

module top(
    input CLK,
    output reg [3:0] led
    );

    reg [7:0] counter = 0;
    reg [7:0] duty_led = 8'd5;

    always @ (posedge CLK) 
    begin
        counter <= counter + 1;
        if (counter < duty_led)
        begin
            led[3:0] <= 4'b1111;
        end
        else
        begin
            led[3:0] <= 4'b0000;
        end
    end
endmodule

Run through the usual steps to program your board. The green LEDs should all be very dim; around 2% of maximum brightness.

Try adjusting the duty value to different values between 0 and 255 to see how the brightness changes. 8'd5 is an 8-bit decimal literal with the value of 5. To specify hex values use h. For example, 8'h80 would be an 8-bit literal with the value of 128 in decimal.

Duty Cycle Module

If we wanted to control the brightness of each LED using the above approach we'd end up with a great deal of duplicated code. Instead, we can break the pulse width modulation code out into a separate module. The pwm module has inputs and outputs, just like the top module.

Our PWM module takes a duty cycle input between 0 and 255 as a parameter and outputs 1 and 0 for the correct proportion of time.

Create a new design source called pwm.v with the following content:

module pwm(
    input clk,
    input [7:0] i_duty,
    output reg o_state
    );

    reg [7:0] counter = 0;

    always @ (posedge clk)
    begin
        counter <= counter + 1;
        o_state <= (counter < i_duty);
    end
endmodule

Note how we've prefixed inputs with i_ and outputs with o_ to make it easy to identify which is which.

To use the pwm module within our top module, we create an instance of it with a unique name and pass the inputs we want to use. We have four LEDs we wish to use with it, so we create four instances.

Replace the top module in top.v with:

module top(
    input CLK,
    output [3:0] led
    );

    pwm pwm_led_0 (
        .clk(CLK),
        .i_duty(4),
        .o_state(led[0])
    );

    pwm pwm_led_1 (
        .clk(CLK),
        .i_duty(16),
        .o_state(led[1])
    );

    pwm pwm_led_2 (
        .clk(CLK),
        .i_duty(64),
        .o_state(led[2])
    );

    pwm pwm_led_3 (
        .clk(CLK),
        .i_duty(255),
        .o_state(led[3])
    );
endmodule

Once you've programmed your board with the updated design, you should see all four green LEDs lit, getting brighter from LD4 to LD7.

Vivado Sources View

If you look at the Sources window in Vivado, you may wonder where your pwm module has gone. Vivado has updated the view to show that it has four instances within top:

Red, Green, Blue

In addition to the green LEDs, the Arty also has four RGB LEDs. By adjusting the brightness of the red, green, and blue components using PWM, we can produce (almost) any colour. You control red, green and blue for each LED with a separate output (pin on the FPGA).

To use the RGB LEDs, we need to include them in the constraints file.

Add the following lines to the bottom of arty.xdc:

## RGB LEDs
set_property -dict {PACKAGE_PIN E1  IOSTANDARD LVCMOS33} [get_ports {led0_b}];
set_property -dict {PACKAGE_PIN F6  IOSTANDARD LVCMOS33} [get_ports {led0_g}];
set_property -dict {PACKAGE_PIN G6  IOSTANDARD LVCMOS33} [get_ports {led0_r}];
set_property -dict {PACKAGE_PIN G4  IOSTANDARD LVCMOS33} [get_ports {led1_b}];
set_property -dict {PACKAGE_PIN J4  IOSTANDARD LVCMOS33} [get_ports {led1_g}];
set_property -dict {PACKAGE_PIN G3  IOSTANDARD LVCMOS33} [get_ports {led1_r}];
set_property -dict {PACKAGE_PIN H4  IOSTANDARD LVCMOS33} [get_ports {led2_b}];
set_property -dict {PACKAGE_PIN J2  IOSTANDARD LVCMOS33} [get_ports {led2_g}];
set_property -dict {PACKAGE_PIN J3  IOSTANDARD LVCMOS33} [get_ports {led2_r}];
set_property -dict {PACKAGE_PIN K2  IOSTANDARD LVCMOS33} [get_ports {led3_b}];
set_property -dict {PACKAGE_PIN H6  IOSTANDARD LVCMOS33} [get_ports {led3_g}];
set_property -dict {PACKAGE_PIN K1  IOSTANDARD LVCMOS33} [get_ports {led3_r}];

Then replace the top module with the following:

module top(
    input CLK,
    output led0_r,
    output led0_g,
    output led0_b
    );

    pwm pwm_led0_r (
        .clk(CLK),
        .i_duty(0),
        .o_state(led0_r)
    );

    pwm pwm_led0_g (
        .clk(CLK),
        .i_duty(64),
        .o_state(led0_g)
    );

    pwm pwm_led0_b (
        .clk(CLK),
        .i_duty(64),
        .o_state(led0_b)
    );
endmodule

The LED LD0 on the board should be a cyan colour (50/50 mix of green and blue), but it looks pale blue to my eyes. If you look closely at the LED, you'll see the separate green and blue components.

If you change the duty cycle for red to 32, green to 4, and blue to 48, then you'll get pinky-purple. Experiment with adjusting the duty cycle for the different colours. The RGB LEDs are intensely bright, so I recommend limiting your values to a maximum of 64/255.

You can easily extend this approach to all the RGB LEDs. See what colours you can create.

In the next part of this tutorial, we'll use the push buttons to control the brightness of the LEDs and learn about debouncing.

©2017-18 Will Green.