authors: Jasjeet Rangi, Kunal Pai

如何使用 gem5 标准库创建您自己的开发板

在本教程中,我们将介绍如何使用 gem5 标准库创建自定义开发板。

本教程基于制作 RiscvMatched 的过程,这是一个继承自 MinorCPU 的 RISC-V 预构建开发板。此开发板可以在 src/python/gem5/prebuilt/riscvmatched 找到。

本教程将创建一个大小为 2 GiB 的单通道 DDR4 内存,一个使用 MinorCPU 和 RISC-V ISA 的核心,尽管相同的过程可以用于其他类型或大小的内存、ISA 和核心。

同样,本教程将利用在开发您自己的组件教程中制作的 UniqueCacheHierarchy,尽管可以使用任何其他缓存层次结构。

首先,我们开始导入所需的组件和标准库功能。

from typing import List

from m5.objects import (
    AddrRange,
    BaseCPU,
    BaseMMU,
    IOXBar,
    Port,
    Process,
)
from m5.objects.RiscvCPU import RiscvMinorCPU

from gem5.components.boards.abstract_system_board import AbstractSystemBoard
from gem5.components.boards.se_binary_workload import SEBinaryWorkload
from gem5.components.cachehierarchies.classic.unique_cache_hierarchy import (
    UniqueCacheHierarchy,
)
from gem5.components.memory import SingleChannelDDR4_2400
from gem5.components.processors.base_cpu_core import BaseCPUCore
from gem5.components.processors.base_cpu_processor import BaseCPUProcessor
from gem5.isas import ISA
from gem5.utils.override import overrides

我们将通过为我们的开发板创建一个专门的 CPU 核心来开始开发,该核心继承自所选 CPU 的 ISA 特定版本。 由于我们的 ISA 是 RISC-V,我们想要的 CPU 类型是 MinorCPU,我们将从 RiscvMinorCPU 继承。 这样做是为了我们可以设置自己的参数以根据我们的要求定制 CPU。 在我们的示例中,我们将覆盖单个参数:decodeToExecuteForwardDelay(默认值为 1)。 我们将这个新的 CPU 核心类型称为 UniqueCPU

class UniqueCPU(RiscvMinorCPU):
    decodeToExecuteForwardDelay = 2

由于 RiscvMinorCPU 继承自 BaseCPU,我们可以使用 BaseCPUCoreBaseCPU 对象的标准库包装器,其源代码可以在 src/python/gem5/components/processors/base_cpu_core.py 找到)将其整合到标准库中。 BaseCPUCore 在构造时将 BaseCPU 作为参数。 因此,我们可以执行以下操作:

core = BaseCPUCore(core=UniqueCPU(), isa=ISA.RISCV)

接下来我们必须定义我们的处理器。 在 gem5 标准库中,处理器是核心的集合。 在这种情况下,例如我们的情况,我们可以利用库的 BaseCPUProcessor,一个包含 BaseCPUCore 对象的处理器(源代码可以在 src/python/gem5/components/processors/base_cpu_processor.py 找到)。 BaseCPUProcessor 需要一个 BaseCPUCore 列表。 因此:

processor = BaseCPUProcessor(cores=[core])

接下来我们专注于构建开发板以承载我们的组件。 所有开发板都必须继承自 AbstractBoard,在大多数情况下,还要继承 gem5 的 System simobject。 因此,在这种情况下,我们的开发板将从 AbstractSystemBoard 继承;这是一个继承自两者的抽象类。

为了在 SE 模式下运行模拟,我们还必须继承自 SEBinaryWorkload

所有 AbstractBoard 都必须指定 clk_freq(时钟频率)、processormemorycache_hierarchy。 我们已经有了处理器,并将使用 UniqueCacheHierarchy 作为 cache_hierarchy,使用大小为 2GiB 的 SingleChannelDDR4_2400 作为内存。

我们将此称为 UniqueBoard,它应该如下所示:

class UniqueBoard(AbstractSystemBoard, SEBinaryWorkload):
    def __init__(
        self,
        clk_freq: str,
    ) -> None:
        core = BaseCPUCore(core=UniqueCPU(), isa=ISA.RISCV)
        processor = BaseCPUProcessor(cores=[core])
        memory = SingleChannelDDR4_2400("2GiB")
        cache_hierarchy = UniqueCacheHierarchy()
        super().__init__(
            clk_freq=clk_freq,
            processor=processor,
            memory=memory,
            cache_hierarchy=cache_hierarchy,
        )

构造函数完成后,我们必须实现 AbstractSystemBoard 中的抽象方法。 在这里查看 /src/python/gem5/components/boards/abstract_system_board.pyAbstractBoard 的源代码很有用。

您选择实现或不实现的抽象方法将取决于您创建的系统类型。 在我们的示例中,诸如 _setup_board 之类的函数是不需要的,因此我们将使用 pass 实现它们。 在其他情况下,我们将使用 NotImplementedError 来处理此开发板上不可用的特定组件/功能的情况,如果尝试访问它,应该返回错误。 例如,我们的开发板将没有 IO 总线。 因此,我们将实现 has_io_bus 返回 False,并让 get_io_bus 在调用时引发 NotImplementedError

除了 _setup_memory_ranges 之外,我们不实现 AbstractSystemBoard 所需的许多功能。开发板应该如下所示:

class UniqueBoard(AbstractSystemBoard, SEBinaryWorkload):
    def __init__(
        self,
        clk_freq: str,
    ) -> None:
        core = BaseCPUCore(core=UniqueCPU(), isa=ISA.RISCV)
        processor = BaseCPUProcessor(cores=[core])
        memory = SingleChannelDDR4_2400("2GiB")
        cache_hierarchy = UniqueCacheHierarchy()
        super().__init__(
            clk_freq=clk_freq,
            processor=processor,
            memory=memory,
            cache_hierarchy=cache_hierarchy,
        )

    @overrides(AbstractSystemBoard)
    def _setup_board(self) -> None:
        pass

    @overrides(AbstractSystemBoard)
    def has_io_bus(self) -> bool:
        return False

    @overrides(AbstractSystemBoard)
    def get_io_bus(self) -> IOXBar:
        raise NotImplementedError(
            "UniqueBoard does not have an IO Bus. "
            "Use `has_io_bus()` to check this."
        )

    @overrides(AbstractSystemBoard)
    def has_dma_ports(self) -> bool:
        return False

    @overrides(AbstractSystemBoard)
    def get_dma_ports(self) -> List[Port]:
        raise NotImplementedError(
            "UniqueBoard does not have DMA Ports. "
            "Use `has_dma_ports()` to check this."
        )

    @overrides(AbstractSystemBoard)
    def has_coherent_io(self) -> bool:
        return False

    @overrides(AbstractSystemBoard)
    def get_mem_side_coherent_io_port(self) -> Port:
        raise NotImplementedError(
            "UniqueBoard does not have any I/O ports. Use has_coherent_io to "
            "check this."
        )

    @overrides(AbstractSystemBoard)
    def _setup_memory_ranges(self) -> None:
        memory = self.get_memory()
        self.mem_ranges = [AddrRange(memory.get_size())]
        memory.set_memory_range(self.mem_ranges)

这完成了为 gem5 标准库创建自定义开发板的工作。 完成的开发板如下:

from typing import List

from m5.objects import (
    AddrRange,
    BaseCPU,
    BaseMMU,
    IOXBar,
    Port,
    Process,
)
from m5.objects.RiscvCPU import RiscvMinorCPU

from gem5.components.boards.abstract_system_board import AbstractSystemBoard
from gem5.components.boards.se_binary_workload import SEBinaryWorkload
from gem5.components.cachehierarchies.classic.unique_cache_hierarchy import (
    UniqueCacheHierarchy,
)
from gem5.components.memory import SingleChannelDDR4_2400
from gem5.components.processors.base_cpu_core import BaseCPUCore
from gem5.components.processors.base_cpu_processor import BaseCPUProcessor
from gem5.isas import ISA
from gem5.utils.override import overrides


class UniqueCPU(RiscvMinorCPU):
    decodeToExecuteForwardDelay = 2


class UniqueBoard(AbstractSystemBoard, SEBinaryWorkload):
    def __init__(
        self,
        clk_freq: str,
    ) -> None:
        core = BaseCPUCore(core=UniqueCPU(), isa=ISA.RISCV)
        processor = BaseCPUProcessor(cores=[core])
        memory = SingleChannelDDR4_2400("2GiB")
        cache_hierarchy = UniqueCacheHierarchy()
        super().__init__(
            clk_freq=clk_freq,
            processor=processor,
            memory=memory,
            cache_hierarchy=cache_hierarchy,
        )

    @overrides(AbstractSystemBoard)
    def _setup_board(self) -> None:
        pass

    @overrides(AbstractSystemBoard)
    def has_io_bus(self) -> bool:
        return False

    @overrides(AbstractSystemBoard)
    def get_io_bus(self) -> IOXBar:
        raise NotImplementedError(
            "UniqueBoard does not have an IO Bus. "
            "Use `has_io_bus()` to check this."
        )

    @overrides(AbstractSystemBoard)
    def has_dma_ports(self) -> bool:
        return False

    @overrides(AbstractSystemBoard)
    def get_dma_ports(self) -> List[Port]:
        raise NotImplementedError(
            "UniqueBoard does not have DMA Ports. "
            "Use `has_dma_ports()` to check this."
        )

    @overrides(AbstractSystemBoard)
    def has_coherent_io(self) -> bool:
        return False

    @overrides(AbstractSystemBoard)
    def get_mem_side_coherent_io_port(self) -> Port:
        raise NotImplementedError(
            "UniqueBoard does not have any I/O ports. Use has_coherent_io to "
            "check this."
        )

    @overrides(AbstractSystemBoard)
    def _setup_memory_ranges(self) -> None:
        memory = self.get_memory()
        self.mem_ranges = [AddrRange(memory.get_size())]
        memory.set_memory_range(self.mem_ranges)

由此,您可以创建一个运行脚本并测试您的开发板:

from unique_board import UniqueBoard

from gem5.resources.resource import obtain_resource
from gem5.simulate.simulator import Simulator

board = UniqueBoard(clk_freq="1.2GHz")

# As we are using the RISCV ISA, "riscv-hello" should work.
board.set_se_binary_workload(obtain_resource("riscv-hello"))

simulator = Simulator(board=board)
simulator.run()