文章目錄
- 1 實驗任務
- 2 系統框圖
- 3 硬件設計
- 4 軟件設計
- 4.1 dynclk_api.h文件
- 4.2 math_compat.h文件
- 4.3 dynclk_api.c文件
- 4.4 main.c文件
1 實驗任務
參見14.1。
2 系統框圖
參見14.1。
3 硬件設計
注意事項:基于14.1做如下改動
- 使能Clocking Wizard IP核的Dynamic Reconfig功能,并選擇AXI4-Lite接口;
- 將Clocking Wizard IP核的AXI4-Lite接口通過AXI Interconnect連接到PS的M_AXI_GP接口。
特別說明:為什么不使用Digilent公司提供的axi_dynclk IP核(正點原子教程使用該IP),原因如下
- axi_dynclk IP核輸出兩個時鐘,PXL_CLK_O和PXL_CLK_5X_O;
- PXL_CLK_5X_O由BUFIO驅動,PXL_CLK_O由BUFR驅動(5倍分頻);
- PXL_CLK_O在本實驗的Vivado工程中布線無法通過,提示路徑太遠,原因應該是BUFR是區域時鐘緩沖器,能夠驅動的資源有限,導致布線失敗;
- 正點原子的教程使用axi_dynclk IP核編譯可以通過,猜測原因可能是整個工程只使用一個時鐘,即PS提供的FCLK_CLK0;
- 本實驗的Vivado工程中使用三個時鐘
- PS輸出的FCLK_CLK0(50MHz),用于連接AXI4-Lite接口;
- PS輸出的FCLK_CLK1(150MHz),用于連接AXI4接口;
- 板載時鐘(50MHz)經Clocking Wizard IP核輸出的視頻像素時鐘(148.5MHz)
- 嘗試在IP Packager中對axi_dynclk IP核進行修改,使其只輸出一個時鐘,且該時鐘由BUFG驅動,但是修改完成之后,在對IP進行重新封裝時,Vivado工具總是閃退,多次嘗試并重啟電腦,依舊如此,故放棄。
4 軟件設計
動態時鐘的軟件配置部分代碼,我是讓deepseek幫我生成的,我負責提需求,包括:
- pg065中關于通過AXI4-Lite接口動態配置時鐘的步驟;
- 輸出頻率Fout的計算公式
- 計算公式中各個參數的取值范圍,包括
- Fvco = 600MHz~1200MHz(和器件型號及等級有關)
- CLKFBOUT_MULT_F = 2.0 ~ 64.0(MMCM,小數部分以0.125步進)/1~64(PLL)
- DIVCLK_DIVIDE = 1 ~ 106(MMCM)/1~56(PLL)
- CLKOUT0_DIVIDE = 1.000 ~ 128.000(MMCM,小數部分以0.125步進)/1~128(PLL)
- 關鍵寄存器的說明,包括
- Software Reset Register (SRR),偏移地址0x00
- Status Register (SR),偏移地址0x04
- Clock Configuration Register 0,偏移地址0x200
- Clock Configuration Register 2,偏移地址0x208
- Clock Configuration Register 23,偏移地址0x25C
代碼生成后,讓deepseek形成xxx_api.c文件和xxx_api.h文件,使用很方便,只需提供輸入時鐘頻率和期望的輸出時鐘頻率(不能輸出類似33.333MHz的時鐘),效果非常好。
4.1 dynclk_api.h文件
代碼如下所示:
#ifndef XCLK_WIZ_API_H
#define XCLK_WIZ_API_H#include "xparameters.h"
#include "xclk_wiz_hw.h"
#include "math_compat.h"// 寄存器基地址
#define XCLKWIZ_BASEADDR XPAR_CLK_WIZ_0_BASEADDR// VCO頻率范圍(MHz)
#define MIN_VCO_FREQ 600.0
#define MAX_VCO_FREQ 1200.0// 參數范圍
#define MIN_CLKFBOUT_MULT 2
#define MAX_CLKFBOUT_MULT 64
#define MIN_DIVCLK_DIVIDE 1
#define MAX_DIVCLK_DIVIDE 106
#define MIN_CLKOUT0_DIVIDE 1
#define MAX_CLKOUT0_DIVIDE 128// 小數部分步進
#define FRAC_STEP 0.125
#define FRAC_SCALE 1000// 寄存器偏移量 (根據文檔修正)
#define XCLKWIZ_SOFT_RESET_OFFSET 0x0 // Soft Reset Register
#define XCLKWIZ_STATUS_OFFSET 0x4 // Status Register
#define XCLKWIZ_CLK_CONFIG_00_OFFSET 0x200 // Clock Configuation Register 00
#define XCLKWIZ_CLK_CONFIG_02_OFFSET 0x208 // Clock Configuation Register 02
#define XCLKWIZ_CLK_CONFIG_23_OFFSET 0x25C // Clock Configuation Register 23// 寄存器值定義
/** To activate software reset, the value 0x0000_000A must be written to the register.*/
#define XCLKWIZ_RESET_VALUE 0xA
/** When '1' MMCM/PLL is Locked and ready for the reconfiguration. Status of this bit is '0' during the reconfiguration.*/
#define XCLKWIZ_STATUS_LOCKED_MASK 0x1// 錯誤代碼
#define XCLK_WIZ_SUCCESS 0
#define XCLK_WIZ_ERROR_INVALID_PARAM -1
#define XCLK_WIZ_ERROR_RESET_FAILED -2
#define XCLK_WIZ_ERROR_LOCK_FAILED -3// 100ms計數值定義
#define COUNT_NUM_100MS (XPAR_CPU_CORTEXA9_0_CPU_CLK_FREQ_HZ / 10)/*** @brief 配置時鐘向導IP* @param input_freq 輸入時鐘頻率(MHz)* @param output_freq 期望的輸出時鐘頻率(MHz)* @return 成功返回XCLK_WIZ_SUCCESS,失敗返回錯誤代碼**/
int clk_wiz_configure(double input_freq, double output_freq);/*** @brief 復位時鐘向導IP* @return 成功返回XCLK_WIZ_SUCCESS,失敗返回錯誤代碼**/
int clk_wiz_reset(void);/*** @brief 檢查時鐘向導是否鎖定* @return 鎖定返回1,未鎖定返回0**/
int clk_wiz_is_locked(void);#endif // XCLK_WIZ_API_H
4.2 math_compat.h文件
代碼如下所示
#ifndef MATH_COMPAT_H
#define MATH_COMPAT_H/*****************************************************************************/// 替代ceil函數實現
static inline double ceil(double x) {if (x >= 0.0) {int int_part = (int)x;return (x == (double)int_part) ? x : (double)(int_part + 1);}else {int int_part = (int)x;return (double)int_part;}
}/*****************************************************************************/// 替代floor函數實現
static inline double floor(double x) {if (x >= 0.0) {return (double)((int)x);}else {int int_part = (int)x;return (x == (double)int_part) ? x : (double)(int_part - 1);}
}/*****************************************************************************/// 替代fmax函數實現
static inline double fmax(double x, double y) {return (x > y) ? x : y;
}/*****************************************************************************/// 替代fmin函數實現
static inline double fmin(double x, double y) {return (x < y) ? x : y;
}/*****************************************************************************/// 替代fabs函數實現(雖然math.h中有聲明,但可能缺少實現)
static inline double fabs(double x) {return (x < 0.0) ? -x : x;
}/*****************************************************************************/#endif // MATH_COMPAT_H
4.3 dynclk_api.c文件
代碼如下所示:
/*****************************************************************************/#include "dynclk_api.h"
#include <stdio.h>/*****************************************************************************/// 內部函數聲明
static int calculate_pll_params(double input_freq, double output_freq, int *divclk_divide, double *clkfbout_mult, double *clkout0_divide);
static u32 build_reg0_value(int divclk_divide, double clkfbout_mult);
static u32 build_reg2_value(double clkout0_divide);
static int wait_for_lock(u32 timeout_us);/*****************************************************************************/int clk_wiz_is_locked(void) {//u32 status = XClk_Wiz_ReadReg(XCLKWIZ_BASEADDR, XCLKWIZ_STATUS_OFFSET);//return (status & XCLKWIZ_STATUS_LOCKED_MASK) ? 1 : 0;
}/*****************************************************************************/static int wait_for_lock(u32 timeout_count) {//while (timeout_count--) {if (clk_wiz_is_locked()) {return XCLK_WIZ_SUCCESS;}}//return XCLK_WIZ_ERROR_LOCK_FAILED;
}/*****************************************************************************/int clk_wiz_reset(void) {//printf("2 - PLL Reset Start.\n");// 發送復位信號XClk_Wiz_WriteReg(XCLKWIZ_BASEADDR, XCLKWIZ_SOFT_RESET_OFFSET, XCLKWIZ_RESET_VALUE);// 檢查復位是否完成printf("\tWaiting for PLL lock...\n");if (wait_for_lock(COUNT_NUM_100MS) != XCLK_WIZ_SUCCESS) { // 100ms超時printf("\tError: PLL Lock Failed.\n");return XCLK_WIZ_ERROR_LOCK_FAILED;}//printf("\tPLL Reset Completed.\n\n");//return XCLK_WIZ_SUCCESS;
}/*****************************************************************************/static int calculate_pll_params(double input_freq, double output_freq, int *divclk_divide, double *clkfbout_mult, double *clkout0_divide) {//double vco_freq;int found = 0;double min_vco = MIN_VCO_FREQ;double max_vco = MAX_VCO_FREQ;// 嘗試不同的DIVCLK_DIVIDE值for (int d = MIN_DIVCLK_DIVIDE; d <= MAX_DIVCLK_DIVIDE && !found; d++) {// 計算可能的CLKFBOUT_MULT值范圍double min_mult = min_vco / (input_freq / d);double max_mult = max_vco / (input_freq / d);// 確保在有效范圍內 - 使用我們的fmax/fmin實現min_mult = (min_mult > MIN_CLKFBOUT_MULT) ? min_mult : MIN_CLKFBOUT_MULT;max_mult = (max_mult < MAX_CLKFBOUT_MULT) ? max_mult : MAX_CLKFBOUT_MULT;if (min_mult > max_mult) continue;// 計算整數部分范圍int min_int = (int)min_mult;int max_int = (int)max_mult + 1;// 嘗試CLKFBOUT_MULT值for (int m = min_int; m <= max_int && !found; m++) {// 嘗試小數部分for (int f = 0; f <= 7; f++) { // 0.125步進(0-0.875)double mult = m + f * FRAC_STEP;// 檢查是否在范圍內if (mult < min_mult || mult > max_mult) continue;// 計算VCO頻率vco_freq = (input_freq / d) * mult;// 計算所需的CLKOUT0_DIVIDEdouble divide = vco_freq / output_freq;// 檢查CLKOUT0_DIVIDE是否在范圍內if (divide < MIN_CLKOUT0_DIVIDE || divide > MAX_CLKOUT0_DIVIDE) continue;// 檢查小數部分double fractional_part = divide - (int)divide;int valid_fraction = 0;// 檢查小數部分是否是0.125的整數倍for (int i = 0; i <= 7; i++) {if ((fractional_part - i * FRAC_STEP) < 0.0001 &&(fractional_part - i * FRAC_STEP) > -0.0001) {valid_fraction = 1;break;}}if (!valid_fraction && fractional_part > 0.0001) continue;// 找到有效參數*divclk_divide = d;*clkfbout_mult = mult;*clkout0_divide = divide;found = 1;break;}}}//return found ? 0 : -1;
}/*****************************************************************************/static u32 build_reg0_value(int divclk_divide, double clkfbout_mult) {//u32 reg_value = 0;// 提取整數和小數部分int integer_part = (int)clkfbout_mult;double fractional_part = clkfbout_mult - integer_part;int fractional_value = (int)(fractional_part * FRAC_SCALE);// 構建寄存器值reg_value |= (divclk_divide & 0xFF); // Bits [7:0]reg_value |= (integer_part & 0xFF) << 8; // Bits [15:8]reg_value |= (fractional_value & 0x3FF) << 16; // Bits [25:16]//return reg_value;
}/*****************************************************************************/static u32 build_reg2_value(double clkout0_divide) {//u32 reg_value = 0;// 提取整數和小數部分int integer_part = (int)clkout0_divide;double fractional_part = clkout0_divide - integer_part;int fractional_value = (int)(fractional_part * FRAC_SCALE);// 構建寄存器值reg_value |= (integer_part & 0xFF); // Bits [7:0]reg_value |= (fractional_value & 0x3FF) << 8; // Bits [17:8]//return reg_value;
}/*****************************************************************************/int clk_wiz_configure(double input_freq, double output_freq) {//int divclk_divide;double clkfbout_mult, clkout0_divide;u32 reg0_value, reg2_value;// 參數檢查if (input_freq <= 0 || output_freq <= 0) {printf("Error: Invalid Frequency Parameters.\n");return XCLK_WIZ_ERROR_INVALID_PARAM;}// 1. 計算PLL參數if (calculate_pll_params(input_freq, output_freq, &divclk_divide,&clkfbout_mult, &clkout0_divide) != 0) {printf("Error: Cannot Find Suitable PLL Parameters.\n");return XCLK_WIZ_ERROR_INVALID_PARAM;}printf("1 - Calculate FVCO Parameters:\n");printf("\tDIVCLK_DIVIDE: %d\n", divclk_divide);printf("\tCLKFBOUT_MULT: %.3f\n", clkfbout_mult);printf("\tCLKOUT0_DIVIDE: %.3f\n\n", clkout0_divide);// 2. 復位PLLif (clk_wiz_reset() != XCLK_WIZ_SUCCESS) {return XCLK_WIZ_ERROR_RESET_FAILED;}// 3. 構建寄存器值reg0_value = build_reg0_value(divclk_divide, clkfbout_mult);reg2_value = build_reg2_value(clkout0_divide);printf("3 - Build Clock Configuration Register Values:\n");printf("\tReg0 (0x%x): 0x%lx\n", XCLKWIZ_CLK_CONFIG_00_OFFSET, reg0_value);printf("\tReg2 (0x%x): 0x%lx\n\n", XCLKWIZ_CLK_CONFIG_02_OFFSET, reg2_value);// 4. 配置寄存器XClk_Wiz_WriteReg(XCLKWIZ_BASEADDR, XCLKWIZ_CLK_CONFIG_00_OFFSET, reg0_value);XClk_Wiz_WriteReg(XCLKWIZ_BASEADDR, XCLKWIZ_CLK_CONFIG_02_OFFSET, reg2_value);printf("4 - Write Clock Configuration Register Values.\n\n");// 5. 加載新配置XClk_Wiz_WriteReg(XCLKWIZ_BASEADDR, XCLKWIZ_CLK_CONFIG_23_OFFSET, 0x3);printf("5 - Load Clock Configuration Register Values.\n\n");// 6. 等待鎖定printf("6 - Waiting for PLL Lock...\n");if (wait_for_lock(COUNT_NUM_100MS) != XCLK_WIZ_SUCCESS) {printf("Error: PLL Lock Failed.\n");return XCLK_WIZ_ERROR_LOCK_FAILED;}printf("\tPLl Lock Succeeded.\n\n");//return XCLK_WIZ_SUCCESS;
}/*****************************************************************************/
4.4 main.c文件
在14.1提供的main函數基礎上,修改while循環部分,代碼如下所示:
while (1) {//sleep(20);result = clk_wiz_configure(50, 123.75);switch(result) {case XCLK_WIZ_SUCCESS:printf("PLL Configuration Completed.\n\n");break;case XCLK_WIZ_ERROR_INVALID_PARAM:printf("Error: Invalid Parameters Provided.\n\n");break;case XCLK_WIZ_ERROR_RESET_FAILED:printf("Error: PLL Reset Failed.\n\n");break;case XCLK_WIZ_ERROR_LOCK_FAILED:printf("Error: PLL Lock Failed.\n\n");break;default:printf("Error: Unknown Error Occurred.\n\n");}//sleep(20);result = clk_wiz_configure(50, 148.5);switch(result) {case XCLK_WIZ_SUCCESS:printf("PLL Configuration Completed.\n\n");break;case XCLK_WIZ_ERROR_INVALID_PARAM:printf("Error: Invalid Parameters Provided.\n\n");break;case XCLK_WIZ_ERROR_RESET_FAILED:printf("Error: PLL Reset Failed.\n\n");break;case XCLK_WIZ_ERROR_LOCK_FAILED:printf("Error: PLL Lock Failed.\n\n");break;default:printf("Error: Unknown Error Occurred.\n\n");}}
實測效果:每隔20s,重配置一次時鐘頻率,輸出視頻在1080p@50Hz和1080p@60Hz之間切換。