【再話Zedboard】如何在SDK中計算某段程序的執行時間

URL: http://blog.chinaaet.com/detail/31789
首先贊一下自動保存功能,今天在網頁上寫的,不小心關掉了,那個心疼啊,幸好有自動保存功能,成功恢復了!
廢話不多說了,直奔主題吧。
計算一段程序的執行時間主要是爲了方便計算一些算法的效率,當然,如果能夠計算出一段程序的執行時間,也就能夠輕鬆編寫出精確延時時間了。
調試51單片機的時候,可以可以在Keil中設定斷點,直觀地計算出兩個斷點之間的程序運行時間,也可以利用反彙編代碼計算某段程序的運行時間。
對於zynq的SDK而言,第一種方法無法實現;第二種方法雖然可行,但對於較長的程序段,計算起來是很麻煩的,也不太可取。
思來想去,也有兩種比較可行的方法:
1、利用一個GPIO指示程序運行的開始和結束,例如程序開始前和結束後將GPIO置低,程序運行過程中,將GPIO拉高,用示波器測量高電平持續時間。
2、獲取某程序段執行前程序運行的機器週期總數N1,該程序段執行後程序運行的機器週期總數N2,就可以計算出該段程序的執行時間T,T=(N2-N1)*機器週期。
第一種方法就不多說了,比較簡單,缺點是需要外界測量設備配合。
第二種方法可以利用ARM 內核中的Performance Monitor Unit(PMU,性能檢測單元)實現。
下面首先給出示例代碼,然後再分析實現過程。

#include <stdio.h>
#include "sleep.h"
#include "xil_io.h"
#include "xtime_l.h"
#include "xil_printf.h"
#include "xpm_counter.h"
#include "xparameters.h"
#define COUNTS_PER_SECOND          (XPAR_CPU_CORTEXA9_CORE_CLOCK_FREQ_HZ / 64)

intmain()
{
    XTime tEnd, tCur;
    u32 tUsed; 
    XTime_GetTime(&tCur);
    usleep(1345);
    XTime_GetTime(&tEnd);
    tUsed = ((tEnd-tCur)*1000000)/(COUNTS_PER_SECOND);
    printf("time elapsed is %d us\r\n",tUsed);
    while(1);  //等待
    return0;
}
調試結果:

本例測量誤差爲:3.57%。對於多個程序段的測量後發現,誤差具有隨機性,例如,延遲45us,測量值爲52us,誤差約爲16%;延時13045us,測量值爲12533us,誤差約爲3.92%。
雖然存在一定的誤差,也差強人意了,精度問題需要進一步研究。
下面,分析一下具體代碼:
上面代碼關鍵點有兩條:
1、XTime_GetTime()函數如何實現;
2、公式tUsed = ((tEnd-tCur)*1000000)/(COUNTS_PER_SECOND)的含義;
 
1、XTime_GetTime()的代碼如下:
/****************************************************************************
*
* Get the time from the Cycle Counter Register.
*
* @param    Pointer to the location to be updated with the time.
*
* @return   None.
*
* @note     None.
*
****************************************************************************/
void XTime_GetTime(XTime *Xtime)
{
    u32 reg;
    u32 low;
 
    /* loop until we got a consistent result */
    do {
#ifdef __GNUC__
        low = mfcp(XREG_CP15_PERF_CYCLE_COUNTER);
        reg = mfcp(XREG_CP15_V_FLAG_STATUS);
#else
        { register unsigned int Reg __asm(XREG_CP15_PERF_CYCLE_COUNTER);
          low = Reg; }
        { register unsigned int Reg __asm(XREG_CP15_V_FLAG_STATUS);
          reg = Reg; }
#endif
        if (reg & CYCLE_COUNTER_MASK) {
            /* clear overflow */
            mtcp(XREG_CP15_V_FLAG_STATUS, CYCLE_COUNTER_MASK);
            high++;
        }
    } while (reg & CYCLE_COUNTER_MASK);
 
    *Xtime = (((XTime) high) << 32) | (XTime) low;
}
其中:
#define XREG_CP15_PERF_CYCLE_COUNTER  "cp15:0:c9:c13:0"
#define XREG_CP15_V_FLAG_STATUS   "cp15:0:c9:c12:3"
調試的時候,由於定義了 #define __GNUC__的情況下,執行
#ifdef __GNUC__ 和 #else之間的代碼。
mfcp指令定義爲:
#define mfcp(rn)    ({unsigned int rval; \
             __asm__ __volatile__(\
               "mrc " rn "\n"\
               : "=r" (rval)\
             );\
             rval;\
             })
mfcp指令如下:

這裏糾結啊,找了這麼多資料,還是看不明白,最後反彙編結果如下:


上述指令操作的是arm CP15協處理器的 c9 寄存器(performance monitors)。
mrc  p15, 0, r12, cr9, cr13, {0}              ;將CP15的寄存器C9的值讀到r12中,由下圖可以看出該語句是讀取PMCCNTR寄存器
mrc  p15, 0, r3, cr9, cr12, {3}              ;將CP15的寄存器C9的值讀到r3中,由下圖可以看出該語句是讀取PMOVSR寄存器
至此,XTime_GetTime()函數解析得差不多了,不過多少還是有點不清晰,希望在行的網友能給我一些幫助~


最終讀取的是PMCCNTR,也就是Performance Monitors Cycle Count Register的值。接下來看一下PMCCNTR:
 
 由上圖可以看出,計數週期可能存在兩個值:i)、PMCR.D = 0,每個時鐘週期計數一次 ii)、PMCR.D = 1時,每64個時鐘週期計數一次。
時間計算公式應根據這兩種情況確定,我這裏用的公式是針對的第ii種情況。
至於系統什麼時候給PMCR.D賦值,明天再分析吧,今天有點晚了~
關於PMCR.D=1的賦值語句,我在這簡單給一下,就不再新開一篇了:
具體賦值是在standalone_bsp提供的啓動代碼,對cpu初始化時進行的,具體代碼爲:

mov r2, #0xd        /* D, C, E */
mcr p15, 0, r2, c9, c12, 0
mov r2, #0x80000000     /* enable cycle counter */
mcr p15, 0, r2, c9, c12, 1
具體意思就不贅述了,文章中已經給出了相關參考資料。該段代碼位於cpu_init.s中。


以上引用了cuter在ChinaAET的博客,參照其代碼自建了工程,非常感謝。這裏補一些自己的說明。

硬件設施:ZedBoard

開發環境:Xilinx Design Tools 14.4

將cuter的源碼導入工程後,得出的結果誤差很大。後來發現是XTime_GetTime的實現方式不同。

/****************************************************************************
*
* Get the time from the Global Timer Counter Register.
*
* @param	Pointer to the location to be updated with the time.
*
* @return	None.
*
* @note		None.
*
****************************************************************************/
void XTime_GetTime(XTime *Xtime)
{
	u32 low;
	u32 high;

	/* Reading Global Timer Counter Register */
	do
	{
		high = Xil_In32(GLOBAL_TMR_BASEADDR + GTIMER_COUNTER_UPPER_OFFSET);
		low = Xil_In32(GLOBAL_TMR_BASEADDR + GTIMER_COUNTER_LOWER_OFFSET);
	} while(Xil_In32(GLOBAL_TMR_BASEADDR + GTIMER_COUNTER_UPPER_OFFSET) != high);

	*Xtime = (((XTime) high) << 32) | (XTime) low;
}
看的出,這裏是由Cortex-A9的全局定時器讀取的計數值,該定時器使用的是CPU時鐘的二分頻。所以更改後的代碼爲:
/*
 * PMU_Demo.c
 *
 *  Created on: 2014-7-14
 *      Author: Administrator
 */
#include <stdio.h>
#include "sleep.h"
#include "xil_io.h"
#include "xtime_l.h"
#include "xil_printf.h"
#include "xpm_counter.h"
#include "xparameters.h"

#define COUNTS_PER_SECOND          (XPAR_CPU_CORTEXA9_CORE_CLOCK_FREQ_HZ / 2)

int main()
{
    XTime tEnd, tCur;
    u32 tUsed;

    XTime_GetTime(&tCur);
    usleep(1345);
    XTime_GetTime(&tEnd);
    tUsed = ((tEnd-tCur)*1000000)/(COUNTS_PER_SECOND);
    xil_printf("time elapsed is %d us\r\n",tUsed);
    while(1);   //等待

    return 0;
}

此時得出的時間誤差就非常小了。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章