You are currently viewing A Digital Pattern Generator – Part 3

A Digital Pattern Generator – Part 3

As configuring the PLL is such an important part of the project I want to explain it in more detail. What I am going to describe here is specific to the FPGA used in this project but the general principles can certainly be transferred to other FPGAs.



Configuring the PLL

The following discussion refers to Altera’s MAX10 PLL and especially to the ALTPLL_RECONFIG Ip core. For details go see MAX10 Clocking and PLL User Guide.

The PLL’s behavior can be configured by setting these parameters

  • Pre-scale counter (N)
  • Feedback counter (M)
  • Post-scale output counters (C0C4)
  • Charge pump current (ICP)
  • Loop filter components (R, C

The PLL Reconfiguration Scan Chain

These parameters are embedded in a structure called PLL Reconfiguration Scan Chain. In C this structure can be described like so.

struct __attribute__((packed))PllChain
{
  int c4_lowcount:8;
  int c4_odd :1;
  int c4_highcount:8;
  int c4_bypass :1;
  int c3_lowcount:8;
  int c3_odd :1;
  int c3_highcount:8;
  int c3_bypass :1;
  int c2_lowcount:8;
  int c2_odd :1;
  int c2_highcount:8;
  int c2_bypass :1;
  int c1_lowcount:8;
  int c1_odd :1;
  int c1_highcount:8;
  int c1_bypass :1;
  int c0_lowcount:8;
  int c0_odd :1;
  int c0_highcount:8;
  int c0_bypass :1;
  int m_lowcount:8;
  int m_odd :1;
  int m_highcount:8;
  int m_bypass :1;
  int n_lowcount:8;
  int n_odd :1;
  int n_highcount:8;
  int n_bypass :1;
  int cp_current:3;
  int cp_reserved:5;
  int vcopostscale :1;
  int lf_resistance:5;
  int lf_capacitance:2;
  int lfreserved:2;
};

For our purposes we only need the three parameters m, n and c to set the main PLL output frequency. To write these values into the scan chain we can use this function.

bool configurePll(uint8_t m, uint8_t n, uint8_t c )
{
  FPGA::PllChain pllchain;
  memset (&pllchain, 0, sizeof(FPGA::PllChain));
  // default configuration 200MHz
  pllchain.c4_bypass = 1;
  pllchain.c3_bypass = 1;
  pllchain.c2_bypass = 1;
  pllchain.c1_bypass = 1;
  pllchain.c0_lowcount = c/2;
  pllchain.c0_highcount = (c+1)/2;
  pllchain.c0_odd = (c & 1) ? 1 : 0;
  pllchain.c0_bypass = (c == 1) ? 1 : 0;
  pllchain.m_lowcount = m/2;
  pllchain.m_highcount = (m+1)/2;
  pllchain.m_odd = (m & 1) ? 1 :0 ;
  pllchain.m_bypass = (m == 1) ? 1 : 0;
  pllchain.n_lowcount = n/2;
  pllchain.n_highcount = (n+1)/2;
  pllchain.n_odd = (n & 1) ? 1 : 0;
  pllchain.n_bypass = (n == 1) ? 1 : 0;
  pllchain.cp_current = 1; // 1
  pllchain.lf_resistance = 27; // 27
  pllchain.lf_capacitance = 0; // 0
  return FPGA::configurePll(&pllchain);
}

Before we continue to the FPGA we need to answer the question of how to determine m, n and c for a given frequency. For this we set up a reference table which lists all possible PLL frequencies with their corresponding values for m, n and c. We can then look up the values in this table.

Computing the PLL table

struct __attribute__((packed)) PLLtable
{
  uint16_t f10kHz;
  uint8_t m;
  uint8_t n;
  uint8_t c;
};

constexpr PLLtable plltable[]
{
  {12000,12,1,5},
  {12009,245,17,6},
  {12012,185,11,7},
  {12013,173,9,8},  
  …
};

Such a table can be computed using the following Python skript.

VCOMAX = 1e9
VCOMIN = 599.9e6
M_MAX = 255
N_MAX = 255
C_MAX = 255
F0 = 50e6
FMAX = 370e6
FMIN = 120e6
combinations = {}


def computeCombinations():
  with open(“plltable.txt”, “w”) as file:
    for m in range(1, M_MAX+1):
      for n in range(1, N_MAX+1):
        fVCO = F0 * m / n
        if fVCO > VCOMAX or fVCO < VCOMIN:
          continue

        for c in range(1, C_MAX+1):
          f = fVCO / c
          if f >= FMIN and f <= FMAX:
            f10kHz = int(f/10000)
            try:
              if combinations[f10kHz]:
                pass
             except:
               combinations[f10kHz] = (m,n,c)
               file.write(f”{f10kHz}, {m}, {n}, {c}\n”)

Writing the Scan Chain into the PLL

On the FPGA we define some registers and instatiate the IP core

wire pll_clk;
wire pll_locked;
reg pll_areset = 0;
reg pll_configupdate = 0;
reg pll_scanclkena = 0;
wire pll_scandata;
wire pll_scandataout;
wire pll_scandone;

// PLL control
reg [7:0] pllchain [17:0];
reg [4:0] pllchainbyteread = 0;
reg [2:0] pllchainbitwrite = 0;
reg [4:0] pllchainbytewrite = 0;
reg pllwritestart = 0;
reg pllsavedrunningstate = 0;
assign pll_scandata = pllchain[pllchainbytewrite][pllchainbitwrite];

PLL200MHzRec PLL200MHzRec_inst (
  .areset ( pll_areset ),
  .configupdate ( pll_configupdate ),
  .inclk0 ( clk ),
  .scanclk ( clk ),
  .scanclkena ( pll_scanclkena ),
  .scandata ( pll_scandata ),
  .c0 ( pll_clk ),
  .locked ( pll_locked ),
  .scandataout ( pll_scandataout ),
  .scandone ( pll_scandone )
);

After the command interpreter has received the last byte of the scan chain from the MCU and put it into pllchain it asserts pllwritestart. This activates the task below which transfers the scan chain into the PLL.

reg [1:0] delayscandata = 0;
// Program PLL scanchain into the device
always @(negedge clk ) begin
  if (pllwritestart) begin
    pll_scanclkena <= 1;
    if (delayscandata < 2) begin
      delayscandata <= delayscandata + 2’d1;
    end else begin
      pllchainbitwrite <= pllchainbitwrite + 3’d1;
      if (pllchainbitwrite == 3’d7) begin
        pllchainbytewrite <= pllchainbytewrite + 5’d1;
        if (pllchainbytewrite == 5’d17)
          pll_scanclkena <= 0;
      end
    end
  end else begin
    delayscandata <= 0;
    pllchainbitwrite <= 0;
    pllchainbytewrite <= 0;
  end
end

After the transfer is complete and the IP core asserts pll_scandone we have to pulse pll_areset to finish the reconfiguration.

guest

0 Comments
Oldest
Newest Most Voted