stdlib: Create BaseCPUCore type

This separates the idea of a SimpleCore and a BaseCPUCore. A SimpleCore
selects the correct BaseCPU subclass based on user-specified CPUTypes
and target ISA. The new BaseCPUCore type simply wraps any BaseCPU core
for usage in the stdlib.

Much of the code previously handled in SimpleCore has been moved to
BaseCPUCore.

The `cpu_simobject_factory` method has been moved from AbstractCore to
SimpleCore; a more logical location for this function.

Change-Id: I29ce9e381e7d5e8fe57e0db5deb04ad976b7dab9
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/62292
Maintainer: Jason Lowe-Power <power.jg@gmail.com>
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jason Lowe-Power <power.jg@gmail.com>
This commit is contained in:
Bobby R. Bruce
2022-08-10 16:03:58 -07:00
committed by Bobby Bruce
parent d023d8a3dd
commit d9b6a7ff9e
4 changed files with 199 additions and 154 deletions

View File

@@ -200,6 +200,8 @@ PySource('gem5.components.processors',
'gem5/components/processors/random_generator.py')
PySource('gem5.components.processors',
'gem5/components/processors/simple_core.py')
PySource('gem5.components.processors',
'gem5/components/processors/base_cpu_core.py')
PySource('gem5.components.processors',
'gem5/components/processors/simple_processor.py')
PySource('gem5.components.processors',

View File

@@ -26,12 +26,8 @@
from abc import ABCMeta, abstractmethod
from typing import Optional
import importlib
import platform
from .cpu_types import CPUTypes
from ...isas import ISA
from ...utils.requires import requires
from m5.objects import BaseMMU, Port, SubSystem
@@ -105,84 +101,3 @@ class AbstractCore(SubSystem):
This is used in the board to setup system-specific MMU settings.
"""
raise NotImplementedError
@classmethod
def cpu_simobject_factory(cls, cpu_type: CPUTypes, isa: ISA, core_id: int):
"""
A factory used to return the SimObject core object given the cpu type,
and ISA target. An exception will be thrown if there is an
incompatibility.
:param cpu_type: The target CPU type.
:param isa: The target ISA.
:param core_id: The id of the core to be returned.
"""
requires(isa_required=isa)
_isa_string_map = {
ISA.X86: "X86",
ISA.ARM: "Arm",
ISA.RISCV: "Riscv",
ISA.SPARC: "Sparc",
ISA.POWER: "Power",
ISA.MIPS: "Mips",
}
_cpu_types_string_map = {
CPUTypes.ATOMIC: "AtomicSimpleCPU",
CPUTypes.O3: "O3CPU",
CPUTypes.TIMING: "TimingSimpleCPU",
CPUTypes.KVM: "KvmCPU",
CPUTypes.MINOR: "MinorCPU",
}
if isa not in _isa_string_map:
raise NotImplementedError(
f"ISA '{isa.name}' does not have an"
"entry in `AbstractCore.cpu_simobject_factory._isa_string_map`"
)
if cpu_type not in _cpu_types_string_map:
raise NotImplementedError(
f"CPUType '{cpu_type.name}' "
"does not have an entry in "
"`AbstractCore.cpu_simobject_factory._cpu_types_string_map`"
)
if cpu_type == CPUTypes.KVM:
# For some reason, the KVM CPU is under "m5.objects" not the
# "m5.objects.{ISA}CPU".
module_str = f"m5.objects"
else:
module_str = f"m5.objects.{_isa_string_map[isa]}CPU"
# GEM5 compiles two versions of KVM for ARM depending upon the host CPU
# : ArmKvmCPU and ArmV8KvmCPU for 32 bit (Armv7l) and 64 bit (Armv8)
# respectively.
if (
isa.name == "ARM"
and cpu_type == CPUTypes.KVM
and platform.architecture()[0] == "64bit"
):
cpu_class_str = (
f"{_isa_string_map[isa]}V8"
f"{_cpu_types_string_map[cpu_type]}"
)
else:
cpu_class_str = (
f"{_isa_string_map[isa]}" f"{_cpu_types_string_map[cpu_type]}"
)
try:
to_return_cls = getattr(
importlib.import_module(module_str), cpu_class_str
)
except ImportError:
raise Exception(
f"Cannot find CPU type '{cpu_type.name}' for '{isa.name}' "
"ISA. Please ensure you have compiled the correct version of "
"gem5."
)
return to_return_cls(cpu_id=core_id)

View File

@@ -0,0 +1,117 @@
# Copyright (c) 2022 The Regents of the University of California
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met: redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer;
# redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution;
# neither the name of the copyright holders nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from typing import Optional
from ...utils.requires import requires
from .abstract_core import AbstractCore
from ...isas import ISA
from ...runtime import get_runtime_isa
from ...utils.override import overrides
from ...utils.requires import requires
from m5.objects import BaseMMU, Port, BaseCPU, Process
class BaseCPUCore(AbstractCore):
"""
An stdlib AbstractCore subclass which wraps a BaseCPU SimObject type.
"""
def __init__(self, core: BaseCPU, isa: Optional[ISA] = None):
super().__init__()
# There is some annoying redundancy here. The BaseCPU type already
# defines the ISA, so here we are defining it twice. However, there
# currently isn't a good way to get the ISA from the BaseCPU Type.
if isa:
requires(isa_required=isa)
self._isa = isa
else:
self._isa = get_runtime_isa()
self.core = core
self.core.createThreads()
def get_simobject(self) -> BaseCPU:
return self.core
@overrides(AbstractCore)
def get_isa(self) -> ISA:
return self._isa
@overrides(AbstractCore)
def connect_icache(self, port: Port) -> None:
self.core.icache_port = port
@overrides(AbstractCore)
def connect_dcache(self, port: Port) -> None:
self.core.dcache_port = port
@overrides(AbstractCore)
def connect_walker_ports(self, port1: Port, port2: Port) -> None:
if self.get_isa() == ISA.ARM:
# Unlike X86 and RISCV MMU, the ARM MMU has two L1 TLB walker ports
# named `walker` and `stage2_walker` for both data and instruction.
# The gem5 standard library currently supports one TLB walker port
# per cache level. Therefore, we are explicitly setting the walker
# ports and not setting the stage2_walker ports for ARM systems.
self.core.mmu.itb_walker.port = port1
self.core.mmu.dtb_walker.port = port2
else:
self.core.mmu.connectWalkerPorts(port1, port2)
@overrides(AbstractCore)
def set_workload(self, process: Process) -> None:
self.core.workload = process
@overrides(AbstractCore)
def set_switched_out(self, value: bool) -> None:
self.core.switched_out = value
@overrides(AbstractCore)
def connect_interrupt(
self,
interrupt_requestor: Optional[Port] = None,
interrupt_responce: Optional[Port] = None,
) -> None:
# TODO: This model assumes that we will only create an interrupt
# controller as we require it. Not sure how true this is in all cases.
self.core.createInterruptController()
if self.get_isa().value == ISA.X86.value:
if interrupt_requestor != None:
self.core.interrupts[0].pio = interrupt_requestor
self.core.interrupts[0].int_responder = interrupt_requestor
if interrupt_responce != None:
self.core.interrupts[0].int_requestor = interrupt_responce
@overrides(AbstractCore)
def get_mmu(self) -> BaseMMU:
return self.core.mmu

View File

@@ -26,18 +26,15 @@
from typing import Optional
from ...utils.requires import requires
from ..processors.abstract_core import AbstractCore
from .base_cpu_core import BaseCPUCore
from .cpu_types import CPUTypes
from ...isas import ISA
from ...runtime import get_runtime_isa
from ...utils.override import overrides
from ...utils.requires import requires
from m5.objects import BaseMMU, Port, BaseCPU, Process
import importlib
import platform
class SimpleCore(AbstractCore):
class SimpleCore(BaseCPUCore):
"""
A SimpleCore instantiates a core based on the CPUType enum pass. The
SimpleCore creates a single SimObject of that type.
@@ -46,81 +43,95 @@ class SimpleCore(AbstractCore):
def __init__(
self, cpu_type: CPUTypes, core_id: int, isa: Optional[ISA] = None
):
super().__init__()
super().__init__(
core=SimpleCore.cpu_simobject_factory(
isa=isa, cpu_type=cpu_type, core_id=core_id
),
isa=isa,
)
self._cpu_type = cpu_type
if cpu_type == CPUTypes.KVM:
requires(kvm_required=True)
if isa:
requires(isa_required=isa)
self._isa = isa
else:
self._isa = get_runtime_isa()
self.core = AbstractCore.cpu_simobject_factory(
isa=self._isa, cpu_type=cpu_type, core_id=core_id
)
self.core.createThreads()
def get_simobject(self) -> BaseCPU:
return self.core
def get_type(self) -> CPUTypes:
return self._cpu_type
@overrides(AbstractCore)
def get_isa(self) -> ISA:
return self._isa
@classmethod
def cpu_simobject_factory(cls, cpu_type: CPUTypes, isa: ISA, core_id: int):
"""
A factory used to return the SimObject core object given the cpu type,
and ISA target. An exception will be thrown if there is an
incompatibility.
@overrides(AbstractCore)
def connect_icache(self, port: Port) -> None:
self.core.icache_port = port
:param cpu_type: The target CPU type.
:param isa: The target ISA.
:param core_id: The id of the core to be returned.
"""
requires(isa_required=isa)
@overrides(AbstractCore)
def connect_dcache(self, port: Port) -> None:
self.core.dcache_port = port
_isa_string_map = {
ISA.X86: "X86",
ISA.ARM: "Arm",
ISA.RISCV: "Riscv",
ISA.SPARC: "Sparc",
ISA.POWER: "Power",
ISA.MIPS: "Mips",
}
@overrides(AbstractCore)
def connect_walker_ports(self, port1: Port, port2: Port) -> None:
if self.get_isa() == ISA.ARM:
_cpu_types_string_map = {
CPUTypes.ATOMIC: "AtomicSimpleCPU",
CPUTypes.O3: "O3CPU",
CPUTypes.TIMING: "TimingSimpleCPU",
CPUTypes.KVM: "KvmCPU",
CPUTypes.MINOR: "MinorCPU",
}
# Unlike X86 and RISCV MMU, the ARM MMU has two L1 TLB walker ports
# named `walker` and `stage2_walker` for both data and instruction.
# The gem5 standard library currently supports one TLB walker port
# per cache level. Therefore, we are explicitly setting the walker
# ports and not setting the stage2_walker ports for ARM systems.
if isa not in _isa_string_map:
raise NotImplementedError(
f"ISA '{isa.name}' does not have an"
"entry in `AbstractCore.cpu_simobject_factory._isa_string_map`"
)
self.core.mmu.itb_walker.port = port1
self.core.mmu.dtb_walker.port = port2
if cpu_type not in _cpu_types_string_map:
raise NotImplementedError(
f"CPUType '{cpu_type.name}' "
"does not have an entry in "
"`AbstractCore.cpu_simobject_factory._cpu_types_string_map`"
)
if cpu_type == CPUTypes.KVM:
# For some reason, the KVM CPU is under "m5.objects" not the
# "m5.objects.{ISA}CPU".
module_str = f"m5.objects"
else:
self.core.mmu.connectWalkerPorts(port1, port2)
module_str = f"m5.objects.{_isa_string_map[isa]}CPU"
@overrides(AbstractCore)
def set_workload(self, process: Process) -> None:
self.core.workload = process
# GEM5 compiles two versions of KVM for ARM depending upon the host CPU
# : ArmKvmCPU and ArmV8KvmCPU for 32 bit (Armv7l) and 64 bit (Armv8)
# respectively.
@overrides(AbstractCore)
def set_switched_out(self, value: bool) -> None:
self.core.switched_out = value
if (
isa.name == "ARM"
and cpu_type == CPUTypes.KVM
and platform.architecture()[0] == "64bit"
):
cpu_class_str = (
f"{_isa_string_map[isa]}V8"
f"{_cpu_types_string_map[cpu_type]}"
)
else:
cpu_class_str = (
f"{_isa_string_map[isa]}" f"{_cpu_types_string_map[cpu_type]}"
)
@overrides(AbstractCore)
def connect_interrupt(
self,
interrupt_requestor: Optional[Port] = None,
interrupt_responce: Optional[Port] = None,
) -> None:
try:
to_return_cls = getattr(
importlib.import_module(module_str), cpu_class_str
)
except ImportError:
raise Exception(
f"Cannot find CPU type '{cpu_type.name}' for '{isa.name}' "
"ISA. Please ensure you have compiled the correct version of "
"gem5."
)
# TODO: This model assumes that we will only create an interrupt
# controller as we require it. Not sure how true this is in all cases.
self.core.createInterruptController()
if self.get_isa() == ISA.X86:
if interrupt_requestor != None:
self.core.interrupts[0].pio = interrupt_requestor
self.core.interrupts[0].int_responder = interrupt_requestor
if interrupt_responce != None:
self.core.interrupts[0].int_requestor = interrupt_responce
@overrides(AbstractCore)
def get_mmu(self) -> BaseMMU:
return self.core.mmu
return to_return_cls(cpu_id=core_id)