1.5 軟件低功耗設計
軟件可以區分為“系統軟件”、“實時操作系統”和“應用軟件”。軟件對于一個低功耗系統的重要性常常被人們忽略。一個重要的原因是,軟件上與功耗設計有關的缺陷并不像硬件那樣容易發現,同時軟件的低功耗特性也沒有一個嚴格的標準來判斷。而對于一個低功耗系統設計,設計者必須注意軟件的低功耗設計方法,盡可能避免那些“看不見”的功耗損失。
1.5.1 編譯優化
在微處理器執行的程序中,每一條指令都將激活微處理器中的某些硬件部件。因此,可以認為每一條指令都有一個固定的功率消耗量,正確選擇指令可以降低微處理器的功耗。通過建立特定處理器架構下指令集的功耗信息,采用“減少跳轉的指令重排序”等方法,可以有效地優化軟件的低功耗設計。
編譯器的作用是將由高級語言(如C/C++等)編寫的程序,翻譯成能夠在目標機上執行的程序。編譯器為高級語言程序員提供了一個抽象層,使得程序員能夠不用匯編或機器語言,而直接采用高級語言代碼編寫解決實際問題的程序。同時,編譯器也使得程序的可讀性和可維護性得到保證,可以提高軟件開發的效率。另外,當需要將程序移植到新的目標機時,也只需要采用相應的編譯器對程序進行重新編譯,而不必重新編寫程序。
但在某些情況下,編譯器的一些做法是以犧牲程序的執行能力為代價的,即需要增加執行的指令數。因此,通過對編譯器的優化,生成效率更高的代碼,可以有效地降低微處理器的功耗。
1.5.2 指令排序
對于一個微處理器,執行某一特定程序的功耗為
E=P×t (1.8)
式中,P=I×VDD(I為平均電流,VDD為微處理器的電源電壓);t為程序的執行時間(t=N×T,T為指令周期,即為主頻的倒數,N為程序執行的周期數)。
因此有
E=(I×VDD)×(N×T) (1.9)
由式(1.9)可見,當VDD和T都是已知量時,程序消耗的電能E與電流I和程序周期數N的乘積成正比。
可以通過建立一定的模型來測量并估計執行每條指令所需要的電流I。在一個嵌入式系統中,可以利用嵌入式微處理器中的多數據存儲區域的特性,實現數據的并行處理,通過對指令的排序,減少指令的執行周期,從而達到降低功耗的目的。
現假設需要完成圖1.15(a)所示的運算,圖1.15(b)所示是其相應的匯編代碼。圖1.15(c)所示為每個節點帶有兩個權值的數據依賴圖(DataDependenceGraph,DDG),第一個權值表示節點在DDG中的深度,如V10的第一個權值為1,V0的第一個權值為6。假設這個權值越大,表示其優先級越高,如圖1.15(c)中V0和V1具有最高的優先級。
指令排序前節點的執行順序見表1.9。注意,表中V2(ADD)、V6(ADD)和V9(MPY)的指令與其他指令(MOVE)不同,ADD和MPY指令需要用到系統的ALU部件。在同一指令周期中,可以同時執行ALU運算及MOVE操作,但是不可以同時執行兩個ALU操作。

圖1.15 運算要求、匯編代碼與數據依賴圖
表1.9 指令排序前節點的執行順序
節點的第二個權值,表示相關寄存器的生命周期。指令排序前的狀態如圖1.16所示,V0所依賴的寄存器是r0,它的生命周期為1到3,即為2。從圖1.16中可以得出以下結論:此段程序總共需要11個指令周期和最少同時使用2個寄存器。
基于排序算法,將指令重新排序后的情況如圖1.17所示,程序總的執行周期變為6,但是所占用的寄存器個數增加到3。由此也可以看到,程序的執行周期與寄存器的個數之間也是一個折中權衡的結果。

圖1.16 指令排序前的狀態

圖1.17 基于排序算法的重新排序
可以通過建立一定的模型來測量并估計執行每條指令所需要的電流I。在該示例中,程序執行時所需要的總電流I=780mA。如圖1.16所示,在不使用任何算法的情況,即I總=N×I=11×780(mA)=8580(mA)。在圖1.17中,總的執行周期數為N=6,因此電路消耗的電流為I總=N×I=6×780(mA)=4680(mA)。由此可見,通過使用基于排序算法,減少了程序的執行周期,程序的執行性能得到提高。同時,由E=(I×VDD)×(N×T)可知,N的減少,也大大降低了執行程序的功耗。
優化的算法描述,主要是以減少程序的執行周期為目的,同時考慮到使用盡量少的寄存器。從優化系統的功耗層面上來看,算法的優化也是非常重要的。
1.5.3 常用的降低軟件功耗的方法
采用高效率的算法可以有效地降低功耗,一些常用的方法如下:
(1)用查表的方法代替實時的計算,盡量減少CPU的運算量。特別是在沒有硬件浮點處理單元的MCU進行浮點處理時,直接用MCU進行浮點處理將會消耗大量的時間。將一些運算的結果預先算好,放在Flash存儲器中,用查表的方法替代實時的計算,減少CPU的運算工作量,可以有效地降低CPU的功耗。很多微處理器都有快速有效的查表指令和尋址方式,用于優化查表算法。這種處理方法在離散余弦變換和A/D數據采集中能夠帶來可觀的效率提升。
(2)對于不可避免的實時計算,應注意計算的精度,算到精度夠了就應立即結束,避免“過度”的計算。在精度允許的情況下,使用簡單函數代替復雜函數作近似運算,也是減少功耗的有效方法。
(3)盡量使用短的數據類型,如盡量使用字符型的8位數據替代16位的整型數據。
(4)盡量使用分數運算而避免浮點數運算等。
(5)用移位運算代替乘除法運算。采用MCU計算乘除法也是非常耗時的,如果采用左移和右移的辦法來實現乘除法運算,將會減少運算時間。注意,除法的移位計算只能針對除數比較特殊的情況。
(6)采用快速算法。在搜索算法中,使用二分搜索算法和分段查找算法的效率是不同的。從理論上可以估算,在1024個測量值的查找中,二分搜索最壞情況下10次可以查找到結果,順序搜索最壞可能需要1024次。這在測量數值更多的情況下更為突出,一個高效率的查找算法有助于減小程序運行功耗。
(7)數字信號處理中的運算,采用FFT和快速卷積等,可以節省大量運算時間。
(8)一個程序使用中斷方式還是查詢方式,對于很多應用來說并不那么重要,但在軟件低功耗設計特性上卻相差甚遠。例如,ADC在采集少量的數據時,MCU讀取A/D轉換數據可以采用查詢方式或中斷方式。查詢方式和中斷方式的低功耗特性相差甚遠。使用中斷方式,MCU可以什么都不做,甚至可以進入待機或停止模式。而采用查詢方式,MCU必須不停地讀取I/O端口寄存器,需要消耗很多額外的功耗。
(9)采用定時器。在程序中可以采用軟件延時。但是,如果系統的定時器資源充裕,在需要定時的場合,最好采用硬件定時器,當定時器到了定時時間后,向MCU發出中斷請求信號,這樣可以減少MCU的工作時間,進而可以降低功耗。
(10)用宏代替子程序。在程序執行的過程中,讀RAM需要比讀Flash更大的功耗。宏是在編譯器預處理階段進行替代,而在子程序的調用中MCU需要進行現場保護。在一次子程序調用中,因為CPU進入子程序時會首先將當前CPU寄存器推入堆棧(RAM),在離開時又將CPU寄存器彈出堆棧,這樣至少對RAM有兩次操作。對于程序設計來說,調用一個子程序還是一個宏,在程序寫法上并沒有什么不同,但宏會在編譯時展開,CPU只是順序執行指令,避免了調用子程序。唯一的問題是增加了代碼的長度(代碼量)。目前,MCU片內的Flash空間越來越大,對于一些不在乎程序代碼量大一些的應用,用宏代替子程序無疑可以降低系統的功耗。