Project F

ECP5 FPGA Clock Generation

Published

An FPGA clock is like the conductor of an orchestra keeping everyone playing to time. The frequency of the clock determines the performance of our design but is also critical to interfacing with external devices, such as memory chips or displays.

Yosys and nextpnr have excellent support for Lattice ECP5 FPGAs. However, without using the ECP5 PLL (phase-locked loop), you’re stuck running at the speed of your dev board oscillator. This post outlines the architecture of ECP5 PLL and provides several practical examples to get you started with generating custom clock frequencies. Generating your own clock frequencies is much more straightforward than it first appears.

ECP5 PLL Architecture

A PLL takes an input clock and generates output clocks of different frequencies. This is a gross simplification, but is enough for our purposes.

ECP5 PLL Architecture

Thanks to Project Trellis, we know the relationship between the frequencies and the clock dividers.

f_clk_in                                    # input clock frequency
f_pfd      = f_clk_in / clki_div            # PFD - phase frequency detector
f_vco      = f_pfd × clkfb_div × clkop_div  # VCO - voltage controlled oscillator
f_clk_out  = f_vco / clkop_div              # primary output clock frequency

We can adjust these parameters to reach the desired output frequency:

  • clki_div - input clock divider
  • clkfb_div - feedback divider
  • clkop_div - primary output clock divider

The parameters must be integers in the range 1 - 128 inclusive.

The eagle-eyed will have spotted that we both multiply and divide by clkop_div, so it doesn’t affect the output clock frequency. However, we can understand the purpose of clkop_div by consulting the sysCLOCK PLL Timing section of the ECP5 data sheet (PDF).

  • Input Clock Frequency: 8 - 400 MHz
  • Output Clock Frequency: 3.125 - 400 MHz
  • VCO Frequency: 400 - 800 MHz

We need clkop_div to keep the VCO frequency in the range 400 - 800 MHz.

Simplifying the equations, the relationship between input and output frequency:

f_clk_out = (f_clk_in / clki_div) * clkfb_div

As we’ll see, this comes down to some simple maths and there are tools that can do this for you. :-)

ProTip: clkfb_div divides the feedback signal, multiplying the frequency.

Practical Examples

My examples are for the Radiona ULX3S dev board with a 25 MHz oscillator, but you can easily adapt them for different boards by changing the input frequency.

50 MHz

Our input clock is 25 MHz and we want a 50 MHz output clock.

We can divide 25 MHz by 1, then multiply by 2 to reach 50 MHz.

50 MHz = (25 MHz / 1) * 2

Our output frequency is 50 MHz and we need to ensure the VCO frequency is in the range 400 - 800 MHz. When several values work, I recommend choosing one in the middle of the range. With an output divider of 12, the VCO frequency will be: 50 × 12 == 600.

Let’s summarise our divider values for reference:

25 MHz -> 50 MHz
====================
clki_div      1
clkfb_div     2
clkop_div    12
====================
f_vco = 600 MHz

40 MHz

Our input clock is 25 MHz and we want a 40 MHz output clock. We can divide by 25 and multiply by 40. Or we can simplify the fraction to find the smallest suitable values: 40/25 = 8/5.

40 MHz = (25 MHz / 5) * 8

To keep the VCO frequency in range, we set our output clock divider to 15: 40 × 15 == 600.

25 MHz -> 40 MHz
====================
clki_div      5
clkfb_div     8
clkop_div    15
====================
f_vco = 600 MHz

25.2 MHz

25.2 is not an integer, so it’s not immediately obvious how to choose the parameters.

However, we can multiply both input and output frequencies by ten to create an integer ratio and then simplify the result: 252/250 = 126/125.

25.2 MHz = (25 MHz / 125) * 126

We’re just in the range of our parameters (1-128), so this will work. An output multiplier of 20 ensures the VCO frequency is in range.

25 MHz -> 25.2 MHz
====================
clki_div    125
clkfb_div   126
clkop_div    20
====================
f_vco = 504 MHz

74.25 MHz

We attempt the same approach for 74.25 MHz. Multiply 74.25 and 25 by 100 to create an integer ratio, then simplify the fraction: 7425/2500 = 297/100.

Unfortunately, 297 is outside the supported range (1-128). It’s impossible to generate 74.25 MHz from a 25 MHz input clock.

However, 74 MHz is straightforward.

25 MHz -> 74 MHz
====================
clki_div     25
clkfb_div    74
clkop_div     8
====================
f_vco = 592 MHz

Helpful Tools

Tools can help you simplify fractions and calculate clock dividers:

NB. When using these tools, remember that ECP5 PLL dividers must be in the range 1-128.

Clock Module

The ECP5 primitive (EHXPLLL) has many signals and parameters. We wrap it in a simple module that makes it easy to incorporate into our designs. Each of the dividers is a parameter, so we can use this module to generate any single output frequency supported by the ECP5 PLL.

module clock_gen #(
    parameter CLKI_DIV  = 1,    // input clock divider
    parameter CLKFB_DIV = 1,    // feedback divider
    parameter CLKOP_DIV = 1,    // primary output clock divider
    parameter CLKOP_CPHASE = 0  // primary output clock phase
    ) (
    input  wire clk_in,     // input clock
    output wire clk_out,    // output clock
    output reg  clk_locked  // clock locked?
    );

    wire locked;  // unsynced lock signal

    // HDL attributes (values are from Project Trellis)
    (* ICP_CURRENT="12" *)
    (* LPF_RESISTOR="8" *)
    (* MFG_ENABLE_FILTEROPAMP="1" *)
    (* MFG_GMCREF_SEL="2" *)

    EHXPLLL #(
        .PLLRST_ENA("DISABLED"),
        .INTFB_WAKE("DISABLED"),
        .STDBY_ENABLE("DISABLED"),
        .DPHASE_SOURCE("DISABLED"),
        .OUTDIVIDER_MUXA("DIVA"),
        .OUTDIVIDER_MUXB("DIVB"),
        .OUTDIVIDER_MUXC("DIVC"),
        .OUTDIVIDER_MUXD("DIVD"),
        .CLKI_DIV(CLKI_DIV),
        .CLKOP_ENABLE("ENABLED"),
        .CLKOP_DIV(CLKOP_DIV),
        .CLKOP_CPHASE(CLKOP_CPHASE),
        .CLKOP_FPHASE(0),
        .FEEDBK_PATH("CLKOP"),
        .CLKFB_DIV(CLKFB_DIV)
    ) pll_i (
        .RST(1'b0),
        .STDBY(1'b0),
        .CLKI(clk_in),
        .CLKOP(clk_out),
        .CLKFB(clk_out),
        .CLKINTFB(),
        .PHASESEL0(1'b0),
        .PHASESEL1(1'b0),
        .PHASEDIR(1'b1),
        .PHASESTEP(1'b1),
        .PHASELOADREG(1'b1),
        .PLLWAKESYNC(1'b0),
        .ENCLKOP(1'b0),
        .LOCK(locked)
    );

    // ensure clock lock is synced with output clock
    reg locked_sync;
    always @(posedge clk_out) begin
        locked_sync <= locked;
        clk_locked <= locked_sync;
    end
endmodule

We can then instantiate this module in our design with a few signals and parameters.

For example, to generate a 40 MHz clock:

wire clk_out, clk_locked;
clock_gen #(
    .CLKI_DIV(5),
    .CLKFB_DIV(8),
    .CLKOP_DIV(15),
    .CLKOP_CPHASE(7)
) clock_gen_inst (
    .clk_in(clk_25m),  // 25 MHz input clock from dev board
    .clk_out(clk_out),
    .clk_locked(clk_locked)
);

Check the 40 MHz example to see where these parameters came from.

Phase

You’ll notice a CLKOP_CPHASE parameter, which controls the phase of the output clock. Finding definitive information on the best setting for this parameter is tricky. Based on Project Trellis and other examples I’ve found online, your best bet is to set this to half CLKOP_DIV. However, my video designs work fine with a value of 0.

Clock Lock

Generating a stable output clock with a PLL takes some time. The PLL indicates when the output clock is ready by setting the lock signal high. The lock signal is asynchronous, so it’s not aligned with the output clock frequency. My module puts the lock signal through a couple of registers to synchronise it with the output clock, but you can remove this if it’s not required.

Two Clocks

You can generate multiple related clocks with one PLL instance. You use the same approach as for a single clock, but each secondary clock has its own output divider. I will use DVI/HDMI clocks as an example, but this could apply to any related clocks, for example, CPU and DDR memory clocks.

When working with DVI or HDMI, you typically generate a pixel clock and a 5x pixel clock that, combined with DDR, creates the 10x pixel clock TMDS encoding requires.

For a 720p60 display, we’d like to use 74.25 MHz for the pixel clock, but 74 MHz is still within spec (±0.5% is generally acceptable for pixel clocks). See 1280x720 60 Hz Display timings for more details.

When generating multiple clocks with one PLL, start with the highest frequency clock. For this example, this is the 5x pixel clock: 5 × 74 MHz = 370 MHz.

Simplifying the ratio of 370 MHz to 25 MHz gives us our first two dividers: 370/25 = 74/5.

370 MHz = (25 MHz / 5) * 74

An output divider of 2 ensures the VCO frequency is in range: 370 × 2 == 740.

For the secondary clock, we divide f_vco by the desired frequency (74 MHz): 740/74 = 10.

In summary, our parameters are:

25 MHz -> 370/74 MHz
====================
clki_div      5
clkfb_div    74
clkop_div     2
clkos_div    10
====================
f_vco = 740 MHz

Here’s a tweaked version of the clock module for two related clocks. Pass the second clock divider through the CLKOS_DIV (secondary clock) parameter.

module clock2_gen #(
    parameter CLKI_DIV  = 1,     // input clock divider
    parameter CLKFB_DIV = 1,     // feedback divider
    parameter CLKOP_DIV = 1,     // primary output clock divider
    parameter CLKOP_CPHASE = 0,  // primary output clock phase
    parameter CLKOS_DIV = 1,     // secondary output clock divider
    parameter CLKOS_CPHASE = 0   // secondary output clock phase
    ) (
    input  wire clk_in,      // input clock
    output wire clk_5x_out,  // output 5x clock
    output wire clk_out,     // output clock
    output reg  clk_locked   // clock locked?
    );

    wire locked;  // unsynced lock signal

    // HDL attributes (values are from Project Trellis)
    (* ICP_CURRENT="12" *)
    (* LPF_RESISTOR="8" *)
    (* MFG_ENABLE_FILTEROPAMP="1" *)
    (* MFG_GMCREF_SEL="2" *)

    EHXPLLL #(
        .PLLRST_ENA("DISABLED"),
        .INTFB_WAKE("DISABLED"),
        .STDBY_ENABLE("DISABLED"),
        .DPHASE_SOURCE("DISABLED"),
        .OUTDIVIDER_MUXA("DIVA"),
        .OUTDIVIDER_MUXB("DIVB"),
        .OUTDIVIDER_MUXC("DIVC"),
        .OUTDIVIDER_MUXD("DIVD"),
        .CLKI_DIV(CLKI_DIV),
        .CLKOP_ENABLE("ENABLED"),
        .CLKOP_DIV(CLKOP_DIV),
        .CLKOP_CPHASE(CLKOP_CPHASE),
        .CLKOP_FPHASE(0),
        .CLKOS_ENABLE("ENABLED"),
        .CLKOS_DIV(CLKOS_DIV),
        .CLKOS_CPHASE(CLKOS_CPHASE),
        .CLKOS_FPHASE(0),
        .FEEDBK_PATH("CLKOP"),
        .CLKFB_DIV(CLKFB_DIV)
    ) pll_i (
        .RST(1'b0),
        .STDBY(1'b0),
        .CLKI(clk_in),
        .CLKOP(clk_5x_out),
        .CLKOS(clk_out),
        .CLKFB(clk_5x_out),
        .CLKINTFB(),
        .PHASESEL0(1'b0),
        .PHASESEL1(1'b0),
        .PHASEDIR(1'b1),
        .PHASESTEP(1'b1),
        .PHASELOADREG(1'b1),
        .PLLWAKESYNC(1'b0),
        .ENCLKOP(1'b0),
        .LOCK(locked)
    );

    // ensure clock lock is synced with output clock
    reg locked_sync;
    always @(posedge clk_out) begin
        locked_sync <= locked;
        clk_locked <= locked_sync;
    end
endmodule

And we can instantiate it like this (370/74 MHz output clocks):

wire clk_out, clk_out_5x, clk_locked;
clock2_gen #(
    .CLKI_DIV(5),
    .CLKFB_DIV(74),
    .CLKOP_DIV(2),
    .CLKOP_CPHASE(1),
    .CLKOS_DIV(10),
    .CLKOS_CPHASE(5)
) clock2_gen_inst (
    .clk_in(clk_25m),  // 25 MHz input clock from dev board
    .clk_5x_out(clk_out_5x),
    .clk_out(clk_out),
    .clk_locked(clk_locked)
);

Example Project

To see a complete design using the clock2_gen module, check out the ULX3S version of FPGA Graphics. The FPGA Graphics design generates 370 and 74 MHz clocks.

More Clocks

The ECP5 PLL supports three secondary clocks. The additional clocks work the same way but with their own divider and phase parameters using the prefix CLKOS2_ and CLKOS3_.

Yosys Output

Check the Yosys log to ensure you’ve got your clock parameters right:

For example, our input clock is 25 MHz and we generate a 50 MHz output clock.

Info: Generating derived timing constraints...
Info:     Input frequency of PLL 'clock_gen_inst.pll_i' is constrained to 25.0 MHz
Info:     Derived frequency constraint of 50.0 MHz for net clk_out
Info: Promoting globals...
Info:     promoting clock net clk_out to global network

Lattice Diamond

I haven’t tested these modules in Lattice Diamond, but they’ll probably work if you add these attributes:

// HDL clock attributes
(* FREQUENCY_PIN_CLKI="nn" *)
(* FREQUENCY_PIN_CLKOP="nn" *)
(* FREQUENCY_PIN_CLKOS="nn" *)

Set the values to match the frequency of the input and output clock(s).

What’s Next?

Now you’re fearlessly generating your own clock frequencies, why not check out my other FPGA & RISC-V Tutorials?

Get in touch on Mastodon, Bluesky, or X. Enjoy my work? Please sponsor me. 🙏

References