Verilog FPGA module for programming CDCE906/CDCE706/CDC906/CDC706 clock synthesizer over SMBus/I2C/IIC
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