Verilog FPGA module for programming CDCE906/CDCE706/CDC906/CDC706 clock synthesizer over SMBus/I2C/IIC

This post was written by eli on January 1, 2009
Posted Under: FPGA

Somewhere in 2006 TI (Texas Instruments) released a clock synthesizer, CDCE906, which is pretty much everything one can look for in a system clock generator: Several (up to 6) clock outputs derived from a single reference clock or crystal with excellent multiplication and division capabilities, not to mention low jitter, slew rate control, spread spectrum and more. On top of all, it’s surprisingly low-priced. So it’s a chip you want to consider, even if all you (think you) need for now, is one single system clock with a fixed frequency. It will save you, when you’ll discover three weeks before release that you actually need another clock with a weird frequency. Believe me.

Except for the non-E devices, (that is, CDC906 and CDC706, which TI seems to neglect) the devices’ setting can be preprogrammed into an on-chip EEPROM, before or after the chip is put on the PCB board. If this is enough for you, frankly, you have nothing to look for on this page. On the other hand, if you want to set the current configuration regardless of the EEPROM, it’s possible over the SMBus.

Now, SMBus is more or less like I2C (also known as IIC), so if you have a controller on the board with I2C/SMBus cababilities, you may want to use it (if it has I2C, I suggest knowing the differences). If there is no controller around, or you prefer the FPGA to set up the synthesizer, the Verilog code below is what you need.

It should be pointed out, that TI also have another very similar device, CDCE706, which is surprisingly enough more capable than its “little brother” CDCE906. Their register structure is identical however, so the code below can be used for CDCE706 as well.

The only downside about both devices is that being very versatile, they’re also complicated. I’m not going to describe how to run these chips. There is no way out of reading the extensive datasheet, and I promise finding some hidden treasures there. The only thing this module will do for you, is get the right values to the right register.

So let’s get to business. Before doing anything else, be sure to set the parameter “clk_freq” to the frequency of the module’s input clock “clk” (in MHz, nearest integer). This is crucial, because the SMBus is derived from this clock. If several frequencies are possible, make “clk_freq” the highest one.

Important notes:

  • Pullup resistors for sclk and sdata are mandatory. Nothing will work without them. When tied to a 3.3V supply, they should be between 10 kOhm and 33 kOhm.
  • This module acts as an SMBus master. It must therefore not share the sclk and sdata lines with another master, unless it’s assured that they won’t attempt to take control at the same time. The simplest solution is to make one-on-one connections of sclk and sdata between the chip and the FPGA (don’t forget the pullups!).
  • Verify that the FPGA outputs are indeed 3.3V (this is usually something you should set). Modern FPGA’s take a lower voltage by default.

Now the basic I/O’s:

clk Input clock, which drives the module’s logic and from which the SMBus clock is derived. Preferably with a known and stable frequency. The module is sensitive to this clock’s rising edge only.
reset Asynchronous reset, active high (input). May be tied to ’0′ if the state variable “state” can be assured to wake up as “st_idle” (don’t trust the syntheizer to do this implicitly)
send_data Input, synchronous with “clk”: Start send data to chip command. Set to ’1′ to update all registers of the chip. This line should be ’1′ for at least one “clk” cycle, but not more than the time it takes for the full write to complete.
busy Output, synchronous with “clk”: This line is high as long as the register data is being written to the chip over the SMBus.
error Output, synchronous with “clk”: This line goes high if an acknowledgement from the chip is missing, which indicates some kind of error. The line remains high until the next initiation with “send_data”.
sclk Output: To be directly connected to the chip’s pin #10 (SCLOCK). This pin toggles between ’0′ and high-Z. Do verify that your development tools indeed create a high-Z condition on the output when the ‘z’ value is assigned to this line (Xilinx FPGA tools are OK with this). Don’t forget the pullup resistor
sdata Output: To be directly connected to the chip’s pin #9 (SDATA). This pin toggles between ’0′ and high-Z. Do verify that your development tools indeed create a high-Z condition on the output when the ‘z’ value is assigned to this line. Don’t forget the pullup resistor
S0 Output: To be directly connected to the chip’s pin #1 (S0/A0/CLK_SEL). Will be tied to constant ’1′ (see datasheet for meaning).
S1 Output: To be directly connected to the chip’s pin #2 (S1/A1). Will be tied to constant ’1′ (see datasheet for meaning).

Next we have the actual synthesizer parameters, whose meaning should be obvious to whoever has read the datasheet well. The only thing note is that the term “PLL” has been exchanged for “VCO”, which I find more intuitive. So VCO1_M and VCO1_N are the M and N for PLL1, using the datasheet’s terminology, etc. The comments in the module itself may help as well.

All these inputs can be constants (which will shrink the synthesized logic) or you can wire them to whatever you like, but these inputs must be steady as long as their content is flushed to the chip ( = as long as the “busy” output is high).

So all we have left is the module itself. I’ve lifted the copyright restrictions for it, so you can use it in any way you like.

(And by the way: I have no special relations with TI whatsoever. Not even as a freelancer).

———————————–

// Eli Billauer, 30.12.06
// This function is released to the public domain; Any use is allowed.

module cdce906
  (
   clk, reset, send_data,

   busy, error, 

   sclk, sdata, S0, S1,

   VCO1_M, VCO1_N, VCO1_bypass, VCO1_hi_freq_range,
   VCO2_M, VCO2_N, VCO2_bypass, VCO2_hi_freq_range,
   VCO3_M, VCO3_N, VCO3_bypass, VCO3_hi_freq_range,
   P0_VCO_select, P0_div, P1_VCO_select, P1_div,
   P2_VCO_select, P2_div, P3_VCO_select, P3_div,
   P4_VCO_select, P4_div, P5_VCO_select, P5_div,
   Y0_en, Y0_inv, Y0_slew_rate, Y0_div_select,
   Y1_en, Y1_inv, Y1_slew_rate, Y1_div_select,
   Y2_en, Y2_inv, Y2_slew_rate, Y2_div_select,
   Y3_en, Y3_inv, Y3_slew_rate, Y3_div_select,
   Y4_en, Y4_inv, Y4_slew_rate, Y4_div_select,
   Y5_en, Y5_inv, Y5_slew_rate, Y5_div_select,
   SSC_mod_select, SSC_freq_select
   );

   input          clk;
   input 	  reset; // Active high

   output 	  sclk;
   inout 	  sdata;
   output 	  S0, S1;
   output 	  busy, error;

   input          send_data;

   // PLL settings:

   input [8:0] 	  VCO1_M;
   input [11:0]   VCO1_N;
   input 	  VCO1_bypass; // '1' means VCO bypassed
   input 	  VCO1_hi_freq_range; // '1' means 180-300 MHz

   input [8:0] 	  VCO2_M;
   input [11:0]   VCO2_N;
   input 	  VCO2_bypass; // '1' means VCO bypassed
   input 	  VCO2_hi_freq_range; // '1' means 180-300 MHz

   input [8:0] 	  VCO3_M;
   input [11:0]   VCO3_N;
   input 	  VCO3_bypass; // '1' means VCO bypassed
   input 	  VCO3_hi_freq_range; // '1' means 180-300 MHz

   // Post divider (P) settings
   // For Px_VCO_select:
   // 0 = input clock (bypass)
   // 1 = VCO1
   // 2 = VCO2
   // 3 = VCO2 with spread spectrum modulation
   // 4 = VCO3

   input [2:0] 	  P0_VCO_select;
   input [6:0] 	  P0_div;

   input [2:0] 	  P1_VCO_select;
   input [6:0] 	  P1_div;

   input [2:0] 	  P2_VCO_select;
   input [6:0] 	  P2_div;

   input [2:0] 	  P3_VCO_select;
   input [6:0] 	  P3_div;

   input [2:0] 	  P4_VCO_select;
   input [6:0] 	  P4_div;

   input [2:0] 	  P5_VCO_select;
   input [6:0] 	  P5_div;

   // Output (Y) settings

   input 	  Y0_en; // '1' is output enabled
   input 	  Y0_inv; // '1' means inverted
   input [1:0] 	  Y0_slew_rate; // '11' is nominal
   input [2:0] 	  Y0_div_select; // 0=P0, 1=P1, ... , 5=P5	  

   input 	  Y1_en; // '1' is output enabled
   input 	  Y1_inv; // '1' means inverted
   input [1:0] 	  Y1_slew_rate; // '11' is nominal
   input [2:0] 	  Y1_div_select; // 0=P0, 1=P1, ... , 5=P5	  

   input 	  Y2_en; // '1' is output enabled
   input 	  Y2_inv; // '1' means inverted
   input [1:0] 	  Y2_slew_rate; // '11' is nominal
   input [2:0] 	  Y2_div_select; // 0=P0, 1=P1, ... , 5=P5	  

   input 	  Y3_en; // '1' is output enabled
   input 	  Y3_inv; // '1' means inverted
   input [1:0] 	  Y3_slew_rate; // '11' is nominal
   input [2:0] 	  Y3_div_select; // 0=P0, 1=P1, ... , 5=P5	  

   input 	  Y4_en; // '1' is output enabled
   input 	  Y4_inv; // '1' means inverted
   input [1:0] 	  Y4_slew_rate; // '11' is nominal
   input [2:0] 	  Y4_div_select; // 0=P0, 1=P1, ... , 5=P5	  

   input 	  Y5_en; // '1' is output enabled
   input 	  Y5_inv; // '1' means inverted
   input [1:0] 	  Y5_slew_rate; // '11' is nominal
   input [2:0] 	  Y5_div_select; // 0=P0, 1=P1, ... , 5=P5	  

   // Spread spectrum settings

   input [2:0] 	  SSC_mod_select; // See datasheet
   input [3:0] 	  SSC_freq_select; // See datasheet

   reg 		  SMBus_en, pre_en;
   reg            kickoff;
   reg [2:0]      state;
   reg 		  get_bit;
   reg 		  next_bit, this_is_ack, wait_for_ack;
   reg 		  sdata_d;
   reg 		  error;
   reg [4:0] 	  word_addr;
   reg [3:0] 	  bit_pos;
   reg 		  no_more_bits;
   reg [7:0] 	  SMBus_word;
   reg [11:0] 	  div_counter;

   reg 		  sclk_logic, sdata_logic;

   parameter 	  clk_freq = 10; // In MHz, nearest integer

   parameter st_idle = 0,
	     st_start0 = 1,
	     st_start1 = 2,
	     st_bit0 = 3,
	     st_bit1 = 4,
	     st_bit2 = 5,
	     st_stop0 = 6,
	     st_stop1 = 7;

   // Emulated open collector output
   // Note that sclk and sdata must be pulled up, possibly with
   // a PULLUP constraint on the IOB (or a 10 kOhm ext. resistor)

   assign    sclk = sclk_logic ? 1'bz : 1'b0 ;
   assign    sdata = sdata_logic ? 1'bz : 1'b0 ;

   assign    S0 = 1; // No powerdown
   assign    S1 = 1; // No output inhibit

   // SMBus_en should be high every 10 us
   // This allows a huge multicycle path constraint on SBBus_en,
   // but "kickoff" MUST BE EXCLUDED from the group.

   always @(posedge clk)
     begin
	SMBus_en <= pre_en;
	sdata_d <= sdata;
	if (div_counter == ((clk_freq * 10) - 1))
	  begin
	     div_counter <= 0;
	     pre_en <= 1;
	  end
	else
	  begin
	     div_counter <= div_counter + 1;
	     pre_en <= 0;
	  end
     end   

   always @(posedge clk or posedge reset)
     if (reset)
       kickoff <= 0;
     else if (send_data)
       kickoff <= 1;
     else if (SMBus_en)
       kickoff <= 0;

   assign busy = kickoff || (state != st_idle);

   always @(posedge clk or posedge reset)
     if (reset)
       begin
	  state <= st_idle;
	  error <= 0;
       end
     else if (SMBus_en)
       case (state)
	 st_idle: begin
	    sclk_logic <= 1;
	    sdata_logic <= 1;
	    get_bit <= 0;

	    if (kickoff)
	      state <= st_start0;
	 end
	 st_start0: begin
	    sclk_logic <= 1;
	    sdata_logic <= 0;
	    state <= st_start1;

	    error <= 0;
	 end
	 st_start1: begin
	    sclk_logic <= 0;
	    state <= st_bit0;
	 end
	 st_bit0: begin
	    sclk_logic <= 0;
	    sdata_logic <= next_bit;
	    wait_for_ack <= this_is_ack;
	    get_bit <= 1;
	    state <= st_bit1;
	 end
	 st_bit1: begin
	    sclk_logic <= 1;
	    get_bit <= 0;
	    state <= st_bit2;
	 end
	 st_bit2: begin
	    sclk_logic <= 0;
	    if (wait_for_ack && !sdata_d && !kickoff)
	      state <= st_bit2;
	    else if (no_more_bits)
	      state <= st_stop0;
	    else
	      state <= st_bit0;

	    if (wait_for_ack && sdata_d && sclk_logic) // No acknowledge
	      error <= 1;
	 end 

	 st_stop0: begin
	    sclk_logic <= 0;
	    sdata_logic <= 0;
	    state <= st_stop1;
	 end
	 st_stop1: begin
	    sclk_logic <= 1;
	    state <= st_idle;
	 end
      endcase

   always @(posedge clk)
     if (SMBus_en)
       begin
	  if (kickoff)
	    begin
	       word_addr <= 0;
	       bit_pos <= 7;
	       no_more_bits <= 0;
	    end
	  else if (get_bit)
	    begin
	       bit_pos <= (bit_pos == 0) ? 8 : bit_pos - 1;

	       if (bit_pos == 0)
		 word_addr <= word_addr + 1;

	       if ((word_addr == 29) &&
		   (bit_pos == 8))
		 no_more_bits <= 1;
	    end 

	  if (bit_pos == 8) // Ack position
	    begin
	       next_bit <= 1; // Don't pull bus to zero
	       this_is_ack <= 1;
	    end
	  else
	    begin
	       next_bit <= SMBus_word[ bit_pos[2:0] ];
	       this_is_ack <= 0;
	    end

       end

   always @(posedge clk)
     if (SMBus_en)
       case (word_addr)
	 0: SMBus_word <= 8'b1101001_0 ; // Slave address, write
	 1: SMBus_word <= 0 ; // Command code = Block (write)
	 2: SMBus_word <= 26 ; // Byte count
	 3: SMBus_word <= 0 ; // Byte 0
	 4: SMBus_word <= VCO1_M[7:0] ;
	 5: SMBus_word <= VCO1_N[7:0] ;
	 6: SMBus_word <= { VCO1_bypass, VCO2_bypass, VCO3_bypass, VCO1_N[11:8], VCO1_M[8] } ;
	 7: SMBus_word <= VCO2_M[7:0] ;
	 8: SMBus_word <= VCO2_N[7:0] ;
	 9: SMBus_word <= { VCO1_hi_freq_range, VCO2_hi_freq_range,
                            VCO3_hi_freq_range, VCO2_N[11:8], VCO2_M[8] } ;
	 10: SMBus_word <= VCO3_M[7:0] ;
	 11: SMBus_word <= VCO3_N[7:0] ;
	 12: SMBus_word <= { P0_VCO_select,  VCO3_N[11:8], VCO3_M[8] } ;
	 13: SMBus_word <= { P1_VCO_select, 1'b0, 2'b00, 2'b00 } ;
	 14: SMBus_word <= { 2'b00, P3_VCO_select, P2_VCO_select } ;
	 15: SMBus_word <= { 2'b00, P5_VCO_select, P4_VCO_select } ;
	 16: SMBus_word <= { 1'b0, P0_div } ;
	 17: SMBus_word <= { 1'b0, P1_div } ;
	 18: SMBus_word <= { 1'b0, P2_div } ;
	 19: SMBus_word <= { 1'b0, P3_div } ;
	 20: SMBus_word <= { 1'b0, P4_div } ;
	 21: SMBus_word <= { 1'b0, P5_div } ;
	 22: SMBus_word <= { 1'b0, Y0_inv, Y0_slew_rate, Y0_en, Y0_div_select } ;
	 23: SMBus_word <= { 1'b0, Y1_inv, Y1_slew_rate, Y1_en, Y1_div_select } ;
	 24: SMBus_word <= { 1'b0, Y2_inv, Y2_slew_rate, Y2_en, Y2_div_select } ;
	 25: SMBus_word <= { 1'b0, Y3_inv, Y3_slew_rate, Y3_en, Y3_div_select } ;
	 26: SMBus_word <= { 1'b0, Y4_inv, Y4_slew_rate, Y4_en, Y4_div_select } ;
	 27: SMBus_word <= { 1'b0, Y5_inv, Y5_slew_rate, Y5_en, Y5_div_select } ;
	 28: SMBus_word <= { 1'b0, SSC_mod_select, SSC_freq_select } ; // Byte 25
	 default: SMBus_word <= 0 ;
       endcase

endmodule

Add a Comment

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

Previose Post: