authors: Jasjeet Rangi, Kunal Pai
last edited: 2024-11-15 22:30:44 +0000

How to Create Your Own Board Using the gem5 Standard Library

In this tutorial we will cover how to create a custom board using the gem5 Standard Library.

This tutorial is based on the process used to make the RiscvMatched, a RISC-V prebuilt board that inherits from MinorCPU. This board can be found at src/python/gem5/prebuilt/riscvmatched.

This tutorial will create a single-channeled DDR4 memory of size 2 GiB, a core using the MinorCPU and the RISC-V ISA though the same process can be used for another type or size of memory, ISA and core.

Likewise, this tutorial will utilize the UniqueCacheHierarchy made in the Developing Your Own Components Tutorial, though anyother cache hierarchy may be used.

First, we start by importing the components and stdlib features we require.

from gem5.components.cachehierarchies.classic.unique_cache_hierarchy import UniqueCacheHierarchy
from gem5.components.boards.abstract_system_board import AbstractSystemBoard
from gem5.components.processors.base_cpu_processor import BaseCPUProcessor
from gem5.components.processors.base_cpu_core import BaseCPUCore
from gem5.components.boards.se_binary_workload import SEBinaryWorkload
from gem5.components.memory import SingleChannelDDR4_2400
from gem5.utils.override import overrides
from gem5.isas import ISA
from typing import List
from m5.objects import AddrRange, IOXBar, Port
from m5.objects import BaseMMU, Port, BaseCPU, Process
from m5.objects.RiscvCPU import RiscvMinorCPU

We will begin development by creating a specialized CPU core for our board which inherits from an ISA-specific version of the chosen CPU. Since our ISA is RISC-V and the CPU type we desire is a MinorCPU, we will inherit from RiscvMinorCPU. This is done so that we can set our own parameters to tailor the CPU it to our requirements. In our example will override a single parameter: decodeToExecuteForwardDelay (the default is 1). We have called this new CPU core type UniqueCPU.

class UniqueCPU(RiscvMinorCPU):
    decodeToExecuteForwardDelay = 2

As RiscvMinorCPU inherits from BaseCPU, we can incorporate this into the standard library using BaseCPUCore, a Standard Library wrapper for BaseCPU objects (source code for this can be found at src/python/gem5/components/processors/base_cpu_core.py). The BaseCPUCore takes the BaseCPU as an argument during construction. Ergo, we can do the following:

core = BaseCPUCore(core = UniqueCPU(core_id=0))

Note: BaseCPU objects require a unique core_id to be specified upon construction.

Next we must define our processor. In the gem5 Standard Library a processor is a collection of cores. In cases, such as ours, we can utilize the library’s BaseCPUProcessor, a processor which contains BaseCPUCore objects (source code can be found in src/python/gem5/components/processors/base_cpu_processor.py). The BaseCPUProcessor requires a list of BaseCPUCores. Therefore:

processor = BaseCPUProcessor(cores = [core])

Next we focus on the construction of the board to host our components. All boards must inherit from AbstractBoard and in most cases, gem5’s System simobject. Therefore, our board will inherit from AbstractSystemBoard in this case; an abstract class that inherits from both.

In order to run simulations with SE mode, we must also inherit from SEBinaryWorkload.

All AbstractBoards must specify clk_freq (the clock frequency), the processor, memory, and the cache_hierarchy. We already have our processor, and will use the UniqueCacheHierarchy for the cache_hierarchy and a SingleChannelDDR4_2400, with a size of 2GiB for the memory.

We will call this the UniqueBoard and it should look like the following:

class UniqueBoard(AbstractSystemBoard, SEBinaryWorkload):
    def __init__(
        self,
        clk_freq: str,
    ) -> None:
        core = BaseCPUCore(core = UniqueCPU(core_id=0))
        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,
        )

With the contructor complete, we must implement the abstract methods in AbstractSystemBoard. It is useful here to look at the source for AbstractBoard in /src/python/gem5/components/boards/abstract_system_board.py.

The abstract methods you choose to implement or not will depend on what type of system you are creating. In our example functions such as _setup_board, are unneeded so we will implement them with pass. In other instances we will use NotImplementedError for cases where a particular component/feature is not available on this board and an error should be returned if trying to access it. For example, our board will have no IO bus. We will therefore implement has_io_bus to return False and have get_io_bus raise a NotImplementedError if called.

With the exception of _setup_memory_ranges, we do not implement many of the features the AbstractSystemBoard requires. The board should look like this:

class UniqueBoard(AbstractSystemBoard, SEBinaryWorkload):
    def __init__(
        self,
        clk_freq: str,
    ) -> None:
        core = BaseCPUCore(core = UniqueCPU(core_id=0))
        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)

This concludes the creation of your custom board for the gem5 standard library. From this you can create a runscript and test your board:

from .unqiue_board import UniqueBoard
from gem5.resources.resource import 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(Resource("riscv-hello"))

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