PLL settings calculator

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:


PLL_Calc.png
382 x 274 - 5K
«1

Comments

  • cgraceycgracey Posts: 12,195
    edited 2020-01-08 - 16:55:21
    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.
  • Excellent! I just shamelessly stole that code lock, stock and barrel!
  • 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...
  • jmgjmg Posts: 14,177
    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.

  • 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.
  • Minimum target clkfreq is 3.3 MHz. It'll bail on anything smaller.

  • 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.

  • 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
  • evanhevanh Posts: 8,619
    edited 2020-01-09 - 10:42:11
    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.

  • cgraceycgracey Posts: 12,195
    edited 2020-01-09 - 14:32:34
    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?
    		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
    		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
  • cgraceycgracey Posts: 12,195
    edited 2020-01-09 - 13:39:38
    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.
  • evanhevanh Posts: 8,619
    edited 2020-01-09 - 14:03:18
    Oh, it's not runtime reusable.
  • evanh wrote: »
    Oh, it's not runtime reusable.

    Whoops! I accidentally edited your post instead of quoting it...

    It would have to be an object to be so, unless I made it part of the interpreter.
  • VonSzarvasVonSzarvas Posts: 1,808
    edited 2020-01-09 - 14:33:17
    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.
  • > @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.
  • VonSzarvasVonSzarvas Posts: 1,808
    edited 2020-01-09 - 15:26:13
    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 !
  • 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?
  • @cgracey , what is the default _clkfreq to use if only _xtlfreq or _xinfreq is given?
  • cgraceycgracey Posts: 12,195
    edited 2020-01-10 - 02:16:09
    > @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.
  • Will Spin2 be able to change the clock safely, when previous sysclk is not known?
  • cgraceycgracey Posts: 12,195
    edited 2020-01-10 - 10:39:16
    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.
  • Why can't you safely switch clkfreq? Can one not always drop back to RCFAST and then to any PLL mode?
  • 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.
  • cgraceycgracey Posts: 12,195
    edited 2020-01-10 - 16:53:33
    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.
  • cgraceycgracey Posts: 12,195
    edited 2020-01-10 - 16:54:56
    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.
  • Are you saying there is no safe way to change between an xtal mode and rcfast without knowing the current clock mode?
  • cgraceycgracey Posts: 12,195
    edited 2020-01-10 - 17:02:00
    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:

  • ersmithersmith Posts: 3,814
    edited 2020-01-10 - 17:11:28
    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).
  • 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?
  • cgraceycgracey Posts: 12,195
    edited 2020-01-10 - 17:07:57
    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.
Sign In or Register to comment.