mem: Make MemCtrl a ClockedObject
[gem5.git] / configs / example / memtest.py
index 5bb874e8543043e7ff0c772389750f687fea668f..ef536c66cfb7abd04664af382c50d5c911c2485f 100644 (file)
@@ -1,3 +1,15 @@
+# Copyright (c) 2015, 2018 ARM Limited
+# All rights reserved.
+#
+# The license below extends only to copyright in the software and shall
+# not be construed as granting a license to any other intellectual
+# property including but not limited to intellectual property relating
+# to a hardware implementation of the functionality of the software
+# licensed hereunder.  You may use the software subject to the license
+# terms below provided that you ensure that this notice is replicated
+# unmodified and in its entirety in all distributions of the software,
+# modified or unmodified, in source code or in binary form.
+#
 # Copyright (c) 2006-2007 The Regents of The University of Michigan
 # All rights reserved.
 #
 # 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: Ron Dreslinski
+
+from __future__ import print_function
+from __future__ import absolute_import
+
+import optparse
+import random
+import sys
 
 import m5
 from m5.objects import *
-import os, optparse, sys
-m5.AddToPath('../common')
+
+# This example script stress tests the memory system by creating false
+# sharing in a tree topology. At the bottom of the tree is a shared
+# memory, and then at each level a number of testers are attached,
+# along with a number of caches that them selves fan out to subtrees
+# of testers and caches. Thus, it is possible to create a system with
+# arbitrarily deep cache hierarchies, sharing or no sharing of caches,
+# and testers not only at the L1s, but also at the L2s, L3s etc.
 
 parser = optparse.OptionParser()
 
@@ -43,146 +66,267 @@ parser.add_option("-m", "--maxtick", type="int", default=m5.MaxTick,
                   metavar="T",
                   help="Stop after T ticks")
 
-#
-# The "tree" specification is a colon-separated list of one or more
-# integers.  The first integer is the number of caches/testers
-# connected directly to main memory.  The last integer in the list is
-# the number of testers associated with the uppermost level of memory
-# (L1 cache, if there are caches, or main memory if no caches).  Thus
-# if there is only one integer, there are no caches, and the integer
-# specifies the number of testers connected directly to main memory.
-# The other integers (if any) specify the number of caches at each
-# level of the hierarchy between.
-#
-# Examples:
-#
-#  "2:1"    Two caches connected to memory with a single tester behind each
-#           (single-level hierarchy, two testers total)
-#
-#  "2:2:1"  Two-level hierarchy, 2 L1s behind each of 2 L2s, 4 testers total
-#
-parser.add_option("-t", "--treespec", type="string", default="8:1",
-                  help="Colon-separated multilevel tree specification, "
+# The tree specification consists of two colon-separated lists of one
+# or more integers, one for the caches, and one for the testers. The
+# first integer is the number of caches/testers closest to main
+# memory. Each cache then fans out to a subtree. The last integer in
+# the list is the number of caches/testers associated with the
+# uppermost level of memory. The other integers (if any) specify the
+# number of caches/testers connected at each level of the crossbar
+# hierarchy. The tester string should have one element more than the
+# cache string as there should always be testers attached to the
+# uppermost caches.
+
+parser.add_option("-c", "--caches", type="string", default="2:2:1",
+                  help="Colon-separated cache hierarchy specification, "
                   "see script comments for details "
                   "[default: %default]")
-
-parser.add_option("--force-bus", action="store_true",
-                  help="Use bus between levels even with single cache")
-
-parser.add_option("-f", "--functional", type="int", default=0,
+parser.add_option("--noncoherent-cache", action="store_true",
+                  help="Adds a non-coherent, last-level cache")
+parser.add_option("-t", "--testers", type="string", default="1:1:0:2",
+                  help="Colon-separated tester hierarchy specification, "
+                  "see script comments for details "
+                  "[default: %default]")
+parser.add_option("-f", "--functional", type="int", default=10,
                   metavar="PCT",
                   help="Target percentage of functional accesses "
                   "[default: %default]")
-parser.add_option("-u", "--uncacheable", type="int", default=0,
+parser.add_option("-u", "--uncacheable", type="int", default=10,
                   metavar="PCT",
                   help="Target percentage of uncacheable accesses "
                   "[default: %default]")
-
-parser.add_option("--progress", type="int", default=1000,
+parser.add_option("-r", "--random", action="store_true",
+                  help="Generate a random tree topology")
+parser.add_option("--progress", type="int", default=100000,
                   metavar="NLOADS",
                   help="Progress message interval "
                   "[default: %default]")
+parser.add_option("--sys-clock", action="store", type="string",
+                  default='1GHz',
+                  help = """Top-level clock for blocks running at system
+                  speed""")
 
 (options, args) = parser.parse_args()
 
 if args:
-     print "Error: script doesn't take any positional arguments"
+     print("Error: script doesn't take any positional arguments")
      sys.exit(1)
 
+# Get the total number of testers
+def numtesters(cachespec, testerspec):
+     # Determine the tester multiplier for each level as the
+     # elements are per subsystem and it fans out
+     multiplier = [1]
+     for c in cachespec:
+          multiplier.append(multiplier[-1] * c)
+
+     total = 0
+     for t, m in zip(testerspec, multiplier):
+          total += t * m
+
+     return total
+
 block_size = 64
 
-try:
-     treespec = [int(x) for x in options.treespec.split(':')]
-     numtesters = reduce(lambda x,y: x*y, treespec)
-except:
-     print "Error parsing treespec option"
-     sys.exit(1)
+# Start by parsing the command line options and do some basic sanity
+# checking
+if options.random:
+     # Generate a tree with a valid number of testers
+     while True:
+          tree_depth = random.randint(1, 4)
+          cachespec = [random.randint(1, 3) for i in range(tree_depth)]
+          testerspec = [random.randint(1, 3) for i in range(tree_depth + 1)]
+          if numtesters(cachespec, testerspec) < block_size:
+               break
 
-if numtesters > block_size:
-     print "Error: Number of testers limited to %s because of false sharing" \
-           % (block_size)
-     sys.exit(1)
+     print("Generated random tree -c", ':'.join(map(str, cachespec)),
+         "-t", ':'.join(map(str, testerspec)))
+else:
+     try:
+          cachespec = [int(x) for x in options.caches.split(':')]
+          testerspec = [int(x) for x in options.testers.split(':')]
+     except:
+          print("Error: Unable to parse caches or testers option")
+          sys.exit(1)
 
-if len(treespec) < 1:
-     print "Error parsing treespec"
-     sys.exit(1)
+     if len(cachespec) < 1:
+          print("Error: Must have at least one level of caches")
+          sys.exit(1)
 
-# define prototype L1 cache
-proto_l1 = BaseCache(size = '32kB', assoc = 4, block_size = block_size,
-                     latency = '1ns', tgts_per_mshr = 8)
+     if len(cachespec) != len(testerspec) - 1:
+          print("Error: Testers must have one element more than caches")
+          sys.exit(1)
+
+     if testerspec[-1] == 0:
+          print("Error: Must have testers at the uppermost level")
+          sys.exit(1)
+
+     for t in testerspec:
+          if t < 0:
+               print("Error: Cannot have a negative number of testers")
+               sys.exit(1)
+
+     for c in cachespec:
+          if c < 1:
+               print("Error: Must have 1 or more caches at each level")
+               sys.exit(1)
+
+     if numtesters(cachespec, testerspec) > block_size:
+          print("Error: Limited to %s testers because of false sharing"
+              % (block_size))
+          sys.exit(1)
+
+# Define a prototype L1 cache that we scale for all successive levels
+proto_l1 = Cache(size = '32kB', assoc = 4,
+                 tag_latency = 1, data_latency = 1, response_latency = 1,
+                 tgts_per_mshr = 8, clusivity = 'mostly_incl',
+                 writeback_clean = True)
 
 if options.blocking:
      proto_l1.mshrs = 1
 else:
-     proto_l1.mshrs = 8
-
-# build a list of prototypes, one for each level of treespec, starting
-# at the end (last entry is tester objects)
-prototypes = [ MemTest(atomic=options.atomic, max_loads=options.maxloads,
-                       percent_functional=options.functional,
-                       percent_uncacheable=options.uncacheable,
-                       progress_interval=options.progress) ]
-
-# next comes L1 cache, if any
-if len(treespec) > 1:
-     prototypes.insert(0, proto_l1)
-
-# now add additional cache levels (if any) by scaling L1 params
-while len(prototypes) < len(treespec):
-     # clone previous level and update params
-     prev = prototypes[0]
+     proto_l1.mshrs = 4
+
+cache_proto = [proto_l1]
+
+# Now add additional cache levels (if any) by scaling L1 params, the
+# first element is Ln, and the last element L1
+for scale in cachespec[:-1]:
+     # Clone previous level and update params
+     prev = cache_proto[0]
      next = prev()
-     next.size = prev.size * 4
-     next.latency = prev.latency * 10
-     next.assoc = prev.assoc * 2
-     prototypes.insert(0, next)
-
-# system simulated
-system = System(funcmem = PhysicalMemory(),
-                physmem = PhysicalMemory(latency = "100ns"))
-
-def make_level(spec, prototypes, attach_obj, attach_port):
-     fanout = spec[0]
-     parent = attach_obj # use attach obj as config parent too
-     if len(spec) > 1 and (fanout > 1 or options.force_bus):
-          new_bus = Bus(clock="500MHz", width=16)
-          new_bus.port = getattr(attach_obj, attach_port)
-          parent.cpu_side_bus = new_bus
-          attach_obj = new_bus
-          attach_port = "port"
-     objs = [prototypes[0]() for i in xrange(fanout)]
-     if len(spec) > 1:
-          # we just built caches, more levels to go
-          parent.cache = objs
-          for cache in objs:
-               cache.mem_side = getattr(attach_obj, attach_port)
-               make_level(spec[1:], prototypes[1:], cache, "cpu_side")
+     next.size = prev.size * scale
+     next.tag_latency = prev.tag_latency * 10
+     next.data_latency = prev.data_latency * 10
+     next.response_latency = prev.response_latency * 10
+     next.assoc = prev.assoc * scale
+     next.mshrs = prev.mshrs * scale
+
+     # Swap the inclusivity/exclusivity at each level. L2 is mostly
+     # exclusive with respect to L1, L3 mostly inclusive, L4 mostly
+     # exclusive etc.
+     next.writeback_clean = not prev.writeback_clean
+     if (prev.clusivity.value == 'mostly_incl'):
+          next.clusivity = 'mostly_excl'
      else:
-          # we just built the MemTest objects
-          parent.cpu = objs
-          for t in objs:
-               t.test = getattr(attach_obj, attach_port)
-               t.functional = system.funcmem.port
+          next.clusivity = 'mostly_incl'
+
+     cache_proto.insert(0, next)
+
+# Make a prototype for the tester to be used throughout
+proto_tester = MemTest(max_loads = options.maxloads,
+                       percent_functional = options.functional,
+                       percent_uncacheable = options.uncacheable,
+                       progress_interval = options.progress)
+
+# Set up the system along with a simple memory and reference memory
+system = System(physmem = SimpleMemory(),
+                cache_line_size = block_size)
+
+system.voltage_domain = VoltageDomain(voltage = '1V')
+
+system.clk_domain = SrcClockDomain(clock =  options.sys_clock,
+                        voltage_domain = system.voltage_domain)
 
-make_level(treespec, prototypes, system.physmem, "port")
+# For each level, track the next subsys index to use
+next_subsys_index = [0] * (len(cachespec) + 1)
 
-# -----------------------
-# run simulation
-# -----------------------
+# Recursive function to create a sub-tree of the cache and tester
+# hierarchy
+def make_cache_level(ncaches, prototypes, level, next_cache):
+     global next_subsys_index, proto_l1, testerspec, proto_tester
+
+     index = next_subsys_index[level]
+     next_subsys_index[level] += 1
+
+     # Create a subsystem to contain the crossbar and caches, and
+     # any testers
+     subsys = SubSystem()
+     setattr(system, 'l%dsubsys%d' % (level, index), subsys)
+
+     # The levels are indexing backwards through the list
+     ntesters = testerspec[len(cachespec) - level]
+
+     # Scale the progress threshold as testers higher up in the tree
+     # (smaller level) get a smaller portion of the overall bandwidth,
+     # and also make the interval of packet injection longer for the
+     # testers closer to the memory (larger level) to prevent them
+     # hogging all the bandwidth
+     limit = (len(cachespec) - level + 1) * 100000000
+     testers = [proto_tester(interval = 10 * (level * level + 1),
+                             progress_check = limit) \
+                     for i in range(ntesters)]
+     if ntesters:
+          subsys.tester = testers
+
+     if level != 0:
+          # Create a crossbar and add it to the subsystem, note that
+          # we do this even with a single element on this level
+          xbar = L2XBar()
+          subsys.xbar = xbar
+          if next_cache:
+               xbar.master = next_cache.cpu_side
+
+          # Create and connect the caches, both the ones fanning out
+          # to create the tree, and the ones used to connect testers
+          # on this level
+          tree_caches = [prototypes[0]() for i in range(ncaches[0])]
+          tester_caches = [proto_l1() for i in range(ntesters)]
+
+          subsys.cache = tester_caches + tree_caches
+          for cache in tree_caches:
+               cache.mem_side = xbar.slave
+               make_cache_level(ncaches[1:], prototypes[1:], level - 1, cache)
+          for tester, cache in zip(testers, tester_caches):
+               tester.port = cache.cpu_side
+               cache.mem_side = xbar.slave
+     else:
+          if not next_cache:
+               print("Error: No next-level cache at top level")
+               sys.exit(1)
+
+          if ntesters > 1:
+               # Create a crossbar and add it to the subsystem
+               xbar = L2XBar()
+               subsys.xbar = xbar
+               xbar.master = next_cache.cpu_side
+               for tester in testers:
+                    tester.port = xbar.slave
+          else:
+               # Single tester
+               testers[0].port = next_cache.cpu_side
+
+# Top level call to create the cache hierarchy, bottom up
+make_cache_level(cachespec, cache_proto, len(cachespec), None)
+
+# Connect the lowest level crossbar to the last-level cache and memory
+# controller
+last_subsys = getattr(system, 'l%dsubsys0' % len(cachespec))
+last_subsys.xbar.point_of_coherency = True
+if options.noncoherent_cache:
+     system.llc = NoncoherentCache(size = '16MB', assoc = 16, tag_latency = 10,
+                                   data_latency = 10, sequential_access = True,
+                                   response_latency = 20, tgts_per_mshr = 8,
+                                   mshrs = 64)
+     last_subsys.xbar.master = system.llc.cpu_side
+     system.llc.mem_side = system.physmem.port
+else:
+     last_subsys.xbar.master = system.physmem.port
 
-root = Root( system = system )
+root = Root(full_system = False, system = system)
 if options.atomic:
     root.system.mem_mode = 'atomic'
 else:
     root.system.mem_mode = 'timing'
 
-# Not much point in this being higher than the L1 latency
-m5.ticks.setGlobalFrequency('1ns')
+# The system port is never used in the tester so merely connect it
+# to avoid problems
+root.system.system_port = last_subsys.xbar.slave
 
-# instantiate configuration
-m5.instantiate(root)
+# Instantiate configuration
+m5.instantiate()
 
-# simulate until program terminates
+# Simulate until program terminates
 exit_event = m5.simulate(options.maxtick)
 
-print 'Exiting @ tick', m5.curTick(), 'because', exit_event.getCause()
+print('Exiting @ tick', m5.curTick(), 'because', exit_event.getCause())