msi_caches.py revision 13774
16019SN/A# -*- coding: utf-8 -*-
26019SN/A# Copyright (c) 2017 Jason Power
37110SN/A# All rights reserved.
47110SN/A#
57110SN/A# Redistribution and use in source and binary forms, with or without
67110SN/A# modification, are permitted provided that the following conditions are
77110SN/A# met: redistributions of source code must retain the above copyright
87110SN/A# notice, this list of conditions and the following disclaimer;
97110SN/A# redistributions in binary form must reproduce the above copyright
107110SN/A# notice, this list of conditions and the following disclaimer in the
117110SN/A# documentation and/or other materials provided with the distribution;
127110SN/A# neither the name of the copyright holders nor the names of its
137110SN/A# contributors may be used to endorse or promote products derived from
147110SN/A# this software without specific prior written permission.
156019SN/A#
166019SN/A# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
176019SN/A# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
186019SN/A# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
196019SN/A# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
206019SN/A# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
216019SN/A# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
226019SN/A# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
236019SN/A# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
246019SN/A# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
256019SN/A# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
266019SN/A# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
276019SN/A#
286019SN/A# Authors: Jason Power
296019SN/A
306019SN/A""" This file creates a set of Ruby caches, the Ruby network, and a simple
316019SN/Apoint-to-point topology.
326019SN/ASee Part 3 in the Learning gem5 book: learning.gem5.org/book/part3
336019SN/A
346019SN/AIMPORTANT: If you modify this file, it's likely that the Learning gem5 book
356019SN/A           also needs to be updated. For now, email Jason <jason@lowepower.com>
366019SN/A
376019SN/A"""
386019SN/A
396019SN/Afrom __future__ import print_function
406019SN/Afrom __future__ import absolute_import
416019SN/A
426019SN/Aimport math
436019SN/A
446019SN/Afrom m5.defines import buildEnv
456019SN/Afrom m5.util import fatal, panic
466019SN/A
476019SN/Afrom m5.objects import *
486243SN/A
497422Sgblack@eecs.umich.educlass MyCacheSystem(RubySystem):
507422Sgblack@eecs.umich.edu
516243SN/A    def __init__(self):
526243SN/A        if buildEnv['PROTOCOL'] != 'MSI':
537138Sgblack@eecs.umich.edu            fatal("This system assumes MSI from learning gem5!")
547138Sgblack@eecs.umich.edu
557138Sgblack@eecs.umich.edu        super(MyCacheSystem, self).__init__()
567138Sgblack@eecs.umich.edu
577138Sgblack@eecs.umich.edu    def setup(self, system, cpus, mem_ctrls):
587138Sgblack@eecs.umich.edu        """Set up the Ruby cache subsystem. Note: This can't be done in the
597138Sgblack@eecs.umich.edu           constructor because many of these items require a pointer to the
607138Sgblack@eecs.umich.edu           ruby system (self). This causes infinite recursion in initialize()
617138Sgblack@eecs.umich.edu           if we do this in the __init__.
627138Sgblack@eecs.umich.edu        """
637138Sgblack@eecs.umich.edu        # Ruby's global network.
647138Sgblack@eecs.umich.edu        self.network = MyNetwork(self)
657138Sgblack@eecs.umich.edu
667138Sgblack@eecs.umich.edu        # MSI uses 3 virtual networks. One for requests (lowest priority), one
677138Sgblack@eecs.umich.edu        # for responses (highest priority), and one for "forwards" or
687138Sgblack@eecs.umich.edu        # cache-to-cache requests. See *.sm files for details.
697138Sgblack@eecs.umich.edu        self.number_of_virtual_networks = 3
707138Sgblack@eecs.umich.edu        self.network.number_of_virtual_networks = 3
717138Sgblack@eecs.umich.edu
727138Sgblack@eecs.umich.edu        # There is a single global list of all of the controllers to make it
737138Sgblack@eecs.umich.edu        # easier to connect everything to the global network. This can be
747138Sgblack@eecs.umich.edu        # customized depending on the topology/network requirements.
757138Sgblack@eecs.umich.edu        # Create one controller for each L1 cache (and the cache mem obj.)
767138Sgblack@eecs.umich.edu        # Create a single directory controller (Really the memory cntrl)
777138Sgblack@eecs.umich.edu        self.controllers = \
787138Sgblack@eecs.umich.edu            [L1Cache(system, self, cpu) for cpu in cpus] + \
797138Sgblack@eecs.umich.edu            [DirController(self, system.mem_ranges, mem_ctrls)]
807138Sgblack@eecs.umich.edu
817138Sgblack@eecs.umich.edu        # Create one sequencer per CPU. In many systems this is more
827138Sgblack@eecs.umich.edu        # complicated since you have to create sequencers for DMA controllers
837138Sgblack@eecs.umich.edu        # and other controllers, too.
847138Sgblack@eecs.umich.edu        self.sequencers = [RubySequencer(version = i,
857138Sgblack@eecs.umich.edu                                # I/D cache is combined and grab from ctrl
867138Sgblack@eecs.umich.edu                                icache = self.controllers[i].cacheMemory,
877138Sgblack@eecs.umich.edu                                dcache = self.controllers[i].cacheMemory,
887138Sgblack@eecs.umich.edu                                clk_domain = self.controllers[i].clk_domain,
897138Sgblack@eecs.umich.edu                                ) for i in range(len(cpus))]
907138Sgblack@eecs.umich.edu
917138Sgblack@eecs.umich.edu        # We know that we put the controllers in an order such that the first
927138Sgblack@eecs.umich.edu        # N of them are the L1 caches which need a sequencer pointer
937138Sgblack@eecs.umich.edu        for i,c in enumerate(self.controllers[0:len(self.sequencers)]):
947138Sgblack@eecs.umich.edu            c.sequencer = self.sequencers[i]
957138Sgblack@eecs.umich.edu
967138Sgblack@eecs.umich.edu        self.num_of_sequencers = len(self.sequencers)
977138Sgblack@eecs.umich.edu
987138Sgblack@eecs.umich.edu        # Create the network and connect the controllers.
997138Sgblack@eecs.umich.edu        # NOTE: This is quite different if using Garnet!
1007138Sgblack@eecs.umich.edu        self.network.connectControllers(self.controllers)
1017138Sgblack@eecs.umich.edu        self.network.setup_buffers()
1027138Sgblack@eecs.umich.edu
1037138Sgblack@eecs.umich.edu        # Set up a proxy port for the system_port. Used for load binaries and
1047138Sgblack@eecs.umich.edu        # other functional-only things.
1057138Sgblack@eecs.umich.edu        self.sys_port_proxy = RubyPortProxy()
1067138Sgblack@eecs.umich.edu        system.system_port = self.sys_port_proxy.slave
1077138Sgblack@eecs.umich.edu
1087138Sgblack@eecs.umich.edu        # Connect the cpu's cache, interrupt, and TLB ports to Ruby
1097138Sgblack@eecs.umich.edu        for i,cpu in enumerate(cpus):
1107138Sgblack@eecs.umich.edu            cpu.icache_port = self.sequencers[i].slave
1117138Sgblack@eecs.umich.edu            cpu.dcache_port = self.sequencers[i].slave
1127138Sgblack@eecs.umich.edu            isa = buildEnv['TARGET_ISA']
1137138Sgblack@eecs.umich.edu            if isa == 'x86':
1147138Sgblack@eecs.umich.edu                cpu.interrupts[0].pio = self.sequencers[i].master
1157138Sgblack@eecs.umich.edu                cpu.interrupts[0].int_master = self.sequencers[i].slave
1167138Sgblack@eecs.umich.edu                cpu.interrupts[0].int_slave = self.sequencers[i].master
1177138Sgblack@eecs.umich.edu            if isa == 'x86' or isa == 'arm':
1187138Sgblack@eecs.umich.edu                cpu.itb.walker.port = self.sequencers[i].slave
1197138Sgblack@eecs.umich.edu                cpu.dtb.walker.port = self.sequencers[i].slave
1207138Sgblack@eecs.umich.edu
1217138Sgblack@eecs.umich.edu
1227138Sgblack@eecs.umich.educlass L1Cache(L1Cache_Controller):
1237138Sgblack@eecs.umich.edu
1247138Sgblack@eecs.umich.edu    _version = 0
1257138Sgblack@eecs.umich.edu    @classmethod
1267138Sgblack@eecs.umich.edu    def versionCount(cls):
1277138Sgblack@eecs.umich.edu        cls._version += 1 # Use count for this particular type
1287138Sgblack@eecs.umich.edu        return cls._version - 1
1296019SN/A
1306019SN/A    def __init__(self, system, ruby_system, cpu):
1316019SN/A        """CPUs are needed to grab the clock domain and system is needed for
1326019SN/A           the cache block size.
1336271SN/A        """
1346271SN/A        super(L1Cache, self).__init__()
1356019SN/A
1366019SN/A        self.version = self.versionCount()
1376019SN/A        # This is the cache memory object that stores the cache data and tags
1386243SN/A        self.cacheMemory = RubyCache(size = '16kB',
1396019SN/A                               assoc = 8,
1406243SN/A                               start_index_bit = self.getBlockSizeBits(system))
1416019SN/A        self.clk_domain = cpu.clk_domain
1426019SN/A        self.send_evictions = self.sendEvicts(cpu)
1436019SN/A        self.ruby_system = ruby_system
1446019SN/A        self.connectQueues(ruby_system)
1457597Sminkyu.jeong@arm.com
1467597Sminkyu.jeong@arm.com    def getBlockSizeBits(self, system):
1476019SN/A        bits = int(math.log(system.cache_line_size, 2))
1486019SN/A        if 2**bits != system.cache_line_size.value:
1497646Sgene.wu@arm.com            panic("Cache line size not a power of 2!")
1507646Sgene.wu@arm.com        return bits
1517408Sgblack@eecs.umich.edu
1527408Sgblack@eecs.umich.edu    def sendEvicts(self, cpu):
1537408Sgblack@eecs.umich.edu        """True if the CPU model or ISA requires sending evictions from caches
1546019SN/A           to the CPU. Two scenarios warrant forwarding evictions to the CPU:
1556019SN/A           1. The O3 model must keep the LSQ coherent with the caches
1566019SN/A           2. The x86 mwait instruction is built on top of coherence
1576019SN/A           3. The local exclusive monitor in ARM systems
1586265SN/A        """
1596265SN/A        if type(cpu) is DerivO3CPU or \
1606265SN/A           buildEnv['TARGET_ISA'] in ('x86', 'arm'):
1616265SN/A            return True
1626265SN/A        return False
1636265SN/A
1646265SN/A    def connectQueues(self, ruby_system):
1656265SN/A        """Connect all of the queues for this controller.
1666265SN/A        """
1676265SN/A        # mandatoryQueue is a special variable. It is used by the sequencer to
1686265SN/A        # send RubyRequests from the CPU (or other processor). It isn't
1696265SN/A        # explicitly connected to anything.
1706265SN/A        self.mandatoryQueue = MessageBuffer()
1716265SN/A
1726270SN/A        # All message buffers must be created and connected to the general
1736270SN/A        # Ruby network. In this case, "slave/master" don't mean the same thing
1746270SN/A        # as normal gem5 ports. If a MessageBuffer is a "to" buffer (i.e., out)
1756270SN/A        # then you use the "master", otherwise, the slave.
1766270SN/A        self.requestToDir = MessageBuffer(ordered = True)
1776270SN/A        self.requestToDir.master = ruby_system.network.slave
178        self.responseToDirOrSibling = MessageBuffer(ordered = True)
179        self.responseToDirOrSibling.master = ruby_system.network.slave
180        self.forwardFromDir = MessageBuffer(ordered = True)
181        self.forwardFromDir.slave = ruby_system.network.master
182        self.responseFromDirOrSibling = MessageBuffer(ordered = True)
183        self.responseFromDirOrSibling.slave = ruby_system.network.master
184
185class DirController(Directory_Controller):
186
187    _version = 0
188    @classmethod
189    def versionCount(cls):
190        cls._version += 1 # Use count for this particular type
191        return cls._version - 1
192
193    def __init__(self, ruby_system, ranges, mem_ctrls):
194        """ranges are the memory ranges assigned to this controller.
195        """
196        if len(mem_ctrls) > 1:
197            panic("This cache system can only be connected to one mem ctrl")
198        super(DirController, self).__init__()
199        self.version = self.versionCount()
200        self.addr_ranges = ranges
201        self.ruby_system = ruby_system
202        self.directory = RubyDirectoryMemory()
203        # Connect this directory to the memory side.
204        self.memory = mem_ctrls[0].port
205        self.connectQueues(ruby_system)
206
207    def connectQueues(self, ruby_system):
208        self.requestFromCache = MessageBuffer(ordered = True)
209        self.requestFromCache.slave = ruby_system.network.master
210        self.responseFromCache = MessageBuffer(ordered = True)
211        self.responseFromCache.slave = ruby_system.network.master
212
213        self.responseToCache = MessageBuffer(ordered = True)
214        self.responseToCache.master = ruby_system.network.slave
215        self.forwardToCache = MessageBuffer(ordered = True)
216        self.forwardToCache.master = ruby_system.network.slave
217
218        # This is another special message buffer. It is used to send replies
219        # from memory back to the controller. Any messages received on the
220        # memory port (see self.memory above) will be directed to this
221        # message buffer.
222        self.responseFromMemory = MessageBuffer()
223
224class MyNetwork(SimpleNetwork):
225    """A simple point-to-point network. This doesn't not use garnet.
226    """
227
228    def __init__(self, ruby_system):
229        super(MyNetwork, self).__init__()
230        self.netifs = []
231        self.ruby_system = ruby_system
232
233    def connectControllers(self, controllers):
234        """Connect all of the controllers to routers and connec the routers
235           together in a point-to-point network.
236        """
237        # Create one router/switch per controller in the system
238        self.routers = [Switch(router_id = i) for i in range(len(controllers))]
239
240        # Make a link from each controller to the router. The link goes
241        # externally to the network.
242        self.ext_links = [SimpleExtLink(link_id=i, ext_node=c,
243                                        int_node=self.routers[i])
244                          for i, c in enumerate(controllers)]
245
246        # Make an "internal" link (internal to the network) between every pair
247        # of routers.
248        link_count = 0
249        self.int_links = []
250        for ri in self.routers:
251            for rj in self.routers:
252                if ri == rj: continue # Don't connect a router to itself!
253                link_count += 1
254                self.int_links.append(SimpleIntLink(link_id = link_count,
255                                                    src_node = ri,
256                                                    dst_node = rj))
257