configs,mem-ruby: CHI-based Ruby protocol
This patch add a new Ruby cache coherence protocol based on Arm' AMBA5
CHI specification. The CHI protocol defines and implements two state
machine types:
- Cache_Controller: generic cache controller that can be configured as:
- Top-level L1 I/D cache
- A intermediate level (L2, L3, ...) private or shared cache
- A CHI home node (i.e. the point of coherence of the system and
has the global directory)
- A DMA requester
- Memory_Controller: implements a CHI slave node and interfaces with
gem5 memory controller. This controller has the functionality of a
Directory_Controller on the other Ruby protocols, except it doesn't
have a directory.
The Cache_Controller has multiple cache allocation/deallocation
parameters to control the clusivity with respect to upstream caches.
Allocation can be completely disabled to use Cache_Controller as a
DMA requester or as a home node without a shared LLC.
The standard configuration file configs/ruby/CHI.py provides a
'create_system' compatible with configs/example/fs.py and
configs/example/se.py and creates a system with private L1/L2 caches
per core and a shared LLC at the home nodes. Different cache topologies
can be defined by modifying 'create_system' or by creating custom
scripts using the structures defined in configs/ruby/CHI.py.
This patch also includes the 'CustomMesh' topology script to be used
with CHI. CustomMesh generates a 2D mesh topology with the placement
of components manually defined in a separate configuration file using
the --noc-config parameter.
The example in configs/example/noc_config/2x4.yaml creates a simple 2x4
mesh. For example, to run a SE mode simulation, with 4 cores,
4 mem ctnrls, and 4 home nodes (L3 caches):
build/ARM/gem5.opt configs/example/se.py \
--cmd 'tests/test-progs/hello/bin/arm/linux/hello' \
--ruby --num-cpus=4 --num-dirs=4 --num-l3caches=4 \
--topology=CustomMesh --noc-config=configs/example/noc_config/2x4.yaml
If one doesn't care about the component placement on the interconnect,
the 'Crossbar' and 'Pt2Pt' may be used and they do not require the
--noc-config option.
Additional authors:
Joshua Randall <joshua.randall@arm.com>
Pedro Benedicte <pedro.benedicteillescas@arm.com>
Tuan Ta <tuan.ta2@arm.com>
JIRA: https://gem5.atlassian.net/browse/GEM5-908
Change-Id: I856524b0afd30842194190f5bd69e7e6ded906b0
Signed-off-by: Tiago Mück <tiago.muck@arm.com>
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/42563
Reviewed-by: Jason Lowe-Power <power.jg@gmail.com>
Maintainer: Jason Lowe-Power <power.jg@gmail.com>
Tested-by: kokoro <noreply+kokoro@google.com>
diff --git a/configs/example/noc_config/2x4.yaml b/configs/example/noc_config/2x4.yaml
new file mode 100644
index 0000000..84ec476
--- /dev/null
+++ b/configs/example/noc_config/2x4.yaml
@@ -0,0 +1,70 @@
+# Copyright (c) 2021 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.
+#
+# 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.
+
+# 2x4 mesh definition
+#
+# 0 --- 1 --- 2 --- 3
+# | | | |
+# 4 --- 5 --- 6 --- 7
+#
+mesh:
+ num_rows : 2
+ num_cols : 4
+ router_latency : 1
+ link_latency : 1
+
+# Bindings for each CHI node type.
+
+CHI_RNF:
+ # Uncomment to map num_nodes_per_router RNFs in each provided router,
+ # assuming num. created CHI_RNFs == len(router_list)*num_nodes_per_router
+ # num_nodes_per_router: 1
+ router_list: [1, 2, 5, 6]
+
+CHI_HNF:
+ # num_nodes_per_router: 1
+ router_list: [1, 2, 5, 6]
+
+CHI_SNF_MainMem:
+ # num_nodes_per_router: 1
+ router_list: [0, 4]
+
+# Applies to CHI_SNF_BootMem and possibly other non-main memories
+CHI_SNF_IO:
+ router_list: [3]
+
+# Applies to CHI_RNI_DMA and CHI_RNI_IO
+CHI_RNI_IO:
+ router_list: [7]
diff --git a/configs/ruby/CHI.py b/configs/ruby/CHI.py
new file mode 100644
index 0000000..0a49371
--- /dev/null
+++ b/configs/ruby/CHI.py
@@ -0,0 +1,840 @@
+# Copyright (c) 2021 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.
+#
+# 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.
+
+import math
+import yaml
+import m5
+from m5.objects import *
+from m5.defines import buildEnv
+from .Ruby import create_topology, setup_memory_controllers
+
+def define_options(parser):
+ parser.add_option("--noc-config", action="store", type="string",
+ default=None,
+ help="YAML NoC config. parameters and bindings. "
+ "required for CustomMesh topology")
+
+class Versions:
+ '''
+ Helper class to obtain unique ids for a given controller class.
+ These are passed as the 'version' parameter when creating the controller.
+ '''
+ _seqs = 0
+ @classmethod
+ def getSeqId(cls):
+ val = cls._seqs
+ cls._seqs += 1
+ return val
+
+ _version = {}
+ @classmethod
+ def getVersion(cls, tp):
+ if tp not in cls._version:
+ cls._version[tp] = 0
+ val = cls._version[tp]
+ cls._version[tp] = val + 1
+ return val
+
+
+class CHI_Node(SubSystem):
+ '''
+ Base class with common functions for setting up Cache or Memory
+ controllers that are part of a CHI RNF, RNFI, HNF, or SNF nodes.
+ Notice getNetworkSideControllers and getAllControllers must be implemented
+ in the derived classes.
+ '''
+
+ def __init__(self, ruby_system):
+ super(CHI_Node, self).__init__()
+ self._ruby_system = ruby_system
+ self._network = ruby_system.network
+
+ def getNetworkSideControllers(self):
+ '''
+ Returns all ruby controllers that need to be connected to the
+ network
+ '''
+ raise NotImplementedError()
+
+ def getAllControllers(self):
+ '''
+ Returns all ruby controllers associated with this node
+ '''
+ raise NotImplementedError()
+
+ def setDownstream(self, cntrls):
+ '''
+ Sets cntrls as the downstream list of all controllers in this node
+ '''
+ for c in self.getNetworkSideControllers():
+ c.downstream_destinations = cntrls
+
+ def connectController(self, cntrl):
+ '''
+ Creates and configures the messages buffers for the CHI input/output
+ ports that connect to the network
+ '''
+ cntrl.reqOut = MessageBuffer()
+ cntrl.rspOut = MessageBuffer()
+ cntrl.snpOut = MessageBuffer()
+ cntrl.datOut = MessageBuffer()
+ cntrl.reqIn = MessageBuffer()
+ cntrl.rspIn = MessageBuffer()
+ cntrl.snpIn = MessageBuffer()
+ cntrl.datIn = MessageBuffer()
+
+ # All CHI ports are always connected to the network.
+ # Controllers that are not part of the getNetworkSideControllers list
+ # still communicate using internal routers, thus we need to wire-up the
+ # ports
+ cntrl.reqOut.out_port = self._network.in_port
+ cntrl.rspOut.out_port = self._network.in_port
+ cntrl.snpOut.out_port = self._network.in_port
+ cntrl.datOut.out_port = self._network.in_port
+ cntrl.reqIn.in_port = self._network.out_port
+ cntrl.rspIn.in_port = self._network.out_port
+ cntrl.snpIn.in_port = self._network.out_port
+ cntrl.datIn.in_port = self._network.out_port
+
+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 CHI_Cache_Controller(Cache_Controller):
+ '''
+ Default parameters for a Cache controller
+ The Cache_Controller can also be used as a DMA requester or as
+ a pure directory if all cache allocation policies are disabled.
+ '''
+
+ def __init__(self, ruby_system):
+ super(CHI_Cache_Controller, self).__init__(
+ version = Versions.getVersion(Cache_Controller),
+ ruby_system = ruby_system,
+ mandatoryQueue = MessageBuffer(),
+ prefetchQueue = MessageBuffer(),
+ triggerQueue = TriggerMessageBuffer(),
+ retryTriggerQueue = OrderedTriggerMessageBuffer(),
+ replTriggerQueue = OrderedTriggerMessageBuffer(),
+ reqRdy = TriggerMessageBuffer(),
+ snpRdy = TriggerMessageBuffer())
+ # 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
+
+class CHI_L1Controller(CHI_Cache_Controller):
+ '''
+ Default parameters for a L1 Cache controller
+ '''
+
+ def __init__(self, ruby_system, sequencer, cache, prefetcher):
+ super(CHI_L1Controller, self).__init__(ruby_system)
+ self.sequencer = sequencer
+ self.cache = cache
+ self.use_prefetcher = False
+ self.send_evictions = True
+ self.is_HN = False
+ self.enable_DMT = False
+ self.enable_DCT = False
+ # Strict inclusive MOESI
+ 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 = True
+ 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
+
+class CHI_L2Controller(CHI_Cache_Controller):
+ '''
+ Default parameters for a L2 Cache controller
+ '''
+
+ def __init__(self, ruby_system, cache, prefetcher):
+ super(CHI_L2Controller, self).__init__(ruby_system)
+ self.sequencer = NULL
+ self.cache = cache
+ self.use_prefetcher = False
+ self.allow_SD = True
+ self.is_HN = False
+ self.enable_DMT = False
+ self.enable_DCT = False
+ self.send_evictions = False
+ # Strict inclusive MOESI
+ self.alloc_on_seq_acc = False
+ 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 = True
+ 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 = 32
+ self.number_of_repl_TBEs = 32
+ self.number_of_snoop_TBEs = 16
+ self.unify_repl_TBEs = False
+
+class CHI_HNFController(CHI_Cache_Controller):
+ '''
+ Default parameters for a coherent home node (HNF) cache controller
+ '''
+
+ def __init__(self, ruby_system, cache, prefetcher, addr_ranges):
+ super(CHI_HNFController, self).__init__(ruby_system)
+ self.sequencer = NULL
+ self.cache = cache
+ self.use_prefetcher = False
+ self.addr_ranges = addr_ranges
+ self.allow_SD = True
+ self.is_HN = True
+ self.enable_DMT = True
+ self.enable_DCT = True
+ self.send_evictions = False
+ # MOESI / Mostly inclusive for shared / Exclusive for unique
+ self.alloc_on_seq_acc = False
+ self.alloc_on_seq_line_write = False
+ self.alloc_on_readshared = True
+ self.alloc_on_readunique = False
+ self.alloc_on_readonce = True
+ self.alloc_on_writeback = True
+ self.dealloc_on_unique = True
+ 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 # should not receive any snoop
+ self.unify_repl_TBEs = False
+
+class CHI_DMAController(CHI_Cache_Controller):
+ '''
+ Default parameters for a DMA controller
+ '''
+
+ def __init__(self, ruby_system, sequencer):
+ super(CHI_DMAController, self).__init__(ruby_system)
+ self.sequencer = sequencer
+ class DummyCache(RubyCache):
+ dataAccessLatency = 0
+ tagAccessLatency = 1
+ size = "128"
+ assoc = 1
+ self.use_prefetcher = False
+ self.cache = DummyCache()
+ self.sequencer.dcache = NULL
+ # All allocations are false
+ # Deallocations are true (don't really matter)
+ self.allow_SD = False
+ self.is_HN = False
+ self.enable_DMT = False
+ self.enable_DCT = 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 = False
+ self.dealloc_backinv_shared = False
+ self.send_evictions = False
+ self.number_of_TBEs = 16
+ self.number_of_repl_TBEs = 1
+ self.number_of_snoop_TBEs = 1 # should not receive any snoop
+ self.unify_repl_TBEs = False
+
+class CPUSequencerWrapper:
+ '''
+ Other generic configuration scripts assume a matching number of sequencers
+ and cpus. This wraps the instruction and data sequencer so they are
+ compatible with the other scripts. This assumes all scripts are using
+ connectCpuPorts/connectIOPorts to bind ports
+ '''
+
+ def __init__(self, iseq, dseq):
+ # use this style due to __setattr__ override below
+ self.__dict__['inst_seq'] = iseq
+ self.__dict__['data_seq'] = dseq
+ self.__dict__['support_data_reqs'] = True
+ self.__dict__['support_inst_reqs'] = True
+ # Compatibility with certain scripts that wire up ports
+ # without connectCpuPorts
+ self.__dict__['slave'] = dseq.in_ports
+ self.__dict__['in_ports'] = dseq.in_ports
+
+ def connectCpuPorts(self, cpu):
+ assert(isinstance(cpu, BaseCPU))
+ cpu.icache_port = self.inst_seq.in_ports
+ for p in cpu._cached_ports:
+ if str(p) != 'icache_port':
+ exec('cpu.%s = self.data_seq.in_ports' % p)
+ cpu.connectUncachedPorts(self.data_seq)
+
+ def connectIOPorts(self, piobus):
+ self.data_seq.connectIOPorts(piobus)
+
+ def __setattr__(self, name, value):
+ setattr(self.inst_seq, name, value)
+ setattr(self.data_seq, name, value)
+
+class CHI_RNF(CHI_Node):
+ '''
+ Defines a CHI request node.
+ Notice all contollers and sequencers are set as children of the cpus, so
+ this object acts more like a proxy for seting things up and has no topology
+ significance unless the cpus are set as its children at the top level
+ '''
+ def __init__(self, cpus, ruby_system,
+ l1Icache_type, l1Dcache_type,
+ cache_line_size,
+ l1Iprefetcher_type=None, l1Dprefetcher_type=None):
+ super(CHI_RNF, self).__init__(ruby_system)
+
+ self._block_size_bits = int(math.log(cache_line_size, 2))
+
+ # All sequencers and controllers
+ self._seqs = []
+ self._cntrls = []
+
+ # Last level controllers in this node, i.e., the ones that will send
+ # requests to the home nodes
+ self._ll_cntrls = []
+
+ self._cpus = cpus
+
+ # First creates L1 caches and sequencers
+ for cpu in self._cpus:
+ cpu.inst_sequencer = RubySequencer(version = Versions.getSeqId(),
+ ruby_system = ruby_system)
+ cpu.data_sequencer = RubySequencer(version = Versions.getSeqId(),
+ ruby_system = ruby_system)
+
+ self._seqs.append(CPUSequencerWrapper(cpu.inst_sequencer,
+ cpu.data_sequencer))
+
+ # caches
+ l1i_cache = l1Icache_type(start_index_bit = self._block_size_bits,
+ is_icache = True)
+
+ l1d_cache = l1Dcache_type(start_index_bit = self._block_size_bits,
+ is_icache = False)
+
+ # Placeholders for future prefetcher support
+ if l1Iprefetcher_type != None or l1Dprefetcher_type != None:
+ m5.fatal('Prefetching not supported yet')
+ l1i_pf = NULL
+ l1d_pf = NULL
+
+ # cache controllers
+ cpu.l1i = CHI_L1Controller(ruby_system, cpu.inst_sequencer,
+ l1i_cache, l1i_pf)
+
+ cpu.l1d = CHI_L1Controller(ruby_system, cpu.data_sequencer,
+ l1d_cache, l1d_pf)
+
+ cpu.inst_sequencer.dcache = NULL
+ cpu.data_sequencer.dcache = cpu.l1d.cache
+
+ cpu.l1d.sc_lock_enabled = True
+
+ cpu._ll_cntrls = [cpu.l1i, cpu.l1d]
+ for c in cpu._ll_cntrls:
+ self._cntrls.append(c)
+ self.connectController(c)
+ self._ll_cntrls.append(c)
+
+ def getSequencers(self):
+ return self._seqs
+
+ def getAllControllers(self):
+ return self._cntrls
+
+ def getNetworkSideControllers(self):
+ return self._cntrls
+
+ def setDownstream(self, cntrls):
+ for c in self._ll_cntrls:
+ c.downstream_destinations = cntrls
+
+ def getCpus(self):
+ return self._cpus
+
+ # Adds a private L2 for each cpu
+ def addPrivL2Cache(self, cache_type, pf_type=None):
+ self._ll_cntrls = []
+ for cpu in self._cpus:
+ l2_cache = cache_type(start_index_bit = self._block_size_bits,
+ is_icache = False)
+ if pf_type != None:
+ m5.fatal('Prefetching not supported yet')
+ l2_pf = NULL
+
+ cpu.l2 = CHI_L2Controller(self._ruby_system, l2_cache, l2_pf)
+
+ self._cntrls.append(cpu.l2)
+ self.connectController(cpu.l2)
+
+ self._ll_cntrls.append(cpu.l2)
+
+ for c in cpu._ll_cntrls:
+ c.downstream_destinations = [cpu.l2]
+ cpu._ll_cntrls = [cpu.l2]
+
+
+class CHI_HNF(CHI_Node):
+ '''
+ Encapsulates an HNF cache/directory controller.
+ Before the first controller is created, the class method
+ CHI_HNF.createAddrRanges must be called before creating any CHI_HNF object
+ to set-up the interleaved address ranges used by the HNFs
+ '''
+
+ _addr_ranges = []
+ @classmethod
+ def createAddrRanges(cls, sys_mem_ranges, cache_line_size, num_hnfs):
+ # Create the HNFs interleaved addr ranges
+ block_size_bits = int(math.log(cache_line_size, 2))
+ cls._addr_ranges = []
+ llc_bits = int(math.log(num_hnfs, 2))
+ numa_bit = block_size_bits + llc_bits - 1
+ for i in range(num_hnfs):
+ ranges = []
+ for r in sys_mem_ranges:
+ addr_range = AddrRange(r.start, size = r.size(),
+ intlvHighBit = numa_bit,
+ intlvBits = llc_bits,
+ intlvMatch = i)
+ ranges.append(addr_range)
+ cls._addr_ranges.append((ranges, numa_bit, i))
+
+ @classmethod
+ def getAddrRanges(cls, hnf_idx):
+ assert(len(cls._addr_ranges) != 0)
+ return cls._addr_ranges[hnf_idx]
+
+ # The CHI controller can be a child of this object or another if
+ # 'parent' if specified
+ def __init__(self, hnf_idx, ruby_system, llcache_type, parent):
+ super(CHI_HNF, self).__init__(ruby_system)
+
+ addr_ranges,intlvHighBit,intlvMatch = CHI_HNF.getAddrRanges(hnf_idx)
+ # All ranges should have the same interleaving
+ assert(len(addr_ranges) >= 1)
+ assert(intlvMatch == hnf_idx)
+
+ ll_cache = llcache_type(start_index_bit = intlvHighBit + 1)
+ self._cntrl = CHI_HNFController(ruby_system, ll_cache, NULL,
+ addr_ranges)
+
+ if parent == None:
+ self.cntrl = self._cntrl
+ else:
+ parent.cntrl = self._cntrl
+
+ self.connectController(self._cntrl)
+
+ def getAllControllers(self):
+ return [self._cntrl]
+
+ def getNetworkSideControllers(self):
+ return [self._cntrl]
+
+
+class CHI_SNF_Base(CHI_Node):
+ '''
+ Creates CHI node controllers for the memory controllers
+ '''
+
+ # The CHI controller can be a child of this object or another if
+ # 'parent' if specified
+ def __init__(self, ruby_system, parent):
+ super(CHI_SNF_Base, self).__init__(ruby_system)
+
+ self._cntrl = Memory_Controller(
+ version = Versions.getVersion(Memory_Controller),
+ ruby_system = ruby_system,
+ triggerQueue = TriggerMessageBuffer(),
+ responseFromMemory = MessageBuffer(),
+ requestToMemory = MessageBuffer(ordered = True),
+ reqRdy = TriggerMessageBuffer())
+
+ self.connectController(self._cntrl)
+
+ if parent:
+ parent.cntrl = self._cntrl
+ else:
+ self.cntrl = self._cntrl
+
+ def getAllControllers(self):
+ return [self._cntrl]
+
+ def getNetworkSideControllers(self):
+ return [self._cntrl]
+
+ def getMemRange(self, mem_ctrl):
+ # TODO need some kind of transparent API for
+ # MemCtrl+DRAM vs SimpleMemory
+ if hasattr(mem_ctrl, 'range'):
+ return mem_ctrl.range
+ else:
+ return mem_ctrl.dram.range
+
+class CHI_SNF_BootMem(CHI_SNF_Base):
+ '''
+ Create the SNF for the boot memory
+ '''
+ def __init__(self, ruby_system, parent, bootmem):
+ super(CHI_SNF_BootMem, self).__init__(ruby_system, parent)
+ self._cntrl.memory_out_port = bootmem.port
+ self._cntrl.addr_ranges = self.getMemRange(bootmem)
+
+class CHI_SNF_MainMem(CHI_SNF_Base):
+ '''
+ Create the SNF for a list main memory controllers
+ '''
+ def __init__(self, ruby_system, parent, mem_ctrl = None):
+ super(CHI_SNF_MainMem, self).__init__(ruby_system, parent)
+ if mem_ctrl:
+ self._cntrl.memory_out_port = mem_ctrl.port
+ self._cntrl.addr_ranges = self.getMemRange(mem_ctrl)
+ # else bind ports and range later
+
+class CHI_RNI_Base(CHI_Node):
+ '''
+ Request node without cache / DMA
+ '''
+
+ # The CHI controller can be a child of this object or another if
+ # 'parent' if specified
+ def __init__(self, ruby_system, parent):
+ super(CHI_RNI_Base, self).__init__(ruby_system)
+
+ self._sequencer = RubySequencer(version = Versions.getSeqId(),
+ ruby_system = ruby_system,
+ clk_domain = ruby_system.clk_domain)
+ self._cntrl = CHI_DMAController(ruby_system, self._sequencer)
+
+ if parent:
+ parent.cntrl = self._cntrl
+ else:
+ self.cntrl = self._cntrl
+
+ self.connectController(self._cntrl)
+
+ def getAllControllers(self):
+ return [self._cntrl]
+
+ def getNetworkSideControllers(self):
+ return [self._cntrl]
+
+class CHI_RNI_DMA(CHI_RNI_Base):
+ '''
+ DMA controller wiredup to a given dma port
+ '''
+ def __init__(self, ruby_system, dma_port, parent):
+ super(CHI_RNI_DMA, self).__init__(ruby_system, parent)
+ assert(dma_port != None)
+ self._sequencer.in_ports = dma_port
+
+class CHI_RNI_IO(CHI_RNI_Base):
+ '''
+ DMA controller wiredup to ruby_system IO port
+ '''
+ def __init__(self, ruby_system, parent):
+ super(CHI_RNI_IO, self).__init__(ruby_system, parent)
+ ruby_system._io_port = self._sequencer
+
+def noc_params_from_config(config, noc_params):
+ # mesh options
+ noc_params.num_rows = config['mesh']['num_rows']
+ noc_params.num_cols = config['mesh']['num_cols']
+ if 'router_latency' in config['mesh']:
+ noc_params.router_latency = config['mesh']['router_latency']
+ if 'link_latency' in config['mesh']:
+ noc_params.router_link_latency = config['mesh']['link_latency']
+ noc_params.node_link_latency = config['mesh']['link_latency']
+ if 'router_link_latency' in config['mesh']:
+ noc_params.router_link_latency = config['mesh']['router_link_latency']
+ if 'node_link_latency' in config['mesh']:
+ noc_params.node_link_latency = config['mesh']['node_link_latency']
+ if 'cross_links' in config['mesh']:
+ noc_params.cross_link_latency = \
+ config['mesh']['cross_link_latency']
+ noc_params.cross_links = []
+ for x, y in config['mesh']['cross_links']:
+ noc_params.cross_links.append((x, y))
+ noc_params.cross_links.append((y, x))
+ else:
+ noc_params.cross_links = []
+ noc_params.cross_link_latency = 0
+
+ # CHI_RNF options
+ noc_params.CHI_RNF = config['CHI_RNF']
+
+ # CHI_RNI_IO
+ noc_params.CHI_RNI_IO = config['CHI_RNI_IO']
+
+ # CHI_HNF options
+ noc_params.CHI_HNF = config['CHI_HNF']
+ if 'pairing' in config['CHI_HNF']:
+ noc_params.pairing = config['CHI_HNF']['pairing']
+
+ # CHI_SNF_MainMem
+ noc_params.CHI_SNF_MainMem = config['CHI_SNF_MainMem']
+
+ # CHI_SNF_IO (applies to CHI_SNF_Bootmem)
+ noc_params.CHI_SNF_IO = config['CHI_SNF_IO']
+
+
+def create_system(options, full_system, system, dma_ports, bootmem,
+ ruby_system):
+
+ if buildEnv['PROTOCOL'] != 'CHI':
+ m5.panic("This script requires the CHI build")
+
+ if options.num_dirs < 1:
+ m5.fatal('--num-dirs must be at least 1')
+
+ if options.num_l3caches < 1:
+ m5.fatal('--num-l3caches must be at least 1')
+
+ # Default parameters for the network
+ class NoC_Params(object):
+ def __init__(self):
+ self.topology = options.topology
+ self.network = options.network
+ self.router_link_latency = 1
+ self.node_link_latency = 1
+ self.router_latency = 1
+ self.router_buffer_size = 4
+ self.cntrl_msg_size = 8
+ self.data_width = 32
+ params = NoC_Params()
+
+ # read additional configurations from yaml file if provided
+ if options.noc_config:
+ with open(options.noc_config, 'r') as file:
+ noc_params_from_config(yaml.load(file), params)
+ elif params.topology == 'CustomMesh':
+ m5.fatal('--noc-config must be provided if topology is CustomMesh')
+
+ # Declare caches and controller types used by the protocol
+ # Notice tag and data accesses are not concurrent, so the a cache hit
+ # latency = tag + data + response latencies.
+ # Default response latencies are 1 cy for all controllers.
+ # For L1 controllers the mandatoryQueue enqueue latency is always 1 cy and
+ # this is deducted from the initial tag read latency for sequencer requests
+ # dataAccessLatency may be set to 0 if one wants to consider parallel
+ # data and tag lookups
+ class L1ICache(RubyCache):
+ dataAccessLatency = 1
+ tagAccessLatency = 1
+ size = options.l1i_size
+ assoc = options.l1i_assoc
+
+ class L1DCache(RubyCache):
+ dataAccessLatency = 2
+ tagAccessLatency = 1
+ size = options.l1d_size
+ assoc = options.l1d_assoc
+
+ class L2Cache(RubyCache):
+ dataAccessLatency = 6
+ tagAccessLatency = 2
+ size = options.l2_size
+ assoc = options.l2_assoc
+
+ class HNFCache(RubyCache):
+ dataAccessLatency = 10
+ tagAccessLatency = 2
+ size = options.l3_size
+ assoc = options.l3_assoc
+
+ # other functions use system.cache_line_size assuming it has been set
+ assert(system.cache_line_size.value == options.cacheline_size)
+
+ cpu_sequencers = []
+ mem_cntrls = []
+ mem_dests = []
+ network_nodes = []
+ network_cntrls = []
+ hnf_dests = []
+ all_cntrls = []
+
+ # Creates on RNF per cpu with priv l2 caches
+ assert(len(system.cpu) == options.num_cpus)
+ ruby_system.rnf = [ CHI_RNF([cpu], ruby_system, L1ICache, L1DCache,
+ system.cache_line_size.value)
+ for cpu in system.cpu ]
+ for rnf in ruby_system.rnf:
+ rnf.addPrivL2Cache(L2Cache)
+ cpu_sequencers.extend(rnf.getSequencers())
+ all_cntrls.extend(rnf.getAllControllers())
+ network_nodes.append(rnf)
+ network_cntrls.extend(rnf.getNetworkSideControllers())
+
+ # Look for other memories
+ other_memories = []
+ if bootmem:
+ other_memories.append(bootmem)
+ if getattr(system, 'sram', None):
+ other_memories.append(getattr(system, 'sram', None))
+ on_chip_mem_ports = getattr(system, '_on_chip_mem_ports', None)
+ if on_chip_mem_ports:
+ other_memories.extend([p.simobj for p in on_chip_mem_ports])
+
+ # Create the LLCs cntrls
+ sysranges = [] + system.mem_ranges
+
+ for m in other_memories:
+ sysranges.append(m.range)
+
+ CHI_HNF.createAddrRanges(sysranges, system.cache_line_size.value,
+ options.num_l3caches)
+ ruby_system.hnf = [ CHI_HNF(i, ruby_system, HNFCache, None)
+ for i in range(options.num_l3caches) ]
+
+ for hnf in ruby_system.hnf:
+ network_nodes.append(hnf)
+ network_cntrls.extend(hnf.getNetworkSideControllers())
+ assert(hnf.getAllControllers() == hnf.getNetworkSideControllers())
+ all_cntrls.extend(hnf.getAllControllers())
+ hnf_dests.extend(hnf.getAllControllers())
+
+ # Create the memory controllers
+ # Notice we don't define a Directory_Controller type so we don't use
+ # create_directories shared by other protocols.
+
+ ruby_system.snf = [ CHI_SNF_MainMem(ruby_system, None, None)
+ for i in range(options.num_dirs) ]
+ for snf in ruby_system.snf:
+ network_nodes.append(snf)
+ network_cntrls.extend(snf.getNetworkSideControllers())
+ assert(snf.getAllControllers() == snf.getNetworkSideControllers())
+ mem_cntrls.extend(snf.getAllControllers())
+ all_cntrls.extend(snf.getAllControllers())
+ mem_dests.extend(snf.getAllControllers())
+
+ if len(other_memories) > 0:
+ ruby_system.rom_snf = [ CHI_SNF_BootMem(ruby_system, None, m)
+ for m in other_memories ]
+ for snf in ruby_system.rom_snf:
+ network_nodes.append(snf)
+ network_cntrls.extend(snf.getNetworkSideControllers())
+ all_cntrls.extend(snf.getAllControllers())
+ mem_dests.extend(snf.getAllControllers())
+
+
+ # Creates the controller for dma ports and io
+
+ if len(dma_ports) > 0:
+ ruby_system.dma_rni = [ CHI_RNI_DMA(ruby_system, dma_port, None)
+ for dma_port in dma_ports ]
+ for rni in ruby_system.dma_rni:
+ network_nodes.append(rni)
+ network_cntrls.extend(rni.getNetworkSideControllers())
+ all_cntrls.extend(rni.getAllControllers())
+
+ if full_system:
+ ruby_system.io_rni = CHI_RNI_IO(ruby_system, None)
+ network_nodes.append(ruby_system.io_rni)
+ network_cntrls.extend(ruby_system.io_rni.getNetworkSideControllers())
+ all_cntrls.extend(ruby_system.io_rni.getAllControllers())
+
+
+ # Assign downstream destinations
+ for rnf in ruby_system.rnf:
+ rnf.setDownstream(hnf_dests)
+ if len(dma_ports) > 0:
+ for rni in ruby_system.dma_rni:
+ rni.setDownstream(hnf_dests)
+ if full_system:
+ ruby_system.io_rni.setDownstream(hnf_dests)
+ for hnf in ruby_system.hnf:
+ hnf.setDownstream(mem_dests)
+
+ # Setup data message size for all controllers
+ for cntrl in all_cntrls:
+ cntrl.data_channel_size = params.data_width
+
+ # Network configurations
+ # virtual networks: 0=request, 1=snoop, 2=response, 3=data
+ ruby_system.network.number_of_virtual_networks = 4
+
+ ruby_system.network.control_msg_size = params.cntrl_msg_size
+ ruby_system.network.data_msg_size = params.data_width
+ ruby_system.network.buffer_size = params.router_buffer_size
+
+ if params.topology == 'CustomMesh':
+ topology = create_topology(network_nodes, params)
+ elif params.topology in ['Crossbar', 'Pt2Pt']:
+ topology = create_topology(network_cntrls, params)
+ else:
+ m5.fatal("%s not supported!" % params.topology)
+
+ # Incorporate the params into options so it's propagated to
+ # makeTopology by the parent script
+ for k in dir(params):
+ if not k.startswith('__'):
+ setattr(options, k, getattr(params, k))
+
+ return (cpu_sequencers, mem_cntrls, topology)
diff --git a/configs/topologies/CustomMesh.py b/configs/topologies/CustomMesh.py
new file mode 100644
index 0000000..73793e4
--- /dev/null
+++ b/configs/topologies/CustomMesh.py
@@ -0,0 +1,444 @@
+# Copyright (c) 2021 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.
+#
+# 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.
+#
+
+import math
+
+from m5.util import fatal
+from m5.params import *
+from m5.objects import *
+
+from m5.defines import buildEnv
+if buildEnv['PROTOCOL'] == 'CHI':
+ import ruby.CHI as CHI
+
+from topologies.BaseTopology import SimpleTopology
+
+class CustomMesh(SimpleTopology):
+ description = 'CustomMesh'
+
+ def __init__(self, controllers):
+ self.nodes = controllers
+
+ #--------------------------------------------------------------------------
+ # _makeMesh
+ #--------------------------------------------------------------------------
+
+ def _makeMesh(self, IntLink, link_latency, num_rows, num_columns,
+ cross_links, cross_link_latency):
+
+ # East->West, West->East, North->South, South->North
+ # XY routing weights
+ link_weights = [1, 1, 2, 2]
+
+ # East output to West input links
+ for row in range(num_rows):
+ for col in range(num_columns):
+ if (col + 1 < num_columns):
+ east_out = col + (row * num_columns)
+ west_in = (col + 1) + (row * num_columns)
+ llat = cross_link_latency \
+ if (east_out, west_in) in cross_links \
+ else link_latency
+ self._int_links.append(\
+ IntLink(link_id=self._link_count,
+ src_node=self._routers[east_out],
+ dst_node=self._routers[west_in],
+ dst_inport="West",
+ latency = llat,
+ weight=link_weights[0]))
+ self._link_count += 1
+
+ # West output to East input links
+ for row in range(num_rows):
+ for col in range(num_columns):
+ if (col + 1 < num_columns):
+ east_in = col + (row * num_columns)
+ west_out = (col + 1) + (row * num_columns)
+ llat = cross_link_latency \
+ if (west_out, east_in) in cross_links \
+ else link_latency
+ self._int_links.append(\
+ IntLink(link_id=self._link_count,
+ src_node=self._routers[west_out],
+ dst_node=self._routers[east_in],
+ dst_inport="East",
+ latency = llat,
+ weight=link_weights[1]))
+ self._link_count += 1
+
+ # North output to South input links
+ for col in range(num_columns):
+ for row in range(num_rows):
+ if (row + 1 < num_rows):
+ north_out = col + (row * num_columns)
+ south_in = col + ((row + 1) * num_columns)
+ llat = cross_link_latency \
+ if (north_out, south_in) in cross_links \
+ else link_latency
+ self._int_links.append(\
+ IntLink(link_id=self._link_count,
+ src_node=self._routers[north_out],
+ dst_node=self._routers[south_in],
+ dst_inport="South",
+ latency = llat,
+ weight=link_weights[2]))
+ self._link_count += 1
+
+ # South output to North input links
+ for col in range(num_columns):
+ for row in range(num_rows):
+ if (row + 1 < num_rows):
+ north_in = col + (row * num_columns)
+ south_out = col + ((row + 1) * num_columns)
+ llat = cross_link_latency \
+ if (south_out, north_in) in cross_links \
+ else link_latency
+ self._int_links.append(\
+ IntLink(link_id=self._link_count,
+ src_node=self._routers[south_out],
+ dst_node=self._routers[north_in],
+ dst_inport="North",
+ latency = llat,
+ weight=link_weights[3]))
+ self._link_count += 1
+
+ #--------------------------------------------------------------------------
+ # distributeNodes
+ #--------------------------------------------------------------------------
+
+ def _createRNFRouter(self, mesh_router):
+ # Create a zero-latency router bridging node controllers
+ # and the mesh router
+ node_router = self._Router(router_id = len(self._routers),
+ latency = 0)
+ self._routers.append(node_router)
+
+ # connect node_router <-> mesh router
+ self._int_links.append(self._IntLink( \
+ link_id = self._link_count,
+ src_node = node_router,
+ dst_node = mesh_router,
+ latency = self._router_link_latency))
+ self._link_count += 1
+
+ self._int_links.append(self._IntLink( \
+ link_id = self._link_count,
+ src_node = mesh_router,
+ dst_node = node_router,
+ latency = self._router_link_latency))
+ self._link_count += 1
+
+ return node_router
+
+ def distributeNodes(self, num_nodes_per_router, router_idx_list,
+ node_list):
+
+ if num_nodes_per_router:
+ # evenly distribute nodes to all listed routers
+ assert(len(router_idx_list)*num_nodes_per_router == len(node_list))
+
+ for idx, node in enumerate(node_list):
+ mesh_router_idx = router_idx_list[idx // num_nodes_per_router]
+ router = self._routers[mesh_router_idx]
+
+ # Create another router bridging RNF node controllers
+ # and the mesh router
+ # for non-RNF nodes, node router is mesh router
+ if isinstance(node, CHI.CHI_RNF):
+ router = self._createRNFRouter(router)
+
+ # connect all ctrls in the node to node_router
+ ctrls = node.getNetworkSideControllers()
+ for c in ctrls:
+ self._ext_links.append(self._ExtLink(
+ link_id = self._link_count,
+ ext_node = c,
+ int_node = router,
+ latency = self._node_link_latency))
+ self._link_count += 1
+ else:
+ # try to circulate all nodes to all routers, some routers may be
+ # connected to zero or more than one node.
+ idx = 0
+ for node in node_list:
+ ridx = router_idx_list[idx]
+ router = self._routers[ridx]
+
+ if isinstance(node, CHI.CHI_RNF):
+ router = self._createRNFRouter(router)
+ ctrls = node.getNetworkSideControllers()
+ for c in ctrls:
+ self._ext_links.append(self._ExtLink( \
+ link_id = self._link_count,
+ ext_node = c,
+ int_node = router,
+ latency = self._node_link_latency))
+ self._link_count += 1
+ idx = (idx + 1) % len(router_idx_list)
+
+ #--------------------------------------------------------------------------
+ # makeTopology
+ #--------------------------------------------------------------------------
+
+ def makeTopology(self, options, network, IntLink, ExtLink, Router):
+ assert(buildEnv['PROTOCOL'] == 'CHI')
+
+ num_rows = options.num_rows
+ num_cols = options.num_cols
+ num_mesh_routers = num_rows * num_cols
+
+ self._IntLink = IntLink
+ self._ExtLink = ExtLink
+ self._Router = Router
+
+ if hasattr(options, 'router_link_latency'):
+ self._router_link_latency = options.router_link_latency
+ self._node_link_latency = options.node_link_latency
+ else:
+ print("WARNING: router/node link latencies not provided")
+ self._router_link_latency = options.link_latency
+ self._node_link_latency = options.link_latency
+
+ # classify nodes into different types
+ rnf_list = []
+ hnf_list = []
+ mem_ctrls = []
+ io_mem_ctrls = []
+ io_rni_ctrls = []
+
+ for n in self.nodes:
+ if isinstance(n, CHI.CHI_RNF):
+ rnf_list.append(n)
+ elif isinstance(n, CHI.CHI_HNF):
+ hnf_list.append(n)
+ elif isinstance(n, CHI.CHI_SNF_MainMem):
+ mem_ctrls.append(n)
+ elif isinstance(n, CHI.CHI_SNF_BootMem):
+ io_mem_ctrls.append(n)
+ elif isinstance(n, CHI.CHI_RNI_DMA):
+ io_rni_ctrls.append(n)
+ elif isinstance(n, CHI.CHI_RNI_IO):
+ io_rni_ctrls.append(n)
+ else:
+ fatal('topologies.CustomMesh: {} not supported'
+ .format(n.__class__.__name__))
+
+ # Create all mesh routers
+ self._routers = [Router(router_id=i, latency = options.router_latency)\
+ for i in range(num_mesh_routers)]
+
+ self._link_count = 0
+ self._int_links = []
+ self._ext_links = []
+
+ # Create all the mesh internal links.
+ self._makeMesh(IntLink, self._router_link_latency, num_rows, num_cols,
+ options.cross_links, options.cross_link_latency)
+
+ # Place CHI_RNF on the mesh
+ num_nodes_per_router = options.CHI_RNF['num_nodes_per_router'] \
+ if 'num_nodes_per_router' in options.CHI_RNF else None
+ self.distributeNodes(num_nodes_per_router,
+ options.CHI_RNF['router_list'],
+ rnf_list)
+
+ # Place CHI_HNF on the mesh
+ num_nodes_per_router = options.CHI_HNF['num_nodes_per_router'] \
+ if 'num_nodes_per_router' in options.CHI_HNF else None
+ self.distributeNodes(num_nodes_per_router,
+ options.CHI_HNF['router_list'],
+ hnf_list)
+
+ # Place CHI_SNF_MainMem on the mesh
+ num_nodes_per_router = options.CHI_SNF_MainMem['num_nodes_per_router']\
+ if 'num_nodes_per_router' in options.CHI_SNF_MainMem else None
+ self.distributeNodes(num_nodes_per_router,
+ options.CHI_SNF_MainMem['router_list'],
+ mem_ctrls)
+
+ # Place all IO mem nodes on the mesh
+ num_nodes_per_router = options.CHI_SNF_IO['num_nodes_per_router'] \
+ if 'num_nodes_per_router' in options.CHI_SNF_IO else None
+ self.distributeNodes(num_nodes_per_router,
+ options.CHI_SNF_IO['router_list'],
+ io_mem_ctrls)
+
+ # Place all IO request nodes on the mesh
+ num_nodes_per_router = options.CHI_RNI_IO['num_nodes_per_router'] \
+ if 'num_nodes_per_router' in options.CHI_RNI_IO else None
+ self.distributeNodes(num_nodes_per_router,
+ options.CHI_RNI_IO['router_list'],
+ io_rni_ctrls)
+
+ # Set up
+ network.int_links = self._int_links
+ network.ext_links = self._ext_links
+ network.routers = self._routers
+
+ pairing = getattr(options, 'pairing', None)
+ if pairing != None:
+ self._autoPairHNFandSNF(hnf_list, mem_ctrls, pairing)
+
+ #--------------------------------------------------------------------------
+ # _autoPair
+ #--------------------------------------------------------------------------
+ def _autoPairHNFandSNF(self, cache_ctrls, mem_ctrls, pairing):
+ # Use the pairing defined by the configuration to reassign the
+ # memory ranges
+ pair_debug = False
+
+ print("Pairing HNFs to SNFs")
+ print(pairing)
+
+ all_cache = []
+ for c in cache_ctrls: all_cache.extend(c.getNetworkSideControllers())
+ all_mem = []
+ for c in mem_ctrls: all_mem.extend(c.getNetworkSideControllers())
+
+ # checks and maps index from pairing map to component
+ assert(len(pairing) == len(all_cache))
+
+ def _tolist(val): return val if isinstance(val, list) else [val]
+
+ for m in all_mem: m._pairing = []
+
+ pairing_check = max(1, len(all_mem) / len(all_cache))
+ for cidx,c in enumerate(all_cache):
+ c._pairing = []
+ for midx in _tolist(pairing[cidx]):
+ c._pairing.append(all_mem[midx])
+ if c not in all_mem[midx]._pairing:
+ all_mem[midx]._pairing.append(c)
+ assert(len(c._pairing) == pairing_check)
+ if pair_debug:
+ print(c.path())
+ for r in c.addr_ranges:
+ print("%s" % r)
+ for p in c._pairing:
+ print("\t"+p.path())
+ for r in p.addr_ranges:
+ print("\t%s" % r)
+
+ # all must be paired
+ for c in all_cache: assert(len(c._pairing) > 0)
+ for m in all_mem: assert(len(m._pairing) > 0)
+
+ # only support a single range for the main memory controllers
+ tgt_range_start = all_mem[0].addr_ranges[0].start.value
+ for mem in all_mem:
+ for r in mem.addr_ranges:
+ if r.start.value != tgt_range_start:
+ fatal('topologies.CustomMesh: not supporting pairing of '\
+ 'main memory with multiple ranges')
+
+ # reassign ranges for a 1 -> N paring
+ def _rerange(src_cntrls, tgt_cntrls, fix_tgt_peer):
+ assert(len(tgt_cntrls) >= len(src_cntrls))
+
+ def _rangeToBit(addr_ranges):
+ bit = None
+ for r in addr_ranges:
+ if bit == None:
+ bit = r.intlvMatch
+ else:
+ assert(bit == r.intlvMatch)
+ return bit
+
+ def _getPeer(cntrl):
+ return cntrl.memory_out_port.peer.simobj
+
+ sorted_src = list(src_cntrls)
+ sorted_src.sort(key = lambda x: _rangeToBit(x.addr_ranges))
+
+ # paired controllers need to have seq. interleaving match values
+ intlvMatch = 0
+ for src in sorted_src:
+ for tgt in src._pairing:
+ for r in tgt.addr_ranges:
+ r.intlvMatch = intlvMatch
+ if fix_tgt_peer:
+ _getPeer(tgt).range.intlvMatch = intlvMatch
+ intlvMatch = intlvMatch + 1
+
+ # recreate masks
+ for src in sorted_src:
+ for src_range in src.addr_ranges:
+ if src_range.start.value != tgt_range_start:
+ continue
+ new_src_mask = []
+ for m in src_range.masks:
+ # TODO should mask all the way to the max range size
+ new_src_mask.append(m | (m*2) | (m*4) |
+ (m*8) | (m*16))
+ for tgt in src._pairing:
+ paired = False
+ for tgt_range in tgt.addr_ranges:
+ if tgt_range.start.value == \
+ src_range.start.value:
+ src_range.masks = new_src_mask
+ new_tgt_mask = []
+ lsbs = len(tgt_range.masks) - \
+ len(new_src_mask)
+ for i in range(lsbs):
+ new_tgt_mask.append(tgt_range.masks[i])
+ for m in new_src_mask:
+ new_tgt_mask.append(m)
+ tgt_range.masks = new_tgt_mask
+ if fix_tgt_peer:
+ _getPeer(tgt).range.masks = new_tgt_mask
+ paired = True
+ if not paired:
+ fatal('topologies.CustomMesh: could not ' \
+ 'reassign ranges {} {}'.format(
+ src.path(), tgt.path()))
+ if len(all_mem) >= len(all_cache):
+ _rerange(all_cache, all_mem, True)
+ else:
+ _rerange(all_mem, all_cache, False)
+
+ if pair_debug:
+ print("")
+ for cidx,c in enumerate(all_cache):
+ assert(len(c._pairing) == pairing_check)
+ print(c.path())
+ for r in c.addr_ranges:
+ print("%s" % r)
+ for p in c._pairing:
+ print("\t"+p.path())
+ for r in p.addr_ranges:
+ print("\t%s" % r)
+
+
diff --git a/src/mem/ruby/SConscript b/src/mem/ruby/SConscript
index bde71c0..c3f8365 100644
--- a/src/mem/ruby/SConscript
+++ b/src/mem/ruby/SConscript
@@ -114,9 +114,11 @@
MakeInclude('common/Address.hh')
MakeInclude('common/BoolVec.hh')
MakeInclude('common/DataBlock.hh')
+MakeInclude('common/ExpectedMap.hh')
MakeInclude('common/IntVec.hh')
MakeInclude('common/MachineID.hh')
MakeInclude('common/NetDest.hh')
+MakeInclude('common/TriggerQueue.hh')
MakeInclude('common/Set.hh')
MakeInclude('common/WriteMask.hh')
MakeInclude('network/MessageBuffer.hh')
diff --git a/src/mem/ruby/common/ExpectedMap.hh b/src/mem/ruby/common/ExpectedMap.hh
new file mode 100644
index 0000000..a1889b7
--- /dev/null
+++ b/src/mem/ruby/common/ExpectedMap.hh
@@ -0,0 +1,232 @@
+/*
+ * Copyright (c) 2021 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.
+ *
+ * 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.
+ */
+
+#ifndef __MEM_RUBY_COMMON_EXPECTEDMAP_HH__
+#define __MEM_RUBY_COMMON_EXPECTEDMAP_HH__
+
+#include <cassert>
+#include <iostream>
+#include <unordered_map>
+
+// ExpectedMap helper class is used to facilitate tracking of pending
+// response and data messages in the CHI protocol. It offers additional
+// functionality when compared to plain counters:
+// - tracks the expected type for received messages
+// - tracks segmented data messages (i.e. when a line transfer is split in
+// multiple messages)
+
+template<typename RespType, typename DataType>
+class ExpectedMap
+{
+ private:
+
+ template<typename Type>
+ struct ExpectedState
+ {
+ struct EnumClassHash
+ {
+ std::size_t operator()(Type t) const
+ {
+ return static_cast<std::size_t>(t);
+ }
+ };
+
+ private:
+ // chunks is the number segmented messages we expect to receive
+ // before incrementing numReceived. This is tipically always 1 for all
+ // non-data messages
+ int chunks;
+ int currChunk;
+ int numReceived;
+ std::unordered_map<Type, bool, EnumClassHash> expectedTypes;
+
+ public:
+ ExpectedState()
+ :chunks(1), currChunk(0), numReceived(0)
+ {}
+
+ void
+ clear(int msg_chunks)
+ {
+ chunks = msg_chunks;
+ currChunk = 0;
+ numReceived = 0;
+ expectedTypes.clear();
+ }
+
+ void
+ addExpectedType(const Type &val)
+ {
+ expectedTypes[val] = false;
+ }
+
+ int received() const { return numReceived; }
+
+ bool
+ increaseReceived(const Type &val)
+ {
+ if (expectedTypes.find(val) == expectedTypes.end())
+ return false;
+
+ expectedTypes[val] = true;
+ ++currChunk;
+ if (currChunk == chunks) {
+ ++numReceived;
+ currChunk = 0;
+ }
+
+ return true;
+ }
+
+ bool
+ receivedType(const Type &val) const
+ {
+ auto i = expectedTypes.find(val);
+ if (i != expectedTypes.end())
+ return i->second;
+ else
+ return false;
+ }
+ };
+
+ ExpectedState<DataType> expectedData;
+ ExpectedState<RespType> expectedResp;
+ int totalExpected;
+
+ public:
+ ExpectedMap()
+ :expectedData(), expectedResp(), totalExpected(0)
+ {}
+
+ // Clear the tracking state and specified the number of chunks are required
+ // to receive a complete data message
+ void
+ clear(int dataChunks)
+ {
+ expectedData.clear(dataChunks);
+ expectedResp.clear(1);
+ totalExpected = 0;
+ }
+
+ // Register an expected response message type
+ void
+ addExpectedRespType(const RespType &val)
+ {
+ expectedResp.addExpectedType(val);
+ }
+
+ // Register an expected data message type
+ void
+ addExpectedDataType(const DataType &val)
+ {
+ expectedData.addExpectedType(val);
+ }
+
+ // Set the number of expected messages
+ void setExpectedCount(int val) { totalExpected = val; }
+
+ void addExpectedCount(int val) { totalExpected += val; }
+
+ // Returns the number of messages received.
+ // Notice that a data message counts as received only after all of
+ // its chunks are received.
+ int
+ received() const
+ {
+ return expectedData.received() + expectedResp.received();
+ }
+
+ // Returns the remaining number of expected messages
+ int expected() const { return totalExpected - received(); }
+
+ // Has any expected message ?
+ bool hasExpected() const { return expected() != 0; }
+
+ // Has received any data ?
+ bool hasReceivedData() const { return expectedData.received() != 0; }
+
+ // Has received any response ?
+ bool hasReceivedResp() const { return expectedResp.received() != 0; }
+
+
+ // Notifies that a response message was received
+ bool
+ receiveResp(const RespType &val)
+ {
+ assert(received() < totalExpected);
+ return expectedResp.increaseReceived(val);
+ }
+
+ // Notifies that a data message chunk was received
+ bool
+ receiveData(const DataType &val)
+ {
+ assert(received() <= totalExpected);
+ return expectedData.increaseReceived(val);
+ }
+
+ // Has received any data of the given type ?
+ bool
+ receivedDataType(const DataType &val) const
+ {
+ return expectedData.receivedType(val);
+ }
+
+ // Has received any response of the given type ?
+ bool
+ receivedRespType(const RespType &val) const
+ {
+ return expectedResp.receivedType(val);
+ }
+
+ void
+ print(std::ostream& out) const
+ {
+ out << expected();
+ }
+};
+
+template<typename RespType, typename DataType>
+inline std::ostream&
+operator<<(std::ostream& out, const ExpectedMap<RespType,DataType>& obj)
+{
+ obj.print(out);
+ return out;
+}
+
+
+#endif // __MEM_RUBY_COMMON_EXPECTEDMAP_HH__
diff --git a/src/mem/ruby/common/TriggerQueue.hh b/src/mem/ruby/common/TriggerQueue.hh
new file mode 100644
index 0000000..2775d7a
--- /dev/null
+++ b/src/mem/ruby/common/TriggerQueue.hh
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2021 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.
+ *
+ * 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.
+ */
+
+#ifndef __MEM_RUBY_COMMON_QUEUE_HH__
+#define __MEM_RUBY_COMMON_QUEUE_HH__
+
+#include <deque>
+#include <iostream>
+
+// TriggerQueue helper class is used keep a list of events that trigger the
+// actions that need to be executed before an ouststanding transaction
+// completes in the CHI protocol. When a transaction no longer has pending
+// respose or data messages, this queue is checked and the event at the head
+// of the queue is triggered. If the queue is empty, the transactions is
+// finalized. Events can be marked as NB (non-blocking). NB are triggered by
+// the protocol even if the transactions has pending data/responses.
+
+template<typename T>
+class TriggerQueue
+{
+ private:
+ struct ValType {
+ T val;
+ bool non_blocking;
+ };
+ std::deque<ValType> queue;
+
+ public:
+ // Returns the head of the queue
+ const T& front() const { return queue.front().val; }
+
+ // Returns the head of the queue
+ // NOTE: SLICC won't allow to reuse front() or different
+ // values of the template parameter, thus we use an additional
+ // def. to workaround that
+ const T& next() const { return queue.front().val; }
+
+ // Returns the end of the queue
+ const T& back() const { return queue.back().val; }
+
+ // Is the head event non-blocking ?
+ bool frontNB() const { return queue.front().non_blocking; }
+
+ // Is the last event non-blocking ?
+ bool backNB() const { return queue.back().non_blocking; }
+
+ // Is the queue empty ?
+ bool empty() const { return queue.empty(); }
+
+ // put an event at the end of the queue
+ void push(const T &elem) { queue.push_back({elem,false}); }
+
+ // emplace an event at the end of the queue
+ template<typename... Ts>
+ void
+ emplace(Ts&&... args)
+ {
+ queue.push_back({T(std::forward<Ts>(args)...),false});
+ }
+
+ // put an event at the head of the queue
+ void pushFront(const T &elem) { queue.push_front({elem,false}); }
+
+ // put a non-blocking event at the end of the queue
+ void pushNB(const T &elem) { queue.push_back({elem,true}); }
+
+ // put a non-blocking event at the head of the queue
+ void pushFrontNB(const T &elem) { queue.push_front({elem,true}); }
+
+ // pop the head of the queue
+ void pop() { queue.pop_front(); }
+
+ void print(std::ostream& out) const;
+};
+
+template<class T>
+inline std::ostream&
+operator<<(std::ostream& out, const TriggerQueue<T>& obj)
+{
+ obj.print(out);
+ out << std::flush;
+ return out;
+}
+
+template<class T>
+inline void
+TriggerQueue<T>::print(std::ostream& out) const
+{
+}
+
+#endif // __MEM_RUBY_COMMON_QUEUE_HH__
diff --git a/src/mem/ruby/protocol/RubySlicc_Exports.sm b/src/mem/ruby/protocol/RubySlicc_Exports.sm
index c2f2c9d..7706f57 100644
--- a/src/mem/ruby/protocol/RubySlicc_Exports.sm
+++ b/src/mem/ruby/protocol/RubySlicc_Exports.sm
@@ -262,7 +262,9 @@
TCCdir, desc="Directory at the GPU L2 Cache (TCC)";
SQC, desc="GPU L1 Instr Cache (Sequencer Cache)";
RegionDir, desc="Region-granular directory";
- RegionBuffer,desc="Region buffer for CPU and GPU";
+ RegionBuffer, desc="Region buffer for CPU and GPU";
+ Cache, desc="Generic coherent cache controller";
+ Memory, desc="Memory controller interface";
NULL, desc="null mach type";
}
diff --git a/src/mem/ruby/protocol/chi/CHI-cache-actions.sm b/src/mem/ruby/protocol/chi/CHI-cache-actions.sm
new file mode 100644
index 0000000..ea5eaff
--- /dev/null
+++ b/src/mem/ruby/protocol/chi/CHI-cache-actions.sm
@@ -0,0 +1,3057 @@
+/*
+ * Copyright (c) 2021 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.
+ *
+ * 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.
+ */
+
+
+////////////////////////////////////////////////////////////////////////////
+// CHI-cache actions definitions
+////////////////////////////////////////////////////////////////////////////
+
+action(AllocateTBE_Request, desc="") {
+ if (storTBEs.areNSlotsAvailable(1)) {
+ // reserve a slot for this request
+ storTBEs.incrementReserved();
+
+ // Move request to rdy queue
+ peek(reqInPort, CHIRequestMsg) {
+ enqueue(reqRdyOutPort, CHIRequestMsg, allocation_latency) {
+ assert(in_msg.addr == address);
+ assert(in_msg.is_local_pf == false);
+ out_msg := in_msg;
+ }
+ }
+
+ } else {
+ // we don't have resources to track this request; enqueue a retry
+ peek(reqInPort, CHIRequestMsg) {
+ assert(in_msg.allowRetry);
+ enqueue(retryTriggerOutPort, RetryTriggerMsg, 0) {
+ out_msg.addr := in_msg.addr;
+ out_msg.event := Event:SendRetryAck;
+ out_msg.retryDest := in_msg.requestor;
+ retryQueue.emplace(in_msg.addr,in_msg.requestor);
+ }
+ }
+ }
+
+ reqInPort.dequeue(clockEdge());
+}
+
+action(AllocateTBE_Request_WithCredit, desc="") {
+ // TBE slot already reserved
+ // Move request to rdy queue
+ peek(reqInPort, CHIRequestMsg) {
+ assert(in_msg.allowRetry == false);
+ enqueue(reqRdyOutPort, CHIRequestMsg, allocation_latency) {
+ assert(in_msg.addr == address);
+ out_msg := in_msg;
+ }
+ }
+ reqInPort.dequeue(clockEdge());
+}
+
+action(AllocateTBE_Snoop, desc="") {
+ // No retry for snoop requests; just create resource stall
+ check_allocate(storSnpTBEs);
+
+ storSnpTBEs.incrementReserved();
+
+ // Move request to rdy queue
+ peek(snpInPort, CHIRequestMsg) {
+ enqueue(snpRdyOutPort, CHIRequestMsg, allocation_latency) {
+ assert(in_msg.addr == address);
+ out_msg := in_msg;
+ }
+
+ // also remove snoop source from waiting retry destinations to prevent
+ // deadlocks in which this snoop is blocked by a transaction that needs to
+ // send a request to the snoop destination before going to BUSY_INTR,
+ // but the destination needs the snoop to complete before sending retry
+ // credit
+ destsWaitingRetry.remove(in_msg.requestor);
+ }
+ snpInPort.dequeue(clockEdge());
+}
+
+action(AllocateTBE_SeqRequest, desc="") {
+ // No retry for sequencer requests; just create resource stall
+ check_allocate(storTBEs);
+
+ // reserve a slot for this request
+ storTBEs.incrementReserved();
+
+ // Move request to rdy queue
+ peek(seqInPort, RubyRequest) {
+ enqueue(reqRdyOutPort, CHIRequestMsg, allocation_latency) {
+ out_msg.addr := in_msg.LineAddress;
+ assert((in_msg.Size > 0) && (in_msg.Size <= blockSize));
+ out_msg.accAddr := in_msg.PhysicalAddress;
+ out_msg.accSize := in_msg.Size;
+ out_msg.requestor := machineID;
+ out_msg.fwdRequestor := machineID;
+ out_msg.seqReq := in_msg.getRequestPtr();
+ out_msg.isSeqReqValid := true;
+ assert(in_msg.Prefetch == PrefetchBit:No);
+ out_msg.is_local_pf := false;
+ out_msg.is_remote_pf := false;
+
+ if ((in_msg.Type == RubyRequestType:LD) ||
+ (in_msg.Type == RubyRequestType:IFETCH)) {
+ out_msg.type := CHIRequestType:Load;
+ } else if (in_msg.Type == RubyRequestType:ST) {
+ if (in_msg.Size == blockSize) {
+ out_msg.type := CHIRequestType:StoreLine;
+ } else {
+ out_msg.type := CHIRequestType:Store;
+ }
+ } else {
+ error("Invalid RubyRequestType");
+ }
+ }
+ }
+ seqInPort.dequeue(clockEdge());
+}
+
+action(AllocateTBE_PfRequest, desc="Allocate TBE for prefetch request") {
+ // No retry for prefetch requests; just create resource stall
+ check_allocate(storTBEs);
+
+ // reserve a slot for this request
+ storTBEs.incrementReserved();
+
+ // Move request to rdy queue
+ peek(pfInPort, RubyRequest) {
+ enqueue(reqRdyOutPort, CHIRequestMsg, 0) {
+ out_msg.addr := in_msg.LineAddress;
+ assert((in_msg.Size > 0) && (in_msg.Size <= blockSize));
+ out_msg.accAddr := in_msg.PhysicalAddress;
+ out_msg.accSize := in_msg.Size;
+ out_msg.requestor := machineID;
+ out_msg.fwdRequestor := machineID;
+ out_msg.seqReq := in_msg.getRequestPtr();
+ out_msg.isSeqReqValid := true;
+ assert(in_msg.Prefetch != PrefetchBit:No);
+ out_msg.is_local_pf := true;
+ out_msg.is_remote_pf := false;
+
+ if (in_msg.Type == RubyRequestType:LD) {
+ out_msg.type := CHIRequestType:Load;
+ } else if (in_msg.Type == RubyRequestType:ST) {
+ error("CHI is not supporting prefetch store requests");
+ } else {
+ error("Invalid RubyRequestType");
+ }
+ }
+ }
+ pfInPort.dequeue(clockEdge());
+}
+
+action(Initiate_Request, desc="") {
+ State initial := getState(tbe, cache_entry, address);
+ bool was_retried := false;
+ peek(reqRdyPort, CHIRequestMsg) {
+ set_tbe(allocateRequestTBE(address, in_msg));
+ // only a msg that was already retried doesn't allow a retry
+ was_retried := in_msg.allowRetry == false;
+ }
+ DirEntry dir_entry := getDirEntry(address);
+ copyCacheAndDir(cache_entry, dir_entry, tbe, initial);
+
+ tbe.use_DMT := is_HN && enable_DMT;
+ tbe.use_DCT := enable_DCT;
+
+ bool alloc_entry := needCacheEntry(tbe.reqType,
+ cache_entry, dir_entry,
+ tbe.is_local_pf);
+ bool dealloc_entry := needDeallocCacheEntry(tbe.reqType);
+ assert((alloc_entry && dealloc_entry) == false);
+
+ // always drops any data when not caching it or when this transaction
+ // requires deallocation
+ tbe.dataToBeInvalid := dealloc_entry ||
+ (is_invalid(cache_entry) && (alloc_entry == false));
+ tbe.doCacheFill := alloc_entry || is_valid(cache_entry);
+
+ // model the initial tag array read
+ tbe.actions.pushNB(Event:TagArrayRead);
+
+ incomingTransactionStart(address, curTransitionEvent(), initial, was_retried);
+}
+
+action(Initiate_Request_Stale, desc="") {
+ State initial := getState(tbe, cache_entry, address);
+ bool was_retried := false;
+ peek(reqRdyPort, CHIRequestMsg) {
+ set_tbe(allocateRequestTBE(address, in_msg));
+ was_retried := in_msg.allowRetry == false;
+ }
+ copyCacheAndDir(cache_entry, getDirEntry(address), tbe, initial);
+ incomingTransactionStart(address, curTransitionEvent(), initial, was_retried);
+}
+
+action(Initiate_Snoop, desc="") {
+ State initial := getState(tbe, cache_entry, address);
+ peek(snpRdyPort, CHIRequestMsg) {
+ set_tbe(allocateSnoopTBE(address, in_msg));
+ }
+ copyCacheAndDir(cache_entry, getDirEntry(address), tbe, initial);
+
+ // if we end up with valid data drop it if no entry allocated
+ tbe.dataToBeInvalid := is_invalid(cache_entry);
+
+ // model the initial tag array read
+ tbe.actions.pushNB(Event:TagArrayRead);
+
+ incomingTransactionStart(address, curTransitionEvent(), initial, false);
+}
+
+action(Initiate_Snoop_Hazard, desc="") {
+ assert(is_valid(tbe));
+ assert(tbe.is_req_tbe || tbe.is_repl_tbe);
+
+ // Switch to the new snoop TBE
+ TBE prev_tbe := tbe;
+ peek(snpRdyPort, CHIRequestMsg) {
+ set_tbe(allocateSnoopTBE(address, in_msg));
+ }
+ assert(tbe.is_snp_tbe);
+ if (prev_tbe.is_req_tbe) {
+ assert(prev_tbe.is_repl_tbe == false);
+ tbe.is_req_hazard := true;
+ } else {
+ assert(prev_tbe.is_repl_tbe);
+ tbe.is_repl_hazard := true;
+ }
+
+ // Use state from prev TBE
+ tbe.pendReqType := prev_tbe.pendReqType;
+ copyCacheAndDirTBEs(prev_tbe, tbe);
+ tbe.wakeup_pending_req := prev_tbe.wakeup_pending_req;
+ tbe.wakeup_pending_snp := prev_tbe.wakeup_pending_snp;
+ tbe.wakeup_pending_tgr := prev_tbe.wakeup_pending_tgr;
+}
+
+action(RestoreFromHazard, desc="") {
+ TBE hazard_tbe := getHazardTBE(tbe);
+
+ // update
+ setDataToBeStates(tbe);
+
+ copyCacheAndDirTBEs(tbe, hazard_tbe);
+ hazard_tbe.wakeup_pending_req := tbe.wakeup_pending_req;
+ hazard_tbe.wakeup_pending_snp := tbe.wakeup_pending_snp;
+ hazard_tbe.wakeup_pending_tgr := tbe.wakeup_pending_tgr;
+
+ deallocateSnpTBE(tbe);
+ set_tbe(hazard_tbe);
+
+ // if the pending request is a WB or Evict then it becomes a stale request
+ // if data is no longer in the expected state
+ if (tbe.pendReqType == CHIRequestType:WriteBackFull) {
+ tbe.is_stale := (tbe.dataValid && tbe.dataDirty) == false;
+ } else if (tbe.pendReqType == CHIRequestType:WriteCleanFull) {
+ tbe.is_stale := (tbe.dataValid && tbe.dataDirty) == false;
+ } else if (hazard_tbe.pendReqType == CHIRequestType:WriteEvictFull) {
+ tbe.is_stale := (tbe.dataValid && tbe.dataUnique) == false;
+ } else if (hazard_tbe.pendReqType == CHIRequestType:Evict) {
+ tbe.is_stale := tbe.dataValid == false;
+ }
+
+ // a pending action from the original request may have been stalled during
+ // the hazard and needs to wakeup up now
+ wakeupPendingTgrs(tbe);
+}
+
+action(Initiate_Replacement, desc="") {
+ assert(is_invalid(tbe));
+ State initial := getState(tbe, cache_entry, address);
+ if (unify_repl_TBEs) {
+ peek(replTriggerInPort, ReplacementMsg) {
+ set_tbe(allocateReplacementTBEOnSlot(address, in_msg.slot));
+ DPRINTF(RubySlicc, "Allocated replacement TBE on slot %d\n", tbe.storSlot);
+ }
+ } else {
+ set_tbe(allocateReplacementTBE(address));
+ DPRINTF(RubySlicc, "Allocated replacement TBE on new slot %d\n", tbe.storSlot);
+ }
+ copyCacheAndDir(cache_entry, getDirEntry(address), tbe, initial);
+
+ // model the initial tag array read
+ tbe.actions.pushNB(Event:TagArrayRead);
+
+ incomingTransactionStart(address, curTransitionEvent(), initial, false);
+}
+
+
+
+action(StallRequest, desc="") {
+ // was stalled because of an existing request
+ assert(is_valid(tbe));
+ assert(tbe.addr == address);
+ // tracks pending
+ tbe.wakeup_pending_req := true;
+ stall_and_wait(reqRdyPort, address);
+}
+
+action(StallSnoop, desc="") {
+ // was stalled because of an existing request
+ assert(is_valid(tbe));
+ assert(tbe.addr == address);
+ // tracks pending
+ tbe.wakeup_pending_snp := true;
+ stall_and_wait(snpRdyPort, address);
+}
+
+action(StallLocalEviction, desc="") {
+ // was stalled because of an existing request
+ assert(is_valid(tbe));
+ assert(tbe.addr == address);
+
+ // Just pop the queue and When this transaction finishes wake-up the original
+ // msgs that caused this eviction
+ tbe.wakeup_pending_tgr := true;
+ replTriggerInPort.dequeue(clockEdge());
+}
+
+action(StallSnoop_NoTBE, desc="") {
+ stall_and_wait(snpRdyPort, address);
+}
+
+action(StallActionOnHazard, desc="") {
+ assert(is_valid(tbe));
+ assert(tbe.is_req_hazard || tbe.is_repl_hazard);
+ tbe.wakeup_pending_tgr := true;
+ stall_and_wait(triggerInPort, address);
+}
+
+action(Initiate_ReadShared_Miss, desc="") {
+ tbe.actions.push(Event:ReadMissPipe);
+ if (is_HN && tbe.use_DMT) {
+ tbe.requestorToBeExclusiveOwner := true;
+ tbe.dataMaybeDirtyUpstream := true; // SNF always replies with CompData_UC
+ if (enable_DMT_early_dealloc) {
+ tbe.actions.push(Event:SendRespSepData);
+ }
+ tbe.actions.push(Event:WaitCompAck);
+ tbe.actions.pushNB(Event:SendReadNoSnpDMT);
+ } else if (is_HN) {
+ tbe.actions.push(Event:SendReadNoSnp);
+ tbe.actions.push(Event:WaitCompAck);
+ tbe.actions.pushNB(Event:SendCompData);
+ } else {
+ tbe.actions.push(Event:SendReadShared);
+ tbe.actions.push(Event:WaitCompAck);
+ tbe.actions.pushNB(Event:SendCompData);
+ }
+ tbe.actions.push(Event:CheckCacheFill);
+ tbe.actions.push(Event:TagArrayWrite);
+}
+
+action(Initiate_ReadShared_Hit, desc="") {
+ tbe.actions.push(Event:ReadHitPipe);
+ tbe.actions.push(Event:DataArrayRead);
+ tbe.actions.push(Event:WaitCompAck);
+ tbe.actions.pushNB(Event:SendCompData);
+ tbe.actions.pushNB(Event:TagArrayWrite);
+}
+
+action(Initiate_ReadShared_HitUpstream, desc="") {
+ tbe.actions.push(Event:ReadMissPipe);
+ if (tbe.use_DCT) {
+ tbe.actions.push(Event:SendSnpSharedFwdToOwner);
+ tbe.actions.pushNB(Event:WaitCompAck);
+ tbe.updateDirOnCompAck := false;
+ } else {
+ tbe.actions.push(Event:SendSnpShared);
+ tbe.actions.push(Event:WaitCompAck);
+ tbe.actions.pushNB(Event:SendCompData);
+ }
+ tbe.actions.push(Event:MaintainCoherence);
+}
+
+action(Initiate_ReadShared_HitUpstream_NoOwner, desc="") {
+ tbe.actions.push(Event:ReadMissPipe);
+ if (tbe.use_DCT) {
+ tbe.actions.push(Event:SendSnpSharedFwdToSharer);
+ tbe.actions.pushNB(Event:WaitCompAck);
+ tbe.updateDirOnCompAck := false;
+ } else {
+ tbe.actions.push(Event:SendSnpOnce);
+ tbe.actions.push(Event:WaitCompAck);
+ tbe.actions.pushNB(Event:SendCompData);
+ }
+ tbe.actions.push(Event:MaintainCoherence);
+}
+
+
+action(Initiate_ReadOnce_Miss, desc="") {
+ // drop at the end if not doing a fill
+ tbe.dataToBeInvalid := tbe.doCacheFill == false;
+
+ tbe.actions.push(Event:ReadMissPipe);
+ if (is_HN && tbe.use_DMT) {
+ assert(is_invalid(cache_entry));
+ tbe.requestorToBeExclusiveOwner := true;
+ tbe.dataMaybeDirtyUpstream := true; // SNF always replies with CompData_UC
+ if (enable_DMT_early_dealloc) {
+ tbe.actions.push(Event:SendRespSepData);
+ }
+ tbe.actions.push(Event:WaitCompAck);
+ tbe.actions.pushNB(Event:SendReadNoSnpDMT);
+ } else if (is_HN) {
+ tbe.actions.push(Event:SendReadNoSnp);
+ tbe.actions.push(Event:WaitCompAck);
+ tbe.actions.pushNB(Event:SendCompData);
+ } else {
+ // if not allocating an entry send a ReadOnce
+ if (tbe.dataToBeInvalid) {
+ tbe.actions.push(Event:SendReadOnce);
+ } else {
+ tbe.actions.push(Event:SendReadShared);
+ }
+ tbe.actions.push(Event:WaitCompAck);
+ tbe.actions.pushNB(Event:SendCompData);
+ }
+
+ tbe.updateDirOnCompAck := false;
+
+ tbe.actions.push(Event:CheckCacheFill);
+ tbe.actions.push(Event:TagArrayWrite);
+}
+
+action(Initiate_ReadOnce_Hit, desc="") {
+ tbe.actions.push(Event:ReadHitPipe);
+ tbe.actions.push(Event:DataArrayRead);
+ tbe.actions.push(Event:WaitCompAck);
+ tbe.actions.pushNB(Event:SendCompData);
+ tbe.updateDirOnCompAck := false;
+}
+
+action(Initiate_ReadOnce_HitUpstream, desc="") {
+ tbe.actions.push(Event:ReadMissPipe);
+ if (tbe.use_DCT) {
+ tbe.actions.push(Event:SendSnpOnceFwd);
+ tbe.actions.pushNB(Event:WaitCompAck);
+ } else {
+ tbe.actions.push(Event:SendSnpOnce);
+ tbe.actions.push(Event:WaitCompAck);
+ tbe.actions.pushNB(Event:SendCompData);
+ }
+ tbe.updateDirOnCompAck := false;
+ // no need to update or access tags/data on ReadOnce served from upstream
+}
+
+
+
+action(Initiate_ReadUnique_Miss, desc="") {
+ tbe.actions.push(Event:ReadMissPipe);
+ if (is_HN && tbe.use_DMT) {
+ tbe.requestorToBeExclusiveOwner := true;
+ tbe.dataMaybeDirtyUpstream := true; // SNF always replies with CompData_UC
+ if (enable_DMT_early_dealloc) {
+ tbe.actions.push(Event:SendRespSepData);
+ }
+ tbe.actions.push(Event:WaitCompAck);
+ tbe.actions.pushNB(Event:SendReadNoSnpDMT);
+ } else if (is_HN) {
+ tbe.actions.push(Event:SendReadNoSnp);
+ tbe.actions.push(Event:WaitCompAck);
+ tbe.actions.pushNB(Event:SendCompData);
+ } else {
+ tbe.actions.push(Event:SendReadUnique);
+ tbe.actions.push(Event:WaitCompAck);
+ tbe.actions.pushNB(Event:SendCompData);
+ }
+ tbe.actions.push(Event:CheckCacheFill);
+ tbe.actions.push(Event:TagArrayWrite);
+}
+
+action(Initiate_ReadUnique_AutoUpgrade, desc="") {
+ assert(is_HN);
+ tbe.dataUnique := true;
+}
+
+action(Initiate_ReadUnique_Upgrade, desc="") {
+ // must use the transitions with auto upgrade otherwise
+ assert(is_HN == false);
+ assert(tbe.use_DCT == false);
+ assert((tbe.dataValid && tbe.dataUnique) == false);
+ assert((tbe.dir_ownerExists && tbe.dir_ownerIsExcl) == false);
+
+ tbe.actions.push(Event:ReadMissPipe);
+ if (tbe.dataMaybeDirtyUpstream) {
+ tbe.actions.push(Event:SendSnpUnique);
+ } else if (tbe.dir_sharers.count() > 0) {
+ // no one will send us data unless we explicitly ask
+ tbe.actions.push(Event:SendSnpUniqueRetToSrc);
+ } else {
+ assert(tbe.dataValid);
+ }
+ // then attempt to upgrade our data
+ tbe.actions.push(Event:SendCleanUnique);
+ tbe.actions.push(Event:CheckUpgrade_FromRU);
+
+ // send up the upgraded data or fresh data if we failed, see CheckUpgrade_FromRU
+ tbe.actions.push(Event:WaitCompAck);
+ tbe.actions.pushNB(Event:SendCompData);
+ tbe.actions.push(Event:CheckCacheFill);
+ tbe.actions.push(Event:TagArrayWrite);
+}
+
+action(Initiate_ReadUnique_Hit, desc="") {
+ tbe.actions.push(Event:ReadHitPipe);
+ tbe.actions.push(Event:DataArrayRead);
+ tbe.actions.push(Event:WaitCompAck);
+ tbe.actions.pushNB(Event:SendCompData);
+ tbe.actions.pushNB(Event:TagArrayWrite);
+}
+
+action(Initiate_ReadUnique_HitUpstream, desc="") {
+ tbe.actions.push(Event:ReadMissPipe);
+ // SnpUniqueFwd can be used only if the line is cached at a single
+ // requester; so force it off if that's the case
+ tbe.use_DCT := tbe.use_DCT && (tbe.dir_sharers.count() == 1) &&
+ (tbe.dir_sharers.isElement(tbe.requestor) == false);
+ if (tbe.use_DCT) {
+ tbe.actions.push(Event:SendSnpUniqueFwd);
+ tbe.actions.pushNB(Event:WaitCompAck);
+ tbe.updateDirOnCompAck := false;
+ } else if (tbe.dataMaybeDirtyUpstream) {
+ tbe.actions.push(Event:SendSnpUnique);
+ tbe.actions.push(Event:WaitCompAck);
+ tbe.actions.pushNB(Event:SendCompData);
+ } else {
+ // no one will send us data unless we explicitly ask
+ tbe.actions.push(Event:SendSnpUniqueRetToSrc);
+ tbe.actions.push(Event:WaitCompAck);
+ tbe.actions.pushNB(Event:SendCompData);
+ }
+ // just tag update since data any data would become stale
+ tbe.actions.pushNB(Event:TagArrayWrite);
+}
+
+action(Initiate_ReadUnique_Hit_InvUpstream, desc="") {
+ tbe.actions.push(Event:ReadHitPipe);
+ tbe.actions.push(Event:SendSnpCleanInvalid);
+ tbe.actions.pushNB(Event:DataArrayRead);
+ tbe.actions.push(Event:WaitCompAck);
+ tbe.actions.pushNB(Event:SendCompData);
+ tbe.actions.pushNB(Event:TagArrayWrite);
+}
+
+action(Initiate_CleanUnique, desc="") {
+ tbe.actions.push(Event:ReadMissPipe); // TODO need another latency pipe ??
+
+ // requestor don't have the line anymore; send response but don't update the
+ // directory on CompAck. The requestor knows we are not tracking it and will
+ // send a ReadUnique later
+ if (tbe.dir_sharers.isElement(tbe.requestor) == false) {
+ tbe.actions.push(Event:SendCompUCResp);
+ tbe.actions.push(Event:WaitCompAck);
+ tbe.updateDirOnCompAck := false;
+ } else {
+ // invalidates everyone except requestor
+ if (tbe.dir_sharers.count() > 1) {
+ tbe.actions.push(Event:SendSnpCleanInvalidNoReq);
+ }
+ // auto upgrade if HN
+ tbe.dataUnique := tbe.dataUnique || is_HN;
+ // get unique permission
+ if (tbe.dataUnique == false) {
+ tbe.actions.push(Event:SendCleanUnique);
+ tbe.actions.push(Event:CheckUpgrade_FromCU);
+ }
+ // next actions will depend on the data state after snoops+CleanUnique
+ tbe.actions.push(Event:FinishCleanUnique);
+ }
+}
+
+action(Finish_CleanUnique, desc="") {
+ // This is should be executed at the end of a transaction
+ assert(tbe.actions.empty());
+ tbe.actions.push(Event:SendCompUCResp);
+ tbe.actions.push(Event:WaitCompAck);
+
+ // everyone may have been hit by an invalidation so check again
+ if (tbe.dir_sharers.isElement(tbe.requestor) == false) {
+ tbe.updateDirOnCompAck := false;
+ assert(tbe.dataValid == false);
+ } else {
+ // must be the only one in sharers map
+ assert(tbe.dir_sharers.count() == 1);
+ assert(tbe.dataUnique);
+
+ // similar to Initiate_MaitainCoherence; writeback if the owner has data as
+ // clean data and we have it dirty and cannot keep it
+ bool fill_pipeline := tbe.dataValid && tbe.dataDirty;
+ bool req_has_dirty := tbe.dir_ownerExists && (tbe.dir_owner == tbe.requestor);
+ if (tbe.dataValid && tbe.dataDirty && tbe.dataToBeInvalid &&
+ (req_has_dirty == false)) {
+ fill_pipeline := false;
+ if (is_HN) {
+ tbe.actions.push(Event:SendWriteNoSnp);
+ } else {
+ tbe.actions.push(Event:SendWriteClean);
+ }
+ tbe.actions.push(Event:WriteBEPipe);
+ tbe.actions.push(Event:SendWBData);
+ }
+
+ // needed by UpdateDirState_FromReqResp triggered by the expected CompAck
+ tbe.dataMaybeDirtyUpstream := true;
+ tbe.requestorToBeExclusiveOwner := true;
+ tbe.dir_ownerExists := false;
+
+ if (fill_pipeline) {
+ tbe.actions.push(Event:CheckCacheFill);
+ }
+ }
+ tbe.actions.push(Event:TagArrayWrite);
+}
+
+
+action(Initiate_LoadHit, desc="") {
+ // Local prefetch requests do not read data array
+ if (tbe.is_local_pf == false) {
+ tbe.actions.push(Event:DataArrayRead);
+ }
+ tbe.actions.push(Event:LoadHit);
+}
+
+action(Initiate_LoadMiss, desc="") {
+ if (tbe.doCacheFill) {
+ tbe.actions.push(Event:SendReadShared);
+ tbe.actions.push(Event:CheckCacheFill);
+ tbe.actions.push(Event:TagArrayWrite);
+ } else {
+ tbe.actions.push(Event:SendReadOnce);
+ tbe.dataToBeInvalid := true;
+ }
+}
+
+
+
+action(Initiate_StoreHit, desc="") {
+ tbe.actions.push(Event:DataArrayRead);
+ tbe.actions.push(Event:StoreHit);
+ tbe.actions.push(Event:CheckCacheFill);
+ tbe.actions.push(Event:TagArrayWrite);
+}
+
+action(Initiate_StoreMiss, desc="") {
+ if (tbe.doCacheFill) {
+ tbe.actions.push(Event:SendReadUnique);
+ tbe.actions.push(Event:CheckCacheFill);
+ tbe.actions.push(Event:TagArrayWrite);
+ } else {
+ tbe.actions.push(Event:SendWriteUnique);
+ tbe.actions.push(Event:SendWUDataCB);
+ tbe.dataToBeInvalid := true;
+ }
+}
+
+action(Initiate_StoreUpgrade, desc="") {
+ assert(tbe.dataValid);
+ assert(is_valid(cache_entry));
+ tbe.actions.push(Event:SendCleanUnique);
+ tbe.actions.push(Event:CheckUpgrade_FromStore);
+ tbe.actions.push(Event:CheckCacheFill);
+ tbe.actions.push(Event:TagArrayWrite);
+}
+
+action(Initiate_WriteUnique_LocalWrite, desc="") {
+ // auto-upgrade if hn but state was not unique
+ assert(is_HN || tbe.dataUnique);
+ tbe.dataUnique := true;
+ if (tbe.dir_sharers.count() > 0) {
+ tbe.actions.push(Event:SendSnpCleanInvalid);
+ }
+ if (comp_wu) {
+ tbe.actions.push(Event:SendDBIDResp_WU);
+ tbe.actions.pushNB(Event:WriteFEPipe);
+ tbe.actions.pushNB(Event:SendComp_WU);
+ } else {
+ tbe.actions.push(Event:SendCompDBIDResp_WU);
+ tbe.actions.pushNB(Event:WriteFEPipe);
+ }
+ tbe.actions.push(Event:CheckCacheFill);
+ tbe.actions.push(Event:TagArrayWrite);
+}
+
+action(Initiate_WriteUnique_LocalWrite_AfterUpgrade, desc="") {
+ assert(is_HN == false);
+ assert((tbe.dataValid && tbe.dataUnique) == false);
+ tbe.actions.push(Event:SendReadUnique);
+ if (comp_wu) {
+ tbe.actions.push(Event:SendDBIDResp_WU);
+ tbe.actions.pushNB(Event:WriteFEPipe);
+ tbe.actions.pushNB(Event:SendComp_WU);
+ } else {
+ tbe.actions.push(Event:SendCompDBIDResp_WU);
+ tbe.actions.pushNB(Event:WriteFEPipe);
+ }
+ tbe.actions.push(Event:CheckCacheFill);
+ tbe.actions.push(Event:TagArrayWrite);
+}
+
+action(Initiate_WriteUnique_Writeback, desc="") {
+ assert(is_HN);
+ assert(tbe.dir_sharers.count() > 0);
+ tbe.actions.push(Event:SendSnpUnique);
+ if (comp_wu) {
+ tbe.actions.push(Event:SendDBIDResp_WU);
+ tbe.actions.pushNB(Event:WriteFEPipe);
+ tbe.actions.pushNB(Event:SendWriteNoSnp);
+ tbe.actions.pushNB(Event:SendComp_WU);
+ } else {
+ tbe.actions.push(Event:SendCompDBIDResp_WU);
+ tbe.actions.pushNB(Event:WriteFEPipe);
+ tbe.actions.pushNB(Event:SendWriteNoSnp);
+ }
+ tbe.actions.push(Event:WriteBEPipe);
+ tbe.actions.push(Event:SendWBData);
+ tbe.dataToBeInvalid := true;
+ tbe.actions.pushNB(Event:TagArrayWrite);
+}
+
+action(Initiate_WriteUnique_PartialWrite, desc="") {
+ assert(is_HN);
+ if (tbe.dir_sharers.count() > 0) {
+ tbe.actions.push(Event:SendSnpCleanInvalid);
+ }
+ if (comp_wu) {
+ tbe.actions.push(Event:SendDBIDResp_WU);
+ tbe.actions.pushNB(Event:WriteFEPipe);
+ tbe.actions.pushNB(Event:SendWriteNoSnpPartial);
+ tbe.actions.pushNB(Event:SendComp_WU);
+ } else {
+ tbe.actions.push(Event:SendCompDBIDResp_WU);
+ tbe.actions.pushNB(Event:WriteFEPipe);
+ tbe.actions.pushNB(Event:SendWriteNoSnpPartial);
+ }
+ tbe.actions.push(Event:WriteBEPipe);
+ tbe.actions.push(Event:SendWUData);
+ tbe.dataToBeInvalid := true;
+
+ tbe.actions.pushNB(Event:TagArrayWrite);
+}
+
+action(Initiate_WriteUnique_Forward, desc="") {
+ if (comp_wu) {
+ tbe.actions.push(Event:SendDBIDResp_WU);
+ tbe.actions.pushNB(Event:WriteFEPipe);
+ tbe.actions.pushNB(Event:SendWriteUnique);
+ tbe.actions.pushNB(Event:SendComp_WU);
+ } else {
+ tbe.actions.push(Event:SendCompDBIDResp_WU);
+ tbe.actions.pushNB(Event:WriteFEPipe);
+ tbe.actions.pushNB(Event:SendWriteUnique);
+ }
+ tbe.actions.push(Event:WriteBEPipe);
+ tbe.actions.push(Event:SendWUData);
+ tbe.dataToBeInvalid := true;
+ tbe.actions.pushNB(Event:TagArrayWrite);
+}
+
+
+
+action(Initiate_CopyBack, desc="") {
+ // expect to receive this data after Send_CompDBIDResp
+ if (tbe.reqType == CHIRequestType:WriteBackFull) {
+ tbe.expected_req_resp.addExpectedDataType(CHIDataType:CBWrData_UD_PD);
+ tbe.expected_req_resp.addExpectedDataType(CHIDataType:CBWrData_SD_PD);
+ } else if (tbe.reqType == CHIRequestType:WriteEvictFull) {
+ assert(tbe.reqType == CHIRequestType:WriteEvictFull);
+ tbe.expected_req_resp.addExpectedDataType(CHIDataType:CBWrData_UC);
+ tbe.expected_req_resp.addExpectedDataType(CHIDataType:CBWrData_SC);
+ } else {
+ assert(tbe.reqType == CHIRequestType:WriteCleanFull);
+ tbe.expected_req_resp.addExpectedDataType(CHIDataType:CBWrData_UD_PD);
+ tbe.expected_req_resp.addExpectedDataType(CHIDataType:CBWrData_SD_PD);
+ }
+ tbe.expected_req_resp.setExpectedCount(1);
+
+ tbe.actions.pushNB(Event:SendCompDBIDResp);
+ tbe.actions.pushNB(Event:WriteFEPipe);
+ tbe.actions.push(Event:MaintainCoherence);
+ // MaintainCoherence queues the Tag/Data updates
+}
+
+action(Initiate_CopyBack_Stale, desc="") {
+ tbe.expected_req_resp.addExpectedDataType(CHIDataType:CBWrData_SC);
+ tbe.expected_req_resp.addExpectedDataType(CHIDataType:CBWrData_I);
+ tbe.expected_req_resp.setExpectedCount(1);
+
+ tbe.actions.pushNB(Event:SendCompDBIDRespStale);
+ tbe.actions.pushNB(Event:WriteFEPipe);
+
+ // if it was the last known sharer and we don't have the data do the same
+ // the Initiate_Evict
+ if ((is_HN == false) && (tbe.dir_sharers.count() == 1) &&
+ tbe.dir_sharers.isElement(tbe.requestor) && (tbe.dataValid == false)) {
+ tbe.actions.push(Event:SendEvict);
+ }
+
+ tbe.dir_sharers.remove(tbe.requestor);
+ assert((tbe.dir_ownerExists == false) || (tbe.dir_owner != tbe.requestor));
+
+ // usually we consider data locally invalid on RU states even if we
+ // have a copy; consider it valid for this transition only so we can
+ // comeback to UD_RU/UC_RU
+ if (is_valid(cache_entry) && (tbe.dataValid == false) &&
+ tbe.dir_ownerExists && tbe.dir_ownerIsExcl) {
+ tbe.dataValid := true;
+ }
+}
+
+action(Initiate_Evict, desc="") {
+ tbe.actions.push(Event:SendCompIResp);
+
+ assert(tbe.dir_sharers.isElement(tbe.requestor));
+ assert((tbe.dir_ownerExists == false) || (tbe.dir_owner != tbe.requestor));
+ tbe.dir_sharers.remove(tbe.requestor);
+
+ if ((is_HN == false) && (tbe.dir_sharers.count() == 0) &&
+ (tbe.dataValid == false)) {
+ tbe.actions.push(Event:SendEvict);
+ }
+
+ tbe.actions.pushNB(Event:TagArrayWrite);
+}
+
+action(Initiate_MaitainCoherence, desc="") {
+ // issue a copy back if necessary to maintain coherence for data we are
+ // droping. This is should be executed at the end of a transaction
+ assert(tbe.actions.empty());
+ // go through either the fill or the writeback pipeline
+ if (tbe.dataValid && tbe.dataToBeInvalid) {
+ if (is_HN) {
+ if (tbe.dataDirty && (tbe.dataMaybeDirtyUpstream == false)) {
+ tbe.actions.push(Event:SendWriteNoSnp);
+ tbe.actions.push(Event:WriteBEPipe);
+ tbe.actions.push(Event:SendWBData);
+ }
+ } else {
+ if (tbe.dir_sharers.isEmpty() && (tbe.dataDirty || tbe.dataUnique)) {
+ tbe.actions.push(Event:SendWriteBackOrWriteEvict);
+ tbe.actions.push(Event:WriteBEPipe);
+ tbe.actions.push(Event:SendWBData);
+ } else if ((tbe.dir_sharers.isEmpty() == false) && tbe.dataDirty &&
+ (tbe.dataMaybeDirtyUpstream == false)) {
+ tbe.actions.push(Event:SendWriteClean);
+ tbe.actions.push(Event:WriteBEPipe);
+ tbe.actions.push(Event:SendWBData);
+ }
+ }
+ }
+ else if (tbe.dataValid) {
+ tbe.actions.push(Event:CheckCacheFill);
+ }
+ tbe.actions.push(Event:TagArrayWrite);
+}
+
+
+
+// Too many common stuff between SnpUnique/SnpUniqueFwd/SnpCleanInvalid
+// so do one action for all of them here
+action(Initiate_InvalidationSnoop, desc="") {
+ tbe.actions.push(Event:SnpInvPipe);
+ // Propagate a snoop upwards depending on the type
+ if (tbe.dir_sharers.count() > 0) {
+ if ((tbe.reqType == CHIRequestType:SnpUniqueFwd) ||
+ (tbe.reqType == CHIRequestType:SnpUnique)) {
+ if ((tbe.snpNeedsData && (tbe.dataMaybeDirtyUpstream == false)) ||
+ (tbe.dataValid == false)) {
+ tbe.actions.push(Event:SendSnpUniqueRetToSrc);
+ } else {
+ tbe.actions.push(Event:SendSnpUnique);
+ }
+ } else {
+ assert(tbe.reqType == CHIRequestType:SnpCleanInvalid);
+ tbe.actions.push(Event:SendSnpCleanInvalid);
+ }
+ }
+
+ if (tbe.reqType == CHIRequestType:SnpUniqueFwd) {
+ tbe.actions.push(Event:SendSnpUniqueFwdCompData);
+ } else {
+ tbe.actions.push(Event:SendInvSnpResp);
+ }
+
+ if(tbe.is_req_hazard || tbe.is_repl_hazard) {
+ tbe.actions.push(Event:RestoreFromHazard);
+ } else {
+ tbe.actions.pushNB(Event:TagArrayWrite);
+ }
+
+ tbe.dataToBeInvalid := true;
+}
+
+action(Initiate_SnpShared, desc="") {
+ // Handles both SnpShared,SnpSharedFwd,SnpNotSharedDirtyFwd
+ tbe.actions.push(Event:SnpSharedPipe);
+ if (tbe.dir_ownerExists) {
+ assert(tbe.dataMaybeDirtyUpstream);
+ tbe.actions.push(Event:SendSnpShared);
+ } else if (tbe.dataValid == false) {
+ // must get a copy of shared data upstream
+ assert(tbe.dataMaybeDirtyUpstream == false);
+ assert(tbe.dir_sharers.count() > 0);
+ tbe.actions.push(Event:SendSnpOnce);
+ } else {
+ tbe.actions.push(Event:DataArrayRead);
+ }
+
+ if (tbe.reqType == CHIRequestType:SnpSharedFwd) {
+ tbe.actions.push(Event:SendSnpSharedFwdCompData);
+ } else if (tbe.reqType == CHIRequestType:SnpNotSharedDirtyFwd) {
+ tbe.actions.push(Event:SendSnpNotSharedDirtyFwdCompData);
+ } else {
+ assert(tbe.reqType == CHIRequestType:SnpShared);
+ tbe.actions.push(Event:SendSnpData);
+ }
+ if (tbe.is_req_hazard || tbe.is_repl_hazard) {
+ tbe.actions.push(Event:RestoreFromHazard);
+ } else {
+ tbe.actions.pushNB(Event:TagArrayWrite);
+ }
+ tbe.dataToBeSharedClean := true;
+}
+
+action(Initiate_SnpOnce, desc="") {
+ tbe.actions.push(Event:SnpOncePipe);
+ if (tbe.dataValid == false) {
+ assert(tbe.dir_sharers.count() > 0);
+ tbe.actions.push(Event:SendSnpOnce);
+ } else {
+ tbe.actions.push(Event:DataArrayRead);
+ }
+
+ if (tbe.reqType == CHIRequestType:SnpOnceFwd) {
+ tbe.actions.push(Event:SendSnpOnceFwdCompData);
+ } else {
+ assert(tbe.reqType == CHIRequestType:SnpOnce);
+ assert(tbe.snpNeedsData);
+ tbe.actions.push(Event:SendSnpData);
+ }
+
+ if (tbe.is_req_hazard || tbe.is_repl_hazard) {
+ tbe.actions.push(Event:RestoreFromHazard);
+ } else {
+ tbe.actions.pushNB(Event:TagArrayWrite);
+ }
+}
+
+
+
+action(Initiate_Replacement_Evict_BackInvalidte, desc="") {
+ assert(is_HN == false);
+ tbe.actions.push(Event:SendSnpCleanInvalid);
+ tbe.actions.push(Event:SendEvict);
+ tbe.dataToBeInvalid := true;
+ tbe.actions.pushNB(Event:TagArrayWrite);
+}
+
+action(Initiate_Replacement_Evict, desc="") {
+ assert(is_HN == false);
+ assert(tbe.dir_sharers.isEmpty());
+ tbe.actions.push(Event:SendEvict);
+ tbe.dataToBeInvalid := true;
+ tbe.actions.pushNB(Event:TagArrayWrite);
+}
+
+action(Initiate_Replacement_JustDrop, desc="") {
+ tbe.dataToBeInvalid := true;
+ tbe.actions.pushNB(Event:TagArrayWrite);
+}
+
+action(Initiate_Replacement_WB_BackInvalidate, desc="") {
+ assert(tbe.dataDirty || tbe.dataUnique || tbe.dataMaybeDirtyUpstream);
+ tbe.actions.push(Event:SendSnpCleanInvalid);
+ tbe.actions.push(Event:WriteFEPipe);
+ if (is_HN) {
+ if (tbe.dataDirty || tbe.dataMaybeDirtyUpstream) {
+ tbe.actions.push(Event:SendWriteNoSnp);
+ }
+ } else {
+ tbe.actions.push(Event:SendWriteBackOrWriteEvict);
+ }
+ tbe.actions.pushNB(Event:DataArrayRead);
+ tbe.actions.push(Event:WriteBEPipe);
+ tbe.actions.push(Event:SendWBData);
+ tbe.dataToBeInvalid := true;
+
+ tbe.actions.pushNB(Event:TagArrayWrite);
+}
+
+action(Initiate_Replacement_WB, desc="") {
+ tbe.actions.push(Event:WriteFEPipe);
+ if (is_HN) {
+ assert(tbe.dataDirty);
+ tbe.actions.push(Event:SendWriteNoSnp);
+ } else if (tbe.dir_sharers.isEmpty()) {
+ assert(tbe.dataDirty || tbe.dataUnique);
+ tbe.actions.push(Event:SendWriteBackOrWriteEvict);
+ } else {
+ assert(tbe.dataDirty);
+ tbe.actions.push(Event:SendWriteClean);
+ }
+ tbe.actions.pushNB(Event:DataArrayRead);
+ tbe.actions.push(Event:WriteBEPipe);
+ tbe.actions.push(Event:SendWBData);
+ tbe.dataToBeInvalid := true;
+ tbe.actions.pushNB(Event:TagArrayWrite);
+}
+
+
+
+action(Send_ReadShared, desc="") {
+ assert(is_HN == false);
+ assert(tbe.dataValid == false);
+
+ clearExpectedReqResp(tbe);
+ tbe.expected_req_resp.addExpectedDataType(CHIDataType:DataSepResp_UC);
+ tbe.expected_req_resp.addExpectedDataType(CHIDataType:CompData_UC);
+ tbe.expected_req_resp.addExpectedDataType(CHIDataType:CompData_UD_PD);
+ tbe.expected_req_resp.addExpectedDataType(CHIDataType:CompData_SC);
+ if (allow_SD) {
+ tbe.expected_req_resp.addExpectedDataType(CHIDataType:CompData_SD_PD);
+ }
+ // NOTE: the first CompData received counts as RespSepData
+ tbe.expected_req_resp.addExpectedRespType(CHIResponseType:RespSepData);
+ tbe.expected_req_resp.setExpectedCount(2);
+ tbe.dataBlkValid.clear();
+
+ enqueue(reqOutPort, CHIRequestMsg, request_latency) {
+ if (allow_SD) {
+ prepareRequest(tbe, CHIRequestType:ReadShared, out_msg);
+ } else {
+ prepareRequest(tbe, CHIRequestType:ReadNotSharedDirty, out_msg);
+ }
+ out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr));
+ out_msg.dataToFwdRequestor := false;
+ allowRequestRetry(tbe, out_msg);
+ }
+}
+
+action(Send_ReadNoSnp, desc="") {
+ assert(is_HN);
+ assert(tbe.use_DMT == false);
+
+ clearExpectedReqResp(tbe);
+ tbe.expected_req_resp.addExpectedDataType(CHIDataType:CompData_UC);
+ // NOTE: the first CompData received counts as RespSepData
+ tbe.expected_req_resp.addExpectedRespType(CHIResponseType:RespSepData);
+ tbe.expected_req_resp.setExpectedCount(2);
+ tbe.dataBlkValid.clear();
+ outgoingTransactionStart(address, curTransitionEvent());
+
+ enqueue(reqOutPort, CHIRequestMsg, request_latency) {
+ prepareRequest(tbe, CHIRequestType:ReadNoSnp, out_msg);
+ out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr));
+ out_msg.dataToFwdRequestor := false;
+ allowRequestRetry(tbe, out_msg);
+ }
+}
+
+action(Send_ReadNoSnpDMT, desc="") {
+ assert(is_HN);
+ assert(tbe.use_DMT);
+
+ CHIRequestType req := CHIRequestType:ReadNoSnp;
+ if (enable_DMT_early_dealloc) {
+ req := CHIRequestType:ReadNoSnpSep;
+ tbe.expected_req_resp.addExpectedRespType(CHIResponseType:ReadReceipt);
+ tbe.expected_req_resp.addExpectedCount(1);
+ }
+
+ enqueue(reqOutPort, CHIRequestMsg, request_latency) {
+ prepareRequest(tbe, req, out_msg);
+ out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr));
+ out_msg.dataToFwdRequestor := true;
+ allowRequestRetry(tbe, out_msg);
+ }
+}
+
+action(Send_ReadOnce, desc="") {
+ assert(is_HN == false);
+ assert(tbe.dataValid == false);
+
+ clearExpectedReqResp(tbe);
+ tbe.expected_req_resp.addExpectedDataType(CHIDataType:DataSepResp_UC);
+ tbe.expected_req_resp.addExpectedDataType(CHIDataType:CompData_UC);
+ tbe.expected_req_resp.addExpectedDataType(CHIDataType:CompData_I);
+ // NOTE: the first CompData received counts as RespSepData
+ tbe.expected_req_resp.addExpectedRespType(CHIResponseType:RespSepData);
+ tbe.expected_req_resp.setExpectedCount(2);
+ tbe.dataBlkValid.clear();
+
+ enqueue(reqOutPort, CHIRequestMsg, request_latency) {
+ prepareRequest(tbe, CHIRequestType:ReadOnce, out_msg);
+ out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr));
+ out_msg.dataToFwdRequestor := false;
+ allowRequestRetry(tbe, out_msg);
+ }
+}
+
+action(Send_ReadUnique, desc="") {
+ assert((tbe.dataValid && tbe.dataUnique) == false);
+
+ assert(tbe.expected_req_resp.hasExpected() == false);
+ clearExpectedReqResp(tbe);
+ tbe.expected_req_resp.addExpectedDataType(CHIDataType:DataSepResp_UC);
+ tbe.expected_req_resp.addExpectedDataType(CHIDataType:CompData_UC);
+ tbe.expected_req_resp.addExpectedDataType(CHIDataType:CompData_UD_PD);
+ // NOTE: the first CompData received counts as RespSepData
+ tbe.expected_req_resp.addExpectedRespType(CHIResponseType:RespSepData);
+ tbe.expected_req_resp.setExpectedCount(2);
+
+ enqueue(reqOutPort, CHIRequestMsg, request_latency) {
+ prepareRequest(tbe, CHIRequestType:ReadUnique, out_msg);
+ out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr));
+ out_msg.dataToFwdRequestor := false;
+ allowRequestRetry(tbe, out_msg);
+ }
+}
+
+action(Send_CleanUnique, desc="") {
+ assert(tbe.dataValid || (tbe.dir_sharers.count() > 0));
+ assert(tbe.dataUnique == false);
+
+ assert(tbe.expected_req_resp.hasExpected() == false);
+ clearExpectedReqResp(tbe);
+ tbe.expected_req_resp.addExpectedRespType(CHIResponseType:Comp_UC);
+ tbe.expected_req_resp.setExpectedCount(1);
+
+ enqueue(reqOutPort, CHIRequestMsg, request_latency) {
+ prepareRequest(tbe, CHIRequestType:CleanUnique, out_msg);
+ out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr));
+ allowRequestRetry(tbe, out_msg);
+ }
+}
+
+action(Send_Evict, desc="") {
+ assert(is_valid(tbe));
+ assert(is_HN == false);
+ assert(tbe.expected_req_resp.hasExpected() == false);
+ clearExpectedReqResp(tbe);
+ enqueue(reqOutPort, CHIRequestMsg, request_latency) {
+ prepareRequest(tbe, CHIRequestType:Evict, out_msg);
+ out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr));
+ allowRequestRetry(tbe, out_msg);
+ }
+ tbe.expected_req_resp.addExpectedRespType(CHIResponseType:Comp_I);
+ tbe.expected_req_resp.setExpectedCount(1);
+}
+
+action(Send_InvSnpResp, desc="") {
+ assert(is_valid(tbe));
+ assert(tbe.dataMaybeDirtyUpstream == false);
+ if (tbe.dataDirty || tbe.snpNeedsData ||
+ (tbe.dataUnique && (tbe.reqType == CHIRequestType:SnpUnique))) {
+ tbe.actions.pushFront(Event:SendSnpData);
+ } else {
+ tbe.actions.pushFront(Event:SendSnpIResp);
+ }
+}
+
+action(Send_WriteBackOrWriteEvict, desc="") {
+ assert(is_valid(tbe));
+ assert(tbe.dataBlkValid.isFull());
+ assert(tbe.dataValid);
+ assert(is_HN == false);
+
+ assert(tbe.dataUnique || tbe.dataDirty);
+ assert(tbe.dir_sharers.isEmpty());
+
+ enqueue(reqOutPort, CHIRequestMsg, request_latency) {
+ if (tbe.dataDirty) {
+ prepareRequest(tbe, CHIRequestType:WriteBackFull, out_msg);
+ } else {
+ prepareRequest(tbe, CHIRequestType:WriteEvictFull, out_msg);
+ }
+ out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr));
+ allowRequestRetry(tbe, out_msg);
+ }
+ clearExpectedReqResp(tbe);
+ tbe.expected_req_resp.addExpectedRespType(CHIResponseType:CompDBIDResp);
+ tbe.expected_req_resp.setExpectedCount(1);
+}
+
+action(Send_WriteCleanFull, desc="") {
+ assert(is_valid(tbe));
+ assert(tbe.dataBlkValid.isFull());
+ assert(tbe.dataValid);
+ assert(is_HN == false);
+ assert(tbe.dataDirty);
+ assert(tbe.dataMaybeDirtyUpstream == false);
+
+ enqueue(reqOutPort, CHIRequestMsg, request_latency) {
+ prepareRequest(tbe, CHIRequestType:WriteCleanFull, out_msg);
+ out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr));
+ allowRequestRetry(tbe, out_msg);
+ }
+ clearExpectedReqResp(tbe);
+ tbe.expected_req_resp.addExpectedRespType(CHIResponseType:CompDBIDResp);
+ tbe.expected_req_resp.setExpectedCount(1);
+}
+
+action(Send_WriteNoSnp, desc="") {
+ assert(is_valid(tbe));
+
+ enqueue(reqOutPort, CHIRequestMsg, request_latency) {
+ prepareRequest(tbe, CHIRequestType:WriteNoSnp, out_msg);
+ out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr));
+ allowRequestRetry(tbe, out_msg);
+ }
+ // allow to expect this on top of data coming from upstream;
+ // so addExpectedCount
+ tbe.expected_req_resp.addExpectedRespType(CHIResponseType:CompDBIDResp);
+ tbe.expected_req_resp.addExpectedCount(1);
+}
+
+action(Send_WriteNoSnp_Partial, desc="") {
+ assert(is_valid(tbe));
+
+ enqueue(reqOutPort, CHIRequestMsg, request_latency) {
+ prepareRequest(tbe, CHIRequestType:WriteNoSnpPtl, out_msg);
+ out_msg.accAddr := tbe.accAddr;
+ out_msg.accSize := tbe.accSize;
+ out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr));
+ allowRequestRetry(tbe, out_msg);
+ }
+ // allow to expect this on top of data coming from upstream;
+ // so addExpectedCount
+ tbe.expected_req_resp.addExpectedRespType(CHIResponseType:CompDBIDResp);
+ tbe.expected_req_resp.addExpectedCount(1);
+}
+
+action(Send_WriteUnique, desc="") {
+ assert(is_valid(tbe));
+
+ enqueue(reqOutPort, CHIRequestMsg, request_latency) {
+ if (tbe.accSize == blockSize) {
+ prepareRequest(tbe, CHIRequestType:WriteUniqueFull, out_msg);
+ } else {
+ prepareRequest(tbe, CHIRequestType:WriteUniquePtl, out_msg);
+ out_msg.accAddr := tbe.accAddr;
+ out_msg.accSize := tbe.accSize;
+ }
+ out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr));
+ allowRequestRetry(tbe, out_msg);
+ }
+ // allow to expect this on top of data coming from upstream;
+ // so addExpectedCount
+ tbe.expected_req_resp.addExpectedRespType(CHIResponseType:CompDBIDResp);
+ // if receive only DBIDResp then will expect Comp later
+ tbe.expected_req_resp.addExpectedRespType(CHIResponseType:DBIDResp);
+ tbe.expected_req_resp.addExpectedCount(1);
+}
+
+action(Send_SnpCleanInvalid, desc="") {
+ assert(is_valid(tbe));
+ assert(tbe.expected_snp_resp.hasExpected() == false);
+ // at least one sharer or owner othrwise should not execute this
+ assert(tbe.dir_sharers.count() > 0);
+ enqueue(snpOutPort, CHIRequestMsg, snoop_latency) {
+ prepareRequest(tbe, CHIRequestType:SnpCleanInvalid, out_msg);
+ out_msg.Destination.addNetDest(tbe.dir_sharers);
+ out_msg.retToSrc := false;
+ }
+ setExpectedForInvSnoop(tbe, false);
+}
+
+action(Send_SnpCleanInvalid_NoReq, desc="") {
+ assert(is_valid(tbe));
+ assert(tbe.expected_snp_resp.hasExpected() == false);
+ enqueue(snpOutPort, CHIRequestMsg, snoop_latency) {
+ prepareRequest(tbe, CHIRequestType:SnpCleanInvalid, out_msg);
+ out_msg.Destination.addNetDest(tbe.dir_sharers);
+ out_msg.Destination.remove(tbe.requestor);
+ // at least one sharer other than requestor
+ assert(out_msg.Destination.count() > 0);
+ out_msg.retToSrc := false;
+ setExpectedForInvSnoop(tbe, false);
+ tbe.expected_snp_resp.setExpectedCount(out_msg.Destination.count());
+ }
+}
+
+action(Send_SnpUnique, desc="") {
+ assert(is_valid(tbe));
+ // at least one sharer or owner othrwise should not execute this
+ assert(tbe.dir_sharers.count() > 0);
+
+ setExpectedForInvSnoop(tbe, true);
+
+ enqueue(snpOutPort, CHIRequestMsg, snoop_latency) {
+ prepareRequest(tbe, CHIRequestType:SnpUnique, out_msg);
+ out_msg.Destination.addNetDest(tbe.dir_sharers);
+ out_msg.retToSrc := false;
+ }
+}
+
+action(Send_SnpUnique_RetToSrc, desc="") {
+ assert(is_valid(tbe));
+ // at least one sharer or owner othrwise should not execute this
+ assert(tbe.dir_sharers.count() > 0);
+
+ setExpectedForInvSnoop(tbe, true);
+
+ MachineID dest;
+ if (tbe.dir_ownerExists) {
+ dest := tbe.dir_owner;
+ } else {
+ // TODO should be random or the closest one
+ dest := tbe.dir_sharers.smallestElement();
+ }
+ enqueue(snpOutPort, CHIRequestMsg, snoop_latency) {
+ prepareRequest(tbe, CHIRequestType:SnpUnique, out_msg);
+ out_msg.Destination.add(dest);
+ out_msg.retToSrc := true;
+ }
+ // if other sharers send with retToSrc=false to others
+ if (tbe.dir_sharers.count() > 1) {
+ enqueue(snpOutPort, CHIRequestMsg, snoop_latency) {
+ prepareRequest(tbe, CHIRequestType:SnpUnique, out_msg);
+ out_msg.Destination.addNetDest(tbe.dir_sharers);
+ out_msg.Destination.remove(dest);
+ out_msg.retToSrc := false;
+ }
+ }
+}
+
+action(Send_SnpUniqueFwd, desc="") {
+ assert(is_valid(tbe));
+ // single sharer or owner otherwise should not execute this
+ assert(tbe.dir_sharers.count() == 1);
+
+ assert(tbe.expected_snp_resp.expected() == 0);
+ clearExpectedSnpResp(tbe);
+ tbe.expected_snp_resp.addExpectedRespType(CHIResponseType:SnpResp_I_Fwded_UC);
+ tbe.expected_snp_resp.addExpectedRespType(CHIResponseType:SnpResp_I_Fwded_UD_PD);
+ tbe.expected_snp_resp.addExpectedCount(1);
+
+ enqueue(snpOutPort, CHIRequestMsg, snoop_latency) {
+ prepareRequest(tbe, CHIRequestType:SnpUniqueFwd, out_msg);
+ out_msg.Destination.addNetDest(tbe.dir_sharers);
+ out_msg.retToSrc := false;
+ }
+}
+
+action(Send_SnpShared, desc="") {
+ assert(is_valid(tbe));
+
+ // only sent to a dirty or exclusive snoopee
+ assert(tbe.dataMaybeDirtyUpstream);
+ assert(tbe.dir_ownerExists);
+ assert(tbe.dir_sharers.count() > 0);
+
+ assert(tbe.expected_snp_resp.expected() == 0);
+ clearExpectedSnpResp(tbe);
+ tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_SC);
+ tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_SC_PD);
+ tbe.expected_snp_resp.setExpectedCount(1);
+
+ enqueue(snpOutPort, CHIRequestMsg, snoop_latency) {
+ prepareRequest(tbe, CHIRequestType:SnpShared, out_msg);
+ out_msg.Destination.add(tbe.dir_owner);
+ out_msg.retToSrc := false;
+ }
+}
+
+action(Send_SnpSharedFwd_ToOwner, desc="") {
+ assert(is_valid(tbe));
+
+ // the dirty snoopee must go to SC and send data
+ assert(tbe.dataMaybeDirtyUpstream);
+ assert(tbe.dir_ownerExists);
+ assert(tbe.dir_sharers.count() > 0);
+
+ assert(tbe.expected_snp_resp.expected() == 0);
+ clearExpectedSnpResp(tbe);
+
+ bool allowFwdSD := tbe.reqType != CHIRequestType:ReadNotSharedDirty;
+
+ // get us a copy if we have allocated a cache entry for this block
+ bool retToSrc := tbe.doCacheFill && (tbe.dataToBeInvalid == false);
+
+ if (allowFwdSD) {
+ if (retToSrc) {
+ tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_SC_Fwded_SC);
+ tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_SC_Fwded_SD_PD);
+ tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_I_Fwded_SC);
+ tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_I_Fwded_SD_PD);
+ } else {
+ tbe.expected_snp_resp.addExpectedRespType(CHIResponseType:SnpResp_SC_Fwded_SC);
+ tbe.expected_snp_resp.addExpectedRespType(CHIResponseType:SnpResp_SC_Fwded_SD_PD);
+ }
+ } else {
+ if (retToSrc) {
+ tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_SC_Fwded_SC);
+ tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_I_Fwded_SC);
+ } else {
+ tbe.expected_snp_resp.addExpectedRespType(CHIResponseType:SnpResp_SC_Fwded_SC);
+ }
+ tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_SC_PD_Fwded_SC);
+ tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_I_PD_Fwded_SC);
+ }
+ tbe.expected_snp_resp.addExpectedCount(1);
+
+ enqueue(snpOutPort, CHIRequestMsg, snoop_latency) {
+ if (allowFwdSD) {
+ prepareRequest(tbe, CHIRequestType:SnpSharedFwd, out_msg);
+ } else {
+ prepareRequest(tbe, CHIRequestType:SnpNotSharedDirtyFwd, out_msg);
+ }
+ out_msg.Destination.add(tbe.dir_owner);
+ out_msg.retToSrc := retToSrc;
+ }
+}
+
+action(Send_SnpSharedFwd_ToSharer, desc="") {
+ assert(is_valid(tbe));
+ // send to onde of the sharers with shared clean data
+ assert(tbe.dataMaybeDirtyUpstream == false);
+ assert(tbe.dir_ownerExists == false);
+ assert(tbe.dir_sharers.count() > 0);
+
+ assert(tbe.expected_snp_resp.expected() == 0);
+ clearExpectedSnpResp(tbe);
+ // if we have a block allocated for this line, asks snoopee to forward
+ // data to us as well
+ bool retToSrc := tbe.doCacheFill;
+ if (retToSrc) {
+ tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_SC_Fwded_SC);
+ } else {
+ tbe.expected_snp_resp.addExpectedRespType(CHIResponseType:SnpResp_SC_Fwded_SC);
+ }
+ tbe.expected_snp_resp.addExpectedCount(1);
+
+ enqueue(snpOutPort, CHIRequestMsg, snoop_latency) {
+ prepareRequest(tbe, CHIRequestType:SnpSharedFwd, out_msg);
+ // TODO should be random or the closest one to the fwd dest
+ out_msg.Destination.add(tbe.dir_sharers.smallestElement());
+ out_msg.retToSrc := retToSrc;
+ }
+}
+
+action(Send_SnpOnce, desc="") {
+ assert(is_valid(tbe));
+
+ // send to one of the sharers or owner to get a copy of the line
+ assert(tbe.dir_sharers.count() > 0);
+
+ assert(tbe.expected_snp_resp.expected() == 0);
+ clearExpectedSnpResp(tbe);
+
+ if (tbe.dir_ownerExists) {
+ if (tbe.dir_ownerIsExcl) {
+ tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_UC);
+ tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_UD);
+ } else {
+ tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_SD);
+ }
+ } else {
+ tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_SC);
+ }
+ tbe.expected_snp_resp.addExpectedCount(1);
+
+ enqueue(snpOutPort, CHIRequestMsg, snoop_latency) {
+ prepareRequest(tbe, CHIRequestType:SnpOnce, out_msg);
+ if (tbe.dir_ownerExists) {
+ out_msg.Destination.add(tbe.dir_owner);
+ } else {
+ // TODO should be random or the closest one
+ out_msg.Destination.add(tbe.dir_sharers.smallestElement());
+ }
+ out_msg.retToSrc := true;
+ }
+}
+
+action(Send_SnpOnceFwd, desc="") {
+ assert(is_valid(tbe));
+
+ // send to one of the sharers or owner to get a copy of the line
+ assert(tbe.dir_sharers.count() > 0);
+
+ assert(tbe.expected_snp_resp.expected() == 0);
+ clearExpectedSnpResp(tbe);
+
+ if (tbe.dir_ownerExists) {
+ if (tbe.dir_ownerIsExcl) {
+ tbe.expected_snp_resp.addExpectedRespType(CHIResponseType:SnpResp_UC_Fwded_I);
+ tbe.expected_snp_resp.addExpectedRespType(CHIResponseType:SnpResp_UD_Fwded_I);
+ } else {
+ tbe.expected_snp_resp.addExpectedRespType(CHIResponseType:SnpResp_SD_Fwded_I);
+ }
+ } else {
+ tbe.expected_snp_resp.addExpectedRespType(CHIResponseType:SnpResp_SC_Fwded_I);
+ }
+ tbe.expected_snp_resp.addExpectedCount(1);
+
+ enqueue(snpOutPort, CHIRequestMsg, snoop_latency) {
+ prepareRequest(tbe, CHIRequestType:SnpOnceFwd, out_msg);
+ if (tbe.dir_ownerExists) {
+ out_msg.Destination.add(tbe.dir_owner);
+ } else {
+ // TODO should be random or the closest one
+ out_msg.Destination.add(tbe.dir_sharers.smallestElement());
+ }
+ out_msg.retToSrc := false;
+ }
+}
+
+
+action(ExpectNCBWrData, desc="") {
+ // Expected data
+ int num_msgs := tbe.accSize / data_channel_size;
+ if ((tbe.accSize % data_channel_size) != 0) {
+ num_msgs := num_msgs + 1;
+ }
+ tbe.expected_req_resp.clear(num_msgs);
+ tbe.expected_req_resp.addExpectedDataType(CHIDataType:NCBWrData);
+ tbe.expected_req_resp.setExpectedCount(1);
+
+ // Clear the mask bits we expect to receive
+ tbe.dataBlkValid.setMask(addressOffset(tbe.accAddr, tbe.addr), tbe.accSize, false);
+}
+
+action(ExpectCompAck, desc="") {
+ assert(is_valid(tbe));
+ tbe.expected_req_resp.addExpectedRespType(CHIResponseType:CompAck);
+ tbe.expected_req_resp.addExpectedCount(1);
+}
+
+action(Receive_ReqDataResp, desc="") {
+ assert(is_valid(tbe));
+ assert(tbe.expected_req_resp.hasExpected());
+ peek(datInPort, CHIDataMsg) {
+ // Decrement pending
+ if (tbe.expected_req_resp.receiveData(in_msg.type) == false) {
+ error("Received unexpected message");
+ }
+ // Copy data to tbe only if we didn't have valid data or the received
+ // data is dirty
+ if ((tbe.dataBlkValid.isFull() == false) ||
+ (in_msg.type == CHIDataType:CompData_UD_PD) ||
+ (in_msg.type == CHIDataType:CompData_SD_PD) ||
+ (in_msg.type == CHIDataType:CBWrData_UD_PD) ||
+ (in_msg.type == CHIDataType:CBWrData_SD_PD) ||
+ (in_msg.type == CHIDataType:NCBWrData)) {
+ // clear mask if started to receive new data
+ if(tbe.dataBlkValid.isFull()){
+ tbe.dataBlkValid.clear();
+ }
+ tbe.dataBlk.copyPartial(in_msg.dataBlk, in_msg.bitMask);
+ assert(tbe.dataBlkValid.isOverlap(in_msg.bitMask) == false);
+ tbe.dataBlkValid.orMask(in_msg.bitMask);
+ }
+ }
+}
+
+action(Receive_RespSepDataFromCompData, desc="") {
+ assert(is_valid(tbe));
+ assert(tbe.expected_req_resp.hasExpected());
+ // check if a previous CompData msg already counted as a RespSepData
+ if (tbe.expected_req_resp.receivedRespType(CHIResponseType:RespSepData) == false) {
+ if (tbe.expected_req_resp.receiveResp(CHIResponseType:RespSepData) == false) {
+ error("Received unexpected message");
+ }
+ if (is_HN == false) {
+ // must now ack the responder
+ tbe.actions.pushFrontNB(Event:SendCompAck);
+ }
+ }
+}
+
+action(Receive_RespSepData, desc="") {
+ assert(is_valid(tbe));
+ assert(tbe.expected_req_resp.hasExpected());
+ if (tbe.expected_req_resp.receiveResp(CHIResponseType:RespSepData) == false) {
+ error("Received unexpected message");
+ }
+ if (is_HN == false) {
+ // must now ack the responder
+ tbe.actions.pushFrontNB(Event:SendCompAck);
+ }
+}
+
+action(Receive_ReadReceipt, desc="") {
+ assert(is_valid(tbe));
+ assert(tbe.expected_req_resp.hasExpected());
+ if (tbe.expected_req_resp.receiveResp(CHIResponseType:ReadReceipt) == false) {
+ error("Received unexpected message");
+ }
+}
+
+action(Receive_SnpDataResp, desc="") {
+ assert(is_valid(tbe));
+ assert(tbe.expected_snp_resp.hasExpected());
+ peek(datInPort, CHIDataMsg) {
+ // Decrement pending
+ if (tbe.expected_snp_resp.receiveData(in_msg.type) == false) {
+ error("Received unexpected message");
+ }
+ // Copy data to tbe only if we didn't have valid data or the received
+ // data is dirty
+ if ((tbe.dataBlkValid.isFull() == false) ||
+ (in_msg.type == CHIDataType:SnpRespData_I_PD) ||
+ (in_msg.type == CHIDataType:SnpRespData_SC_PD) ||
+ (in_msg.type == CHIDataType:SnpRespData_SC_Fwded_SD_PD) ||
+ (in_msg.type == CHIDataType:SnpRespData_SC_PD_Fwded_SC) ||
+ (in_msg.type == CHIDataType:SnpRespData_I_Fwded_SD_PD) ||
+ (in_msg.type == CHIDataType:SnpRespData_I_PD_Fwded_SC)) {
+ // clear mask if started to receive new data
+ if(tbe.dataBlkValid.isFull()){
+ tbe.dataBlkValid.clear();
+ }
+ tbe.dataBlk.copyPartial(in_msg.dataBlk, in_msg.bitMask);
+ assert(tbe.dataBlkValid.isOverlap(in_msg.bitMask) == false);
+ tbe.dataBlkValid.orMask(in_msg.bitMask);
+ }
+ }
+}
+
+action(UpdateDirState_FromReqDataResp, desc="") {
+ assert(is_valid(tbe));
+ // only perform the update once we received all chunks
+ if (tbe.expected_req_resp.hasReceivedData()) {
+ assert(tbe.dataBlkValid.isFull());
+ peek(datInPort, CHIDataMsg) {
+
+ if (in_msg.type == CHIDataType:CBWrData_UC) {
+ assert(tbe.dir_ownerExists && tbe.dir_ownerIsExcl && (tbe.dir_owner == in_msg.responder));
+ assert(tbe.dir_sharers.isElement(in_msg.responder));
+ tbe.dir_ownerExists := false;
+ tbe.dir_ownerIsExcl := false;
+ tbe.dir_sharers.remove(in_msg.responder);
+
+ } else if (in_msg.type == CHIDataType:CBWrData_UD_PD) {
+ assert(tbe.dir_ownerExists && tbe.dir_ownerIsExcl && (tbe.dir_owner == in_msg.responder));
+ assert(tbe.dir_sharers.isElement(in_msg.responder));
+ if (tbe.pendReqType != CHIRequestType:WriteCleanFull) {
+ tbe.dir_ownerExists := false;
+ tbe.dir_ownerIsExcl := false;
+ tbe.dir_sharers.remove(in_msg.responder);
+ }
+
+ } else if (in_msg.type == CHIDataType:CBWrData_SC) {
+ assert((tbe.dir_ownerExists == false) || (tbe.dir_owner != in_msg.responder));
+ tbe.dir_sharers.remove(in_msg.responder);
+
+ } else if (in_msg.type == CHIDataType:CBWrData_SD_PD) {
+ assert(tbe.dir_ownerExists && (tbe.dir_ownerIsExcl == false) && (tbe.dir_owner == in_msg.responder));
+ assert(tbe.dir_sharers.isElement(in_msg.responder));
+ tbe.dir_ownerExists := false;
+ tbe.dir_ownerIsExcl := false;
+ if (tbe.pendReqType != CHIRequestType:WriteCleanFull) {
+ tbe.dir_sharers.remove(in_msg.responder);
+ }
+
+ } else if (in_msg.type == CHIDataType:CBWrData_I) {
+ // nothing to do here; just check
+ assert((tbe.dir_ownerExists == false) || (tbe.dir_owner != in_msg.responder));
+ assert(tbe.dir_sharers.isElement(in_msg.responder) == false);
+
+ } else {
+ error("Unsuported data type");
+ }
+ }
+ }
+ printTBEState(tbe);
+}
+
+action(UpdateDirState_FromSnpDataResp, desc="") {
+ assert(is_valid(tbe));
+ // only perform the update once we received all chunks
+ if (tbe.expected_snp_resp.hasReceivedData()) {
+ assert(tbe.dataBlkValid.isFull());
+ peek(datInPort, CHIDataMsg) {
+
+ if (in_msg.type == CHIDataType:SnpRespData_I) {
+ assert(tbe.dir_sharers.isElement(in_msg.responder));
+ tbe.dir_ownerExists := false;
+ tbe.dir_ownerIsExcl := false;
+ tbe.dir_sharers.remove(in_msg.responder);
+
+ } else if (in_msg.type == CHIDataType:SnpRespData_I_PD) {
+ assert(tbe.dir_ownerExists && (tbe.dir_owner == in_msg.responder));
+ assert(tbe.dir_sharers.isElement(in_msg.responder));
+ tbe.dir_ownerExists := false;
+ tbe.dir_ownerIsExcl := false;
+ tbe.dir_sharers.remove(in_msg.responder);
+
+ } else if ((in_msg.type == CHIDataType:SnpRespData_SC_PD) ||
+ (in_msg.type == CHIDataType:SnpRespData_SC) ||
+ (in_msg.type == CHIDataType:SnpRespData_SC_Fwded_SC) ||
+ (in_msg.type == CHIDataType:SnpRespData_SC_Fwded_SD_PD) ||
+ (in_msg.type == CHIDataType:SnpRespData_SC_PD_Fwded_SC)) {
+ // the owner must have been the responder, if there was one
+ assert((tbe.dir_ownerExists == false) ||
+ (tbe.dir_ownerExists && (tbe.dir_owner == in_msg.responder)));
+ assert(tbe.dir_sharers.isElement(in_msg.responder));
+ tbe.dir_ownerExists := false;
+ tbe.dir_ownerIsExcl := false;
+ if ((in_msg.type == CHIDataType:SnpRespData_SC_Fwded_SC) ||
+ (in_msg.type == CHIDataType:SnpRespData_SC_PD_Fwded_SC) ||
+ (in_msg.type == CHIDataType:SnpRespData_SC_Fwded_SD_PD)) {
+ tbe.dir_sharers.add(tbe.requestor);
+ }
+ if (in_msg.type == CHIDataType:SnpRespData_SC_Fwded_SD_PD) {
+ tbe.dir_ownerExists := true;
+ tbe.dir_owner := tbe.requestor;
+ }
+
+ } else if ((in_msg.type == CHIDataType:SnpRespData_I_Fwded_SD_PD) ||
+ (in_msg.type == CHIDataType:SnpRespData_I_PD_Fwded_SC) ||
+ (in_msg.type == CHIDataType:SnpRespData_I_Fwded_SC)) {
+ // the owner must have been the responder, if there was one
+ assert((tbe.dir_ownerExists == false) ||
+ (tbe.dir_ownerExists && (tbe.dir_owner == in_msg.responder)));
+ assert(tbe.dir_sharers.isElement(in_msg.responder));
+ tbe.dir_ownerExists := false;
+ tbe.dir_ownerIsExcl := false;
+ tbe.dir_sharers.remove(in_msg.responder);
+ tbe.dir_sharers.add(tbe.requestor);
+ if (in_msg.type == CHIDataType:SnpRespData_I_Fwded_SD_PD) {
+ tbe.dir_ownerExists := true;
+ tbe.dir_owner := tbe.requestor;
+ }
+
+ } else if ((in_msg.type == CHIDataType:SnpRespData_SD) ||
+ (in_msg.type == CHIDataType:SnpRespData_UC) ||
+ (in_msg.type == CHIDataType:SnpRespData_UD)) {
+ // expected only in response to a SnpOnce; just do some checks
+ // also may get SnpRespData_SC, but handled properly above
+ assert(tbe.dir_ownerExists && (tbe.dir_owner == in_msg.responder));
+ assert(tbe.dir_sharers.isElement(in_msg.responder));
+
+ } else {
+ error("Unsuported data type");
+ }
+ }
+ }
+ printTBEState(tbe);
+}
+
+action(UpdateDataState_FromReqDataResp, desc="") {
+ assert(is_valid(tbe));
+ // only perform the update once we received all chunks
+ if (tbe.expected_req_resp.hasReceivedData()) {
+ assert(tbe.dataBlkValid.isFull());
+ peek(datInPort, CHIDataMsg) {
+
+ if ((in_msg.type == CHIDataType:CompData_UC) ||
+ (in_msg.type == CHIDataType:DataSepResp_UC)) {
+ assert(tbe.dataUnique == false);
+ assert((tbe.dataValid && tbe.dataDirty) == false);
+ tbe.dataDirty := false;
+ tbe.dataUnique := true;
+ tbe.dataValid := true;
+ assert(tbe.dataMaybeDirtyUpstream == false);
+
+ } else if (in_msg.type == CHIDataType:CompData_UD_PD) {
+ assert(tbe.dataUnique == false);
+ assert((tbe.dataValid && tbe.dataDirty) == false);
+ tbe.dataDirty := true;
+ tbe.dataUnique := true;
+ tbe.dataValid := true;
+ assert(tbe.dataMaybeDirtyUpstream == false);
+
+ } else if (in_msg.type == CHIDataType:CompData_SC) {
+ assert(tbe.dataUnique == false);
+ assert((tbe.dataValid && tbe.dataDirty) == false);
+ tbe.dataDirty := false;
+ tbe.dataUnique := false;
+ tbe.dataValid := true;
+ assert(tbe.dataMaybeDirtyUpstream == false);
+
+ } else if (in_msg.type == CHIDataType:CompData_SD_PD) {
+ assert(tbe.dataUnique == false);
+ assert((tbe.dataValid && tbe.dataDirty) == false);
+ tbe.dataDirty := true;
+ tbe.dataUnique := false;
+ tbe.dataValid := true;
+ assert(tbe.dataMaybeDirtyUpstream == false);
+
+ } else if (in_msg.type == CHIDataType:CompData_I) {
+ tbe.dataValid := true;
+ tbe.dataToBeInvalid := true;
+ assert(tbe.dataMaybeDirtyUpstream == false);
+
+ } else if (in_msg.type == CHIDataType:CBWrData_UC) {
+ assert(tbe.dataUnique);
+ tbe.dataMaybeDirtyUpstream := false;
+ tbe.dataValid := true;
+
+ } else if (in_msg.type == CHIDataType:CBWrData_SC) {
+ // stale WB, nothing to do ??
+
+ } else if (in_msg.type == CHIDataType:CBWrData_UD_PD) {
+ assert(tbe.dataUnique);
+ tbe.dataDirty := true;
+ tbe.dataValid := true;
+ tbe.dataMaybeDirtyUpstream := false;
+
+ } else if (in_msg.type == CHIDataType:CBWrData_SD_PD) {
+ tbe.dataDirty := true;
+ tbe.dataValid := true;
+ tbe.dataMaybeDirtyUpstream := false;
+
+ } else if (in_msg.type == CHIDataType:CBWrData_I) {
+ // stale WB, nothing to do ??
+
+ } else {
+ error("Unsuported data type");
+ }
+ }
+ }
+ printTBEState(tbe);
+}
+
+action(UpdateDataState_FromWUDataResp, desc="") {
+ assert(is_valid(tbe));
+ int offset := addressOffset(tbe.accAddr, tbe.addr);
+ if (tbe.expected_req_resp.hasReceivedData()) {
+ assert(tbe.dataBlkValid.test(offset));
+ assert(tbe.dataBlkValid.test(offset + tbe.accSize - 1));
+ peek(datInPort, CHIDataMsg) {
+ assert(in_msg.type == CHIDataType:NCBWrData);
+ tbe.dataDirty := true;
+ tbe.dataValid := tbe.accSize == blockSize;
+ }
+ }
+ printTBEState(tbe);
+}
+
+action(UpdateDataState_FromCUResp, desc="") {
+ assert(is_valid(tbe));
+ peek(rspInPort, CHIResponseMsg) {
+ assert(in_msg.type == CHIResponseType:Comp_UC);
+ assert(tbe.dataUnique == false);
+ tbe.dataUnique := tbe.dataValid || (tbe.dir_sharers.count() > 0);
+ // self and upstream may have been invalidated while waiting for this
+ // expect to follow up with a ReadUnique
+ }
+ printTBEState(tbe);
+}
+
+action(UpdateDataState_FromSnpDataResp, desc="") {
+ assert(is_valid(tbe));
+ // only perform the update once we received all chunks
+ if (tbe.expected_snp_resp.hasReceivedData()) {
+ assert(tbe.dataBlkValid.isFull());
+ peek(datInPort, CHIDataMsg) {
+
+ if ((in_msg.type == CHIDataType:SnpRespData_I_PD) ||
+ (in_msg.type == CHIDataType:SnpRespData_SC_PD) ||
+ (in_msg.type == CHIDataType:SnpRespData_SC_PD_Fwded_SC) ||
+ (in_msg.type == CHIDataType:SnpRespData_I_PD_Fwded_SC)) {
+ tbe.dataDirty := true;
+ tbe.dataValid := true;
+ tbe.dataMaybeDirtyUpstream := false;
+
+ } else if ((in_msg.type == CHIDataType:SnpRespData_SD) ||
+ (in_msg.type == CHIDataType:SnpRespData_SC_Fwded_SD_PD) ||
+ (in_msg.type == CHIDataType:SnpRespData_I_Fwded_SD_PD)) {
+ tbe.dataDirty := true;
+ tbe.dataValid := true;
+ tbe.dataMaybeDirtyUpstream := true;
+
+ } else if ((in_msg.type == CHIDataType:SnpRespData_I) ||
+ (in_msg.type == CHIDataType:SnpRespData_SC) ||
+ (in_msg.type == CHIDataType:SnpRespData_SC_Fwded_SC) ||
+ (in_msg.type == CHIDataType:SnpRespData_I_Fwded_SC)) {
+ tbe.dataValid := true;
+ tbe.dataMaybeDirtyUpstream := false;
+
+ } else if ((in_msg.type == CHIDataType:SnpRespData_UC) ||
+ (in_msg.type == CHIDataType:SnpRespData_UD)) {
+ tbe.dataValid := true;
+ tbe.dataUnique := true;
+ tbe.dataMaybeDirtyUpstream := true;
+ if (in_msg.type == CHIDataType:SnpRespData_UD){
+ tbe.dataDirty := true;
+ }
+
+ } else {
+ error("Unsuported data type");
+ }
+ }
+ }
+ printTBEState(tbe);
+}
+
+action(UpdateDirState_FromReqResp, desc="") {
+ peek(rspInPort, CHIResponseMsg) {
+ if ((in_msg.type == CHIResponseType:CompAck) && tbe.updateDirOnCompAck) {
+ assert(tbe.requestor == in_msg.responder);
+
+ tbe.dir_sharers.add(in_msg.responder);
+
+ if (tbe.requestorToBeOwner) {
+ assert(tbe.dataMaybeDirtyUpstream);
+ assert(tbe.dir_ownerExists == false);
+ assert(tbe.requestorToBeExclusiveOwner == false);
+ tbe.dir_owner := in_msg.responder;
+ tbe.dir_ownerExists := true;
+ tbe.dir_ownerIsExcl := false;
+
+ } else if (tbe.requestorToBeExclusiveOwner) {
+ assert(tbe.dataMaybeDirtyUpstream);
+ assert(tbe.dir_ownerExists == false);
+ assert(tbe.dir_sharers.count() == 1);
+ tbe.dir_owner := in_msg.responder;
+ tbe.dir_ownerExists := true;
+ tbe.dir_ownerIsExcl := true;
+ }
+ }
+ }
+ printTBEState(tbe);
+}
+
+action(UpdateDirState_FromSnpResp, desc="") {
+ peek(rspInPort, CHIResponseMsg) {
+
+ if (in_msg.type == CHIResponseType:SnpResp_I) {
+ // must have been a known sharer otherwise we would receive data
+ assert(tbe.dir_sharers.isElement(in_msg.responder));
+ tbe.dir_sharers.remove(in_msg.responder);
+ if (tbe.dir_ownerExists && (tbe.dir_owner == in_msg.responder)){
+ tbe.dir_ownerExists := false;
+ }
+
+ } else if (in_msg.type == CHIResponseType:SnpResp_SC) {
+ // expected from a sharer that already has it in shared state
+ assert(tbe.dir_sharers.isElement(in_msg.responder));
+ assert((tbe.dir_ownerExists == false) || (tbe.dir_owner != in_msg.responder));
+
+ } else if ((in_msg.type == CHIResponseType:SnpResp_SC_Fwded_SC) ||
+ (in_msg.type == CHIResponseType:SnpResp_SC_Fwded_SD_PD)) {
+ // the SnpSharedFwd must have been sent to the owner if there was one
+ assert((tbe.dir_ownerExists == false) ||
+ (tbe.dir_ownerExists && (tbe.dir_owner == in_msg.responder)));
+ assert(tbe.dir_sharers.isElement(in_msg.responder));
+ tbe.dir_ownerExists := false;
+ tbe.dir_ownerIsExcl := false;
+ tbe.dir_sharers.add(tbe.requestor);
+ if (in_msg.type == CHIResponseType:SnpResp_SC_Fwded_SD_PD) {
+ // Requestor is new owner
+ tbe.dir_ownerExists := true;
+ tbe.dir_owner := tbe.requestor;
+ }
+
+ } else if ((in_msg.type == CHIResponseType:SnpResp_I_Fwded_UC) ||
+ (in_msg.type == CHIResponseType:SnpResp_I_Fwded_UD_PD)) {
+ // must have been a single sharer that received SnpUniqueFwd
+ assert(tbe.dir_sharers.isElement(in_msg.responder));
+ assert(tbe.dir_sharers.count() == 1);
+ tbe.dir_sharers.remove(in_msg.responder);
+ // requestor is the new owner
+ tbe.dir_sharers.add(tbe.requestor);
+ tbe.dir_ownerExists := true;
+ tbe.dir_ownerIsExcl := true;
+ tbe.dir_owner := tbe.requestor;
+
+ } else if ((in_msg.type == CHIResponseType:SnpResp_UC_Fwded_I) ||
+ (in_msg.type == CHIResponseType:SnpResp_UD_Fwded_I) ||
+ (in_msg.type == CHIResponseType:SnpResp_SD_Fwded_I)) {
+ // SnpSharedFwd; just confirm
+ assert(tbe.dir_sharers.isElement(in_msg.responder));
+ assert(tbe.dir_ownerExists && (tbe.dir_owner == in_msg.responder));
+
+ } else if (in_msg.type == CHIResponseType:SnpResp_SC_Fwded_I) {
+ // SnpSharedFwd; just confirm
+ assert(tbe.dir_sharers.isElement(in_msg.responder));
+ assert((tbe.dir_ownerExists == false) || (tbe.dir_owner != in_msg.responder));
+ }
+
+ tbe.dataMaybeDirtyUpstream := tbe.dir_ownerExists;
+
+ }
+ printTBEState(tbe);
+}
+
+action(Receive_ReqResp, desc="") {
+ assert(tbe.expected_req_resp.hasExpected());
+ peek(rspInPort, CHIResponseMsg) {
+ // Decrement pending
+ if (tbe.expected_req_resp.receiveResp(in_msg.type) == false) {
+ error("Received unexpected message");
+ }
+ assert(in_msg.stale == tbe.is_stale);
+ }
+}
+
+action(Receive_ReqResp_WUNeedComp, desc="") {
+ tbe.defer_expected_comp := true;
+}
+
+action(Receive_ReqResp_WUComp, desc="") {
+ if (tbe.defer_expected_comp) {
+ tbe.defer_expected_comp := false;
+ } else if (tbe.expected_req_resp.receiveResp(CHIResponseType:Comp) == false) {
+ error("Received unexpected message");
+ }
+}
+
+action(Receive_SnpResp, desc="") {
+ assert(tbe.expected_snp_resp.hasExpected());
+ peek(rspInPort, CHIResponseMsg) {
+ // Decrement pending
+ if (tbe.expected_snp_resp.receiveResp(in_msg.type) == false) {
+ error("Received unexpected message");
+ }
+ assert(in_msg.stale == tbe.is_stale);
+ }
+}
+
+action(Receive_RetryAck, desc="") {
+ assert(is_valid(tbe));
+ assert(tbe.pendReqAllowRetry);
+ assert(tbe.rcvdRetryAck == false);
+ tbe.rcvdRetryAck := true;
+ destsWaitingRetry.addNetDest(tbe.pendReqDest);
+ enqueueDoRetry(tbe);
+}
+
+action(Receive_PCrdGrant, desc="") {
+ assert(tbe.pendReqAllowRetry);
+ assert(tbe.rcvdRetryCredit == false);
+ tbe.rcvdRetryCredit := true;
+ enqueueDoRetry(tbe);
+}
+
+action(Send_Retry, desc="") {
+ assert(tbe.pendReqAllowRetry);
+ assert(tbe.rcvdRetryCredit);
+ assert(tbe.rcvdRetryAck);
+ enqueue(reqOutPort, CHIRequestMsg, request_latency) {
+ prepareRequestRetry(tbe, out_msg);
+ }
+}
+
+action(Receive_RetryAck_Hazard, desc="") {
+ TBE hazard_tbe := getHazardTBE(tbe);
+ assert(hazard_tbe.pendReqAllowRetry);
+ assert(hazard_tbe.rcvdRetryAck == false);
+ hazard_tbe.rcvdRetryAck := true;
+ destsWaitingRetry.addNetDest(hazard_tbe.pendReqDest);
+ enqueueDoRetry(hazard_tbe);
+}
+
+action(Receive_PCrdGrant_Hazard, desc="") {
+ TBE hazard_tbe := getHazardTBE(tbe);
+ assert(hazard_tbe.pendReqAllowRetry);
+ assert(hazard_tbe.rcvdRetryCredit == false);
+ hazard_tbe.rcvdRetryCredit := true;
+ enqueueDoRetry(hazard_tbe);
+}
+
+action(Send_Retry_Hazard, desc="") {
+ TBE hazard_tbe := getHazardTBE(tbe);
+ assert(hazard_tbe.pendReqAllowRetry);
+ assert(hazard_tbe.rcvdRetryCredit);
+ assert(hazard_tbe.rcvdRetryAck);
+ enqueue(reqOutPort, CHIRequestMsg, request_latency) {
+ prepareRequestRetry(hazard_tbe, out_msg);
+ }
+}
+
+action(Send_CompData, desc="") {
+ assert(is_valid(tbe));
+ assert(tbe.dataValid);
+
+ bool is_rd_once := tbe.reqType == CHIRequestType:ReadOnce;
+ bool is_rd_shared := (tbe.reqType == CHIRequestType:ReadShared) ||
+ (tbe.reqType == CHIRequestType:ReadNotSharedDirty);
+ bool is_rd_nsd := tbe.reqType == CHIRequestType:ReadNotSharedDirty;
+ bool is_rd_unique := tbe.reqType == CHIRequestType:ReadUnique;
+
+ if (is_rd_once) {
+ tbe.snd_msgType := CHIDataType:CompData_I;
+ } else if (tbe.dataToBeInvalid) {
+ // We will drop the data so propagate it's coherent state upstream
+ if (tbe.dataUnique && tbe.dataDirty) {
+ tbe.snd_msgType := CHIDataType:CompData_UD_PD;
+ } else if (tbe.dataUnique) {
+ tbe.snd_msgType := CHIDataType:CompData_UC;
+ } else if (tbe.dataDirty) {
+ if (is_rd_nsd) {
+ tbe.snd_msgType := CHIDataType:CompData_SC;
+ } else {
+ tbe.snd_msgType := CHIDataType:CompData_SD_PD;
+ }
+ } else {
+ tbe.snd_msgType := CHIDataType:CompData_SC;
+ }
+ } else if (is_rd_unique ||
+ (is_rd_shared && tbe.dataUnique &&
+ fwd_unique_on_readshared && (tbe.dir_ownerExists == false))) {
+ // propagates dirtyness
+ assert(tbe.dataUnique);
+ if (tbe.dataDirty) {
+ tbe.snd_msgType := CHIDataType:CompData_UD_PD;
+ } else {
+ tbe.snd_msgType := CHIDataType:CompData_UC;
+ }
+ } else if (is_rd_shared) {
+ // still keeping a copy so can send as SC
+ tbe.snd_msgType := CHIDataType:CompData_SC;
+ } else {
+ error("Invalid request type");
+ }
+
+ tbe.dataMaybeDirtyUpstream := tbe.dataMaybeDirtyUpstream ||
+ (tbe.snd_msgType == CHIDataType:CompData_UD_PD) ||
+ (tbe.snd_msgType == CHIDataType:CompData_SD_PD) ||
+ (tbe.snd_msgType == CHIDataType:CompData_UC);
+ tbe.requestorToBeExclusiveOwner := tbe.requestorToBeExclusiveOwner ||
+ (tbe.snd_msgType == CHIDataType:CompData_UD_PD) ||
+ (tbe.snd_msgType == CHIDataType:CompData_UC);
+ tbe.requestorToBeOwner := tbe.requestorToBeOwner ||
+ (tbe.snd_msgType == CHIDataType:CompData_SD_PD);
+
+ tbe.snd_destination := tbe.requestor;
+ setupPendingSend(tbe);
+ printTBEState(tbe);
+}
+
+action(Send_WBData, desc="") {
+ assert(is_valid(tbe));
+ if (is_HN) {
+ assert(tbe.dataBlkValid.isFull());
+ assert(tbe.dataDirty);
+ assert(tbe.dataValid);
+ tbe.snd_msgType := CHIDataType:NCBWrData;
+ } else {
+ if (tbe.dataValid == false) {
+ // only possible when the WB was made stale by a snoop
+ assert(tbe.is_stale);
+ tbe.dataBlkValid.fillMask();
+ tbe.snd_msgType := CHIDataType:CBWrData_I;
+ } else if (tbe.dataUnique) {
+ assert(tbe.dataBlkValid.isFull());
+ if (tbe.dataDirty) {
+ tbe.snd_msgType := CHIDataType:CBWrData_UD_PD;
+ } else {
+ tbe.snd_msgType := CHIDataType:CBWrData_UC;
+ }
+ } else {
+ assert(tbe.dataBlkValid.isFull());
+ if (tbe.dataDirty) {
+ tbe.snd_msgType := CHIDataType:CBWrData_SD_PD;
+ } else {
+ tbe.snd_msgType := CHIDataType:CBWrData_SC;
+ }
+ }
+ }
+ tbe.snd_destination := mapAddressToDownstreamMachine(tbe.addr);
+ setupPendingSend(tbe);
+}
+
+action(Send_WUData, desc="") {
+ assert(is_valid(tbe));
+ assert(tbe.dataBlkValid.count() > 0);
+ tbe.snd_msgType := CHIDataType:NCBWrData;
+ tbe.snd_destination := mapAddressToDownstreamMachine(tbe.addr);
+ setupPendingPartialSend(tbe);
+}
+
+action(CheckWUComp, desc="") {
+ assert(is_valid(tbe));
+ if (tbe.defer_expected_comp) {
+ tbe.defer_expected_comp := false;
+ tbe.expected_req_resp.addExpectedCount(1);
+ tbe.expected_req_resp.addExpectedRespType(CHIResponseType:Comp);
+ }
+}
+
+action(Send_SnpRespData, desc="") {
+ assert(is_HN == false);
+ assert(is_valid(tbe));
+ assert(tbe.dataBlkValid.isFull());
+ assert(tbe.dataValid);
+
+ assert(tbe.snpNeedsData ||
+ (tbe.dataDirty && (tbe.reqType == CHIRequestType:SnpCleanInvalid)) ||
+ ((tbe.dataDirty || tbe.dataUnique) && (tbe.reqType == CHIRequestType:SnpShared)) ||
+ ((tbe.dataDirty || tbe.dataUnique) && (tbe.reqType == CHIRequestType:SnpUnique)));
+
+ if (tbe.dataToBeInvalid) {
+ assert(tbe.dataMaybeDirtyUpstream == false);
+ if (tbe.dataDirty) {
+ tbe.snd_msgType := CHIDataType:SnpRespData_I_PD;
+ } else {
+ tbe.snd_msgType := CHIDataType:SnpRespData_I;
+ }
+ } else if (tbe.dataToBeSharedClean) {
+ assert(tbe.dataMaybeDirtyUpstream == false);
+ if (tbe.dataDirty) {
+ tbe.snd_msgType := CHIDataType:SnpRespData_SC_PD;
+ } else {
+ tbe.snd_msgType := CHIDataType:SnpRespData_SC;
+ }
+ } else {
+ assert(tbe.reqType == CHIRequestType:SnpOnce);
+ if (tbe.dataDirty && tbe.dataUnique) {
+ tbe.snd_msgType := CHIDataType:SnpRespData_UD;
+ } else if (tbe.dataDirty) {
+ tbe.snd_msgType := CHIDataType:SnpRespData_SD;
+ } else if (tbe.dataUnique) {
+ tbe.snd_msgType := CHIDataType:SnpRespData_UC;
+ } else {
+ tbe.snd_msgType := CHIDataType:SnpRespData_SC;
+ }
+ }
+
+ tbe.snd_destination := tbe.requestor;
+ setupPendingSend(tbe);
+}
+
+action(Send_CompData_SnpUniqueFwd, desc="") {
+ assert(tbe.dataValid);
+ assert(tbe.dataToBeInvalid);
+ assert(tbe.dataMaybeDirtyUpstream == false);
+
+ if (tbe.dataDirty) {
+ tbe.fwdedState := State:UD;
+ tbe.snd_msgType := CHIDataType:CompData_UD_PD;
+ } else {
+ tbe.fwdedState := State:UC;
+ tbe.snd_msgType := CHIDataType:CompData_UC;
+ }
+ tbe.actions.pushFront(Event:SendSnpFwdedResp);
+
+ tbe.snd_destination := tbe.fwdRequestor;
+ setupPendingSend(tbe);
+}
+
+action(Send_CompData_SnpSharedFwd, desc="") {
+ assert(tbe.dataValid);
+ assert(tbe.dataToBeSharedClean);
+ assert(tbe.dataMaybeDirtyUpstream == false);
+
+ if (tbe.dataDirty) {
+ tbe.fwdedState := State:SD;
+ tbe.snd_msgType := CHIDataType:CompData_SD_PD;
+ } else {
+ tbe.fwdedState := State:SC;
+ tbe.snd_msgType := CHIDataType:CompData_SC;
+ }
+ if (tbe.snpNeedsData) {
+ tbe.actions.pushFront(Event:SendSnpFwdedData);
+ } else {
+ tbe.actions.pushFront(Event:SendSnpFwdedResp);
+ }
+
+ tbe.snd_destination := tbe.fwdRequestor;
+ setupPendingSend(tbe);
+}
+
+action(Send_CompData_SnpNSDFwd, desc="") {
+ assert(tbe.dataValid);
+ assert(tbe.dataToBeSharedClean);
+ assert(tbe.dataMaybeDirtyUpstream == false);
+
+ tbe.snd_msgType := CHIDataType:CompData_SC;
+ tbe.fwdedState := State:SC;
+ if (tbe.dataDirty || tbe.snpNeedsData) {
+ tbe.actions.pushFront(Event:SendSnpFwdedData);
+ } else {
+ tbe.actions.pushFront(Event:SendSnpFwdedResp);
+ }
+
+ tbe.snd_destination := tbe.fwdRequestor;
+ setupPendingSend(tbe);
+}
+
+action(Send_CompData_SnpOnceFwd, desc="") {
+ assert(tbe.dataValid);
+
+ tbe.fwdedState := State:I;
+ tbe.snd_msgType := CHIDataType:CompData_I;
+ tbe.actions.pushFront(Event:SendSnpFwdedResp);
+
+ tbe.snd_destination := tbe.fwdRequestor;
+ setupPendingSend(tbe);
+}
+
+action(Send_SnpRespDataFwded, desc="") {
+ assert(tbe.dataValid);
+
+ // right only using this for the SnpShared/SnpNSD, so check
+ assert(tbe.dataToBeSharedClean);
+ assert(tbe.dataMaybeDirtyUpstream == false);
+
+ // We have the data (locally or upstream) or are dropping it
+ bool keepData := (tbe.dir_sharers.count() > 0) ||
+ (tbe.dataToBeInvalid == false);
+
+ if (keepData) {
+ if (tbe.fwdedState == State:SD) {
+ tbe.snd_msgType := CHIDataType:SnpRespData_SC_Fwded_SD_PD;
+ } else if (tbe.dataDirty && (tbe.fwdedState == State:SC)) {
+ tbe.snd_msgType := CHIDataType:SnpRespData_SC_PD_Fwded_SC;
+ } else {
+ tbe.snd_msgType := CHIDataType:SnpRespData_SC_Fwded_SC;
+ }
+ } else {
+ if (tbe.fwdedState == State:SD) {
+ tbe.snd_msgType := CHIDataType:SnpRespData_I_Fwded_SD_PD;
+ } else if (tbe.dataDirty && (tbe.fwdedState == State:SC)) {
+ tbe.snd_msgType := CHIDataType:SnpRespData_I_PD_Fwded_SC;
+ } else {
+ tbe.snd_msgType := CHIDataType:SnpRespData_I_Fwded_SC;
+ }
+ }
+
+ tbe.snd_destination := tbe.requestor;
+ setupPendingSend(tbe);
+}
+
+action(Send_FwdSnpResp, desc="") {
+ assert(is_valid(tbe));
+ assert(tbe.dataValid);
+
+ enqueue(rspOutPort, CHIResponseMsg, response_latency) {
+ out_msg.addr := address;
+ out_msg.responder := machineID;
+ out_msg.Destination.add(tbe.requestor);
+
+ // We have the data (locally or upstream) or are dropping it
+ bool keepData := (tbe.dir_sharers.count() > 0) ||
+ (tbe.dataToBeInvalid == false);
+
+ if (keepData && tbe.dataToBeSharedClean) {
+ assert((tbe.reqType == CHIRequestType:SnpSharedFwd) ||
+ (tbe.reqType == CHIRequestType:SnpNotSharedDirtyFwd));
+ assert(tbe.dataMaybeDirtyUpstream == false);
+ if (tbe.fwdedState == State:SD) {
+ out_msg.type := CHIResponseType:SnpResp_SC_Fwded_SD_PD;
+ } else {
+ assert(tbe.fwdedState == State:SC);
+ out_msg.type := CHIResponseType:SnpResp_SC_Fwded_SC;
+ }
+
+ } else if (keepData) {
+ assert(tbe.reqType == CHIRequestType:SnpOnceFwd);
+ assert(tbe.fwdedState == State:I);
+ if (tbe.dataUnique && (tbe.dataDirty || tbe.dataMaybeDirtyUpstream)) {
+ out_msg.type := CHIResponseType:SnpResp_UD_Fwded_I;
+ } else if (tbe.dataUnique) {
+ out_msg.type := CHIResponseType:SnpResp_UC_Fwded_I;
+ } else if (tbe.dataDirty || tbe.dataMaybeDirtyUpstream) {
+ out_msg.type := CHIResponseType:SnpResp_SD_Fwded_I;
+ } else {
+ out_msg.type := CHIResponseType:SnpResp_SC_Fwded_I;
+ }
+
+ } else {
+ assert(tbe.reqType == CHIRequestType:SnpUniqueFwd);
+ assert(tbe.dataMaybeDirtyUpstream == false);
+ if (tbe.fwdedState == State:UD) {
+ out_msg.type := CHIResponseType:SnpResp_I_Fwded_UD_PD;
+ } else {
+ assert(tbe.fwdedState == State:UC);
+ out_msg.type := CHIResponseType:SnpResp_I_Fwded_UC;
+ }
+ }
+ }
+}
+
+action(Send_Data, desc="") {
+ assert(tbe.snd_pendEv);
+ assert(tbe.snd_pendBytes.count() > 0);
+ tbe.snd_pendEv := false;
+ enqueue(datOutPort, CHIDataMsg, data_latency) {
+ out_msg.addr := tbe.addr;
+ out_msg.type := tbe.snd_msgType;
+
+ int offset := tbe.snd_pendBytes.firstBitSet(true);
+ assert(offset < blockSize);
+ int range := tbe.snd_pendBytes.firstBitSet(false, offset) - offset;
+ assert((range > 0) && (range <= blockSize));
+ if (range > data_channel_size) {
+ range := data_channel_size;
+ }
+ tbe.snd_pendBytes.setMask(offset, range, false);
+
+ out_msg.dataBlk := tbe.dataBlk;
+ out_msg.bitMask.setMask(offset, range);
+
+ out_msg.responder := machineID;
+
+ out_msg.Destination.add(tbe.snd_destination);
+ }
+
+ // send next chunk (if any) next cycle
+ scheduleSendData(tbe, 1);
+}
+
+action(Send_RespSepData, desc="") {
+ assert(is_valid(tbe));
+ enqueue(rspOutPort, CHIResponseMsg, response_latency) {
+ out_msg.addr := address;
+ out_msg.type := CHIResponseType:RespSepData;
+ out_msg.responder := machineID;
+ out_msg.Destination.add(tbe.requestor);
+ }
+}
+
+action(Send_CompI, desc="") {
+ assert(is_valid(tbe));
+ enqueue(rspOutPort, CHIResponseMsg, response_latency) {
+ out_msg.addr := address;
+ out_msg.type := CHIResponseType:Comp_I;
+ out_msg.responder := machineID;
+ out_msg.Destination.add(tbe.requestor);
+ }
+}
+
+action(Send_CompUC, desc="") {
+ assert(is_valid(tbe));
+ enqueue(rspOutPort, CHIResponseMsg, response_latency) {
+ out_msg.addr := address;
+ out_msg.type := CHIResponseType:Comp_UC;
+ out_msg.responder := machineID;
+ out_msg.Destination.add(tbe.requestor);
+ }
+}
+
+action(Send_CompAck, desc="") {
+ assert(is_valid(tbe));
+ enqueue(rspOutPort, CHIResponseMsg, response_latency) {
+ out_msg.addr := address;
+ out_msg.type := CHIResponseType:CompAck;
+ out_msg.responder := machineID;
+ out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr));
+ }
+}
+
+action(Send_CompI_Stale, desc="") {
+ assert(is_valid(tbe));
+ enqueue(rspOutPort, CHIResponseMsg, response_latency) {
+ out_msg.addr := address;
+ out_msg.type := CHIResponseType:Comp_I;
+ out_msg.responder := machineID;
+ out_msg.Destination.add(tbe.requestor);
+ // We don't know if this is a stale writeback or a bug, so flag the
+ // reponse so the requestor can make further checks
+ out_msg.stale := true;
+ }
+}
+
+action(Send_CompDBIDResp, desc="") {
+ assert(is_valid(tbe));
+ enqueue(rspOutPort, CHIResponseMsg, response_latency) {
+ out_msg.addr := address;
+ out_msg.type := CHIResponseType:CompDBIDResp;
+ out_msg.responder := machineID;
+ out_msg.Destination.add(tbe.requestor);
+ }
+}
+
+action(Send_CompDBIDResp_Stale, desc="") {
+ assert(is_valid(tbe));
+ enqueue(rspOutPort, CHIResponseMsg, response_latency) {
+ out_msg.addr := address;
+ out_msg.type := CHIResponseType:CompDBIDResp;
+ out_msg.responder := machineID;
+ out_msg.Destination.add(tbe.requestor);
+ // We don't know if this is a stale writeback or a bug, so flag the
+ // reponse so the requestor can make further checks
+ out_msg.stale := true;
+ }
+}
+
+action(Send_DBIDResp, desc="") {
+ assert(is_valid(tbe));
+ enqueue(rspOutPort, CHIResponseMsg, response_latency) {
+ out_msg.addr := address;
+ out_msg.type := CHIResponseType:DBIDResp;
+ out_msg.responder := machineID;
+ out_msg.Destination.add(tbe.requestor);
+ }
+}
+
+action(Send_Comp_WU, desc="") {
+ assert(is_valid(tbe));
+ enqueue(rspOutPort, CHIResponseMsg, comp_wu_latency + response_latency) {
+ out_msg.addr := address;
+ out_msg.type := CHIResponseType:Comp;
+ out_msg.responder := machineID;
+ out_msg.Destination.add(tbe.requestor);
+ }
+}
+
+action(Send_SnpRespI, desc="") {
+ enqueue(rspOutPort, CHIResponseMsg, response_latency) {
+ out_msg.addr := address;
+ out_msg.type := CHIResponseType:SnpResp_I;
+ out_msg.responder := machineID;
+ out_msg.Destination.add(tbe.requestor);
+ }
+}
+
+action(Send_RetryAck, desc="") {
+ peek(retryTriggerInPort, RetryTriggerMsg) {
+ enqueue(rspOutPort, CHIResponseMsg, response_latency) {
+ out_msg.addr := in_msg.addr;
+ out_msg.type := CHIResponseType:RetryAck;
+ out_msg.responder := machineID;
+ out_msg.Destination.add(in_msg.retryDest);
+ }
+ }
+}
+
+action(Send_PCrdGrant, desc="") {
+ peek(retryTriggerInPort, RetryTriggerMsg) {
+ enqueue(rspOutPort, CHIResponseMsg, response_latency) {
+ out_msg.addr := in_msg.addr;
+ out_msg.type := CHIResponseType:PCrdGrant;
+ out_msg.responder := machineID;
+ out_msg.Destination.add(in_msg.retryDest);
+ }
+ }
+}
+
+// Note on CheckUpgrade_FromStore/CheckUpgrade_FromCU/CheckUpgrade_FromRU
+// We will always get Comp_UC; but if our data is invalidated before
+// Comp_UC we would need to go to UCE. Since we don't use the UCE state
+// we remain in the transient state and follow-up with ReadUnique.
+// Note this assumes the responder knows we have invalid data when sending
+// us Comp_UC and does not register us as owner.
+
+action(CheckUpgrade_FromStore, desc="") {
+ assert(is_HN == false);
+ if (tbe.dataUnique) {
+ // success, just send CompAck next
+ assert(tbe.dataValid);
+ } else {
+ tbe.actions.pushFront(Event:SendReadUnique);
+ }
+ tbe.actions.pushFront(Event:SendCompAck);
+}
+
+action(CheckUpgrade_FromCU, desc="") {
+ assert(is_HN == false);
+ if (tbe.dataUnique == false) {
+ // actually failed, so just cancel the directory update
+ assert(tbe.dir_sharers.isElement(tbe.requestor) == false);
+ tbe.requestorToBeExclusiveOwner := false;
+ tbe.updateDirOnCompAck := false;
+ }
+ // otherwise nothing else to do here other than acking the CleanUnique
+ tbe.actions.pushFront(Event:SendCompAck);
+}
+
+action(CheckUpgrade_FromRU, desc="") {
+ assert(is_HN == false);
+ if (tbe.dataUnique) {
+ // success, just send CompAck next
+ assert(tbe.dataValid);
+ } else {
+ // will need to get data instead
+ tbe.actions.pushFront(Event:SendReadUnique);
+ }
+ tbe.actions.pushFront(Event:SendCompAck);
+}
+
+action(Finalize_UpdateCacheFromTBE, desc="") {
+ assert(is_valid(tbe));
+ State final := tbe.finalState;
+ if ((final == State:UD_RSC) || (final == State:SD_RSC) || (final == State:UC_RSC) ||
+ (final == State:SC_RSC) || (final == State:UD) || (final == State:UD_T) ||
+ (final == State:SD) || (final == State:UC) || (final == State:SC) ||
+ (final == State:UC_RU) || (final == State:UD_RU) || (final == State:UD_RSD) ||
+ (final == State:SD_RSD)) {
+ assert(tbe.dataBlkValid.isFull());
+ assert(tbe.dataValid);
+ assert(is_valid(cache_entry));
+ cache_entry.DataBlk := tbe.dataBlk;
+ DPRINTF(RubySlicc, "Cached data %s pfb %s\n", tbe.dataBlk, cache_entry.HWPrefetched);
+ } else {
+ // make sure only deallocate the cache entry if data is invalid
+ assert(tbe.dataValid == false);
+ if (is_valid(cache_entry)) {
+ cache.deallocate(address);
+ unset_cache_entry();
+ }
+ }
+}
+
+action(Finalize_UpdateDirectoryFromTBE, desc="") {
+ assert(is_valid(tbe));
+ State final := tbe.finalState;
+ if ((final == State:UD_RSC) || (final == State:SD_RSC) || (final == State:UC_RSC) ||
+ (final == State:SC_RSC) || (final == State:UC_RU) || (final == State:UD_RU) ||
+ (final == State:UD_RSD) || (final == State:SD_RSD) || (final == State:RU) ||
+ (final == State:RSC) || (final == State:RSD) || (final == State:RUSD) ||
+ (final == State:RUSC)) {
+ DirEntry dir_entry := getDirEntry(address);
+ assert(is_valid(dir_entry));
+ assert(tbe.dir_sharers.count() > 0);
+ dir_entry.ownerExists := tbe.dir_ownerExists;
+ dir_entry.ownerIsExcl := tbe.dir_ownerIsExcl;
+ dir_entry.owner := tbe.dir_owner;
+ dir_entry.sharers := tbe.dir_sharers;
+ } else {
+ assert((tbe.dir_ownerExists == false) && tbe.dir_sharers.isEmpty());
+ if(directory.isTagPresent(address)) {
+ directory.deallocate(address);
+ }
+ }
+}
+
+action(Deallocate_CacheBlock, desc="") {
+ assert(is_valid(cache_entry));
+ cache.deallocate(address);
+ unset_cache_entry();
+}
+
+action(Allocate_DirEntry, desc="") {
+ assert(directory.isTagPresent(address) == false);
+ directory.allocate(address);
+}
+
+action(Deallocate_DirEntry, desc="") {
+ assert(directory.isTagPresent(address));
+ directory.deallocate(address);
+}
+
+action(CheckCacheFill, desc="") {
+ assert(is_valid(tbe));
+
+ // only perform the write if we have valid data and need to write
+ bool need_fill := tbe.dataValid && (tbe.dataToBeInvalid == false) && tbe.doCacheFill;
+ bool execute_next := true;
+
+ if (need_fill && is_valid(cache_entry)) {
+ // can write
+ tbe.actions.pushFront(Event:DataArrayWrite);
+ tbe.actions.pushFront(Event:FillPipe);
+
+ } else if (need_fill && cache.cacheAvail(address)) {
+ // don't have a cache block, but there is space to allocate one
+ set_cache_entry(cache.allocate(address, new CacheEntry));
+ tbe.actions.pushFront(Event:DataArrayWriteOnFill);
+ tbe.actions.pushFront(Event:FillPipe);
+
+ } else if (need_fill) {
+ // performs a cache block replacement. CheckCacheFill executes again
+ // after the replacement
+ execute_next := false;
+
+ // pick a victim to deallocate
+ Addr victim_addr := cache.cacheProbe(address);
+ CacheEntry victim_entry := getCacheEntry(victim_addr);
+ assert(is_valid(victim_entry));
+ TBE victim_tbe := getCurrentActiveTBE(victim_addr);
+
+ if (is_invalid(victim_tbe)) {
+ DPRINTF(RubySlicc, "Eviction for %#x victim: %#x state=%s\n",
+ address, victim_addr, victim_entry.state);
+ enqueue(replTriggerOutPort, ReplacementMsg, 0) {
+ out_msg.addr := victim_addr;
+ out_msg.from_addr := address;
+ if (unify_repl_TBEs) {
+ out_msg.slot := tbe.storSlot;
+ DPRINTF(RubySlicc, "Reusing slot %d\n", out_msg.slot);
+ }
+ }
+ } else {
+ DPRINTF(RubySlicc, "Eviction for %#x victim: %#x state=%s\n",
+ address, victim_addr, victim_tbe.state);
+ // just wait until the transaction finishes to try again
+ victim_tbe.wakeup_pending_tgr := true;
+ }
+
+ // wait until we can deallocate the victim_addr
+ stall_and_wait(triggerInPort, victim_addr);
+ }
+
+ // only do the usual Pop_TriggerQueue+ProcessNextState if we have a block
+ if (execute_next) {
+ triggerInPort.dequeue(clockEdge());
+ clearPendingAction(tbe);
+ processNextState(address, tbe, cache_entry);
+ } else {
+ wakeupPendingSnps(tbe); // might have stalled snoops that can execute now
+ }
+}
+
+
+action(Finalize_DeallocateRequest, desc="") {
+ assert(is_valid(tbe));
+ assert(tbe.actions.empty());
+ wakeupPendingReqs(tbe);
+ wakeupPendingSnps(tbe);
+ wakeupPendingTgrs(tbe);
+
+ if (tbe.is_req_tbe) {
+ deallocateReqTBE(tbe);
+ processRetryQueue();
+
+ } else if (tbe.is_snp_tbe) {
+ deallocateSnpTBE(tbe);
+
+ } else {
+ deallocateReplacementTBE(tbe);
+ if (unify_repl_TBEs) {
+ processRetryQueue();
+ }
+ }
+ unset_tbe();
+
+ incomingTransactionEnd(address, curTransitionNextState());
+}
+
+action(Pop_ReqRdyQueue, desc="") {
+ reqRdyPort.dequeue(clockEdge());
+}
+
+action(Pop_RespInQueue, desc="") {
+ rspInPort.dequeue(clockEdge());
+}
+
+action(Pop_SnoopRdyQueue, desc="") {
+ snpRdyPort.dequeue(clockEdge());
+}
+
+action(Pop_DataInQueue, desc="") {
+ datInPort.dequeue(clockEdge());
+}
+
+// NOTICE a trigger event may wakeup another stalled trigger event so
+// this is always called first in the transitions so we don't pop the
+// wrong message
+action(Pop_TriggerQueue, desc="") {
+ triggerInPort.dequeue(clockEdge());
+}
+
+action(Pop_ReplTriggerQueue, desc="") {
+ replTriggerInPort.dequeue(clockEdge());
+ // wakeup the transaction that triggered this eviction
+ wakeup_port(triggerInPort, address);
+}
+
+action(Pop_RetryTriggerQueue, desc="") {
+ retryTriggerInPort.dequeue(clockEdge());
+}
+
+action(ProcessNextState, desc="") {
+ assert(is_valid(tbe));
+ processNextState(address, tbe, cache_entry);
+}
+
+action(ProcessNextState_ClearPending, desc="") {
+ assert(is_valid(tbe));
+ clearPendingAction(tbe);
+ processNextState(address, tbe, cache_entry);
+}
+
+action(Callback_LoadHit, desc="") {
+ assert(is_valid(tbe));
+ assert(tbe.reqType == CHIRequestType:Load);
+ if (tbe.is_local_pf == false) {
+ assert(tbe.dataValid);
+ DPRINTF(RubySlicc, "Read data %s\n", tbe.dataBlk);
+ sequencer.readCallback(tbe.addr, tbe.dataBlk, false);
+ }
+}
+
+action(Callback_StoreHit, desc="") {
+ assert(is_valid(tbe));
+ assert((tbe.reqType == CHIRequestType:StoreLine) ||
+ (tbe.reqType == CHIRequestType:Store));
+ if (tbe.is_local_pf == false) {
+ assert(tbe.dataValid);
+ DPRINTF(RubySlicc, "Write before %s\n", tbe.dataBlk);
+ sequencer.writeCallback(tbe.addr, tbe.dataBlk, false);
+ DPRINTF(RubySlicc, "Write after %s\n", tbe.dataBlk);
+ tbe.dataDirty := true;
+ }
+}
+
+action(Callback_ExpressPrefetchHit, desc="") {
+ // have not allocated TBE, but must clear the reservation
+ assert(is_invalid(tbe));
+ storTBEs.decrementReserved();
+ assert(storTBEs.areNSlotsAvailable(1));
+ assert(use_prefetcher);
+
+ cache.profilePrefetchHit();
+ peek(reqRdyPort, CHIRequestMsg) {
+ assert(in_msg.is_local_pf);
+ notifyPfComplete(in_msg.addr);
+ }
+}
+
+// This is called everytime a data message is received but only goes
+// though once all the blocks are present (tbe.dataValid)
+// NOTE: should create a separate trigger for this callback ?
+action(Callback_Miss, desc="") {
+ assert(is_valid(tbe));
+ if (tbe.dataValid && tbe.is_local_pf) {
+ assert(use_prefetcher);
+ notifyPfComplete(tbe.addr);
+
+ } else if (tbe.dataValid && (tbe.reqType == CHIRequestType:Load)) {
+ DPRINTF(RubySlicc, "Read data %s\n", tbe.dataBlk);
+ sequencer.readCallback(tbe.addr, tbe.dataBlk, true);
+
+ } else if (tbe.dataValid && ((tbe.reqType == CHIRequestType:Store) ||
+ (tbe.reqType == CHIRequestType:StoreLine))) {
+ DPRINTF(RubySlicc, "Write before %s\n", tbe.dataBlk);
+ sequencer.writeCallback(tbe.addr, tbe.dataBlk, true);
+ DPRINTF(RubySlicc, "Write after %s\n", tbe.dataBlk);
+ tbe.dataDirty := true;
+
+ // sets a use time out for store misses to prevent LL/SC livelocks
+ int use_timeout_latency := scLockLatency();
+ if (use_timeout_latency > 0) {
+ if (tbe.hasUseTimeout) {
+ assert(useTimerTable.isSet(tbe.addr));
+ } else {
+ useTimerTable.set(
+ tbe.addr,
+ clockEdge() + cyclesToTicks(intToCycles(use_timeout_latency)));
+ tbe.hasUseTimeout := true;
+ }
+ // also decay the timeout
+ scLockDecayLatency();
+ }
+ }
+}
+
+action(Unset_Timeout_TBE, desc="") {
+ assert(is_valid(tbe));
+ assert(tbe.hasUseTimeout);
+ assert(useTimerTable.isSet(tbe.addr));
+ useTimerTable.unset(tbe.addr);
+ tbe.hasUseTimeout := false;
+ // A snoop may have been stalled without setting the TBE flag
+ wakeup_port(snpRdyPort, address);
+}
+
+action(Unset_Timeout_Cache, desc="") {
+ assert(useTimerTable.isSet(address));
+ useTimerTable.unset(address);
+ wakeup_port(snpRdyPort, address);
+}
+
+action(Callback_WriteUnique, desc="") {
+ assert(is_valid(tbe));
+ assert((tbe.is_local_pf || tbe.is_remote_pf) == false);
+ assert((tbe.reqType == CHIRequestType:StoreLine) ||
+ (tbe.reqType == CHIRequestType:Store));
+ assert(tbe.dataValid == false);
+ sequencer.writeUniqueCallback(tbe.addr, tbe.dataBlk);
+ DPRINTF(RubySlicc, "WriteUnique data %s\n", tbe.dataBlk);
+ // set mask; note data is never considered valid
+ assert(tbe.dataBlkValid.isEmpty());
+ tbe.dataBlkValid.setMask(addressOffset(tbe.accAddr, tbe.addr), tbe.accSize);
+}
+
+action(Profile_Miss, desc="") {
+ assert(is_valid(tbe));
+ bool is_demand := (tbe.is_local_pf || tbe.is_remote_pf) == false;
+ bool is_remote_can_notify := tbe.is_remote_pf && upstream_prefetch_trains_prefetcher;
+ if (is_demand) {
+ cache.profileDemandMiss();
+ } else {
+ assert(use_prefetcher || tbe.is_remote_pf);
+ cache.profilePrefetchMiss();
+ }
+ // notify prefetcher about this demand miss
+ if (use_prefetcher && tbe.isSeqReqValid && (is_demand || is_remote_can_notify)) {
+ bool is_read := false;
+ if (isReadReqType(tbe.reqType)) {
+ is_read := true;
+ } else {
+ assert(isWriteReqType(tbe.reqType));
+ }
+
+ // FIXME: this dataBlk is likely to have stale data. This should be fixed
+ // if our prefetcher uses cached data to make prefetch decisions.
+ notifyPfMiss(tbe.seqReq, is_read, tbe.dataBlk);
+ }
+}
+
+action(Profile_Hit, desc="") {
+ assert(is_valid(tbe));
+ assert(is_valid(cache_entry));
+ assert(tbe.dataValid);
+ bool is_demand := (tbe.is_local_pf || tbe.is_remote_pf) == false;
+ bool is_remote_can_notify := tbe.is_remote_pf && upstream_prefetch_trains_prefetcher;
+ if (is_demand) {
+ cache.profileDemandHit();
+ } else {
+ assert(use_prefetcher || tbe.is_remote_pf);
+ cache.profilePrefetchHit();
+ }
+ // notify prefetcher about this demand hit
+ if (use_prefetcher && tbe.isSeqReqValid && (is_demand || is_remote_can_notify)) {
+ bool is_read := false;
+ if (isReadReqType(tbe.reqType)) {
+ is_read := true;
+ } else {
+ assert(isWriteReqType(tbe.reqType));
+ }
+ notifyPfHit(tbe.seqReq, is_read, tbe.dataBlk);
+
+ cache_entry.HWPrefetched := false;
+ }
+}
+
+action(Profile_Fill, desc="") {
+ assert(is_valid(tbe));
+ assert(is_valid(cache_entry));
+ if (use_prefetcher && tbe.isSeqReqValid) {
+
+ cache_entry.HWPrefetched := tbe.is_local_pf ||
+ (tbe.is_remote_pf &&
+ (upstream_prefetch_trains_prefetcher == false));
+
+ // Prefetchers that use this info require notifications from both
+ // demand and pf fills (unlike notifyPfHit/notifyPfMiss)
+ notifyPfFill(tbe.seqReq, tbe.dataBlk, tbe.is_local_pf);
+ }
+}
+
+action(Profile_Eviction, desc="") {
+ if (sc_lock_enabled && sequencer.llscCheckMonitor(address)) {
+ DPRINTF(LLSC, "Invalidating monitored address %#x\n", address);
+ scLockIncLatency();
+ }
+ if (send_evictions) {
+ DPRINTF(RubySlicc, "Sending invalidation for %#x to the sequencer\n", address);
+ sequencer.evictionCallback(address);
+ }
+ if (use_prefetcher && is_valid(cache_entry)) {
+ notifyPfEvict(address, cache_entry.HWPrefetched);
+ }
+}
+
+action(Profile_OutgoingStart, desc="") {
+ outgoingTransactionStart(address, curTransitionEvent());
+}
+
+action(Profile_OutgoingEnd_DataResp, desc="") {
+ assert(is_valid(tbe));
+ // completes once all data is received
+ if (tbe.expected_req_resp.hasReceivedData()) {
+ outgoingTransactionEnd(address, tbe.rcvdRetryAck);
+ }
+}
+
+action(Profile_OutgoingEnd_DatalessResp, desc="") {
+ assert(is_valid(tbe));
+ outgoingTransactionEnd(address, tbe.rcvdRetryAck);
+}
+
+action(TagArrayRead, desc="") {
+ assert(is_valid(tbe));
+ tbe.delayNextAction := curTick() + cyclesToTicks(
+ tagLatency((tbe.reqType == CHIRequestType:Load) ||
+ (tbe.reqType == CHIRequestType:Store) ||
+ (tbe.reqType == CHIRequestType:StoreLine)));
+}
+
+action(TagArrayWrite, desc="") {
+ assert(is_valid(tbe));
+ // when hasUseTimeout is set the final state is UD_T, but adding a delay
+ // between now and triggering Fin_UD_T may allow the timer to expire and then
+ // we end up in the wrong state
+ if (dealloc_wait_for_tag && (tbe.hasUseTimeout == false)) {
+ tbe.delayNextAction := curTick() + cyclesToTicks(tagLatency(false));
+ }
+}
+
+action(DataArrayRead, desc="") {
+ assert(is_valid(tbe));
+ tbe.delayNextAction := curTick() + cyclesToTicks(dataLatency());
+}
+
+action(DataArrayWrite, desc="") {
+ assert(is_valid(tbe));
+ assert(is_valid(cache_entry));
+ assert(tbe.doCacheFill);
+ if(wait_for_cache_wr) {
+ tbe.delayNextAction := curTick() + cyclesToTicks(dataLatency());
+ }
+}
+
+action(ReadHitPipe, desc="") {
+ assert(is_valid(tbe));
+ tbe.delayNextAction := curTick() + cyclesToTicks(read_hit_latency);
+}
+
+action(ReadMissPipe, desc="") {
+ assert(is_valid(tbe));
+ tbe.delayNextAction := curTick() + cyclesToTicks(read_miss_latency);
+}
+
+action(WriteFEPipe, desc="") {
+ assert(is_valid(tbe));
+ tbe.delayNextAction := curTick() + cyclesToTicks(write_fe_latency);
+}
+
+action(WriteBEPipe, desc="") {
+ assert(is_valid(tbe));
+ tbe.delayNextAction := curTick() + cyclesToTicks(write_be_latency);
+}
+
+action(FillPipe, desc="") {
+ assert(is_valid(tbe));
+ tbe.delayNextAction := curTick() + cyclesToTicks(fill_latency);
+}
+
+action(SnpSharedPipe, desc="") {
+ assert(is_valid(tbe));
+ tbe.delayNextAction := curTick() + cyclesToTicks(snp_latency);
+}
+
+action(SnpInvPipe, desc="") {
+ assert(is_valid(tbe));
+ tbe.delayNextAction := curTick() + cyclesToTicks(snp_latency + snp_inv_latency);
+}
+
+action(SnpOncePipe, desc="") {
+ assert(is_valid(tbe));
+ tbe.delayNextAction := curTick() + cyclesToTicks(snp_latency);
+}
diff --git a/src/mem/ruby/protocol/chi/CHI-cache-funcs.sm b/src/mem/ruby/protocol/chi/CHI-cache-funcs.sm
new file mode 100644
index 0000000..db008b0
--- /dev/null
+++ b/src/mem/ruby/protocol/chi/CHI-cache-funcs.sm
@@ -0,0 +1,1255 @@
+/*
+ * Copyright (c) 2021 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.
+ *
+ * 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.
+ */
+
+
+////////////////////////////////////////////////////////////////////////////
+// CHI-cache function definitions
+////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////
+// External functions
+
+Tick clockEdge();
+Tick curTick();
+Tick cyclesToTicks(Cycles c);
+Cycles ticksToCycles(Tick t);
+void set_cache_entry(AbstractCacheEntry b);
+void unset_cache_entry();
+void set_tbe(TBE b);
+void unset_tbe();
+MachineID mapAddressToDownstreamMachine(Addr addr);
+
+void incomingTransactionStart(Addr, Event, State, bool);
+void incomingTransactionEnd(Addr, State);
+void outgoingTransactionStart(Addr, Event);
+void outgoingTransactionEnd(Addr, bool);
+Event curTransitionEvent();
+State curTransitionNextState();
+
+// Placeholders for future prefetch support
+void notifyPfHit(RequestPtr req, bool is_read, DataBlock blk) { }
+void notifyPfMiss(RequestPtr req, bool is_read, DataBlock blk) { }
+void notifyPfFill(RequestPtr req, DataBlock blk, bool from_pf) { }
+void notifyPfEvict(Addr blkAddr, bool hwPrefetched) { }
+void notifyPfComplete(Addr addr) { }
+
+////////////////////////////////////////////////////////////////////////////
+// Interface functions required by SLICC
+
+CacheEntry getCacheEntry(Addr addr), return_by_pointer="yes" {
+ return static_cast(CacheEntry, "pointer", cache.lookup(addr));
+}
+
+DirEntry getDirEntry(Addr addr), return_by_pointer = "yes" {
+ if (directory.isTagPresent(addr)) {
+ return directory.lookup(addr);
+ } else {
+ return OOD;
+ }
+}
+
+State getState(TBE tbe, CacheEntry cache_entry, Addr addr) {
+ if (is_valid(tbe)) {
+ return tbe.state;
+ } else if (is_valid(cache_entry)) {
+ return cache_entry.state;
+ } else {
+ DirEntry dir_entry := getDirEntry(addr);
+ if (is_valid(dir_entry)) {
+ return dir_entry.state;
+ } else {
+ return State:I;
+ }
+ }
+}
+
+void setState(TBE tbe, CacheEntry cache_entry, Addr addr, State state) {
+ if (is_valid(tbe)) {
+ tbe.state := state;
+ }
+ if (is_valid(cache_entry)) {
+ cache_entry.state := state;
+ }
+ DirEntry dir_entry := getDirEntry(addr);
+ if (is_valid(dir_entry)) {
+ dir_entry.state := state;
+ }
+}
+
+TBE getCurrentActiveTBE(Addr addr), return_by_pointer="yes" {
+ // snoops take precedence over wbs and reqs
+ // it's invalid to have a replacement and a req active at the same time
+ // for the same line
+ TBE snp_tbe := snpTBEs[addr];
+ if (is_valid(snp_tbe)) {
+ return snp_tbe;
+ }
+ TBE req_tbe := TBEs[addr];
+ TBE repl_tbe := replTBEs[addr];
+ if (is_valid(req_tbe)) {
+ assert(is_invalid(repl_tbe));
+ return req_tbe;
+ }
+ if (is_valid(repl_tbe)) {
+ assert(is_invalid(req_tbe));
+ return repl_tbe;
+ }
+ return OOD;
+}
+
+AccessPermission getAccessPermission(Addr addr) {
+ TBE tbe := getCurrentActiveTBE(addr);
+ if(is_valid(tbe)) {
+ assert(Cache_State_to_permission(tbe.state) == AccessPermission:Busy);
+ if (tbe.expected_req_resp.hasExpected() ||
+ tbe.expected_snp_resp.hasExpected()) {
+ DPRINTF(RubySlicc, "%x %s,%s\n", addr, tbe.state, AccessPermission:Busy);
+ return AccessPermission:Busy;
+ }
+ else if (tbe.dataValid && (tbe.dataMaybeDirtyUpstream == false)) {
+ if (tbe.dataUnique) {
+ DPRINTF(RubySlicc, "%x %s,%s\n", addr, tbe.state, AccessPermission:Read_Write);
+ return AccessPermission:Read_Write;
+ } else {
+ DPRINTF(RubySlicc, "%x %s,%s\n", addr, tbe.state, AccessPermission:Read_Only);
+ return AccessPermission:Read_Only;
+ }
+ } else {
+ DPRINTF(RubySlicc, "%x %s,%s\n", addr, tbe.state, AccessPermission:Busy);
+ return AccessPermission:Busy;
+ }
+ }
+ CacheEntry cache_entry := getCacheEntry(addr);
+ if(is_valid(cache_entry)) {
+ DPRINTF(RubySlicc, "%x %s,%s\n", addr, cache_entry.state, Cache_State_to_permission(cache_entry.state));
+ return Cache_State_to_permission(cache_entry.state);
+ }
+ DPRINTF(RubySlicc, "%x %s,%s\n", addr, State:I, AccessPermission:NotPresent);
+ return AccessPermission:NotPresent;
+}
+
+void setAccessPermission(CacheEntry cache_entry, Addr addr, State state) {
+ if (is_valid(cache_entry)) {
+ cache_entry.changePermission(Cache_State_to_permission(state));
+ }
+}
+
+void functionalRead(Addr addr, Packet *pkt, WriteMask &mask) {
+ // read if bitmask has bytes not in mask or if data is dirty
+
+ TBE tbe := getCurrentActiveTBE(addr);
+ CacheEntry cache_entry := getCacheEntry(addr);
+ DPRINTF(RubySlicc, "functionalRead %x\n", addr);
+ WriteMask read_mask;
+ bool dirty := false;
+ bool from_tbe := false;
+
+ if (is_valid(tbe)) {
+ from_tbe := true;
+ dirty := tbe.dataDirty;
+ if (tbe.dataValid) {
+ read_mask.fillMask();
+ } else {
+ read_mask := tbe.dataBlkValid;
+ // could have received dirty data but tbe.dataDirty not set yet because
+ // some data is pending, so check for dirty received message types
+ dirty := dirty ||
+ tbe.expected_req_resp.receivedDataType(CHIDataType:CompData_UD_PD) ||
+ tbe.expected_req_resp.receivedDataType(CHIDataType:CompData_SD_PD) ||
+ tbe.expected_req_resp.receivedDataType(CHIDataType:CBWrData_UD_PD) ||
+ tbe.expected_req_resp.receivedDataType(CHIDataType:CBWrData_SD_PD) ||
+ tbe.expected_req_resp.receivedDataType(CHIDataType:NCBWrData) ||
+ tbe.expected_snp_resp.receivedDataType(CHIDataType:SnpRespData_I_PD) ||
+ tbe.expected_snp_resp.receivedDataType(CHIDataType:SnpRespData_SC_PD) ||
+ tbe.expected_snp_resp.receivedDataType(CHIDataType:SnpRespData_SD) ||
+ tbe.expected_snp_resp.receivedDataType(CHIDataType:SnpRespData_UD) ||
+ tbe.expected_snp_resp.receivedDataType(CHIDataType:SnpRespData_SC_Fwded_SD_PD) ||
+ tbe.expected_snp_resp.receivedDataType(CHIDataType:SnpRespData_SC_PD_Fwded_SC) ||
+ tbe.expected_snp_resp.receivedDataType(CHIDataType:SnpRespData_I_Fwded_SD_PD) ||
+ tbe.expected_snp_resp.receivedDataType(CHIDataType:SnpRespData_I_PD_Fwded_SC);
+ }
+ } else if (is_valid(cache_entry) &&
+ ((Cache_State_to_permission(cache_entry.state) == AccessPermission:Read_Write) ||
+ (Cache_State_to_permission(cache_entry.state) == AccessPermission:Read_Only))) {
+ from_tbe := false;
+ read_mask.fillMask();
+ dirty := (cache_entry.state == State:UD) || (cache_entry.state == State:UD_RSC) ||
+ (cache_entry.state == State:SD) || (cache_entry.state == State:SD_RSC) ||
+ (cache_entry.state == State:UD_RU) || (cache_entry.state == State:UD_RSD) ||
+ (cache_entry.state == State:SD_RSD) || (cache_entry.state == State:UD_T);
+ }
+
+ WriteMask test_mask := mask;
+ test_mask.orMask(read_mask);
+ if ((test_mask.cmpMask(mask) == false) || dirty) {
+ if (from_tbe) {
+ if(testAndReadMask(addr, tbe.dataBlk, read_mask, pkt)) {
+ DPRINTF(RubySlicc, "functionalRead tbe %x %s dirty=%d %s %s\n", addr, tbe.dataBlk, tbe.dataDirty, read_mask, mask);
+ mask.orMask(read_mask);
+ }
+ } else {
+ if (testAndReadMask(addr, cache_entry.DataBlk, read_mask, pkt)) {
+ DPRINTF(RubySlicc, "functionalRead cache %x %s dirty=%d %s %s\n", addr, cache_entry.DataBlk, dirty, read_mask, mask);
+ mask.orMask(read_mask);
+ }
+ }
+ }
+}
+
+int functionalWrite(Addr addr, Packet *pkt) {
+ int num_functional_writes := 0;
+ TBE tbe := getCurrentActiveTBE(addr);
+ if(is_valid(tbe)) {
+ num_functional_writes := num_functional_writes +
+ testAndWrite(addr, tbe.dataBlk, pkt);
+ DPRINTF(RubySlicc, "functionalWrite tbe %x %s\n", addr, tbe.dataBlk);
+ }
+ CacheEntry cache_entry := getCacheEntry(addr);
+ if (is_valid(cache_entry)) {
+ num_functional_writes := num_functional_writes +
+ testAndWrite(addr, cache_entry.DataBlk, pkt);
+ DPRINTF(RubySlicc, "functionalWrite cache %x %s\n", addr, cache_entry.DataBlk);
+ }
+ return num_functional_writes;
+}
+
+Cycles mandatoryQueueLatency(RubyRequestType type) {
+ return intToCycles(1);
+}
+
+Cycles tagLatency(bool from_sequencer) {
+ if (from_sequencer) {
+ //mandatoryQueueLatency accounts for 1 cy
+ return cache.getTagLatency() - intToCycles(1);
+ } else {
+ return cache.getTagLatency();
+ }
+}
+
+Cycles dataLatency() {
+ return cache.getDataLatency();
+}
+
+bool inCache(Addr addr) {
+ CacheEntry entry := getCacheEntry(makeLineAddress(addr));
+ // NOTE: we consider data for the addr to be in cache if it exists in local,
+ // upstream, or both caches.
+ if ((is_valid(entry) == false) || (entry.state == State:I)) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+bool hasBeenPrefetched(Addr addr) {
+ CacheEntry entry := getCacheEntry(makeLineAddress(addr));
+ if (is_valid(entry)) {
+ return entry.HWPrefetched;
+ } else {
+ return false;
+ }
+}
+
+bool inMissQueue(Addr addr) {
+ Addr line_addr := makeLineAddress(addr);
+ TBE tbe := getCurrentActiveTBE(line_addr);
+ return is_valid(tbe);
+}
+
+void notifyCoalesced(Addr addr, RubyRequestType type, RequestPtr req,
+ DataBlock data_blk, bool was_miss) {
+ DPRINTF(RubySlicc, "notifyCoalesced(addr=%#x, type=%s, was_miss=%d)\n",
+ addr, type, was_miss);
+ if (was_miss) {
+ cache.profileDemandMiss();
+ } else {
+ cache.profileDemandHit();
+ }
+ if (use_prefetcher) {
+ bool is_read := (type == RubyRequestType:LD) ||
+ (type == RubyRequestType:Load_Linked) ||
+ (type == RubyRequestType:IFETCH);
+ if (was_miss) {
+ notifyPfMiss(req, is_read, data_blk);
+ } else {
+ notifyPfHit(req, is_read, data_blk);
+ }
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+// Helper functions
+
+
+void clearExpectedReqResp(TBE tbe) {
+ assert(blockSize >= data_channel_size);
+ assert((blockSize % data_channel_size) == 0);
+ tbe.expected_req_resp.clear(blockSize / data_channel_size);
+}
+
+void clearExpectedSnpResp(TBE tbe) {
+ assert(blockSize >= data_channel_size);
+ assert((blockSize % data_channel_size) == 0);
+ tbe.expected_snp_resp.clear(blockSize / data_channel_size);
+}
+
+void initializeTBE(TBE tbe, Addr addr, int storSlot) {
+ assert(is_valid(tbe));
+
+ tbe.wakeup_pending_req := false;
+ tbe.wakeup_pending_snp := false;
+ tbe.wakeup_pending_tgr := false;
+
+ tbe.addr := addr;
+
+ tbe.storSlot := storSlot;
+
+ clearExpectedReqResp(tbe);
+ clearExpectedSnpResp(tbe);
+ tbe.defer_expected_comp := false;
+
+ tbe.requestorToBeOwner := false;
+ tbe.requestorToBeExclusiveOwner := false;
+ tbe.updateDirOnCompAck := true;
+
+ tbe.dataToBeInvalid := false;
+ tbe.dataToBeSharedClean := false;
+
+ tbe.doCacheFill := false;
+
+ tbe.pendReqType := CHIRequestType:null;
+
+ tbe.pendAction := Event:null;
+ tbe.finalState := State:null;
+ tbe.delayNextAction := intToTick(0);
+
+ tbe.is_stale := false;
+}
+
+TBE allocateRequestTBE(Addr addr, CHIRequestMsg in_msg), return_by_pointer="yes" {
+ // We must have reserved resources for this allocation
+ storTBEs.decrementReserved();
+ assert(storTBEs.areNSlotsAvailable(1));
+
+ TBEs.allocate(addr);
+ TBE tbe := TBEs[addr];
+
+ initializeTBE(tbe, addr, storTBEs.addEntryToNewSlot());
+
+ assert(tbe.is_snp_tbe == false);
+ assert(tbe.is_repl_tbe == false);
+ tbe.is_req_tbe := true;
+
+ tbe.accAddr := in_msg.accAddr;
+ tbe.accSize := in_msg.accSize;
+ tbe.requestor := in_msg.requestor;
+ tbe.reqType := in_msg.type;
+
+ tbe.isSeqReqValid := in_msg.isSeqReqValid;
+ tbe.seqReq := in_msg.seqReq;
+ tbe.is_local_pf := in_msg.is_local_pf;
+ tbe.is_remote_pf := in_msg.is_remote_pf;
+
+ tbe.use_DMT := false;
+ tbe.use_DCT := false;
+
+ tbe.hasUseTimeout := false;
+
+ return tbe;
+}
+
+
+TBE allocateSnoopTBE(Addr addr, CHIRequestMsg in_msg), return_by_pointer="yes" {
+ // We must have reserved resources for this allocation
+ storSnpTBEs.decrementReserved();
+ assert(storSnpTBEs.areNSlotsAvailable(1));
+
+ snpTBEs.allocate(addr);
+ TBE tbe := snpTBEs[addr];
+ initializeTBE(tbe, addr, storSnpTBEs.addEntryToNewSlot());
+
+ assert(tbe.is_req_tbe == false);
+ assert(tbe.is_repl_tbe == false);
+ tbe.is_snp_tbe := true;
+
+ tbe.accAddr := addr;
+ tbe.accSize := blockSize;
+ tbe.requestor := in_msg.requestor;
+ tbe.fwdRequestor := in_msg.fwdRequestor;
+ tbe.reqType := in_msg.type;
+
+ tbe.snpNeedsData := in_msg.retToSrc;
+
+ tbe.use_DMT := false;
+ tbe.use_DCT := false;
+
+ return tbe;
+}
+
+
+TBE _allocateReplacementTBE(Addr addr, int storSlot), return_by_pointer="yes" {
+ TBE tbe := replTBEs[addr];
+ initializeTBE(tbe, addr, storSlot);
+
+ assert(tbe.is_req_tbe == false);
+ assert(tbe.is_snp_tbe == false);
+ tbe.is_repl_tbe := true;
+
+ tbe.accAddr := addr;
+ tbe.accSize := blockSize;
+ tbe.requestor := machineID;
+ tbe.reqType := CHIRequestType:null;
+
+ tbe.use_DMT := false;
+ tbe.use_DCT := false;
+
+ return tbe;
+}
+
+TBE allocateReplacementTBE(Addr addr), return_by_pointer="yes" {
+ // We must have resources for this allocation
+ assert(storReplTBEs.areNSlotsAvailable(1));
+
+ replTBEs.allocate(addr);
+ return _allocateReplacementTBE(addr, storReplTBEs.addEntryToNewSlot());
+}
+
+TBE allocateReplacementTBEOnSlot(Addr addr, int slot), return_by_pointer="yes" {
+ // only when reusing slot from main TBE table
+ assert(unify_repl_TBEs);
+ storTBEs.addEntryToSlot(slot);
+
+ replTBEs.allocate(addr);
+ return _allocateReplacementTBE(addr, slot);
+}
+
+TBE getHazardTBE(TBE tbe), return_by_pointer="yes" {
+ assert(is_valid(tbe));
+ assert(tbe.is_snp_tbe);
+ TBE hazard_tbe := TBEs[tbe.addr];
+ if (tbe.is_req_hazard) {
+ assert(tbe.is_repl_hazard == false);
+ } else {
+ assert(tbe.is_repl_hazard);
+ hazard_tbe := replTBEs[tbe.addr];
+ }
+ assert(is_valid(hazard_tbe));
+ return hazard_tbe;
+}
+
+void scheduleSendData(TBE tbe, int when) {
+ if (tbe.snd_pendBytes.count() > 0) {
+ assert(tbe.snd_pendEv == false);
+ tbe.snd_pendEv := true;
+ // enqueue send event
+ tbe.pendAction := Event:TX_Data;
+ enqueue(triggerOutPort, TriggerMsg, intToCycles(when)) {
+ out_msg.addr := tbe.addr;
+ out_msg.from_hazard := tbe.is_req_hazard || tbe.is_repl_hazard;
+ }
+ }
+}
+
+void setupPendingSend(TBE tbe) {
+ assert(blockSize >= data_channel_size);
+ assert((blockSize % data_channel_size) == 0);
+ // data must be complete in the TBE
+ assert(tbe.dataBlkValid.isFull());
+ tbe.snd_pendBytes.fillMask();
+ scheduleSendData(tbe, 0);
+}
+
+void setupPendingPartialSend(TBE tbe) {
+ assert(blockSize >= data_channel_size);
+ assert((blockSize % data_channel_size) == 0);
+ // data must be complete in the TBE
+ assert(tbe.dataBlkValid.count() > 0);
+ tbe.snd_pendBytes := tbe.dataBlkValid;
+ scheduleSendData(tbe, 0);
+}
+
+// common code for downstream requests
+void prepareRequest(TBE tbe, CHIRequestType type, CHIRequestMsg & out_msg) {
+ out_msg.addr := tbe.addr;
+ out_msg.accAddr := tbe.addr;
+ out_msg.accSize := blockSize;
+ out_msg.requestor := machineID;
+ out_msg.fwdRequestor := tbe.requestor;
+ out_msg.type := type;
+ out_msg.allowRetry := false;
+ tbe.pendReqAllowRetry := false;
+ tbe.rcvdRetryAck := false;
+ tbe.rcvdRetryCredit := false;
+ tbe.pendReqType := type;
+ out_msg.isSeqReqValid := tbe.isSeqReqValid;
+ out_msg.seqReq := tbe.seqReq;
+ out_msg.is_local_pf := false;
+ out_msg.is_remote_pf := tbe.is_local_pf || tbe.is_remote_pf;
+}
+
+void allowRequestRetry(TBE tbe, CHIRequestMsg & out_msg) {
+ out_msg.allowRetry := true;
+ tbe.pendReqAllowRetry := true;
+ tbe.pendReqAccAddr := out_msg.accAddr;
+ tbe.pendReqAccSize := out_msg.accSize;
+ tbe.pendReqDest := out_msg.Destination;
+ tbe.pendReqD2OrigReq := out_msg.dataToFwdRequestor;
+ tbe.pendReqRetToSrc := out_msg.retToSrc;
+}
+
+void prepareRequestRetry(TBE tbe, CHIRequestMsg & out_msg) {
+ assert(tbe.pendReqAllowRetry);
+ tbe.pendReqAllowRetry := false;
+ out_msg.allowRetry := false;
+
+ out_msg.addr := tbe.addr;
+ out_msg.requestor := machineID;
+ out_msg.fwdRequestor := tbe.requestor;
+ out_msg.accAddr := tbe.pendReqAccAddr;
+ out_msg.accSize := tbe.pendReqAccSize;
+ out_msg.type := tbe.pendReqType;
+ out_msg.Destination := tbe.pendReqDest;
+ out_msg.dataToFwdRequestor := tbe.pendReqD2OrigReq;
+ out_msg.retToSrc := tbe.pendReqRetToSrc;
+ out_msg.isSeqReqValid := tbe.isSeqReqValid;
+ out_msg.seqReq := tbe.seqReq;
+ out_msg.is_local_pf := false;
+ out_msg.is_remote_pf := tbe.is_local_pf || tbe.is_remote_pf;
+}
+
+void enqueueDoRetry(TBE tbe) {
+ if (tbe.rcvdRetryAck && tbe.rcvdRetryCredit) {
+ enqueue(retryTriggerOutPort, RetryTriggerMsg, 0) {
+ out_msg.addr := tbe.addr;
+ out_msg.event := Event:DoRetry;
+ }
+ destsWaitingRetry.removeNetDest(tbe.pendReqDest);
+ }
+}
+
+void processRetryQueue() {
+ // send credit if requestor waiting for it and we have resources
+ bool has_avail := storTBEs.areNSlotsAvailable(1);
+ assert(unify_repl_TBEs || has_avail);
+ // the slot might still be used by a replacement if unify_repl_TBEs is set
+ if (retryQueue.empty() == false && has_avail) {
+ storTBEs.incrementReserved();
+ RetryQueueEntry e := retryQueue.next();
+ retryQueue.pop();
+ enqueue(retryTriggerOutPort, RetryTriggerMsg, 0) {
+ out_msg.addr := e.addr;
+ out_msg.retryDest := e.retryDest;
+ out_msg.event := Event:SendPCrdGrant;
+ }
+ }
+}
+
+void printResources() {
+ if (unify_repl_TBEs) {
+ assert(storReplTBEs.size() == 0);
+ assert(storReplTBEs.reserved() == 0);
+ DPRINTF(RubySlicc, "Resources(used/rsvd/max): TBEs=%d/%d/%d snpTBEs=%d/%d/%d replTBEs=%d/%d/%d\n",
+ storTBEs.size(), storTBEs.reserved(), storTBEs.capacity(),
+ storSnpTBEs.size(), storSnpTBEs.reserved(), storSnpTBEs.capacity(),
+ storTBEs.size(), storTBEs.reserved(), storTBEs.capacity());
+ } else {
+ DPRINTF(RubySlicc, "Resources(used/rsvd/max): TBEs=%d/%d/%d snpTBEs=%d/%d/%d replTBEs=%d/%d/%d\n",
+ storTBEs.size(), storTBEs.reserved(), storTBEs.capacity(),
+ storSnpTBEs.size(), storSnpTBEs.reserved(), storSnpTBEs.capacity(),
+ storReplTBEs.size(), storReplTBEs.reserved(), storReplTBEs.capacity());
+ }
+ DPRINTF(RubySlicc, "Resources(in/out size): req=%d/%d rsp=%d/%d dat=%d/%d snp=%d/%d trigger=%d\n",
+ reqIn.getSize(curTick()), reqOut.getSize(curTick()),
+ rspIn.getSize(curTick()), rspOut.getSize(curTick()),
+ datIn.getSize(curTick()), datOut.getSize(curTick()),
+ snpIn.getSize(curTick()), snpOut.getSize(curTick()),
+ triggerQueue.getSize(curTick()));
+}
+
+bool needCacheEntry(CHIRequestType req_type,
+ CacheEntry cache_entry, DirEntry dir_entry,
+ bool is_prefetch) {
+ // never allocates:
+ // - if entry already valid
+ // - if using DMT; the request is a Read*; and dir entry is invalid
+ // oterwise follow config params
+ if (is_valid(cache_entry) ||
+ (enable_DMT && is_invalid(dir_entry) &&
+ ((req_type == CHIRequestType:ReadShared) ||
+ (req_type == CHIRequestType:ReadUnique) ||
+ (req_type == CHIRequestType:ReadOnce)))) {
+ return false;
+ } else {
+ return is_prefetch ||
+ (alloc_on_readshared && ((req_type == CHIRequestType:ReadShared) ||
+ (req_type == CHIRequestType:ReadNotSharedDirty))) ||
+ (alloc_on_readunique && (req_type == CHIRequestType:ReadUnique)) ||
+ (alloc_on_readonce && (req_type == CHIRequestType:ReadOnce)) ||
+ (alloc_on_writeback && ((req_type == CHIRequestType:WriteBackFull) ||
+ (req_type == CHIRequestType:WriteCleanFull) ||
+ (req_type == CHIRequestType:WriteEvictFull) ||
+ (is_HN && (req_type == CHIRequestType:WriteUniqueFull)))) ||
+ (alloc_on_seq_acc && ((req_type == CHIRequestType:Load) ||
+ (req_type == CHIRequestType:Store))) ||
+ (alloc_on_seq_line_write && (req_type == CHIRequestType:StoreLine));
+ }
+}
+
+bool needDeallocCacheEntry(CHIRequestType req_type) {
+ return (dealloc_on_shared && ((req_type == CHIRequestType:ReadShared) ||
+ (req_type == CHIRequestType:ReadNotSharedDirty))) ||
+ (dealloc_on_unique && ((req_type == CHIRequestType:ReadUnique) ||
+ (req_type == CHIRequestType:CleanUnique)));
+}
+
+bool upstreamHasUnique(State state) {
+ return (state == State:RU) || (state == State:UD_RU) || (state == State:UC_RU);
+}
+
+bool upstreamHasShared(State state) {
+ return (state == State:RSC) || (state == State:RSD) ||
+ (state == State:RUSD) || (state == State:RUSC) ||
+ (state == State:UD_RSD) || (state == State:SD_RSD) ||
+ (state == State:UD_RSC) || (state == State:SD_RSC) ||
+ (state == State:UC_RSC) || (state == State:SC_RSC);
+}
+
+void printTBEState(TBE tbe) {
+ DPRINTF(RubySlicc, "STATE: addr: %#x data present=%d valid=%d unique=%d dirty=%d mu_dirty=%d dir ownerV=%d ownerE=%d sharers=%d tobe_I=%d tobe_SC=%d doFill=%d pendAction=%s\n",
+ tbe.addr, tbe.dataBlkValid.isFull(), tbe.dataValid, tbe.dataUnique,
+ tbe.dataDirty, tbe.dataMaybeDirtyUpstream, tbe.dir_ownerExists,
+ tbe.dir_ownerIsExcl,tbe.dir_sharers.count(),
+ tbe.dataToBeInvalid, tbe.dataToBeSharedClean,
+ tbe.doCacheFill, tbe.pendAction);
+ DPRINTF(RubySlicc, "dataBlkValid = %s\n", tbe.dataBlkValid);
+}
+
+void copyCacheAndDir(CacheEntry cache_entry, DirEntry dir_entry,
+ TBE tbe, State initialState) {
+ assert(is_valid(tbe));
+
+ // have dir entry
+ if (is_valid(dir_entry)) {
+ assert((initialState == State:UD_RSC) || (initialState == State:SD_RSC) ||
+ (initialState == State:UC_RSC) || (initialState == State:SC_RSC) ||
+ (initialState == State:UD_RU) || (initialState == State:UC_RU) ||
+ (initialState == State:RU) || (initialState == State:RSC) ||
+ (initialState == State:RSD) || (initialState == State:RUSD) ||
+ (initialState == State:RUSC) ||
+ (initialState == State:UD_RSD) || (initialState == State:SD_RSD));
+ tbe.dir_sharers := dir_entry.sharers;
+ tbe.dir_owner := dir_entry.owner;
+ tbe.dir_ownerExists := dir_entry.ownerExists;
+ tbe.dir_ownerIsExcl := dir_entry.ownerIsExcl;
+ assert(tbe.dir_sharers.count() > 0);
+ } else {
+ tbe.dir_sharers.clear();
+ tbe.dir_ownerExists := false;
+ }
+ // Sanity checks
+ assert((tbe.dir_ownerExists && tbe.dir_ownerIsExcl) ==
+ ((initialState == State:UD_RU) || (initialState == State:UC_RU) ||
+ (initialState == State:RU)));
+ assert((tbe.dir_ownerExists && (tbe.dir_ownerIsExcl == false)) ==
+ ((initialState == State:RSD) || (initialState == State:RUSD) ||
+ (initialState == State:UD_RSD) || (initialState == State:SD_RSD)));
+
+ // have usable data
+ if (is_valid(cache_entry) &&
+ ((initialState == State:UD) || (initialState == State:SD) ||
+ (initialState == State:UC) || (initialState == State:SC) ||
+ (initialState == State:UD_RSC) || (initialState == State:SD_RSC) ||
+ (initialState == State:UC_RSC) || (initialState == State:SC_RSC) ||
+ (initialState == State:UD_RSD) || (initialState == State:SD_RSD) ||
+ (initialState == State:UD_T))) {
+ tbe.dataBlk := cache_entry.DataBlk;
+ tbe.dataBlkValid.fillMask();
+ tbe.dataValid := true;
+ DPRINTF(RubySlicc, "Cached data %s\n", tbe.dataBlk);
+ } else {
+ assert(is_invalid(cache_entry) ||
+ (is_valid(cache_entry) && (initialState == State:UD_RU) ||
+ (initialState == State:UC_RU)));
+ tbe.dataBlkValid.clear();
+ tbe.dataValid := false;
+ }
+
+ // set MRU for accessed block
+ if (is_valid(cache_entry) && ((tbe.is_local_pf || tbe.is_remote_pf) == false)) {
+ cache.setMRU(cache_entry);
+ }
+
+ // data is dirty here
+ tbe.dataDirty := (initialState == State:UD) || (initialState == State:UD_RSC) ||
+ (initialState == State:SD) || (initialState == State:SD_RSC) ||
+ (initialState == State:UD_RU) || (initialState == State:UD_RSD) ||
+ (initialState == State:SD_RSD) || (initialState == State:UD_T);
+
+ // maybe dirty upstream
+ tbe.dataMaybeDirtyUpstream := (initialState == State:UD_RU) || (initialState == State:UC_RU) ||
+ (initialState == State:UD_RSD) || (initialState == State:SD_RSD) ||
+ (initialState == State:RU) || (initialState == State:RSD) ||
+ (initialState == State:RUSD);
+ assert(tbe.dir_ownerExists == tbe.dataMaybeDirtyUpstream);
+
+ // data is unique here or upstream
+ tbe.dataUnique := (initialState == State:UD) || (initialState == State:UD_RSC) ||
+ (initialState == State:UD_RU) || (initialState == State:UC) ||
+ (initialState == State:UC_RSC) || (initialState == State:UC_RU) ||
+ (initialState == State:RU) || (initialState == State:RUSD) ||
+ (initialState == State:RUSC) ||
+ (initialState == State:UD_RSD) || (initialState == State:UD_T);
+
+ // it is locked until timeout ?
+ tbe.hasUseTimeout := initialState == State:UD_T;
+
+ tbe.dataToBeSharedClean := false;
+ tbe.dataToBeInvalid := false;
+
+ printTBEState(tbe);
+}
+
+void copyCacheAndDirTBEs(TBE src, TBE dst) {
+ assert(is_valid(src));
+ assert(is_valid(dst));
+ dst.dataBlk := src.dataBlk;
+ dst.dataBlkValid := src.dataBlkValid;
+ dst.dataValid := src.dataValid;
+ dst.dataDirty := src.dataDirty;
+ dst.dataMaybeDirtyUpstream := src.dataMaybeDirtyUpstream;
+ dst.dataUnique := src.dataUnique;
+ dst.dir_sharers := src.dir_sharers;
+ dst.dir_owner := src.dir_owner;
+ dst.dir_ownerExists := src.dir_ownerExists;
+ dst.dir_ownerIsExcl := src.dir_ownerIsExcl;
+ printTBEState(dst);
+}
+
+void deallocateReqTBE(TBE tbe) {
+ assert(is_valid(tbe));
+ assert(tbe.is_req_tbe);
+ storTBEs.removeEntryFromSlot(tbe.storSlot);
+ TBEs.deallocate(tbe.addr);
+}
+
+void deallocateSnpTBE(TBE tbe) {
+ assert(is_valid(tbe));
+ assert(tbe.is_snp_tbe);
+ storSnpTBEs.removeEntryFromSlot(tbe.storSlot);
+ snpTBEs.deallocate(tbe.addr);
+}
+
+void deallocateReplacementTBE(TBE tbe) {
+ assert(is_valid(tbe));
+ assert(tbe.is_repl_tbe);
+ if (unify_repl_TBEs) {
+ storTBEs.removeEntryFromSlot(tbe.storSlot);
+ } else {
+ storReplTBEs.removeEntryFromSlot(tbe.storSlot);
+ }
+ replTBEs.deallocate(tbe.addr);
+}
+
+void setDataToBeStates(TBE tbe) {
+ assert(is_valid(tbe));
+ if (tbe.dataToBeInvalid) {
+ tbe.dataValid := false;
+ tbe.dataBlkValid.clear();
+ }
+ if (tbe.dataToBeSharedClean) {
+ tbe.dataUnique := false;
+ tbe.dataDirty := false;
+ assert(tbe.dataMaybeDirtyUpstream == false);
+ }
+ tbe.dataToBeInvalid := false;
+ tbe.dataToBeSharedClean := false;
+}
+
+void setExpectedForInvSnoop(TBE tbe, bool expectCleanWB) {
+ assert(tbe.expected_snp_resp.hasExpected() == false);
+ assert(tbe.dir_sharers.count() > 0);
+ clearExpectedSnpResp(tbe);
+ if (expectCleanWB) {
+ tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_I);
+ }
+ if (tbe.dataMaybeDirtyUpstream) {
+ assert(tbe.dir_ownerExists);
+ tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_I_PD);
+ if ((expectCleanWB == false) || (tbe.dir_sharers.count() > 1)) {
+ tbe.expected_snp_resp.addExpectedRespType(CHIResponseType:SnpResp_I);
+ }
+ } else {
+ tbe.expected_snp_resp.addExpectedRespType(CHIResponseType:SnpResp_I);
+ }
+ tbe.expected_snp_resp.setExpectedCount(tbe.dir_sharers.count());
+}
+
+State makeFinalStateHelper(State cs, State ds) {
+ if (ds == State:RSC) {
+ if (cs == State:UD) {
+ return State:UD_RSC;
+ } else if (cs == State:SD) {
+ return State:SD_RSC;
+ } else if (cs == State:UC) {
+ return State:UC_RSC;
+ } else if (cs == State:SC) {
+ return State:SC_RSC;
+ } else {
+ return State:RSC;
+ }
+ } else if (ds == State:RU) {
+ if (cs == State:UD) {
+ return State:UD_RU;
+ } else if (cs == State:UC) {
+ return State:UC_RU;
+ } else {
+ assert(cs != State:SC);
+ assert(cs != State:SD);
+ return State:RU;
+ }
+ } else if (ds == State:RSD) {
+ if (cs == State:UD) {
+ return State:UD_RSD;
+ } else if (cs == State:SD) {
+ return State:SD_RSD;
+ } else {
+ assert(cs == State:I);
+ return State:RSD;
+ }
+ } else if (ds == State:RUSD) {
+ if (cs == State:UD) {
+ return State:UD_RSD;
+ } else {
+ assert(cs == State:I);
+ return State:RUSD;
+ }
+ } else if (ds == State:RUSC) {
+ if (cs == State:UC) {
+ return State:UC_RSC;
+ } else if (cs == State:UD) {
+ return State:UD_RSC;
+ } else {
+ assert(cs == State:I);
+ return State:RUSC;
+ }
+ } else {
+ assert(ds == State:I);
+ return cs;
+ }
+}
+
+State makeFinalState(TBE tbe, CacheEntry cache_entry, DirEntry dir_entry) {
+ setDataToBeStates(tbe);
+ printTBEState(tbe);
+
+ State cache_state := State:I;
+ State dir_state := State:I;
+
+ if (tbe.dir_ownerExists) {
+ assert(is_valid(dir_entry));
+ assert(tbe.dataMaybeDirtyUpstream);
+ if (tbe.dir_ownerIsExcl) {
+ assert(tbe.dir_sharers.count() == 1);
+ dir_state := State:RU;
+ } else {
+ assert(tbe.dir_sharers.count() >= 1);
+ if (tbe.dataUnique) {
+ dir_state := State:RUSD;
+ } else {
+ dir_state := State:RSD;
+ }
+ }
+ } else if (tbe.dir_sharers.count() > 0) {
+ assert(is_valid(dir_entry));
+ assert(tbe.dataMaybeDirtyUpstream == false);
+ if (tbe.dataUnique) {
+ dir_state := State:RUSC;
+ } else {
+ dir_state := State:RSC;
+ }
+ }
+
+ if (tbe.dataValid && is_valid(cache_entry)) {
+ if (tbe.dataUnique && tbe.dataDirty) {
+ if (tbe.hasUseTimeout) {
+ cache_state := State:UD_T;
+ } else {
+ cache_state := State:UD;
+ }
+ } else if (tbe.dataUnique && (tbe.dataDirty == false)) {
+ cache_state := State:UC;
+ } else if ((tbe.dataUnique == false) && tbe.dataDirty) {
+ assert(allow_SD);
+ cache_state := State:SD;
+ } else {
+ cache_state := State:SC;
+ }
+ }
+
+ return makeFinalStateHelper(cache_state, dir_state);
+}
+
+// This is used only with the finalization transitions
+State getNextState(Addr address) {
+ TBE tbe := getCurrentActiveTBE(address);
+ assert(is_valid(tbe));
+ assert(tbe.pendAction == Event:Final);
+ tbe.finalState := makeFinalState(tbe, getCacheEntry(address), getDirEntry(address));
+ assert(tbe.finalState != State:null);
+ return tbe.finalState;
+}
+
+
+int scLockLatency() {
+ return sc_lock_multiplier * sc_lock_base_latency_cy;
+}
+
+void scLockIncLatency()
+{
+ sc_lock_multiplier := sc_lock_multiplier + sc_lock_multiplier_inc;
+ if (sc_lock_multiplier > sc_lock_multiplier_max) {
+ sc_lock_multiplier := sc_lock_multiplier_max;
+ }
+ DPRINTF(LLSC, "SC lock latency increased to %d cy\n", scLockLatency());
+}
+
+void scLockDecayLatency()
+{
+ sc_lock_multiplier := sc_lock_multiplier - sc_lock_multiplier_decay;
+ if (sc_lock_multiplier < 0) {
+ sc_lock_multiplier := 0;
+ }
+ DPRINTF(LLSC, "SC lock latency decayed to %d cy\n", scLockLatency());
+}
+
+void clearPendingAction(TBE tbe) {
+ // only clear pendAction if snd_pendEv not set
+ if (tbe.snd_pendEv) {
+ assert(tbe.pendAction == Event:TX_Data);
+ } else {
+ tbe.pendAction := Event:null;
+ }
+}
+
+bool isReadReqType(CHIRequestType type) {
+ if (type == CHIRequestType:Load ||
+ type == CHIRequestType:ReadShared ||
+ type == CHIRequestType:ReadNotSharedDirty ||
+ type == CHIRequestType:ReadOnce) {
+ return true;
+ }
+ return false;
+}
+
+bool isWriteReqType(CHIRequestType type) {
+ if (type == CHIRequestType:Store ||
+ type == CHIRequestType:StoreLine ||
+ type == CHIRequestType:WriteUniquePtl ||
+ type == CHIRequestType:WriteUniqueFull ||
+ type == CHIRequestType:ReadUnique) {
+ return true;
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// State->Event converters
+
+Event reqToEvent(CHIRequestType type, bool is_prefetch) {
+ if (type == CHIRequestType:Load) {
+ if (is_prefetch == false) {
+ return Event:Load;
+ } else {
+ return Event:Prefetch;
+ }
+ } else if (type == CHIRequestType:Store) {
+ return Event:Store;
+ } else if (type == CHIRequestType:StoreLine) {
+ return Event:Store;
+ } else if (type == CHIRequestType:ReadShared) {
+ return Event:ReadShared;
+ } else if (type == CHIRequestType:ReadNotSharedDirty) {
+ return Event:ReadNotSharedDirty;
+ } else if (type == CHIRequestType:ReadUnique) {
+ if (is_HN) {
+ return Event:ReadUnique_PoC;
+ } else {
+ return Event:ReadUnique;
+ }
+ } else if (type == CHIRequestType:CleanUnique) {
+ return Event:CleanUnique;
+ } else if (type == CHIRequestType:ReadOnce) {
+ return Event:ReadOnce;
+ } else if (type == CHIRequestType:Evict) {
+ return Event:Evict;
+ } else if (type == CHIRequestType:WriteBackFull) {
+ return Event:WriteBackFull;
+ } else if (type == CHIRequestType:WriteEvictFull) {
+ return Event:WriteEvictFull;
+ } else if (type == CHIRequestType:WriteCleanFull) {
+ return Event:WriteCleanFull;
+ } else if (type == CHIRequestType:WriteUniquePtl) {
+ if (is_HN) {
+ return Event:WriteUniquePtl_PoC;
+ } else {
+ return Event:WriteUnique; // all WriteUnique handled the same when ~PoC
+ }
+ } else if (type == CHIRequestType:WriteUniqueFull) {
+ if (is_HN && alloc_on_writeback) {
+ return Event:WriteUniqueFull_PoC_Alloc;
+ } else if (is_HN) {
+ return Event:WriteUniqueFull_PoC;
+ } else {
+ return Event:WriteUnique; // all WriteUnique handled the same when ~PoC
+ }
+ } else {
+ error("Invalid CHIRequestType");
+ }
+}
+
+Event respToEvent (CHIResponseType type, TBE tbe) {
+ bool on_hazard := is_valid(tbe) && (tbe.is_req_hazard || tbe.is_repl_hazard);
+ if (type == CHIResponseType:Comp_I) {
+ return Event:Comp_I;
+ } else if (type == CHIResponseType:Comp_UC) {
+ return Event:Comp_UC;
+ } else if (type == CHIResponseType:Comp_SC) {
+ return Event:Comp_SC;
+ } else if (type == CHIResponseType:CompDBIDResp) {
+ return Event:CompDBIDResp;
+ } else if (type == CHIResponseType:DBIDResp) {
+ return Event:DBIDResp;
+ } else if (type == CHIResponseType:Comp) {
+ return Event:Comp;
+ } else if (type == CHIResponseType:CompAck) {
+ return Event:CompAck;
+ } else if (type == CHIResponseType:ReadReceipt) {
+ return Event:ReadReceipt;
+ } else if (type == CHIResponseType:RespSepData) {
+ return Event:RespSepData;
+ } else if (type == CHIResponseType:SnpResp_I) {
+ return Event:SnpResp_I;
+ } else if (type == CHIResponseType:SnpResp_I_Fwded_UC) {
+ return Event:SnpResp_I_Fwded_UC;
+ } else if (type == CHIResponseType:SnpResp_I_Fwded_UD_PD) {
+ return Event:SnpResp_I_Fwded_UD_PD;
+ } else if (type == CHIResponseType:SnpResp_SC) {
+ return Event:SnpResp_SC;
+ } else if (type == CHIResponseType:SnpResp_SC_Fwded_SC) {
+ return Event:SnpResp_SC_Fwded_SC;
+ } else if (type == CHIResponseType:SnpResp_SC_Fwded_SD_PD) {
+ return Event:SnpResp_SC_Fwded_SD_PD;
+ } else if (type == CHIResponseType:SnpResp_SD_Fwded_I) {
+ return Event:SnpResp_SD_Fwded_I;
+ } else if (type == CHIResponseType:SnpResp_SC_Fwded_I) {
+ return Event:SnpResp_SC_Fwded_I;
+ } else if (type == CHIResponseType:SnpResp_UD_Fwded_I) {
+ return Event:SnpResp_UD_Fwded_I;
+ } else if (type == CHIResponseType:SnpResp_UC_Fwded_I) {
+ return Event:SnpResp_UC_Fwded_I;
+ } else if (type == CHIResponseType:RetryAck) {
+ if (is_HN) {
+ if (on_hazard) {
+ return Event:RetryAck_PoC_Hazard;
+ } else {
+ return Event:RetryAck_PoC;
+ }
+ } else {
+ if (on_hazard) {
+ return Event:RetryAck_Hazard;
+ } else {
+ return Event:RetryAck;
+ }
+ }
+ } else if (type == CHIResponseType:PCrdGrant) {
+ if (is_HN) {
+ if (on_hazard) {
+ return Event:PCrdGrant_PoC_Hazard;
+ } else {
+ return Event:PCrdGrant_PoC;
+ }
+ } else {
+ if (on_hazard) {
+ return Event:PCrdGrant_Hazard;
+ } else {
+ return Event:PCrdGrant;
+ }
+ }
+ } else {
+ error("Invalid CHIResponseType");
+ }
+}
+
+Event dataToEvent (CHIDataType type) {
+ if (type == CHIDataType:CompData_I) {
+ return Event:CompData_I;
+ } else if (type == CHIDataType:CompData_UC) {
+ return Event:CompData_UC;
+ } else if (type == CHIDataType:CompData_SC) {
+ return Event:CompData_SC;
+ } else if (type == CHIDataType:CompData_UD_PD) {
+ return Event:CompData_UD_PD;
+ } else if (type == CHIDataType:CompData_SD_PD) {
+ return Event:CompData_SD_PD;
+ } else if (type == CHIDataType:DataSepResp_UC) {
+ return Event:DataSepResp_UC;
+ } else if (type == CHIDataType:CBWrData_I) {
+ return Event:CBWrData_I;
+ } else if (type == CHIDataType:CBWrData_UC) {
+ return Event:CBWrData_UC;
+ } else if (type == CHIDataType:CBWrData_SC) {
+ return Event:CBWrData_SC;
+ } else if (type == CHIDataType:CBWrData_UD_PD) {
+ return Event:CBWrData_UD_PD;
+ } else if (type == CHIDataType:CBWrData_SD_PD) {
+ return Event:CBWrData_SD_PD;
+ } else if (type == CHIDataType:NCBWrData) {
+ return Event:NCBWrData;
+ } else if (type == CHIDataType:SnpRespData_I_PD) {
+ return Event:SnpRespData_I_PD;
+ } else if (type == CHIDataType:SnpRespData_I) {
+ return Event:SnpRespData_I;
+ } else if (type == CHIDataType:SnpRespData_SC_PD) {
+ return Event:SnpRespData_SC_PD;
+ } else if (type == CHIDataType:SnpRespData_SC) {
+ return Event:SnpRespData_SC;
+ } else if (type == CHIDataType:SnpRespData_SD) {
+ return Event:SnpRespData_SD;
+ } else if (type == CHIDataType:SnpRespData_UC) {
+ return Event:SnpRespData_UC;
+ } else if (type == CHIDataType:SnpRespData_UD) {
+ return Event:SnpRespData_UD;
+ } else if (type == CHIDataType:SnpRespData_SC_Fwded_SC) {
+ return Event:SnpRespData_SC_Fwded_SC;
+ } else if (type == CHIDataType:SnpRespData_SC_Fwded_SD_PD) {
+ return Event:SnpRespData_SC_Fwded_SD_PD;
+ } else if (type == CHIDataType:SnpRespData_SC_PD_Fwded_SC) {
+ return Event:SnpRespData_SC_PD_Fwded_SC;
+ } else if (type == CHIDataType:SnpRespData_I_Fwded_SD_PD) {
+ return Event:SnpRespData_I_Fwded_SD_PD;
+ } else if (type == CHIDataType:SnpRespData_I_PD_Fwded_SC) {
+ return Event:SnpRespData_I_PD_Fwded_SC;
+ } else if (type == CHIDataType:SnpRespData_I_Fwded_SC) {
+ return Event:SnpRespData_I_Fwded_SC;
+ } else {
+ error("Invalid CHIDataType");
+ }
+}
+
+Event snpToEvent (CHIRequestType type) {
+ if (type == CHIRequestType:SnpCleanInvalid) {
+ return Event:SnpCleanInvalid;
+ } else if (type == CHIRequestType:SnpShared) {
+ return Event:SnpShared;
+ } else if (type == CHIRequestType:SnpUnique) {
+ return Event:SnpUnique;
+ } else if (type == CHIRequestType:SnpSharedFwd) {
+ return Event:SnpSharedFwd;
+ } else if (type == CHIRequestType:SnpNotSharedDirtyFwd) {
+ return Event:SnpNotSharedDirtyFwd;
+ } else if (type == CHIRequestType:SnpUniqueFwd) {
+ return Event:SnpUniqueFwd;
+ } else if (type == CHIRequestType:SnpOnce) {
+ return Event:SnpOnce;
+ } else if (type == CHIRequestType:SnpOnceFwd) {
+ return Event:SnpOnceFwd;
+ } else {
+ error("Invalid CHIRequestType");
+ }
+}
+
+//////////////////////////////////////////
+// Cache bank utilization tracking
+
+enumeration(RequestType, desc="To communicate stats from transitions to recordStats") {
+ TagArrayRead, desc="Read or write the dir/cache tag/data array";
+ TagArrayWrite, desc="Read or write the dir/cache tag/data array";
+ DataArrayRead, desc="Read or write the dir/cache tag/data array";
+ DataArrayWrite, desc="Read or write the dir/cache tag/data array";
+
+ DestinationAvailable, desc="Check if there is a pending retry from the destination";
+
+ ReplTBEAvailable, desc="Check if a replacement TBE is available";
+}
+
+void recordRequestType(RequestType request_type, Addr addr) {
+ if (request_type == RequestType:DataArrayRead) {
+ cache.recordRequestType(CacheRequestType:DataArrayRead, addr);
+ } else if (request_type == RequestType:DataArrayWrite) {
+ cache.recordRequestType(CacheRequestType:DataArrayWrite, addr);
+ } else if (request_type == RequestType:TagArrayRead) {
+ cache.recordRequestType(CacheRequestType:TagArrayRead, addr);
+ } else if (request_type == RequestType:TagArrayWrite) {
+ cache.recordRequestType(CacheRequestType:TagArrayWrite, addr);
+ }
+}
+
+bool _checkResourceAvailable(RequestType request_type, Addr addr) {
+ if (request_type == RequestType:DataArrayRead) {
+ return cache.checkResourceAvailable(CacheResourceType:DataArray, addr);
+ } else if (request_type == RequestType:DataArrayWrite) {
+ return cache.checkResourceAvailable(CacheResourceType:DataArray, addr);
+ } else if (request_type == RequestType:TagArrayRead) {
+ return cache.checkResourceAvailable(CacheResourceType:TagArray, addr);
+ } else if (request_type == RequestType:TagArrayWrite) {
+ return cache.checkResourceAvailable(CacheResourceType:TagArray, addr);
+ } else if (request_type == RequestType:DestinationAvailable) {
+ if (throttle_req_on_retry) {
+ MachineID dest := mapAddressToDownstreamMachine(addr);
+ DPRINTF(RubySlicc, "Checking %s for addr %#x dest %s\n", request_type, addr, dest);
+ return destsWaitingRetry.isElement(dest) == false;
+ } else {
+ return true;
+ }
+ } else if (request_type == RequestType:ReplTBEAvailable) {
+ // if unify_repl_TBEs the replacement uses the same slot as the request
+ // that initiated it, so the resource is always available
+ return unify_repl_TBEs || storReplTBEs.areNSlotsAvailable(1);
+ } else {
+ error("Invalid RequestType type in checkResourceAvailable");
+ return true;
+ }
+}
+
+bool checkResourceAvailable(RequestType request_type, Addr addr) {
+ bool avail := _checkResourceAvailable(request_type, addr);
+ if (avail == false) {
+ DPRINTF(RubySlicc, "Resource %s not available for addr: %#x\n", request_type, addr);
+ }
+ return avail;
+}
diff --git a/src/mem/ruby/protocol/chi/CHI-cache-ports.sm b/src/mem/ruby/protocol/chi/CHI-cache-ports.sm
new file mode 100644
index 0000000..6a4fe5b
--- /dev/null
+++ b/src/mem/ruby/protocol/chi/CHI-cache-ports.sm
@@ -0,0 +1,398 @@
+/*
+ * Copyright (c) 2021 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.
+ *
+ * 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.
+ */
+
+// Outbound port definitions
+
+out_port(reqOutPort, CHIRequestMsg, reqOut);
+out_port(snpOutPort, CHIRequestMsg, snpOut);
+out_port(rspOutPort, CHIResponseMsg, rspOut);
+out_port(datOutPort, CHIDataMsg, datOut);
+out_port(triggerOutPort, TriggerMsg, triggerQueue);
+out_port(retryTriggerOutPort, RetryTriggerMsg, retryTriggerQueue);
+out_port(replTriggerOutPort, TriggerMsg, replTriggerQueue);
+out_port(reqRdyOutPort, CHIRequestMsg, reqRdy);
+out_port(snpRdyOutPort, CHIRequestMsg, snpRdy);
+
+
+// Include helper functions here. Some of them require the outports to be
+// already defined
+// Notice 'processNextState' and 'wakeupPending*' functions are defined after
+// the required input ports. Currently the SLICC compiler does not support
+// separate declaration and definition of functions in the .sm files.
+include "CHI-cache-funcs.sm";
+
+
+// Inbound port definitions and internal triggers queues
+// Notice we never stall input ports connected to the network
+// Incoming data and responses are always consumed.
+// Incoming requests/snoop are moved to the respective internal rdy queue
+// if a TBE can be allocated, or retried otherwise.
+
+// Trigger events from the UD_T state
+in_port(useTimerTable_in, Addr, useTimerTable, rank=11) {
+ if (useTimerTable_in.isReady(clockEdge())) {
+ Addr readyAddress := useTimerTable.nextAddress();
+ trigger(Event:UseTimeout, readyAddress, getCacheEntry(readyAddress),
+ getCurrentActiveTBE(readyAddress));
+ }
+}
+
+
+// Response
+in_port(rspInPort, CHIResponseMsg, rspIn, rank=10,
+ rsc_stall_handler=rspInPort_rsc_stall_handler) {
+ if (rspInPort.isReady(clockEdge())) {
+ printResources();
+ peek(rspInPort, CHIResponseMsg) {
+ TBE tbe := getCurrentActiveTBE(in_msg.addr);
+ trigger(respToEvent(in_msg.type, tbe), in_msg.addr,
+ getCacheEntry(in_msg.addr), tbe);
+ }
+ }
+}
+bool rspInPort_rsc_stall_handler() {
+ error("rspInPort must never stall\n");
+ return false;
+}
+
+
+// Data
+in_port(datInPort, CHIDataMsg, datIn, rank=9,
+ rsc_stall_handler=datInPort_rsc_stall_handler) {
+ if (datInPort.isReady(clockEdge())) {
+ printResources();
+ peek(datInPort, CHIDataMsg) {
+ int received := in_msg.bitMask.count();
+ assert((received <= data_channel_size) && (received > 0));
+ trigger(dataToEvent(in_msg.type), in_msg.addr,
+ getCacheEntry(in_msg.addr), getCurrentActiveTBE(in_msg.addr));
+ }
+ }
+}
+bool datInPort_rsc_stall_handler() {
+ error("datInPort must never stall\n");
+ return false;
+}
+
+
+// Snoops with an allocated TBE
+in_port(snpRdyPort, CHIRequestMsg, snpRdy, rank=8,
+ rsc_stall_handler=snpRdyPort_rsc_stall_handler) {
+ if (snpRdyPort.isReady(clockEdge())) {
+ printResources();
+ peek(snpRdyPort, CHIRequestMsg) {
+ assert(in_msg.allowRetry == false);
+ TBE tbe := getCurrentActiveTBE(in_msg.addr);
+ if (is_valid(tbe) && tbe.hasUseTimeout) {
+ // we may be in the BUSY_INTR waiting for a cache block, but if
+ // the timeout is set the snoop must still wait, so trigger the
+ // stall form here to prevent creating other states
+ trigger(Event:SnpStalled, in_msg.addr,
+ getCacheEntry(in_msg.addr), tbe);
+ } else {
+ trigger(snpToEvent(in_msg.type), in_msg.addr,
+ getCacheEntry(in_msg.addr), tbe);
+ }
+ }
+ }
+}
+bool snpRdyPort_rsc_stall_handler() {
+ error("snpRdyPort must never stall\n");
+ return false;
+}
+void wakeupPendingSnps(TBE tbe) {
+ if (tbe.wakeup_pending_snp) {
+ Addr addr := tbe.addr;
+ wakeup_port(snpRdyPort, addr);
+ tbe.wakeup_pending_snp := false;
+ }
+}
+
+
+// Incoming snoops
+// Not snoops are not retried, so the snoop channel is stalled if no
+// Snp TBEs available
+in_port(snpInPort, CHIRequestMsg, snpIn, rank=7) {
+ if (snpInPort.isReady(clockEdge())) {
+ assert(is_HN == false);
+ printResources();
+ peek(snpInPort, CHIRequestMsg) {
+ assert(in_msg.allowRetry == false);
+ trigger(Event:AllocSnoop, in_msg.addr,
+ getCacheEntry(in_msg.addr), getCurrentActiveTBE(in_msg.addr));
+ }
+ }
+}
+
+
+// Retry action triggers
+// These are handled before other triggers since a retried request should
+// be enqueued ahead of a new request
+// TODO: consider moving DoRetry to the triggerQueue
+in_port(retryTriggerInPort, RetryTriggerMsg, retryTriggerQueue, rank=6,
+ rsc_stall_handler=retryTriggerInPort_rsc_stall_handler) {
+ if (retryTriggerInPort.isReady(clockEdge())) {
+ printResources();
+ peek(retryTriggerInPort, RetryTriggerMsg) {
+ Event ev := in_msg.event;
+ TBE tbe := getCurrentActiveTBE(in_msg.addr);
+ assert((ev == Event:SendRetryAck) || (ev == Event:SendPCrdGrant) ||
+ (ev == Event:DoRetry));
+ if (ev == Event:DoRetry) {
+ assert(is_valid(tbe));
+ if (tbe.is_req_hazard || tbe.is_repl_hazard) {
+ ev := Event:DoRetry_Hazard;
+ }
+ }
+ trigger(ev, in_msg.addr, getCacheEntry(in_msg.addr), tbe);
+ }
+ }
+}
+bool retryTriggerInPort_rsc_stall_handler() {
+ DPRINTF(RubySlicc, "Retry trigger queue resource stall\n");
+ retryTriggerInPort.recycle(clockEdge(), cyclesToTicks(stall_recycle_lat));
+ return true;
+}
+
+
+// Action triggers
+in_port(triggerInPort, TriggerMsg, triggerQueue, rank=5,
+ rsc_stall_handler=triggerInPort_rsc_stall_handler) {
+ if (triggerInPort.isReady(clockEdge())) {
+ printResources();
+ peek(triggerInPort, TriggerMsg) {
+ TBE tbe := getCurrentActiveTBE(in_msg.addr);
+ assert(is_valid(tbe));
+ if (in_msg.from_hazard != (tbe.is_req_hazard || tbe.is_repl_hazard)) {
+ // possible when handling a snoop hazard and an action from the
+ // the initial transaction got woken up. Stall the action until the
+ // hazard ends
+ assert(in_msg.from_hazard == false);
+ assert(tbe.is_req_hazard || tbe.is_repl_hazard);
+ trigger(Event:ActionStalledOnHazard, in_msg.addr,
+ getCacheEntry(in_msg.addr), tbe);
+ } else {
+ trigger(tbe.pendAction, in_msg.addr, getCacheEntry(in_msg.addr), tbe);
+ }
+ }
+ }
+}
+bool triggerInPort_rsc_stall_handler() {
+ DPRINTF(RubySlicc, "Trigger queue resource stall\n");
+ triggerInPort.recycle(clockEdge(), cyclesToTicks(stall_recycle_lat));
+ return true;
+}
+void wakeupPendingTgrs(TBE tbe) {
+ if (tbe.wakeup_pending_tgr) {
+ Addr addr := tbe.addr;
+ wakeup_port(triggerInPort, addr);
+ tbe.wakeup_pending_tgr := false;
+ }
+}
+
+
+// internally triggered evictions
+// no stall handler for this one since it doesn't make sense try the next
+// request when out of TBEs
+in_port(replTriggerInPort, ReplacementMsg, replTriggerQueue, rank=4) {
+ if (replTriggerInPort.isReady(clockEdge())) {
+ printResources();
+ peek(replTriggerInPort, ReplacementMsg) {
+ TBE tbe := getCurrentActiveTBE(in_msg.addr);
+ CacheEntry cache_entry := getCacheEntry(in_msg.addr);
+ Event trigger := Event:null;
+ if (is_valid(cache_entry) &&
+ ((upstreamHasUnique(cache_entry.state) && dealloc_backinv_unique) ||
+ (upstreamHasShared(cache_entry.state) && dealloc_backinv_shared))) {
+ trigger := Event:Global_Eviction;
+ } else {
+ if (is_HN) {
+ trigger := Event:LocalHN_Eviction;
+ } else {
+ trigger := Event:Local_Eviction;
+ }
+ }
+ trigger(trigger, in_msg.addr, cache_entry, tbe);
+ }
+ }
+}
+
+
+// Requests with an allocated TBE
+in_port(reqRdyPort, CHIRequestMsg, reqRdy, rank=3,
+ rsc_stall_handler=reqRdyPort_rsc_stall_handler) {
+ if (reqRdyPort.isReady(clockEdge())) {
+ printResources();
+ peek(reqRdyPort, CHIRequestMsg) {
+ CacheEntry cache_entry := getCacheEntry(in_msg.addr);
+ TBE tbe := getCurrentActiveTBE(in_msg.addr);
+
+ DirEntry dir_entry := getDirEntry(in_msg.addr);
+
+ // Special case for possibly stale writebacks or evicts
+ if (in_msg.type == CHIRequestType:WriteBackFull) {
+ if (is_invalid(dir_entry) || (dir_entry.ownerExists == false) ||
+ (dir_entry.owner != in_msg.requestor)) {
+ trigger(Event:WriteBackFull_Stale, in_msg.addr, cache_entry, tbe);
+ }
+ } else if (in_msg.type == CHIRequestType:WriteEvictFull) {
+ if (is_invalid(dir_entry) || (dir_entry.ownerExists == false) ||
+ (dir_entry.ownerIsExcl == false) || (dir_entry.owner != in_msg.requestor)) {
+ trigger(Event:WriteEvictFull_Stale, in_msg.addr, cache_entry, tbe);
+ }
+ } else if (in_msg.type == CHIRequestType:WriteCleanFull) {
+ if (is_invalid(dir_entry) || (dir_entry.ownerExists == false) ||
+ (dir_entry.ownerIsExcl == false) || (dir_entry.owner != in_msg.requestor)) {
+ trigger(Event:WriteCleanFull_Stale, in_msg.addr, cache_entry, tbe);
+ }
+ } else if (in_msg.type == CHIRequestType:Evict) {
+ if (is_invalid(dir_entry) ||
+ (dir_entry.sharers.isElement(in_msg.requestor) == false)) {
+ trigger(Event:Evict_Stale, in_msg.addr, cache_entry, tbe);
+ }
+ }
+
+ // Normal request path
+ trigger(reqToEvent(in_msg.type, in_msg.is_local_pf), in_msg.addr, cache_entry, tbe);
+ }
+ }
+}
+bool reqRdyPort_rsc_stall_handler() {
+ DPRINTF(RubySlicc, "ReqRdy queue resource stall\n");
+ reqRdyPort.recycle(clockEdge(), cyclesToTicks(stall_recycle_lat));
+ return true;
+}
+void wakeupPendingReqs(TBE tbe) {
+ if (tbe.wakeup_pending_req) {
+ Addr addr := tbe.addr;
+ wakeup_port(reqRdyPort, addr);
+ tbe.wakeup_pending_req := false;
+ }
+}
+
+
+// Incoming new requests
+in_port(reqInPort, CHIRequestMsg, reqIn, rank=2,
+ rsc_stall_handler=reqInPort_rsc_stall_handler) {
+ if (reqInPort.isReady(clockEdge())) {
+ printResources();
+ peek(reqInPort, CHIRequestMsg) {
+ if (in_msg.allowRetry) {
+ trigger(Event:AllocRequest, in_msg.addr,
+ getCacheEntry(in_msg.addr), getCurrentActiveTBE(in_msg.addr));
+ } else {
+ trigger(Event:AllocRequestWithCredit, in_msg.addr,
+ getCacheEntry(in_msg.addr), getCurrentActiveTBE(in_msg.addr));
+ }
+ }
+ }
+}
+bool reqInPort_rsc_stall_handler() {
+ error("reqInPort must never stall\n");
+ return false;
+}
+
+
+// Incoming new sequencer requests
+in_port(seqInPort, RubyRequest, mandatoryQueue, rank=1) {
+ if (seqInPort.isReady(clockEdge())) {
+ printResources();
+ peek(seqInPort, RubyRequest) {
+ trigger(Event:AllocSeqRequest, in_msg.LineAddress,
+ getCacheEntry(in_msg.LineAddress),
+ getCurrentActiveTBE(in_msg.LineAddress));
+ }
+ }
+}
+
+
+// Incoming new prefetch requests
+in_port(pfInPort, RubyRequest, prefetchQueue, rank=0) {
+ if (pfInPort.isReady(clockEdge())) {
+ printResources();
+ peek(pfInPort, RubyRequest) {
+ trigger(Event:AllocPfRequest, in_msg.LineAddress,
+ getCacheEntry(in_msg.LineAddress),
+ getCurrentActiveTBE(in_msg.LineAddress));
+ }
+ }
+}
+
+void processNextState(Addr address, TBE tbe, CacheEntry cache_entry) {
+ assert(is_valid(tbe));
+ DPRINTF(RubySlicc, "GoToNextState expected_req_resp=%d expected_snp_resp=%d snd_pendEv=%d snd_pendBytes=%d\n",
+ tbe.expected_req_resp.expected(),
+ tbe.expected_snp_resp.expected(),
+ tbe.snd_pendEv, tbe.snd_pendBytes.count());
+
+ // if no pending trigger and not expecting to receive anything, enqueue
+ // next
+ bool has_nb_trigger := (tbe.actions.empty() == false) &&
+ tbe.actions.frontNB() &&
+ (tbe.snd_pendEv == false);
+ int expected_msgs := tbe.expected_req_resp.expected() +
+ tbe.expected_snp_resp.expected() +
+ tbe.snd_pendBytes.count();
+ if ((tbe.pendAction == Event:null) && ((expected_msgs == 0) || has_nb_trigger)) {
+ Cycles trigger_latency := intToCycles(0);
+ if (tbe.delayNextAction > curTick()) {
+ trigger_latency := ticksToCycles(tbe.delayNextAction) -
+ ticksToCycles(curTick());
+ tbe.delayNextAction := intToTick(0);
+ }
+
+ tbe.pendAction := Event:null;
+ if (tbe.actions.empty()) {
+ // time to go to the final state
+ tbe.pendAction := Event:Final;
+ } else {
+ tbe.pendAction := tbe.actions.front();
+ tbe.actions.pop();
+ }
+ assert(tbe.pendAction != Event:null);
+ enqueue(triggerOutPort, TriggerMsg, trigger_latency) {
+ out_msg.addr := tbe.addr;
+ out_msg.from_hazard := tbe.is_req_hazard || tbe.is_repl_hazard;
+ }
+ }
+
+ printTBEState(tbe);
+
+ // we might be going to BUSY_INTERRUPTABLE so wakeup pending snoops
+ // if any
+ wakeupPendingSnps(tbe);
+}
diff --git a/src/mem/ruby/protocol/chi/CHI-cache-transitions.sm b/src/mem/ruby/protocol/chi/CHI-cache-transitions.sm
new file mode 100644
index 0000000..d69d28e
--- /dev/null
+++ b/src/mem/ruby/protocol/chi/CHI-cache-transitions.sm
@@ -0,0 +1,1218 @@
+/*
+ * Copyright (c) 2021 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.
+ *
+ * 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.
+ */
+
+////////////////////////////////////////////////////////////////////////////
+// CHI-cache transition definition
+////////////////////////////////////////////////////////////////////////////
+
+// Allocate resources and move to the ready queue
+transition({I,SC,UC,SD,UD,RU,RSC,RSD,RUSD,SC_RSC,UC_RSC,SD_RSC,UD_RSC,UC_RU,UD_RU,UD_RSD,SD_RSD,RUSC
+ BUSY_INTR,BUSY_BLKD}, AllocRequest) {
+ AllocateTBE_Request;
+}
+
+transition({I,SC,UC,SD,UD,RU,RSC,RSD,RUSD,SC_RSC,UC_RSC,SD_RSC,UD_RSC,UC_RU,UD_RU,UD_RSD,SD_RSD,RUSC
+ BUSY_INTR,BUSY_BLKD}, AllocRequestWithCredit) {
+ AllocateTBE_Request_WithCredit;
+}
+
+transition({I,SC,UC,SD,UD,RU,RSC,RSD,RUSD,SC_RSC,UC_RSC,SD_RSC,UD_RSC,UC_RU,UD_RU,UD_RSD,SD_RSD,RUSC
+ BUSY_INTR,BUSY_BLKD}, SendRetryAck) {
+ Send_RetryAck;
+ Pop_RetryTriggerQueue;
+}
+
+transition({I,SC,UC,SD,UD,RU,RSC,RSD,RUSD,SC_RSC,UC_RSC,SD_RSC,UD_RSC,UC_RU,UD_RU,UD_RSD,SD_RSD,RUSC
+ BUSY_INTR,BUSY_BLKD}, SendPCrdGrant) {
+ Send_PCrdGrant;
+ Pop_RetryTriggerQueue;
+}
+
+transition({I,SC,UC,SD,UD,UD_T,RU,RSC,RSD,RUSD,SC_RSC,UC_RSC,SD_RSC,UD_RSC,UC_RU,UD_RU,UD_RSD,SD_RSD,RUSC
+ BUSY_INTR,BUSY_BLKD}, AllocSnoop) {
+ AllocateTBE_Snoop;
+}
+
+transition({UD,UD_T,SD,UC,SC,I,BUSY_INTR,BUSY_BLKD}, AllocSeqRequest) {
+ AllocateTBE_SeqRequest;
+}
+
+transition({I,SC,UC,SD,UD,UD_T,RU,RSC,RSD,RUSD,SC_RSC,SD_RSC,SD_RSD,UC_RSC,UC_RU,UD_RU,UD_RSD,UD_RSC,RUSC
+ BUSY_INTR,BUSY_BLKD}, AllocPfRequest) {
+ AllocateTBE_PfRequest;
+}
+
+transition({BUSY_INTR,BUSY_BLKD}, TagArrayRead) {TagArrayRead} {
+ Pop_TriggerQueue;
+ TagArrayRead;
+ ProcessNextState_ClearPending;
+}
+
+transition({BUSY_INTR,BUSY_BLKD}, TagArrayWrite) {TagArrayWrite} {
+ Pop_TriggerQueue;
+ TagArrayWrite;
+ ProcessNextState_ClearPending;
+}
+
+transition({BUSY_INTR,BUSY_BLKD}, DataArrayRead) {DataArrayRead} {
+ Pop_TriggerQueue;
+ DataArrayRead;
+ ProcessNextState_ClearPending;
+}
+
+// goes to BUSY_INTR as we may need to accept snoops while waiting
+// on potential replacement
+transition({BUSY_INTR,BUSY_BLKD}, CheckCacheFill, BUSY_INTR) {
+ CheckCacheFill;
+ // CheckCacheFill either does Pop_TriggerQueue+ProcessNextState_ClearPending
+ // or a stall depending on block availability
+}
+
+transition({BUSY_INTR,BUSY_BLKD}, DataArrayWrite) {DataArrayWrite} {
+ Pop_TriggerQueue;
+ DataArrayWrite;
+ ProcessNextState_ClearPending;
+}
+
+transition({BUSY_INTR,BUSY_BLKD}, DataArrayWriteOnFill) {DataArrayWrite} {
+ Pop_TriggerQueue;
+ Profile_Fill;
+ DataArrayWrite;
+ ProcessNextState_ClearPending;
+}
+
+transition({BUSY_INTR,BUSY_BLKD}, ReadHitPipe) {
+ Pop_TriggerQueue;
+ ReadHitPipe;
+ ProcessNextState_ClearPending;
+}
+
+transition({BUSY_INTR,BUSY_BLKD}, ReadMissPipe) {
+ Pop_TriggerQueue;
+ ReadMissPipe;
+ ProcessNextState_ClearPending;
+}
+
+transition({BUSY_INTR,BUSY_BLKD}, WriteFEPipe) {
+ Pop_TriggerQueue;
+ WriteFEPipe;
+ ProcessNextState_ClearPending;
+}
+
+transition({BUSY_INTR,BUSY_BLKD}, WriteBEPipe) {
+ Pop_TriggerQueue;
+ WriteBEPipe;
+ ProcessNextState_ClearPending;
+}
+
+transition({BUSY_INTR,BUSY_BLKD}, FillPipe) {
+ Pop_TriggerQueue;
+ FillPipe;
+ ProcessNextState_ClearPending;
+}
+
+transition({BUSY_INTR,BUSY_BLKD}, SnpSharedPipe) {
+ Pop_TriggerQueue;
+ SnpSharedPipe;
+ ProcessNextState_ClearPending;
+}
+
+transition({BUSY_INTR,BUSY_BLKD}, SnpInvPipe) {
+ Pop_TriggerQueue;
+ SnpInvPipe;
+ ProcessNextState_ClearPending;
+}
+
+transition({BUSY_INTR,BUSY_BLKD}, SnpOncePipe) {
+ Pop_TriggerQueue;
+ SnpOncePipe;
+ ProcessNextState_ClearPending;
+}
+
+// ReadShared / ReadNotSharedDirty
+
+transition(I, {ReadShared,ReadNotSharedDirty}, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_ReadShared_Miss;
+ Allocate_DirEntry;
+ Profile_Miss;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({RSC,RUSC}, {ReadShared,ReadNotSharedDirty}, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_ReadShared_HitUpstream_NoOwner;
+ Profile_Miss;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({UD,SD,UC,SC}, {ReadShared,ReadNotSharedDirty}, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_ReadShared_Hit;
+ Allocate_DirEntry;
+ Profile_Hit;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({UD_RSC,SD_RSC,UC_RSC,SC_RSC,UD_RSD,SD_RSD}, {ReadShared,ReadNotSharedDirty}, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_ReadShared_Hit;
+ Profile_Hit;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({UD_RU,UC_RU,RU,RSD,RUSD}, {ReadShared,ReadNotSharedDirty}, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_ReadShared_HitUpstream;
+ Profile_Miss;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+// ReadOnce
+
+transition(I, ReadOnce, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_ReadOnce_Miss;
+ Profile_Miss;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({UD,SD,UC,SC,UD_RSC,SD_RSC,UC_RSC,SC_RSC,UD_RSD,SD_RSD}, ReadOnce, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_ReadOnce_Hit;
+ Profile_Hit;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({UD_RU,UC_RU,RU,RSD,RUSD,RSC,RUSC}, ReadOnce, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_ReadOnce_HitUpstream;
+ Profile_Miss;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+
+// ReadUnique
+
+transition(I, {ReadUnique,ReadUnique_PoC}, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_ReadUnique_Miss;
+ Allocate_DirEntry;
+ Profile_Miss;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({UD,UC}, {ReadUnique,ReadUnique_PoC}, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_ReadUnique_Hit;
+ Allocate_DirEntry;
+ Profile_Hit;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({UD_RSC,UC_RSC,UD_RSD}, {ReadUnique,ReadUnique_PoC}, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_ReadUnique_Hit_InvUpstream;
+ Profile_Hit;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({UD_RU,UC_RU,RU,RUSD,RUSC}, {ReadUnique,ReadUnique_PoC}, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_ReadUnique_HitUpstream;
+ Profile_Miss;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({SC,SD}, ReadUnique_PoC, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_ReadUnique_AutoUpgrade;
+ Initiate_ReadUnique_Hit;
+ Allocate_DirEntry;
+ Profile_Hit;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({SC_RSC, SD_RSC, SD_RSD}, ReadUnique_PoC, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_ReadUnique_AutoUpgrade;
+ Initiate_ReadUnique_Hit_InvUpstream;
+ Profile_Hit;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({RSC,RSD}, ReadUnique_PoC, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_ReadUnique_AutoUpgrade;
+ Initiate_ReadUnique_HitUpstream;
+ Profile_Miss;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+
+transition({SC,SD}, ReadUnique, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_ReadUnique_Upgrade;
+ Allocate_DirEntry;
+ Profile_Miss;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({SC_RSC, SD_RSC, RSC, SD_RSD, RSD}, ReadUnique, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_ReadUnique_Upgrade;
+ Profile_Miss;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+// CleanUnique
+
+transition({I, SC, UC, SD, UD, RU, RSC, RSD, RUSD, RUSC,
+ SC_RSC, SD_RSD, SD_RSC, UC_RSC, UC_RU, UD_RU, UD_RSD, UD_RSC}, CleanUnique, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_CleanUnique;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+// WriteUniquePtl
+
+transition({UD,UD_RU,UD_RSD,UD_RSC,UC,UC_RU,UC_RSC},
+ {WriteUnique, WriteUniquePtl_PoC, WriteUniqueFull_PoC, WriteUniqueFull_PoC_Alloc},
+ BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_WriteUnique_LocalWrite;
+ Profile_Hit;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({SD, SD_RSD, SD_RSC, SC, SC_RSC},
+ {WriteUniquePtl_PoC, WriteUniqueFull_PoC, WriteUniqueFull_PoC_Alloc},
+ BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_WriteUnique_LocalWrite;
+ Profile_Miss;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({RSC,RSD,RUSD,RUSC,RU,I}, WriteUniqueFull_PoC_Alloc, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_WriteUnique_LocalWrite;
+ Profile_Miss;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({SD, SD_RSD, SD_RSC, SC, SC_RSC},
+ {WriteUnique}, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_WriteUnique_LocalWrite_AfterUpgrade;
+ Profile_Miss;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({RSD,RUSD,RUSC,RU}, {WriteUniquePtl_PoC, WriteUniqueFull_PoC}, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_WriteUnique_Writeback;
+ Profile_Miss;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({RSC,I}, {WriteUniquePtl_PoC, WriteUniqueFull_PoC}, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_WriteUnique_PartialWrite;
+ Profile_Miss;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({RSC,RSD,RUSD,RUSC,RU,I}, WriteUnique, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_WriteUnique_Forward;
+ Profile_Miss;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+
+// Load / Store from sequencer & Prefetch from prefetcher
+
+transition({UD,UD_T,SD,UC,SC}, Load, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_LoadHit;
+ Profile_Hit;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+// Prefetch hits if either this cache or one of its upstream caches has a
+// valid block.
+// In some states, using the normal hit path for a prefetch will deallocate
+// the local cache entry at the end since our data is stale. If the cache is
+// inclusive for unique data we need to keep the block, so just bypass the
+// normal path.
+transition({UD,UD_T,SD,UC,SC,RU,RSC,RSD,RUSD,SC_RSC,SD_RSC,SD_RSD,UC_RSC,UC_RU,UD_RU,UD_RSD,UD_RSC}, Prefetch) {
+ Callback_ExpressPrefetchHit;
+ Pop_ReqRdyQueue;
+}
+
+transition(BUSY_BLKD, LoadHit) {
+ Pop_TriggerQueue;
+ Callback_LoadHit;
+ ProcessNextState_ClearPending;
+}
+
+transition({UD,UD_T,UC}, Store, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_StoreHit;
+ Profile_Hit;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition(BUSY_BLKD, StoreHit) {
+ Pop_TriggerQueue;
+ Callback_StoreHit;
+ ProcessNextState_ClearPending;
+}
+
+transition(I, {Load,Prefetch}, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_LoadMiss;
+ Profile_Miss;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition(I, Store, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_StoreMiss;
+ Profile_Miss;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({SD,SC}, Store, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_StoreUpgrade;
+ Profile_Miss;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+// write timeout
+
+transition(UD_T, UseTimeout, UD) {
+ Unset_Timeout_Cache;
+}
+
+transition({BUSY_BLKD,BUSY_INTR}, UseTimeout) {
+ Unset_Timeout_TBE;
+}
+
+// Evict from Upstream
+
+transition({UD_RSC,SD_RSC,UC_RSC,SC_RSC,RSC,RSD,RUSD,RUSC,UD_RSD,SD_RSD}, Evict, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_Evict;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({UD, UD_RSC, SD_RSC, UC_RSC, SC_RSC, UD_RU, UC_RU, UD_RSD, SD_RSD, RU, RSC, RSD, RUSD, RUSC, SD, UC, SC, I},
+ Evict_Stale) {
+ Initiate_Request_Stale;
+ Send_CompI_Stale;
+ Finalize_DeallocateRequest;
+ Pop_ReqRdyQueue;
+}
+
+// WriteBack from upstream
+
+transition({UD_RU, UC_RU, RU, UD_RSD, SD_RSD, RSD, RUSD}, {WriteBackFull, WriteCleanFull}, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_CopyBack;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({UD_RU, UC_RU, RU}, WriteEvictFull, BUSY_BLKD) {
+ Initiate_Request;
+ Initiate_CopyBack;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+transition({UD_RSC, UC_RSC, SC_RSC, UD, RU, RSD, RUSD, RUSC, UD_RSD, SD_RSD, RSC, UD_RU, UC_RU, SD, UC, SC, I},
+ {WriteBackFull_Stale, WriteEvictFull_Stale, WriteCleanFull_Stale}, BUSY_BLKD) {
+ Initiate_Request_Stale;
+ Initiate_CopyBack_Stale;
+ Pop_ReqRdyQueue;
+ ProcessNextState;
+}
+
+// Cache Replacement
+
+// When in UD_RU,UC_RU,UD_RSD,SD_RSD we also just drop the line since an upstream
+// cache has an up-to-data line that it will either WriteBack or WriteEvict
+transition({SC,UC,SC_RSC,UC_RSC,
+ UD_RU,UC_RU,UD_RSD,SD_RSD}, LocalHN_Eviction, BUSY_BLKD) {ReplTBEAvailable} {
+ Initiate_Replacement;
+ Initiate_Replacement_JustDrop;
+ Profile_Eviction;
+ Deallocate_CacheBlock;
+ Pop_ReplTriggerQueue;
+ ProcessNextState;
+}
+
+transition({UD,SD,UD_RSC,SD_RSC}, LocalHN_Eviction, BUSY_BLKD) {ReplTBEAvailable} {
+ Initiate_Replacement;
+ Initiate_Replacement_WB;
+ Profile_Eviction;
+ Deallocate_CacheBlock;
+ Pop_ReplTriggerQueue;
+ ProcessNextState;
+}
+
+transition(SC, Local_Eviction, BUSY_BLKD) {ReplTBEAvailable} {
+ Initiate_Replacement;
+ Initiate_Replacement_Evict;
+ Profile_Eviction;
+ Deallocate_CacheBlock;
+ Pop_ReplTriggerQueue;
+ ProcessNextState;
+}
+
+transition({UD,SD,UC}, Local_Eviction, BUSY_BLKD) {ReplTBEAvailable} {
+ Initiate_Replacement;
+ Initiate_Replacement_WB;
+ Profile_Eviction;
+ Deallocate_CacheBlock;
+ Pop_ReplTriggerQueue;
+ ProcessNextState;
+}
+
+transition({UD_RU,UC_RU,UD_RSD,SD_RSD,SC_RSC,UC_RSC}, Local_Eviction, BUSY_BLKD) {ReplTBEAvailable} {
+ Initiate_Replacement;
+ Initiate_Replacement_JustDrop;
+ Profile_Eviction;
+ Deallocate_CacheBlock;
+ Pop_ReplTriggerQueue;
+ ProcessNextState;
+}
+
+transition({UD_RSC,SD_RSC}, Local_Eviction, BUSY_BLKD) {ReplTBEAvailable} {
+ Initiate_Replacement;
+ Initiate_Replacement_WB;
+ Profile_Eviction;
+ Deallocate_CacheBlock;
+ Pop_ReplTriggerQueue;
+ ProcessNextState;
+}
+
+transition({UD_RSC,SD_RSC,UC_RSC,UD_RU,UC_RU,UD_RSD}, Global_Eviction, BUSY_BLKD) {ReplTBEAvailable} {
+ Initiate_Replacement;
+ Initiate_Replacement_WB_BackInvalidate;
+ Profile_Eviction;
+ Deallocate_CacheBlock;
+ Deallocate_DirEntry;
+ Pop_ReplTriggerQueue;
+ ProcessNextState;
+}
+
+transition(SC_RSC, Global_Eviction, BUSY_BLKD) {ReplTBEAvailable} {
+ Initiate_Replacement;
+ Initiate_Replacement_Evict_BackInvalidte;
+ Profile_Eviction;
+ Deallocate_CacheBlock;
+ Deallocate_DirEntry;
+ Pop_ReplTriggerQueue;
+ ProcessNextState;
+}
+
+// This could happen if enqueued the eviction when the line was busy
+// or couldn't handle it immediately due to no TBE available
+transition({RU,RSC,RSD,RUSD,I}, {Local_Eviction, LocalHN_Eviction}) {
+ Pop_ReplTriggerQueue;
+}
+transition(I, Global_Eviction) {
+ Pop_ReplTriggerQueue;
+}
+
+// Snoops
+
+// SnpCleanInvalid/SnpUnique/SnpUniqueFwd
+// All invalidating snoops have a simular behavior
+
+transition({UD,SD,UC,SC,UD_RSC,SD_RSC,UC_RSC,UD_RU,UC_RU,RU,RUSD,RUSC,RSD,UD_RSD,SD_RSD,SC_RSC,RSC},
+ {SnpUnique,SnpUniqueFwd,SnpCleanInvalid}, BUSY_BLKD) {
+ Initiate_Snoop;
+ Initiate_InvalidationSnoop;
+ Profile_Eviction;
+ Pop_SnoopRdyQueue;
+ ProcessNextState;
+}
+
+transition(BUSY_INTR, {SnpUnique,SnpUniqueFwd,SnpCleanInvalid}, BUSY_BLKD) {
+ Initiate_Snoop_Hazard;
+ Initiate_InvalidationSnoop;
+ Profile_Eviction;
+ Pop_SnoopRdyQueue;
+ ProcessNextState;
+}
+
+// SnpShared / SnpNotSharedDirty
+
+transition({UD,UD_RSC,SD,SD_RSC,UC,UC_RSC,UD_RU,UC_RU,RU,UD_RSD,SD_RSD,RSD,RUSD,RUSC},
+ {SnpShared,SnpSharedFwd,SnpNotSharedDirtyFwd}, BUSY_BLKD) {
+ Initiate_Snoop;
+ Initiate_SnpShared;
+ Pop_SnoopRdyQueue;
+ ProcessNextState;
+}
+
+transition({SC, SC_RSC, RSC}, {SnpSharedFwd, SnpNotSharedDirtyFwd}, BUSY_BLKD) {
+ Initiate_Snoop;
+ Initiate_SnpShared;
+ Pop_SnoopRdyQueue;
+ ProcessNextState;
+}
+
+transition(BUSY_INTR, {SnpShared,SnpSharedFwd,SnpNotSharedDirtyFwd}, BUSY_BLKD) {
+ Initiate_Snoop_Hazard;
+ Initiate_SnpShared;
+ Pop_SnoopRdyQueue;
+ ProcessNextState;
+}
+
+// SnpOnce
+transition({UD,UD_T,UD_RSC,UD_RU,UD_RSD,SD,SD_RSC,SD_RSD,UC,UC_RSC,UC_RU,SC,SC_RSC,RU,RSC,RSD,RUSD,RUSC},
+ {SnpOnce,SnpOnceFwd}, BUSY_BLKD) {
+ Initiate_Snoop;
+ Initiate_SnpOnce;
+ Pop_SnoopRdyQueue;
+ ProcessNextState;
+}
+
+transition(BUSY_INTR, {SnpOnce,SnpOnceFwd}, BUSY_BLKD) {
+ Initiate_Snoop_Hazard;
+ Initiate_SnpOnce;
+ Pop_SnoopRdyQueue;
+ ProcessNextState;
+}
+
+
+// Stalls
+
+transition({BUSY_BLKD,BUSY_INTR},
+ {ReadShared, ReadNotSharedDirty, ReadUnique, ReadUnique_PoC,
+ ReadOnce, CleanUnique,
+ Load, Store, Prefetch,
+ WriteBackFull, WriteBackFull_Stale,
+ WriteEvictFull, WriteEvictFull_Stale,
+ WriteCleanFull, WriteCleanFull_Stale,
+ Evict, Evict_Stale,
+ WriteUnique,WriteUniquePtl_PoC,
+ WriteUniqueFull_PoC,WriteUniqueFull_PoC_Alloc}) {
+ StallRequest;
+}
+
+transition({BUSY_BLKD,BUSY_INTR},
+ {Global_Eviction, Local_Eviction, LocalHN_Eviction}) {
+ StallLocalEviction;
+}
+
+// Kill the timer and try again as a snoop may be pending as well
+transition(UD_T, {Global_Eviction, Local_Eviction, LocalHN_Eviction}, UD) {
+ Unset_Timeout_Cache;
+ Pop_ReplTriggerQueue;
+}
+
+transition(BUSY_BLKD,
+ {SnpCleanInvalid,SnpShared,SnpUnique,SnpSharedFwd,SnpUniqueFwd,
+ SnpNotSharedDirtyFwd, SnpOnce}) {
+ StallSnoop;
+}
+
+transition({BUSY_BLKD,BUSY_INTR}, SnpStalled) {
+ StallSnoop;
+}
+
+transition(UD_T, {SnpCleanInvalid,SnpShared,SnpUnique,SnpSharedFwd,SnpUniqueFwd,
+ SnpNotSharedDirtyFwd}) {
+ StallSnoop_NoTBE;
+}
+
+transition({BUSY_BLKD,BUSY_INTR}, ActionStalledOnHazard) {
+ StallActionOnHazard;
+}
+
+// Trigger-specifc transitions
+
+transition(BUSY_BLKD, SendWriteBackOrWriteEvict, BUSY_INTR) {DestinationAvailable} {
+ Pop_TriggerQueue;
+ Send_WriteBackOrWriteEvict;
+ Profile_OutgoingStart;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendWriteClean, BUSY_INTR) {DestinationAvailable} {
+ Pop_TriggerQueue;
+ Send_WriteCleanFull;
+ Profile_OutgoingStart;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendWriteUnique, BUSY_INTR) {DestinationAvailable} {
+ Pop_TriggerQueue;
+ Send_WriteUnique;
+ Profile_OutgoingStart;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendWriteNoSnp, BUSY_INTR) {DestinationAvailable} {
+ Pop_TriggerQueue;
+ Send_WriteNoSnp;
+ Profile_OutgoingStart;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendWriteNoSnpPartial, BUSY_INTR) {DestinationAvailable} {
+ Pop_TriggerQueue;
+ Send_WriteNoSnp_Partial;
+ Profile_OutgoingStart;
+ ProcessNextState_ClearPending;
+}
+
+
+transition(BUSY_BLKD, SendEvict, BUSY_INTR) {DestinationAvailable} {
+ Pop_TriggerQueue;
+ Send_Evict;
+ Profile_OutgoingStart;
+ ProcessNextState_ClearPending;
+}
+
+// May get here from BUSY_INTR
+transition({BUSY_BLKD, BUSY_INTR}, SendCompData, BUSY_BLKD) {
+ Pop_TriggerQueue;
+ Send_CompData;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendWBData) {
+ Pop_TriggerQueue;
+ Send_WBData;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendWUData) {
+ Pop_TriggerQueue;
+ Send_WUData;
+ CheckWUComp;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendWUDataCB) {
+ Pop_TriggerQueue;
+ Callback_WriteUnique;
+ Send_WUData;
+ CheckWUComp;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendInvSnpResp) {
+ Pop_TriggerQueue;
+ Send_InvSnpResp;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendSnpData) {
+ Pop_TriggerQueue;
+ Send_SnpRespData;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendSnpUniqueFwdCompData) {
+ Pop_TriggerQueue;
+ Send_CompData_SnpUniqueFwd;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendSnpSharedFwdCompData) {
+ Pop_TriggerQueue;
+ Send_CompData_SnpSharedFwd;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendSnpNotSharedDirtyFwdCompData) {
+ Pop_TriggerQueue;
+ Send_CompData_SnpNSDFwd;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendSnpOnceFwdCompData) {
+ Pop_TriggerQueue;
+ Send_CompData_SnpOnceFwd;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendSnpFwdedData) {
+ Pop_TriggerQueue;
+ Send_SnpRespDataFwded;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendSnpFwdedResp) {
+ Pop_TriggerQueue;
+ Send_FwdSnpResp;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendCompAck) {
+ Pop_TriggerQueue;
+ Send_CompAck;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendSnpIResp) {
+ Pop_TriggerQueue;
+ Send_SnpRespI;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendCompIResp) {
+ Pop_TriggerQueue;
+ Send_CompI;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendCompUCResp) {
+ Pop_TriggerQueue;
+ Send_CompUC;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendRespSepData) {
+ Pop_TriggerQueue;
+ Send_RespSepData;
+ ProcessNextState_ClearPending;
+}
+
+transition({BUSY_INTR, BUSY_BLKD}, WaitCompAck) {
+ Pop_TriggerQueue;
+ ExpectCompAck;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, RestoreFromHazard, BUSY_INTR) {
+ Pop_TriggerQueue;
+ RestoreFromHazard;
+}
+
+transition(BUSY_BLKD, SendReadShared, BUSY_INTR) {DestinationAvailable} {
+ Pop_TriggerQueue;
+ Send_ReadShared;
+ Profile_OutgoingStart;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendReadOnce, BUSY_INTR) {DestinationAvailable} {
+ Pop_TriggerQueue;
+ Send_ReadOnce;
+ Profile_OutgoingStart;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendReadUnique, BUSY_INTR) {DestinationAvailable} {
+ Pop_TriggerQueue;
+ Send_ReadUnique;
+ Profile_OutgoingStart;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendCleanUnique, BUSY_INTR) {DestinationAvailable} {
+ Pop_TriggerQueue;
+ Send_CleanUnique;
+ Profile_OutgoingStart;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendReadNoSnp, BUSY_INTR) {DestinationAvailable} {
+ Pop_TriggerQueue;
+ Send_ReadNoSnp;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendReadNoSnpDMT, BUSY_INTR) {DestinationAvailable} {
+ Pop_TriggerQueue;
+ Send_ReadNoSnpDMT;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendSnpShared) {
+ Pop_TriggerQueue;
+ Send_SnpShared;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendSnpSharedFwdToOwner) {
+ Pop_TriggerQueue;
+ Send_SnpSharedFwd_ToOwner;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendSnpSharedFwdToSharer) {
+ Pop_TriggerQueue;
+ Send_SnpSharedFwd_ToSharer;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendSnpOnceFwd) {
+ Pop_TriggerQueue;
+ Send_SnpOnceFwd;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendSnpOnce) {
+ Pop_TriggerQueue;
+ Send_SnpOnce;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendSnpUnique) {
+ Pop_TriggerQueue;
+ Send_SnpUnique;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendSnpUniqueRetToSrc) {
+ Pop_TriggerQueue;
+ Send_SnpUnique_RetToSrc;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendSnpUniqueFwd) {
+ Pop_TriggerQueue;
+ Send_SnpUniqueFwd;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendSnpCleanInvalid) {
+ Pop_TriggerQueue;
+ Send_SnpCleanInvalid;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendSnpCleanInvalidNoReq) {
+ Pop_TriggerQueue;
+ Send_SnpCleanInvalid_NoReq;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendCompDBIDResp) {
+ Pop_TriggerQueue;
+ Send_CompDBIDResp;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendCompDBIDResp_WU) {
+ Pop_TriggerQueue;
+ ExpectNCBWrData;
+ Send_CompDBIDResp;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendDBIDResp_WU) {
+ Pop_TriggerQueue;
+ ExpectNCBWrData;
+ Send_DBIDResp;
+ ProcessNextState_ClearPending;
+}
+
+transition({BUSY_BLKD,BUSY_INTR}, SendComp_WU) {
+ Pop_TriggerQueue;
+ Send_Comp_WU;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, SendCompDBIDRespStale) {
+ Pop_TriggerQueue;
+ Send_CompDBIDResp_Stale;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, MaintainCoherence) {
+ Pop_TriggerQueue;
+ Initiate_MaitainCoherence;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, FinishCleanUnique) {
+ Pop_TriggerQueue;
+ Finish_CleanUnique;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, CheckUpgrade_FromStore) {
+ Pop_TriggerQueue;
+ Callback_Miss; // note: Callback happens only if tbe.dataValid
+ CheckUpgrade_FromStore;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, CheckUpgrade_FromCU) {
+ Pop_TriggerQueue;
+ CheckUpgrade_FromCU;
+ ProcessNextState_ClearPending;
+}
+
+transition(BUSY_BLKD, CheckUpgrade_FromRU) {
+ Pop_TriggerQueue;
+ CheckUpgrade_FromRU;
+ ProcessNextState_ClearPending;
+}
+
+
+// Generic send/receive transitions
+
+// waiting for data
+transition(BUSY_BLKD,
+ {CBWrData_I,CBWrData_SC,CBWrData_SD_PD,CBWrData_UC,CBWrData_UD_PD}) {
+ Receive_ReqDataResp;
+ UpdateDirState_FromReqDataResp;
+ UpdateDataState_FromReqDataResp;
+ Pop_DataInQueue;
+ ProcessNextState;
+}
+
+// could be waiting for both data and CompDBIDResp on a WriteUnique
+transition({BUSY_BLKD,BUSY_INTR}, NCBWrData) {
+ Receive_ReqDataResp;
+ UpdateDataState_FromWUDataResp;
+ Pop_DataInQueue;
+ ProcessNextState;
+}
+
+transition(BUSY_BLKD,
+ {SnpRespData_I_PD,SnpRespData_I,SnpRespData_SC_PD,
+ SnpRespData_SC,SnpRespData_SD,SnpRespData_UD,
+ SnpRespData_SC_Fwded_SC,SnpRespData_SC_Fwded_SD_PD,
+ SnpRespData_SC_PD_Fwded_SC,SnpRespData_I_Fwded_SD_PD,
+ SnpRespData_I_PD_Fwded_SC,SnpRespData_I_Fwded_SC}) {
+ Receive_SnpDataResp;
+ UpdateDirState_FromSnpDataResp;
+ UpdateDataState_FromSnpDataResp;
+ Pop_DataInQueue;
+ ProcessNextState;
+}
+
+transition({BUSY_BLKD,BUSY_INTR}, RespSepData, BUSY_BLKD) {
+ Receive_RespSepData;
+ Pop_RespInQueue;
+ ProcessNextState;
+}
+
+transition({BUSY_BLKD,BUSY_INTR}, DataSepResp_UC, BUSY_BLKD) {
+ Receive_ReqDataResp;
+ UpdateDataState_FromReqDataResp;
+ Callback_Miss;
+ Profile_OutgoingEnd_DataResp;
+ Pop_DataInQueue;
+ ProcessNextState;
+}
+
+transition({BUSY_BLKD,BUSY_INTR},
+ {CompData_I,CompData_SC,CompData_SD_PD,CompData_UC,CompData_UD_PD},
+ BUSY_BLKD) {
+ Receive_RespSepDataFromCompData;
+ Receive_ReqDataResp;
+ UpdateDataState_FromReqDataResp;
+ Callback_Miss;
+ Profile_OutgoingEnd_DataResp;
+ Pop_DataInQueue;
+ ProcessNextState;
+}
+
+transition(BUSY_INTR, ReadReceipt, BUSY_BLKD) {
+ Receive_ReadReceipt;
+ Pop_RespInQueue;
+ ProcessNextState;
+}
+
+// Retry handling
+
+transition(BUSY_INTR, {RetryAck, RetryAck_PoC}) {
+ Receive_RetryAck;
+ Pop_RespInQueue;
+ ProcessNextState;
+}
+
+transition(BUSY_INTR, {PCrdGrant, PCrdGrant_PoC}) {
+ Receive_PCrdGrant;
+ Pop_RespInQueue;
+ ProcessNextState;
+}
+
+// RetryAck/PCrdGrant on BUSY_BLKD is only expected in a PoC/HN when waiting
+// for CompAck after sending down a request with DMT enabled. Handle the same
+// as BUSY_INTR
+
+transition(BUSY_BLKD, RetryAck_PoC) {
+ Receive_RetryAck;
+ Pop_RespInQueue;
+ ProcessNextState;
+}
+
+transition(BUSY_BLKD, PCrdGrant_PoC) {
+ Receive_PCrdGrant;
+ Pop_RespInQueue;
+ ProcessNextState;
+}
+
+// RetryAck/PCrdGrant received during a snoop hazard may arrive in both
+// BUSY_BLKD and BUSY_INTR
+transition({BUSY_INTR,BUSY_BLKD}, {RetryAck_Hazard, RetryAck_PoC_Hazard}) {
+ Receive_RetryAck_Hazard;
+ Pop_RespInQueue;
+ ProcessNextState;
+}
+
+transition({BUSY_INTR,BUSY_BLKD}, {PCrdGrant_Hazard, PCrdGrant_PoC_Hazard}) {
+ Receive_PCrdGrant_Hazard;
+ Pop_RespInQueue;
+ ProcessNextState;
+}
+
+// Resend the request after RetryAck+PCrdGrant received
+
+transition({BUSY_INTR,BUSY_BLKD}, DoRetry) {
+ Send_Retry;
+ Pop_RetryTriggerQueue;
+}
+
+transition({BUSY_INTR,BUSY_BLKD}, DoRetry_Hazard) {
+ Send_Retry_Hazard;
+ Pop_RetryTriggerQueue;
+}
+
+// waiting for completion ack
+transition({BUSY_BLKD,BUSY_INTR}, CompAck) {
+ Receive_ReqResp;
+ UpdateDirState_FromReqResp;
+ Pop_RespInQueue;
+ ProcessNextState;
+}
+
+transition(BUSY_BLKD,
+ {SnpResp_I,SnpResp_SC,
+ SnpResp_I_Fwded_UC,SnpResp_I_Fwded_UD_PD,
+ SnpResp_SC_Fwded_SC,SnpResp_SC_Fwded_SD_PD,
+ SnpResp_UC_Fwded_I,SnpResp_UD_Fwded_I,
+ SnpResp_SC_Fwded_I,SnpResp_SD_Fwded_I}) {
+ Receive_SnpResp;
+ UpdateDirState_FromSnpResp;
+ Pop_RespInQueue;
+ ProcessNextState;
+}
+
+// waiting for WB or evict ack
+transition(BUSY_INTR,
+ {CompDBIDResp,Comp_I}, BUSY_BLKD) {
+ Receive_ReqResp;
+ Profile_OutgoingEnd_DatalessResp;
+ Pop_RespInQueue;
+ ProcessNextState;
+}
+
+// currently this happens after a CleanUnique
+transition(BUSY_INTR, Comp_UC, BUSY_BLKD) {
+ Receive_ReqResp;
+ UpdateDataState_FromCUResp;
+ Profile_OutgoingEnd_DatalessResp;
+ Pop_RespInQueue;
+ ProcessNextState;
+}
+
+// alternative flow for WU with separate Comp
+transition(BUSY_INTR, DBIDResp, BUSY_BLKD) {
+ Receive_ReqResp;
+ Receive_ReqResp_WUNeedComp;
+ Pop_RespInQueue;
+ ProcessNextState;
+}
+transition(BUSY_BLKD, Comp) {
+ Receive_ReqResp_WUComp;
+ Profile_OutgoingEnd_DatalessResp;
+ Pop_RespInQueue;
+ ProcessNextState;
+}
+
+transition(BUSY_BLKD, TX_Data) {
+ Pop_TriggerQueue;
+ Send_Data;
+ ProcessNextState_ClearPending;
+}
+
+// Finalization transition
+
+transition({BUSY_BLKD,BUSY_INTR}, Final, *) {
+ Pop_TriggerQueue;
+ Finalize_UpdateCacheFromTBE;
+ Finalize_UpdateDirectoryFromTBE;
+ Finalize_DeallocateRequest;
+}
diff --git a/src/mem/ruby/protocol/chi/CHI-cache.sm b/src/mem/ruby/protocol/chi/CHI-cache.sm
new file mode 100644
index 0000000..160f674
--- /dev/null
+++ b/src/mem/ruby/protocol/chi/CHI-cache.sm
@@ -0,0 +1,775 @@
+/*
+ * Copyright (c) 2021 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.
+ *
+ * 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.
+ */
+
+
+machine(MachineType:Cache, "Cache coherency protocol") :
+ // Sequencer to insert Load/Store requests.
+ // May be null if this is not a L1 cache
+ Sequencer * sequencer;
+
+ // Cache for storing local lines.
+ // NOTE: it is assumed that a cache tag and directory lookups and updates
+ // happen in parallel. The cache tag latency is used for both cases.
+ CacheMemory * cache;
+
+ // Additional pipeline latency modeling for the different request types
+ // When defined, these are applied after the initial tag array read and
+ // sending necessary snoops.
+ Cycles read_hit_latency := 0;
+ Cycles read_miss_latency := 0;
+ Cycles write_fe_latency := 0; // Front-end: Rcv req -> Snd req
+ Cycles write_be_latency := 0; // Back-end: Rcv ack -> Snd data
+ Cycles fill_latency := 0; // Fill latency
+ Cycles snp_latency := 0; // Applied before handling any snoop
+ Cycles snp_inv_latency := 0; // Additional latency for invalidating snoops
+
+ // Waits for cache data array write to complete before executing next action
+ // Note a new write will always block if bank stalls are enabled in the cache
+ bool wait_for_cache_wr := "False";
+
+ // Request TBE allocation latency
+ Cycles allocation_latency := 0;
+
+ // Enqueue latencies for outgoing messages
+ // NOTE: should remove this and only use parameters above?
+ Cycles request_latency := 1;
+ Cycles response_latency := 1;
+ Cycles snoop_latency := 1;
+ Cycles data_latency := 1;
+
+ // When an SC fails, unique lines are locked to this controller for a period
+ // proportional to the number of consecutive failed SC requests. See
+ // the usage of sc_lock_multiplier and llscCheckMonitor for details
+ int sc_lock_base_latency_cy := 4;
+ int sc_lock_multiplier_inc := 4;
+ int sc_lock_multiplier_decay := 1;
+ int sc_lock_multiplier_max := 256;
+ bool sc_lock_enabled;
+
+ // Recycle latency on resource stalls
+ Cycles stall_recycle_lat := 1;
+
+ // Notify the sequencer when a line is evicted. This should be set is the
+ // sequencer is not null and handled LL/SC request types.
+ bool send_evictions;
+
+ // Number of entries in the snoop and replacement TBE tables
+ // notice the "number_of_TBEs" parameter is defined by AbstractController
+ int number_of_snoop_TBEs;
+ int number_of_repl_TBEs;
+
+ // replacements use the same TBE slot as the request that triggered it
+ // in this case the number_of_repl_TBEs parameter is ignored
+ bool unify_repl_TBEs;
+
+ // wait for the final tag update to complete before deallocating TBE and
+ // going to final stable state
+ bool dealloc_wait_for_tag := "False";
+
+ // Width of the data channel. Data transfer are split in multiple messages
+ // at the protocol level when this is less than the cache line size.
+ int data_channel_size;
+
+ // Set when this is used as the home node and point of coherency of the
+ // system. Must be false for every other cache level.
+ bool is_HN;
+
+ // Enables direct memory transfers between SNs and RNs when the data is
+ // not cache in the HN.
+ bool enable_DMT;
+
+ // Use ReadNoSnpSep instead of ReadNoSnp for DMT requests, which allows
+ // the TBE to be deallocated at HNFs before the requester receives the data
+ bool enable_DMT_early_dealloc := "False";
+
+ // Enables direct cache transfers, i.e., use forwarding snoops whenever
+ // possible.
+ bool enable_DCT;
+
+ // Use separate Comp/DBIDResp responses for WriteUnique
+ bool comp_wu := "False";
+ // additional latency for the WU Comp response
+ Cycles comp_wu_latency := 0;
+
+ // Controls cache clusivity for different request types.
+ // set all alloc_on* to false to completelly disable caching
+ bool alloc_on_readshared;
+ bool alloc_on_readunique;
+ bool alloc_on_readonce;
+ bool alloc_on_writeback;
+ bool alloc_on_seq_acc;
+ bool alloc_on_seq_line_write;
+ // Controls if the clusivity is strict.
+ bool dealloc_on_unique;
+ bool dealloc_on_shared;
+ bool dealloc_backinv_unique;
+ bool dealloc_backinv_shared;
+
+ // If the responder has the line in UC or UD state, propagate this state
+ // on a ReadShared. Notice data won't be deallocated if dealloc_on_unique is
+ // set
+ bool fwd_unique_on_readshared := "False";
+
+ // Allow receiving data in SD state.
+ bool allow_SD;
+
+ // stall new requests to destinations with a pending retry
+ bool throttle_req_on_retry := "True";
+
+ // Use prefetcher
+ bool use_prefetcher, default="false";
+
+ // Message Queues
+
+ // Interface to the network
+ // Note vnet_type is used by Garnet only. "response" type is assumed to
+ // have data, so use it for data channels and "none" for the rest.
+ // network="To" for outbound queue; network="From" for inbound
+ // virtual networks: 0=request, 1=snoop, 2=response, 3=data
+
+ MessageBuffer * reqOut, network="To", virtual_network="0", vnet_type="none";
+ MessageBuffer * snpOut, network="To", virtual_network="1", vnet_type="none";
+ MessageBuffer * rspOut, network="To", virtual_network="2", vnet_type="none";
+ MessageBuffer * datOut, network="To", virtual_network="3", vnet_type="response";
+
+ MessageBuffer * reqIn, network="From", virtual_network="0", vnet_type="none";
+ MessageBuffer * snpIn, network="From", virtual_network="1", vnet_type="none";
+ MessageBuffer * rspIn, network="From", virtual_network="2", vnet_type="none";
+ MessageBuffer * datIn, network="From", virtual_network="3", vnet_type="response";
+
+ // Mandatory queue for receiving requests from the sequencer
+ MessageBuffer * mandatoryQueue;
+
+ // Internal queue for trigger events
+ MessageBuffer * triggerQueue;
+
+ // Internal queue for retry trigger events
+ MessageBuffer * retryTriggerQueue;
+
+ // Internal queue for accepted requests
+ MessageBuffer * reqRdy;
+
+ // Internal queue for accepted snoops
+ MessageBuffer * snpRdy;
+
+ // Internal queue for eviction requests
+ MessageBuffer * replTriggerQueue;
+
+ // Prefetch queue for receiving prefetch requests from prefetcher
+ MessageBuffer * prefetchQueue;
+
+ // Requests that originated from a prefetch in a upstream cache are treated
+ // as demand access in this cache. Notice the demand access stats are still
+ // updated only on true demand requests.
+ bool upstream_prefetch_trains_prefetcher := "False";
+
+{
+
+ ////////////////////////////////////////////////////////////////////////////
+ // States
+ ////////////////////////////////////////////////////////////////////////////
+
+ state_declaration(State, default="Cache_State_null") {
+ // Stable states
+
+ I, AccessPermission:Invalid, desk="Invalid / not present locally or upstream";
+
+ // States when block is present in local cache only
+ SC, AccessPermission:Read_Only, desc="Shared Clean";
+ UC, AccessPermission:Read_Write, desc="Unique Clean";
+ SD, AccessPermission:Read_Only, desc="Shared Dirty";
+ UD, AccessPermission:Read_Write, desc="Unique Dirty";
+ UD_T, AccessPermission:Read_Write, desc="UD with use timeout";
+
+ // Invalid in local cache but present in upstream caches
+ RU, AccessPermission:Invalid, desk="Upstream requester has line in UD/UC";
+ RSC, AccessPermission:Invalid, desk="Upstream requester has line in SC";
+ RSD, AccessPermission:Invalid, desk="Upstream requester has line in SD and maybe SC";
+ RUSC, AccessPermission:Invalid, desk="RSC + this node stills has exclusive access";
+ RUSD, AccessPermission:Invalid, desk="RSD + this node stills has exclusive access";
+
+ // Both in local and upstream caches. In some cases local maybe stale
+ SC_RSC, AccessPermission:Read_Only, desk="SC + RSC";
+ SD_RSC, AccessPermission:Read_Only, desk="SD + RSC";
+ SD_RSD, AccessPermission:Read_Only, desk="SD + RSD";
+ UC_RSC, AccessPermission:Read_Write, desk="UC + RSC";
+ UC_RU, AccessPermission:Invalid, desk="UC + RU";
+ UD_RU, AccessPermission:Invalid, desk="UD + RU";
+ UD_RSD, AccessPermission:Read_Write, desk="UD + RSD";
+ UD_RSC, AccessPermission:Read_Write, desk="UD + RSC";
+
+ // Generic transient state
+ // There is only a transient "BUSY" state. The actions taken at this state
+ // and the final stable state are defined by information in the TBE.
+ // While on BUSY_INTR, we will reply to incoming snoops and the
+ // state of the cache line may change. While on BUSY_BLKD snoops
+ // are blocked
+ BUSY_INTR, AccessPermission:Busy, desc="Waiting for data and/or ack";
+ BUSY_BLKD, AccessPermission:Busy, desc="Waiting for data and/or ack; blocks snoops";
+
+ // Null state for debugging
+ null, AccessPermission:Invalid, desc="Null state";
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Events
+ ////////////////////////////////////////////////////////////////////////////
+
+ enumeration(Event) {
+ // Events triggered by incoming requests. Allocate TBE and move
+ // request or snoop to the ready queue
+ AllocRequest, desc="Allocates a TBE for a request. Triggers a retry if table is full";
+ AllocRequestWithCredit, desc="Allocates a TBE for a request. Always succeeds.";
+ AllocSeqRequest, desc="Allocates a TBE for a sequencer request. Stalls requests if table is full";
+ AllocPfRequest, desc="Allocates a TBE for a prefetch request. Stalls requests if table is full";
+ AllocSnoop, desc="Allocates a TBE for a snoop. Stalls snoop if table is full";
+
+ // Events triggered by sequencer requests or snoops in the rdy queue
+ // See CHIRequestType in CHi-msg.sm for descriptions
+ Load;
+ Store;
+ Prefetch;
+ ReadShared;
+ ReadNotSharedDirty;
+ ReadUnique;
+ ReadUnique_PoC;
+ ReadOnce;
+ CleanUnique;
+ Evict;
+ WriteBackFull;
+ WriteEvictFull;
+ WriteCleanFull;
+ WriteUnique;
+ WriteUniquePtl_PoC;
+ WriteUniqueFull_PoC;
+ WriteUniqueFull_PoC_Alloc;
+ SnpCleanInvalid;
+ SnpShared;
+ SnpSharedFwd;
+ SnpNotSharedDirtyFwd;
+ SnpUnique;
+ SnpUniqueFwd;
+ SnpOnce;
+ SnpOnceFwd;
+ SnpStalled; // A snoop stall triggered from the inport
+
+ // Events triggered by incoming response messages
+ // See CHIResponseType in CHi-msg.sm for descriptions
+ CompAck;
+ Comp_I;
+ Comp_UC;
+ Comp_SC;
+ CompDBIDResp;
+ DBIDResp;
+ Comp;
+ ReadReceipt;
+ RespSepData;
+ SnpResp_I;
+ SnpResp_I_Fwded_UC;
+ SnpResp_I_Fwded_UD_PD;
+ SnpResp_SC;
+ SnpResp_SC_Fwded_SC;
+ SnpResp_SC_Fwded_SD_PD;
+ SnpResp_UC_Fwded_I;
+ SnpResp_UD_Fwded_I;
+ SnpResp_SC_Fwded_I;
+ SnpResp_SD_Fwded_I;
+ RetryAck;
+ RetryAck_PoC;
+ PCrdGrant;
+ PCrdGrant_PoC;
+ RetryAck_Hazard;
+ RetryAck_PoC_Hazard;
+ PCrdGrant_Hazard;
+ PCrdGrant_PoC_Hazard;
+
+ // Events triggered by incoming data response messages
+ // See CHIDataType in CHi-msg.sm for descriptions
+ CompData_I;
+ CompData_UC;
+ CompData_SC;
+ CompData_UD_PD;
+ CompData_SD_PD;
+ DataSepResp_UC;
+ CBWrData_I;
+ CBWrData_UC;
+ CBWrData_SC;
+ CBWrData_UD_PD;
+ CBWrData_SD_PD;
+ NCBWrData;
+ SnpRespData_I;
+ SnpRespData_I_PD;
+ SnpRespData_SC;
+ SnpRespData_SC_PD;
+ SnpRespData_SD;
+ SnpRespData_UC;
+ SnpRespData_UD;
+ SnpRespData_SC_Fwded_SC;
+ SnpRespData_SC_Fwded_SD_PD;
+ SnpRespData_SC_PD_Fwded_SC;
+ SnpRespData_I_Fwded_SD_PD;
+ SnpRespData_I_PD_Fwded_SC;
+ SnpRespData_I_Fwded_SC;
+
+ // We use special events for requests that we detect to be stale. This is
+ // done for debugging only. We sent a stale response so the requester can
+ // confirm the request is indeed stale and this is not a protocol bug.
+ // A Write or Evict becomes stale when the requester receives a snoop that
+ // changes the state of the data while the request was pending.
+ // Actual CHI implementations don't have this check.
+ Evict_Stale;
+ WriteBackFull_Stale;
+ WriteEvictFull_Stale;
+ WriteCleanFull_Stale;
+
+ // Cache fill handling
+ CheckCacheFill, desc="Check if need to write or update the cache and trigger any necessary allocation and evictions";
+
+ // Internal requests generated to evict or writeback a local copy
+ // to free-up cache space
+ Local_Eviction, desc="Evicts/WB the local copy of the line";
+ LocalHN_Eviction, desc="Local_Eviction triggered when is HN";
+ Global_Eviction, desc="Local_Eviction + back-invalidate line in all upstream requesters";
+
+ // Events triggered from tbe.actions
+ // In general, for each event we define a single transition from
+ // BUSY_BLKD and/or BUSY_INTR.
+ // See processNextState functions and Initiate_* actions.
+ // All triggered transitions execute in the same cycle until it has to wait
+ // for pending pending responses or data (set by expected_req_resp and
+ // expected_snp_resp). Triggers queued with pushNB are executed even if
+ // there are pending messages.
+
+ // Cache/directory access events. Notice these only model the latency.
+ TagArrayRead, desc="Read the cache and directory tag array";
+ TagArrayWrite, desc="Write the cache and directory tag array";
+ DataArrayRead, desc="Read the cache data array";
+ DataArrayWrite, desc="Write the cache data array";
+ DataArrayWriteOnFill, desc="Write the cache data array (cache fill)";
+
+ // Events for modeling the pipeline latency
+ ReadHitPipe, desc="Latency of reads served from local cache";
+ ReadMissPipe, desc="Latency of reads not served from local cache";
+ WriteFEPipe, desc="Front-end latency of write requests";
+ WriteBEPipe, desc="Back-end latency of write requests";
+ FillPipe, desc="Cache fill latency";
+ SnpSharedPipe, desc="Latency for SnpShared requests";
+ SnpInvPipe, desc="Latency for SnpUnique and SnpCleanInv requests";
+ SnpOncePipe, desc="Latency for SnpOnce requests";
+
+ // Send a read request downstream.
+ SendReadShared, desc="Send a ReadShared or ReadNotSharedDirty is allow_SD is false";
+ SendReadOnce, desc="Send a ReadOnce";
+ SendReadNoSnp, desc="Send a SendReadNoSnp";
+ SendReadNoSnpDMT, desc="Send a SendReadNoSnp using DMT";
+ SendReadUnique, desc="Send a ReadUnique";
+ SendCompAck, desc="Send CompAck";
+ // Read handling at the completer
+ SendCompData, desc="Send CompData";
+ WaitCompAck, desc="Expect to receive CompAck";
+ SendRespSepData, desc="Send RespSepData for a DMT request";
+
+ // Send a write request downstream.
+ SendWriteBackOrWriteEvict, desc="Send a WriteBackFull (if line is UD or SD) or WriteEvictFull (if UC)";
+ SendWriteClean, desc="Send a WriteCleanFull";
+ SendWriteNoSnp, desc="Send a WriteNoSnp for a full line";
+ SendWriteNoSnpPartial, desc="Send a WriteNoSnpPtl";
+ SendWriteUnique, desc="Send a WriteUniquePtl";
+ SendWBData, desc="Send writeback data";
+ SendWUData, desc="Send write unique data";
+ SendWUDataCB, desc="Send write unique data from a sequencer callback";
+ // Write handling at the completer
+ SendCompDBIDResp, desc="Ack WB with CompDBIDResp";
+ SendCompDBIDRespStale, desc="Ack stale WB with CompDBIDResp";
+ SendCompDBIDResp_WU, desc="Ack WU with CompDBIDResp and set expected data";
+ SendDBIDResp_WU, desc="Ack WU with DBIDResp and set expected data";
+ SendComp_WU, desc="Ack WU completion";
+
+ // Dataless requests
+ SendEvict, desc="Send a Evict";
+ SendCompIResp, desc="Ack Evict with Comp_I";
+ SendCleanUnique,desc="Send a CleanUnique";
+ SendCompUCResp, desc="Ack CleanUnique with Comp_UC";
+
+ // Checks if an upgrade using a CleanUnique was sucessfull
+ CheckUpgrade_FromStore, desc="Upgrade needed by a Store";
+ CheckUpgrade_FromCU, desc="Upgrade needed by an upstream CleanUnique";
+ CheckUpgrade_FromRU, desc="Upgrade needed by an upstream ReadUnique";
+
+ // Snoop requests
+ // SnpNotSharedDirty are sent instead of SnpShared for ReadNotSharedDirty
+ SendSnpShared, desc="Send a SnpShared/SnpNotSharedDirty to sharer in UC,UD, or SD state";
+ SendSnpSharedFwdToOwner, desc="Send a SnpSharedFwd/SnpNotSharedDirtyFwd to sharer in UC,UD, or SD state";
+ SendSnpSharedFwdToSharer, desc="Send a SnpSharedFwd/SnpNotSharedDirtyFwd to a sharer in SC state";
+ SendSnpOnce, desc="Send a SnpOnce to a sharer";
+ SendSnpOnceFwd, desc="Send a SnpOnceFwd to a sharer";
+ SendSnpUnique, desc="Send a SnpUnique to all sharers";
+ SendSnpUniqueRetToSrc, desc="Send a SnpUnique to all sharers. Sets RetToSrc for only one sharer.";
+ SendSnpUniqueFwd, desc="Send a SnpUniqueFwd to a single sharer";
+ SendSnpCleanInvalid, desc="Send a SnpCleanInvalid to all sharers";
+ SendSnpCleanInvalidNoReq, desc="Send a SnpCleanInvalid to all sharers except requestor";
+ // Snoop responses
+ SendSnpData, desc="Send SnpRespData as snoop reply";
+ SendSnpIResp, desc="Send SnpResp_I as snoop reply";
+ SendInvSnpResp, desc="Check data state and queue either SendSnpIResp or SendSnpData";
+ SendSnpUniqueFwdCompData, desc="Send CompData to SnpUniqueFwd target and queue either SendSnpFwdedData or SendSnpFwdedResp";
+ SendSnpSharedFwdCompData, desc="Send CompData to SnpUniqueFwd target and queue either SendSnpFwdedData or SendSnpFwdedResp";
+ SendSnpNotSharedDirtyFwdCompData, desc="Send CompData to SnpNotSharedDirtyFwd target and queue either SendSnpFwdedData or SendSnpFwdedResp";
+ SendSnpOnceFwdCompData, desc="Send CompData to SnpOnceFwd target and queue either SendSnpFwdedData or SendSnpFwdedResp";
+ SendSnpFwdedData, desc="Send SnpResp for a forwarding snoop";
+ SendSnpFwdedResp, desc="Send SnpRespData for a forwarding snoop";
+
+ // Retry handling
+ SendRetryAck, desc="Send RetryAck";
+ SendPCrdGrant, desc="Send PCrdGrant";
+ DoRetry, desc="Resend the current pending request";
+ DoRetry_Hazard, desc="DoRetry during a hazard";
+
+ // Misc triggers
+ LoadHit, desc="Complete a load hit";
+ StoreHit, desc="Complete a store hit";
+ UseTimeout, desc="Transition from UD_T -> UD";
+ RestoreFromHazard, desc="Restore from a snoop hazard";
+ TX_Data, desc="Transmit pending data messages";
+ MaintainCoherence, desc="Queues a WriteBack or Evict before droping the only valid copy of the block";
+ FinishCleanUnique, desc="Sends acks and perform any writeback after a CleanUnique";
+ ActionStalledOnHazard, desc="Stall a trigger action because until finish handling snoop hazard";
+
+ // This is triggered once a transaction doesn't have
+ // any queued action and is not expecting responses/data. The transaction
+ // is finalized and the next stable state is stored in the cache/directory
+ // See the processNextState and makeFinalState functions
+ Final;
+
+ null;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Data structures
+ ////////////////////////////////////////////////////////////////////////////
+
+ // Cache block size
+ int blockSize, default="RubySystem::getBlockSizeBytes()";
+
+ // CacheEntry
+ structure(CacheEntry, interface="AbstractCacheEntry") {
+ State state, desc="SLICC line state";
+ DataBlock DataBlk, desc="data for the block";
+ bool HWPrefetched, default="false", desc="Set if this cache entry was prefetched";
+ }
+
+ // Directory entry
+ structure(DirEntry, interface="AbstractCacheEntry", main="false") {
+ NetDest sharers, desc="All upstream controllers that have this line (includes ownwer)";
+ MachineID owner, desc="Controller that has the line in UD,UC, or SD state";
+ bool ownerExists, default="false", desc="true if owner exists";
+ bool ownerIsExcl, default="false", desc="true if owner is UD or UC";
+ State state, desc="SLICC line state";
+ }
+
+ // Helper class for tracking expected response and data messages
+ structure(ExpectedMap, external ="yes") {
+ void clear(int dataChunks);
+ void addExpectedRespType(CHIResponseType);
+ void addExpectedDataType(CHIDataType);
+ void setExpectedCount(int val);
+ void addExpectedCount(int val);
+ bool hasExpected();
+ bool hasReceivedResp();
+ bool hasReceivedData();
+ int expected();
+ int received();
+ bool receiveResp(CHIResponseType);
+ bool receiveData(CHIDataType);
+ bool receivedDataType(CHIDataType);
+ bool receivedRespType(CHIResponseType);
+ }
+
+ // Tracks a pending retry
+ structure(RetryQueueEntry) {
+ Addr addr, desc="Line address";
+ MachineID retryDest, desc="Retry destination";
+ }
+
+ // Queue for event triggers. Used to specify a list of actions that need
+ // to be performed across multiple transitions.
+ // This class is also used to track pending retries
+ structure(TriggerQueue, external ="yes") {
+ Event front();
+ Event back();
+ bool frontNB();
+ bool backNB();
+ bool empty();
+ void push(Event);
+ void pushNB(Event);
+ void pushFront(Event);
+ void pushFrontNB(Event);
+ void pop();
+ // For the retry queue
+ void emplace(Addr,MachineID);
+ RetryQueueEntry next(); //SLICC won't allow to reuse front()
+ }
+
+ // TBE fields
+ structure(TBE, desc="Transaction buffer entry definition") {
+ // in which table was this allocated
+ bool is_req_tbe, desc="Allocated in the request table";
+ bool is_snp_tbe, desc="Allocated in the snoop table";
+ bool is_repl_tbe, desc="Allocated in the replacements table";
+
+ int storSlot, desc="Slot in the storage tracker occupied by this entry";
+
+ // Transaction info mostly extracted from the request message
+ Addr addr, desc="Line address for this TBE";
+ Addr accAddr, desc="Access address for Load/Store/WriteUniquePtl; otherwisse == addr";
+ int accSize, desc="Access size for Load/Store/WriteUniquePtl; otherwisse == blockSize";
+ CHIRequestType reqType, desc="Request type that initiated this transaction";
+ MachineID requestor, desc="Requestor ID";
+ MachineID fwdRequestor, desc="Requestor to receive data on fwding snoops";
+ bool use_DMT, desc="Use DMT for this transaction";
+ bool use_DCT, desc="Use DCT for this transaction";
+
+ // if either is set prefetchers are not notified on miss/hit/fill and
+ // demand hit/miss stats are not incremented
+ bool is_local_pf, desc="Request generated by a local prefetcher";
+ bool is_remote_pf, desc="Request generated a prefetcher in another cache";
+
+ // NOTE: seqReq is a smart pointer pointing to original CPU request object
+ // that triggers transactions associated with this TBE. seqReq carries some
+ // information (e.g., PC of requesting instruction, virtual address of this
+ // request, etc.). Not all transactions have this field set if they are not
+ // triggered directly by a demand request from CPU.
+ RequestPtr seqReq, default="nullptr", desc="Pointer to original request from CPU/sequencer";
+ bool isSeqReqValid, default="false", desc="Set if seqReq is valid (not nullptr)";
+
+ // Transaction state information
+ State state, desc="SLICC line state";
+
+ // Transient state information. These are set at the beggining of a
+ // transactions and updated as data and responses are received. After
+ // finalizing the transactions these are used to create the next SLICC
+ // stable state.
+ bool hasUseTimeout, desc="Line is locked under store/use timeout";
+ DataBlock dataBlk, desc="Local copy of the line";
+ WriteMask dataBlkValid, desc="Marks which bytes in the DataBlock are valid";
+ bool dataValid, desc="Local copy is valid";
+ bool dataDirty, desc="Local copy is dirtry";
+ bool dataMaybeDirtyUpstream, desc="Line maybe dirty upstream";
+ bool dataUnique, desc="Line is unique either locally or upsatream";
+ bool dataToBeInvalid, desc="Local copy will be invalidated at the end of transaction";
+ bool dataToBeSharedClean, desc="Local copy will become SC at the end of transaction";
+ NetDest dir_sharers, desc="Upstream controllers that have the line (includes owner)";
+ MachineID dir_owner, desc="Owner ID";
+ bool dir_ownerExists, desc="Owner ID is valid";
+ bool dir_ownerIsExcl, desc="Owner is UD or UC; SD otherwise";
+ bool doCacheFill, desc="Write valid data to the cache when completing transaction";
+ // NOTE: dataMaybeDirtyUpstream and dir_ownerExists are the same except
+ // when we had just sent dirty data upstream and are waiting for ack to set
+ // dir_ownerExists
+
+ // Helper structures to track expected events and additional transient
+ // state info
+
+ // List of actions to be performed while on a transient state
+ // See the processNextState function for details
+ TriggerQueue actions, template="<Cache_Event>", desc="List of actions";
+ Event pendAction, desc="Current pending action";
+ Tick delayNextAction, desc="Delay next action until given tick";
+ State finalState, desc="Final state; set when pendAction==Final";
+
+ // List of expected responses and data. Checks the type of data against the
+ // expected ones for debugging purposes
+ // See the processNextState function for details
+ ExpectedMap expected_req_resp, template="<CHIResponseType,CHIDataType>";
+ ExpectedMap expected_snp_resp, template="<CHIResponseType,CHIDataType>";
+ bool defer_expected_comp; // expect to receive Comp before the end of transaction
+ CHIResponseType slicchack1; // fix compiler not including headers
+ CHIDataType slicchack2; // fix compiler not including headers
+
+ // Tracks pending data messages that need to be generated when sending
+ // a line
+ bool snd_pendEv, desc="Is there a pending tx event ?";
+ WriteMask snd_pendBytes, desc="Which bytes are pending transmission";
+ CHIDataType snd_msgType, desc="Type of message being sent";
+ MachineID snd_destination, desc="Data destination";
+
+ // Tracks how to update the directory when receiving a CompAck
+ bool updateDirOnCompAck, desc="Update directory on CompAck";
+ bool requestorToBeOwner, desc="Sets dir_ownerExists";
+ bool requestorToBeExclusiveOwner, desc="Sets dir_ownerIsExcl";
+ // NOTE: requestor always added to dir_sharers if updateDirOnCompAck is set
+
+ // Set for incoming snoop requests
+ bool snpNeedsData, desc="Set if snoop requires data as response";
+ State fwdedState, desc="State of CompData sent due to a forwarding snoop";
+ bool is_req_hazard, desc="Snoop hazard with an outstanding request";
+ bool is_repl_hazard, desc="Snoop hazard with an outstanding writeback request";
+ bool is_stale, desc="Request is now stale because of a snoop hazard";
+
+ // Tracks requests sent downstream
+ CHIRequestType pendReqType, desc="Sent request type";
+ bool pendReqAllowRetry, desc="Sent request can be retried";
+ bool rcvdRetryAck, desc="Received a RetryAck";
+ bool rcvdRetryCredit, desc="Received a PCrdGrant";
+ // NOTE: the message is retried only after receiving both RetryAck and
+ // PCrdGrant. A request can be retried only once.
+ // These are a copy of the retry msg fields in case we need to retry
+ Addr pendReqAccAddr;
+ int pendReqAccSize;
+ NetDest pendReqDest;
+ bool pendReqD2OrigReq;
+ bool pendReqRetToSrc;
+
+ // This TBE stalled a message and thus we need to call wakeUpBuffers
+ // at some point
+ bool wakeup_pending_req;
+ bool wakeup_pending_snp;
+ bool wakeup_pending_tgr;
+ }
+
+ // TBE table definition
+ structure(TBETable, external ="yes") {
+ TBE lookup(Addr);
+ void allocate(Addr);
+ void deallocate(Addr);
+ bool isPresent(Addr);
+ }
+
+ structure(TBEStorage, external ="yes") {
+ int size();
+ int capacity();
+ int reserved();
+ int slotsAvailable();
+ bool areNSlotsAvailable(int n);
+ void incrementReserved();
+ void decrementReserved();
+ int addEntryToNewSlot();
+ void addEntryToSlot(int slot);
+ void removeEntryFromSlot(int slot);
+ }
+
+ // Directory memory definition
+ structure(PerfectCacheMemory, external = "yes") {
+ void allocate(Addr);
+ void deallocate(Addr);
+ DirEntry lookup(Addr);
+ bool isTagPresent(Addr);
+ }
+
+ // Directory
+ PerfectCacheMemory directory, template="<Cache_DirEntry>";
+
+ // Tracks unique lines locked after a store miss
+ TimerTable useTimerTable;
+
+ // Multiplies sc_lock_base_latency to obtain the lock timeout.
+ // This is incremented at Profile_Eviction and decays on
+ // store miss completion
+ int sc_lock_multiplier, default="0";
+
+ // Definitions of the TBE tables
+
+ // Main TBE table used for incoming requests
+ TBETable TBEs, template="<Cache_TBE>", constructor="m_number_of_TBEs";
+ TBEStorage storTBEs, constructor="this, m_number_of_TBEs";
+
+ // TBE table for WriteBack/Evict requests generated by a replacement
+ // Notice storTBEs will be used when unify_repl_TBEs is set
+ TBETable replTBEs, template="<Cache_TBE>", constructor="m_unify_repl_TBEs ? m_number_of_TBEs : m_number_of_repl_TBEs";
+ TBEStorage storReplTBEs, constructor="this, m_number_of_repl_TBEs";
+
+ // TBE table for incoming snoops
+ TBETable snpTBEs, template="<Cache_TBE>", constructor="m_number_of_snoop_TBEs";
+ TBEStorage storSnpTBEs, constructor="this, m_number_of_snoop_TBEs";
+
+ // Retry handling
+
+ // Destinations that will be sent PCrdGrant when a TBE becomes available
+ TriggerQueue retryQueue, template="<Cache_RetryQueueEntry>";
+
+
+ // Pending RetryAck/PCrdGrant/DoRetry
+ structure(RetryTriggerMsg, interface="Message") {
+ Addr addr;
+ Event event;
+ MachineID retryDest;
+
+ bool functionalRead(Packet *pkt) { return false; }
+ bool functionalRead(Packet *pkt, WriteMask &mask) { return false; }
+ bool functionalWrite(Packet *pkt) { return false; }
+ }
+
+ // Destinations from we received a RetryAck. Sending new requests to these
+ // destinations will be blocked until a PCrdGrant is received if
+ // throttle_req_on_retry is set
+ NetDest destsWaitingRetry;
+
+ // Pending transaction actions (generated by TBE:actions)
+ structure(TriggerMsg, interface="Message") {
+ Addr addr;
+ bool from_hazard; // this actions was generate during a snoop hazard
+ bool functionalRead(Packet *pkt) { return false; }
+ bool functionalRead(Packet *pkt, WriteMask &mask) { return false; }
+ bool functionalWrite(Packet *pkt) { return false; }
+ }
+
+ // Internal replacement request
+ structure(ReplacementMsg, interface="Message") {
+ Addr addr;
+ Addr from_addr;
+ int slot; // set only when unify_repl_TBEs is set
+ bool functionalRead(Packet *pkt) { return false; }
+ bool functionalRead(Packet *pkt, WriteMask &mask) { return false; }
+ bool functionalWrite(Packet *pkt) { return false; }
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Input/output port definitions
+ ////////////////////////////////////////////////////////////////////////////
+
+ include "CHI-cache-ports.sm";
+ // CHI-cache-ports.sm also includes CHI-cache-funcs.sm
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Actions and transitions
+ ////////////////////////////////////////////////////////////////////////////
+
+ include "CHI-cache-actions.sm";
+ include "CHI-cache-transitions.sm";
+}
diff --git a/src/mem/ruby/protocol/chi/CHI-mem.sm b/src/mem/ruby/protocol/chi/CHI-mem.sm
new file mode 100644
index 0000000..954a449
--- /dev/null
+++ b/src/mem/ruby/protocol/chi/CHI-mem.sm
@@ -0,0 +1,792 @@
+/*
+ * Copyright (c) 2021 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.
+ *
+ * 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.
+ */
+
+
+machine(MachineType:Memory, "Memory controller interface") :
+
+ // no explicit modeling of allocation latency like the Caches, so add one
+ // cycle to the response enqueue latency as default
+ Cycles response_latency := 2;
+ Cycles data_latency := 1;
+ Cycles to_memory_controller_latency := 1;
+
+ int data_channel_size;
+
+ // Interface to the network
+ // Note vnet_type is used by Garnet only. "response" type is assumed to
+ // have data, so use it for data channels and "none" for the rest.
+ // network="To" for outbound queue; network="From" for inbound
+ // virtual networks: 0=request, 1=snoop, 2=response, 3=data
+
+ MessageBuffer * reqOut, network="To", virtual_network="0", vnet_type="none";
+ MessageBuffer * snpOut, network="To", virtual_network="1", vnet_type="none";
+ MessageBuffer * rspOut, network="To", virtual_network="2", vnet_type="none";
+ MessageBuffer * datOut, network="To", virtual_network="3", vnet_type="response";
+
+ MessageBuffer * reqIn, network="From", virtual_network="0", vnet_type="none";
+ MessageBuffer * snpIn, network="From", virtual_network="1", vnet_type="none";
+ MessageBuffer * rspIn, network="From", virtual_network="2", vnet_type="none";
+ MessageBuffer * datIn, network="From", virtual_network="3", vnet_type="response";
+
+ // Requests that can allocate a TBE
+ MessageBuffer * reqRdy;
+
+ // Data/ack to/from memory
+ MessageBuffer * requestToMemory;
+ MessageBuffer * responseFromMemory;
+
+ // Trigger queue for internal events
+ MessageBuffer * triggerQueue;
+
+{
+
+ ////////////////////////////////////////////////////////////////////////////
+ // States
+ ////////////////////////////////////////////////////////////////////////////
+
+ state_declaration(State, desc="Transaction states", default="Memory_State_READY") {
+ // We don't know if the line is cached, so the memory copy is maybe stable
+ READY, AccessPermission:Backing_Store, desk="Ready to transfer the line";
+
+ WAITING_NET_DATA, AccessPermission:Backing_Store_Busy, desc="Waiting data from the network";
+ SENDING_NET_DATA, AccessPermission:Backing_Store_Busy, desc="Sending data to the network";
+ READING_MEM, AccessPermission:Backing_Store_Busy, desc="Waiting data from memory";
+
+ // Null state for debugging; allow writes
+ null, AccessPermission:Backing_Store, desc="Null state";
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Events
+ ////////////////////////////////////////////////////////////////////////////
+
+ enumeration(Event, desc="Memory events") {
+ // Checks if a request can allocate a TBE be moved to reqRdy
+ CheckAllocTBE;
+ CheckAllocTBE_WithCredit;
+
+ // Requests
+ WriteNoSnpPtl;
+ WriteNoSnp;
+ ReadNoSnp;
+ ReadNoSnpSep;
+
+ // Data
+ WriteData;
+
+ // Memory side
+ MemoryData;
+ MemoryAck;
+
+ // Internal event triggers
+ Trigger_Send;
+ Trigger_SendDone;
+ Trigger_ReceiveDone;
+ Trigger_SendRetry;
+ Trigger_SendPCrdGrant;
+ }
+
+
+ // Is there a less tedious way to convert messages to events ??
+
+ Event reqToEvent (CHIRequestType type) {
+ if (type == CHIRequestType:WriteNoSnpPtl) {
+ return Event:WriteNoSnpPtl;
+ } else if (type == CHIRequestType:WriteNoSnp) {
+ return Event:WriteNoSnp;
+ } else if (type == CHIRequestType:ReadNoSnp) {
+ return Event:ReadNoSnp;
+ } else if (type == CHIRequestType:ReadNoSnpSep) {
+ return Event:ReadNoSnpSep;
+ } else {
+ error("Invalid CHIRequestType");
+ }
+ }
+
+ Event respToEvent (CHIResponseType type) {
+ error("Invalid CHIResponseType");
+ }
+
+ Event dataToEvent (CHIDataType type) {
+ if (type == CHIDataType:NCBWrData) {
+ return Event:WriteData;
+ } else {
+ error("Invalid CHIDataType");
+ }
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Data structures
+ ////////////////////////////////////////////////////////////////////////////
+
+ // Cache block size
+ int blockSize, default="RubySystem::getBlockSizeBytes()";
+
+ // TBE fields
+ structure(TBE, desc="...") {
+ int storSlot, desc="Slot in the storage tracker occupied by this entry";
+ Addr addr, desc="Line address for this TBE";
+ Addr accAddr, desc="Original access address. Set only for Write*Ptl";
+ int accSize, desc="Access size. Set only for Write*Ptl";
+ State state, desc="Current line state";
+ DataBlock dataBlk, desc="Transaction data";
+ WriteMask dataBlkValid, desc="valid bytes in dataBlk";
+ int rxtxBytes, desc="Bytes sent or received";
+ MachineID requestor, desc="Requestor that originated this request";
+ MachineID destination, desc="Where we are sending data";
+ bool useDataSepResp, desc="Replies with DataSepResp instead of CompData";
+ }
+
+ structure(TBETable, external ="yes") {
+ TBE lookup(Addr);
+ void allocate(Addr);
+ void deallocate(Addr);
+ bool isPresent(Addr);
+ bool areNSlotsAvailable(int n, Tick curTime);
+ }
+
+ structure(TBEStorage, external ="yes") {
+ int size();
+ int capacity();
+ int reserved();
+ int slotsAvailable();
+ bool areNSlotsAvailable(int n);
+ void incrementReserved();
+ void decrementReserved();
+ int addEntryToNewSlot();
+ void removeEntryFromSlot(int slot);
+ }
+
+ TBETable TBEs, template="<Memory_TBE>", constructor="m_number_of_TBEs";
+ TBEStorage storTBEs, constructor="this, m_number_of_TBEs";
+
+ // Tracks all pending MemoryAcks (debug purposes only)
+ int pendingWrites, default="0";
+
+ structure(TriggerMsg, desc="...", interface="Message") {
+ Addr addr;
+ Event event;
+ MachineID retryDest;
+
+ bool functionalRead(Packet *pkt) { return false; }
+ bool functionalRead(Packet *pkt, WriteMask &mask) { return false; }
+ bool functionalWrite(Packet *pkt) { return false; }
+ }
+
+ // Tracks a pending credit request from a retry
+ structure(RetryQueueEntry) {
+ Addr addr, desc="Line address";
+ MachineID retryDest, desc="Retry destination";
+ }
+
+ structure(TriggerQueue, external ="yes") {
+ void pop();
+ bool empty();
+ void emplace(Addr,MachineID);
+ RetryQueueEntry next();
+ }
+
+ TriggerQueue retryQueue, template="<Memory_RetryQueueEntry>";
+
+ ////////////////////////////////////////////////////////////////////////////
+ // External functions
+ ////////////////////////////////////////////////////////////////////////////
+
+ Tick clockEdge();
+ Tick curTick();
+ Tick cyclesToTicks(Cycles c);
+ void set_tbe(TBE b);
+ void unset_tbe();
+ void wakeUpAllBuffers(Addr a);
+ bool respondsTo(Addr addr);
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Interface functions required by SLICC
+ ////////////////////////////////////////////////////////////////////////////
+
+ State getState(TBE tbe, Addr addr) {
+ if (is_valid(tbe)) {
+ assert(tbe.addr == addr);
+ return tbe.state;
+ } else {
+ return State:READY;
+ }
+ }
+
+ void setState(TBE tbe, Addr addr, State state) {
+ if (is_valid(tbe)) {
+ assert(tbe.addr == addr);
+ tbe.state := state;
+ }
+ }
+
+ AccessPermission getAccessPermission(Addr addr) {
+ if (respondsTo(addr)) {
+ TBE tbe := TBEs[addr];
+ if (is_valid(tbe)) {
+ DPRINTF(RubySlicc, "%x %s,%s\n", addr, tbe.state, Memory_State_to_permission(tbe.state));
+ return Memory_State_to_permission(tbe.state);
+ } else {
+ DPRINTF(RubySlicc, "%x %s\n", addr, AccessPermission:Backing_Store);
+ return AccessPermission:Backing_Store;
+ }
+ } else {
+ DPRINTF(RubySlicc, "%x %s\n", addr, AccessPermission:NotPresent);
+ return AccessPermission:NotPresent;
+ }
+ }
+
+ void setAccessPermission(Addr addr, State state) {
+ }
+
+ void functionalRead(Addr addr, Packet *pkt, WriteMask &mask) {
+ if (respondsTo(addr)) {
+ DPRINTF(RubySlicc, "functionalRead %x\n", addr);
+ TBE tbe := TBEs[addr];
+
+ if (mask.isEmpty()) {
+ functionalMemoryRead(pkt);
+ mask.fillMask();
+ DPRINTF(RubySlicc, "functionalRead mem %x %s\n", addr, mask);
+ }
+
+ // Update with any transient data
+ //TODO additional handling of partial data ??
+ if (is_valid(tbe)) {
+ WriteMask read_mask;
+ read_mask.setMask(addressOffset(tbe.accAddr, tbe.addr), tbe.accSize);
+ read_mask.andMask(tbe.dataBlkValid);
+ if (read_mask.isEmpty() == false) {
+ testAndReadMask(addr, tbe.dataBlk, read_mask, pkt);
+ DPRINTF(RubySlicc, "functionalRead tbe %x %s %s %s\n", addr, tbe.dataBlk, read_mask, mask);
+ mask.orMask(read_mask);
+ }
+ }
+ }
+ }
+
+ int functionalWrite(Addr addr, Packet *pkt) {
+ if(respondsTo(addr)) {
+ int num_functional_writes := 0;
+ TBE tbe := TBEs[addr];
+ if (is_valid(tbe)) {
+ num_functional_writes := num_functional_writes +
+ testAndWrite(addr, tbe.dataBlk, pkt);
+ DPRINTF(RubySlicc, "functionalWrite tbe %x %s\n", addr, tbe.dataBlk);
+ }
+ num_functional_writes := num_functional_writes + functionalMemoryWrite(pkt);
+ DPRINTF(RubySlicc, "functionalWrite mem %x\n", addr);
+ return num_functional_writes;
+ } else {
+ return 0;
+ }
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Helper functions
+ ////////////////////////////////////////////////////////////////////////////
+
+ void printResources() {
+ DPRINTF(RubySlicc, "Resources(avail/max): TBEs=%d/%d\n",
+ storTBEs.size(), storTBEs.capacity());
+ DPRINTF(RubySlicc, "Resources(in/out size): rdy=%d req=%d/%d rsp=%d/%d dat=%d/%d snp=%d/%d\n",
+ reqRdy.getSize(curTick()),
+ reqIn.getSize(curTick()), reqOut.getSize(curTick()),
+ rspIn.getSize(curTick()), rspOut.getSize(curTick()),
+ datIn.getSize(curTick()), datOut.getSize(curTick()),
+ snpIn.getSize(curTick()), snpOut.getSize(curTick()));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Input/output port definitions
+ ////////////////////////////////////////////////////////////////////////////
+
+ // Outbound port definitions
+
+ out_port(reqOutPort, CHIRequestMsg, reqOut);
+ out_port(snpOutPort, CHIRequestMsg, snpOut);
+ out_port(rspOutPort, CHIResponseMsg, rspOut);
+ out_port(datOutPort, CHIDataMsg, datOut);
+ out_port(triggerOutPort, TriggerMsg, triggerQueue);
+ out_port(memQueue_out, MemoryMsg, requestToMemory);
+ out_port(reqRdyOutPort, CHIRequestMsg, reqRdy);
+
+ // Inbound port definitions
+
+ // Response
+ in_port(rspInPort, CHIResponseMsg, rspIn, rank=6) {
+ if (rspInPort.isReady(clockEdge())) {
+ printResources();
+ peek(rspInPort, CHIResponseMsg) {
+ error("Unexpected message");
+ }
+ }
+ }
+
+ // Data
+ in_port(datInPort, CHIDataMsg, datIn, rank=5) {
+ if (datInPort.isReady(clockEdge())) {
+ printResources();
+ peek(datInPort, CHIDataMsg) {
+ int received := in_msg.bitMask.count();
+ assert((received <= data_channel_size) && (received > 0));
+ trigger(dataToEvent(in_msg.type), in_msg.addr, TBEs[in_msg.addr]);
+ }
+ }
+ }
+
+ // Data/Ack from memory
+
+ in_port(memQueue_in, MemoryMsg, responseFromMemory, rank=4) {
+ if (memQueue_in.isReady(clockEdge())) {
+ printResources();
+ peek(memQueue_in, MemoryMsg) {
+ Addr addr := makeLineAddress(in_msg.addr);
+ if (in_msg.Type == MemoryRequestType:MEMORY_READ) {
+ trigger(Event:MemoryData, addr, TBEs[addr]);
+ } else if (in_msg.Type == MemoryRequestType:MEMORY_WB) {
+ trigger(Event:MemoryAck, addr, TBEs[addr]);
+ } else {
+ error("Invalid message");
+ }
+ }
+ }
+ }
+
+ // Trigger
+ in_port(triggerInPort, TriggerMsg, triggerQueue, rank=3) {
+ if (triggerInPort.isReady(clockEdge())) {
+ printResources();
+ peek(triggerInPort, TriggerMsg) {
+ trigger(in_msg.event, in_msg.addr, TBEs[in_msg.addr]);
+ }
+ }
+ }
+
+ // Snoops
+ in_port(snpInPort, CHIRequestMsg, snpIn, rank=2) {
+ if (snpInPort.isReady(clockEdge())) {
+ printResources();
+ peek(snpInPort, CHIRequestMsg) {
+ error("Unexpected message");
+ }
+ }
+ }
+
+ // Requests
+ in_port(reqRdyInPort, CHIRequestMsg, reqRdy, rank=1) {
+ if (reqRdyInPort.isReady(clockEdge())) {
+ printResources();
+ peek(reqRdyInPort, CHIRequestMsg) {
+ trigger(reqToEvent(in_msg.type), in_msg.addr, TBEs[in_msg.addr]);
+ }
+ }
+ }
+
+ in_port(reqInPort, CHIRequestMsg, reqIn, rank=0) {
+ if (reqInPort.isReady(clockEdge())) {
+ printResources();
+ peek(reqInPort, CHIRequestMsg) {
+ if (in_msg.allowRetry) {
+ trigger(Event:CheckAllocTBE, in_msg.addr, TBEs[in_msg.addr]);
+ } else {
+ // Only expected requests that do not allow retry are the ones that
+ // are being retried after receiving credit
+ trigger(Event:CheckAllocTBE_WithCredit,
+ in_msg.addr, TBEs[in_msg.addr]);
+ }
+ }
+ }
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Actions
+ ////////////////////////////////////////////////////////////////////////////
+
+ action(checkAllocateTBE, desc="") {
+ // Move to reqRdy if resources available, otherwise send retry
+ if (storTBEs.areNSlotsAvailable(1)) {
+ // reserve a slot for this request
+ storTBEs.incrementReserved();
+
+ peek(reqInPort, CHIRequestMsg) {
+ enqueue(reqRdyOutPort, CHIRequestMsg, 0) {
+ out_msg := in_msg;
+ }
+ }
+
+ } else {
+ peek(reqInPort, CHIRequestMsg) {
+ assert(in_msg.allowRetry);
+ enqueue(triggerOutPort, TriggerMsg, 0) {
+ out_msg.addr := in_msg.addr;
+ out_msg.event := Event:Trigger_SendRetry;
+ out_msg.retryDest := in_msg.requestor;
+ retryQueue.emplace(in_msg.addr,in_msg.requestor);
+ }
+ }
+ }
+ reqInPort.dequeue(clockEdge());
+ }
+
+ action(checkAllocateTBE_withCredit, desc="") {
+ // We must have reserved resources for this request
+ peek(reqInPort, CHIRequestMsg) {
+ assert(in_msg.allowRetry == false);
+ enqueue(reqRdyOutPort, CHIRequestMsg, 0) {
+ out_msg := in_msg;
+ }
+ }
+ reqInPort.dequeue(clockEdge());
+ }
+
+ action(allocateTBE, "atbe", desc="Allocate TBEs for a miss") {
+ // We must have reserved resources for this allocation
+ storTBEs.decrementReserved();
+ assert(storTBEs.areNSlotsAvailable(1));
+
+ TBEs.allocate(address);
+ set_tbe(TBEs[address]);
+ tbe.storSlot := storTBEs.addEntryToNewSlot();
+ tbe.addr := address;
+ tbe.rxtxBytes := 0;
+ tbe.useDataSepResp := false;
+ }
+
+ action(initializeFromReqTBE, "itbe", desc="Initialize TBE fields") {
+ peek(reqRdyInPort, CHIRequestMsg) {
+ tbe.requestor := in_msg.requestor;
+ if (in_msg.dataToFwdRequestor) {
+ tbe.destination := in_msg.fwdRequestor;
+ } else {
+ tbe.destination := in_msg.requestor;
+ }
+ tbe.accAddr := in_msg.accAddr;
+ tbe.accSize := in_msg.accSize;
+ }
+ }
+
+ action(decWritePending, "dwp", desc="Decrement pending writes") {
+ assert(pendingWrites >= 1);
+ pendingWrites := pendingWrites - 1;
+ }
+
+ action(deallocateTBE, "dtbe", desc="Deallocate TBEs") {
+ assert(is_valid(tbe));
+ storTBEs.removeEntryFromSlot(tbe.storSlot);
+ TBEs.deallocate(address);
+ unset_tbe();
+ // send credit if requestor waiting for it
+ if (retryQueue.empty() == false) {
+ assert(storTBEs.areNSlotsAvailable(1));
+ storTBEs.incrementReserved();
+ RetryQueueEntry e := retryQueue.next();
+ retryQueue.pop();
+ enqueue(triggerOutPort, TriggerMsg, 0) {
+ out_msg.addr := e.addr;
+ out_msg.retryDest := e.retryDest;
+ out_msg.event := Event:Trigger_SendPCrdGrant;
+ }
+ }
+ }
+
+ action(sendReadReceipt, "sRR", desc="Send receipt to requestor") {
+ assert(is_valid(tbe));
+ enqueue(rspOutPort, CHIResponseMsg, response_latency) {
+ out_msg.addr := address;
+ out_msg.type := CHIResponseType:ReadReceipt;
+ out_msg.responder := machineID;
+ out_msg.Destination.add(tbe.requestor);
+ }
+ // also send different type of data when ready
+ tbe.useDataSepResp := true;
+ }
+
+ action(sendCompDBIDResp, "sCbid", desc="Send ack to requestor") {
+ assert(is_valid(tbe));
+ enqueue(rspOutPort, CHIResponseMsg, response_latency) {
+ out_msg.addr := address;
+ out_msg.type := CHIResponseType:CompDBIDResp;
+ out_msg.responder := machineID;
+ out_msg.Destination.add(tbe.requestor);
+ }
+ }
+
+ action(sendMemoryRead, "smr", desc="Send request to memory") {
+ assert(is_valid(tbe));
+ enqueue(memQueue_out, MemoryMsg, to_memory_controller_latency) {
+ out_msg.addr := address;
+ out_msg.Type := MemoryRequestType:MEMORY_READ;
+ out_msg.Sender := tbe.requestor;
+ out_msg.MessageSize := MessageSizeType:Request_Control;
+ out_msg.Len := 0;
+ }
+ }
+
+ action(sendMemoryWrite, "smw", desc="Send request to memory") {
+ assert(is_valid(tbe));
+ enqueue(memQueue_out, MemoryMsg, to_memory_controller_latency) {
+ out_msg.addr := tbe.accAddr;
+ out_msg.Type := MemoryRequestType:MEMORY_WB;
+ out_msg.Sender := tbe.requestor;
+ out_msg.MessageSize := MessageSizeType:Writeback_Data;
+ out_msg.DataBlk := tbe.dataBlk;
+ out_msg.Len := tbe.accSize;
+ }
+ tbe.dataBlkValid.clear();
+ pendingWrites := pendingWrites + 1;
+ }
+
+ action(prepareSend, "ps", desc="Copies received memory data to TBE") {
+ assert(is_valid(tbe));
+ peek(memQueue_in, MemoryMsg) {
+ tbe.dataBlk := in_msg.DataBlk;
+ }
+ tbe.rxtxBytes := 0;
+ tbe.dataBlkValid.setMask(addressOffset(tbe.accAddr, tbe.addr), tbe.accSize);
+ }
+
+ action(copyWriteDataToTBE, "cpWDat", desc="Copies received net data to TBE") {
+ peek(datInPort, CHIDataMsg) {
+ assert(is_valid(tbe));
+ tbe.dataBlk.copyPartial(in_msg.dataBlk, in_msg.bitMask);
+ tbe.dataBlkValid.orMask(in_msg.bitMask);
+ tbe.rxtxBytes := tbe.rxtxBytes + in_msg.bitMask.count();
+ }
+ }
+
+ action(sendDataAndCheck, "sd", desc="Send received data to requestor") {
+ assert(is_valid(tbe));
+ assert(tbe.rxtxBytes < blockSize);
+ enqueue(datOutPort, CHIDataMsg, data_latency) {
+ out_msg.addr := tbe.addr;
+ if (tbe.useDataSepResp) {
+ out_msg.type := CHIDataType:DataSepResp_UC;
+ } else {
+ out_msg.type := CHIDataType:CompData_UC;
+ }
+ out_msg.dataBlk := tbe.dataBlk;
+ // Called in order for the whole block so use rxtxBytes as offset
+ out_msg.bitMask.setMask(tbe.rxtxBytes, data_channel_size);
+ out_msg.Destination.add(tbe.destination);
+ }
+
+ //DPRINTF(RubySlicc, "rxtxBytes=%d\n", tbe.rxtxBytes);
+
+ tbe.rxtxBytes := tbe.rxtxBytes + data_channel_size;
+
+ // end or send next chunk next cycle
+ Event next := Event:Trigger_SendDone;
+ Cycles delay := intToCycles(0);
+ if (tbe.rxtxBytes < blockSize) {
+ next := Event:Trigger_Send;
+ delay := intToCycles(1);
+ }
+ enqueue(triggerOutPort, TriggerMsg, delay) {
+ out_msg.addr := address;
+ out_msg.event := next;
+ }
+ }
+
+ action(checkForReceiveCompletion, "cWc", desc="Check if all data is received") {
+ assert(is_valid(tbe));
+ DPRINTF(RubySlicc, "rxtxBytes=%d\n", tbe.rxtxBytes);
+ assert((tbe.rxtxBytes <= tbe.accSize) && (tbe.rxtxBytes > 0));
+ if (tbe.rxtxBytes == tbe.accSize) {
+ enqueue(triggerOutPort, TriggerMsg, 0) {
+ out_msg.addr := address;
+ out_msg.event := Event:Trigger_ReceiveDone;
+ }
+ tbe.rxtxBytes := 0;
+ assert(tbe.dataBlkValid.getMask(addressOffset(tbe.accAddr, tbe.addr), tbe.accSize));
+ }
+ }
+
+ action(popReqInQueue, "preq", desc="Pop request queue.") {
+ reqRdyInPort.dequeue(clockEdge());
+ }
+
+ action(popDataInQueue, "pdata", desc="Pop data queue.") {
+ datInPort.dequeue(clockEdge());
+ }
+
+ action(popTriggerQueue, "ptrigger", desc="Pop trigger queue.") {
+ triggerInPort.dequeue(clockEdge());
+ }
+
+ action(popMemoryQueue, "pmem", desc="Pop memory queue.") {
+ memQueue_in.dequeue(clockEdge());
+ }
+
+ // Stall/wake-up only used for requests that arrive when we are on the
+ // WAITING_NET_DATA state. For all other case the line should be either
+ // ready or we can overlap
+ action(stallRequestQueue, "str", desc="Stall and wait on the address") {
+ peek(reqRdyInPort, CHIRequestMsg){
+ stall_and_wait(reqRdyInPort, address);
+ }
+ }
+ action(wakeUpStalled, "wa", desc="Wake up any requests waiting for this address") {
+ wakeUpAllBuffers(address);
+ }
+
+ action(sendRetryAck, desc="") {
+ peek(triggerInPort, TriggerMsg) {
+ enqueue(rspOutPort, CHIResponseMsg, response_latency) {
+ out_msg.addr := in_msg.addr;
+ out_msg.type := CHIResponseType:RetryAck;
+ out_msg.responder := machineID;
+ out_msg.Destination.add(in_msg.retryDest);
+ }
+ }
+ }
+
+ action(sendPCrdGrant, desc="") {
+ peek(triggerInPort, TriggerMsg) {
+ enqueue(rspOutPort, CHIResponseMsg, response_latency) {
+ out_msg.addr := in_msg.addr;
+ out_msg.type := CHIResponseType:PCrdGrant;
+ out_msg.responder := machineID;
+ out_msg.Destination.add(in_msg.retryDest);
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Transitions
+ ////////////////////////////////////////////////////////////////////////////
+
+ transition(READY, ReadNoSnp, READING_MEM) {
+ allocateTBE;
+ initializeFromReqTBE;
+ sendMemoryRead;
+ popReqInQueue;
+ }
+
+ transition(READY, ReadNoSnpSep, READING_MEM) {
+ allocateTBE;
+ initializeFromReqTBE;
+ sendMemoryRead;
+ sendReadReceipt;
+ popReqInQueue;
+ }
+
+ transition(READING_MEM, MemoryData, SENDING_NET_DATA) {
+ prepareSend;
+ sendDataAndCheck;
+ popMemoryQueue;
+ }
+
+ transition(SENDING_NET_DATA, Trigger_Send) {
+ sendDataAndCheck;
+ popTriggerQueue;
+ }
+
+ transition(READY, WriteNoSnpPtl, WAITING_NET_DATA) {
+ allocateTBE;
+ initializeFromReqTBE;
+ sendCompDBIDResp;
+ popReqInQueue;
+ }
+
+ transition(READY, WriteNoSnp, WAITING_NET_DATA) {
+ allocateTBE;
+ initializeFromReqTBE;
+ sendCompDBIDResp;
+ popReqInQueue;
+ }
+
+ transition(WAITING_NET_DATA, WriteData) {
+ copyWriteDataToTBE;
+ checkForReceiveCompletion;
+ popDataInQueue;
+ }
+
+ transition(WAITING_NET_DATA, Trigger_ReceiveDone, READY) {
+ sendMemoryWrite;
+ deallocateTBE;
+ wakeUpStalled;
+ popTriggerQueue;
+ }
+
+ transition(SENDING_NET_DATA, Trigger_SendDone, READY) {
+ deallocateTBE;
+ wakeUpStalled;
+ popTriggerQueue;
+ }
+
+ // Just sanity check against counter of pending acks
+ transition({READING_MEM,WAITING_NET_DATA,SENDING_NET_DATA,READY},
+ MemoryAck) {
+ decWritePending;
+ popMemoryQueue;
+ }
+
+ // Notice we only use this here and call wakeUp when leaving this state
+ transition({READING_MEM,WAITING_NET_DATA,SENDING_NET_DATA},
+ {ReadNoSnp, ReadNoSnpSep, WriteNoSnpPtl}) {
+ stallRequestQueue;
+ }
+
+ transition({READING_MEM,WAITING_NET_DATA,SENDING_NET_DATA,READY},
+ Trigger_SendRetry) {
+ sendRetryAck;
+ popTriggerQueue;
+ }
+
+ transition({READING_MEM,WAITING_NET_DATA,SENDING_NET_DATA,READY},
+ Trigger_SendPCrdGrant) {
+ sendPCrdGrant;
+ popTriggerQueue;
+ }
+
+ transition({READING_MEM,WAITING_NET_DATA,SENDING_NET_DATA,READY},
+ CheckAllocTBE) {
+ checkAllocateTBE;
+ }
+
+ transition({READING_MEM,WAITING_NET_DATA,SENDING_NET_DATA,READY},
+ CheckAllocTBE_WithCredit) {
+ checkAllocateTBE_withCredit;
+ }
+
+}
diff --git a/src/mem/ruby/protocol/chi/CHI-msg.sm b/src/mem/ruby/protocol/chi/CHI-msg.sm
new file mode 100644
index 0000000..d51fb76
--- /dev/null
+++ b/src/mem/ruby/protocol/chi/CHI-msg.sm
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 2021 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.
+ *
+ * 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.
+ */
+
+// All CHI request and response types match the name style in the standard doc.
+// For a description of a specific message type, refer to the Arm's AMBA 5
+// CHI specification (issue D):
+// https://static.docs.arm.com/ihi0050/d/
+// IHI0050D_amba_5_chi_architecture_spec.pdf
+
+enumeration(CHIRequestType, desc="") {
+ // Incoming requests generated by the sequencer
+ Load;
+ Store;
+ StoreLine;
+
+ // CHI request types
+ ReadShared;
+ ReadNotSharedDirty;
+ ReadUnique;
+ ReadOnce;
+ CleanUnique;
+
+ Evict;
+
+ WriteBackFull;
+ WriteCleanFull;
+ WriteEvictFull;
+ WriteUniquePtl;
+ WriteUniqueFull;
+
+ SnpSharedFwd;
+ SnpNotSharedDirtyFwd;
+ SnpUniqueFwd;
+ SnpOnceFwd;
+ SnpOnce;
+ SnpShared;
+ SnpUnique;
+ SnpCleanInvalid;
+
+ WriteNoSnpPtl;
+ WriteNoSnp;
+ ReadNoSnp;
+ ReadNoSnpSep;
+
+ null;
+}
+
+structure(CHIRequestMsg, desc="", interface="Message") {
+ Addr addr, desc="Request line address";
+ Addr accAddr, desc="Original access address. Set for Write*Ptl and requests from the sequencer";
+ int accSize, desc="Access size. Set for Write*Ptl and requests from the sequencer";
+ CHIRequestType type, desc="Request type";
+ MachineID requestor, desc="Requestor ID";
+ MachineID fwdRequestor, desc="Where to send data for DMT/DCT requests";
+ bool dataToFwdRequestor, desc="Data has to be forwarded to fwdRequestor";
+ bool retToSrc, desc="Affects whether or not a snoop resp returns data";
+ bool allowRetry, desc="This request can be retried";
+ NetDest Destination, desc="Message destination";
+
+ RequestPtr seqReq, default="nullptr", desc="Pointer to original request from CPU/sequencer (nullptr if not valid)";
+ bool isSeqReqValid, default="false", desc="Set if seqReq is valid (not nullptr)";
+
+ bool is_local_pf, desc="Request generated by a local prefetcher";
+ bool is_remote_pf, desc="Request generated a prefetcher in another cache";
+
+ MessageSizeType MessageSize, default="MessageSizeType_Control";
+
+ // No data for functional access
+ bool functionalRead(Packet *pkt) { return false; }
+ bool functionalRead(Packet *pkt, WriteMask &mask) { return false; }
+ bool functionalWrite(Packet *pkt) { return false; }
+}
+
+enumeration(CHIResponseType, desc="...") {
+ // CHI response types
+ Comp_I;
+ Comp_UC;
+ Comp_SC;
+ CompAck;
+ CompDBIDResp;
+ DBIDResp;
+ Comp;
+ ReadReceipt;
+ RespSepData;
+
+ SnpResp_I;
+ SnpResp_I_Fwded_UC;
+ SnpResp_I_Fwded_UD_PD;
+ SnpResp_SC;
+ SnpResp_SC_Fwded_SC;
+ SnpResp_SC_Fwded_SD_PD;
+ SnpResp_UC_Fwded_I;
+ SnpResp_UD_Fwded_I;
+ SnpResp_SC_Fwded_I;
+ SnpResp_SD_Fwded_I;
+
+ RetryAck;
+ PCrdGrant;
+
+ null;
+}
+
+structure(CHIResponseMsg, desc="", interface="Message") {
+ Addr addr, desc="Line address";
+ CHIResponseType type, desc="Response type";
+ MachineID responder, desc="Responder ID";
+ NetDest Destination, desc="Response destination";
+ bool stale, desc="Response to a stale request";
+ //NOTE: not in CHI and for debuging only
+
+ MessageSizeType MessageSize, default="MessageSizeType_Control";
+
+ // No data for functional access
+ bool functionalRead(Packet *pkt) { return false; }
+ bool functionalRead(Packet *pkt, WriteMask &mask) { return false; }
+ bool functionalWrite(Packet *pkt) { return false; }
+}
+
+enumeration(CHIDataType, desc="...") {
+ // CHI data response types
+ CompData_I;
+ CompData_UC;
+ CompData_SC;
+ CompData_UD_PD;
+ CompData_SD_PD;
+ DataSepResp_UC;
+ CBWrData_UC;
+ CBWrData_SC;
+ CBWrData_UD_PD;
+ CBWrData_SD_PD;
+ CBWrData_I;
+ NCBWrData;
+ SnpRespData_I;
+ SnpRespData_I_PD;
+ SnpRespData_SC;
+ SnpRespData_SC_PD;
+ SnpRespData_SD;
+ SnpRespData_UC;
+ SnpRespData_UD;
+ SnpRespData_SC_Fwded_SC;
+ SnpRespData_SC_Fwded_SD_PD;
+ SnpRespData_SC_PD_Fwded_SC;
+ SnpRespData_I_Fwded_SD_PD;
+ SnpRespData_I_PD_Fwded_SC;
+ SnpRespData_I_Fwded_SC;
+ null;
+}
+
+structure(CHIDataMsg, desc="", interface="Message") {
+ Addr addr, desc="Line address";
+ CHIDataType type, desc="Response type";
+ MachineID responder, desc="Responder ID";
+ NetDest Destination, desc="Response destination";
+ DataBlock dataBlk, desc="Line data";
+ WriteMask bitMask, desc="Which bytes in the data block are valid";
+
+
+ MessageSizeType MessageSize, default="MessageSizeType_Data";
+
+ bool functionalRead(Packet *pkt) {
+ if(bitMask.isFull()) {
+ return testAndRead(addr, dataBlk, pkt);
+ } else {
+ return false;
+ }
+ }
+
+ bool functionalRead(Packet *pkt, WriteMask &mask) {
+ // read if bitmask has bytes not in mask or if data is dirty
+ bool is_dirty := (type == CHIDataType:CompData_UD_PD) ||
+ (type == CHIDataType:CompData_SD_PD) ||
+ (type == CHIDataType:CBWrData_UD_PD) ||
+ (type == CHIDataType:CBWrData_SD_PD) ||
+ (type == CHIDataType:NCBWrData) ||
+ (type == CHIDataType:SnpRespData_I_PD) ||
+ (type == CHIDataType:SnpRespData_SC_PD) ||
+ (type == CHIDataType:SnpRespData_SD) ||
+ (type == CHIDataType:SnpRespData_UD) ||
+ (type == CHIDataType:SnpRespData_SC_Fwded_SD_PD) ||
+ (type == CHIDataType:SnpRespData_SC_PD_Fwded_SC) ||
+ (type == CHIDataType:SnpRespData_I_Fwded_SD_PD) ||
+ (type == CHIDataType:SnpRespData_I_PD_Fwded_SC);
+ assert(bitMask.isEmpty() == false);
+ WriteMask test_mask := mask;
+ test_mask.orMask(bitMask);
+ if ((test_mask.cmpMask(mask) == false) || is_dirty) {
+ if (testAndReadMask(addr, dataBlk, bitMask, pkt)) {
+ mask.orMask(bitMask);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool functionalWrite(Packet *pkt) {
+ return testAndWrite(addr, dataBlk, pkt);
+ }
+}
+
+
diff --git a/src/mem/ruby/protocol/chi/CHI.slicc b/src/mem/ruby/protocol/chi/CHI.slicc
new file mode 100644
index 0000000..27724bb
--- /dev/null
+++ b/src/mem/ruby/protocol/chi/CHI.slicc
@@ -0,0 +1,6 @@
+protocol "CHI";
+
+include "RubySlicc_interfaces.slicc";
+include "CHI-msg.sm";
+include "CHI-cache.sm";
+include "CHI-mem.sm";
diff --git a/src/mem/ruby/protocol/chi/SConsopts b/src/mem/ruby/protocol/chi/SConsopts
new file mode 100644
index 0000000..91c10d2
--- /dev/null
+++ b/src/mem/ruby/protocol/chi/SConsopts
@@ -0,0 +1,47 @@
+# -*- mode:python -*-
+
+# Copyright (c) 2021 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.
+#
+# 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.
+
+Import('*')
+
+# Register this protocol with gem5/SCons
+
+all_protocols.append('CHI')
+
+# CHI requires Ruby's inerface to support partial functional reads
+need_partial_func_reads.append('CHI')
+
+protocol_dirs.append(Dir('.').abspath)