使用 gem5

在 gem5 中运行程序


我们将介绍的内容


系统调用仿真模式简介


什么是系统调用仿真模式,何时使用/避免使用

系统调用仿真(SE)模式不会模拟系统中的所有设备。它专注于模拟 CPU 和内存系统。它只仿真 Linux 系统调用,并且只模拟用户模式代码。

当实验不需要模拟操作系统(例如页表遍历)、不需要高保真度模型(仿真即可),并且需要更快的仿真速度时,SE 模式是一个不错的选择。

但是,如果实验需要模拟操作系统交互,或者需要高保真度地模拟系统,那么我们应该使用全系统(FS)模式。FS 模式将在 07-full-system 中介绍。


示例

00-SE-hello-world

materials/02-Using-gem5/03-running-in-gem5/00-SE-hello-world 目录下,有一个 SE 仿真的小例子。 00-SE-hello-world.py 将使用简单的 X86 配置运行 00-SE-hello-world 二进制文件。 这个二进制文件会打印字符串 Hello, World!。 如果我们使用调试标志 SyscallAll,我们将能够看到模拟了哪些系统调用。 我们可以使用以下命令来执行:

gem5 -re --debug-flags=SyscallAll 00-SE-hello-world.py

-re--stdout-file--stderr-file 的别名,用于将输出重定向到文件。 默认输出在 m5out/simout.txt 和 m5out/simerr.txt` 中。


00-SE-hello-world

然后在 simout.txt 中,我们应该看到:

280945000: board.processor.cores.core: T0 : syscall Calling write(1, 21152, 14)...
Hello, World!
280945000: board.processor.cores.core: T0 : syscall Returned 14.

左侧是仿真的时间戳。 正如时间戳所示,SE 仿真不会记录系统调用的时间

注意,在 simout.txt 文件中,来自仿真器客户机的标准输出混合在一起。


m5ops


什么是 m5ops


重要提示


关于 m5ops 的更多信息

m5ops 有三种版本:

  1. 指令模式:仅适用于模拟的 CPU 模型
  2. 地址模式:适用于模拟的 CPU 模型和 KVM CPU(仅支持 Arm 和 X86)
  3. 半主机模式:适用于模拟的 CPU 模型和 Fast Model

应根据 CPU 类型和 ISA 使用不同的模式。

地址模式 m5ops 将在 07-full-system 中作为 gem5-bridge 介绍,并在 08-accelerating-simulation 中介绍 KVM CPU 后讨论。 在本节中,我们只介绍指令模式。


何时使用 m5ops

使用 m5ops 主要有两种方式:

  1. 注释工作负载
  2. 在磁盘镜像中进行 gem5-bridge 调用

在本节中,我们将重点学习如何使用 m5ops 来注释工作负载。


如何使用 m5ops

m5ops 提供了一个功能库。所有函数可以在 gem5/include/gem5/m5ops.h 中找到。 常用的函数(它们与上面列出的常用功能对应):

为了在工作负载中调用这些函数,我们需要将 m5ops 库链接到工作负载。 所以首先,我们需要构建 m5ops 库。


构建 m5ops 库

m5 工具位于 gem5/util/m5 目录中。​ 为了构建 m5ops 库,

  1. cd 进入 gem5/util/m5 目录
  2. 运行 scons [{TARGET_ISA}.CROSS_COMPILE={TARGET_ISA CROSS COMPILER}] build/{TARGET_ISA}/out/m5​
  3. 编译后的库(m5 用于命令行工具,libm5.a 是 C 库)将位于 gem5/util/m5/build/{TARGET_ISA}/out

注意事项


动手实践!

01-build-m5ops-library

让我们为 x86 和 arm64 构建 m5ops 库

cd /workspaces/2024/gem5/util/m5
scons build/x86/out/m5
scons arm64.CROSS_COMPILE=aarch64-linux-gnu- build/arm64/out/m5

注意:虽然我们使用 Scons 来构建这些,但这是一个与使用不同目标和选项构建 gem5 不同的环境。 不要期望它们相似(例如,使用 arm64 而不是 ARM)。


将 m5ops 库链接到 C/C++ 代码​

构建 m5ops 库后,我们可以通过以下方式将它们链接到我们的工作负载:​

  1. 在工作负载的源文件中包含 gem5/m5ops.h (<gem5/m5ops.h>)

  2. gem5/include 添加到编译器的包含搜索路径中 (-Igem5/include)

  3. gem5/util/m5/build/{TARGET_ISA}/out 添加到链接器搜索路径中 (-Lgem5/util/m5/build/{TARGET_ISA}/out)

  4. 使用 (-lm5) 链接 libm5.a


动手实践!

02-annotate-this

让我们使用 m5_work_beginm5_work_end 来注释工作负载

materials/02-Using-gem5/03-running-in-gem5/02-annotate-this 目录中,有一个名为 02-annotate-this.cpp 的工作负载源文件和一个 Makefile

工作负载主要做两件事:

  1. 将字符串写入标准输出
write(1, "This will be output to standard out\n", 36);

02-annotate-this

  1. 输出当前目录中所有文件和文件夹的名称
struct dirent *d;
DIR *dr;
dr = opendir(".");
if (dr!=NULL) {
    std::cout<<"List of Files & Folders:\n";
    for (d=readdir(dr); d!=NULL; d=readdir(dr)) {
        std::cout<<d->d_name<< ", ";
    }
    closedir(dr);
}
else {
    std::cout<<"\nError Occurred!";
}
std::cout<<std::endl;

02-annotate-this

本练习的目标

我们如何做到这一点?

  1. 使用 #include <gem5/m5ops.h> 包含 m5ops 头文件
  2. write(1, "This will be output to standard out\n", 36); 之前立即调用 m5_work_begin(0, 0);
  3. write(1, "This will be output to standard out\n", 36); 之后立即调用 m5_work_end(0, 0);
  4. 使用以下要求编译工作负载:
    1. gem5/include 添加到编译器的包含搜索路径中
    2. gem5/util/m5/build/x86/out 添加到链接器搜索路径中
    3. 链接 libm5.a

02-annotate-this

对于步骤 4,我们可以修改 Makefile 使其运行

$(GXX) -o 02-annotate-this 02-annotate-this.cpp \
  -I$(GEM5_PATH)/include \
  -L$(GEM5_PATH)/util/m5/build/$(ISA)/out \
  -lm5

如果您遇到任何问题,所有内容的完成版本位于 materials/02-Using-gem5/03-running-in-gem5/02-annotate-this/complete


02-annotate-this

如果工作负载编译成功,我们可以尝试运行它

./02-annotate-this

但是,我们将看到以下错误:

Illegal instruction (core dumped)

这是因为主机无法识别指令版本的 m5ops。

这也是如果我们在仿真中使用 KVM CPU,我们需要使用地址版本的 m5ops 的原因。


动手实践!

03-run-x86-SE

让我们编写一个处理程序来处理 m5 退出事件

首先,让我们看看默认行为是什么。进入文件夹 materials/02-Using-gem5/03-running-in-gem5/03-run-x86-SE 并使用以下命令运行 03-run-x86-SE.py

gem5 -re 03-run-x86-SE.py

运行仿真后,我们应该在 materials/02-Using-gem5/03-running-in-gem5/03-run-x86-SE 中看到一个名为 m5out 的目录。打开 m5out 中的文件 simerr.txt。我们应该看到如下两行:

warn: No behavior was set by the user for work begin. Default behavior is resetting the stats and continuing.

warn: No behavior was set by the user for work end. Default behavior is dumping the stats and continuing.

03-run-x86-SE

如前所述,gem5 标准库可能对某些 m5ops 有默认行为。在这里,我们可以看到它对 m5_work_beginm5_work_end 有默认行为。 让我们稍微绕一下,看看 gem5 标准库如何识别退出事件并为其分配默认的退出处理程序。 所有标准库定义的退出事件可以在 src/python/gem5/simulate/exit_event.py 中找到。它使用退出事件的退出字符串来对退出事件进行分类。例如,"workbegin""m5_workend instruction encountered" 退出字符串都被归类为 ExitEvent.WORKBEGIN。 所有预定义的退出事件处理程序可以在 src/python/gem5/simulate/exit_event_generators.py 中找到。

例如,ExitEvent.WORKBEGIN 默认使用 reset_stats_generator。这意味着当我们使用标准库的 Simulator 对象时,如果有退出字符串为 "workbegin""m5_workbegin instruction encountered" 的退出,它将自动执行 m5.stats.reset(),除非我们使用 gem5 stdlib Simulator 参数中的 on_exit_event 参数覆盖默认行为。


03-run-x86-SE

让我们添加自定义的 workbegin 和 workend 处理程序,并使用 Simulator 参数中的 on_exit_event 参数来覆盖默认行为。为此,将以下内容添加到 03-run-x86-SE.py 中:

# define a workbegin handler
def workbegin_handler():
    print("Workbegin handler")
    m5.debug.flags["ExecAll"].enable()
    yield False
#
# define a workend handler
def workend_handler():
    m5.debug.flags["ExecAll"].disable()
    yield False
#

###

此外,在 Simulator 对象构造中使用 on_exit_event 参数注册处理程序。

# setup handler for ExitEvent.WORKBEGIN and ExitEvent.WORKEND
    on_exit_event= {
        ExitEvent.WORKBEGIN: workbegin_handler(),
        ExitEvent.WORKEND: workend_handler()
    }
#

03-run-x86-SE

让我们使用以下命令再次运行此仿真

gem5 -re 03-run-x86-SE.py

现在,我们将在 materials/02-Using-gem5/03-running-in-gem5/03-run-x86-SE/m5out/simout.txt 中看到以下内容

3757178000: board.processor.cores.core: A0 T0 : 0x7ffff7c82572 @_end+140737350460442    :   syscall                  : IntAlu :   flags=()
This will be output to standard out
3757180000: board.processor.cores.core: A0 T0 : 0x7ffff7c82574 @_end+140737350460444    : cmp	rax, 0xfffffffffffff000

这显示了我们使用 m5.debug.flags["ExecAll"].enable() 为 ROI 启用的调试标志 ExecAll 的日志。它显示了我们 ROI 的完整执行跟踪。正如左侧的时间戳再次表明的那样,SE 模式不会记录模拟系统调用的时间。此外,正如日志所示,我们覆盖了 m5_work_beginm5_work_end 的默认行为。


然后,通过输出

List of Files & Folders:
., .., 03-run-SE.py, m5out,
Simulation Done

这表明 SE 模式能够读取主机上的文件。此外,SE 模式能够写入主机上的文件。

但是,再次强调,SE 模式不能记录模拟系统调用的时间。


SE 模式提示

使用 gem5 stdlib,我们通常使用 board 对象中的 set_se_binary_workload 函数来设置工作负载。我们可以使用相应的参数将文件、参数、环境变量和输出文件路径传递给 set_se_binary_workload 函数。

def set_se_binary_workload(
    self,
    binary: BinaryResource,
    exit_on_work_items: bool = True,
    stdin_file: Optional[FileResource] = None,
    stdout_file: Optional[Path] = None,
    stderr_file: Optional[Path] = None,
    env_list: Optional[List[str]] = None,
    arguments: List[str] = [],
    checkpoint: Optional[Union[Path, CheckpointResource]] = None,
) -> None:

更多信息,我们可以查看 src/python/gem5/components/boards/se_binary_workload.py


交叉编译


从一个 ISA 交叉编译到另一个 ISA。​

Cross compiling width:800px center


动手实践!

04-cross-compile-workload

让我们将工作负载静态和动态交叉编译到 arm64

对于静态编译,将以下命令添加到 materials/02-Using-gem5/03-running-in-gem5/04-cross-compile-workload 中的 Makefile:

$(GXX) -o 04-cross-compile-this-static 04-cross-compile-this.cpp -static -I$(GEM5_PATH)/include -L$(GEM5_PATH)/util/m5/build/$(ISA)/out -lm5

对于动态编译,添加以下命令:

$(GXX) -o 04-cross-compile-this-dynamic 04-cross-compile-this.cpp -I$(GEM5_PATH)/include -L$(GEM5_PATH)/util/m5/build/$(ISA)/out -lm5

接下来,在与 Makefile 相同的目录中运行 make


04-cross-compile-workload

注意事项:

注意我们使用 arm64 作为 ISA,使用 aarch64-linux-gnu-g++ 作为交叉编译器。这与练习 2 形成对比,练习 2 中 ISA 是 x86,编译器是 g++

还要注意,静态编译命令有 -static 标志,而动态命令没有额外的标志。


动手实践!

05-run-arm-SE

让我们运行编译好的 arm64 工作负载,看看会发生什么

首先,让我们运行静态编译的工作负载。cd 进入目录 materials/02-Using-gem5/03-running-in-gem5/05-run-arm-SE 并使用以下命令运行 05-run-arm-SE.py

gem5 -re --outdir=static 05-run-arm-SE.py --workload-type=static

接下来,让我们使用以下命令运行动态编译的工作负载:

gem5 -re --outdir=dynamic 05-run-arm-SE.py --workload-type=dynamic

05-run-arm-SE

运行动态编译的工作负载时,您将在 dynamic/simout.txt 中看到以下错误输出:

src/base/loader/image_file_data.cc:105: fatal: fatal condition fd < 0 occurred: Failed to open file /lib/ld-linux-aarch64.so.1.
This error typically occurs when the file path specified is incorrect.
Memory Usage: 217652 KBytes

要使用动态编译的工作负载,我们必须重定向库路径。我们可以通过在配置脚本中的 print("Time to redirect the library path") 下添加以下内容来实现:

setInterpDir("/usr/aarch64-linux-gnu/")
board.redirect_paths = [RedirectPath(app_path=f"/lib",
                        host_paths=[f"/usr/aarch64-linux-gnu/lib"])]


总结

SE 模式不实现很多东西!​


gem5 中的流量生成器


合成流量生成

合成流量生成是一种驱动内存子系统的技术,不需要模拟处理器模型和运行工作负载程序。关于合成流量生成,我们必须注意以下几点。

合成流量可以遵循某些模式,如 sequential (linear)stridedrandom。在本节中,我们将了解 gem5 中促进合成流量生成的工具。

Traffic generator center


gem5:用于合成流量生成的标准库组件

gem5 的标准库有一组用于生成合成流量的组件。所有这些组件都继承自 AbstractGenerator,位于 src/python/gem5/components/processors

我们将看到如何使用 LinearGeneratorRandomGenerator 来刺激内存子系统。我们将使用的内存子系统将包括一个具有 private l1 caches and a shared l2 cache 的缓存层次结构,以及一个 DDR3 内存通道。

在接下来的幻灯片中,我们将从高层次查看 LinearGeneratorRandomGenerator。我们将看到如何编写使用它们的配置脚本。


##

LinearGenerator

Python Here

class LinearGenerator(AbstractGenerator):
    def __init__(
        self,
        num_cores: int = 1,
        duration: str = "1ms",
        rate: str = "100GB/s",
        block_size: int = 64,
        min_addr: int = 0,
        max_addr: int = 32768,
        rd_perc: int = 100,
        data_limit: int = 0,
    ) -> None:

RandomGenerator

Python Here

class RandomGenerator(AbstractGenerator):
    def __init__(
        self,
        num_cores: int = 1,
        duration: str = "1ms",
        rate: str = "100GB/s",
        block_size: int = 64,
        min_addr: int = 0,
        max_addr: int = 32768,
        rd_perc: int = 100,
        data_limit: int = 0,
    ) -> None:

LinearGenerator/RandomGenerator:参数

###


流量模式可视化

min_addr: 0, max_addr: 4, block_size: 1

Linear(线性):我们想要访问地址 0 到 4,所以线性访问意味着按以下顺序访问内存。

Random(随机):我们想要访问地址 0 到 4,所以随机访问意味着以任何顺序访问内存。(在此示例中,我们显示的顺序是:1, 3, 2, 0)。

###

linear traffic pattern

random traffic pattern


动手实践!

06-traffic-gen

让我们运行一个关于如何使用流量生成器的示例

打开以下文件。 materials/02-Using-gem5/03-running-in-gem5/06-traffic-gen/simple-traffic-generators.py

步骤:

  1. 使用线性流量生成器运行。
  2. 使用混合流量生成器运行。

06-traffic-gen: LinearGenerator:查看代码

转到右侧的代码部分。

现在,我们已经设置了一个具有私有 L1 共享 L2 缓存层次结构的板(转到 materials/02-Using-gem5/03-running-in-gem5/06-traffic-gen/components/cache_hierarchy.py 查看其构造方式),以及一个单通道内存系统。

memory = SingleChannelDDR3_1600() 下方立即添加流量生成器,使用以下行。

generator = LinearGenerator(num_cores=1, rate="1GB/s")

###

cache_hierarchy = MyPrivateL1SharedL2CacheHierarchy()

memory = SingleChannelDDR3_1600()

motherboard = TestBoard(
    clk_freq="3GHz",
    generator=generator,
    memory=memory,
    cache_hierarchy=cache_hierarchy,
)

06-traffic-gen: LinearGenerator:完成的代码

完成的代码片段应该如下所示。

cache_hierarchy = MyPrivateL1SharedL2CacheHierarchy()

memory = SingleChannelDDR3_1600()

generator = LinearGenerator(num_cores=1, rate="1GB/s")

motherboard = TestBoard(
    clk_freq="3GHz",
    generator=generator,
    memory=memory,
    cache_hierarchy=cache_hierarchy,
)

06-traffic-gen: LinearGenerator:运行代码

运行以下命令以查看线性流量生成器的运行情况

cd ./materials/02-Using-gem5/03-running-in-gem5/06-traffic-gen/

gem5 --debug-flags=TrafficGen --debug-end=1000000 \
simple-traffic-generators.py

我们将在下一张幻灯片中看到一些预期的输出。


06-traffic-gen: LinearGenerator 结果

  59605: system.processor.cores.generator: LinearGen::getNextPacket: r to addr 0, size 64
  59605: system.processor.cores.generator: Next event scheduled at 119210
 119210: system.processor.cores.generator: LinearGen::getNextPacket: r to addr 40, size 64
 119210: system.processor.cores.generator: Next event scheduled at 178815
 178815: system.processor.cores.generator: LinearGen::getNextPacket: r to addr 80, size 64
 178815: system.processor.cores.generator: Next event scheduled at 238420

在整个输出中,我们看到 r to addr --。这意味着流量生成器正在模拟访问内存地址 0x--请求。

在上面,我们在第 1 行看到 r to addr 0,在第 3 行看到 r to addr 40,在第 5 行看到 r to addr 80

这是因为线性流量生成器正在模拟访问内存地址 0x0000、0x0040、0x0080 的请求。

如您所见,模拟的请求非常线性。每次新的内存访问都比前一次高 0x0040 字节。


06-traffic-gen: Random

我们现在不会这样做,但如果您将 LinearGenerator 替换为 RandomGenerator 并保持参数相同,输出将如下所示。请注意地址模式不再是线性序列。

  59605: system.processor.cores.generator: RandomGen::getNextPacket: r to addr 2000, size 64
  59605: system.processor.cores.generator: Next event scheduled at 119210
 119210: system.processor.cores.generator: RandomGen::getNextPacket: r to addr 7900, size 64
 119210: system.processor.cores.generator: Next event scheduled at 178815
 178815: system.processor.cores.generator: RandomGen::getNextPacket: r to addr 33c0, size 64
 178815: system.processor.cores.generator: Next event scheduled at 238420

我们的重点:LinearGenerator 和 AbstractGenerator

Different Generators


详细查看某些组件

接下来,我们将查看如何扩展 AbstractGenerator 以创建同时具有 LinearGeneratorCoresRandomGeneratorCoresHybridGenerator


扩展 AbstractGenerator

gem5 在其标准库中有很多工具,但如果您想在研究中模拟特定的内存访问模式,标准库中可能没有相应的工具。

在这种情况下,您必须扩展 AbstractGenerator 以创建适合您需求的具体生成器。

为此,我们将通过一个名为 HybridGenerator 的示例来说明。

HybridGenerator 的目标是同时模拟线性和随机内存访问。

为此,我们需要 LinearGeneratorCores(用于模拟线性流量)和 RandomGeneratorCores(用于模拟随机流量)。


06-traffic-gen: HybridGenerator:关于 LinearGeneratorCores 的快速说明

LinearGeneratorCores 模拟线性流量。

当我们有多个 LinearGeneratorCores 时,如果我们将每个都配置为具有相同的 min_addrmax_addr,每个都将从相同的 min_addr 开始模拟内存访问,并上升到相同的 max_addr。它们将同时访问相同的地址。

我们希望 LinearGeneratorCore 模拟更合理的访问模式。

因此,我们将让每个 LinearGeneratorCore 模拟对不同内存块的访问。为此,我们必须将内存分割成大小相等的块,并配置每个 LinearGeneratorCore 以模拟对这些块之一的访问。


06-traffic-gen: HybridGenerator:关于 LinearGeneratorCores 的快速说明(续)

这是一个显示每个 LinearGeneratorCore 应该如何访问内存的图表。

Linear Generator Core Memory Access Diagram


06-traffic-gen: HybridGenerator:划分内存地址范围

当我们创建 HybridGenerator 时,我们必须确定哪个 LinearGeneratorCore 获得哪个内存块。

如前所述,我们需要将内存地址范围划分为大小相等的部分,并配置每个 LinearGeneratorCore 以模拟对不同部分的访问。

为了划分,我们将使用 gem5/src/python/gem5/components/processors/abstract_generator.py 中的 partition_range() 函数。

此函数接受 min_addrmax_addr 的范围,并将其划分为 num_partitions 个等长的片段。

例如,如果 min_addr = 0,max_addr = 9,num_partitions = 3,那么 partition_range 将返回 <0,3>、<3,6>、<6,9>。


06-traffic-gen: HybridGenerator:关于 RandomGeneratorCores 的快速提醒

我们还必须考虑 RandomGeneratorCores

假设我们应该像 LinearGeneratorCores 一样对它们进行分区是合理的,但事实并非如此。

即使每个 RandomGeneratorCore 具有相同的 min_addrmax_addr,由于每个都模拟随机内存访问,每个都将模拟对不同(随机)内存地址的访问。


06-traffic-gen: HybridGenerator:划分内存地址范围(续)

最后,这就是每个核心将如何模拟内存访问。

Linear vs. Random memory accesses


06-traffic-gen: HybridGenerator:选择核心分布

既然我们知道每个核心将如何访问内存,接下来,我们需要确定需要多少个 LinearGeneratorCoresRandomGeneratorCores

有很多正确的方法可以做到这一点,但我们将使用以下函数来确定 LinearGeneratorCores 的数量。

        def get_num_linear_cores(num_cores: int):
            """
            Returns the largest power of two that is smaller than num_cores
            """
            if (num_cores & (num_cores - 1) == 0):
                return num_cores//2
            else:
                return 2 ** int(log(num_cores, 2))

其余的核心将是 RandomGeneratorCores


06-traffic-gen: HybridGenerator 构造函数

让我们开始查看代码!

确保您已打开以下文件。 materials/02-Using-gem5/03-running-in-gem5/06-traffic-gen/components/hybrid_generator.py

在右侧,您将看到 HybridGenerator 的构造函数。

当我们初始化 HybridGenerator(通过 def __init__)时,我们将使用右侧的值初始化 AbstractGenerator(通过 super() __init__)。

class HybridGenerator(AbstractGenerator):
    def __init__(
        self,
        num_cores: int = 2,
        duration: str = "1ms",
        rate: str = "1GB/s",
        block_size: int = 8,
        min_addr: int = 0,
        max_addr: int = 131072,
        rd_perc: int = 100,
        data_limit: int = 0,
    ) -> None:
        if num_cores < 2:
            raise ValueError("num_cores should be >= 2!")
        super().__init__(
            cores=self._create_cores(
                num_cores=num_cores,
                duration=duration,
                rate=rate,
                block_size=block_size,
                min_addr=min_addr,
                max_addr=max_addr,
                rd_perc=rd_perc,
                data_limit=data_limit,
            )
        )

06-traffic-gen: 设计 HybridGenerator

现在,我们的 HybridGenerator 类有一个构造函数,但我们需要返回一个核心列表。

在 gem5 中,返回核心列表的方法通常命名为 _create_cores

如果您查看我们的文件 hybrid_generator.py,您会看到这个名为 _create_cores 的方法。


06-traffic-gen: HybridGenerator:初始化变量

让我们定义 _create_cores

让我们首先声明/定义一些重要的变量。

首先,我们将声明我们的核心列表。

然后,我们将定义 LinearGeneratorCoresRandomGeneratorCores 的数量。

在标记为 (1) 的注释下添加以下行。

core_list = []

num_linear_cores = get_num_linear_cores(num_cores)
num_random_cores = num_cores - num_linear_cores

06-traffic-gen: HybridGenerator:划分内存地址范围

接下来,让我们为每个 LinearGeneratorCore 定义内存地址范围。

如果我们想给每个 LinearGeneratorCore 一个相等的给定内存地址范围块,我们需要将 min_addrmax_addr 的范围划分为 num_linear_cores 个片段。

为此,我们需要在标记为 (2) 的注释下向代码添加以下行。

addr_ranges = partition_range(min_addr, max_addr, num_linear_cores)

addr_ranges 将是一个从 min_addrmax_addr 的等长分区的 num_linear_cores 长度列表。


06-traffic-gen: 划分内存地址范围(续)

例如,我们有 min_addr=0max_addr=32768num_cores=16(8 个 LinearGeneratorCores),那么

addr_ranges=
  [(0, 4096), (4096, 8192), (8192, 12288), (12288, 16384),
  (16384, 20480), (20480, 24576), (24576, 28672), (28672, 32768)]

对于第 iLinearGeneratorCore,我们取 addr_ranges 中的第 i 个条目。min_addr 是该条目的第一个值,max_addr 是该条目中的第二个值。

在此示例中,LinearGeneratorCore 0 使用 min_addr=0max_addr=4096 初始化,LinearGeneratorCore 1 使用 min_addr=4096max_addr=8192 初始化,依此类推。


06-traffic-gen: HybridGenerator:创建核心列表:LinearGeneratorCore

接下来,让我们开始创建我们的核心列表。

首先,让我们添加所有 LinearGeneratorCores

在标记为 (3) 的注释下添加右侧的行。

for i in range(num_linear_cores):
            core_list.append(LinearGeneratorCore(
                duration=duration,
                rate=rate,
                block_size=block_size,
                min_addr=addr_ranges[i][0],
                max_addr=addr_ranges[i][1],
                rd_perc=rd_perc,
                data_limit=data_limit,)
            )

06-traffic-gen: HybridGenerator:创建核心列表说明:LinearGeneratorCore

在 for 循环中,我们创建 num_linear_coresLinearGeneratorCores,并将每个添加到我们的 core_list 中。

每个 LinearGeneratorCore 参数都使用构造函数中的相同值初始化,除了 min_addrmax_addr

我们更改 min_addrmax_addr,以便每个 LinearGeneratorCore 只模拟对 HybridGeneratormin_addrmax_addr 范围的一部分的访问。

###

for i in range(num_linear_cores):
            core_list.append(LinearGeneratorCore(
                duration=duration,
                rate=rate,
                block_size=block_size,
                min_addr=addr_ranges[i][0],
                max_addr=addr_ranges[i][1],
                rd_perc=rd_perc,
                data_limit=data_limit,)
            )

06-traffic-gen: HybridGenerator:创建核心列表:RandomGeneratorCore

现在我们已经添加了 LinearGeneratorCores,让我们添加所有 RandomGeneratorCores

在标记为 (4) 的注释下添加右侧的行。

###

for i in range(num_random_cores):
            core_list.append(RandomGeneratorCore(
                duration=duration,
                rate=rate,
                block_size=block_size,
                min_addr=min_addr,
                max_addr=max_addr,
                rd_perc=rd_perc,
                data_limit=data_limit,)
            )

06-traffic-gen: HybridGenerator:创建核心列表说明:RandomGeneratorCore

再次,在 for 循环中,我们创建 num_linear_coresRandomGeneratorCores,并将每个添加到我们的 core_list 中。

每个 RandomGeneratorCore 参数都使用构造函数中的相同值初始化,包括 min_addrmax_addr

min_addrmax_addr 不会改变,因为每个 RandomGeneratorCore 应该能够访问 HybridGeneratormin_addrmax_addr 的整个范围。

###

for i in range(num_random_cores):
            core_list.append(RandomGeneratorCore(
                duration=duration,
                rate=rate,
                block_size=block_size,
                min_addr=min_addr,
                max_addr=max_addr,
                rd_perc=rd_perc,
                data_limit=data_limit,)
            )

06-traffic-gen: HybridGenerator:返回并开始配置

我们几乎完成了这个文件!

让我们通过在标记为 (5) 的注释下添加以下行来返回我们的 core_list

return core_list

现在,打开文件 materials/02-Using-gem5/03-running-in-gem5/06-traffic-gen/simple-traffic-generators.py

让我们用 HybridGenerator 替换我们的 LinearGenerator

首先,在代码顶部的某个位置添加以下行以导入 HybridGenerator

from components.hybrid_generator import HybridGenerator

06-traffic-gen: HybridGenerator:配置

在右侧的这段代码中,您当前应该有一个 LinearGenerator

让我们用 HybridGenerator 替换它。

将以下行

generator = LinearGenerator(
    num_cores=1
)

替换为

generator = HybridGenerator(
    num_cores=6
)

###

cache_hierarchy = MyPrivateL1SharedL2CacheHierarchy()

memory = SingleChannelDDR3_1600()

generator = LinearGenerator(
    num_cores=1
)

motherboard = TestBoard(
    clk_freq="3GHz",
    generator=generator,
    memory=memory,
    cache_hierarchy=cache_hierarchy,
)

06-traffic-gen: HybridGenerator:配置(续)

现在它应该如下所示。

###

cache_hierarchy = MyPrivateL1SharedL2CacheHierarchy()

memory = SingleChannelDDR3_1600()

generator = HybridGenerator(
    num_cores=6
)

motherboard = TestBoard(
    clk_freq="3GHz",
    generator=generator,
    memory=memory,
    cache_hierarchy=cache_hierarchy,
)

06-traffic-gen: HybridGenerator:运行

现在,我们已经创建了一个 HybridGenerator,让我们再次运行程序!

确保您在以下目录中。

materials/02-Using-gem5/03-running-in-gem5/06-traffic-gen/

现在使用以下命令运行。

gem5 --debug-flags=TrafficGen --debug-end=1000000 \
simple-traffic-generators.py

06-traffic-gen: HybridGenerator:输出

运行命令后,您应该看到类似以下的内容。

   7451: system.processor.cores5.generator: RandomGen::getNextPacket: r to addr 80a8, size 8
   7451: system.processor.cores5.generator: Next event scheduled at 14902
   7451: system.processor.cores4.generator: RandomGen::getNextPacket: r to addr 10a90, size 8
   7451: system.processor.cores4.generator: Next event scheduled at 14902
   7451: system.processor.cores3.generator: LinearGen::getNextPacket: r to addr 18000, size 8
   7451: system.processor.cores3.generator: Next event scheduled at 14902
   7451: system.processor.cores2.generator: LinearGen::getNextPacket: r to addr 10000, size 8
   7451: system.processor.cores2.generator: Next event scheduled at 14902
   7451: system.processor.cores1.generator: LinearGen::getNextPacket: r to addr 8000, size 8
   7451: system.processor.cores1.generator: Next event scheduled at 14902
   7451: system.processor.cores0.generator: LinearGen::getNextPacket: r to addr 0, size 8
   7451: system.processor.cores0.generator: Next event scheduled at 14902

如您所见,核心 0、1、2 和 3 是 LinearGeneratorCores,核心 4 和 5 是 RandomGeneratorCores


06-traffic-gen: HybridGenerator:统计信息

现在,让我们看看 LinearGeneratorCoresRandomGeneratorCores 之间的一些统计差异。

运行以下命令以查看每个核心的 l1 数据缓存的未命中率。

grep ReadReq.missRate::processor m5out/stats.txt

在下一张幻灯片中,您将看到预期的输出(为便于阅读,删除了一些文本)。


06-traffic-gen: HybridGenerator:统计信息(续)

system.cache_hierarchy.l1dcaches0.ReadReq.missRate::processor.cores0.generator     0.132345
system.cache_hierarchy.l1dcaches1.ReadReq.missRate::processor.cores1.generator     0.133418
system.cache_hierarchy.l1dcaches2.ReadReq.missRate::processor.cores2.generator     0.133641
system.cache_hierarchy.l1dcaches3.ReadReq.missRate::processor.cores3.generator     0.132971
system.cache_hierarchy.l1dcaches4.ReadReq.missRate::processor.cores4.generator     0.876426
system.cache_hierarchy.l1dcaches5.ReadReq.missRate::processor.cores5.generator     0.875055

核心 0、1、2 和 3(LinearGeneratorCores)的平均未命中率为 0.13309375(约 13.3%)。

核心 4 和 5(RandomGeneratorCores)的平均未命中率为 0.8757405(约 87.5%)。

这是因为 LinearGeneratorCores 线性访问内存。因此,它们表现出更多的局部性,这反过来导致 l1dcache 中的未命中更少。

另一方面,由于 RandomGeneratorCores 随机访问内存,缓存无法以相同的方式利用局部性。


总结

总的来说,我们讨论了两类流量生成器:Linear(线性)Random(随机)

LinearGenerators 模拟线性内存访问,RandomGenerators 模拟随机内存访问。

我们研究了如何配置使用这些流量生成器的板。

我们还扩展了 AbstractGenerator 类以创建 HybridGenerator,它同时模拟线性和随机内存访问。

最后,我们看到了 LinearGeneratorCoresRandomGeneratorCores 之间的一些统计差异。