When TI’s CDCE62002 fails to lock

This post was written by eli on October 5, 2010
Posted Under: electronics,FPGA

I really banged my head on this one: I was sure I had set up all registers correctly, and still I got complete garbage at the output. Or, as some investigation showed, everything worked OK, only the PLL didn’t seem to do anything: The VCO was stuck at its lowest possible frequency (which depended on whether I picked the one for higher or lower frequencies).

At first I thought that there was something wrong with my reference clock. But it was OK.

Only after a while did I realize that the PLL needs to be recalibrated after the registers are set. The CDCE62002 wasn’t intended to be programmed after powerup, like the CDCE906. The by-design use is to program the EEPROM once, and then power it up. Doing it this way, the correct values go into the RAM from EEPROM, after which calibration takes place with the correct parameters.

Solution: Power down the device by clearing bit 7 in register #2, write the desired values in registers #0 and #1, and then power up again by setting bit 7 (and and bit 8, regardless) in register #2. This way the device wakes up as is the registers were loaded from EEPROM, and runs its calibration routine correctly.

What I still don’t understand, is why I have to do this twice. The VCO seems to go to its highest frequency now, unless I repeat the ritual mentioned above again 100 ms after the first time. If I do this within microseconds it’s no good.

I’ve written a Verilog module to handle this. Basically, send_data should be asserted during one clock cycle, and the parameter inputs should be held steady for some 256 clock cycles afterwards. As I’m using this module, there are constant values there.

This is not an example of best Verilog coding techniques, but since I didn’t care about either slice count or timing here, I went for the quickest solution, even if it’s a bit dirty. And it works.

Note that the module’s clock frequency should not exceed 40 MHz, since the maximal SPI clock allowed by spec is 20 MHz. And again, for this to really work, send_data has to be asserted twice, with some 100 ms or so between assertions. I’ll check with TI about this.

module cdce62002
  (
   input          clk, // Maximum 40 MHz
   input 	  reset, // Active high

   output 	  busy,

   input          send_data,
   output reg     spi_clk, spi_le, spi_mosi,
   input          spi_miso,  // Never used

   // The names below match those used in pages 22-24 of the datasheet

   input  INBUFSELX,
   input  INBUFSELY,
   input  REFSEL,
   input  AUXSEL,
   input  ACDCSEL,
   input  TERMSEL,
   input [3:0]  REFDIVIDE,
   input [1:0]  LOCKW,
   input [3:0]  OUT0DIVRSEL,
   input [3:0]  OUT1DIVRSEL,
   input   HIPERFORMANCE,
   input   OUTBUFSEL0X,
   input   OUTBUFSEL0Y,
   input   OUTBUFSEL1X,
   input   OUTBUFSEL1Y,

   input  SELVCO,
   input [7:0]  SELINDIV,
   input [1:0]  SELPRESC,
   input [7:0]  SELFBDIV,
   input [2:0]  SELBPDIV,
   input [3:0]  LFRCSEL
   );

   reg [7:0] 	  out_pointer;
   reg 		  active;

   wire [255:0]   data_out;
   wire [255:0]   le_out;
   wire [27:0] 	  word0, word1, word2, word3;
   wire [27:0] 	  ones = 28'hfff_ffff;

   // synthesis attribute IOB of spi_clk is true;
   // synthesis attribute IOB of spi_le is true;
   // synthesis attribute IOB of spi_mosi is true;

   // synthesis attribute init of out_pointer is 0 ;
   // synthesis attribute init of active is 0 ;
   // synthesis attribute init of spi_le is 1;

   assign 	  busy = (out_pointer != 0);

   // "active" is necessary because we don't rely on getting a proper
   // reset signal, and out_pointer is subject to munching by the
   // synthesizer, which may result in nasty things during wakeup

   always @(posedge clk or posedge reset)
     if (reset)
       begin
	  out_pointer <= 0;
	  active <= 0;
       end
     else if (send_data)
       begin
	  out_pointer <= 1;
	  active <= 1;
       end
     else if ((spi_clk) && busy)
       out_pointer <= out_pointer + 1;

   always @(posedge clk)
     begin
	if (spi_clk)
	  begin
	     spi_mosi <= data_out[out_pointer];
	     spi_le <= !(le_out[out_pointer] && active);
	  end
	spi_clk <= !spi_clk;
     end 

   assign data_out = { word3, 4'd2, 2'd0, // To register #2 again.
		       64'd0, // Dwell a bit in power down
		       word1, 4'd1, 2'd0,
		       word0, 4'd0, 2'd0,
		       word2, 4'd2, 4'd0
		       };

   assign le_out = { ones[27:0], ones[3:0], 2'd0,
		     64'd0, // Dwell a bit in power down
		     ones[27:0], ones[3:0], 2'd0,
		     ones[27:0], ones[3:0], 2'd0,
		     ones[27:0], ones[3:0], 4'd0 };

   assign word0[0] = INBUFSELX;
   assign word0[1] = INBUFSELY;
   assign word0[2] = REFSEL;
   assign word0[3] = AUXSEL;
   assign word0[4] = ACDCSEL;
   assign word0[5] = TERMSEL;
   assign word0[9:6] = REFDIVIDE;
   assign word0[10] = 0; // TI trashed external feedback
   assign word0[12:11] = 0; // TI's test bits
   assign word0[14:13] = LOCKW;
   assign word0[18:15] = OUT0DIVRSEL;
   assign word0[22:19] = OUT1DIVRSEL;
   assign word0[23] = HIPERFORMANCE;
   assign word0[24] = OUTBUFSEL0X;
   assign word0[25] = OUTBUFSEL0Y;
   assign word0[26] = OUTBUFSEL1X;
   assign word0[27] = OUTBUFSEL1Y;

   assign word1[0] = SELVCO;
   assign word1[8:1] = SELINDIV;
   assign word1[10:9] = SELPRESC;
   assign word1[18:11] = SELFBDIV;
   assign word1[21:19] = SELBPDIV;
   assign word1[25:22] = LFRCSEL;
   assign word1[27:26] = 2'b10; // Read only bits   

   // word2 and word3 are both sent to register #2 in order to
   // restart the PLL calibration after registers are set.

   assign word2 = 28'h000_0100; // Power down
   assign word3 = 28'h000_0180; // Exit powerdown
endmodule

Add a Comment

required, use real name
required, will not be published
optional, your blog address