stdlib,mem-ruby: CHI support in components

This changeset adds CHI support in the components library. Currently,
only a very simple one level protocol is implemented, but hopefully this
design will be able to scale to other more complex hierarchies.

I've tested this with RISC-V with 1 and 4 cores and with x86 with 1
core. Since we don't have an Arm-compatible board, I haven't tested with
ARM. Note that x86 with more than 1 core boots most of the way, but it
hangs during systemd (the kernel comes up completely).

Change-Id: I56953238c6b0ca5ac754b103a1b6ec05a85a0af5
Signed-off-by: Jason Lowe-Power <jason@lowepower.com>
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/52463
Reviewed-by: Tiago Mück <tiago.muck@arm.com>
Reviewed-by: Bobby R. Bruce <bbruce@ucdavis.edu>
Maintainer: Jason Lowe-Power <power.jg@gmail.com>
Maintainer: Bobby R. Bruce <bbruce@ucdavis.edu>
Tested-by: kokoro <noreply+kokoro@google.com>
diff --git a/src/python/SConscript b/src/python/SConscript
index d210960..f9b74c0 100644
--- a/src/python/SConscript
+++ b/src/python/SConscript
@@ -50,6 +50,22 @@
     'gem5/components/cachehierarchies/abstract_cache_hierarchy.py')
 PySource('gem5.components.cachehierarchies',
     'gem5/components/cachehierarchies/abstract_two_level_cache_hierarchy.py')
+PySource('gem5.components.cachehierarchies.chi',
+    'gem5/components/cachehierarchies/chi/__init__.py')
+PySource('gem5.components.cachehierarchies.chi',
+    'gem5/components/cachehierarchies/chi/private_l1_cache_hierarchy.py')
+PySource('gem5.components.cachehierarchies.chi.nodes',
+    'gem5/components/cachehierarchies/chi/nodes/__init__.py')
+PySource('gem5.components.cachehierarchies.chi.nodes',
+    'gem5/components/cachehierarchies/chi/nodes/abstract_node.py')
+PySource('gem5.components.cachehierarchies.chi.nodes',
+    'gem5/components/cachehierarchies/chi/nodes/directory.py')
+PySource('gem5.components.cachehierarchies.chi.nodes',
+    'gem5/components/cachehierarchies/chi/nodes/dma_requestor.py')
+PySource('gem5.components.cachehierarchies.chi.nodes',
+    'gem5/components/cachehierarchies/chi/nodes/private_l1_moesi_cache.py')
+PySource('gem5.components.cachehierarchies.chi.nodes',
+    'gem5/components/cachehierarchies/chi/nodes/memory_controller.py')
 PySource('gem5.components.cachehierarchies.classic',
     'gem5/components/cachehierarchies/classic/__init__.py')
 PySource('gem5.components.cachehierarchies.classic',
diff --git a/src/python/gem5/components/cachehierarchies/chi/__init__.py b/src/python/gem5/components/cachehierarchies/chi/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/python/gem5/components/cachehierarchies/chi/__init__.py
diff --git a/src/python/gem5/components/cachehierarchies/chi/nodes/__init__.py b/src/python/gem5/components/cachehierarchies/chi/nodes/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/python/gem5/components/cachehierarchies/chi/nodes/__init__.py
diff --git a/src/python/gem5/components/cachehierarchies/chi/nodes/abstract_node.py b/src/python/gem5/components/cachehierarchies/chi/nodes/abstract_node.py
new file mode 100644
index 0000000..e8797b6
--- /dev/null
+++ b/src/python/gem5/components/cachehierarchies/chi/nodes/abstract_node.py
@@ -0,0 +1,132 @@
+# Copyright (c) 2021 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 abc import abstractmethod
+from gem5.isas import ISA
+from gem5.components.processors.cpu_types import CPUTypes
+from gem5.components.processors.abstract_core import AbstractCore
+
+from m5.objects import Cache_Controller, MessageBuffer, RubyNetwork
+
+import math
+
+class TriggerMessageBuffer(MessageBuffer):
+    '''
+    MessageBuffer for triggering internal controller events.
+    These buffers should not be affected by the Ruby tester randomization
+    and allow poping messages enqueued in the same cycle.
+    '''
+    randomization = 'disabled'
+    allow_zero_latency = True
+
+class OrderedTriggerMessageBuffer(TriggerMessageBuffer):
+    ordered = True
+
+class AbstractNode(Cache_Controller):
+    """A node is the abstract unit for caches in the CHI protocol.
+
+    You can extend the AbstractNode to create caches (private or shared) and
+    directories with or without data caches.
+    """
+    _version = 0
+
+    @classmethod
+    def versionCount(cls):
+        cls._version += 1  # Use count for this particular type
+        return cls._version - 1
+
+    # TODO: I don't love that we have to pass in the cache line size.
+    # However, we need some way to set the index bits
+    def __init__(self, network: RubyNetwork, cache_line_size: int):
+        super(AbstractNode, self).__init__()
+
+        # Note: Need to call versionCount method on *this* class, not the
+        # potentially derived class
+        self.version = AbstractNode.versionCount()
+        self._cache_line_size = cache_line_size
+
+        # Set somewhat large number since we really a lot on internal
+        # triggers. To limit the controller performance, tweak other
+        # params such as: input port buffer size, cache banks, and output
+        # port latency
+        self.transitions_per_cycle = 128
+        # This should be set to true in the data cache controller to enable
+        # timeouts on unique lines when a store conditional fails
+        self.sc_lock_enabled = False
+
+        # Use 32-byte channels (two flits per message)
+        self.data_channel_size = 32
+
+        self.connectQueues(network)
+
+    def getBlockSizeBits(self):
+        bits = int(math.log(self._cache_line_size, 2))
+        if 2 ** bits != self._cache_line_size.value:
+            raise Exception("Cache line size not a power of 2!")
+        return bits
+
+    def sendEvicts(self, core: AbstractCore, target_isa: ISA):
+        """True if the CPU model or ISA requires sending evictions from caches
+        to the CPU. 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 core.get_type() is CPUTypes.O3 or target_isa in (ISA.X86, ISA.ARM):
+            return True
+        return False
+
+    def connectQueues(self, network: RubyNetwork):
+        """Connect all of the queues for this controller.
+        This may be extended in subclasses.
+        """
+        self.mandatoryQueue = MessageBuffer()
+        self.prefetchQueue = MessageBuffer()
+
+        self.triggerQueue = TriggerMessageBuffer()
+        self.retryTriggerQueue = OrderedTriggerMessageBuffer()
+        self.replTriggerQueue = OrderedTriggerMessageBuffer()
+        self.reqRdy = TriggerMessageBuffer()
+        self.snpRdy = TriggerMessageBuffer()
+
+        self.reqOut = MessageBuffer()
+        self.rspOut = MessageBuffer()
+        self.snpOut = MessageBuffer()
+        self.datOut = MessageBuffer()
+        self.reqIn = MessageBuffer()
+        self.rspIn = MessageBuffer()
+        self.snpIn = MessageBuffer()
+        self.datIn = MessageBuffer()
+        self.reqOut.out_port = network.in_port
+        self.rspOut.out_port = network.in_port
+        self.snpOut.out_port = network.in_port
+        self.datOut.out_port = network.in_port
+        self.reqIn.in_port = network.out_port
+        self.rspIn.in_port = network.out_port
+        self.snpIn.in_port = network.out_port
+        self.datIn.in_port = network.out_port
+
+
diff --git a/src/python/gem5/components/cachehierarchies/chi/nodes/directory.py b/src/python/gem5/components/cachehierarchies/chi/nodes/directory.py
new file mode 100644
index 0000000..8f1275c
--- /dev/null
+++ b/src/python/gem5/components/cachehierarchies/chi/nodes/directory.py
@@ -0,0 +1,90 @@
+# Copyright (c) 2021 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 .abstract_node import AbstractNode
+
+from m5.objects import (
+    ClockDomain,
+    NULL,
+    RubyCache,
+    RubyNetwork,
+)
+
+class SimpleDirectory(AbstractNode):
+    """A directory or home node (HNF)
+
+    This simple directory has no cache. It forwards all requests as directly
+    as possible.
+    """
+    def __init__(
+        self,
+        network: RubyNetwork,
+        cache_line_size: int,
+        clk_domain: ClockDomain,
+    ):
+        super().__init__(network, cache_line_size)
+
+        # Dummy cache
+        self.cache = RubyCache(
+            dataAccessLatency = 0,
+            tagAccessLatency = 1,
+            size = "128",
+            assoc = 1
+        )
+
+        self.clk_domain = clk_domain
+
+        # Only used for L1 controllers
+        self.send_evictions = False
+        self.sequencer = NULL
+
+        self.use_prefetcher = False
+
+        # Set up home node that allows three hop protocols
+        self.is_HN = True
+        self.enable_DMT = True
+        self.enable_DCT = True
+
+        # "Owned state"
+        self.allow_SD = True
+
+        # No cache
+        self.alloc_on_seq_acc = False
+        self.alloc_on_seq_line_write = False
+        self.alloc_on_readshared = False
+        self.alloc_on_readunique = False
+        self.alloc_on_readonce = False
+        self.alloc_on_writeback = False
+        self.dealloc_on_unique = False
+        self.dealloc_on_shared = False
+        self.dealloc_backinv_unique = False
+        self.dealloc_backinv_shared = False
+
+        # Some reasonable default TBE params
+        self.number_of_TBEs = 32
+        self.number_of_repl_TBEs = 32
+        self.number_of_snoop_TBEs = 1
+        self.unify_repl_TBEs = False
diff --git a/src/python/gem5/components/cachehierarchies/chi/nodes/dma_requestor.py b/src/python/gem5/components/cachehierarchies/chi/nodes/dma_requestor.py
new file mode 100644
index 0000000..7543e06
--- /dev/null
+++ b/src/python/gem5/components/cachehierarchies/chi/nodes/dma_requestor.py
@@ -0,0 +1,80 @@
+# Copyright (c) 2021 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 gem5.components.processors.abstract_core import AbstractCore
+from gem5.isas import ISA
+
+from .abstract_node import AbstractNode
+
+from m5.objects import (
+    ClockDomain,
+    RubyCache,
+)
+
+class DMARequestor(AbstractNode):
+    def __init__(
+        self,
+        network,
+        cache_line_size,
+        clk_domain: ClockDomain,
+    ):
+        super().__init__(network, cache_line_size)
+
+        # Dummy cache
+        self.cache = RubyCache(
+            dataAccessLatency = 0,
+            tagAccessLatency = 1,
+            size = "128",
+            assoc = 1
+        )
+
+        self.clk_domain = clk_domain
+
+        # Only applies to home nodes
+        self.is_HN = False
+        self.enable_DMT = False
+        self.enable_DCT = False
+
+        # No cache
+        self.allow_SD = False
+        self.alloc_on_seq_acc = False
+        self.alloc_on_seq_line_write = False
+        self.alloc_on_readshared = False
+        self.alloc_on_readunique = False
+        self.alloc_on_readonce = False
+        self.alloc_on_writeback = False
+        self.dealloc_on_unique = False
+        self.dealloc_on_shared = False
+        self.dealloc_backinv_unique = True
+        self.dealloc_backinv_shared = True
+
+        self.send_evictions = False
+        self.use_prefetcher = False
+        # Some reasonable default TBE params
+        self.number_of_TBEs = 16
+        self.number_of_repl_TBEs = 1
+        self.number_of_snoop_TBEs = 1 # Should never receive snoops
+        self.unify_repl_TBEs = False
diff --git a/src/python/gem5/components/cachehierarchies/chi/nodes/memory_controller.py b/src/python/gem5/components/cachehierarchies/chi/nodes/memory_controller.py
new file mode 100644
index 0000000..cf7d660
--- /dev/null
+++ b/src/python/gem5/components/cachehierarchies/chi/nodes/memory_controller.py
@@ -0,0 +1,86 @@
+# Copyright (c) 2021 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 typing import List
+
+from m5.objects import (
+    AddrRange,
+    Memory_Controller,
+    MessageBuffer,
+    Port,
+    RubyNetwork,
+)
+
+from .abstract_node import TriggerMessageBuffer
+
+class MemoryController(Memory_Controller):
+    """A controller that connects to memory
+    """
+    _version = 0
+
+    @classmethod
+    def versionCount(cls):
+        cls._version += 1  # Use count for this particular type
+        return cls._version - 1
+
+    def __init__(
+        self,
+        network: RubyNetwork,
+        ranges: List[AddrRange],
+        port: Port
+    ):
+        super().__init__()
+
+        self.version = self.versionCount()
+
+        self.addr_ranges = ranges
+        self.memory_out_port = port
+        self.data_channel_size = 32
+
+        self.connectQueues(network)
+
+    def connectQueues(self, network):
+        self.triggerQueue = TriggerMessageBuffer()
+        self.responseFromMemory = MessageBuffer()
+        self.requestToMemory = MessageBuffer(ordered = True)
+        self.reqRdy = TriggerMessageBuffer()
+
+        self.reqOut = MessageBuffer()
+        self.rspOut = MessageBuffer()
+        self.snpOut = MessageBuffer()
+        self.datOut = MessageBuffer()
+        self.reqIn = MessageBuffer()
+        self.rspIn = MessageBuffer()
+        self.snpIn = MessageBuffer()
+        self.datIn = MessageBuffer()
+        self.reqOut.out_port = network.in_port
+        self.rspOut.out_port = network.in_port
+        self.snpOut.out_port = network.in_port
+        self.datOut.out_port = network.in_port
+        self.reqIn.in_port = network.out_port
+        self.rspIn.in_port = network.out_port
+        self.snpIn.in_port = network.out_port
+        self.datIn.in_port = network.out_port
diff --git a/src/python/gem5/components/cachehierarchies/chi/nodes/private_l1_moesi_cache.py b/src/python/gem5/components/cachehierarchies/chi/nodes/private_l1_moesi_cache.py
new file mode 100644
index 0000000..664522e
--- /dev/null
+++ b/src/python/gem5/components/cachehierarchies/chi/nodes/private_l1_moesi_cache.py
@@ -0,0 +1,81 @@
+# Copyright (c) 2021 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 gem5.components.processors.abstract_core import AbstractCore
+from gem5.isas import ISA
+
+from .abstract_node import AbstractNode
+
+from m5.objects import (
+    ClockDomain,
+    RubyCache,
+    RubyNetwork,
+)
+
+
+class PrivateL1MOESICache(AbstractNode):
+    def __init__(
+        self,
+        size: str,
+        assoc: int,
+        network: RubyNetwork,
+        core: AbstractCore,
+        cache_line_size,
+        target_isa: ISA,
+        clk_domain: ClockDomain,
+    ):
+        super().__init__(network, cache_line_size)
+
+        self.cache = RubyCache(
+            size=size, assoc=assoc, start_index_bit=self.getBlockSizeBits()
+        )
+
+        self.clk_domain = clk_domain
+        self.send_evictions = self.sendEvicts(core=core, target_isa=target_isa)
+        self.use_prefetcher = False
+
+        # Only applies to home nodes
+        self.is_HN = False
+        self.enable_DMT = False
+        self.enable_DCT = False
+
+        # MOESI states for a 1 level cache
+        self.allow_SD = True
+        self.alloc_on_seq_acc = True
+        self.alloc_on_seq_line_write = False
+        self.alloc_on_readshared = True
+        self.alloc_on_readunique = True
+        self.alloc_on_readonce = True
+        self.alloc_on_writeback = False       # Should never happen in an L1
+        self.dealloc_on_unique = False
+        self.dealloc_on_shared = False
+        self.dealloc_backinv_unique = True
+        self.dealloc_backinv_shared = True
+        # Some reasonable default TBE params
+        self.number_of_TBEs = 16
+        self.number_of_repl_TBEs = 16
+        self.number_of_snoop_TBEs = 4
+        self.unify_repl_TBEs = False
diff --git a/src/python/gem5/components/cachehierarchies/chi/private_l1_cache_hierarchy.py b/src/python/gem5/components/cachehierarchies/chi/private_l1_cache_hierarchy.py
new file mode 100644
index 0000000..d692710
--- /dev/null
+++ b/src/python/gem5/components/cachehierarchies/chi/private_l1_cache_hierarchy.py
@@ -0,0 +1,249 @@
+# Copyright (c) 2021 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 itertools import chain
+from typing import List
+
+from m5.objects.SubSystem import SubSystem
+from gem5.components.cachehierarchies.ruby.abstract_ruby_cache_hierarchy \
+    import AbstractRubyCacheHierarchy
+from gem5.components.cachehierarchies.abstract_cache_hierarchy import (
+    AbstractCacheHierarchy,
+)
+from gem5.coherence_protocol import CoherenceProtocol
+from gem5.isas import ISA
+from gem5.runtime import get_runtime_isa
+from gem5.utils.requires import requires
+from gem5.utils.override import overrides
+from gem5.components.boards.abstract_board import AbstractBoard
+from gem5.components.processors.abstract_core import AbstractCore
+
+from gem5.components.cachehierarchies.ruby.topologies.simple_pt2pt import (
+    SimplePt2Pt,
+)
+
+from .nodes.private_l1_moesi_cache import PrivateL1MOESICache
+from .nodes.dma_requestor import DMARequestor
+from .nodes.directory import SimpleDirectory
+from .nodes.memory_controller import MemoryController
+
+from m5.objects import (
+    NULL,
+    RubySystem,
+    RubySequencer,
+    RubyPortProxy,
+)
+
+
+class PrivateL1CacheHierarchy(AbstractRubyCacheHierarchy):
+    """A single level cache based on CHI for RISC-V
+
+    This hierarchy has a split I/D L1 caches per CPU, a single directory (HNF),
+    and as many memory controllers (SNF) as memory channels. The directory does
+    not have an associated cache.
+
+    The network is a simple point-to-point between all of the controllers.
+    """
+
+    def __init__(self, size: str, assoc: int) -> None:
+        """
+        :param size: The size of the priavte I/D caches in the hierarchy.
+        :param assoc: The associativity of each cache.
+        """
+        super().__init__()
+
+        self._size = size
+        self._assoc = assoc
+
+    @overrides(AbstractCacheHierarchy)
+    def incorporate_cache(self, board: AbstractBoard) -> None:
+
+        requires(coherence_protocol_required=CoherenceProtocol.CHI)
+
+        self.ruby_system = RubySystem()
+
+        # Ruby's global network.
+        self.ruby_system.network = SimplePt2Pt(self.ruby_system)
+
+        # Network configurations
+        # virtual networks: 0=request, 1=snoop, 2=response, 3=data
+        self.ruby_system.number_of_virtual_networks = 4
+        self.ruby_system.network.number_of_virtual_networks = 4
+
+        # Create a single centralized directory
+        self.directory = SimpleDirectory(
+            self.ruby_system.network,
+            cache_line_size=board.get_cache_line_size(),
+            clk_domain=board.get_clock_domain(),
+        )
+        self.directory.ruby_system = self.ruby_system
+
+        # Create one core cluster with a split I/D cache for each core
+        self.core_clusters = [
+            self._create_core_cluster(core, i, board)
+            for i, core in enumerate(board.get_processor().get_cores())
+        ]
+
+        # Create the coherent side of the memory controllers
+        self.memory_controllers = self._create_memory_controllers(board)
+        self.directory.downstream_destinations = self.memory_controllers
+
+        # Create the DMA Controllers, if required.
+        if board.has_dma_ports():
+            self.dma_controllers = self._create_dma_controllers(board)
+            self.ruby_system.num_of_sequencers = len(self.core_clusters) * 2 \
+                + len(self.dma_controllers)
+        else:
+            self.ruby_system.num_of_sequencers = len(self.core_clusters) * 2
+
+        self.ruby_system.network.connectControllers(
+            list(
+                chain.from_iterable( # Grab the controllers from each cluster
+                    [
+                        (cluster.dcache, cluster.icache)
+                        for cluster in self.core_clusters
+                    ]
+                )
+            )
+            + self.memory_controllers
+            + [self.directory]
+            + (self.dma_controllers if board.has_dma_ports() else [])
+        )
+
+        self.ruby_system.network.setup_buffers()
+
+        # Set up a proxy port for the system_port. Used for load binaries and
+        # other functional-only things.
+        self.ruby_system.sys_port_proxy = RubyPortProxy()
+        board.connect_system_port(self.ruby_system.sys_port_proxy.in_ports)
+
+    def _create_core_cluster(self,
+        core: AbstractCore,
+        core_num: int,
+        board: AbstractBoard
+    ) -> SubSystem:
+        """Given the core and the core number this function creates a cluster
+        for the core with a split I/D cache
+        """
+        cluster = SubSystem()
+        cluster.dcache = PrivateL1MOESICache(
+            size=self._size,
+            assoc=self._assoc,
+            network=self.ruby_system.network,
+            core=core,
+            cache_line_size=board.get_cache_line_size(),
+            target_isa=get_runtime_isa(),
+            clk_domain=board.get_clock_domain(),
+        )
+        cluster.icache = PrivateL1MOESICache(
+            size=self._size,
+            assoc=self._assoc,
+            network=self.ruby_system.network,
+            core=core,
+            cache_line_size=board.get_cache_line_size(),
+            target_isa=get_runtime_isa(),
+            clk_domain=board.get_clock_domain(),
+        )
+
+        cluster.icache.sequencer = RubySequencer(
+            version=core_num,
+            dcache=NULL,
+            clk_domain=cluster.icache.clk_domain,
+        )
+        cluster.dcache.sequencer = RubySequencer(
+            version=core_num,
+            dcache=cluster.dcache.cache,
+            clk_domain=cluster.dcache.clk_domain,
+        )
+
+        if board.has_io_bus():
+            cluster.dcache.sequencer.connectIOPorts(board.get_io_bus())
+
+        cluster.dcache.ruby_system = self.ruby_system
+        cluster.icache.ruby_system = self.ruby_system
+
+        core.connect_icache(cluster.icache.sequencer.in_ports)
+        core.connect_dcache(cluster.dcache.sequencer.in_ports)
+
+        core.connect_walker_ports(
+            cluster.dcache.sequencer.in_ports,
+            cluster.icache.sequencer.in_ports,
+        )
+
+        # Connect the interrupt ports
+        if get_runtime_isa() == ISA.X86:
+            int_req_port = cluster.dcache.sequencer.interrupt_out_port
+            int_resp_port = cluster.dcache.sequencer.in_ports
+            core.connect_interrupt(int_req_port, int_resp_port)
+        else:
+            core.connect_interrupt()
+
+        cluster.dcache.downstream_destinations = [self.directory]
+        cluster.icache.downstream_destinations = [self.directory]
+
+        return cluster
+
+    def _create_memory_controllers(
+        self,
+        board: AbstractBoard
+    ) -> List[MemoryController]:
+        memory_controllers = []
+        for rng, port in board.get_memory().get_mem_ports():
+            mc = MemoryController(
+                self.ruby_system.network,
+                rng,
+                port,
+            )
+            mc.ruby_system = self.ruby_system
+            memory_controllers.append(mc)
+        return memory_controllers
+
+    def _create_dma_controllers(
+        self,
+        board: AbstractBoard
+    ) -> List[DMARequestor]:
+        dma_controllers = []
+        for i, port in enumerate(board.get_dma_ports()):
+            ctrl = DMARequestor(
+                self.ruby_system.network,
+                board.get_cache_line_size(),
+                board.get_clock_domain(),
+            )
+            version = len(board.get_processor().get_cores()) + i
+            ctrl.sequencer = RubySequencer(
+                version=version,
+                in_ports=port
+            )
+            ctrl.sequencer.dcache = NULL
+
+            ctrl.ruby_system = self.ruby_system
+            ctrl.sequencer.ruby_system = self.ruby_system
+
+            ctrl.downstream_destinations = [self.directory]
+
+            dma_controllers.append(ctrl)
+
+        return dma_controllers