| # 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 ...utils.requires import requires |
| from ...utils.override import overrides |
| from typing import List, Sequence, Tuple |
| 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** |
| * stage2 walker ports are ignored. |
| """ |
| |
| __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: |
| |
| # The platform and the clk has to be set before calling the super class |
| self._platform = platform |
| self._clk_freq = clk_freq |
| |
| 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 up ARM release here. We use the ARM default release, which |
| # corresponds to an ARMv8 system. |
| self.release = release |
| |
| # 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 extension 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. We set the clock to the same frequency |
| # as the user specified in the config script. |
| self.voltage_domain = VoltageDomain(voltage="1.0V") |
| self.clk_domain = SrcClockDomain( |
| clock=self._clk_freq, 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 |
| |
| # We now need to setup the dma_ports. |
| self._dma_ports = None |
| |
| # An else part is not required as for CHI protocol, the dma_ports has |
| # to be set to [] |
| |
| # RealView sets up most of the on-chip and off-chip devices and GIC |
| # for the ARM board. These devices' information is also used to |
| # generate the dtb file. We then connect the I/O devices to the |
| # I/O bus. |
| self._setup_io_devices() |
| |
| # Once the realview is setup, we can continue setting up the memory |
| # ranges. ArmBoard's memory can only be setup once realview is |
| # initialized. |
| memory = self.get_memory() |
| mem_size = memory.get_size() |
| |
| # The following code is taken from configs/example/arm/devices.py. It |
| # sets up all the memory ranges for the board. |
| self.mem_ranges = [] |
| success = False |
| for mem_range in self.realview._mem_regions: |
| size_in_range = min(mem_size, mem_range.size()) |
| self.mem_ranges.append( |
| AddrRange(start=mem_range.start, size=size_in_range) |
| ) |
| |
| mem_size -= size_in_range |
| if mem_size == 0: |
| success = True |
| break |
| |
| if success: |
| memory.set_memory_range(self.mem_ranges) |
| else: |
| raise ValueError("Memory size too big for platform capabilities") |
| |
| # the image is initially set to None as a sanity check. This is |
| # overwritten in the method _setup_pci_devices. |
| self._image = None |
| |
| # Calling _setup_pci_devices. DMA ports has to be setup beforehand. PCI |
| # devices has to be setup before adding disk to board as the dma_ports |
| # has to be correctly setup before incorporating ruby caches. The issue |
| # is that the dma_controllers can only be created correctly when we |
| # have the dma_ports for the PCI device. The current order of function |
| # calls is: |
| # ArmBoard AbstractBoard KernelDiskWorkload |
| # _setup_pci_devices() -> incorporate_cache() -> _add_disk_to_board() |
| self._setup_pci_devices() |
| |
| def _setup_io_devices(self) -> None: |
| """ |
| This method first sets up the platform. ARM uses `realview` platform. |
| Most of the on-chip and off-chip devices are setup by the realview |
| platform. Once realview is setup, we connect the I/O devices to the |
| I/O bus. |
| """ |
| |
| # 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 = self._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 |
| |
| # IO devices has to setup before incorporating the caches in the case |
| # of ruby caches. Otherwise the DMA controllers are incorrectly |
| # created. The IO device has to be attached first. This is done in the |
| # realview class. |
| if self.get_cache_hierarchy().is_ruby(): |
| |
| # All the on-chip devices are attached in this method. |
| self.realview.attachOnChipIO( |
| self.iobus, |
| dma_ports=self.get_dma_ports(), |
| mem_ports=self.get_memory().get_mem_ports(), |
| ) |
| self.realview.attachIO(self.iobus, dma_ports=self.get_dma_ports()) |
| |
| else: |
| # 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. |
| |
| # 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() |
| ) |
| |
| if isinstance(self.cache_hierarchy, NoCache) is True: |
| # This corresponds to a machine without caches. We have a DMA |
| # bridge 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.cache_hierarchy.get_cpu_side_port() |
| ) |
| self.dmabridge.cpu_side_port = self.iobus.mem_side_ports |
| |
| # The classic caches are setup in the _setup_io_cache() method |
| # defined under the cachehierarchy class. Verified it with both |
| # PrivateL1PrivateL2CacheHierarchy and PrivateL1CacheHierarchy |
| # classes. |
| self.realview.attachOnChipIO( |
| self.cache_hierarchy.membus, self.iobridge |
| ) |
| self.realview.attachIO(self.iobus) |
| |
| @overrides(AbstractBoard) |
| def get_mem_ports(self) -> Sequence[Tuple[AddrRange, Port]]: |
| all_ports = [ |
| (self.realview.bootmem.range, self.realview.bootmem.port), |
| ] + self.get_memory().get_mem_ports() |
| |
| return all_ports |
| |
| @overrides(AbstractBoard) |
| def has_io_bus(self) -> bool: |
| return True |
| |
| @overrides(AbstractBoard) |
| def get_io_bus(self) -> IOXBar: |
| return self.iobus |
| |
| @overrides(AbstractBoard) |
| def has_coherent_io(self) -> bool: |
| # The setup of the caches gets a little tricky here. We need to |
| # override the default cache_hierarchy.iobridge due to different delay |
| # values (see method _setup_io_devices()). One way to do it would be to |
| # prevent creating cache_hierarchy.iobridge altogether. We trick |
| # NoCache() to assume that this board has no coherent_io and we we |
| # simply setup our own iobridge in the _setup_io_devices() method. |
| if isinstance(self.cache_hierarchy, NoCache): |
| return False |
| # In all other cases, we use the default values setup in the |
| # respective cache hierarchy class. |
| 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 |
| |
| @overrides(AbstractBoard) |
| def get_dma_ports(self) -> List[Port]: |
| # The DMA ports differ depending upon the cache hierarchy. The method |
| # self.set_dma_ports takes care of that. In the case of ruby caches, |
| # this method should initially return an empty list. |
| if self.cache_hierarchy.is_ruby(): |
| if self._dma_ports is None: |
| self._dma_ports = [] |
| |
| # _dma_ports should always be empty for classic caches. |
| return self._dma_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" |
| |
| def _setup_pci_devices(self): |
| |
| # We define the image. The _image has to be None initially. |
| assert self._image is None |
| |
| self._image = CowDiskImage( |
| child=RawDiskImage(read_only=True), read_only=False |
| ) |
| |
| self.pci_devices = [PciVirtIO(vio=VirtIOBlock(image=self._image))] |
| |
| for device in self.pci_devices: |
| self.realview.attachPciDevice( |
| device, self.iobus, dma_ports=self.get_dma_ports() |
| ) |
| |
| @overrides(KernelDiskWorkload) |
| def _add_disk_to_board(self, disk_image: AbstractResource): |
| |
| assert self._image is not None |
| |
| # 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. |
| self._image.child.image_file = disk_image.get_local_path() |
| |
| # 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 |
| ) |
| |
| @overrides(AbstractBoard) |
| def _setup_memory_ranges(self) -> None: |
| """ |
| The ArmBoard's memory can only be setup after realview is setup. We set |
| this up in the `_setup_board` function. |
| """ |
| pass |
| |
| @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(), |
| ] |