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.
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.
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.
| Port | Dir | Width | Meaning |
|---|---|---|---|
| clk | input | 1 | system clock (100 MHz on most dev boards) |
| rst | input | 1 | synchronous reset — ties to a button on the board |
| led | output | 1 | drive high/low to blink the LED |
// 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# 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]We use a tiny HALF_PERIOD=5 to make the simulation fast:
`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
endmoduleok led=1 after first half-period ok led=0 after second half-period ok led=1 after third half-period ALL TESTS PASSED
Count to 50,000,000 cycles (0.5 s), toggle the LED, reset and repeat. Needs a 27-bit counter.
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.