01 · PLL and clocks

Make any clock you need from the board's 50 MHz oscillator, and prove it: the design measures every clock it has and shows the result on the 7-segment display and on your PC.

Verified on the board 2026-10-24 Quartus Prime Lite 17.1 No shield needed Skill: set-pll-clocks
Download pll_clocks.qar (full project)

The repo keeps the full project (.qar, plus the sources here) but no prebuilt bitstream: one build (a few minutes) makes the .sof on your computer.

What you will see

WhereWhat
HEX5..HEX0Frequency of the selected clock in kHz, for example 25000 = 25 MHz, 100000 = 100 MHz
SW9 upCycles through the 8 clocks, one per second. SW9 down: SW2..SW0 choose one (0-4 = PLL outputs, 5 = MAX10_CLK2_50, 6 = ADC_CLK_10, 7 = MAX10_CLK1_50)
LEDR2..LEDR0Number of the clock on the display
LEDR8..LEDR4One blinker per PLL output. Each counts in its own clock, so all five blink at about 1 Hz
LEDR9PLL locked. Hold KEY0 to reset the PLL: LEDR9 goes off and the five blinkers stop
Your PCpython scripts/de10.py log prints one line per clock every second

Block diagram

MAX10_CLK1_50 MAX10_CLK2_50 ADC_CLK_10 KEY0 (reset) pll_de10 altpll, VCO 600 MHz c0 25 MHzc1 100 MHzc2 33.333 MHz c3 10 MHzc4 50 MHz +90° 5 blinkers one counter per clock domain LEDR8..4, LEDR9 8 × freq_counter 1 ms gate from 50 MHz,edges counted in each clock,result = kHz selectSW9 / SW2..0 bin2bcd HEX5..HEX0 line_printer+ jtag_uart USB → your PC

How the PLL is set

The PLL is the reusable lib/rtl/pll_de10.v: a direct instance of Altera's altpll (the same thing the IP wizard writes) with each output set by three parameters:

pll_de10 #(
  .C0_MUL(1), .C0_DIV(2),                      // 50 * 1/2 = 25 MHz
  .C1_MUL(2), .C1_DIV(1),                      // 100 MHz
  .C2_MUL(2), .C2_DIV(3),                      // 33.333 MHz
  .C3_MUL(1), .C3_DIV(5),                      // 10 MHz
  .C4_MUL(1), .C4_DIV(1), .C4_PHASE("5000")    // 50 MHz, 5000 ps = 90 degrees late
) pll (.areset(~KEY[0]), .inclk0(MAX10_CLK1_50), .clk(pll_clk), .locked(locked));

Rule: fout = 50 MHz × MUL / DIV. Quartus finds a VCO frequency and counter values that make all five outputs at once; if it can't, the build stops with an error naming the clock. What it actually built is in output_files/pll_clocks.fit.rpt, section PLL Usage. For this example:

OutputMUL/DIVFrequencyPhaseCounter (from VCO 600 MHz)
c01/225.0 MHz0C0 = 24
c12/1100.0 MHz0C1 = 6
c22/333.33 MHz0C2 = 18
c31/510.0 MHz0C3 = 60
c41/150.0 MHz90° (5000 ps)C4 = 12

The VCO runs at 50 × M / N = 50 × 12 / 1 = 600 MHz, and each output divides it by its counter. Phase steps come in VCO steps of 208 ps, so a phase that is not a multiple of 208 ps is rounded (the report shows the value used).

Things to know

Measured on the board

$ python scripts/de10.py log
c0 PLL 50*1/2  = 025000 kHz
c1 PLL 50*2/1  = 100000 kHz
c2 PLL 50*2/3  = 033333 kHz     (33 333.3 kHz: shows 033333 or 033334)
c3 PLL 50*1/5  = 010000 kHz
c4 PLL 50 +90d = 050000 kHz
MAX10_CLK2_50  = 050000 kHz
ADC_CLK_10     = 010000 kHz
MAX10_CLK1_50  = 050000 kHz

Resources: 1,631 logic elements (3 %), 1 of 4 PLLs, 0 multipliers. Timing: no negative slack.

Run it

Build and load it

python scripts/de10.py build examples/01_pll_clocks
python scripts/de10.py program examples/01_pll_clocks
python scripts/de10.py log 3

Or open Quartus Prime Programmer, choose USB-Blaster, add output_files/pll_clocks.sof, tick Program/Configure, Start.

Build it yourself

python scripts/new_project.py examples/01_pll_clocks pll_clocks_top --lib pll print bcd freq sevenseg
python scripts/de10.py build examples/01_pll_clocks
python scripts/de10.py program examples/01_pll_clocks

Or restore pll_clocks.qar in Quartus (Project → Restore Archived Project); it holds the project and the library files it uses, in their folders.

Test without the board

python scripts/sim.py examples/01_pll_clocks/sim/tb_freq_counter.sv              # Icarus Verilog
python scripts/sim.py examples/01_pll_clocks/sim/tb_freq_counter.sv --modelsim   # ModelSim
→ 25 MHz -> 25000 kHz, 10 MHz -> 10000 kHz   PASS

This test caught a real bug while the example was being made: the first version lost one edge per gate and showed 24999.

Files

FileWhat
src/pll_clocks_top.svThe example: PLL, counters, selector, display, report
sim/tb_freq_counter.svUnit test of the frequency counter
lib/rtl/pll_de10.vReusable PLL (5 outputs, parameters)
lib/rtl/freq_counter.svFrequency counter with safe clock crossing
lib/rtl/bin2bcd.sv, seven_segment.svNumber to decimal digits to segments
lib/rtl/line_printer.sv, jtag_uart.svText lines to the PC over the USB cable