Last week we looked at configuring and deploying multi-gigabit GTH transceivers using the GTH wizard. One of the key things when using these links is selecting the right reference clock frequency for the required line rate. To provide the maximum flexibility in line rate, it is common to use a programmable crystal oscillator (XO). These devices are often programmable over a simple interface such as I2C.
In this blog, we are going to look at how we can configure the commonly used SI570 programmable crystal oscillator from Skyworks, which can generate frequencies with a low jitter between 10 MHz and 1400 MHz. This device is also commonly used on AMD-Xilinx development boards to provide a reference clock for the GTH transceivers and is programmable using an I2C interface.
While you can program the SI570 using the platform controller software provided for AMD-Xilinx development boards, it is better to create an example from scratch. The I2C network on the ZCU102 is connected to both the system controller and the PS and PL sides of the MPSOC. Since the ZCU102 has several I2C slaves, there is an I2C switch used between the PS I2C port and the SI570 device that needs to be configured first to enable communication with the SI570.
Upon examining the ZCU102 user guide, you will see that the board has two fitted SI570 crystal oscillators; one provides the GTH reference clock and another provides a user logic clock. These are connected to different I2C switches to avoid I2C addressing issues.
The SI570 for the user GTH is initially configured to run at 156 MHz which is a commonly used reference frequency. This frequency is important because we will be using it to help set the updated frequency.
We can also find the I2C addresses of the SI570 within the ZCU102 user manual.
The schematics also show the configuration of the GTH user clock on the ZCU102 board. In addition to the SI570, a buffer device is also used to fan out to provide the actual reference clocks.
Writing the software to update the registers in the SI570 is simple and we have done it many times in the blog over the years. However, determining what those registers should be can be a little more complicated.
Looking at the data sheet for the SI570, there are a number of registers that need to be set from reference frequencies to divder and PPM settings.
To detemine what these register settings should be, Skyworks provide a software application to specify exactly the register settings to be used.
To get started updating the output frequency, the first step is to download the application. Once the application is running, select the device you wish to update.
The program itself is straight forward. Enter the default start up frequency, which in this case is 156.2 MHz, and enter the desired output frequency. Here, I set the frequency for 100 MHz. The register settings are generated along with the instructions by clicking the create example button.
To change the frequency, we need to first pause the DCO, update the register values, and then un pause the DCO.
To test these register settings, I created a project in Vitis targeting the default that is built in the ZCU102 example, and also created the hello world example.
The software application is very simple. First I set the I2C switch to the correct channel and then I read back the current settings of the SI570. These should be the same as what is reported in the tool as the current settings of the register.
The software then writes in the updated register settings before enabling the DCO again. Once this has been completed, the software also reads back the register settings to make sure the settings took.
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xiicps.h"
#define IIC_DEVICE_ID XPAR_XIICPS_1_DEVICE_ID
#define IIC_SWITCH_ADDR 0x74
#define IIC_MGT_CLK_ADDR 0x5D
#define IIC_SCLK_RATE 100000
u8 SendBuffer[128];
u8 RecvBuffer[128];
struct clock {
u8 reg7;
u8 reg8;
u8 reg9;
u8 reg10;
u8 reg11;
u8 reg12;
};
const struct clock initial ={0x01,0xC2,0xBB,0xC7,0xC6,0x90};
struct clock read_back;
struct clock new_settings ={0x22,0x42,0xBC,0x01,0x1E,0xB8};
XIicPs Iic;
u8 read_reg(u8 addr){
int Status;
while (XIicPs_BusIsBusy(&Iic)) {
/* NOP */
}
Status = XIicPs_MasterSendPolled(&Iic, &addr, 1, IIC_MGT_CLK_ADDR);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
while (XIicPs_BusIsBusy(&Iic)) {
/* NOP */
}
Status = XIicPs_MasterRecvPolled(&Iic, RecvBuffer,
1, IIC_MGT_CLK_ADDR);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
return RecvBuffer[0];
}
void write_reg_sw(int bytes){
int Status;
Status = XIicPs_MasterSendPolled(&Iic, SendBuffer, bytes, IIC_SWITCH_ADDR);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
while (XIicPs_BusIsBusy(&Iic)) {
/* NOP */
}
}
void write_reg(int bytes){
int Status;
Status = XIicPs_MasterSendPolled(&Iic, SendBuffer, bytes, IIC_MGT_CLK_ADDR);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
while (XIicPs_BusIsBusy(&Iic)) {
/* NOP */
}
}
int main()
{
XIicPs_Config *Config;
int Status;
init_platform();
Config = XIicPs_LookupConfig(IIC_DEVICE_ID);
XIicPs_CfgInitialize(&Iic, Config, Config->BaseAddress);
XIicPs_SetSClk(&Iic, IIC_SCLK_RATE);
print("SI570\n\r");
print("Setting Switch Channel\n\r");
//set switch to right channel
SendBuffer[0] = 0x08;
write_reg_sw(1);
print("Reading Initial Settings\n\r");
read_back.reg7 = read_reg(0x07);
read_back.reg8 = read_reg(0x08);
read_back.reg9 = read_reg(0x09);
read_back.reg10 = read_reg(0x0a);
read_back.reg11 = read_reg(0x0b);
read_back.reg12 = read_reg(0x0c);
print("Updating Settings\n\r");
//pause the DCO;
SendBuffer[0] = 137;
SendBuffer[1] = 0x08;
write_reg(2);
//update register 7;
SendBuffer[0] = 0x07;
SendBuffer[1] = new_settings.reg7;
write_reg(2);
//update register 8;
SendBuffer[0] = 0x08;
SendBuffer[1] = new_settings.reg8;
write_reg(2);
//update register 9;
SendBuffer[0] = 0x09;
SendBuffer[1] = new_settings.reg9;
write_reg(2);
//update register 10;
SendBuffer[0] = 0x0a;
SendBuffer[1] = new_settings.reg10;
write_reg(2);
//update register 11;
SendBuffer[0] = 0x0b;
SendBuffer[1] = new_settings.reg11;
write_reg(2);
//update register 12;
SendBuffer[0] = 0x0c;
SendBuffer[1] = new_settings.reg12;
write_reg(2);
//restart the DCO;
SendBuffer[0] = 137;
SendBuffer[1] = 0x00;
write_reg(2);
read_back.reg7 = read_reg(0x07);
read_back.reg8 = read_reg(0x08);
read_back.reg9 = read_reg(0x09);
read_back.reg10 = read_reg(0x0a);
read_back.reg11 = read_reg(0x0b);
read_back.reg12 = read_reg(0x0c);
printf("Registers Updated \n\r");
cleanup_platform();
return 0;
}
With the frequency updated, we can now started developing the GTH for the desired line rate and application. Of course, different programmable oscillators exist. However, many follow a similar approach with the software provided to enable the updated settings to be calculated.
Workshops and Webinars
Enjoy the blog why not take a look at the free webinars, workshops and training courses we have created over the years. Highlights include
Ultra96, MiniZed & ZU1 three day course looking at HW, SW and Petalinux
Arty Z7-20 Class looking at HW, SW and Petalinux
Mastering MicroBlaze learn how to create MicroBlaze solutions
HLS Hero Workshop learn how to create High Level Synthesis based solutions
Embedded System Book
Do you want to know more about designing embedded systems from scratch? Check out our book on creating embedded systems. This book will walk you through all the stages of requirements, architecture, component selection, schematics, layout, and FPGA / software design.
We designed and manufactured the board at the heart of the book! The schematics and layout are available in Altium here
Learn more about the board (see previous blogs on Bring up, DDR validation, USB, Sensors) and view the schematics here.
Sponsored by AMD Xilinx
Comments