Arty FPGA 03: Controlling Things with Buttons

Introduction

Welcome back to my FPGA tutorial series with Verilog and the Digilent Arty board. In this third part, we use the push buttons to control the brightness of the LEDs and learn about button debouncing. In this part we'll also learn more about wire and reg net types.

If you've not already read part 1 and part 2 then I strongly recommend you go back and complete them first.

Feedback to @WillFlux is most welcome.

This tutorial is still in draft form. Additional content will be added soon.

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

Requirements

The requirements for part 3 are the same as the previous parts:

  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

Push the Button

In the first part of this series we used a button to control an LED. To do this we checked the button state on every clock edge; if the button was held down at that moment, we did something. However, usually we want to push a button and have something trigger once: think of turning on a computer or typing on a keyboard.

Switching to triggering an event on a button press is not as simple as it first appears. Firstly, it's normal for a button press to result in several signals because the button "bounces" as you press it, making several contacts in quick succession. If we want to use a button to control things we need to tackle this, otherwise one button press will result in multiple events.

Secondly, the button press doesn't neatly arrive in sync with our clock: it could occur at any time. If we were to use this button input directly, we'd risk making our design unstable. This metastability happens when a button press arrives during a clock transition; leaving the system in an inconsistent state. Inconsistency can cascade through a design causing the whole thing to behave unpredictably!

To ensure the button press is synchronised with the clock, minimising the risk of metastability, we pass the button input through two synchronisation registers (sync_0 and sync_1 in the module, below). At this stage, we don't need to dig into the details of metastability, but if you want to learn more, I recommend Altera's Metastability whitepaper.

Up and Down

Our debounce module takes a raw button input and produces a synchronised, bounce-free, output by:

  1. Synchronising the button press to the clock to combat metastability
  2. Waiting for a consistent signal before reporting a button state change: this negates bounce

Create a new project in Vivado called tut03 using the same settings as in part 1: an RTL Project with the Arty board. Within your new project create a design source called debounce.v with the following code:

module debounce(
    input clk,
    input i_btn,
    output reg o_state,
    output o_ondn,
    output o_onup
    );

    // sync with clock and combat metastability
    reg sync_0, sync_1;
    always @(posedge clk) sync_0 <= i_btn;
    always @(posedge clk) sync_1 <= sync_0;

    // 2.6 ms counter at 100 MHz
    reg [18:0] counter;
    wire idle = (o_state == sync_1);
    wire max = &counter;

    always @(posedge clk)
    begin
        if (idle)
            counter <= 0;
        else
        begin
            counter <= counter + 1;
            if (max)
                o_state <= ~o_state;
        end
    end

    assign o_ondn = ~idle & max & ~o_state;
    assign o_onup = ~idle & max & o_state;
endmodule

Our module checks the button has been continuously pressed or released for 2.6 ms (10 ns x 218) before changing the output state. This should be enough time for the button contact to settle, but you can adjust the counter if required.

The o_ondn and o_onup outputs are similar to the JavaScript keyboard events (onkeydown and onkeyup). They're true for one clock tick when the button is pushed down and released respectively. You can test these signals to allow button presses (and releases) to control things.

Note the single-line style of always block we've used for the clock synchronisation.

Reg & Wire

The debounce module introduces the use of wire as well as registers (reg).

New section explaining wire and assign coming shortly.

PWM

Next we'll reuse our 256 level pulse width modulation design from part 2.

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

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

Controlling Brightness

To control the brightness of an LED we use the debounce module with btn[0] and btn[1] combined with pulse width modulation on led[0].

Add a design source called top.v with the following code:

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

    reg [7:0] duty_led = 8'b00001111;

    wire btn0_state, btn0_dn, btn0_up;
    debounce d_btn0 (
        .clk(CLK),
        .i_btn(btn[0]),
        .o_state(btn0_state),
        .o_ondn(btn0_dn),
        .o_onup(btn0_up)
    );

    wire btn1_state, btn1_dn, btn1_up;
    debounce d_btn1 (
        .clk(CLK),
        .i_btn(btn[1]),
        .o_state(btn1_state),
        .o_ondn(btn1_dn),
        .o_onup(btn1_up)
    );

    always @ (posedge CLK) 
    begin
        if (btn0_dn)
        begin
            duty_led <= duty_led * 2 + 1;
        end

        if (btn1_dn)
        begin
            duty_led <= duty_led >> 1;
        end
    end

    pwm pwm_led (
        .clk(CLK),
        .i_duty(duty_led),
        .o_state(led[0])
    );
endmodule

Constraints

As usual, we need a constraints file for relevant inputs and outputs on the board.

Create a new constraints file 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]}];

## Buttons
set_property -dict {PACKAGE_PIN D9  IOSTANDARD LVCMOS33} [get_ports {btn[0]}]; 
set_property -dict {PACKAGE_PIN C9  IOSTANDARD LVCMOS33} [get_ports {btn[1]}];
set_property -dict {PACKAGE_PIN B9  IOSTANDARD LVCMOS33} [get_ports {btn[2]}];
set_property -dict {PACKAGE_PIN B8  IOSTANDARD LVCMOS33} [get_ports {btn[3]}];

Buttons on Board

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

On your Arty board use BTN0 and BTN1 to control the brightness of LED LD4. It has eight steps from off to maximum brightness. The LED starts with a duty cycle of 8'b00001111 (15/255).

  • When you press BTN0 the value is multiplied by 2 and has 1 added, becoming 8'b00011111 (31), and so on up to 8'b11111111 (255).
  • When you press BTN1 the output is right-shifted 1 bit, so 8'b00001111 (15) becomes 8'b00000111 (7), and so on down to 8'b00000000 (0).

The brightness control shows two different ways to perform maths using Verilog: multiplication/addition and bit shifting.

Controlling Four LEDs at Once

We can use a generate loop to apply the same PWM and duty cycle to many LEDs. Vivado will use the generate loop to synthesise multiple pwm instances.

Replace the pwm pwm_led instance at the bottom of top.v with:

genvar i;
generate 
    for (i = 0; i < 4; i=i+1) begin: pwm_loop
        pwm pwm_led (
            .clk(CLK),
            .i_duty(duty_led),
            .o_state(led[i])
        );
    end
endgenerate

When you program your board with this new design, you should find buttons can control all four green LEDs. Functionality wise this is the same as writing out four pwm instances yourself but is cleaner and less error-prone.

Look out for the next part of this tutorial series in late January 2018. In the meantime try FPGA graphics with your Arty board.

©2017-18 Will Green.