You dont have javascript enabled! Please enable it!

Practical case: Basys3 LED Blink Vivado XDC Constraints

Practical case: Basys3 LED Blink Vivado XDC Constraints — hero

Objective and use case

What you’ll build: A simple LED blinker on the Basys 3 FPGA using a clock divider to convert the 100 MHz clock into a visible blink on LED0.

Why it matters / Use cases

  • Demonstrates the fundamentals of FPGA programming and hardware design.
  • Provides a practical introduction to using Verilog for hardware description.
  • Illustrates the importance of clock management in digital circuits.
  • Serves as a foundation for more complex projects involving sensors and communication protocols.

Expected outcome

  • LED0 blinks at a visible rate, confirming successful clock division.
  • Completion of the project within 1 hour, demonstrating efficiency.
  • Validation of design through simulation and programming on the Basys 3 board.
  • Understanding of XDC constraints and their role in FPGA design.

Audience: Beginners in FPGA programming; Level: Introductory

Architecture/flow: One Verilog source file, one XDC constraint file, and two Tcl scripts for programming.

Practical Case: LED Blink with Clock Divider on Basys 3 (Xilinx Artix-7)

This beginner-friendly, hands-on exercise guides you through building a simple LED blinker on the Basys 3 (Xilinx Artix-7) FPGA board. You will implement a clock divider that turns the 100 MHz onboard clock into a human-visible blink on LED0, then build, program, and validate the design using the Vivado WebPACK command-line tools.

The flow is entirely non-GUI, reproducible, and minimal: one Verilog source file, one constraint file (XDC), and two short Tcl scripts. Expect to complete the entire project in under an hour.


Prerequisites

  • Operating system:
  • Linux (Ubuntu 20.04/22.04 or similar) or Windows 10/11
  • Xilinx Vivado WebPACK installed with Artix-7 device support:
  • Version: Vivado WebPACK 2023.2
  • Confirm installation:
    • Linux: /opt/Xilinx/Vivado/2023.2/bin/vivado
    • Windows: C:\Xilinx\Vivado\2023.2\bin\vivado.bat
  • USB drivers for Digilent USB-JTAG:
  • Windows: Installed during Vivado setup (Digilent Cable Drivers)
  • Linux: Install cable drivers from Vivado package (see Troubleshooting)
  • One available USB port
  • Basic familiarity with a shell or Command Prompt

Verify Vivado is accessible:
– Linux:
/opt/Xilinx/Vivado/2023.2/bin/vivado -version
– Windows (PowerShell):
& "C:\Xilinx\Vivado\2023.2\bin\vivado.bat" -version

Expected output includes “Vivado v2023.2 (64-bit) …” and the copyright line.


Materials (with exact model)

  • FPGA board: Basys 3 (Xilinx Artix-7), Digilent part number: 410-183
  • FPGA device: xc7a35t-1cpg236 (Vivado part code: xc7a35tcpg236-1)
  • Onboard clock: 100 MHz system oscillator
  • Micro-USB cable (data-capable), connected to the Basys 3 “PROG/UART” micro-USB port
  • Host PC with Vivado WebPACK 2023.2

Setup/Connection

  1. Power off the Basys 3 (slide the power switch to OFF).
  2. Connect the micro-USB cable from the PC to the Basys 3 “PROG/UART” micro-USB connector.
  3. Switch Basys 3 power to ON. The board should power from USB; the power LED (ON) should light.
  4. Ensure programming mode is set to JTAG (default on Basys 3; no extra jumpers required for typical use).
  5. Leave all onboard switches in their default positions; this project only uses the 100 MHz clock input and LED0.

No external jumper wires are used in this project; all resources are on-board.


Pin Mapping and Constraints Overview

We will use only two FPGA pins: the 100 MHz clock and LED0. The mapping below is specific to Basys 3.

Signal (Top Port) Resource (Basys 3) Package Pin I/O Standard Notes
CLK100MHZ 100 MHz clock W5 LVCMOS33 Primary system clock
LED0 LED LD0 U16 LVCMOS33 Active-high user LED

We will constrain these in an XDC file and define the 100 MHz timing constraint (10.000 ns period).


Full Code

We’ll create a minimal project with this directory structure (you can place it anywhere; the example uses a Linux home directory):

  • ~/fpga/basys3_led_blink/
  • src/top.v
  • constr/basys3_led_blink.xdc
  • tcl/build.tcl
  • tcl/program.tcl

Create the directories, then add the following files.

1) Verilog top-level (src/top.v)

This module divides the 100 MHz clock to make LED0 toggle every 0.5 seconds (i.e., LED blinks at 1 Hz: 0.5 s on, 0.5 s off). A 26-bit counter is sufficient to count up to 50,000,000−1.

// File: src/top.v
// Target: Basys 3 (Xilinx Artix-7)
// Function: Blink LED0 at ~1 Hz by dividing 100 MHz clock.

module top (
    input  wire CLK100MHZ, // 100 MHz onboard clock (pin W5)
    output reg  LED0       // LED LD0 (pin U16), active-high
);

    // Divide 100 MHz to 1 Hz blink:
    // We want LED0 to toggle every 0.5 seconds => 0.5 s * 100,000,000 = 50,000,000 cycles.
    localparam integer DIV_COUNT = 50_000_000; // half-period in clock cycles

    // 50,000,000 fits within 26 bits (2^26 = 67,108,864)
    reg [25:0] counter = 26'd0;

    // Initialize LED0 low (Xilinx allows register init at configuration time)
    initial LED0 = 1'b0;

    always @(posedge CLK100MHZ) begin
        if (counter == DIV_COUNT - 1) begin
            counter <= 26'd0;
            LED0    <= ~LED0; // toggle every 0.5 s (1 Hz blink)
        end else begin
            counter <= counter + 1'b1;
        end
    end

endmodule

Notes:
– This design uses synchronous logic only.
– No reset is required; the initial values are sufficient for this basic case.

2) Constraints (constr/basys3_led_blink.xdc)

Constrain the clock and LED pin for the Basys 3 and define the clock’s timing requirement. These settings are standard for LVCMOS33 on user I/O.

# Basys 3 (Xilinx Artix-7) constraints for LED blink

# Clock: 100 MHz system oscillator on pin W5
set_property PACKAGE_PIN W5 [get_ports {CLK100MHZ}]
set_property IOSTANDARD LVCMOS33 [get_ports {CLK100MHZ}]
create_clock -name sys_clk -period 10.000 [get_ports {CLK100MHZ}]

# LED0: LD0 on pin U16
set_property PACKAGE_PIN U16 [get_ports {LED0}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED0}]
set_property DRIVE 8       [get_ports {LED0}]
set_property SLEW SLOW     [get_ports {LED0}]

Build/Flash/Run Commands

We will use pure Vivado CLI (batch mode) with Tcl scripts for project creation, synthesis, implementation, bitstream generation, and programming.

Directory layout recap:
– src/top.v
– constr/basys3_led_blink.xdc
– tcl/build.tcl
– tcl/program.tcl

1) Build script (tcl/build.tcl)

This script creates a new project targeting the exact Artix-7 part on Basys 3, adds sources/constraints, runs synthesis and implementation, and writes the bitstream.

# File: tcl/build.tcl
# Vivado 2023.2 batch build for Basys 3 LED blink

# Set a reproducible project directory (adjust as needed)
set proj_dir [file normalize [file join [pwd] ".."]]
set proj_name "basys3_led_blink"
set proj_path [file join $proj_dir $proj_name]

# Clean prior runs if desired (optional)
if { [file exists $proj_path] } {
    puts "INFO: Project directory already exists: $proj_path"
}

# Create project
create_project $proj_name $proj_path -part xc7a35tcpg236-1 -force

# Add design sources and constraints
add_files -fileset sources_1 [file normalize [file join $proj_dir "src" "top.v"]]
add_files -fileset constrs_1 [file normalize [file join $proj_dir "constr" "basys3_led_blink.xdc"]]

# Ensure correct compilation order
update_compile_order -fileset sources_1

# Elaborate (optional check)
# synth_design will elaborate and synthesize automatically; we proceed.

# Run synthesis and implementation to bitstream
launch_runs synth_1 -jobs 4
wait_on_run synth_1

launch_runs impl_1 -to_step write_bitstream -jobs 4
wait_on_run impl_1

# Write useful reports (timing/utilization)
open_run impl_1
report_timing_summary -file [file join $proj_path "timing_summary_impl.txt"] -delay_type max -max_paths 10
report_utilization     -file [file join $proj_path "utilization_impl.txt"]

# Print bitstream location
set bitfile [file join $proj_path "$proj_name.runs" "impl_1" "top.bit"]
if { [file exists $bitfile] } {
    puts "INFO: Bitstream ready: $bitfile"
} else {
    puts "ERROR: Bitstream not found where expected."
    exit 1
}
exit

2) Programming script (tcl/program.tcl)

This script connects to the local hardware server, detects the Basys 3 over USB-JTAG, and programs the bitstream.

# File: tcl/program.tcl
# Program Basys 3 over JTAG in Vivado 2023.2

# Locate the bitstream produced by build.tcl
set proj_dir [file normalize [file join [pwd] ".."]]
set proj_name "basys3_led_blink"
set bitfile [file join $proj_dir $proj_name "$proj_name.runs" "impl_1" "top.bit"]

if { ! [file exists $bitfile] } {
    puts "ERROR: Bitstream not found: $bitfile"
    exit 1
}

open_hw
connect_hw_server -url localhost:3121

# Open the local JTAG target
open_hw_target

# Select the first detected device (Basys 3: xc7a35t)
set devs [get_hw_devices]
if { [llength $devs] == 0 } {
    puts "ERROR: No hardware devices found. Check USB, power, drivers."
    exit 1
}
current_hw_device [lindex $devs 0]
refresh_hw_device -update_hw_probes false [current_hw_device]

# Program the FPGA
set_property PROGRAM.FILE $bitfile [current_hw_device]
program_hw_devices [current_hw_device]

puts "INFO: Programmed device with: $bitfile"
close_hw_target
disconnect_hw_server
close_hw
exit

3) Running the commands

Use the appropriate Vivado binary path for your OS. Adjust the working directory to inside the tcl folder.

  • Linux:
    cd ~/fpga/basys3_led_blink/tcl
    /opt/Xilinx/Vivado/2023.2/bin/vivado -mode batch -source build.tcl
    /opt/Xilinx/Vivado/2023.2/bin/vivado -mode batch -source program.tcl

  • Windows (PowerShell):
    cd $HOME\fpga\basys3_led_blink\tcl
    & "C:\Xilinx\Vivado\2023.2\bin\vivado.bat" -mode batch -source build.tcl
    & "C:\Xilinx\Vivado\2023.2\bin\vivado.bat" -mode batch -source program.tcl

If successful, LED0 (LD0) on the Basys 3 will blink at approximately 1 Hz.


Step‑by‑step Validation

  1. Visual check:
  2. Confirm Basys 3 is powered (ON LED lit).
  3. After programming completes, observe LED0 (the leftmost LED labeled LD0) blinking on and off at about once per second.

  4. Timing sanity check:

  5. The 100 MHz clock has a 10 ns period.
  6. The design counts 50,000,000 cycles before toggling LED0.
  7. 50,000,000 cycles × 10 ns = 0.5 s per toggle, so 1 full period (on+off) = 1.0 s.
  8. Use a stopwatch or phone timer to crudely verify: count 10 blinks; it should take ~10 seconds.

  9. Adjust blink rate for quick verification (optional test):

  10. Edit src/top.v: change DIV_COUNT to 5_000_000 (10× faster).
  11. Re-run build and program commands.
  12. LED should now blink at 10 Hz (faster flash). This confirms the divider is effective.

  13. Basic functional assertions:

  14. LED must be dark or lit at power-up but change state every 0.5 s after programming.
  15. If LED is steady or erratic, revisit constraints and ensure the correct clock pin W5 is used.

  16. Verify timing status:

  17. Open the generated timing report:
    • Linux: less ~/fpga/basys3_led_blink/basys3_led_blink/timing_summary_impl.txt
    • Look for “Design Timing Summary” and “All user specified timing constraints were met.”
  18. For this simple design, there should be no timing violations at 100 MHz.

Troubleshooting

  • Board not detected (no devices in program.tcl):
  • Check USB cable and try a different port.
  • Make sure the board is powered (switch ON).
  • Windows: Open Device Manager; “Digilent USB Device” should appear. Reinstall Digilent Cable Drivers via Vivado if needed.
  • Linux: Install cable drivers and replug:
    sudo /opt/Xilinx/Vivado/2023.2/data/xicom/cable_drivers/lin64/install_script/install_drivers
    Then re-run program.tcl.

  • Bitstream not found:

  • Ensure you ran build.tcl first.
  • Verify the path in program.tcl matches your project structure.
  • Check for typos in directory names (src, constr, tcl).

  • LED not blinking:

  • Confirm the LED pin (U16) and clock pin (W5) constraints match the Basys 3 board.
  • Open the utilization report to ensure I/O ports are correctly bound (LED0 and CLK100MHZ should be listed).
  • Verify the LED is not obscured or stuck by any other design (there is only one top.v).
  • If you edited the code, ensure the counter comparison is DIV_COUNT - 1 and the counter resets to 0 on match.

  • Wrong device selected:

  • The Basys 3 uses part xc7a35tcpg236-1. Ensure build.tcl uses -part xc7a35tcpg236-1.
  • Using a different part can cause constraint mismatches or implementation failures.

  • Timing warnings:

  • The design is simple; any timing failures likely indicate wrong clock constraint. Make sure create_clock -period 10.000 [get_ports {CLK100MHZ}] is in the .xdc.

  • Permission issues on Linux:

  • If Vivado cannot access the USB device, check user permissions or run vivado with sudo (not recommended generally). Prefer installing correct udev rules via the driver install script shown above.

  • Multiple Vivado versions installed:

  • Explicitly call Vivado 2023.2 binary path in commands to avoid PATH ambiguity.

Improvements

Once the basic LED blink works, consider the following incremental enhancements:

  1. Parameterized Divider:
  2. Replace the fixed DIV_COUNT with a parameter and allow selection via synthesis parameters or switches.
  3. For example, map two switches (SW0–SW1) to scale the divider for different blink rates.
  4. You can extend the constraints file to include those switches. Basys 3 slide switches are easily constrained if you add their pins (refer to Digilent’s Basys 3 master XDC).

  5. Wider LED Patterns:

  6. Drive multiple LEDs with different divider tap points (e.g., use bits of a free-running counter) to create a “binary counter” display across all 16 LEDs.
  7. This is a good way to check clock correctness and confirm that each LED pin is functioning.

  8. Reset/Enable Logic:

  9. Add a synchronous reset input (e.g., from BTNC) to clear the counter.
  10. Implement an enable signal to pause/resume blinking.

  11. Use Xilinx Clocking Wizard:

  12. Generate a derived clock (e.g., 1 MHz or 10 MHz) via MMCM/PLL using the Clocking Wizard IP, and then build your divider from that slowed clock for finer control.

  13. Integrate a Testbench and Run Simulation:

  14. Create a simple testbench that toggles CLK100MHZ at 100 MHz (10 ns period) for a few millisecond-equivalents and asserts LED0 toggles at the expected intervals.
  15. Use xsim for command-line simulation and waveform capture.

  16. Hardware Debugging with an ILA:

  17. For more advanced debugging, add an Integrated Logic Analyzer (ILA) core to observe the counter and LED toggle in hardware.

  18. Packaging:

  19. Create a single Tcl script that both builds and programs, accepting parameters for DIV_COUNT or output names, improving repeatability.

Final Checklist

Use this checklist to confirm you have completed each step correctly.

  • Prerequisites
  • Vivado WebPACK 2023.2 is installed and accessible from CLI.
  • Digilent USB-JTAG drivers installed (Windows) or cable drivers installed (Linux).
  • You can run vivado -version successfully.

  • Materials

  • Basys 3 (Xilinx Artix-7) connected via micro-USB to the PROG/UART port.
  • Board power switch set to ON.

  • Directory Structure

  • Created folders: src, constr, tcl in your project directory.
  • Files present:

    • src/top.v
    • constr/basys3_led_blink.xdc
    • tcl/build.tcl
    • tcl/program.tcl
  • Code and Constraints

  • top.v includes a 26-bit counter dividing the 100 MHz clock to generate a 1 Hz blink on LED0.
  • XDC file maps CLK100MHZ to W5 and LED0 to U16, with proper IOSTANDARD and create_clock.

  • Build

  • Ran build script:
    • Linux: /opt/Xilinx/Vivado/2023.2/bin/vivado -mode batch -source tcl/build.tcl
    • Windows: & "C:\Xilinx\Vivado\2023.2\bin\vivado.bat" -mode batch -source tcl/build.tcl
  • Bitstream generated without critical errors.
  • Timing summary shows constraints met.

  • Program

  • Ran program script:
    • Linux: /opt/Xilinx/Vivado/2023.2/bin/vivado -mode batch -source tcl/program.tcl
    • Windows: & "C:\Xilinx\Vivado\2023.2\bin\vivado.bat" -mode batch -source tcl/program.tcl
  • Device detected and programmed successfully.

  • Validation

  • LED0 blinks visibly at approximately 1 Hz.
  • Optional: adjusted divider and confirmed blink rate changes accordingly.

  • Troubleshooting (if needed)

  • Verified connections, drivers, correct part number, and accurate pin constraints.

If every item is checked, you have successfully completed a basic FPGA project on the Basys 3 (Xilinx Artix-7), using Vivado WebPACK CLI, achieving the objective “led-blink-with-clock-divider.” This foundation prepares you for more complex designs involving multiple I/Os, peripherals, and clocking strategies.

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 FPGA board is used in this practical case?




Question 3: What is the frequency of the onboard clock used in the project?




Question 4: Which version of Xilinx Vivado WebPACK is required?




Question 5: What type of cable is needed to connect to the Basys 3 board?




Question 6: Which operating systems are mentioned as prerequisites?




Question 7: What is the expected completion time for the entire project?




Question 8: What is the command to verify Vivado installation on Linux?




Question 9: Which device support is required for Xilinx Vivado WebPACK?




Question 10: What is the expected output of the LED blinker project?




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