Skip to content

Instantly share code, notes, and snippets.

@maqifrnswa
Last active February 22, 2021 02:45
Show Gist options
  • Save maqifrnswa/f83bdcf1bf73b5f656a3053282db4de3 to your computer and use it in GitHub Desktop.
Save maqifrnswa/f83bdcf1bf73b5f656a3053282db4de3 to your computer and use it in GitHub Desktop.

Is your feature request related to a problem? Please describe. The current method for finding the PLL and multisync ratios for register values are based on some hardcoded values that leads to (rounding) errors between the desired frequency and what gets set. Enclosed is a computationally cheap approach to find values for the dividers that (1) allow you to set the integer bit on the output divider and (2) give you the exact values needed for a given frequency. It works for one or two clocks on a PLL.

Describe the solution you'd like Either update the functions the calculate the PLL and multisync values, or add a new function for "enhanced_accuracy_set_freq" or something like that.

The current method sets the PLL to 800 MHz then finds approximate values for the A+B/C output divider ratios to set the correct frequency by setting C=RFRAC_DENOM. That can work for many situations, but introduces error on the order of Vxtal/RFRAC_DENOM. For the same computational effort, you can find exact solutions. Below is a demonstration of how using a single clock on a PLL, then I'll show with 2 clocks per PLL.

Single Clock per PLL, exact solution, set integer divide bit on multisync output divider

Fundamentally, this is the equation that governs the 5351:

600 MHz < Fout*x1/y1 = Fxtal*x2/y2 < 900 MHz

I just use x1/y1 instead of A +B/C for simplicity. x1,y1,x2, and y2 are all integers, and y1, y2 < 1,048,575. We're also told that we should try to make as many output dividers integers (even better if they are even integers so we can set the integer bit!) So let's rewrite it as:

600 MHz < Fout*A1 = Fxtal*x2/y2 < 900 MHz

Where A1 is the output divisor that we'll make sure is an even integer. Here's the pseudocode that is explained in more detail below:

A1 = floor(900e6/Fout0)

COMMON_SCALING_FACTOR=gcd(Fout, Fxtal) // greatest common denominator
// see text below for a computationally faster way, you don't really need the gcd, any common factor will do.

y2 = min(Fxtal/COMMON_SCALING_FACTOR, 1048757)
// The above accounts for requesting Fouts such that y1,y2 > 1048757. Only happens when Fout's precision is on the order
// of 3 Hz. So it should rarely happen.
// However, if you want the exact solution: follow steps below for "fractional divide" below.

x2 = Fout*A1*y2/Fxtal

PLL Feedback Equation:
A+B/C
A=floor(x2, y2)
B = x2 % y2
C = y2

Multisynth 0 Output Divider
A+B/C
A = A1
B = 0
C = 1
You can set the even integer divide bit!

Example

In this example, we'll make an output at Fout=7.1MHz using a Fxtal=27 MHz crystal.

  1. Find A1. A1= round down to the nearest even integer (900MHz/Fout). To do that, first pick your Fco. Let's say close to 900 MHz (Si does that for their clockbuilder pro, so we can too).
A1 < 900MHz/7.1MHz
A1 < 126.76
A1 = 126
  1. Next, we find y2 using y2 = Fxtal/COMMON_SCALING_FACTOR where COMMON_SCALING_FACTOR is any integer that goes into Fxtal and Fout an integer number of times. Doing that guarantees that y2 is an integer and that Fout*y2/Fxta=x2 is an integer. How do you find that? Two ways:
    1. Fast way: make COMMON_SCALING_FACTOR=10^something such that you just drop out all those zeros you don't need. In our case. Fxtal = 27,000,000 and Fout=7,100,000, so make COMMON_SCALING_FACTOR=10,000 so you get two integers when you divide Fout and Fxtal by COMMON_SCALING_FACTOR: 7,100,000/COMMON_SCALING_FACTOR=71 and 27,000,000/COMMON_SCALING_FACTOR=270. Then y2 = Fxtal/COMMON_SCALING_FACTOR=270
    2. Slowest computationally, but you can exactly find the maximum COMMON_SCALING_FACTOR - which is also known as the greatest common denominator between the two. But it's slow. y2 = Fxtal/gcd(Fout,Fxtal) where gcd is the greatest common denominator using tricks like Euclidian Expansion. In this case, gcd = 10,000, just a coincidence that it's what we choose above.
  2. Now you can find x2 = Fout*A1/(Fxtal/y2)=7.1e6*126/(27e6/270)=8946
  3. Finally, you care write the exact solution!
PLL feedback ration values:
A+B/C = x1/y1 = A1 = 126
A = 126 (we can set the integer divide bit!)
B = 0
C = 1

Multisynth equation
A+B/C = x2/y2 = 8946/270
A = floor(8946, 270) = 31
B = 8946 % 270 = 36
C = 270
A+B/C = 31 + 36/270

You have to make sure that y2<=1,048,757. If it doesn't, see the next section

Single Clock per PLL, exact solution, fractional divide on multisync output (if you really want Hz level precision)

  1. You have to make sure that y2<=1,048,757 If a Fout was chosen such that y2>1,048,757 (that happens if you want a lot of precision in your output frequency), you can either:
    1. just go and put a hard cap on y2 = 1,048,757 and accept the tiny error (on the order of 3 Hz)
    2. or you get an exact solution, but lose out on using the scaling multiplier for A1. In that case, you don't need to find a COMMON_SCALING_FACTOR anymore. Instead just do the following:
y1 = Fout/INTEGER_FACTOR1
x1 = 900e6*/INTEGER_FACTOR1  <-- needs to be an integer. If you choose INTEGER_FACTOR1 to be 10^something, this will be an integer too!
Multisynth output equation:
A+B/C
A = floor(x1, y1)
B = x1 % y1
C = y1

where INTEGER_FACTOR1 is any integer that also makes y1 an integer such that y1 < 1,048,757 and is wholly divisible into 900 MHz. That makes it easy - you can pick 10^something. And you can do the same thing with the feedback equation

y2 = Fxctl / INTEGER_FACTOR2
x2 = 900e6*/INTEGER_FACTOR2
PLL feedback equation:
A+B/C
A =floor(x2, y2)
B = x2 % y2
C = y1

where INTEGER_FACTOR2 is any integer that also makes y2 an integer such that y2 < 1,048,757 and is wholly divisible into 900 MHz. That makes it easy - you can pick 10^something.

Multiple clocks per PLL, exact solution, at least 1 integer divide multisync output (maybe both if you are lucky!)

Now we have 2 Fouts (you can extend this to as many Fouts per PLL that you'd like, follow the same exact proceedure):

600 MHz <  Fout0*x0/y0 = Fout1*x1/y1 = Fxtal*x2/y2 < 900 MHz

We'll guarantee that at least 1 output can set the multisynth even integer divide bit (where Fout0 < Fout1)

600 MHz <  Fout0*A0 = Fout1*x1/y1 = Fxtal*x2/y2 < 900 MHz

We go through the same process as above

A0 = floor(900e6/Fout0)

Find the COMMON_SCALING_FACTOR between Fout0, Fout1, Fxtal either by:
SLOW method: COMMON_SCALING_FACTOR=gcd(gcd(Fout0,Fout1),Fxtal)
FAST method: COMMON_SCALING_FACTOR = 10^something that gets us the right significant figures
such that COMMON_SCALING_FACTOR wholy divides into Fout0, Fout1, and Fxtal

y1 = min(Fout1/COMMON_SCALING_FACTOR, 1048757)
y2 = min(Fxtal/COMMON_SCALING_FACTOR, 1048757)
The above accounts for requesting Fouts such that y1,y2 > 1048757. If you need precision, follow the above steps using INTEGER_FACTORX for each Fout and Fxtal instead of a single COMMON_SCALING_FACTOR.

x1 = Fout0*A0*y1/Fout1
x2 = Fout0*A0*y2/Fxtal

PLL Feedback Equation:
A+B/C
A=floor(x2, y2)
B = x2 % y2
C = y2

Multisynth 0 Output Divider
A+B/C
A = A0
B = 0
C = 1
You can set the even integer divide bit!

Multisynth 1 output Divider
A+B/C
A= floor(x1, y1)
B = x1 % y1
C = y1
If B = 0, you can set the even integer divide bit here too! (basically, it's a harmonic or copy of Fout0)

Everything integer multiples

Maybe this can be an extra function, but we can say you want everything integer multiples (PLL feedback divider and multisync dividers all integers). Here's the required condition:

Fout0*A0=Fout1*A1=Fxtal*A2=Fvco

For this to work, you need Fvco to be a integer factor times the least common multiple, and between 600-900 MHz. They are a little limiting, but possible. Here are some examples

One output, 1 PLL:
Fout0 = 7 MHz
Fxtal = 25 Mhz
lcm(Fout, Fxtal) = 175 MHz
Fvco = ceil(600 MHz / lcm(Fout, Fxtal) )* 600 MHz = 700 MHz
A0 = 700/7 = 100
A2 = 700/25 = 28

One output, 1 PLL:
Fout0 = 7 MHz
Fxtal = 27 Mhz
lcm(Fout0, Fxtal) = 189 MHz
Fvco = ceil(600 MHz / lcm(Fout, Fxtal) )* 600 MHz = 756 MHz
A0 = 756/7 = 108
A2 = 756/27 = 28

Two outputs, 1 PLL:
Fout0 = 6 MHz
Fout1 = 4 Mhz
Fxtal = 25 Mhz
lcm( lcm(Fout0, Fxtal), Fout1) = 300MHz
Fvco = ceil(600 MHz / lcm(Fout, Fxtal) )* 600 MHz = 600 MHz
A0 =600/6 = 100
A1 = 600/4 = 150
A2 = 600/25 = 24

Two outputs, 1 PLL:
Fout0 = 6 MHz
Fout1 = 7 Mhz
Fxtal = 25 Mhz
lcm( lcm(Fout0, Fxtal), Fout1) = 1.05 GHz
Fvco = ceil(600 MHz / lcm(Fout, Fxtal) )* 600 MHz = 1.05 GHz
Not possible! The fastest we can go is 900 Mhz!

Describe alternatives you've considered A bunch, but mathematically this is the purest and computationally the simplest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment