P2 PLL/Clock considerations

in Propeller 2
The little picture above shows how I have understood the PLL/Clock generation in P2.
As one can see there are 2 Dividers and a PLL multiplier. This makes reaching the
desired clock possible using many combinations of the factors available.
- Which limits apply to fa and fb?
- Are there any constrains regarding divider and mulitplier factors?
- Is it better to divide the clock down and multiply then again or is it better to not divide in the first place?
- Are there any parts of the P2 that use fa or fb?
- What is the intention of the final divider that makes fb to fcpu? It seems redundant to me
Thanks for your time
Comments
The main constraint is: Fb (PLL output) 100Mhz to 350MHz. A PLL synchronizes a variable frequency oscillator to a reference frequency. These oscillators have certain operating frequency range.
The post divider is necessary if you want to run below 100MHz.
Phase noise is reduced by keeping Fa (divider output) as high as possible. This is mostly a concern for radio transmitters. So don't divide down unnecessarily. The PLL was improved with rev B so it works well with lower reference frequency. With 20MHz input, any divider value should be ok for most applications.
Here is a frequency table I generated some time ago to make setting the frequency/knowing the real frequency set: https://forums.parallax.com/discussion/173054/a-frequency-table-for-20-mhz-quartz-attached
As these are frequencies from the upper range, divider B is considered 1.
This begs another question: is there any way to bypass the PLL/multiplier/divider and just let the P2 be driven at whatever freq appears on the XIN pin?
It has to be noted that PNut / Propeller Tool (and I think flexspin uses the same algorithm?) are very good at calculating the clock settings for you. The algorithm used is described here: https://forums.parallax.com/discussion/comment/1486815/#Comment_1486815
Thank you all very much!
This information will help me
Most Xtal oscilators work like in the above diagram. So perhaps you can use a external clock on one of the xtal input pins.
Yep, there is a couple of options. Both supported in Spin2 and documented near end of the manual under the heading of "Clock Setup":
Both involve not defining the _clkfreq constant.
PS: The HUBSET mode bits of each mode are also provided in the same table.
PPS: And I see the two shared hubRAM variables are still located way off at $40 and $44 rather than the agreed $10 through $1f. Chip, you might want to get that fixed.
yes i also found the mode and frequency in hubstart+$40 and $44.
Yes, but I (1) wanted to set the frequency on the fly while changing video modes and (2) know what I already set. So I made this table. Or rather a program which generated it for me.
https://forums.parallax.com/discussion/comment/1486815/#Comment_1486815
viewing the pseudocode provided here the considerations seem to be:
The code needs some tweaking to exit the loops if it found a good value combination or found.
Best would be to run all loops and keep track of the best result
And to add a warning for anyone reading this, when changing frequencies, there are constraints on how to do this. Evan worked out the method to do it reliably so I’ll let him point you to the reference.
If you're using a high level language like Spin then it's handled for you. EDIT2: Spin2 provides the clkset() function.
If you are hitting the metal and want to use Pasm code to adjust the clock frequency then have a read here - https://forums.parallax.com/discussion/comment/1466528/#Comment_1466528
and related handover discussions - https://forums.parallax.com/discussion/comment/1466702/#Comment_1466702
Also, the hardware doc details it as well. Chip originally recommended and documented only using the "reuse prior mode" method. Others have since added the $F0 mode as an alternative, myself included. Chip hasn't said don't do this as yet, but he's also never given it a blessing. The existence of $F0 in the hardware doc is likely due to others adding it.
EDIT: Here is Chip's only comment on $F0 to date - https://forums.parallax.com/discussion/comment/1466494/#Comment_1466494
BTW: I don't use $F0 in my wrapper code, nor does Chip with Spin2. I do use it for quick hacks without the wrapper.
Here is the code in the compiler that determines the PLL setting for a requested frequency. You can read the comments to get an idea of its decision making:
; ; ; Calculate PLL setting ; ; on entry: eax = input frequency in Hz ; ebx = desired output frequency in Hz ; ecx = max allowable error in Hz ; ; on exit: eax = PLL mode with crystal bits cleared (eax[3:2]=0) ; ebx = actual output frequency in Hz ; c = 1 if setting found ; pll_calc: mov [@@xinfreq],eax mov [@@clkfreq],ebx mov [@@errfreq],ecx mov [@@found],0 ;clear the found flag in case no success mov [@@error],ecx ;set initial error allowance cmp [@@xinfreq],250000 ;xinfreq must be 250KHz to 500MHz jb @@abort cmp [@@xinfreq],500000000 ja @@abort cmp [@@clkfreq],3333333 ;clkfreq must be 3.333333MHz to 500MHz jb @@abort cmp [@@clkfreq],500000000 ja @@abort mov [@@pppp],0 ;sweep post divider from 1,2,4,6,..30 @@loop1: mov eax,[@@pppp] ;determine post divider value shl eax,1 jnz @@notzero inc eax @@notzero: mov [@@post],eax mov [@@divd],64 ;sweep xin divider from 64 to 1 @@loop2: mov eax,[@@xinfreq] ;fpfd = round(xinfreq / divd) shl eax,1 ;x2 for later rounding mov edx,0 ;xinfreq --> edx:eax div [@@divd] ;divide edx:eax by divd inc eax ;round quotient shr eax,1 mov [@@fpfd],eax mov eax,[@@post] ;mult = round((post * divd) * clkfreq / xinfreq) mul [@@divd] ;multiply post by divd --> eax shl eax,1 ;x2 for later rounding mul [@@clkfreq] ;multiply by clkfreq --> edx:eax div [@@xinfreq] ;divide edx:eax by xinfreq inc eax ;round quotient shr eax,1 mov [@@mult],eax mov eax,[@@xinfreq] ;fvco = round(xinfreq * mult / divd) shl eax,1 ;x2 for later rounding mul [@@mult] ;multiply xinfreq by mult --> edx:eax cmp [@@divd],edx ;if divd > edx then safe to divide ja @@safe mov eax,0 ;else, fvco = 0 jmp @@unsafe @@safe: div [@@divd] ;divide edx:eax by divd inc eax ;round quotient shr eax,1 @@unsafe: mov [@@fvco],eax mov eax,[@@fvco] ;fout = round(fvco / post) shl eax,1 ;x2 for later rounding mov edx,0 ;fvco --> edx:eax div [@@post] ;divide edx:eax by post inc eax ;round quotient shr eax,1 mov [@@fout],eax mov eax,[@@fout] ;abse = absolute(fout - clkfreq) sub eax,[@@clkfreq] jnc @@pos neg eax @@pos: mov [@@abse],eax cmp eax,[@@error] ;does this setting have lower or same error? ja @@nope cmp [@@fpfd],250000 ;is fpfd at least 250KHz? jb @@nope cmp [@@mult],1024 ;is mult 1024 or less? ja @@nope cmp [@@fvco],99000000 ;is fvco at least 99MHz? jb @@nope cmp [@@fvco],201000000 ;is fvco no more than 201MHz? jbe @@yep mov eax,[@@clkfreq] ;is fvco no more than clkfreq + errfreq? add eax,[@@errfreq] cmp [@@fvco],eax ja @@nope @@yep: mov [@@found],1 ;found the best setting so far, set flag mov eax,[@@abse] ;update error to abse mov [@@error],eax mov eax,[@@divd] ;set the divider field dec eax shl eax,18 mov [@@mode],eax mov eax,[@@mult] ;set the multiplier field dec eax shl eax,8 or [@@mode],eax mov eax,[@@pppp] ;set the post divider field dec eax and eax,1111b shl eax,4 or [@@mode],eax or [@@mode],01000003h ;set the pll-enable bit and select the pll mov eax,[@@fout] ;save the pll frequency mov [@@freq],eax @@nope: dec [@@divd] ;decrement divd and loop if not 0 jnz @@loop2 inc [@@pppp] ;increment pppp and loop if under 16 cmp [@@pppp],16 jb @@loop1 mov eax,[@@mode] ;get mode into eax mov ebx,[@@freq] ;get freq into ebx @@abort: shr [@@found],1 ;get found flag into c ret ddx @@xinfreq ddx @@clkfreq ddx @@errfreq ddx @@found ddx @@error ddx @@abse ddx @@pppp ddx @@post ddx @@divd ddx @@fpfd ddx @@mult ddx @@fvco ddx @@fout ddx @@mode ddx @@freq
For people who need some SPIN2 code to determine the clock mode for a given desired P2 operating frequency using the PLL here is the algorithm I use in my video driver to scan through and determine the clock mode setting to be used with HUBSET for minimizing the frequency error. You can customize the error tolerance, and if it can't meet the error tolerance a zero is returned so you know this requested frequency is outside the range of possible values:
CON ' clock source used below #0, CLKSRC_XTAL, CLKSRC_XIN ' setup one of these based on your P2 HW input clock, ' this will only be used if the PLL settings get automatically computed (see code below) 'CLKIN_HZ = _xtalfreq ' also only enable CLKSRC_XTAL below as CLKSRC 'CLKIN_HZ = _xinfreq ' also only enable CLKSRC_XIN below as CLKSRC CLKIN_HZ = 20000000 ' assume 20MHz crystal by default CLKSRC = CLKSRC_XTAL ' enable this for crystal clock source (default) 'CLKSRC = CLKSRC_XIN ' enable this for direct input clock source on XI (no crystal) ' parameters used when automatically determining PLL settings TOLERANCE_HZ = 500000 ' pixel clock accuracy will be constrained by this when no exact ratios are found MAXVCO_HZ = 350000000 ' for safety, but you could try to overclock even higher at your own risk MINVCO_HZ = 100000000 MINPLLIN_HZ = 500000 ' setting lower can find more PLL ratios but may begin to introduce more PLL jitter DAT PUB computeClockMode(desiredHz) : mode | vco, finput, f, p, div, m, error, bestError bestError := -1 repeat p from 0 to 30 step 2 ' compute the ideal VCO frequency f at this value of P if p <> 0 if desiredHz > MAXVCO_HZ/p ' test it like this to not overflow quit f := desiredHz * p else f := desiredHz if f > MAXVCO_HZ quit ' scan through D values, and find best M, retain best case repeat div from 1 to 64 'compute the PLL input frequency from the crystal through the divider finput := CLKIN_HZ/div if finput < MINPLLIN_HZ ' input getting too low, and only gets lower so quit now quit ' determine M value needed for this ideal VCO frequency and input frequency m := f / finput ' check for the out of divider range case if m +> 1024 quit ' zero is special and gets a second chance if m == 0 m++ ' compute the actual VCO frequency at this particular M, D setting vco := finput * m if vco +< MINVCO_HZ quit if vco +> MAXVCO_HZ next ' compute the error and check next higher M value if possible, it may be closer error := abs(f - vco) if m < 1024 and (vco + finput) +< MAXVCO_HZ if error > abs(f - (vco + finput)) error := abs(f - (vco + finput)) m++ ' retain best allowed frequency error and divider bits found so far if error +< bestError and error +< TOLERANCE_HZ+1 bestError := error mode := ((div-1) << 18) + ((m-1) << 8) + (((p/2 - 1) & $f) << 4) ' quit whenever perfect match found if bestError == 0 quit if bestError == 0 quit ' final clock mode format is this #%0000_000E_DDDD_DDMM_MMMM_MMMM_PPPP_CCSS if mode ' also set 15 or 30pF capacitor loading based on input crystal frequency mode |= (1<<24) ' enable PLL if (CLKSRC == CLKSRC_XTAL) ' enable oscillator and caps for crystal mode |= (CLKIN_HZ < 16000000) ? %1111 : %1011 else mode |= %0111 ' don't enable oscillator
Here is a small Python 3.7 program that will construct the neccersary pasm code to initialy set the clock
It will show up to 3 combinations and prefersts low preDivider and low Error values. Feel free to comment
and change
please change the upper most lines in the file to meet your needs! You need to input the desired
values in the program file. The ist no gui.
Edit python file extension is not allowed in forum so i post the .py file as a .txt file. please rename the file to *.py
befor using it