| # 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 m5.objects import ( |
| Port, |
| IOXBar, |
| Bridge, |
| BadAddr, |
| Terminal, |
| PciVirtIO, |
| VncServer, |
| AddrRange, |
| ArmSystem, |
| ArmRelease, |
| ArmFsLinux, |
| VirtIOBlock, |
| CowDiskImage, |
| RawDiskImage, |
| VoltageDomain, |
| SrcClockDomain, |
| ArmDefaultRelease, |
| VExpress_GEM5_Base, |
| VExpress_GEM5_Foundation, |
| ) |
| |
| import os |
| import m5 |
| from abc import ABCMeta |
| from ...isas import ISA |
| from typing import List |
| from m5.util import fatal |
| from ...utils.requires import requires |
| from ...utils.override import overrides |
| from .abstract_board import AbstractBoard |
| from ...resources.resource import AbstractResource |
| from .kernel_disk_workload import KernelDiskWorkload |
| from ..cachehierarchies.classic.no_cache import NoCache |
| from ..processors.abstract_processor import AbstractProcessor |
| from ..memory.abstract_memory_system import AbstractMemorySystem |
| from ..cachehierarchies.abstract_cache_hierarchy import AbstractCacheHierarchy |
| |
| class ArmBoard(ArmSystem, AbstractBoard, KernelDiskWorkload): |
| """ |
| A board capable of full system simulation for ARM instructions. It is based |
| ARMv8. |
| |
| The board is based on Arm Motherboard Express uATX (V2M-P1), Arm |
| CoreTile Express A15x2 (V2P-CA15) and on Armv8-A FVP Foundation platform |
| v11.8, depending on the simulated platform. These boards are parts of ARM's |
| Versatile(TM) Express family of boards. |
| |
| **Limitations** |
| * The board currently does not support ruby caches. |
| * stage2 walker ports are ignored. |
| * This version does not support SECURITY extension. |
| """ |
| __metaclass__ = ABCMeta |
| |
| def __init__( |
| self, |
| clk_freq: str, |
| processor: AbstractProcessor, |
| memory: AbstractMemorySystem, |
| cache_hierarchy: AbstractCacheHierarchy, |
| platform: VExpress_GEM5_Base = VExpress_GEM5_Foundation(), |
| release: ArmRelease = ArmDefaultRelease() |
| ) -> None: |
| super().__init__() |
| AbstractBoard.__init__( |
| self, |
| clk_freq = clk_freq, |
| processor = processor, |
| memory = memory, |
| cache_hierarchy = cache_hierarchy, |
| ) |
| |
| # This board requires ARM ISA to work. |
| |
| requires(isa_required = ISA.ARM) |
| |
| # Setting the voltage domain here. |
| |
| self.voltage_domain = self.clk_domain.voltage_domain |
| |
| # Setting up ARM release here. We use the ARM default release, which |
| # corresponds to an ARMv8 system. |
| |
| self.release = release |
| |
| # RealView sets up most of the on-chip and off-chip devices and GIC |
| # for the ARM board. These devices' iformation is also used to |
| # generate the dtb file. |
| |
| self._setup_realview(platform) |
| |
| # ArmBoard's memory can only be setup once realview is initialized. |
| |
| self._setup_arm_memory_ranges() |
| |
| # Setting multi_proc of ArmSystem by counting the number of processors. |
| |
| if processor.get_num_cores() != 1: |
| self.multi_proc = False |
| else: |
| self.multi_proc = True |
| |
| @overrides(AbstractBoard) |
| def _setup_board(self) -> None: |
| |
| # This board is expected to run full-system simulation. |
| # Loading ArmFsLinux() from `src/arch/arm/ArmFsWorkload.py` |
| |
| self.workload = ArmFsLinux() |
| |
| # We are fixing the following variable for the ArmSystem to work. The |
| # security extension is checked while generating the dtb file in |
| # realview. This board does not have security extention enabled. |
| |
| self._have_psci = False |
| |
| # highest_el_is_64 is set to True. True if the register width of the |
| # highest implemented exception level is 64 bits. |
| |
| self.highest_el_is_64 = True |
| |
| # Setting up the voltage and the clock domain here for the ARM board. |
| # The ArmSystem/RealView expects voltage_domain to be a parameter. |
| # The voltage and the clock frequency are taken from the devices.py |
| # file from configs/example/arm |
| |
| self.voltage_domain = VoltageDomain(voltage="1.0V") |
| self.clk_domain = SrcClockDomain( |
| clock="1GHz", voltage_domain=self.voltage_domain |
| ) |
| |
| # The ARM board supports both Terminal and VncServer. |
| |
| self.terminal = Terminal() |
| self.vncserver = VncServer() |
| |
| # Incoherent I/O Bus |
| |
| self.iobus = IOXBar() |
| self.iobus.badaddr_responder = BadAddr() |
| self.iobus.default = self.iobus.badaddr_responder.pio |
| |
| def _setup_io_devices(self) -> None: |
| """ |
| This method connects the I/O devices to the I/O bus. |
| """ |
| |
| # We setup the iobridge for the ARM Board. The default |
| # cache_hierarchy's NoCache class has an iobridge has a latency of |
| # 10. We are using an iobridge with latency = 50ns, taken from the |
| # configs/example/arm/devices.py |
| |
| self.iobridge = Bridge(delay="50ns") |
| self.iobridge.mem_side_port = self.iobus.cpu_side_ports |
| self.iobridge.cpu_side_port = ( |
| self.cache_hierarchy.get_mem_side_port() |
| ) |
| |
| # We either have iocache or dmabridge depending upon the |
| # cache_hierarchy. If we have "NoCache", then we use the dmabridge. |
| # Otherwise, we use the iocache on the board. |
| |
| if isinstance(self.cache_hierarchy, NoCache) is False: |
| |
| # The ArmBoard does not support ruby caches. |
| |
| if self.get_cache_hierarchy().is_ruby(): |
| fatal("Ruby caches are not supported by the ArmBoard.") |
| |
| # The classic caches are setup in the _setup_io_cache() method, |
| # defined under the cachehierarchy class. Verified it with both |
| # PrivateL1PrivateL2CacheHierarchy and PrivateL1CacheHierarchy |
| # classes. |
| |
| else: |
| |
| # This corresponds to a machine without caches. We have a DMA |
| # beidge in this case. Parameters of this bridge are also taken |
| # from the common/example/arm/devices.py file. |
| |
| self.dmabridge = Bridge( |
| delay="50ns", ranges=self.mem_ranges |
| ) |
| |
| self.dmabridge.mem_side_port = self.get_dma_ports()[0] |
| self.dmabridge.cpu_side_port = self.get_dma_ports()[1] |
| |
| self.realview.attachOnChipIO( |
| self.cache_hierarchy.membus, self.iobridge |
| ) |
| self.realview.attachIO(self.iobus) |
| |
| def _setup_realview(self, platform) -> None: |
| """ |
| Notes: |
| The ARM Board has realview platform. Most of the on-chip and |
| off-chip devices are setup by the RealView platform. Currently, there |
| are 5 different types of realview platforms supported by the ArmBoard. |
| |
| :param platform: the user can specify the platform while instantiating |
| an ArmBoard object. |
| """ |
| |
| # Currently, the ArmBoard supports VExpress_GEM5_V1, |
| # VExpress_GEM5_V1_HDLcd and VExpress_GEM5_Foundation. |
| # VExpress_GEM5_V2 and VExpress_GEM5_V2_HDLcd are not supported by the |
| # ArmBoard. |
| |
| self.realview = platform |
| |
| # We need to setup the global interrupt controller (GIC) addr for the |
| # realview system. |
| |
| if hasattr(self.realview.gic, "cpu_addr"): |
| self.gic_cpu_addr = self.realview.gic.cpu_addr |
| |
| def _setup_io_cache(self): |
| pass |
| |
| @overrides(AbstractBoard) |
| def has_io_bus(self) -> bool: |
| return True |
| |
| @overrides(AbstractBoard) |
| def get_io_bus(self) -> IOXBar: |
| return [self.iobus.cpu_side_ports, self.iobus.mem_side_ports] |
| |
| @overrides(AbstractBoard) |
| def has_coherent_io(self) -> bool: |
| return True |
| |
| @overrides(AbstractBoard) |
| def get_mem_side_coherent_io_port(self) -> Port: |
| return self.iobus.mem_side_ports |
| |
| @overrides(AbstractBoard) |
| def has_dma_ports(self) -> bool: |
| return True |
| |
| def _setup_coherent_io_bridge(self, board: AbstractBoard) -> None: |
| pass |
| |
| @overrides(AbstractBoard) |
| def get_dma_ports(self) -> List[Port]: |
| return [ |
| self.cache_hierarchy.get_cpu_side_port(), |
| self.iobus.mem_side_ports |
| ] |
| |
| @overrides(AbstractBoard) |
| def connect_system_port(self, port: Port) -> None: |
| self.system_port = port |
| |
| @overrides(KernelDiskWorkload) |
| def get_disk_device(self): |
| return "/dev/vda" |
| |
| @overrides(KernelDiskWorkload) |
| def _add_disk_to_board(self, disk_image: AbstractResource): |
| |
| # We define the image. |
| |
| image = CowDiskImage( |
| child=RawDiskImage(read_only=True), read_only=False |
| ) |
| |
| self.pci_devices = [PciVirtIO(vio=VirtIOBlock(image=image))] |
| self.realview.attachPciDevice( |
| self.pci_devices[0], self.iobus |
| ) |
| |
| # Now that the disk and workload are set, we can generate the device |
| # tree file. We will generate the dtb file everytime the board is |
| # boot-up. |
| |
| image.child.image_file = disk_image.get_local_path() |
| |
| # _setup_io_devices needs to be implemented. |
| |
| self._setup_io_devices() |
| |
| # Specifying the dtb file location to the workload. |
| |
| self.workload.dtb_filename = os.path.join( |
| m5.options.outdir, "device.dtb" |
| ) |
| |
| # Calling generateDtb from class ArmSystem to add memory information to |
| # the dtb file. |
| |
| self.generateDtb(self.workload.dtb_filename) |
| |
| # Finally we need to setup the bootloader for the ArmBoard. An ARM |
| # system requires three inputs to simulate a full system: a disk image, |
| # the kernel file and the bootloader file(s). |
| |
| self.realview.setupBootLoader( |
| self, self.workload.dtb_filename, self._bootloader) |
| |
| def _get_memory_ranges(self, mem_size) -> list: |
| """ |
| This method is taken from configs/example/arm/devices.py. It sets up |
| all the memory ranges for the board. |
| """ |
| mem_ranges = [] |
| |
| for mem_range in self.realview._mem_regions: |
| size_in_range = min(mem_size, mem_range.size()) |
| mem_ranges.append( |
| AddrRange(start = mem_range.start, size = size_in_range) |
| ) |
| |
| mem_size -= size_in_range |
| if mem_size == 0: |
| return mem_ranges |
| |
| raise ValueError("Memory size too big for platform capabilities") |
| |
| @overrides(AbstractBoard) |
| def _setup_memory_ranges(self) -> None: |
| """ |
| The ArmBoard's memory can only be setup after realview is setup. Once |
| realview is initialized, we call _setup_arm_memory_ranges() to |
| correctly setup the memory ranges. |
| """ |
| pass |
| |
| def _setup_arm_memory_ranges(self) -> None: |
| |
| # We setup the memory here. The memory size is specified in the run |
| # script that the user uses. |
| |
| memory = self.get_memory() |
| mem_size = memory.get_size() |
| |
| self.mem_ranges = self._get_memory_ranges(mem_size) |
| memory.set_memory_range(self.mem_ranges) |
| |
| @overrides(KernelDiskWorkload) |
| def get_default_kernel_args(self) -> List[str]: |
| |
| # The default kernel string is taken from the devices.py file. |
| |
| return [ |
| "console=ttyAMA0", |
| "lpj=19988480", |
| "norandmaps", |
| "root={root_value}", |
| "rw", |
| "mem=%s" % self.get_memory().get_size(), |
| ] |