| # -*- coding: utf-8 -*- |
| # Copyright (c) 2015 Jason Power |
| # 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. |
| # |
| # Authors: Jason Power |
| |
| """ This file creates a set of Ruby caches, the Ruby network, and a simple |
| point-to-point topology. |
| See Part 3 in the Learning gem5 book: learning.gem5.org/book/part3 |
| You can change simple_ruby to import from this file instead of from msi_caches |
| to use the MI_example protocol instead of MSI. |
| |
| IMPORTANT: If you modify this file, it's likely that the Learning gem5 book |
| also needs to be updated. For now, email Jason <jason@lowepower.com> |
| |
| """ |
| |
| from __future__ import print_function |
| from __future__ import absolute_import |
| |
| import math |
| |
| from m5.defines import buildEnv |
| from m5.util import fatal, panic |
| |
| from m5.objects import * |
| |
| class MIExampleSystem(RubySystem): |
| |
| def __init__(self): |
| if buildEnv['PROTOCOL'] != 'MI_example': |
| fatal("This system assumes MI_example!") |
| |
| super(MIExampleSystem, self).__init__() |
| |
| def setup(self, system, cpus, mem_ctrls, dma_ports, iobus): |
| """Set up the Ruby cache subsystem. Note: This can't be done in the |
| constructor because many of these items require a pointer to the |
| ruby system (self). This causes infinite recursion in initialize() |
| if we do this in the __init__. |
| """ |
| # Ruby's global network. |
| self.network = MyNetwork(self) |
| |
| # MI example uses 5 virtual networks |
| self.number_of_virtual_networks = 5 |
| self.network.number_of_virtual_networks = 5 |
| |
| # There is a single global list of all of the controllers to make it |
| # easier to connect everything to the global network. This can be |
| # customized depending on the topology/network requirements. |
| # Create one controller for each L1 cache (and the cache mem obj.) |
| # Create a single directory controller (Really the memory cntrl) |
| self.controllers = \ |
| [L1Cache(system, self, cpu) for cpu in cpus] + \ |
| [DirController(self, system.mem_ranges, mem_ctrls)] + \ |
| [DMAController(self) for i in range(len(dma_ports))] |
| |
| # Create one sequencer per CPU. In many systems this is more |
| # complicated since you have to create sequencers for DMA controllers |
| # and other controllers, too. |
| self.sequencers = [RubySequencer(version = i, |
| # Grab dcache from ctrl |
| dcache = self.controllers[i].cacheMemory, |
| clk_domain = self.controllers[i].clk_domain, |
| pio_request_port = iobus.cpu_side_ports, |
| mem_request_port = iobus.cpu_side_ports, |
| pio_response_port = iobus.mem_side_ports |
| ) for i in range(len(cpus))] + \ |
| [DMASequencer(version = i, |
| in_ports = port) |
| for i,port in enumerate(dma_ports) |
| ] |
| |
| for i,c in enumerate(self.controllers[0:len(cpus)]): |
| c.sequencer = self.sequencers[i] |
| |
| for i,d in enumerate(self.controllers[-len(dma_ports):]): |
| i += len(cpus) |
| d.dma_sequencer = self.sequencers[i] |
| |
| self.num_of_sequencers = len(self.sequencers) |
| |
| # Create the network and connect the controllers. |
| # NOTE: This is quite different if using Garnet! |
| self.network.connectControllers(self.controllers) |
| self.network.setup_buffers() |
| |
| # Set up a proxy port for the system_port. Used for load binaries and |
| # other functional-only things. |
| self.sys_port_proxy = RubyPortProxy() |
| system.system_port = self.sys_port_proxy.in_ports |
| self.sys_port_proxy.pio_request_port = iobus.cpu_side_ports |
| |
| # Connect the cpu's cache, interrupt, and TLB ports to Ruby |
| for i,cpu in enumerate(cpus): |
| cpu.icache_port = self.sequencers[i].in_ports |
| cpu.dcache_port = self.sequencers[i].in_ports |
| isa = buildEnv['TARGET_ISA'] |
| if isa == 'x86': |
| cpu.interrupts[0].pio = self.sequencers[i].interrupt_out_port |
| cpu.interrupts[0].int_requestor = self.sequencers[i].in_ports |
| cpu.interrupts[0].int_responder = \ |
| self.sequencers[i].interrupt_out_port |
| if isa == 'x86' or isa == 'arm': |
| cpu.mmu.connectWalkerPorts( |
| self.sequencers[i].in_ports, self.sequencers[i].in_ports) |
| |
| class L1Cache(L1Cache_Controller): |
| |
| _version = 0 |
| @classmethod |
| def versionCount(cls): |
| cls._version += 1 # Use count for this particular type |
| return cls._version - 1 |
| |
| def __init__(self, system, ruby_system, cpu): |
| """CPUs are needed to grab the clock domain and system is needed for |
| the cache block size. |
| """ |
| super(L1Cache, self).__init__() |
| |
| self.version = self.versionCount() |
| # This is the cache memory object that stores the cache data and tags |
| self.cacheMemory = RubyCache(size = '16kB', |
| assoc = 8, |
| start_index_bit = self.getBlockSizeBits(system)) |
| self.clk_domain = cpu.clk_domain |
| self.send_evictions = self.sendEvicts(cpu) |
| self.ruby_system = ruby_system |
| self.connectQueues(ruby_system) |
| |
| def getBlockSizeBits(self, system): |
| bits = int(math.log(system.cache_line_size, 2)) |
| if 2**bits != system.cache_line_size.value: |
| panic("Cache line size not a power of 2!") |
| return bits |
| |
| def sendEvicts(self, cpu): |
| """True if the CPU model or ISA requires sending evictions from caches |
| to the CPU. Two scenarios warrant forwarding evictions to the CPU: |
| 1. The O3 model must keep the LSQ coherent with the caches |
| 2. The x86 mwait instruction is built on top of coherence |
| 3. The local exclusive monitor in ARM systems |
| """ |
| if type(cpu) is DerivO3CPU or \ |
| buildEnv['TARGET_ISA'] in ('x86', 'arm'): |
| return True |
| return False |
| |
| def connectQueues(self, ruby_system): |
| """Connect all of the queues for this controller. |
| """ |
| self.mandatoryQueue = MessageBuffer() |
| self.requestFromCache = MessageBuffer(ordered = True) |
| self.requestFromCache.out_port = ruby_system.network.in_port |
| self.responseFromCache = MessageBuffer(ordered = True) |
| self.responseFromCache.out_port = ruby_system.network.in_port |
| self.forwardToCache = MessageBuffer(ordered = True) |
| self.forwardToCache.in_port = ruby_system.network.out_port |
| self.responseToCache = MessageBuffer(ordered = True) |
| self.responseToCache.in_port = ruby_system.network.out_port |
| |
| class DirController(Directory_Controller): |
| |
| _version = 0 |
| @classmethod |
| def versionCount(cls): |
| cls._version += 1 # Use count for this particular type |
| return cls._version - 1 |
| |
| def __init__(self, ruby_system, ranges, mem_ctrls): |
| """ranges are the memory ranges assigned to this controller. |
| """ |
| if len(mem_ctrls) > 1: |
| panic("This cache system can only be connected to one mem ctrl") |
| super(DirController, self).__init__() |
| self.version = self.versionCount() |
| self.addr_ranges = ranges |
| self.ruby_system = ruby_system |
| self.directory = RubyDirectoryMemory() |
| # Connect this directory to the memory side. |
| self.memory_out_port = mem_ctrls[0].port |
| self.connectQueues(ruby_system) |
| |
| def connectQueues(self, ruby_system): |
| self.requestToDir = MessageBuffer(ordered = True) |
| self.requestToDir.in_port = ruby_system.network.out_port |
| self.dmaRequestToDir = MessageBuffer(ordered = True) |
| self.dmaRequestToDir.in_port = ruby_system.network.out_port |
| |
| self.responseFromDir = MessageBuffer() |
| self.responseFromDir.out_port = ruby_system.network.in_port |
| self.dmaResponseFromDir = MessageBuffer(ordered = True) |
| self.dmaResponseFromDir.out_port = ruby_system.network.in_port |
| self.forwardFromDir = MessageBuffer() |
| self.forwardFromDir.out_port = ruby_system.network.in_port |
| self.requestToMemory = MessageBuffer() |
| self.responseFromMemory = MessageBuffer() |
| |
| class DMAController(DMA_Controller): |
| |
| _version = 0 |
| @classmethod |
| def versionCount(cls): |
| cls._version += 1 # Use count for this particular type |
| return cls._version - 1 |
| |
| def __init__(self, ruby_system): |
| super(DMAController, self).__init__() |
| self.version = self.versionCount() |
| self.ruby_system = ruby_system |
| self.connectQueues(ruby_system) |
| |
| def connectQueues(self, ruby_system): |
| self.mandatoryQueue = MessageBuffer() |
| self.requestToDir = MessageBuffer() |
| self.requestToDir.out_port = ruby_system.network.in_port |
| self.responseFromDir = MessageBuffer(ordered = True) |
| self.responseFromDir.in_port = ruby_system.network.out_port |
| |
| |
| class MyNetwork(SimpleNetwork): |
| """A simple point-to-point network. This doesn't not use garnet. |
| """ |
| |
| def __init__(self, ruby_system): |
| super(MyNetwork, self).__init__() |
| self.netifs = [] |
| self.ruby_system = ruby_system |
| |
| def connectControllers(self, controllers): |
| """Connect all of the controllers to routers and connec the routers |
| together in a point-to-point network. |
| """ |
| # Create one router/switch per controller in the system |
| self.routers = [Switch(router_id = i) for i in range(len(controllers))] |
| |
| # Make a link from each controller to the router. The link goes |
| # externally to the network. |
| self.ext_links = [SimpleExtLink(link_id=i, ext_node=c, |
| int_node=self.routers[i]) |
| for i, c in enumerate(controllers)] |
| |
| # Make an "internal" link (internal to the network) between every pair |
| # of routers. |
| link_count = 0 |
| self.int_links = [] |
| for ri in self.routers: |
| for rj in self.routers: |
| if ri == rj: continue # Don't connect a router to itself! |
| link_count += 1 |
| self.int_links.append(SimpleIntLink(link_id = link_count, |
| src_node = ri, |
| dst_node = rj)) |