When TI’s CDCE62002 fails to lock
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