與大多數現代 CPU 一樣,ARM CPU 支持 DVFS(動態電壓頻率調整)。可以在 gem5 中對此進行建模,並監控由此產生的功耗使用情況。DVFS 建模是通過使用時鐘對象的兩個組件來實現的:電壓域和時鐘域。本章節詳細介紹了這些不同的組件,並展示了將它們添加到現有模擬中的不同方法。

電壓域

        電壓域規定了 CPU 可以使用的電壓值。如果在 gem5 中運行全系統模擬時沒有指定電壓域,則使用默認值 1.0 伏特。這是為了避免在用户對模擬電壓不感興趣時強迫他們考慮電壓問題。

        電壓域可以通過單個值或值列表來構造,通過 voltage 關鍵字參數傳遞給 VoltageDomain 構造函數。如果指定了單個值和多個頻率,則該電壓將用於時鐘域中的所有頻率。如果指定了電壓值列表,則其條目數必須與相應時鐘域中的條目數匹配,並且條目必須按降序排列。與真實硬件一樣,一個電壓域適用於整個處理器插槽。這意味着如果您希望為不同的處理器(例如,在 big.LITTLE 設置中)設置不同的電壓域,您需要確保 big 集羣和 LITTLE 集羣在不同的插槽上(檢查與集羣關聯的 socket_id 值)。

        有兩種方法可以將電壓域添加到現有的 CPU/模擬中,一種更靈活,另一種更直接。第一種方法向提供的 configs/example/arm/fs_bigLITTLE.py 文件添加命令行標誌,而第二種方法添加自定義類。

  1. 使用命令行標誌(更靈活)
            向模擬添加電壓域最靈活的方法是使用命令行標誌。要添加命令行標誌,請在文件的 addOptions 函數中找到並添加該標誌,可以選擇附帶一些幫助文本。
    支持單個和多個電壓的示例:
def addOptions(parser):
    [...]
    parser.add_argument("--big-cpu-voltage", nargs="+", default="1.0V",
                        help="Big CPU voltage(s).")
    return parser

        然後可以使用以下方式指定電壓域值:

--big-cpu-voltage <val1>V [<val2>V [<val3>V [...]]]

        這將在 build 函數中通過 options.big_cpu_voltage 訪問。nargs="+" 確保至少需要一個參數。在 build 中的使用示例:

def build(options):
    [...]
    # big cluster
    if options.big_cpus > 0:
        system.bigCluster = big_model(system, options.big_cpus,
                                      options.big_cpu_clock,
                                      options.big_cpu_voltage)
    [...]

        可以添加類似的標誌和對 build 函數的補充,以支持為 LITTLE CPU 指定電壓值。這種方法可以非常容易地指定和修改電壓。這種方法的唯一缺點是多個命令行參數(有些是列表形式)可能會使用於調用模擬器的命令變得混亂。

  1. 使用子類(不太靈活)
            指定電壓域的不太靈活的方法是創建 CpuCluster 的子類。與現有的 BigCluster 和 LittleCluster 子類類似,這些子類將擴展 CpuCluster 類。在子類的構造函數中,除了指定 CPU 類型外,我們還為電壓域定義一個值列表,並使用 cpu_voltage 關鍵字參數將其傳遞給父類構造函數。以下是為 BigCluster 添加電壓的示例:
class VDBigCluster(devices.CpuCluster):
    def __init__(self, system, num_cpus, cpu_clock=None, cpu_voltage=None):
        # use the same CPU as the stock BigCluster
        abstract_cpu = ObjectList.cpu_list.get("O3_ARM_v7a_3")
        # voltage value(s)
        my_voltages = [ '1.0V', '0.75V', '0.51V']

        super(VDBigCluster, self).__init__(
            cpu_voltage=my_voltages,
            system=system,
            num_cpus=num_cpus,
            cpu_type=abstract_cpu,
            l1i_type=devices.L1I,
            l1d_type=devices.L1D,
            wcache_type=devices.WalkCache,
            l2_type=devices.L2
        )

        然後可以通過定義類似的 VDLittleCluster 類來為 LittleCluster 添加電壓。
定義了子類之後,我們仍然需要向文件中的 cpu_types 字典添加一個條目,指定一個字符串名稱作為鍵,一對類作為值,例如:

cpu_types = {
    [...]
    "vd-timing" : (VDBigCluster, VDLittleCluster)
}

        然後可以通過傳遞以下命令來使用具有電壓域的 CPU:

--cpu-type vd-timing

        由於對電壓值的任何修改都必須通過找到正確的子類並修改其代碼,或者添加更多子類和 cpu_types 條目來完成,因此這種方法比基於標誌的方法靈活性差很多。

時鐘域

        電壓域與時鐘域結合使用。如前所述,如果未指定自定義電壓值,則對時鐘域中的所有值使用默認值 1.0V。

時鐘域的類型
        與電壓域相反,時鐘域有 3 種類型(來自 src/sim/clock_domain.hh):

  • ClockDomain -- 為捆綁在同一時鐘域下的一組時鐘對象提供時鐘。時鐘域又分組到電壓域中。時鐘域支持具有"源"和"派生"時鐘域的層次結構。
  • SrcClockDomain -- 提供了連接到可調時鐘源的時鐘域的概念。它維護時鐘週期並提供設置/獲取時鐘的方法,以及處理程序將要管理的時鐘域的配置參數。這包括不同性能級別下的頻率值、域 ID 和當前性能級別。請注意,軟件請求的性能級別對應於時鍾域可以運行的頻率操作點之一。
  • DerivedClockDomain -- 提供了連接到父時鐘域的時鐘域的概念,父時鐘域可以是 SrcClockDomain 或 DerivedClockDomain。它維護時鐘分頻器並提供獲取時鐘的方法。

向現有模擬添加時鐘域

        此示例將使用與電壓域示例相同的提供文件,即 configs/example/arm/fs_bigLITTLE.py 和 configs/example/arm/devices.py

        與電壓域類似,時鐘域可以是單個值或值列表。如果給定了時鐘速度列表,則適用與提供給電壓域的電壓列表相同的規則,即時鐘域中的值數量必須與電壓域中的值數量匹配;並且時鐘速度必須按降序給出。提供的文件支持將時鐘指定為單個值(通過 --{big,little}-cpu-clock 標誌),但不支持作為值列表。擴展/修改所提供標誌的行為是添加多值時鐘域支持的最簡單和最靈活的方法,但也可以通過添加子類來實現。

  1. 為現有的 --{big,little}-cpu-clock 標誌添加多值支持
            找到 configs/example/arm/fs_bigLITTLE.py 文件中的 addOptions 函數。在各種 parser.add_argument 調用中,找到添加 CPU 時鐘標誌的那些,並將關鍵字參數 type=str 替換為 nargs="+"
def addOptions(parser):
    [...]
    parser.add_argument("--big-cpu-clock", nargs="+", default="2GHz",
                        help="Big CPU clock frequency.")
    parser.add_argument("--little-cpu-clock", nargs="+", default="1GHz",
                        help="Little CPU clock frequency.")
    [...]

        這樣,可以類似於用於電壓域的標誌來指定多個頻率:

--{big,little}-cpu-clock <val1>GHz [<val2>MHz [<val3>MHz [...]]]

        由於這修改了現有的標誌,標誌的值已經連接到 build 函數中的相關構造函數和關鍵字參數,因此在那裏不需要修改任何東西。

  1. 在子類中添加時鐘域
            這個過程與將電壓域作為子類添加的過程非常相似。區別在於,我們不是指定電壓並使用 cpu_voltage 關鍵字參數,而是指定時鐘值並在 super 調用中使用 cpu_clock 關鍵字參數:
class CDBigCluster(devices.CpuCluster):
    def __init__(self, system, num_cpus, cpu_clock=None, cpu_voltage=None):
        # use the same CPU as the stock BigCluster
        abstract_cpu = ObjectList.cpu_list.get("O3_ARM_v7a_3")
        # clock value(s)
        my_freqs = [ '1510MHz', '1000MHz', '667MHz']

        super(CDBigCluster, self).__init__( # 注意:這裏類名修正為 CDBigCluster
            cpu_clock=my_freqs,
            system=system,
            num_cpus=num_cpus,
            cpu_type=abstract_cpu,
            l1i_type=devices.L1I,
            l1d_type=devices.L1D,
            wcache_type=devices.WalkCache,
            l2_type=devices.L2
        )

        這可以與電壓域示例結合,以便為集羣同時指定電壓域和時鐘域。
與使用此方法添加電壓域一樣,您需要為您想要使用的每種 CPU 類型定義一個類,並在 cpu_types 字典中指定它們的名稱-cpuPair 值。這種方法也有相同的侷限性,並且比基於標誌的方法靈活性差很多。

確保時鐘域具有有效的域 ID

        無論使用前面的哪種方法,都需要進行一些額外的修改。這些修改涉及提供的 configs/example/arm/devices.py 文件。

        在文件中,找到 CpuClusters 類,並找到 self.clk_domain 被初始化為 SrcClockDomain 的地方。如上文關於 SrcClockDomain 的註釋所述,這些域有一個域 ID。如果沒有設置,就像在提供的設置中那樣,將使用默認 ID -1。請更改代碼以確保設置了域 ID:

[...]
self.clk_domain = SrcClockDomain(clock=cpu_clock,
                                 voltage_domain=self.voltage_domain,
                                 domain_id=system.numCpuClusters())
[...]

        這裏使用 system.numCpuClusters() 是因為時鐘域適用於整個集羣,即第一個集羣為 0,第二個集羣為 1,依此類推。

        如果您不設置域 ID,當嘗試運行支持 DVFS 的模擬時,您將遇到以下錯誤,因為一些內部檢查捕獲了默認的域 ID:

fatal: fatal condition domain_id == SrcClockDomain::emptyDomainID occurred:
DVFS: Controlled domain system.bigCluster.clk_domain needs to have a properly
assigned ID.

DVFS 處理程序

        如果您指定了電壓域和時鐘域,然後嘗試運行模擬,它很可能會運行,但您可能會在輸出中注意到以下警告:

warn: Existing EnergyCtrl, but no enabled DVFSHandler found.

        電壓域和時鐘域已添加,但沒有系統可以與之交互以調整值的 DVFSHandler。解決此問題的最簡單方法是在 configs/example/arm/fs_bigLITTLE.py 文件中添加另一個命令行標誌。

        與電壓域和時鐘域示例一樣,找到 addOptions 函數並將以下代碼附加到其中:

def addOptions(parser):
    [...]
    parser.add_argument("--dvfs", action="store_true",
                        help="Enable the DVFS Handler.")
    return parser

        然後,找到 build 函數並將此代碼附加到其中:

def build(options):
    [...]
    if options.dvfs:
        system.dvfs_handler.domains = [system.bigCluster.clk_domain,
                                       system.littleCluster.clk_domain]
        system.dvfs_handler.enable = options.dvfs

    return root

        完成這些設置後,您現在應該能夠通過在調用模擬時使用 --dvfs 標誌來運行支持 DVFS 的模擬,並可以根據需要指定 big 和 LITTLE 集羣的電壓和頻率操作點。