You dont have javascript enabled! Please enable it!

Practical case: Icebreaker

Practical case: Icebreaker — hero

Objective and use case

What you’ll build: This project focuses on implementing button debouncing and controlling an RGB LED on the Lattice iCEBreaker (iCE40UP5K) FPGA. You will synthesize Verilog code to achieve reliable button input and LED control.

Why it matters / Use cases

  • Enhancing user interface reliability by preventing false toggles from mechanical button bounce.
  • Controlling visual indicators (RGB LED) based on user input, applicable in various embedded systems.
  • Demonstrating the integration of hardware design and software logic in FPGA applications.
  • Providing a foundational understanding of synchronous design principles in digital circuits.

Expected outcome

  • Achieve a debounce time of less than 20 milliseconds for button presses.
  • Successfully toggle the RGB LED state with each button press, verified by visual feedback.
  • Measure and report the latency of button response to be within 50 milliseconds.
  • Ensure the design can handle at least 1000 button presses without failure.

Audience: Beginners in FPGA development; Level: Basic

Architecture/flow: The flow includes synthesizing Verilog, placing and routing the design, and programming the iCEBreaker board.

Hands‑On Practical: Button Debouncing and LED Control on Lattice iCEBreaker (iCE40UP5K)

This basic‑level exercise walks you through building a reliable button input (debounced) that toggles an on‑board LED on the Lattice iCEBreaker (iCE40UP5K). You will synthesize Verilog with yosys, place-and-route with nextpnr‑ice40, and program the board with openFPGALoader. You’ll learn how to:

  • Synchronize an asynchronous button signal to the FPGA clock
  • Debounce the button so it doesn’t produce false toggles due to mechanical bounce
  • Drive the on‑board RGB LED (active‑low) with clean logic
  • Build, flash, validate, and troubleshoot the design end‑to‑end

The instructions below are precise and command‑line oriented, designed to be reproducible.


Prerequisites

  • Ability to use a Unix‑like shell (Linux or macOS) or Windows with WSL2
  • Basic Verilog familiarity (modules, always blocks, regs, wires)
  • Basic CLI familiarity (running commands, navigating directories)

Toolchain (tested versions):
– Yosys 0.41+ (synthesizer)
– nextpnr‑ice40 0.7+ (place & route)
– Icestorm (icepack) 0.0‑2024xx (bitstream packing)
– openFPGALoader v0.12.0+ (programming the FPGA)

You can check what you have installed (examples shown for Debian/Ubuntu‑like systems):

yosys -V
nextpnr-ice40 --version
icepack -v
openFPGALoader --version

If you need to install the tools (Debian/Ubuntu):

sudo apt update
sudo apt install yosys nextpnr-ice40 icestorm openfpgaloader

Note: Package versions may differ by distribution. The commands in this guide assume the standard tool names as installed above.


Materials

Exact device model:
– Board: Lattice iCEBreaker (iCE40UP5K‑SG48)

Other materials:
– USB‑C cable for power/data (follow your board’s USB connector type; many iCEBreakers use USB‑C)
– Optional: 1x momentary pushbutton and 2x jumper wires (for an external button via PMOD if desired)
– Optional: Breadboard (only if you choose an external button rather than the on‑board button)

We will use:
– On‑board 12 MHz oscillator
– On‑board RGB LED (active‑low channels)
– One debounced button input (you may use the on‑board user button or wire an external one to a PMOD pin with internal pull‑up enabled)


Setup/Connection

The project targets the on‑board RGB LED and a single button input. The RGB LED channels are typically wired active‑low (drive low to turn on). We will use only one LED channel (Red) in this exercise.

We’ll map pins as follows (common iCEBreaker usage):

Function Net name in RTL FPGA pin (SG48) Notes
12 MHz clock clk_12m 35 On‑board oscillator
User button (active‑low) btn_n 10 Use pull‑up; low when pressed
RGB LED Red (active‑low) led_r_n 39 Drive low to turn on
RGB LED Green (active‑low) led_g_n 41 Unused here
RGB LED Blue (active‑low) led_b_n 40 Unused here

Notes:
– If you use the on‑board user button: it is typically wired with a pull‑up; pressing the button drives the input low. We will also enable an internal pull‑up in the constraint file for safety.
– If you prefer an external button via a PMOD: tie one side of the pushbutton to a chosen IO pin (we will still use pin 10 for consistency), and tie the other side to GND. Keep the internal pull‑up enabled so the line is high when not pressed.

Physical connections (text description, no drawings):
– Ensure the USB cable connects the iCEBreaker to your PC.
– To use the on‑board button, no additional wiring is required.
– To use an external button instead:
– Connect one terminal of the pushbutton to the board pad that routes to FPGA pin 10 (refer to the iCEBreaker pinout silkscreen or documentation; many users route this via a PMOD if they want an external switch).
– Connect the other pushbutton terminal to GND on the same PMOD header.
– Do not add an external pull‑up; we’ll enable the internal pull‑up in the PCF.


Full Code

We will implement a synchronizer + debouncer module and a top module that toggles the LED on the debounced rising edge of the button press. The design uses a counter‑based debouncer with a saturating counter sized for ~20–22 ms debounce at 12 MHz.

Project layout (you’ll create these files shortly):
– ~/projects/icebreaker_debounce_led/src/debouncer.v
– ~/projects/icebreaker_debounce_led/src/top.v
– ~/projects/icebreaker_debounce_led/constr/icebreaker.pcf

debouncer.v

This module:
– Synchronizes the asynchronous input to the clock (two‑flip‑flop synchronizer).
– Uses a saturating counter to require the input to be stable for a period before updating the debounced state.
– Provides both the debounced level and a one‑clock pulse on the rising edge (useful for toggle actions).

/*
 * debouncer.v
 * Parameterizable synchronizing debouncer for mechanical switches
 * Assumes 'in_n' is active-low (0 when pressed) for convenience in this design.
 */
module debouncer #(
    parameter integer CNTR_BITS = 18  // 2^18 @ 12 MHz ~= 21.8 ms of stability
)(
    input  wire clk,     // system clock
    input  wire in_n,    // raw asynchronous input, active-low (0 when pressed)
    output reg  level,   // debounced active-high level (1 when pressed)
    output reg  rise     // 1-cycle pulse on rising edge of debounced level
);
    // 2-stage synchronizer for metastability protection
    reg sync0, sync1;
    always @(posedge clk) begin
        sync0 <= ~in_n;   // convert to active-high (1 when pressed)
        sync1 <= sync0;
    end

    // Saturating counter-based debounce
    reg [CNTR_BITS-1:0] cnt = {CNTR_BITS{1'b0}};
    reg stable = 1'b0;   // debounced stable state (active-high)

    always @(posedge clk) begin
        if (sync1 == stable) begin
            // Input matches stable state; clear the counter
            cnt <= {CNTR_BITS{1'b0}};
        end else begin
            // Input differs; increment counter
            cnt <= cnt + 1'b1;
            // When counter saturates, accept new state
            if (&cnt) begin
                stable <= sync1;
                cnt    <= {CNTR_BITS{1'b0}};
            end
        end
    end

    // Output registers for level and rising-edge pulse
    reg stable_d = 1'b0;
    always @(posedge clk) begin
        stable_d <= stable;
        level    <= stable;
        rise     <= (stable & ~stable_d);  // rising edge detect
    end
endmodule

top.v

The top-level module connects the clock, button, and LED pins. It toggles the red LED each time a debounced press is detected. Because the RGB LED channels are active‑low, the LED output is inverted when driving the pin.

/*
 * top.v
 * Button debouncing and LED toggle on the Lattice iCEBreaker (iCE40UP5K)
 * Clock: 12 MHz on pin 35
 * Button: active-low on pin 10
 * RGB LED: active-low (using Red on pin 39)
 */
module top (
    input  wire clk_12m, // 12 MHz oscillator
    input  wire btn_n,   // raw button (active-low)
    output wire led_r_n, // RGB red channel (active-low)
    output wire led_g_n, // RGB green channel (active-low, unused)
    output wire led_b_n  // RGB blue channel (active-low, unused)
);

    // Instantiate debouncer with ~21.8 ms stability at 12 MHz (2^18 cycles)
    wire btn_level;  // debounced level (1 when pressed)
    wire btn_rise;   // 1-cycle pulse on press

    debouncer #(
        .CNTR_BITS(18)
    ) u_debouncer (
        .clk   (clk_12m),
        .in_n  (btn_n),
        .level (btn_level),
        .rise  (btn_rise)
    );

    // LED state toggles on each debounced rising edge (press event)
    reg led_state = 1'b0;
    always @(posedge clk_12m) begin
        if (btn_rise) begin
            led_state <= ~led_state;
        end
    end

    // Active-low LED pins: drive low to turn on, high to turn off
    assign led_r_n = ~led_state; // red LED shows the toggling state
    assign led_g_n = 1'b1;       // keep green off
    assign led_b_n = 1'b1;       // keep blue off

endmodule

Constraint file: icebreaker.pcf

The PCF (Physical Constraints File) maps logical port names to package pins and sets pull‑ups for the button input.

# iCEBreaker (iCE40UP5K-SG48) pinout for this project

# Clock input (12 MHz)
set_io clk_12m 35

# Button input (active-low), enable internal pull-up
set_io -pullup yes btn_n 10

# RGB LED pins (active-low)
set_io led_r_n 39
set_io led_g_n 41
set_io led_b_n 40

Build/Flash/Run Commands

Create the project structure and files. The following commands assume a Linux/macOS shell. Adjust paths if needed.

1) Create directories:

mkdir -p ~/projects/icebreaker_debounce_led/src
mkdir -p ~/projects/icebreaker_debounce_led/constr
mkdir -p ~/projects/icebreaker_debounce_led/build

2) Save the Verilog files and PCF:
– Save the debouncer.v contents to:
~/projects/icebreaker_debounce_led/src/debouncer.v
– Save the top.v contents to:
~/projects/icebreaker_debounce_led/src/top.v
– Save the icebreaker.pcf contents to:
~/projects/icebreaker_debounce_led/constr/icebreaker.pcf

3) Synthesize with yosys:

cd ~/projects/icebreaker_debounce_led
yosys -p "read_verilog -sv src/debouncer.v src/top.v; synth_ice40 -top top -json build/top.json"

4) Place & route with nextpnr‑ice40 (targeting UP5K SG48):

nextpnr-ice40 --up5k --package sg48 \
  --json build/top.json \
  --pcf constr/icebreaker.pcf \
  --asc build/top.asc \
  --freq 12

Notes:
– –freq 12 requests a 12 MHz timing target; nextpnr will report timing slack.
– If timing slightly misses, this design is simple and will still meet 12 MHz comfortably on UP5K; investigate if it doesn’t.

5) Pack the bitstream with icepack:

icepack build/top.asc build/top.bin

6) Program the board with openFPGALoader:

openFPGALoader -b icebreaker build/top.bin

If you prefer icestorm’s iceprog (FTDI) and your iCEBreaker supports it:

iceprog build/top.bin

7) Power-cycling is not required; the bitstream configures immediately.


Step‑by‑Step Validation

Follow these steps to confirm the button is debounced and the LED behaves predictably.

1) Power and initial state
– Plug in the iCEBreaker via USB.
– Program the bitstream as shown above. The red LED should be off initially (since led_state = 0 maps to led_r_n = 1, which is off for active‑low).

2) Single deliberate press
– Press and release the button once, cleanly.
– Expected: The red LED turns on and remains on after you release.

3) Second deliberate press
– Press and release again.
– Expected: The red LED turns off and remains off after you release.

4) Fast tapping (bounce stress test)
– Rapidly tap the button with short presses (attempt to produce bouncy transitions).
– Expected: The LED only toggles once per genuine press. If you try to “hover” at the contact point and produce chatter, the debouncer should ignore the rapid toggling and only accept a new state after ~22 ms of stable input.

5) Long press
– Hold the button down for 1–2 seconds.
– Expected: The LED toggles once upon the press, then holds its state as long as the button is pressed, and does not toggle again until you release and press again.

6) Observe lack of flicker
– Look closely at the LED during press and release.
– Expected: No visible flicker or multiple toggles during a single press due to mechanical bounce. The counter‑based debouncer suppresses transitions until the input stabilizes.

7) Optional logic probe or scope (if available)
– Probe the raw button pin and the LED pin.
– Expected: Raw pin may show multiple short pulses during a press; the LED pin changes state only once per clean press, with approximately ≥ CNTR_BITS worth of cycles of stability required before acceptance.

8) Verify timing report
– Review nextpnr stdout for timing. With such a small design, it should meet the 12 MHz target with positive slack.


Troubleshooting

  • The LED never turns on, regardless of button presses
  • Check polarity: The RGB LED is active‑low. In top.v, led_r_n is assigned ~led_state. If your board’s LED is wired differently, invert accordingly.
  • Make sure the LED pin mapping in the PCF matches your iCEBreaker revision. In this guide, we assume Red=39, Green=41, Blue=40.
  • Ensure you programmed the board successfully (openFPGALoader reported success with no errors).

  • The LED is inverted (on at power‑up and off when pressed)

  • Your LED channel might be wired differently. Swap the inversion: assign led_r_n = led_state; in top.v, or confirm the LED is indeed active‑low as expected.

  • The button appears stuck (always pressed)

  • Verify the pull‑up: The PCF sets -pullup yes on the button pin. If you are using an external button, ensure the button is between the pin and GND (not 3V3).
  • If wired externally, ensure there is no short to GND.

  • The button input seems floating or very noisy

  • Confirm the PCF is actually used by nextpnr (check the logs; pin 10 should be assigned).
  • Check that the constraint file path is correct in the nextpnr command line: –pcf constr/icebreaker.pcf
  • Verify no conflicting constraints or duplicate set_io lines.

  • Build failures

  • Yosys “unknown module” errors: Ensure both src/debouncer.v and src/top.v are included in the read_verilog invocation in the same order as given.
  • nextpnr “no matching IO” or “constraint error”: Confirm the pin names (e.g., pin 35 exists on UP5K SG48 for the clock).
  • “Device not found” on programming: Confirm the -b icebreaker target in openFPGALoader is correct. Try running openFPGALoader -l to list devices.

  • Timing misses

  • This simple design should not fail 12 MHz timing on UP5K. If you see failures:

    • Rebuild from a clean directory (rm -rf build && mkdir build).
    • Ensure you didn’t accidentally constrain the clock to an unrealistic frequency (e.g., –freq 120).
    • Ignore minor negative slack if your empirical testing is stable; however, it’s better to meet timing cleanly.
  • Multiple LEDs flicker or glow

  • Ensure unused channels (green/blue) are driven high (off for active‑low) explicitly in top.v. This guide sets led_g_n = 1’b1 and led_b_n = 1’b1.

Improvements

Once the base design works, extend it thoughtfully.

  • Parameterize by time rather than raw bits
  • Replace CNTR_BITS with a computed value from desired milliseconds and clock frequency. For example, use $clog2 in SystemVerilog to compute the number of bits needed for a target debounce time:
    • Required cycles = ceil(T_ms/1000 * Fclk_Hz)
    • CNTR_BITS = ceil(log2(Required cycles))
  • Note: If using pure Verilog‑2001, you’ll pass CNTR_BITS as a parameter and compute offline.

  • Edge selection options

  • Detect both rising and falling edges for richer logic (e.g., toggle on press, do something else on release).
  • Provide a “press and hold” repeat function by adding a repeat timer once pressed remains stable for N milliseconds.

  • Multiple buttons

  • Instantiate debouncer per input (buttons, encoders). Keep each independent to improve robustness.

  • LED patterns

  • Use the other RGB channels for status (e.g., flash green on every valid press, blue for error).

  • Glitch filtering and metastability hardening

  • The current design uses a 2‑FF synchronizer. For very noisy environments, consider 3‑FF, although this will slightly increase latency.

  • Clock enable approach

  • Instead of a counter on the input, create a clock enable at ~1 kHz and sample the synchronized input only on enable pulses. This reduces logic usage at the cost of a more complex clock‑enable network.

  • Formal verification or testbenches

  • Add a small simulation to show that bouncing inputs don’t cause extra toggles. For example, a testbench that drives bursts of toggles around edges.

Final Checklist

  • Prerequisites
  • Yosys, nextpnr‑ice40, icestorm, openFPGALoader installed and versions verified
  • iCEBreaker (iCE40UP5K‑SG48) connected via USB

  • Materials

  • iCEBreaker board
  • Optional external pushbutton and jumpers (if not using on‑board button)

  • Setup/Connection

  • Confirmed pin mapping:
    • clk_12m → pin 35
    • btn_n → pin 10 (pull‑up enabled)
    • led_r_n → pin 39 (active‑low)
    • led_g_n → pin 41 (active‑low, unused)
    • led_b_n → pin 40 (active‑low, unused)
  • External button wired to pin 10 and GND if used; on‑board button otherwise

  • Files

  • src/debouncer.v (counter‑based debouncer with synchronizer)
  • src/top.v (LED toggle on btn_rise)
  • constr/icebreaker.pcf (pin assignments and pull‑up)

  • Build

  • Synthesis:
    • yosys -p «read_verilog -sv src/debouncer.v src/top.v; synth_ice40 -top top -json build/top.json»
  • Place & route:
    • nextpnr-ice40 –up5k –package sg48 –json build/top.json –pcf constr/icebreaker.pcf –asc build/top.asc –freq 12
  • Bitstream pack:

    • icepack build/top.asc build/top.bin
  • Flash

  • openFPGALoader -b icebreaker build/top.bin
  • Alternative: iceprog build/top.bin

  • Validation

  • LED toggles once per deliberate press
  • No visible flicker or multiple toggles per press
  • Holds state during long press
  • Rapid tapping does not cause extra toggles

  • Troubleshooting

  • Check polarity, pin mappings, pull‑ups, tool logs
  • Confirm device detection and programming success

By following this practical, you have a robust button interface on the Lattice iCEBreaker using a proper synchronizer and a counter‑based debouncer. This pattern scales well to multiple inputs and provides a solid foundation for more interactive FPGA designs.

Find this product and/or books on this topic on Amazon

Go to Amazon

As an Amazon Associate, I earn from qualifying purchases. If you buy through this link, you help keep this project running.

Quick Quiz

Question 1: What is the main purpose of the exercise described in the article?




Question 2: Which tool is used for synthesis in this exercise?




Question 3: What is the function of debouncing in this context?




Question 4: Which programming language is primarily used in this exercise?




Question 5: What is the active state of the on-board RGB LED mentioned?




Question 6: What is the first step to check the installed tools on a system?




Question 7: Which version of openFPGALoader is required at minimum?




Question 8: What type of shell environment is needed to complete the exercise?




Question 9: What is the purpose of the USB-C cable mentioned in the materials?




Question 10: What is the primary function of nextpnr-ice40 in this exercise?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Telecommunications Electronics Engineer and Computer Engineer (official degrees in Spain).

Follow me:
Scroll to Top