PLL settings calculator

13»

Comments

  • cgracey wrote: »
    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
    

    I've remembered that I wanted to make this into a runtime Pasm library function. Just completed transcribing it directly. Looking good so far. Next is to compact it down and comment with more than just the pseudo code.

    Anyone care to do a Spin version? I struggle with lack of spin knowledge.

  • I did my own Spin2 version here and I already use it as part of my video driver. It works out the clock mode for a desired frequency or returns 0 if it could not meet the tolerance. The only annoying part is figuring out the bottom bits of the clock mode automatically. I sort of need to provide that in extra constants which may be duplicating some SPIN2 constants (and hence prone to inconsistency + mismatch) because I'm not sure this information can be accessed the way I wanted in the lower level object.
    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
    
    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
    
  • Cool, thanks.
  • evanhevanh Posts: 9,854
    edited 2020-09-13 - 23:34:21
    Here's what I've been using historically. It's been very useful for my testing. It uses only XMUL (For the M field) as an input variable so makes it a small routine. The other components are all constants.
    'recalculate sysclock hertz using cordic
    		qmul	xtalmul, ##(XTALFREQ / XDIV / XDIVP)	'integer component of pre-divided crystal frequency
    		mov	pa, xtalmul
    		mul	pa, ##round((float(XDIV*XDIVP)+0.5) * (float(XTALFREQ)/float(XDIV)/float(XDIVP) - float(XTALFREQ/XDIV/XDIVP)))
    		qdiv	pa, ##(XDIV * XDIVP)		'fractional component of pre-divided crystal frequency
    		getqx	clk_freq			'result of integer component
    		getqx	pa				'result of fractional component
    		add	clk_freq, pa			'de-error the integer rounding
    

    It doesn't compute the clock mode because XMUL is the only component to adjust. So it goes in the opposite direction of computing the sys-clock frequency from the given mode components. This is then needed for further computing timing parameters like for the serial pins for example.
Sign In or Register to comment.