PLL settings calculator — Parallax Forums

# PLL settings calculator

Posts: 13,564
In getting Spin2 going, I realized it would be really nice to just be able to set the clock frequency using 'CON _clkfreq = 250_000_000' and let the compiler work out the details, on the assumption there's a 20MHz crystal, which could be overridden by an _xinfreq declaration;

I spent all night working out the details of this PLL settings calculator. It favors low crystal divisors (to maintain high Fpfd), never lets Fpfd frop below 250KHz, and keeps the VCO between 99MHz and 201MHz, unless the frequency called for is higher than that. It's not that complicated and has a search space of 1,024 possibilities.
I wrote it in SmallBASIC and now that the concept is proven, I can implement it in the compiler. Here's the SmallBASIC code:
```xinfreq =  12000000
clkfreq = 148500000

error = 1e9
for pppp = 0 to 15
if pppp = 0 then post = 1 else post = pppp * 2
for divd = 64 to 1 step -1
Fpfd = round(xinfreq / divd)
mult = round(clkfreq * post / Fpfd)
Fvco = Fpfd * mult
Fout = round(Fvco / post)
e = abs(Fout - clkfreq)
if (e <= error) and (Fpfd >= 250000) and (mult <= 1024) and (Fvco > 99e6) and ((Fvco <= 201e6) or (Fvco <= clkfreq + 1e6)) then
result_divd = divd
result_mult = mult
result_post = post
result_pppp = (pppp-1) & 15 '%1111 = /1, %0000..%1110 = /2..30
result_Fpfd = Fpfd
result_Fvco = Fvco
result_Fout = Fout
error = e
endif
next
next

print "XINFREQ:  "; xinfreq, "  (XI Input Frequency)"
print "CLKFREQ:  "; clkfreq, "  (PLL Goal Frequency)"
print
print "Divd: "; result_divd, "D field = "; result_divd-1
print "Mult: "; result_mult, "M field = "; result_mult-1
print "Post: "; result_post, "P field = "; result_pppp
print
print "setclk(%1_";
print right(bin(result_divd-1),6); "_";
print right(bin(result_mult-1),10); "_";
print right(bin(result_pppp),4); "_10_11, "; clkfreq; ")"
print
print "Fpfd:  "; result_Fpfd
print "Fvco:  "; result_Fvco
print
print "Fout:  "; result_Fout
print "Error: "; result_Fout - clkfreq
```

You can set the xinfreq and clkfreq values and it will come up with optimal PLL settings. Here is some output. SmallBASIC doesn't let me copy the screen text, so I had to do a screenshot:

«13

• Posts: 13,564
edited 2020-01-08 16:55
This will make setting and changing the clock frequency really simple:
```con
_clkfreq = 320_000_000

pub go
repeat
pinnot(16)
```

I've got the Spin2 interpreter lodged into PNut.exe now, so F12 compiles and downloads Spin code. I made a quick list of all the things I need to do to get it set up right, but I got sidetracked on this PLL thing all night.
• Posts: 708
Excellent! I just shamelessly stole that code lock, stock and barrel!
• Posts: 11,934
Are these settings going to give you the lowest jitter in any VGA mode?

I think it was ozpropdev who came up with the code I use that includes:
```_XDIV         = 2'10                                            ' crystal divider to give 1MHz
_XMUL         = 25'125                                          ' crystal / div * mul
_XDIVP        = 1                                             ' crystal / div * mul /divp to give _CLKFREQ (1,2,4..30)
```

Maybe _XDIV might be nice to still play around with?

But, actually, I guess one could just restart the clock in assembly if one wanted to explore different settings...
• Posts: 14,650
cgracey wrote: »
This will make setting and changing the clock frequency really simple:

Does the listing file include the report above ?
Also, it can be useful to allow users to specify the error threshold, and to have the search software function report all found values (_XDIV _XMUL _XDIVP) < given error
Sometimes jitter matters more than absolute precision, or users may be wanting to do some margin testing.

• Posts: 10,863
Chip has it favouring the low jitter end already.
It favors low crystal divisors (to maintain high Fpfd), never lets Fpfd frop below 250KHz, and keeps the VCO between 99MHz and 201MHz, unless the frequency called for is higher than that.
• Posts: 10,863
Minimum target clkfreq is 3.3 MHz. It'll bail on anything smaller.

• Posts: 10,863
Rayman wrote: »
Are these settings going to give you the lowest jitter in any VGA mode?
The biggest source of visible crawling in VGA output is when the streamer NCO is not set to a whole divider of the sysclock.

• Posts: 1,443
Sheepishly had to recreate Chip's code in Python3:
```#
# Chip Gracey's PLL settings calculator in Python3
#
#

xinfreq =  12000000
clkfreq = 148500000

error = 0x1e9

def right(value, count):
# To get right part of string, use negative first index in slice.
return value[-count:]

for pppp in range(0,15,1):
if pppp == 0:
post = 1
else:
post = pppp * 2

for divd in range(64,1,-1):
Fpfd = round(xinfreq / divd)
mult = round(clkfreq * post / Fpfd)
Fvco = Fpfd * mult
Fout = round(Fvco / post)
e = abs(Fout - clkfreq)

if (e <= error) and (Fpfd >= 250000) and (mult <= 1024) and (Fvco > 99e6) and ((Fvco <= 201e6) or (Fvco <= clkfreq + 1e6)):
result_divd = divd
result_mult = mult
result_post = post
# '%1111 = /1, %0000..%1110 = /2..30
result_pppp = (pppp-1) & 15
result_Fpfd = Fpfd
result_Fvco = Fvco
result_Fout = Fout
error = e

print("XINFREQ:  ", xinfreq, "  (XI Input Frequency)")
print("CLKFREQ:  ", clkfreq, "  (PLL Goal Frequency)")
print("Divd: ", result_divd, " D field = ", result_divd - 1)
print("Mult: ", result_mult, "M field = ", result_mult - 1)
print("Post: ", result_post, " P field = ", result_pppp)
print()
print("setclk(%1_" + format(result_divd-1,'06b') + "_" + format(result_mult-1,'010b') + "_" + format(result_pppp,'04b') + "_10_11, " + str(clkfreq) + ")")
print()
print("Fpfd:  ", result_Fpfd)
print("Fvco:  ", result_Fvco)
print()
print("Fout:  ", result_Fout)
print("Error: ", result_Fout - clkfreq)
```

dgately
• Posts: 10,863
edited 2020-01-09 10:42
Here's my post-clock-calculation actioning code that has been quite heavily tested. It can be a reference for porting to Spin or C or BASIC:
```'recalculate baud divider (clk_freq / asyn_baud) of diag comport using cordic
'  low bauds won't operate at high sysclocks, the divider only has 16-bit reach
qdiv	clk_freq, asyn_baud		'comport divider
qfrac	#1, asyn_baud			'remainder scale factor, 2**32 / baud
getqx	pa				'comport divider
getqy	pb				'divider remainder, for .6 fraction
getqx	temp1				'scale factor
qmul	pb, temp1			'convert remainder to a "big" fraction
getqx	pb				'fractional component of comport divider
rolword	pa, pb, #1			'16.16 comport divider
sets	pa, #7				'comport 8N1 framing (bottom 10 bits should be replaced but 9's enough)

'make sure not transmitting on comport before adjusting hardware
.txwait		rqpin	inb, #DIAGTXPIN	wc		'transmiting? (C high == yes)
if_c	jmp	#.txwait

wxpin	pa, #DIAGTXPIN			'set tx baud and framing (divider format is 16.0 if divider >= 1024.0)
wxpin	pa, #DIAGRXPIN			'set rx baud and framing (divider format is 10.6 if divider < 1024.0)

'adjust hardware to new XMUL sysclock frequency
andn	clk_mode, #%11			'clear the two select bits to force RCFAST selection
hubset	clk_mode			'**IMPORTANT**  Switches to RCFAST using known prior mode

mov	clk_mode, xtalmul		'replace old with new ...
sub	clk_mode, #1			'range 1-1024
shl	clk_mode, #8
or	clk_mode, ##(1<<24 + (XDIV-1)<<18 + XPPPP<<4 + XOSC<<2)
hubset	clk_mode			'setup PLL mode for new frequency (still operating at RCFAST)

or	clk_mode, #XSEL			'add PLL as the clock source select
waitx	##25_000_000/100		'~10ms (at RCFAST) for PLL to stabilise
hubset	clk_mode			'engage!  Switch back to newly set PLL
ret			wcz
```

EDIT: Obviously, my modifying "clk_mode" variable isn't correct for Chip's fully calculated version. It would be a case of replacing the four instructions of "replace old with new ..." with a simple "MOV clk_mode, result_clkmode" instead.

• Posts: 13,564
edited 2020-01-09 14:32
I realized that by setting the initial error term, tolerance can be specified in Hz. I've added stuff into Spin2 to support this. It makes it nice, because you can be sure you're within desired tolerance. If not, the compiler reports an error. This alleviates the need for detailed reporting.

Here is the PLL calculator in 80386 that is inside the compiler now:
```;
;
; 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

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)
mov	edx,0
shl	eax,1			;x2 for later rounding
rcl	edx,1
div	[@@divd]		;divide edx:eax by divd
inc	eax			;round quotient
shr	eax,1
mov	[@@fpfd],eax

mov	eax,[@@clkfreq]		;mult = round(clkfreq * post / fpfd)
shl	eax,1			;x2 for later rounding
mul	[@@post]		;multiply clkfreq by post --> edx:eax
div	[@@fpfd]		;divide edx:eax by fpfd
inc	eax			;round quotient
shr	eax,1
mov	[@@mult],eax

mov	eax,[@@fpfd]		;fvco = fpfd * mult
mul	[@@mult]
mov	[@@fvco],eax

mov	eax,[@@fvco]		;fout = round(fvco / post)
mov	edx,0
shl	eax,1			;x2 for later rounding
rcl	edx,1
div	[@@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?
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
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
```

Man, the forum software hides things when it sees two @ symbols together.

ModEdit: Code attached in a txt file
• Posts: 13,564
edited 2020-01-09 13:39
These are all the ways in which clock setup can be done in Spin2 now. I think every angle is covered:
```CON
_clkfreq = 250_000_000	'Set 20MHz-crystal+PLL mode (%10_11)
' _errfreq = 1_000		'(optional error tolerance)

CON
_xtlfreq =  12_000_000	'Set crystal+PLL mode (_xtlfreq >= 16MHz ? %10_11 : %11_11)
_clkfreq = 148_500_000
' _errfreq = 100		'(optional error tolerance)

CON
_xinfreq =   4_000_000	'Set input+PLL mode (%01_11)
_clkfreq = 320_000_000
' _errfreq = 0			'(optional error tolerance)

CON
_xtlfreq =  24_000_000	'Set crystal mode (_xtlfreq >= 16MHz ? %10_10 : %11_10)

CON
_xinfreq =  50_000_000	'Set input mode (%01_10)

CON
_rcfast			'Set RCFAST mode (%00_00) - this is the default clock mode

CON
_rcslow			'Set RCSLOW mode (%00_01)

CON
_clkmode = %1_000000_0000001111_1111_10_11	'Set parameters directly
_clkfreq = 320_000_000
```

Only one of these schemes at a time is acceptable to the compiler. It checks to make sure valid label sets are in the user's code and then acts accordingly.
• Posts: 10,863
edited 2020-01-09 14:03
Oh, it's not runtime reusable.
• Posts: 13,564
evanh wrote: »
Oh, it's not runtime reusable.

It would have to be an object to be so, unless I made it part of the interpreter.
• Posts: 2,361
edited 2020-01-09 14:33
cgracey wrote: »

Man, the forum software hides things when it sees two @ symbols together.

FYI.. That issue is top of the list to be resolved, and need not be an issue much longer.

As a quick solution, the code has been attached as a text file to your post above.
• Posts: 13,564
> @VonSzarvas said:
> cgracey wrote: »
>
>
> Man, the forum software hides things when it sees two @ symbols together.
>
>
>
>
> FYI.. That issue is top of the list to be resolved, and need not be an issue much longer.
>
> As a quick solution, the code has been attached as a text file to your post above.

Thanks. I noticed earlier that sometimes, rather than quote a message, it would just put '>' signs in front of each paragraph.
• Posts: 2,361
edited 2020-01-09 15:26
cgracey wrote: »
> @VonSzarvas said:
> cgracey wrote: »
>
>
> Man, the forum software hides things when it sees two @ symbols together.
>
>
>
>
> FYI.. That issue is top of the list to be resolved, and need not be an issue much longer.
>
> As a quick solution, the code has been attached as a text file to your post above.

Thanks. I noticed earlier that sometimes, rather than quote a message, it would just put '>' signs in front of each paragraph.

I'd been trying to figure out the source of that, and I think I just found the issue in the same plugin that's spoiling the [@ @] chars (and a bunch of other char combos). Thanks for mentioning it, as I wasn't entirely sure if that's something you were deliberately doing !
• Posts: 10,170
Why not leave what Chip did, and roll the calc and clock change code into an object?

Anyone needing a runtime change need only include and call it?
• Posts: 4,867
@cgracey , what is the default _clkfreq to use if only _xtlfreq or _xinfreq is given?
• Posts: 13,564
edited 2020-01-10 02:16
> @ersmith said:
> @cgracey , what is the default _clkfreq to use if only _xtlfreq or _xinfreq is given?

You need to assign a value to _xtlfreq or _xinfreq and that value becomes clkfreq. Also, clkmode is set to %1110 or %1010 for _xtlfreq or %0110 for _xinfreq. There is no default clkfreq value for those cases.
• Posts: 1,683
Will Spin2 be able to change the clock safely, when previous sysclk is not known?
• Posts: 13,564
edited 2020-01-10 10:39
TonyB_ wrote: »
Will Spin2 be able to change the clock safely, when previous sysclk is not known?

The previous clkmode must be known in order to safely switch away from it. That value is always kept in Spin2, so there's never a chance of the clkmode being unknown. Well, if a programmer didn't use the CLKSET instruction and just did a HUBSET, that would cause the value to become unknown.
• Posts: 11,934
Why can't you safely switch clkfreq? Can one not always drop back to RCFAST and then to any PLL mode?
• Posts: 971
Chip knows better what's actually happening but that's how I read the docs:

If you switch clocks you have to wait until both clocks are high (or both are low) to avoid glitches. So when you switch from a faster clock to a slower one it sometimes takes more than serveral clock cycles of the faster one. If the PLLs is switched off before the clock switchover is completed then the processor hangs. This could have been avoided if PLL shutdown had been delayed until after switchover was sucessful. But I guess this wasn't implemented in the current silicon.
• Posts: 13,564
edited 2020-01-10 16:53
ManAtWork wrote: »
Chip knows better what's actually happening but that's how I read the docs:

If you switch clocks you have to wait until both clocks are high (or both are low) to avoid glitches. So when you switch from a faster clock to a slower one it sometimes takes more than serveral clock cycles of the faster one. If the PLLs is switched off before the clock switchover is completed then the processor hangs. This could have been avoided if PLL shutdown had been delayed until after switchover was sucessful. But I guess this wasn't implemented in the current silicon.

It just locks the current clock state (0/1) and then waits for the new source to transition to the same state before switching over to it.

The whole thing worked fine until I added a divide-by-one PLL output mode which bypassed the post-divider. When deselected, it glitches before the clock switcher grabs hold of the current state. Now, in case you are using the divide-by-one mode, you need to keep the PLL mode bits in the same state while switching to another clock source. This means you need to keep a copy of the current clock mode in RAM somewhere that you can employ to switch the clock mode later.
• Posts: 13,564
edited 2020-01-10 16:54
Rayman wrote: »
Why can't you safely switch clkfreq? Can one not always drop back to RCFAST and then to any PLL mode?

You need to switch from PLL mode to RCFAST mode while keeping the same PLL mode bits.

In Spin2, this is all taken care of automatically with a SETCLK(mode,freq) instruction.
• Posts: 11,934
Are you saying there is no safe way to change between an xtal mode and rcfast without knowing the current clock mode?
• Posts: 13,564
edited 2020-01-10 17:02
I improved the PLL calculator so that it acts numerically perfect now:
```xinfreq =   20000000
clkfreq =  333333333
errfreq =     100000

error = errfreq
for pppp = 0 to 15
if pppp = 0 then post = 1 else post = pppp * 2
for divd = 64 to 1 step -1
Fpfd = round(xinfreq / divd)
mult = round(clkfreq * (post * divd) / xinfreq)
Fvco = round(xinfreq * mult / divd)
Fout = round(Fvco / post)
e = abs(Fout - clkfreq)
if (e <= error) and (Fpfd >= 250000) and (mult <= 1024) and (Fvco >= 99e6) and ((Fvco <= 201e6) or (Fvco <= clkfreq + errfreq)) then
result_divd = divd
result_mult = mult
result_post = post
result_pppp = (pppp-1) & 15 '%1111 = /1, %0000..%1110 = /2..30
result_Fpfd = Fpfd
result_Fvco = Fvco
result_Fout = Fout
error = e
endif
next
next

print "XINFREQ:  "; xinfreq, "  (XI Input Frequency)"
print "CLKFREQ:  "; clkfreq, "  (PLL Goal Frequency)"
print
print "Divd: "; result_divd, "D field = "; result_divd-1
print "Mult: "; result_mult, "M field = "; result_mult-1
print "Post: "; result_post, "P field = "; result_pppp
print
print "setclk(%1_";
print right(bin(result_divd-1),6); "_";
print right(bin(result_mult-1),10); "_";
print right(bin(result_pppp),4); "_10_11, "; clkfreq; ")"
print
print "Fpfd:  "; result_Fpfd
print "Fvco:  "; result_Fvco
print
print "Fout:  "; result_Fout
print "Error: "; result_Fout - clkfreq
```

Here is the code that goes into the Spin2 compiler that is in PNut.exe:

• Posts: 4,867
edited 2020-01-10 17:11
Rayman wrote: »
Are you saying there is no safe way to change between an xtal mode and rcfast without knowing the current clock mode?

That is correct. (*edit: I think Chip has clarified below that it's PLL modes rather than xtal modes that have the issue). The convention that fastspin, TAQOZ, p2gcc, Catalina, and riscvp2 have adopted is that the current clock mode is stored at \$18 in HUB memory and the current clock frequency is at \$14. MicroPython and proplisp also follow this convention because the compilers used to build them do so.

loadp2 and the default ROM bootloader leave the system in RCFAST mode before they start the loaded program, so it's safe to set any mode in a freshly loaded program. The exception is that if the -PATCH option is given to loadp2 then it patches the loaded binary to contain the current clock frequency and mode at \$14-\$18 and leaves the system in that frequency (whatever was given as the -f option to loadp2).
• Posts: 11,934
Ok, thanks, I see now...

What about changing from one PLL clock mode to another?
Do you have to know the current clock mode in order to do this?
• Posts: 13,564
edited 2020-01-10 17:07
Rayman wrote: »
Are you saying there is no safe way to change between an xtal mode and rcfast without knowing the current clock mode?

It's just switching to the PLL mode or away from the PLL mode that is problematic. Switching among crystal, RCFAST, and RCSLOW modes is no problem.