Objective and use case
What you’ll build: This tutorial guides you through creating a PWM LED brightness controller on the DE10-Lite FPGA board, utilizing Verilog for implementation.
Why it matters / Use cases
- Control LED brightness in embedded systems for visual feedback in user interfaces.
- Implement PWM dimming in IoT devices to save power while maintaining visibility.
- Utilize PWM techniques in educational projects to teach students about digital signal processing.
- Enhance lighting systems in smart homes by allowing dynamic brightness adjustments based on ambient light.
Expected outcome
- Achieve smooth brightness transitions with a PWM frequency of 1 kHz.
- Measure LED brightness levels with a variance of less than 5% across different PWM duty cycles.
- Validate responsiveness to user input via on-board switches with less than 100 ms latency.
- Demonstrate successful programming of the DE10-Lite board with a 100% success rate in compilation and upload.
Audience: Electronics enthusiasts; Level: Intermediate
Architecture/flow: Verilog design -> Compilation with Intel Quartus Prime -> JTAG programming -> LED brightness validation
Basic Hands‑On Practical: LED Brightness Control with PWM on DE10‑Lite (Intel MAX 10)
This tutorial walks you through building a pulse‑width‑modulation (PWM) LED brightness controller on the DE10‑Lite (Intel MAX 10) FPGA board. You will write a small Verilog design, compile it with Intel Quartus Prime Lite, program the board via JTAG, and validate the brightness levels using on‑board switches and LEDs. The focus is practical and reproducible: exact commands, file paths, and a lean Verilog implementation.
Target device family and model: FPGA — DE10‑Lite (Intel MAX 10), device 10M50DAF484C7G, using Quartus Prime Lite Edition.
Objective: led‑brightness‑control‑pwm
Prerequisites
- Skills
- Basic understanding of Verilog (always blocks, parameters, modules).
-
Ability to use a terminal/command prompt for running build/programming tools.
-
Operating system
-
Windows 10/11 (64‑bit) or Ubuntu 22.04 LTS (64‑bit).
-
Tools (versions known to support DE10‑Lite/MAX 10)
- Intel Quartus Prime Lite Edition 22.1std (Build 922 or later).
- Windows default install path: C:\intelFPGA_lite\22.1std\quartus
- Linux default install path: /opt/intelFPGA_lite/22.1std/quartus
- USB‑Blaster II driver (installed via Quartus Programmer).
-
JTAG utilities included with Quartus:
- quartus_sh (scripts and project automation)
- quartus_map, quartus_fit, quartus_asm (invoked by quartus_sh –flow compile)
- quartus_pgm (JTAG programming)
- jtagconfig (enumerate JTAG chain)
- quartus_cpf (SOF→POF conversion for flash programming, optional)
-
Board support files
- DE10‑Lite official pin assignment TCL/QSF from the Terasic DE10‑Lite Golden Reference Design.
-
Place the vendor pin TCL somewhere in your project (for example, board/DE10_Lite_pins.tcl). We will source it during project creation so you don’t have to hand‑type device pin locations.
-
Verify tool availability
- On Windows (PowerShell):
& "C:\intelFPGA_lite\22.1std\quartus\bin64\quartus_sh.exe" --version
& "C:\intelFPGA_lite\22.1std\quartus\bin64\jtagconfig.exe" - On Linux (bash):
/opt/intelFPGA_lite/22.1std/quartus/bin/quartus_sh --version
/opt/intelFPGA_lite/22.1std/quartus/bin/jtagconfig
Materials (exact model)
- DE10‑Lite development kit (Intel MAX 10 10M50DAF484C7G)
- Micro‑USB cable for the on‑board USB‑Blaster II
- Computer with Quartus Prime Lite 22.1std installed
- Optional for validation:
- A multimeter or oscilloscope (if you later export the PWM to a header pin; not mandatory for this tutorial)
- Smartphone with slow‑motion video to visually compare brightness steps
Setup / Connection
- Board power and JTAG connection
- Ensure the power switch on the DE10‑Lite is OFF.
- Connect the micro‑USB cable from the PC to the on‑board USB‑Blaster II connector (labeled “USB‑BLASTER”).
- Turn the DE10‑Lite power switch ON. The power LED should light.
-
Verify JTAG connectivity:
- Windows (PowerShell):
& "C:\intelFPGA_lite\22.1std\quartus\bin64\jtagconfig.exe" - Linux (bash):
/opt/intelFPGA_lite/22.1std/quartus/bin/jtagconfig - Expected: a line showing “10M50” or “Intel MAX 10” and the USB‑Blaster II cable.
- Windows (PowerShell):
-
No external wiring required
- We will use on‑board resources only:
- 50 MHz oscillator input (CLOCK_50)
- Slide switches SW[7:0] to set the PWM duty cycle
- User LEDs LEDR[9:0], primarily LEDR[0] for PWM output
What We Will Build
We will implement a simple PWM generator using a free‑running counter and comparator:
- PWM frequency = fclk / 2^N. With fclk = 50 MHz and N = 12, PWM ≈ 12.207 kHz (well above flicker).
- Duty cycle is 12‑bit (0..4095). We map SW[7:0] to the 12‑bit duty by left‑shifting 4 bits (i.e., duty = SW × 16).
- LEDR[0] shows the PWM brightness.
- LEDR[8:1] mirror the eight switches so you can confirm the input value.
- LEDR[9] is a slow “heartbeat” to confirm the design is running.
Interface Mapping (Logical)
This table describes the top‑level ports and how they map to the DE10‑Lite board resources we will use. The physical pin locations will be assigned by importing the official DE10‑Lite pin‑assignment TCL/QSF file in the project script.
| Top‑Level Port | Board Resource | Direction | Purpose |
|---|---|---|---|
| CLOCK_50 | 50 MHz oscillator | Input | System clock |
| SW[7:0] | Slide switches (SW[7:0]) | Input | PWM duty control (0..255 mapped to 0..4095) |
| KEY[1:0] | Pushbuttons | Input | Reserved for future expansion (not required) |
| LEDR[0] | User LED 0 | Output | PWM output (brightness control) |
| LEDR[8:1] | User LEDs 1..8 | Output | Display raw switch inputs (debug) |
| LEDR[9] | User LED 9 | Output | Heartbeat (slow blink) |
Note: DE10‑Lite LEDs are active‑high; a logic 1 turns the LED on.
Full Code
Create a workspace folder with the following structure:
- de10lite_pwm/
- rtl/
- pwm.v
- de10lite_pwm_top.v
- constraints/
- de10lite_pwm.sdc
- board/
- DE10_Lite_pins.tcl (from the DE10‑Lite Golden Reference; see Setup)
- scripts/
- de10lite_pwm_project.tcl
rtl/pwm.v
A compact, parameterized PWM generator. The PWM frequency is fclk / 2^WIDTH.
// File: rtl/pwm.v
// Simple parameterized PWM: free-running counter compared to duty.
// PWM frequency = fclk / (2^WIDTH)
`timescale 1ns/1ps
module pwm #(
parameter integer WIDTH = 12 // PWM resolution bits
)(
input wire clk, // System clock
input wire [WIDTH-1:0] duty, // Duty cycle (0..2^WIDTH-1)
output reg pwm_out
);
reg [WIDTH-1:0] ctr = {WIDTH{1'b0}};
always @(posedge clk) begin
ctr <= ctr + 1'b1;
pwm_out <= (ctr < duty);
end
endmodule
rtl/de10lite_pwm_top.v
Top‑level for DE10‑Lite. Maps switches to duty, drives LEDs.
// File: rtl/de10lite_pwm_top.v
// Target board: DE10-Lite (Intel MAX 10), device 10M50DAF484C7G
// Resources used: CLOCK_50, SW[7:0], LEDR[9:0], KEY[1:0] (reserved)
`timescale 1ns/1ps
module de10lite_pwm_top (
input wire CLOCK_50,
input wire [7:0] SW,
input wire [1:0] KEY, // active-low buttons; not used here
output wire [9:0] LEDR
);
// Map 8-bit switches to 12-bit duty by left shift of 4 (x16)
// 0x00 -> 0 (off), 0xFF -> 0xFF0 (~100% minus 15/4096)
wire [11:0] duty12 = {SW, 4'b0000};
// Instantiate PWM (12-bit -> ~12.2 kHz at 50 MHz)
wire pwm_led;
pwm #(.WIDTH(12)) u_pwm (
.clk(CLOCK_50),
.duty(duty12),
.pwm_out(pwm_led)
);
// Heartbeat: divide clock down to ~1 Hz blink on LEDR[9]
reg [25:0] hb_cnt = 26'd0; // 2^26 / 50e6 ~ 1.34 s
always @(posedge CLOCK_50) begin
hb_cnt <= hb_cnt + 1'b1;
end
wire heartbeat = hb_cnt[25]; // toggle every ~0.67 s (visible blink)
// Drive LEDs
assign LEDR[0] = pwm_led; // PWM brightness
assign LEDR[8:1] = SW; // shows the input value
assign LEDR[9] = heartbeat; // heartbeat indicator
// KEYs are unused but kept in port list for completeness
// They can be used later for step-up/step-down control with debouncing.
endmodule
constraints/de10lite_pwm.sdc
Timing constraint for the 50 MHz clock.
create_clock -name CLOCK_50 -period 20.000 [get_ports {CLOCK_50}]
Build/Flash/Run Commands (CLI)
We will create a Quartus project from a TCL script, import DE10‑Lite pin assignments, build the design, and program the board. The flow is the same on Windows and Linux; only the tool paths differ.
1) Project creation script
Place the official DE10‑Lite pin assignment script in:
– board/DE10_Lite_pins.tcl
This file is provided by Terasic in the DE10‑Lite Golden Reference Design package. It contains set_location_assignment calls for CLOCK_50, SW, KEY, LEDR, etc. If you prefer the GUI, you can import it via Assignments → Import Assignments; here we use CLI for reproducibility.
Create scripts/de10lite_pwm_project.tcl:
# File: scripts/de10lite_pwm_project.tcl
# Creates the Quartus project, sets device, adds files, imports pin assignments, and sets constraints.
set proj_name "de10lite_pwm"
set top_name "de10lite_pwm_top"
set device "10M50DAF484C7G" ;# DE10-Lite device
project_new $proj_name -overwrite
set_global_assignment -name FAMILY "MAX 10"
set_global_assignment -name DEVICE $device
set_global_assignment -name TOP_LEVEL_ENTITY $top_name
# Add RTL sources
set_global_assignment -name VERILOG_FILE "rtl/pwm.v"
set_global_assignment -name VERILOG_FILE "rtl/de10lite_pwm_top.v"
# Add SDC timing constraints
set_global_assignment -name SDC_FILE "constraints/de10lite_pwm.sdc"
# Import DE10-Lite official pin assignments (from Terasic Golden Reference)
# Adjust the path below if you placed the file elsewhere.
source board/DE10_Lite_pins.tcl
# Optional: set optimization effort and other compile switches
set_global_assignment -name OPTIMIZATION_MODE "AGGRESSIVE PERFORMANCE"
# Save and exit
project_close
2) Run the project script and compile
- Windows (PowerShell):
Set-Location C:\work\fpga\de10lite_pwm
& "C:\intelFPGA_lite\22.1std\quartus\bin64\quartus_sh.exe" -t scripts\de10lite_pwm_project.tcl
& "C:\intelFPGA_lite\22.1std\quartus\bin64\quartus_sh.exe" --flow compile de10lite_pwm
- Linux (bash):
cd ~/work/fpga/de10lite_pwm
/opt/intelFPGA_lite/22.1std/quartus/bin/quartus_sh -t scripts/de10lite_pwm_project.tcl
/opt/intelFPGA_lite/22.1std/quartus/bin/quartus_sh --flow compile de10lite_pwm
Expected outputs:
– Database and output files under ./output_files/
– A .sof file at ./output_files/de10lite_pwm.sof (for JTAG SRAM programming)
– Fitter/timing reports showing one clock domain at 50 MHz
3) Program the FPGA (volatile, .sof)
-
Verify JTAG cable:
-
Windows:
& "C:\intelFPGA_lite\22.1std\quartus\bin64\jtagconfig.exe" -
Linux:
/opt/intelFPGA_lite/22.1std/quartus/bin/jtagconfig -
Program:
-
Windows:
& "C:\intelFPGA_lite\22.1std\quartus\bin64\quartus_pgm.exe" -m jtag -o "p;output_files\de10lite_pwm.sof" - Linux:
/opt/intelFPGA_lite/22.1std/quartus/bin/quartus_pgm -m jtag -o "p;output_files/de10lite_pwm.sof"
The LED configuration will be loaded into SRAM; it is lost on power‑cycle.
4) Optional: Program on‑chip configuration flash (non‑volatile, .pof)
MAX 10 devices include on‑chip configuration flash. Convert SOF to POF and program it to retain the design after power‑cycle.
-
Convert SOF→POF:
-
Windows:
& "C:\intelFPGA_lite\22.1std\quartus\bin64\quartus_cpf.exe" -c output_files\de10lite_pwm.sof output_files\de10lite_pwm.pof -
Linux:
/opt/intelFPGA_lite/22.1std/quartus/bin/quartus_cpf -c output_files/de10lite_pwm.sof output_files/de10lite_pwm.pof -
Program POF to flash:
-
Windows:
& "C:\intelFPGA_lite\22.1std\quartus\bin64\quartus_pgm.exe" -m jtag -o "p;output_files\de10lite_pwm.pof" - Linux:
/opt/intelFPGA_lite/22.1std/quartus/bin/quartus_pgm -m jtag -o "p;output_files/de10lite_pwm.pof"
Note: If your JTAG chain has multiple devices, you may need to specify the index with @N (e.g., p;file.sof@2). For a single DE10‑Lite in the chain, the above typically works.
Step‑by‑Step Validation
Follow these steps immediately after programming with the .sof or .pof.
1) Basic function check
– LEDR[9] (heartbeat) should blink slowly (toggle roughly once per ~0.67 s).
– LEDR[8:1] should mirror SW[7:0] exactly: toggling any switch drives the corresponding LED.
2) PWM brightness behavior on LEDR[0]
– Set all switches SW[7:0] = 0x00. LEDR[0] should be off (duty = 0%).
– Set SW[7:0] = 0xFF. LEDR[0] should be fully on (almost 100% duty; with 0xFF0/4096 ≈ 99.6%).
– Try mid‑scale:
– SW = 0x80 (128 dec). LEDR[0] should be about “half brightness.”
– SW = 0x40 (64 dec). LEDR[0] should look dimmer than the previous step.
– SW = 0xC0 (192 dec). LEDR[0] should be brighter than half.
– Observe that perceived brightness is not perfectly linear with duty (human vision is logarithmic). This is expected; see Improvements for gamma correction.
3) Verify PWM frequency is not causing flicker
– The PWM frequency is ~12.2 kHz; the LED should not visibly flicker at any duty setting.
– Optional: record a slow‑motion video at 240 fps on a smartphone while pointing at LEDR[0] at low duty (e.g., SW=0x10) and high duty (e.g., SW=0xF0). You should not see rolling or banding artifacts because 12 kHz >> 240 fps.
4) Stability over time
– Leave the board on for a few minutes and change switches. LEDR[0] should respond instantaneously and consistently with no bounces (switch bounces don’t matter here because they change a static value; we’re not edge‑detecting).
5) Optional quantitative check (if you have a DMM/oscilloscope and use headers)
– If later you export pwm_led to a GPIO header pin (by adding a port and pin assignment), you can:
– Use a DMM with duty measurement to confirm the duty ratio at several SW values (e.g., 25%, 50%, 75%).
– Use an oscilloscope to confirm frequency ≈ 50e6/4096 ≈ 12.207 kHz and duty tracks SW×(1/256).
6) Edge cases
– SW = 0x00 → duty12 = 0x000 → LED off.
– SW = 0x01 → duty12 = 0x010 → barely on (about 0.39% duty).
– SW = 0xFE → duty12 = 0xFE0 → ~99.0% duty.
– SW = 0xFF → duty12 = 0xFF0 → ~99.6% duty; not exactly 100% because of the shift scheme (avoids corner case saturation and keeps comparator logic simple).
Troubleshooting
- quartus_pgm or jtagconfig shows no cable/device
- Symptom:
1) No JTAG hardware available -
Fix:
- Ensure the DE10‑Lite power switch is ON and the USB cable is connected to the USB‑BLASTER port (not the UART).
- Install the USB‑Blaster II driver (Windows: run Quartus Programmer as admin once; Device Manager → Update Driver).
- Try another USB port/cable. On Linux, add your user to the plugdev group or configure udev rules (Intel provides a script in
/drivers/usb-blaster).
-
Device mismatch or assignment errors
- Symptom:
Error (18933): Device 10M50DAF484C7G not found in project -
Fix:
- Ensure the project TCL sets DEVICE «10M50DAF484C7G».
- Re‑run the project creation script:
quartus_sh -t scripts/de10lite_pwm_project.tcl - Delete (or move) the db/ and incremental_db/ folders before re‑compiling if the device was changed.
-
Pin assignment conflicts or missing locations
- Symptom:
Error (169171): Can't place node LEDR[0] -- no legal location -
Fix:
- Confirm the DE10_Lite_pins.tcl is sourced in the project TCL and points to the correct file path.
- Use Assignments → Pin Planner to verify that CLOCK_50, SW[7:0], LEDR[9:0] have valid locations.
- Do not mix pin assignments from other MAX 10 boards; use the DE10‑Lite official file.
-
Timing violation messages
- At 50 MHz with such a small design, timing violations should not occur.
-
If you see negative slack:
- Double‑check that your SDC has the 20 ns clock on CLOCK_50 and no stray clocks.
- Try a clean rebuild or set OPTIMIZATION_MODE to “Balanced”.
-
LED polarity inverted (LED appears off when expected on)
- DE10‑Lite user LEDs are active‑high. If you used a different pin file with inverted polarity or added external wiring, adjust your logic accordingly.
-
For on‑board LEDR, the provided mapping is active‑high.
-
SOF programming succeeds but the design vanishes on reboot
- Expected: .sof goes to SRAM (volatile).
- If you want it to persist, convert to .pof and program the on‑chip flash as shown above.
Improvements (Beyond the Basic Objective)
- Gamma correction (perceptual linearity)
-
Human brightness perception is roughly logarithmic. To achieve perceptually even steps, apply a gamma curve (e.g., gamma ≈ 2.2):
- Create an 8‑bit to 12‑bit lookup table (LUT) mapping SW[7:0] to duty12 = floor((SW/255)^γ * 4095).
- Store the LUT in a simple ROM in Verilog or initialize from a MIF.
-
Adjustable PWM frequency
-
Parameterize PWM WIDTH or add a prescaler so you can sweep frequency from a few hundred Hz to tens of kHz. For multiplexed LED displays you may want ~1–2 kHz; for motor control, tens of kHz may be desirable.
-
Pushbutton step control with debouncing
- Use KEY[1:0] to increment/decrement an 8‑bit duty register with proper debouncing (two‑flip‑flop sync + counter‑based debounce).
-
This improves usability without touching slide switches.
-
Multiple channels
-
Instantiate several PWM modules to control multiple LEDs (e.g., LEDR[0..7]) independently, or implement a shared counter with multiple comparators for resource efficiency.
-
Advanced features
- Add a “breathing” effect by modulating duty with a slow triangle/SIN LUT.
-
Export PWM to GPIO headers and drive an external high‑power LED (with a suitable current driver/transistor and resistor).
-
Safety and electrical considerations
- On‑board LEDs have appropriate series resistors. For external LEDs or devices, ensure current limiting and do not exceed pin current limits of the MAX 10 device.
Final Checklist
- Prerequisites
- Quartus Prime Lite 22.1std installed and working
- USB‑Blaster II recognized by jtagconfig
-
DE10‑Lite powered and connected via USB
-
Project files present
- rtl/pwm.v
- rtl/de10lite_pwm_top.v
- constraints/de10lite_pwm.sdc
- board/DE10_Lite_pins.tcl (official pin assignments)
-
scripts/de10lite_pwm_project.tcl
-
Build
- quartus_sh -t scripts/de10lite_pwm_project.tcl completes without error
-
quartus_sh –flow compile de10lite_pwm generates output_files/de10lite_pwm.sof
-
Program
- quartus_pgm -m jtag -o «p;output_files/de10lite_pwm.sof» succeeds
-
Optional: quartus_cpf and quartus_pgm used to create/program .pof for non‑volatile storage
-
Validation
- LEDR[9] blinks (heartbeat)
- LEDR[8:1] mirror SW[7:0]
- LEDR[0] brightness changes with SW value; off at 0x00 and near‑full at 0xFF
-
No visible flicker across the range
-
Documentation
- Notes kept on any deviations (e.g., custom pin file) and validated PWM frequency/duty if measured
With this, you have a complete, reproducible baseline for PWM‑based LED brightness control on the DE10‑Lite (Intel MAX 10) platform using Quartus Prime Lite and clean, minimal Verilog.
Find this product and/or books on this topic on Amazon
As an Amazon Associate, I earn from qualifying purchases. If you buy through this link, you help keep this project running.



