HomeFPGA from ScratchDay 8
DAY 8 · FIRST REAL PROJECT

Project: Blink an LED on FPGA

By EcrioniX · Updated Jun 11, 2026

The "Hello World" of FPGA design. Every FPGA engineer has blinked an LED — it proves your toolchain works, your constraints file is correct, your board is alive, and your design compiles and runs. Simple to understand, surprisingly deep to explain correctly. Let's build it properly.

1. The problem: clocks are fast, human eyes are slow

Your FPGA board runs at 100 MHz — 100 million clock cycles per second. An LED toggling every cycle would appear as constant light to the human eye. You need to divide the clock down to about 1 Hz — one toggle per second.

The maths: 100,000,000 cycles/sec ÷ 2 toggles/blink = 50,000,000 cycles per half-period. Count to 50 million, toggle the LED, reset the counter, repeat.

⚡ Bit width

50,000,000 in binary needs 26 bits (2²⁶ = 67M). So a 27-bit counter is the minimum. In the module we use a parameter so you can change the frequency easily.

2. Port table

PortDirWidthMeaning
clkinput1system clock (100 MHz on most dev boards)
rstinput1synchronous reset — ties to a button on the board
ledoutput1drive high/low to blink the LED

3. blink.v — the design

blink.v — LED blinker, parameterized
// LED blinker: toggles LED every HALF_PERIOD clock cycles
// Default: 50_000_000 cycles = 0.5s at 100 MHz -> 1 Hz blink
module blink #(parameter HALF_PERIOD = 50_000_000) (
    input  wire clk,
    input  wire rst,
    output reg  led
);
    // Counter needs ceil(log2(HALF_PERIOD)) bits.
    // 27 bits handles up to 134M cycles safely.
    reg [26:0] cnt;

    always @(posedge clk) begin
        if (rst) begin
            cnt <= 27'd0;
            led <= 1'b0;
        end else if (cnt == HALF_PERIOD - 1) begin
            cnt <= 27'd0;
            led <= ~led;   // toggle LED every half period
        end else begin
            cnt <= cnt + 1;
        end
    end
endmodule

4. Constraints file (Xilinx Basys3 example)

blink.xdc — Xilinx Basys3 constraints
# 100 MHz system clock on Basys3
set_property PACKAGE_PIN W5 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
create_clock -add -name sys_clk -period 10.00 -waveform {0 5} [get_ports clk]

# Active-low reset button (BTNC)
set_property PACKAGE_PIN U18 [get_ports rst]
set_property IOSTANDARD LVCMOS33 [get_ports rst]

# LED0
set_property PACKAGE_PIN U16 [get_ports led]
set_property IOSTANDARD LVCMOS33 [get_ports led]

5. Testbench — verify in simulation

We use a tiny HALF_PERIOD=5 to make the simulation fast:

tb_blink.v — testbench
`timescale 1ns/1ps
module tb_blink;
    reg  clk=0, rst=1;
    wire led;
    // Small HALF_PERIOD=5 so sim finishes quickly
    blink #(.HALF_PERIOD(5)) dut(.clk(clk),.rst(rst),.led(led));
    always #5 clk=~clk;
    integer errors=0;
    initial begin
        @(posedge clk); rst=0; // release reset
        // After 5 cycles LED should toggle (0->1)
        repeat(5) @(posedge clk); #1;
        if(led!==1'b1)begin $display("FAIL: led should be 1 after 5 cycles");errors=errors+1;end
        else $display("ok   led=1 after first half-period");
        // After another 5 cycles LED toggles again (1->0)
        repeat(5) @(posedge clk); #1;
        if(led!==1'b0)begin $display("FAIL: led should be 0 after 10 cycles");errors=errors+1;end
        else $display("ok   led=0 after second half-period");
        // Third toggle
        repeat(5) @(posedge clk); #1;
        if(led!==1'b1)begin $display("FAIL: led should be 1 after 15 cycles");errors=errors+1;end
        else $display("ok   led=1 after third half-period");
        if(errors==0) $display("ALL TESTS PASSED"); else $display("%0d FAILED",errors);
        $finish;
    end
endmodule
expected output
ok   led=1 after first half-period
ok   led=0 after second half-period
ok   led=1 after third half-period
ALL TESTS PASSED

🎯 Day 8 takeaways

FAQ

How do you blink an LED at 1 Hz on a 100 MHz FPGA?

Count to 50,000,000 cycles (0.5 s), toggle the LED, reset and repeat. Needs a 27-bit counter.

What is a constraints file?

A file (.xdc for Xilinx, .sdc/.qsf for Intel) that maps Verilog port names to physical FPGA pins and sets timing requirements like clock period.

Previous
← Day 7: Sequential logic & clocks

← Full roadmap