使用AXI4总线控制MMCM时钟模块
最近笔者因工作需要开始使用锁相环。我使用的是Xilinx XC7Z015 FPGA主控下的MMCM IP核。之前在其它项目中使用MMCM时,没有深入了解过这个IP核,只当做普通的PLL来使用。但这次仔细研究后发现,该IP核的功能非常强大。例如,ZYNQ系列还可以使用AXI4总线进行时钟动态调节和监控如图所示位MMCM模块结构图。最近我恰好需要这些功能,于是随手记录下了笔记。
一、 PL端IP核配置
1、PL端的配置相对比较简单,主要就是开启动态调节和写入DRP寄存器即可。
2、在MMCM Settings这个标签中可以勾选上允许覆盖模式,这样我们就可以自己去调节锁相环中的VCO的倍频系数和输出时钟的分频系数。如图所示。
3、最后在DRP Registers选卡中可以查看到根据我们设置的值而同步的相关寄存器值,这个寄存器都是什么在后续PS端会有详解。
最后将AXI4总线连接到RAM核中至此PL端就设置完成了。
二、PS端
关于PS端简单的说就是使用AXI4总线对MMCM模块的寄存器进行读写就可以了。这里可以参考一下官方给的文档。我这里也是参考的官方文档做修改的。
2、首先是初始化MMCM模块,这里主要就是获取MMCM模块的地址指针以便于后面的操作。当然MMCM作为ARM核的外设也可以产生中断例如失锁中断等,但我这里没有用到所以就没有设置也没有打开中断功能,需要的同志们也可以查看官方文档里面相对比较详细。
u32 ClkWiz_IntrExample(INTC *IntcInstancePtr, u32 DeviceId)
{
XClk_Wiz_Config *CfgPtr_Dynamic;
ULONG Exit_Count = 0;
u32 Status = XST_SUCCESS;
/*
* Get the CLK_WIZ Dynamic reconfiguration driver instance
*/
CfgPtr_Dynamic = XClk_Wiz_LookupConfig(XCLK_WIZ_DYN_DEVICE_ID);
if (!CfgPtr_Dynamic) {
return XST_FAILURE;
}
/*
* Initialize the CLK_WIZ Dynamic reconfiguration driver
*/
Status = XClk_Wiz_CfgInitialize(&ClkWiz_Dynamic, CfgPtr_Dynamic,
CfgPtr_Dynamic->BaseAddr);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
/* 调用时钟向导动态重新配置 */
Clk_Wiz_Reconfig(CfgPtr_Dynamic);
return XST_SUCCESS;
}
2、初始化完毕之后根据PG065手册,可以向(BaseAddr + 0x00) 地址中写入0X0A来复位MMCM模块。
复位完成后,可以通过检测 (BaseAddr + 0x04) 地址中的第0位来判断锁相环是否已经锁定。
3 、通过PS端设置MMCM模块的时钟倍频和分频参数
1、设置VCO的倍频系数根据手册可以可以向(C_BASEADDR + 0x200)地址和(C_BASEADDR + 0x204)写入参数即可。其中(C_BASEADDR + 0x200)地址是用来设置VCO的倍频系数,(C_BASEADDR + 0x204)是用来设置VCO的相位。如图所示
同理设置OUT0输出的分频系数可以通过配置C_BASEADDR + 0x208和C_BASEADDR + 0x20C即可。重点需要说明的是,使用这几个地址配置后的参数不会立即生效,需要先对C_BASEADDR + 0x25C地址的[0] [1] 位写入1后,等待MMCM复位锁定,然后再向该地址的[0]位写入0才可以完成设置。
关于设置官方的历程代码如下:
int Clk_Wiz_Reconfig(XClk_Wiz_Config *CfgPtr_Dynamic)
{
u32 Count = 0;
u32 Error = 0;
u32 Fail = 0;
u32 Frac_en = 0;
u32 Frac_divide = 0;
u32 Divide = 0;
float Freq = 0.0;
Fail = Wait_For_Lock(CfgPtr_Dynamic); //这个函数是用来检测锁相环是否已经锁定的
if(Fail) {
Error++;
xil_printf("\n ERROR: Clock is not locked for default frequency" \
" : 0x%x\n\r", *(u32 *)(CfgPtr_Dynamic->BaseAddr + 0x04) & CLK_LOCK);
}
/* SW reset applied */
*(u32 *)(CfgPtr_Dynamic->BaseAddr + 0x00) = 0xA; //在这里复位了一下锁相环
if(*(u32 *)(CfgPtr_Dynamic->BaseAddr + 0x04) & CLK_LOCK) {
Error++;
xil_printf("\n ERROR: Clock is locked : 0x%x \t expected "\
"0x00\n\r", *(u32 *)(CfgPtr_Dynamic->BaseAddr + 0x04) & CLK_LOCK);
}
/* Wait cycles after SW reset */
for(Count = 0; Count < 2000; Count++);
Fail = Wait_For_Lock(CfgPtr_Dynamic);
if(Fail) {
Error++;
xil_printf("\n ERROR: Clock is not locked after SW reset :"
"0x%x \t Expected : 0x1\n\r",
*(u32 *)(CfgPtr_Dynamic->BaseAddr + 0x04) & CLK_LOCK);
}
/* Calculation of Input Freq and Divide factors*/
Freq = ((float) CLK_WIZ_VCO_FACTOR/ DYNAMIC_INPUT_FREQ_FACTOR);
Divide = Freq;
Freq = (float)(Freq - Divide);
Frac_divide = Freq * 10000;
if(Frac_divide % 10 > 5) {
Frac_divide = Frac_divide + 10;
}
Frac_divide = Frac_divide/10;
if(Frac_divide > 1023 ) {
Frac_divide = Frac_divide / 10;
}
if(Frac_divide) {
/* if fraction part exists, Frac_en is shifted to 26
* for input Freq */
Frac_en = (CLK_FRAC_EN << 26);
}
else {
Frac_en = 0;
}
/* Configuring Multiply and Divide values */
*(u32 *)(CfgPtr_Dynamic->BaseAddr + 0x200) = \
Frac_en | (Frac_divide << 16) | (Divide << 8) | 0x01;
*(u32 *)(CfgPtr_Dynamic->BaseAddr + 0x204) = 0x00;
/* Calculation of Output Freq and Divide factors*/
Freq = ((float) CLK_WIZ_VCO_FACTOR / DYNAMIC_OUTPUT_FREQFACTOR);
Divide = Freq;
Freq = (float)(Freq - Divide);
Frac_divide = Freq * 10000;
if(Frac_divide%10 > 5) {
Frac_divide = Frac_divide + 10;
}
Frac_divide = Frac_divide / 10;
if(Frac_divide > 1023 ) {
Frac_divide = Frac_divide / 10;
}
if(Frac_divide) {
/* if fraction part exists, Frac_en is shifted to 18 for output Freq */
Frac_en = (CLK_FRAC_EN << 18);
}
else {
Frac_en = 0;
}
/* Configuring Multiply and Divide values */
*(u32 *)(CfgPtr_Dynamic->BaseAddr + 0x208) =
Frac_en | (Frac_divide << 8) | (Divide);
*(u32 *)(CfgPtr_Dynamic->BaseAddr + 0x20C) = 0x00;
/* Load Clock Configuration Register values */
*(u32 *)(CfgPtr_Dynamic->BaseAddr + 0x25C) = 0x07;
if(*(u32 *)(CfgPtr_Dynamic->BaseAddr + 0x04) & CLK_LOCK) {
Error++;
xil_printf("\n ERROR: Clock is locked : 0x%x \t expected "
"0x00\n\r", *(u32 *)(CfgPtr_Dynamic->BaseAddr + 0x04) & CLK_LOCK);
}
/* Clock Configuration Registers are used for dynamic reconfiguration */
*(u32 *)(CfgPtr_Dynamic->BaseAddr + 0x25C) = 0x02;
Fail = Wait_For_Lock(CfgPtr_Dynamic);
if(Fail) {
Error++;
xil_printf("\n ERROR: Clock is not locked : 0x%x \t Expected "\
": 0x1\n\r", *(u32 *)(CfgPtr_Dynamic->BaseAddr + 0x04) & CLK_LOCK);
}
return Error;
}
第二种动态调节的办法是可以通过写DRP相关寄存器来达到动态调节的目的。例如下图是来自官方手册XAP888介绍的DRP寄存器(部分)
由图可以看出对DRP该方法与上述方法大同小异。需要注意的是这里这是的参数和上述的不太一样,例如CLKREG1寄存器中对VCO的设置是通过设置VCO输出的高低电平时间来设置VCO的频率的。
总结
这篇文章主要记录了笔者在使用AXI控制MMCM模块的一些笔记和过程,当然MMCM模块还有好多功能本文并未体现。
笔者水平有限,如有错误尽请指正。