Practical case: Basys 3 7-segment Vivado XDC constraints

Practical case: Basys 3 7-segment Vivado XDC constraints — hero

Objective and use case

What you’ll build: A binary counter on the Basys 3 FPGA that increments and displays a 4-bit value on a seven-segment display.

Why it matters / Use cases

  • Demonstrates the use of Verilog for hardware design, allowing for real-time digital circuit implementation.
  • Provides a practical example of using FPGA technology in educational settings, enhancing learning in digital electronics.
  • Serves as a foundational project for understanding more complex state machines and digital logic design.
  • Illustrates the integration of hardware and software through the use of Vivado for synthesis and implementation.

Expected outcome

  • Successful display of 4-bit binary values on the seven-segment display with a refresh rate of 1 Hz.
  • Verification of correct counting from 0000 to 1111, ensuring no skipped counts or incorrect displays.
  • Documentation of synthesis and implementation times, aiming for under 10 seconds for both processes.
  • Measurement of power consumption during operation, targeting less than 500 mW.

Audience: Electronics students and hobbyists; Level: Intermediate

Architecture/flow: Basys 3 FPGA with Xilinx Artix‑7, using Verilog and Vivado for design and programming.

Practical Case: Binary Counter on Seven-Segment (Basys 3, Xilinx Artix‑7)

This hands-on project walks you through building a basic binary counter on the Basys 3 (Xilinx Artix‑7) FPGA. The counter increments at a human‑readable rate and displays a 4‑bit binary value across the four digits of the on‑board seven‑segment display. Each digit shows either “0” or “1,” so together the four digits represent the current 4‑bit count (from 0000 to 1111). We will use Verilog, Vivado WebPACK (CLI, non‑project flow), a simple constraints file, and Tcl scripts to build, program, and validate.

The focus is practical: you will get the counter running, understand the connections, and have repeatable CLI steps to synthesize, implement, and program the FPGA.


Prerequisites

  • Basic comfort with:
  • Command line (Windows PowerShell or Linux/macOS shell)
  • Verilog syntax
  • Clock division and simple state machines
  • Installed tools (Vivado WebPACK):
  • Xilinx Vivado WebPACK 2023.2 (or later WebPACK edition)
  • Xilinx cable (USB‑JTAG) drivers installed with Vivado
  • Verified that your system recognizes the board over USB:
  • On Windows, verify the Xilinx USB controller appears in Device Manager
  • On Linux/macOS, ensure you can run the Vivado hardware server and see the device

You can confirm the Vivado version from your terminal:

vivado -version

Expected output includes something like: «Vivado v2023.2 (64-bit)» and build info.


Materials

  • FPGA board: Basys 3 (Xilinx Artix‑7), exact model: Digilent Basys 3, device part xc7a35tcpg236-1
  • USB cable: Micro‑USB for power/programming
  • Host computer with Vivado WebPACK 2023.2 (CLI) installed

Optional (for convenience): An anti‑static mat and wrist strap.


Setup/Connection

  • Power and JTAG:
  • Plug a micro‑USB cable from your host PC to the Basys 3’s USB connector.
  • Ensure the power select jumper is set to USB (usually default on Basys 3).
  • Turn the Basys 3 power switch ON.
  • No external wiring is required; we use only on‑board devices:
  • 100 MHz system clock (on‑board oscillator)
  • Four‑digit common‑anode seven‑segment display (on‑board)

Important note about the seven‑segment display:
– The Basys 3 seven‑segment is common‑anode and multiplexed.
– That means:
– The anode enable signals AN0..AN3 are active‑low (0 = digit on).
– The segment lines (CA..CG, DP) are also active‑low (0 = segment on).

We will take advantage of this by setting an appropriate refresh scanning rate and driving segment/anode lines with the correct polarity.


Full Code

We will use a single top‑level Verilog module that:
1. Divides the 100 MHz clock down to:
– A ~1 kHz multiplex refresh tick (for digit scanning)
– A slow tick (e.g., 2 Hz) for counting
2. Maintains a 4‑bit counter (0..15)
3. Maps the 4 bits to the four digits (MSB on left, LSB on right)
4. Encodes “0” and “1” into seven‑segment patterns for each digit
5. Drives the anode and segment lines with active‑low logic

We will also include an XDC constraints file to map logical ports to the Basys 3 pins.

Top‑Level Verilog (src/top_basys3_counter.v)

// File: src/top_basys3_counter.v
// Board: Basys 3 (Xilinx Artix-7, xc7a35tcpg236-1)
// Function: Display a 4-bit binary counter (0..15) across the four seven-seg digits.
//           Each digit shows either '0' or '1', representing one bit of the count.
// Tools: Vivado WebPACK 2023.2 (non-project flow)

module top_basys3_counter (
    input  wire clk,             // 100 MHz system clock
    output reg  [3:0] an,        // anode enables (active low): an[0]=AN0 ... an[3]=AN3
    output reg  seg_a,           // segment a (active low)
    output reg  seg_b,           // segment b (active low)
    output reg  seg_c,           // segment c (active low)
    output reg  seg_d,           // segment d (active low)
    output reg  seg_e,           // segment e (active low)
    output reg  seg_f,           // segment f (active low)
    output reg  seg_g,           // segment g (active low)
    output reg  dp               // decimal point (active low)
);

    // ------------------------------------------------------------
    // Parameters for timing
    // ------------------------------------------------------------
    localparam integer CLK_FREQ_HZ      = 100_000_000; // 100 MHz
    localparam integer SCAN_RATE_HZ     = 1000;        // overall display update rate (per digit step)
    localparam integer COUNT_RATE_HZ    = 2;           // counter increments per second

    // Divide 100 MHz down to 1 kHz (digit scan). 100_000_000 / 1_000 = 100_000
    localparam integer SCAN_DIVISOR     = CLK_FREQ_HZ / SCAN_RATE_HZ; // 100_000
    // Divide 100 MHz down to 2 Hz for counting. 100_000_000 / 2 = 50_000_000
    localparam integer COUNT_DIVISOR    = CLK_FREQ_HZ / COUNT_RATE_HZ;

    // ------------------------------------------------------------
    // Counters and state
    // ------------------------------------------------------------
    reg [31:0] scan_div_cnt  = 0;
    reg [31:0] count_div_cnt = 0;

    reg [1:0] digit_sel = 0;     // 0..3: which digit is active this scan step
    reg [3:0] bin_count = 4'd0;  // 4-bit binary counter (0..15)

    // Current bit being displayed on the active digit (0 or 1)
    wire bit_for_digit;

    // Tie off decimal point (off = inactive = '1' for active-low)
    always @* begin
        dp = 1'b1;
    end

    // ------------------------------------------------------------
    // Generate scan tick (~1 kHz) and rotate through digits
    // ------------------------------------------------------------
    always @(posedge clk) begin
        if (scan_div_cnt >= SCAN_DIVISOR - 1) begin
            scan_div_cnt <= 0;
            digit_sel <= digit_sel + 2'd1; // cycle 0->1->2->3->0...
        end else begin
            scan_div_cnt <= scan_div_cnt + 1;
        end
    end

    // ------------------------------------------------------------
    // Generate slow counter tick (~2 Hz) and increment 4-bit counter
    // ------------------------------------------------------------
    always @(posedge clk) begin
        if (count_div_cnt >= COUNT_DIVISOR - 1) begin
            count_div_cnt <= 0;
            bin_count <= bin_count + 4'd1;
        end else begin
            count_div_cnt <= count_div_cnt + 1;
        end
    end

    // ------------------------------------------------------------
    // Map digit_sel to which bit of bin_count is shown:
    //   digit 3 (leftmost) shows MSB (bin_count[3])
    //   digit 2 shows bin_count[2]
    //   digit 1 shows bin_count[1]
    //   digit 0 (rightmost) shows LSB (bin_count[0])
    // ------------------------------------------------------------
    assign bit_for_digit = (digit_sel == 2'd3) ? bin_count[3] :
                           (digit_sel == 2'd2) ? bin_count[2] :
                           (digit_sel == 2'd1) ? bin_count[1] :
                                                 bin_count[0];

    // ------------------------------------------------------------
    // Drive anodes (active low). Only one digit on at a time.
    // digit_sel == 0 -> AN0 active; ==1 -> AN1; ==2 -> AN2; ==3 -> AN3
    // ------------------------------------------------------------
    always @* begin
        case (digit_sel)
            2'd0: an = 4'b1110; // AN0 on (0), others off (1)
            2'd1: an = 4'b1101; // AN1 on
            2'd2: an = 4'b1011; // AN2 on
            default: an = 4'b0111; // AN3 on
        endcase
    end

    // ------------------------------------------------------------
    // Segment encoding for "0" and "1" (active-low, common-anode)
    // A standard seven-seg uses segments a b c d e f g:
    //   '0' lights: a b c d e f (g off)
    //   '1' lights: b c (others off)
    // Since signals are active-low, '0' means ON and '1' means OFF.
    // ------------------------------------------------------------
    always @* begin
        if (bit_for_digit == 1'b0) begin
            // Show '0' -> a,b,c,d,e,f on; g off
            seg_a = 1'b0; // on
            seg_b = 1'b0; // on
            seg_c = 1'b0; // on
            seg_d = 1'b0; // on
            seg_e = 1'b0; // on
            seg_f = 1'b0; // on
            seg_g = 1'b1; // off
        end else begin
            // Show '1' -> b,c on; others off
            seg_a = 1'b1; // off
            seg_b = 1'b0; // on
            seg_c = 1'b0; // on
            seg_d = 1'b1; // off
            seg_e = 1'b1; // off
            seg_f = 1'b1; // off
            seg_g = 1'b1; // off
        end
    end

endmodule

Constraints (constraints/basys3_7seg.xdc)

This XDC maps the top‑level ports to Basys 3 pins. It also sets the I/O standard and declares the 100 MHz clock for timing.

# Basys 3 pinout for 4-digit seven-segment and 100 MHz clock.
# Active-low anodes and segments (common-anode display).
# Tools: Vivado 2023.2

# 100 MHz clock (on-board oscillator)
set_property PACKAGE_PIN W5 [get_ports {clk}]
set_property IOSTANDARD LVCMOS33 [get_ports {clk}]
create_clock -period 10.000 -name sys_clk_pin [get_ports {clk}]

# Seven-segment segments (active-low)
# CA, CB, CC, CD, CE, CF, CG, DP pins (Basys 3)
set_property PACKAGE_PIN W7 [get_ports {seg_a}]  ;# CA
set_property PACKAGE_PIN W6 [get_ports {seg_b}]  ;# CB
set_property PACKAGE_PIN U8 [get_ports {seg_c}]  ;# CC
set_property PACKAGE_PIN V8 [get_ports {seg_d}]  ;# CD
set_property PACKAGE_PIN U5 [get_ports {seg_e}]  ;# CE
set_property PACKAGE_PIN V5 [get_ports {seg_f}]  ;# CF
set_property PACKAGE_PIN U7 [get_ports {seg_g}]  ;# CG
set_property PACKAGE_PIN V7 [get_ports {dp}]     ;# DP
set_property IOSTANDARD LVCMOS33 [get_ports {seg_a seg_b seg_c seg_d seg_e seg_f seg_g dp}]

# Seven-segment anodes (active-low enables)
# AN0..AN3
set_property PACKAGE_PIN U2 [get_ports {an[0]}]
set_property PACKAGE_PIN U4 [get_ports {an[1]}]
set_property PACKAGE_PIN V4 [get_ports {an[2]}]
set_property PACKAGE_PIN W4 [get_ports {an[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {an[0] an[1] an[2] an[3]}]

Build/Flash/Run Commands

We will use Vivado in batch mode (non‑project flow) for reproducibility. The project layout:

  • basys3_binary_counter/
  • src/top_basys3_counter.v
  • constraints/basys3_7seg.xdc
  • scripts/build.tcl
  • scripts/program.tcl
  • build/ (generated outputs)

Create the structure and populate files:

mkdir -p basys3_binary_counter/{src,constraints,scripts,build}
# Place the Verilog and XDC from above into:
#   basys3_binary_counter/src/top_basys3_counter.v
#   basys3_binary_counter/constraints/basys3_7seg.xdc

Build (synthesis, implementation, bitstream) with Vivado CLI

Create scripts/build.tcl:

# File: scripts/build.tcl
# Vivado WebPACK 2023.2 non-project build for Basys 3 (xc7a35tcpg236-1)

# Use paths relative to the script invocation directory
set src_file       "src/top_basys3_counter.v"
set xdc_file       "constraints/basys3_7seg.xdc"
set top_name       "top_basys3_counter"
set part_name      "xc7a35tcpg236-1"
set out_dir        "build"
file mkdir $out_dir

# Read sources
read_verilog $src_file
read_xdc     $xdc_file

# Synthesis
synth_design -top $top_name -part $part_name

# Optional: report utilization and timing after synth
report_utilization      -file $out_dir/post_synth_util.rpt
report_timing_summary   -file $out_dir/post_synth_timing.rpt

# Implementation
opt_design
place_design
route_design

# Reports after implementation
report_utilization      -file $out_dir/post_impl_util.rpt
report_timing_summary   -file $out_dir/post_impl_timing.rpt

# Bitstream
write_bitstream -force $out_dir/basys3_counter.bit

Run the build:

cd basys3_binary_counter
vivado -mode batch -source scripts/build.tcl

If successful, you will get build/basys3_counter.bit and timing/utilization reports in the build/ directory.

Program the FPGA from the CLI

Ensure the board is connected and powered, then create scripts/program.tcl:

# File: scripts/program.tcl
# Program Basys 3 with generated bitstream using Vivado 2023.2

set bitfile "build/basys3_counter.bit"

open_hw
connect_hw_server -url 127.0.0.1:3121
open_hw_target

# Select the first xc7a35t device found (Basys 3)
set devs [get_hw_devices xc7a35t*]
if {[llength $devs] == 0} {
    puts "ERROR: No xc7a35t device found. Is the board connected and powered?"
    exit 1
}
current_hw_device [lindex $devs 0]
refresh_hw_device -update_hw_probes false [current_hw_device]

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

# Clean up
close_hw_target
disconnect_hw_server
close_hw

Program the board:

vivado -mode batch -source scripts/program.tcl

You should see status lines indicating the device was successfully programmed.


Step‑by‑step Validation

  1. Power on and program:
  2. Confirm the board power LED is lit.
  3. Run the program Tcl script. If successful, the DONE LED (often labeled “DONE”) should assert after configuration.

  4. Observe the seven‑segment display:

  5. The four digits should be active (some slight multiplexing dimness is normal).
  6. The rightmost digit (AN0) shows the LSB of the count; the leftmost (AN3) shows the MSB.
  7. The display should update at roughly 2 counts per second: 0000 → 0001 → 0010 → 0011 → … → 1111 → 0000 → …

  8. Confirm bit ordering:

  9. When you first see 0000, wait for the next transition to 0001.
  10. After several seconds, you’ll see 0011, 0100, etc. After ~8 seconds from 0000, expect 1000 (MSB set).
  11. This confirms that digits map MSB..LSB from left to right: AN3 AN2 AN1 AN0.

  12. Check decimal point and segment polarity:

  13. The decimal point should stay OFF (not illuminated), because we drive dp high (inactive, active‑low).
  14. If the display appears inverted (e.g., you see a “-” instead of “1”), revisit the active‑low logic in the segment drive or the XDC pin mapping.

  15. Timing sanity:

  16. The counter rate is controlled by COUNT_RATE_HZ = 2 in the Verilog. If you measure different timing, verify the board clock is 100 MHz and that your Vivado timing constraints include the 10 ns period.

  17. Multiplex refresh:

  18. There should be no noticeable flickering. If you see flicker, you can increase SCAN_RATE_HZ (e.g., 2000) to raise the refresh rate, but ensure it remains comfortably below the clock division resolution limit.

Pin Mapping Summary and Active Levels

The following table summarizes the key connections used by this design. All I/Os are LVCMOS33.

Signal (Top Port) Basys 3 Net FPGA Pin Direction Active Level Notes
clk CLK100MHZ W5 Input 100 MHz on‑board oscillator
an[0] AN0 U2 Output Low Enable rightmost digit
an[1] AN1 U4 Output Low Enable second digit from right
an[2] AN2 V4 Output Low Enable third digit from right
an[3] AN3 W4 Output Low Enable leftmost digit
seg_a CA W7 Output Low Segment a
seg_b CB W6 Output Low Segment b
seg_c CC U8 Output Low Segment c
seg_d CD V8 Output Low Segment d
seg_e CE U5 Output Low Segment e
seg_f CF V5 Output Low Segment f
seg_g CG U7 Output Low Segment g
dp DP V7 Output Low Decimal point (kept off = high)

Troubleshooting

  • Board not detected:
  • Ensure the power switch is ON and the micro‑USB cable is known‑good.
  • On Windows, confirm Xilinx USB drivers are installed (reinstall via Vivado if necessary).
  • Run: vivado -mode tcl -source scripts/program.tcl and check for “No xc7a35t device found.” If so, try open_hw; connect_hw_server; open_hw_target in interactive Tcl console to see available devices.

  • Bitstream fails timing:

  • Check that your constraints include create_clock -period 10.000 on the clk port.
  • Inspect build/post_impl_timing.rpt. This simple design should meet timing easily; if it does not, verify you used the correct part (xc7a35tcpg236-1).

  • Display is blank:

  • If anodes are incorrectly mapped or driven high all the time, no digits will light. Confirm an outputs are active‑low and that your XDC pin assignments match.
  • Ensure your constraints use the exact port names from the Verilog (case‑sensitive).
  • Verify that dp is set high (inactive). A stuck‑low dp can cause unexpected brightness but should not blank digits.

  • Display shows wrong segments:

  • Cross‑check the CA..CG pins and ensure seg_a..seg_g map exactly to W7, W6, U8, V8, U5, V5, U7 respectively.
  • Remember: For common‑anode, segments are active‑low; a 0 lights the segment.

  • Visible flicker:

  • Increase SCAN_RATE_HZ to 2000 or 4000, regenerate bitstream, and reprogram. Keep in mind this increases the divider resolution (still trivial at 100 MHz).
  • Ensure only one anode is active at a time.

  • Counter runs too fast/slow:

  • Adjust COUNT_RATE_HZ in Verilog. For example, set to 1 for a 1 Hz update. Rebuild and re‑flash.

  • Programming fails with “CRC error” or similar:

  • Try power cycling the board and reprogramming.
  • Confirm your bitstream path is correct in scripts/program.tcl and you’re not accidentally using a stale file.

Improvements

  • Display hexadecimal instead of binary:
  • Replace the “0/1” encoding with a full 0..F hex decoder and show the nibble value on a single digit (e.g., always AN0). Or show the hex on all four digits (mirrored) to increase brightness.

  • Show larger counters:

  • Expand to an 8‑bit or 16‑bit counter and scroll the bits across digits.
  • Use both “0/1” patterns and add a moving “dp” indicator to visualize bit groupings.

  • Add user control:

  • Use an on‑board push button (e.g., BTNC) as reset or pause. Debounce the button in logic.
  • Use slide switches to select counting direction or speed.

  • Better timing:

  • Introduce a clock enable derived from a single wide counter instead of explicit equality comparisons for higher synthesis efficiency.
  • Add an MMCM/PLL to derive a precise refresh clock if you later integrate complex logic.

  • Power/burn‑in diagnostics:

  • Add a startup animation to validate all segments and anodes before entering counter mode.
  • Provide a UART or LEDs mirror of the current count for debugging.

Checklist

  • Prerequisites:
  • Vivado WebPACK 2023.2 installed and on PATH
  • Board connected via micro‑USB and powered ON

  • Files created:

  • src/top_basys3_counter.v (Verilog)
  • constraints/basys3_7seg.xdc (pin mapping and clock)
  • scripts/build.tcl (non‑project build)
  • scripts/program.tcl (program device)

  • Commands executed:

  • Build: vivado -mode batch -source scripts/build.tcl
  • Program: vivado -mode batch -source scripts/program.tcl

  • Hardware validation:

  • Four seven‑segment digits display a 4‑bit binary count in “0/1” symbols
  • Count updates ~2 times per second from 0000 to 1111 and wraps
  • Decimal point remains off

  • If issues arise:

  • Verify XDC port names and pins
  • Confirm active‑low logic for anodes and segments
  • Check timing and part selection (xc7a35tcpg236-1)
  • Rebuild and re‑flash after any change

With these steps, you have a working binary counter on the Basys 3 seven‑segment display using a clean, reproducible CLI flow in Vivado. This forms a solid foundation to build richer display logic (e.g., hex/decimal decoding, animations) and to integrate user inputs, all while reinforcing clock division, multiplexing, and constraints fundamentals on an Artix‑7 FPGA.

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




Question 2: Which programming language is used for the binary counter?




Question 3: What version of Vivado WebPACK is required?




Question 4: What is the maximum count displayed by the 4-bit binary counter?




Question 5: Which command is used to verify the Vivado version?




Question 6: What type of connection is required for programming the FPGA?




Question 7: What should you verify on Windows to confirm the board is recognized?




Question 8: Which tool is primarily used for building and programming the FPGA?




Question 9: What is an optional item mentioned for convenience?




Question 10: What is the main focus of this practical case?




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

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

Follow me:
error: Contenido Protegido / Content is protected !!
Scroll to Top