mem: Add NVM interface
authorWendy Elsasser <wendy.elsasser@arm.com>
Wed, 1 Apr 2020 17:39:42 +0000 (12:39 -0500)
committerJason Lowe-Power <power.jg@gmail.com>
Tue, 8 Sep 2020 16:38:47 +0000 (16:38 +0000)
Add NVM interface to memory controller.

This can be used with or instead of the existing
DRAM interface. Therefore, a single controller can interface
to either DRAM or NVM, or both.

Specifically, a memory channel can be configured as:
- Memory controller interfacing to DRAM only
- Memory controller interfacing to NVM only
- Memory controller interfacing to both DRAM and NVM

How data is placed or migrated between media types is outside
of the scope of this change.

The NVM interface incorporates new static delay parameters
for read and write completion. The interface defines a 2
stage read to manage non-deterministic read delays while
enabling deterministic data transfer, similar to NVDIMM-P.
The NVM interface also includes parameters to define
read and write buffers on the media side (on-DIMM). These are
utilized to quickly offload commands and write data, mitigating
the effects of lower latency and bandwidth media characteristics.

Change-Id: I6b22ddb495877f88d161f0bd74ade32cc8fdcbcc
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/29027
Reviewed-by: Jason Lowe-Power <power.jg@gmail.com>
Reviewed-by: Wendy Elsasser <wendy.elsasser@arm.com>
Tested-by: kokoro <noreply+kokoro@google.com>
Maintainer: Jason Lowe-Power <power.jg@gmail.com>

20 files changed:
configs/common/MemConfig.py
configs/nvm/sweep.py [new file with mode: 0644]
configs/nvm/sweep_hybrid.py [new file with mode: 0644]
configs/ruby/Ruby.py
src/cpu/testers/traffic_gen/PyTrafficGen.py
src/cpu/testers/traffic_gen/SConscript
src/cpu/testers/traffic_gen/base.cc
src/cpu/testers/traffic_gen/base.hh
src/cpu/testers/traffic_gen/hybrid_gen.cc [new file with mode: 0644]
src/cpu/testers/traffic_gen/hybrid_gen.hh [new file with mode: 0644]
src/cpu/testers/traffic_gen/nvm_gen.cc [new file with mode: 0644]
src/cpu/testers/traffic_gen/nvm_gen.hh [new file with mode: 0644]
src/cpu/testers/traffic_gen/traffic_gen.cc
src/mem/DRAMCtrl.py
src/mem/DRAMInterface.py
src/mem/MemInterface.py [new file with mode: 0644]
src/mem/NVMInterface.py [new file with mode: 0644]
src/mem/SConscript
src/mem/dram_ctrl.cc
src/mem/dram_ctrl.hh

index 1ace87518e2587caf6dc03e9bacb6805aaa6a92a..7aa6761b0e6280e9a8691aed4702c473883e8588 100644 (file)
@@ -80,6 +80,21 @@ def create_mem_intf(intf, r, i, nbr_mem_ctrls, intlv_bits, intlv_size,
 
             intlv_low_bit = int(math.log(rowbuffer_size, 2))
 
+    # Also adjust interleaving bits for NVM attached as memory
+    # Will have separate range defined with unique interleaving
+    if issubclass(intf, m5.objects.NVMInterface):
+        # If the channel bits are appearing after the low order
+        # address bits (buffer bits), we need to add the appropriate
+        # number of bits for the buffer size
+        if interface.addr_mapping.value == 'RoRaBaChCo':
+            # This computation only really needs to happen
+            # once, but as we rely on having an instance we
+            # end up having to repeat it for each and every
+            # one
+            buffer_size = interface.per_bank_buffer_size.value
+
+            intlv_low_bit = int(math.log(buffer_size, 2))
+
     # We got all we need to configure the appropriate address
     # range
     interface.range = m5.objects.AddrRange(r.start, size = r.size(),
@@ -102,15 +117,23 @@ def config_mem(options, system):
     """
 
     # Mandatory options
-    opt_mem_type = options.mem_type
     opt_mem_channels = options.mem_channels
 
+    # Semi-optional options
+    # Must have either mem_type or nvm_type or both
+    opt_mem_type = getattr(options, "mem_type", None)
+    opt_nvm_type = getattr(options, "nvm_type", None)
+    if not opt_mem_type and not opt_nvm_type:
+        fatal("Must have option for either mem-type or nvm-type, or both")
+
     # Optional options
     opt_tlm_memory = getattr(options, "tlm_memory", None)
     opt_external_memory_system = getattr(options, "external_memory_system",
                                          None)
     opt_elastic_trace_en = getattr(options, "elastic_trace_en", False)
     opt_mem_ranks = getattr(options, "mem_ranks", None)
+    opt_nvm_ranks = getattr(options, "nvm_ranks", None)
+    opt_hybrid_channel = getattr(options, "hybrid_channel", False)
     opt_dram_powerdown = getattr(options, "enable_dram_powerdown", None)
     opt_mem_channels_intlv = getattr(options, "mem_channels_intlv", 128)
     opt_xor_low_bit = getattr(options, "xor_low_bit", 0)
@@ -142,13 +165,19 @@ def config_mem(options, system):
         return
 
     nbr_mem_ctrls = opt_mem_channels
+
     import math
     from m5.util import fatal
     intlv_bits = int(math.log(nbr_mem_ctrls, 2))
     if 2 ** intlv_bits != nbr_mem_ctrls:
         fatal("Number of memory channels must be a power of 2")
 
-    intf = ObjectList.mem_list.get(opt_mem_type)
+    if opt_mem_type:
+        intf = ObjectList.mem_list.get(opt_mem_type)
+    if opt_nvm_type:
+        n_intf = ObjectList.mem_list.get(opt_nvm_type)
+
+    nvm_intfs = []
     mem_ctrls = []
 
     if opt_elastic_trace_en and not issubclass(intf, m5.objects.SimpleMemory):
@@ -164,51 +193,80 @@ def config_mem(options, system):
     # For every range (most systems will only have one), create an
     # array of memory interfaces and set their parameters to match
     # their address mapping in the case of a DRAM
+    range_iter = 0
     for r in system.mem_ranges:
+        # As the loops iterates across ranges, assign them alternatively
+        # to DRAM and NVM if both configured, starting with DRAM
+        range_iter += 1
+
         for i in range(nbr_mem_ctrls):
-            # Create the DRAM interface
-            dram_intf = create_mem_intf(intf, r, i, nbr_mem_ctrls, intlv_bits,
-                                       intlv_size, opt_xor_low_bit)
-
-            # Set the number of ranks based on the command-line
-            # options if it was explicitly set
-            if issubclass(intf, m5.objects.DRAMInterface) and opt_mem_ranks:
-                dram_intf.ranks_per_channel = opt_mem_ranks
-
-            # Enable low-power DRAM states if option is set
-            if issubclass(intf, m5.objects.DRAMInterface):
-                dram_intf.enable_dram_powerdown = opt_dram_powerdown
-
-            if opt_elastic_trace_en:
-                dram_intf.latency = '1ns'
-                print("For elastic trace, over-riding Simple Memory "
-                    "latency to 1ns.")
-
-            # Create the controller that will drive the interface
-            if opt_mem_type == "HMC_2500_1x32":
-                # The static latency of the vault controllers is estimated
-                # to be smaller than a full DRAM channel controller
-                mem_ctrl = m5.objects.DRAMCtrl(min_writes_per_switch = 8,
-                                               static_backend_latency = '4ns',
-                                               static_frontend_latency = '4ns')
-            else:
-                mem_ctrl = m5.objects.DRAMCtrl()
-
-            # Hookup the controller to the interface and add to the list
-            mem_ctrl.dram = dram_intf
-            mem_ctrls.append(mem_ctrl)
-
-    # Create a controller and connect the interfaces to a controller
+            if opt_mem_type and (not opt_nvm_type or range_iter % 2 != 0):
+                # Create the DRAM interface
+                dram_intf = create_mem_intf(intf, r, i, nbr_mem_ctrls,
+                                    intlv_bits, intlv_size, opt_xor_low_bit)
+
+                # Set the number of ranks based on the command-line
+                # options if it was explicitly set
+                if issubclass(intf, m5.objects.DRAMInterface) and \
+                   opt_mem_ranks:
+                    dram_intf.ranks_per_channel = opt_mem_ranks
+
+                # Enable low-power DRAM states if option is set
+                if issubclass(intf, m5.objects.DRAMInterface):
+                    dram_intf.enable_dram_powerdown = opt_dram_powerdown
+
+                if opt_elastic_trace_en:
+                    dram_intf.latency = '1ns'
+                    print("For elastic trace, over-riding Simple Memory "
+                        "latency to 1ns.")
+
+                # Create the controller that will drive the interface
+                if opt_mem_type == "HMC_2500_1x32":
+                    # The static latency of the vault controllers is estimated
+                    # to be smaller than a full DRAM channel controller
+                    mem_ctrl = m5.objects.DRAMCtrl(min_writes_per_switch = 8,
+                                             static_backend_latency = '4ns',
+                                             static_frontend_latency = '4ns')
+                else:
+                    mem_ctrl = m5.objects.DRAMCtrl()
+
+                # Hookup the controller to the interface and add to the list
+                mem_ctrl.dram = dram_intf
+                mem_ctrls.append(mem_ctrl)
+
+            elif opt_nvm_type and (not opt_mem_type or range_iter % 2 == 0):
+                nvm_intf = create_mem_intf(n_intf, r, i, nbr_mem_ctrls,
+                                           intlv_bits, intlv_size)
+                # Set the number of ranks based on the command-line
+                # options if it was explicitly set
+                if issubclass(n_intf, m5.objects.NVMInterface) and \
+                   opt_nvm_ranks:
+                    nvm_intf.ranks_per_channel = opt_nvm_ranks
+
+                # Create a controller if not sharing a channel with DRAM
+                # in which case the controller has already been created
+                if not opt_hybrid_channel:
+                    mem_ctrl = m5.objects.DRAMCtrl()
+                    mem_ctrl.nvm = nvm_intf
+
+                    mem_ctrls.append(mem_ctrl)
+                else:
+                    nvm_intfs.append(nvm_intf)
+
+    # hook up NVM interface when channel is shared with DRAM + NVM
+    for i in range(len(nvm_intfs)):
+        mem_ctrls[i].nvm = nvm_intfs[i];
+
+    # Connect the controller to the xbar port
     for i in range(len(mem_ctrls)):
         if opt_mem_type == "HMC_2500_1x32":
             # Connect the controllers to the membus
             mem_ctrls[i].port = xbar[i/4].master
-            # Set memory device size. There is an independent controller for
-            # each vault. All vaults are same size.
+            # Set memory device size. There is an independent controller
+            # for each vault. All vaults are same size.
             mem_ctrls[i].dram.device_size = options.hmc_dev_vault_size
         else:
             # Connect the controllers to the membus
             mem_ctrls[i].port = xbar.master
 
     subsystem.mem_ctrls = mem_ctrls
-
diff --git a/configs/nvm/sweep.py b/configs/nvm/sweep.py
new file mode 100644 (file)
index 0000000..5bc5819
--- /dev/null
@@ -0,0 +1,199 @@
+# Copyright (c) 2020 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.
+#
+# Authors: Andreas Hansson
+
+from __future__ import print_function
+from __future__ import absolute_import
+
+import math
+import optparse
+
+import m5
+from m5.objects import *
+from m5.util import addToPath
+from m5.stats import periodicStatDump
+
+addToPath('../')
+
+from common import ObjectList
+from common import MemConfig
+
+# this script is helpful to sweep the efficiency of a specific memory
+# controller configuration, by varying the number of banks accessed,
+# and the sequential stride size (how many bytes per activate), and
+# observe what bus utilisation (bandwidth) is achieved
+
+parser = optparse.OptionParser()
+
+nvm_generators = {
+    "NVM" : lambda x: x.createNvm,
+}
+
+# Use a single-channel DDR3-1600 x64 (8x8 topology) by default
+parser.add_option("--nvm-type", type="choice", default="NVM_2400_1x64",
+                  choices=ObjectList.mem_list.get_names(),
+                  help = "type of memory to use")
+
+parser.add_option("--nvm-ranks", "-r", type="int", default=1,
+                  help = "Number of ranks to iterate across")
+
+parser.add_option("--rd_perc", type="int", default=100,
+                  help = "Percentage of read commands")
+
+parser.add_option("--mode", type="choice", default="NVM",
+                  choices=nvm_generators.keys(),
+                  help = "NVM: Random traffic")
+
+parser.add_option("--addr-map", type="choice",
+                  choices=ObjectList.dram_addr_map_list.get_names(),
+                  default="RoRaBaCoCh", help = "NVM address map policy")
+
+(options, args) = parser.parse_args()
+
+if args:
+    print("Error: script doesn't take any positional arguments")
+    sys.exit(1)
+
+# at the moment we stay with the default open-adaptive page policy,
+# and address mapping
+
+# start with the system itself, using a multi-layer 2.0 GHz
+# crossbar, delivering 64 bytes / 3 cycles (one header cycle)
+# which amounts to 42.7 GByte/s per layer and thus per port
+system = System(membus = IOXBar(width = 32))
+system.clk_domain = SrcClockDomain(clock = '2.0GHz',
+                                   voltage_domain =
+                                   VoltageDomain(voltage = '1V'))
+
+# we are fine with 256 MB memory for now
+mem_range = AddrRange('512MB')
+system.mem_ranges = [mem_range]
+
+# do not worry about reserving space for the backing store
+system.mmap_using_noreserve = True
+
+# force a single channel to match the assumptions in the DRAM traffic
+# generator
+options.mem_channels = 1
+options.external_memory_system = 0
+MemConfig.config_mem(options, system)
+
+# the following assumes that we are using the native memory
+# controller with an NVM interface, check to be sure
+if not isinstance(system.mem_ctrls[0], m5.objects.DRAMCtrl):
+    fatal("This script assumes the controller is a DRAMCtrl subclass")
+if not isinstance(system.mem_ctrls[0].nvm, m5.objects.NVMInterface):
+    fatal("This script assumes the memory is a NVMInterface class")
+
+# there is no point slowing things down by saving any data
+system.mem_ctrls[0].nvm.null = True
+
+# Set the address mapping based on input argument
+system.mem_ctrls[0].nvm.addr_mapping = options.addr_map
+
+# stay in each state for 0.25 ms, long enough to warm things up, and
+# short enough to avoid hitting a refresh
+period = 250000000
+
+# stay in each state as long as the dump/reset period, use the entire
+# range, issue transactions of the right DRAM burst size, and match
+# the DRAM maximum bandwidth to ensure that it is saturated
+
+# get the number of regions
+nbr_banks = system.mem_ctrls[0].nvm.banks_per_rank.value
+
+# determine the burst length in bytes
+burst_size = int((system.mem_ctrls[0].nvm.devices_per_rank.value *
+                  system.mem_ctrls[0].nvm.device_bus_width.value *
+                  system.mem_ctrls[0].nvm.burst_length.value) / 8)
+
+
+# next, get the page size in bytes
+buffer_size = system.mem_ctrls[0].nvm.devices_per_rank.value * \
+    system.mem_ctrls[0].nvm.device_rowbuffer_size.value
+
+# match the maximum bandwidth of the memory, the parameter is in seconds
+# and we need it in ticks (ps)
+itt = system.mem_ctrls[0].nvm.tBURST.value * 1000000000000
+
+# assume we start at 0
+max_addr = mem_range.end
+
+# use min of the page size and 512 bytes as that should be more than
+# enough
+max_stride = min(256, buffer_size)
+
+# create a traffic generator, and point it to the file we just created
+system.tgen = PyTrafficGen()
+
+# add a communication monitor
+system.monitor = CommMonitor()
+
+# connect the traffic generator to the bus via a communication monitor
+system.tgen.port = system.monitor.slave
+system.monitor.master = system.membus.slave
+
+# connect the system port even if it is not used in this example
+system.system_port = system.membus.slave
+
+# every period, dump and reset all stats
+periodicStatDump(period)
+
+# run Forrest, run!
+root = Root(full_system = False, system = system)
+root.system.mem_mode = 'timing'
+
+m5.instantiate()
+
+def trace():
+    addr_map = ObjectList.dram_addr_map_list.get(options.addr_map)
+    generator = nvm_generators[options.mode](system.tgen)
+    for stride_size in range(burst_size, max_stride + 1, burst_size):
+        for bank in range(1, nbr_banks + 1):
+            num_seq_pkts = int(math.ceil(float(stride_size) / burst_size))
+            yield generator(period,
+                            0, max_addr, burst_size, int(itt), int(itt),
+                            options.rd_perc, 0,
+                            num_seq_pkts, buffer_size, nbr_banks, bank,
+                            addr_map, options.nvm_ranks)
+    yield system.tgen.createExit(0)
+
+system.tgen.start(trace())
+
+m5.simulate()
+
+print("NVM sweep with burst: %d, banks: %d, max stride: %d" %
+    (burst_size, nbr_banks, max_stride))
diff --git a/configs/nvm/sweep_hybrid.py b/configs/nvm/sweep_hybrid.py
new file mode 100644 (file)
index 0000000..a2513df
--- /dev/null
@@ -0,0 +1,242 @@
+# Copyright (c) 2020 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.
+#
+# Authors: Andreas Hansson
+
+from __future__ import print_function
+from __future__ import absolute_import
+
+import math
+import optparse
+
+import m5
+from m5.objects import *
+from m5.util import addToPath
+from m5.stats import periodicStatDump
+
+addToPath('../')
+
+from common import ObjectList
+from common import MemConfig
+
+# this script is helpful to sweep the efficiency of a specific memory
+# controller configuration, by varying the number of banks accessed,
+# and the sequential stride size (how many bytes per activate), and
+# observe what bus utilisation (bandwidth) is achieved
+
+parser = optparse.OptionParser()
+
+hybrid_generators = {
+    "HYBRID" : lambda x: x.createHybrid,
+}
+
+# Use a single-channel DDR3-1600 x64 (8x8 topology) by default
+parser.add_option("--nvm-type", type="choice", default="NVM_2400_1x64",
+                  choices=ObjectList.mem_list.get_names(),
+                  help = "type of memory to use")
+
+parser.add_option("--mem-type", type="choice", default="DDR4_2400_16x4",
+                  choices=ObjectList.mem_list.get_names(),
+                  help = "type of memory to use")
+
+parser.add_option("--nvm-ranks", "-n", type="int", default=1,
+                  help = "Number of ranks to iterate across")
+
+parser.add_option("--mem-ranks", "-r", type="int", default=2,
+                  help = "Number of ranks to iterate across")
+
+parser.add_option("--rd-perc", type="int", default=100,
+                  help = "Percentage of read commands")
+
+parser.add_option("--nvm-perc", type="int", default=100,
+                  help = "Percentage of NVM commands")
+
+parser.add_option("--mode", type="choice", default="HYBRID",
+                  choices=hybrid_generators.keys(),
+                  help = "Hybrid: Random DRAM + NVM traffic")
+
+parser.add_option("--addr-map", type="choice",
+                  choices=ObjectList.dram_addr_map_list.get_names(),
+                  default="RoRaBaCoCh", help = "NVM address map policy")
+
+(options, args) = parser.parse_args()
+
+if args:
+    print("Error: script doesn't take any positional arguments")
+    sys.exit(1)
+
+# at the moment we stay with the default open-adaptive page policy,
+# and address mapping
+
+# start with the system itself, using a multi-layer 2.0 GHz
+# crossbar, delivering 64 bytes / 3 cycles (one header cycle)
+# which amounts to 42.7 GByte/s per layer and thus per port
+system = System(membus = IOXBar(width = 32))
+system.clk_domain = SrcClockDomain(clock = '2.0GHz',
+                                   voltage_domain =
+                                   VoltageDomain(voltage = '1V'))
+
+# set 2 ranges, the first, smaller range for DDR
+# the second, larger (1024) range for NVM
+# the NVM range starts directly after the DRAM range
+system.mem_ranges = [AddrRange('128MB'),
+                     AddrRange(Addr('128MB'), size ='1024MB')]
+
+# do not worry about reserving space for the backing store
+system.mmap_using_noreserve = True
+
+# force a single channel to match the assumptions in the DRAM traffic
+# generator
+options.mem_channels = 1
+options.external_memory_system = 0
+options.hybrid_channel = True
+MemConfig.config_mem(options, system)
+
+# the following assumes that we are using the native controller
+# with NVM and DRAM interfaces, check to be sure
+if not isinstance(system.mem_ctrls[0], m5.objects.DRAMCtrl):
+    fatal("This script assumes the controller is a DRAMCtrl subclass")
+if not isinstance(system.mem_ctrls[0].dram, m5.objects.DRAMInterface):
+    fatal("This script assumes the first memory is a DRAMInterface subclass")
+if not isinstance(system.mem_ctrls[0].nvm, m5.objects.NVMInterface):
+    fatal("This script assumes the second memory is a NVMInterface subclass")
+
+# there is no point slowing things down by saving any data
+system.mem_ctrls[0].dram.null = True
+system.mem_ctrls[0].nvm.null = True
+
+# Set the address mapping based on input argument
+system.mem_ctrls[0].dram.addr_mapping = options.addr_map
+system.mem_ctrls[0].nvm.addr_mapping = options.addr_map
+
+# stay in each state for 0.25 ms, long enough to warm things up, and
+# short enough to avoid hitting a refresh
+period = 250000000
+
+# stay in each state as long as the dump/reset period, use the entire
+# range, issue transactions of the right burst size, and match
+# the maximum bandwidth to ensure that it is saturated
+
+# get the number of banks
+nbr_banks_dram = system.mem_ctrls[0].dram.banks_per_rank.value
+
+# determine the burst length in bytes
+burst_size_dram = int((system.mem_ctrls[0].dram.devices_per_rank.value *
+                  system.mem_ctrls[0].dram.device_bus_width.value *
+                  system.mem_ctrls[0].dram.burst_length.value) / 8)
+
+# next, get the page size in bytes
+page_size_dram = system.mem_ctrls[0].dram.devices_per_rank.value * \
+    system.mem_ctrls[0].dram.device_rowbuffer_size.value
+
+# get the number of regions
+nbr_banks_nvm = system.mem_ctrls[0].nvm.banks_per_rank.value
+
+# determine the burst length in bytes
+burst_size_nvm = int((system.mem_ctrls[0].nvm.devices_per_rank.value *
+                  system.mem_ctrls[0].nvm.device_bus_width.value *
+                  system.mem_ctrls[0].nvm.burst_length.value) / 8)
+
+
+burst_size = max(burst_size_dram, burst_size_nvm)
+
+# next, get the page size in bytes
+buffer_size_nvm = system.mem_ctrls[0].nvm.devices_per_rank.value * \
+    system.mem_ctrls[0].nvm.device_rowbuffer_size.value
+
+# match the maximum bandwidth of the memory, the parameter is in seconds
+# and we need it in ticks (ps)
+itt = min(system.mem_ctrls[0].dram.tBURST.value,
+          system.mem_ctrls[0].nvm.tBURST.value) * 1000000000000
+
+# assume we start at 0 for DRAM
+max_addr_dram = system.mem_ranges[0].end
+min_addr_nvm = system.mem_ranges[1].start
+max_addr_nvm = system.mem_ranges[1].end
+
+# use min of the page size and 512 bytes as that should be more than
+# enough
+max_stride = min(256, buffer_size_nvm, page_size_dram)
+
+# create a traffic generator, and point it to the file we just created
+system.tgen = PyTrafficGen()
+
+# add a communication monitor
+system.monitor = CommMonitor()
+
+# connect the traffic generator to the bus via a communication monitor
+system.tgen.port = system.monitor.slave
+system.monitor.master = system.membus.slave
+
+# connect the system port even if it is not used in this example
+system.system_port = system.membus.slave
+
+# every period, dump and reset all stats
+periodicStatDump(period)
+
+# run Forrest, run!
+root = Root(full_system = False, system = system)
+root.system.mem_mode = 'timing'
+
+m5.instantiate()
+
+def trace():
+    addr_map = ObjectList.dram_addr_map_list.get(options.addr_map)
+    generator = hybrid_generators[options.mode](system.tgen)
+    for stride_size in range(burst_size, max_stride + 1, burst_size):
+        num_seq_pkts_dram = int(math.ceil(float(stride_size) /
+                                          burst_size_dram))
+        num_seq_pkts_nvm = int(math.ceil(float(stride_size) / burst_size_nvm))
+        yield generator(period,
+                        0, max_addr_dram, burst_size_dram,
+                        min_addr_nvm, max_addr_nvm, burst_size_nvm,
+                        int(itt), int(itt),
+                        options.rd_perc, 0,
+                        num_seq_pkts_dram, page_size_dram,
+                        nbr_banks_dram, nbr_banks_dram,
+                        num_seq_pkts_nvm, buffer_size_nvm,
+                        nbr_banks_nvm, nbr_banks_nvm,
+                        addr_map, options.mem_ranks,
+                        options.nvm_ranks, options.nvm_perc)
+
+    yield system.tgen.createExit(0)
+
+system.tgen.start(trace())
+
+m5.simulate()
+
+print("Hybrid DRAM + NVM sweep with max_stride: %d" % (max_stride))
+print("NVM burst: %d, NVM banks: %d" % (burst_size_nvm, nbr_banks_nvm))
+print("DRAM burst: %d, DRAM banks: %d" % (burst_size_dram, nbr_banks_dram))
index 9f400a8a94e3eca3ae90792552079d7e8a0b2dc5..4e382af05cc4958a1c966c5ef62364a577c5a6ac 100644 (file)
@@ -147,8 +147,8 @@ def setup_memory_controllers(system, ruby, dir_cntrls, options):
                 mem_ctrl.port = dir_cntrl.memory
 
             # Enable low-power DRAM states if option is set
-            if issubclass(mem_type, DRAMCtrl):
-                mem_ctrl.enable_dram_powerdown = \
+            if issubclass(mem_type, DRAMInterface):
+                mem_ctrl.dram.enable_dram_powerdown = \
                         options.enable_dram_powerdown
 
         index += 1
index e4f25fc4fa6b93c53242e32ec8548a529307d36a..962f8e074b8e87b4689d8f20fcc728ed46c0edb9 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2018 ARM Limited
+# Copyright (c) 2018-2020 ARM Limited
 # All rights reserved.
 #
 # The license below extends only to copyright in the software and shall
@@ -58,6 +58,8 @@ class PyTrafficGen(BaseTrafficGen):
         PyBindMethod("createRandom"),
         PyBindMethod("createDram"),
         PyBindMethod("createDramRot"),
+        PyBindMethod("createHybrid"),
+        PyBindMethod("createNvm"),
     ]
 
     @cxxMethod(override=True)
index 94865ab3ae6b0d261ab581cfae3fa3fa1c4f2059..987ed67572f6c80a941be34855d8bb903ed1e79b 100644 (file)
@@ -1,6 +1,6 @@
 # -*- mode:python -*-
 
-# Copyright (c) 2012, 2017-2018 ARM Limited
+# Copyright (c) 2012, 2017-2020 ARM Limited
 # All rights reserved.
 #
 # The license below extends only to copyright in the software and shall
@@ -43,8 +43,10 @@ Source('base_gen.cc')
 Source('dram_gen.cc')
 Source('dram_rot_gen.cc')
 Source('exit_gen.cc')
+Source('hybrid_gen.cc')
 Source('idle_gen.cc')
 Source('linear_gen.cc')
+Source('nvm_gen.cc')
 Source('random_gen.cc')
 Source('stream_gen.cc')
 
index c1c0b46cae6efb207e274cdf49b753faec4f8658..bc8b601508ddd284634fddf5d6c27a99456af1a1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012-2013, 2016-2019 ARM Limited
+ * Copyright (c) 2012-2013, 2016-2020 ARM Limited
  * All rights reserved
  *
  * The license below extends only to copyright in the software and shall
 #include "cpu/testers/traffic_gen/dram_gen.hh"
 #include "cpu/testers/traffic_gen/dram_rot_gen.hh"
 #include "cpu/testers/traffic_gen/exit_gen.hh"
+#include "cpu/testers/traffic_gen/hybrid_gen.hh"
 #include "cpu/testers/traffic_gen/idle_gen.hh"
 #include "cpu/testers/traffic_gen/linear_gen.hh"
+#include "cpu/testers/traffic_gen/nvm_gen.hh"
 #include "cpu/testers/traffic_gen/random_gen.hh"
 #include "cpu/testers/traffic_gen/stream_gen.hh"
 #include "debug/Checkpoint.hh"
@@ -399,7 +401,7 @@ BaseTrafficGen::createDram(Tick duration,
                            Tick min_period, Tick max_period,
                            uint8_t read_percent, Addr data_limit,
                            unsigned int num_seq_pkts, unsigned int page_size,
-                           unsigned int nbr_of_banks_DRAM,
+                           unsigned int nbr_of_banks,
                            unsigned int nbr_of_banks_util,
                            Enums::AddrMap addr_mapping,
                            unsigned int nbr_of_ranks)
@@ -411,7 +413,7 @@ BaseTrafficGen::createDram(Tick duration,
                                                 min_period, max_period,
                                                 read_percent, data_limit,
                                                 num_seq_pkts, page_size,
-                                                nbr_of_banks_DRAM,
+                                                nbr_of_banks,
                                                 nbr_of_banks_util,
                                                 addr_mapping,
                                                 nbr_of_ranks));
@@ -424,7 +426,7 @@ BaseTrafficGen::createDramRot(Tick duration,
                               uint8_t read_percent, Addr data_limit,
                               unsigned int num_seq_pkts,
                               unsigned int page_size,
-                              unsigned int nbr_of_banks_DRAM,
+                              unsigned int nbr_of_banks,
                               unsigned int nbr_of_banks_util,
                               Enums::AddrMap addr_mapping,
                               unsigned int nbr_of_ranks,
@@ -437,13 +439,80 @@ BaseTrafficGen::createDramRot(Tick duration,
                                                    min_period, max_period,
                                                    read_percent, data_limit,
                                                    num_seq_pkts, page_size,
-                                                   nbr_of_banks_DRAM,
+                                                   nbr_of_banks,
                                                    nbr_of_banks_util,
                                                    addr_mapping,
                                                    nbr_of_ranks,
                                                    max_seq_count_per_rank));
 }
 
+std::shared_ptr<BaseGen>
+BaseTrafficGen::createHybrid(Tick duration,
+                           Addr start_addr_dram, Addr end_addr_dram,
+                           Addr blocksize_dram,
+                           Addr start_addr_nvm, Addr end_addr_nvm,
+                           Addr blocksize_nvm,
+                           Tick min_period, Tick max_period,
+                           uint8_t read_percent, Addr data_limit,
+                           unsigned int num_seq_pkts_dram,
+                           unsigned int page_size_dram,
+                           unsigned int nbr_of_banks_dram,
+                           unsigned int nbr_of_banks_util_dram,
+                           unsigned int num_seq_pkts_nvm,
+                           unsigned int buffer_size_nvm,
+                           unsigned int nbr_of_banks_nvm,
+                           unsigned int nbr_of_banks_util_nvm,
+                           Enums::AddrMap addr_mapping,
+                           unsigned int nbr_of_ranks_dram,
+                           unsigned int nbr_of_ranks_nvm,
+                           uint8_t nvm_percent)
+{
+    return std::shared_ptr<BaseGen>(new HybridGen(*this, masterID,
+                                                duration, start_addr_dram,
+                                                end_addr_dram, blocksize_dram,
+                                                start_addr_nvm,
+                                                end_addr_nvm, blocksize_nvm,
+                                                system->cacheLineSize(),
+                                                min_period, max_period,
+                                                read_percent, data_limit,
+                                                num_seq_pkts_dram,
+                                                page_size_dram,
+                                                nbr_of_banks_dram,
+                                                nbr_of_banks_util_dram,
+                                                num_seq_pkts_nvm,
+                                                buffer_size_nvm,
+                                                nbr_of_banks_nvm,
+                                                nbr_of_banks_util_nvm,
+                                                addr_mapping,
+                                                nbr_of_ranks_dram,
+                                                nbr_of_ranks_nvm,
+                                                nvm_percent));
+}
+
+std::shared_ptr<BaseGen>
+BaseTrafficGen::createNvm(Tick duration,
+                           Addr start_addr, Addr end_addr, Addr blocksize,
+                           Tick min_period, Tick max_period,
+                           uint8_t read_percent, Addr data_limit,
+                           unsigned int num_seq_pkts, unsigned int buffer_size,
+                           unsigned int nbr_of_banks,
+                           unsigned int nbr_of_banks_util,
+                           Enums::AddrMap addr_mapping,
+                           unsigned int nbr_of_ranks)
+{
+    return std::shared_ptr<BaseGen>(new NvmGen(*this, masterID,
+                                                duration, start_addr,
+                                                end_addr, blocksize,
+                                                system->cacheLineSize(),
+                                                min_period, max_period,
+                                                read_percent, data_limit,
+                                                num_seq_pkts, buffer_size,
+                                                nbr_of_banks,
+                                                nbr_of_banks_util,
+                                                addr_mapping,
+                                                nbr_of_ranks));
+}
+
 std::shared_ptr<BaseGen>
 BaseTrafficGen::createTrace(Tick duration,
                             const std::string& trace_file, Addr addr_offset)
index 17b1aa1cfc41d8d2b5a47779ec417585c5d0efad..6f419e88d80497abc2fbf99e7e4d96a3c26d2f29 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012-2013, 2016-2019 ARM Limited
+ * Copyright (c) 2012-2013, 2016-2020 ARM Limited
  * All rights reserved
  *
  * The license below extends only to copyright in the software and shall
@@ -274,7 +274,7 @@ class BaseTrafficGen : public ClockedObject
         Tick min_period, Tick max_period,
         uint8_t read_percent, Addr data_limit,
         unsigned int num_seq_pkts, unsigned int page_size,
-        unsigned int nbr_of_banks_DRAM, unsigned int nbr_of_banks_util,
+        unsigned int nbr_of_banks, unsigned int nbr_of_banks_util,
         Enums::AddrMap addr_mapping,
         unsigned int nbr_of_ranks);
 
@@ -284,11 +284,36 @@ class BaseTrafficGen : public ClockedObject
         Tick min_period, Tick max_period,
         uint8_t read_percent, Addr data_limit,
         unsigned int num_seq_pkts, unsigned int page_size,
-        unsigned int nbr_of_banks_DRAM, unsigned int nbr_of_banks_util,
+        unsigned int nbr_of_banks, unsigned int nbr_of_banks_util,
         Enums::AddrMap addr_mapping,
         unsigned int nbr_of_ranks,
         unsigned int max_seq_count_per_rank);
 
+    std::shared_ptr<BaseGen> createHybrid(
+        Tick duration,
+        Addr start_addr_dram, Addr end_addr_dram, Addr blocksize_dram,
+        Addr start_addr_nvm, Addr end_addr_nvm, Addr blocksize_nvm,
+        Tick min_period, Tick max_period,
+        uint8_t read_percent, Addr data_limit,
+        unsigned int num_seq_pkts_dram, unsigned int page_size_dram,
+        unsigned int nbr_of_banks_dram, unsigned int nbr_of_banks_util_dram,
+        unsigned int num_seq_pkts_nvm, unsigned int buffer_size_nvm,
+        unsigned int nbr_of_banks_nvm, unsigned int nbr_of_banks_util_nvm,
+        Enums::AddrMap addr_mapping,
+        unsigned int nbr_of_ranks_dram,
+        unsigned int nbr_of_ranks_nvm,
+        uint8_t nvm_percent);
+
+    std::shared_ptr<BaseGen> createNvm(
+        Tick duration,
+        Addr start_addr, Addr end_addr, Addr blocksize,
+        Tick min_period, Tick max_period,
+        uint8_t read_percent, Addr data_limit,
+        unsigned int num_seq_pkts, unsigned int buffer_size,
+        unsigned int nbr_of_banks, unsigned int nbr_of_banks_util,
+        Enums::AddrMap addr_mapping,
+        unsigned int nbr_of_ranks);
+
     std::shared_ptr<BaseGen> createTrace(
         Tick duration,
         const std::string& trace_file, Addr addr_offset);
diff --git a/src/cpu/testers/traffic_gen/hybrid_gen.cc b/src/cpu/testers/traffic_gen/hybrid_gen.cc
new file mode 100644 (file)
index 0000000..303884a
--- /dev/null
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2020 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 here under.  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.
+ *
+ * Authors: Wendy Elsasser
+ */
+
+#include "cpu/testers/traffic_gen/hybrid_gen.hh"
+
+#include <algorithm>
+
+#include "base/random.hh"
+#include "base/trace.hh"
+#include "debug/TrafficGen.hh"
+#include "enums/AddrMap.hh"
+
+using namespace std;
+
+HybridGen::HybridGen(SimObject &obj,
+               MasterID master_id, Tick _duration,
+               Addr start_addr_dram, Addr end_addr_dram,
+               Addr blocksize_dram,
+               Addr start_addr_nvm, Addr end_addr_nvm,
+               Addr blocksize_nvm,
+               Addr cacheline_size,
+               Tick min_period, Tick max_period,
+               uint8_t read_percent, Addr data_limit,
+               unsigned int num_seq_pkts_dram, unsigned int page_size_dram,
+               unsigned int nbr_of_banks_dram,
+               unsigned int nbr_of_banks_util_dram,
+               unsigned int num_seq_pkts_nvm, unsigned int buffer_size_nvm,
+               unsigned int nbr_of_banks_nvm,
+               unsigned int nbr_of_banks_util_nvm,
+               Enums::AddrMap addr_mapping,
+               unsigned int nbr_of_ranks_dram,
+               unsigned int nbr_of_ranks_nvm,
+               uint8_t nvm_percent)
+       : BaseGen(obj, master_id, _duration),
+         startAddrDram(start_addr_dram),
+         endAddrDram(end_addr_dram),
+         blocksizeDram(blocksize_dram),
+         startAddrNvm(start_addr_nvm),
+         endAddrNvm(end_addr_nvm),
+         blocksizeNvm(blocksize_nvm),
+         cacheLineSize(cacheline_size),
+         minPeriod(min_period), maxPeriod(max_period),
+         readPercent(read_percent), dataLimit(data_limit),
+         numSeqPktsDram(num_seq_pkts_dram),
+         numSeqPktsNvm(num_seq_pkts_nvm),
+         countNumSeqPkts(0), addr(0),
+         pageSizeDram(page_size_dram),
+         pageBitsDram(floorLog2(pageSizeDram / blocksizeDram)),
+         bankBitsDram(floorLog2(nbr_of_banks_dram)),
+         blockBitsDram(floorLog2(blocksizeDram)),
+         nbrOfBanksDram(nbr_of_banks_dram),
+         nbrOfBanksUtilDram(nbr_of_banks_util_dram),
+         bufferSizeNvm(buffer_size_nvm),
+         pageBitsNvm(floorLog2(bufferSizeNvm / blocksizeNvm)),
+         bankBitsNvm(floorLog2(nbr_of_banks_nvm)),
+         blockBitsNvm(floorLog2(blocksizeNvm)),
+         nbrOfBanksNvm(nbr_of_banks_nvm),
+         nbrOfBanksUtilNvm(nbr_of_banks_util_nvm),
+         addrMapping(addr_mapping),
+         nbrOfRanksDram(nbr_of_ranks_dram),
+         rankBitsDram(floorLog2(nbrOfRanksDram)),
+         nbrOfRanksNvm(nbr_of_ranks_nvm),
+         rankBitsNvm(floorLog2(nbrOfRanksNvm)),
+         nvmPercent(nvm_percent),
+         isRead(true),
+         isNvm(false),
+         dataManipulated(0)
+{
+    if (blocksizeDram > cacheLineSize)
+        fatal("TrafficGen %s Dram block size (%d) is larger than "
+              "cache line size (%d)\n", name(),
+              blocksizeDram, cacheLineSize);
+
+    if (blocksizeNvm > cacheLineSize)
+        fatal("TrafficGen %s Nvm block size (%d) is larger than "
+              "cache line size (%d)\n", name(),
+              blocksizeNvm, cacheLineSize);
+
+    if (readPercent > 100)
+        fatal("%s cannot have more than 100% reads", name());
+
+    if (minPeriod > maxPeriod)
+        fatal("%s cannot have min_period > max_period", name());
+
+    if (nbrOfBanksUtilDram > nbrOfBanksDram)
+        fatal("Attempting to use more Dram banks (%d) than "
+              "what is available (%d)\n",
+              nbrOfBanksUtilDram, nbrOfBanksDram);
+
+    if (nbrOfBanksUtilNvm > nbrOfBanksNvm)
+        fatal("Attempting to use more Nvm banks (%d) than "
+              "what is available (%d)\n",
+              nbrOfBanksUtilNvm, nbrOfBanksNvm);
+}
+
+void
+HybridGen::enter()
+{
+    // reset the counter to zero
+    dataManipulated = 0;
+}
+
+PacketPtr
+HybridGen::getNextPacket()
+{
+    // if this is the first of the packets in series to be generated,
+    // start counting again
+    if (countNumSeqPkts == 0) {
+        isNvm = nvmPercent != 0 &&
+            (nvmPercent == 100 || random_mt.random(0, 100) < nvmPercent);
+
+        // choose if we generate a read or a write here
+        isRead = readPercent != 0 &&
+            (readPercent == 100 || random_mt.random(0, 100) < readPercent);
+
+        assert((readPercent == 0 && !isRead) ||
+               (readPercent == 100 && isRead) ||
+               readPercent != 100);
+
+        if (isNvm) {
+            // Select the appropriate parameters for this interface
+            numSeqPkts = numSeqPktsNvm;
+            startAddr = startAddrNvm;
+            endAddr = endAddrNvm;
+            blocksize = blocksizeNvm;
+            pageSize = bufferSizeNvm;
+            pageBits = pageBitsNvm;
+            bankBits = bankBitsNvm;
+            blockBits = blockBitsNvm;
+            nbrOfBanks = nbrOfBanksNvm;
+            nbrOfBanksUtil = nbrOfBanksUtilNvm;
+            nbrOfRanks = nbrOfRanksNvm;
+            rankBits = rankBitsNvm;
+        } else {
+            // Select the appropriate parameters for this interface
+            numSeqPkts = numSeqPktsDram;
+            startAddr = startAddrDram;
+            endAddr = endAddrDram;
+            blocksize = blocksizeDram;
+            pageSize = pageSizeDram;
+            pageBits = pageBitsDram;
+            bankBits = bankBitsDram;
+            blockBits = blockBitsDram;
+            nbrOfBanks = nbrOfBanksDram;
+            nbrOfBanksUtil = nbrOfBanksUtilDram;
+            nbrOfRanks = nbrOfRanksDram;
+            rankBits = rankBitsDram;
+        }
+
+        countNumSeqPkts = numSeqPkts;
+
+        // pick a random bank
+        unsigned int new_bank =
+            random_mt.random<unsigned int>(0, nbrOfBanksUtil - 1);
+
+        // pick a random rank
+        unsigned int new_rank =
+            random_mt.random<unsigned int>(0, nbrOfRanks - 1);
+
+        // Generate the start address of the command series
+        // routine will update addr variable with bank, rank, and col
+        // bits updated for random traffic mode
+        genStartAddr(new_bank, new_rank);
+
+
+    } else {
+        // increment the column by one
+        if (addrMapping == Enums::RoRaBaCoCh ||
+            addrMapping == Enums::RoRaBaChCo)
+            // Simply increment addr by blocksize to increment
+            // the column by one
+            addr += blocksize;
+
+        else if (addrMapping == Enums::RoCoRaBaCh) {
+            // Explicity increment the column bits
+            unsigned int new_col = ((addr / blocksize /
+                                       nbrOfBanks / nbrOfRanks) %
+                                   (pageSize / blocksize)) + 1;
+            replaceBits(addr, blockBits + bankBits + rankBits + pageBits - 1,
+                        blockBits + bankBits + rankBits, new_col);
+        }
+    }
+
+    DPRINTF(TrafficGen, "HybridGen::getNextPacket: %c to addr %x, "
+            "size %d, countNumSeqPkts: %d, numSeqPkts: %d\n",
+            isRead ? 'r' : 'w', addr, blocksize, countNumSeqPkts, numSeqPkts);
+
+    // create a new request packet
+    PacketPtr pkt = getPacket(addr, blocksize,
+                              isRead ? MemCmd::ReadReq : MemCmd::WriteReq);
+
+    // add the amount of data manipulated to the total
+    dataManipulated += blocksize;
+
+    // subtract the number of packets remained to be generated
+    --countNumSeqPkts;
+
+    // return the generated packet
+    return pkt;
+}
+
+void
+HybridGen::genStartAddr(unsigned int new_bank, unsigned int new_rank)
+{
+    // start by picking a random address in the range
+    addr = random_mt.random<Addr>(startAddr, endAddr - 1);
+
+    // round down to start address of a block, i.e. a DRAM burst
+    addr -= addr % blocksize;
+
+    // insert the bank bits at the right spot, and align the
+    // address to achieve the required hit length, this involves
+    // finding the appropriate start address such that all
+    // sequential packets target successive columns in the same
+    // page
+
+    // for example, if we have a stride size of 192B, which means
+    // for LPDDR3 where burstsize = 32B we have numSeqPkts = 6,
+    // the address generated previously can be such that these
+    // 192B cross the page boundary, hence it needs to be aligned
+    // so that they all belong to the same page for page hit
+    unsigned int burst_per_page = pageSize / blocksize;
+
+    // pick a random column, but ensure that there is room for
+    // numSeqPkts sequential columns in the same page
+    unsigned int new_col =
+        random_mt.random<unsigned int>(0, burst_per_page - numSeqPkts);
+
+    if (addrMapping == Enums::RoRaBaCoCh ||
+        addrMapping == Enums::RoRaBaChCo) {
+        // Block bits, then page bits, then bank bits, then rank bits
+        replaceBits(addr, blockBits + pageBits + bankBits - 1,
+                    blockBits + pageBits, new_bank);
+        replaceBits(addr, blockBits + pageBits - 1, blockBits, new_col);
+        if (rankBits != 0) {
+            replaceBits(addr, blockBits + pageBits + bankBits +rankBits - 1,
+                        blockBits + pageBits + bankBits, new_rank);
+        }
+    } else if (addrMapping == Enums::RoCoRaBaCh) {
+        // Block bits, then bank bits, then rank bits, then page bits
+        replaceBits(addr, blockBits + bankBits - 1, blockBits, new_bank);
+        replaceBits(addr, blockBits + bankBits + rankBits + pageBits - 1,
+                    blockBits + bankBits + rankBits, new_col);
+        if (rankBits != 0) {
+            replaceBits(addr, blockBits + bankBits + rankBits - 1,
+                        blockBits + bankBits, new_rank);
+        }
+    }
+}
+
+Tick
+HybridGen::nextPacketTick(bool elastic, Tick delay) const
+{
+    // Check to see if we have reached the data limit. If dataLimit is
+    // zero we do not have a data limit and therefore we will keep
+    // generating requests for the entire residency in this state.
+    if (dataLimit && dataManipulated >= dataLimit)
+    {
+        DPRINTF(TrafficGen, "Data limit for RandomGen reached.\n");
+        // No more requests. Return MaxTick.
+        return MaxTick;
+    } else {
+        // return the time when the next request should take place
+        Tick wait = random_mt.random(minPeriod, maxPeriod);
+
+        // compensate for the delay experienced to not be elastic, by
+        // default the value we generate is from the time we are
+        // asked, so the elasticity happens automatically
+        if (!elastic) {
+            if (wait < delay)
+                wait = 0;
+            else
+                wait -= delay;
+        }
+
+        return curTick() + wait;
+    }
+}
diff --git a/src/cpu/testers/traffic_gen/hybrid_gen.hh b/src/cpu/testers/traffic_gen/hybrid_gen.hh
new file mode 100644 (file)
index 0000000..795826c
--- /dev/null
@@ -0,0 +1,277 @@
+/*
+ * Copyright (c) 2020 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 here under.  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.
+ *
+ * Authors: Wendy Elsasser
+ */
+
+/**
+ * @file
+ * Declaration of the NVM generator for issuing variable buffer
+ * hit length requests and bank utilisation.
+ */
+
+#ifndef __CPU_TRAFFIC_GEN_HYBRID_GEN_HH__
+#define __CPU_TRAFFIC_GEN_HYBRID_GEN_HH__
+
+#include "base/bitfield.hh"
+#include "base/intmath.hh"
+#include "base_gen.hh"
+#include "enums/AddrMap.hh"
+#include "mem/packet.hh"
+
+/**
+ * Hybrid NVM + DRAM specific generator is for issuing request with variable
+ * buffer hit length and bank utilization. Currently assumes a single
+ * channel configuration.
+ */
+class HybridGen : public BaseGen
+{
+
+  public:
+
+    /**
+     * Create a hybrid DRAM + NVM address sequence generator.
+     *
+     * @param obj SimObject owning this sequence generator
+     * @param master_id MasterID related to the memory requests
+     * @param _duration duration of this state before transitioning
+     * @param start_addr_dram Start address for DRAM range
+     * @param end_addr_dram End address for DRAM range
+     * @param _blocksize_dram Size used for transactions injected
+     * @param start_addr_nvm Start address for NVM range
+     * @param end_addr_nvm End address for NVM range
+     * @param _blocksize_nvm Size used for transactions injected
+     * @param cacheline_size cache line size in the system
+     * @param min_period Lower limit of random inter-transaction time
+     * @param max_period Upper limit of random inter-transaction time
+     * @param read_percent Percent of transactions that are reads
+     * @param data_limit Upper limit on how much data to read/write
+     * @param num_seq_pkts_dram Number of packets per stride, each _blocksize
+     * @param page_size_dram Buffer size (bytes) used in the NVM
+     * @param nbr_of_banks_dram Total number of parallel banks in NVM
+     * @param nbr_of_banks_util_dram Number of banks to utilized,
+     *                          for N banks, we will use banks: 0->(N-1)
+     * @param num_seq_pkts_nvm Number of packets per stride, each _blocksize
+     * @param buffer_size_nvm Buffer size (bytes) used in the NVM
+     * @param nbr_of_banks_nvm Total number of parallel banks in NVM
+     * @param nbr_of_banks_util_nvm Number of banks to utilized,
+     *                          for N banks, we will use banks: 0->(N-1)
+     * @param addr_mapping Address mapping to be used,
+     *                     assumes single channel system
+     * @param nbr_of_ranks_dram Number of DRAM ranks
+     * @param nbr_of_ranks_nvm Number of NVM ranks
+     * @param nvm_percent Percentage of traffic going to NVM
+     */
+    HybridGen(SimObject &obj,
+           MasterID master_id, Tick _duration,
+           Addr start_addr_dram, Addr end_addr_dram,
+           Addr blocksize_dram,
+           Addr start_addr_nvm, Addr end_addr_nvm,
+           Addr blocksize_nvm,
+           Addr cacheline_size,
+           Tick min_period, Tick max_period,
+           uint8_t read_percent, Addr data_limit,
+           unsigned int num_seq_pkts_dram, unsigned int page_size_dram,
+           unsigned int nbr_of_banks_dram, unsigned int nbr_of_banks_util_dram,
+           unsigned int num_seq_pkts_nvm, unsigned int buffer_size_nvm,
+           unsigned int nbr_of_banks_nvm, unsigned int nbr_of_banks_util_nvm,
+           Enums::AddrMap addr_mapping,
+           unsigned int nbr_of_ranks_dram,
+           unsigned int nbr_of_ranks_nvm,
+           uint8_t nvm_percent);
+
+    void enter();
+
+    PacketPtr getNextPacket();
+
+    /** Insert bank, rank, and column bits into packed
+     *  address to create address for 1st command in a
+     *  series
+     * @param new_bank Bank number of next packet series
+     * @param new_rank Rank value of next packet series
+    */
+    void genStartAddr(unsigned int new_bank , unsigned int new_rank);
+
+    Tick nextPacketTick(bool elastic, Tick delay) const;
+
+  protected:
+    /** Start of DRAM address range */
+    const Addr startAddrDram;
+
+    /** End of DRAM address range */
+    const Addr endAddrDram;
+
+    /** Blocksize and address increment for DRAM */
+    const Addr blocksizeDram;
+
+    /** Start of DRAM address range */
+    const Addr startAddrNvm;
+
+    /** End of DRAM address range */
+    const Addr endAddrNvm;
+
+    /** Blocksize and address increment for DRAM */
+    const Addr blocksizeNvm;
+
+    /** Cache line size in the simulated system */
+    const Addr cacheLineSize;
+
+    /** Request generation period */
+    const Tick minPeriod;
+    const Tick maxPeriod;
+
+     /** Percent of generated transactions that should be reads */
+    const uint8_t readPercent;
+
+    /** Maximum amount of data to manipulate */
+    const Addr dataLimit;
+
+    /** Number of sequential packets to be generated per cpu request */
+    const unsigned int numSeqPktsDram;
+    const unsigned int numSeqPktsNvm;
+
+    /** Track number of sequential packets generated for a request  */
+    unsigned int countNumSeqPkts;
+
+    /** Address of request */
+    Addr addr;
+
+    /** Page size of DRAM */
+    const unsigned int pageSizeDram;
+
+    /** Number of page bits in DRAM address */
+    const unsigned int pageBitsDram;
+
+    /** Number of bank bits in DRAM address*/
+    const unsigned int bankBitsDram;
+
+    /** Number of block bits in DRAM address */
+    const unsigned int blockBitsDram;
+
+    /** Number of banks in DRAM */
+    const unsigned int nbrOfBanksDram;
+
+    /** Number of banks to be utilized for a given configuration */
+    const unsigned int nbrOfBanksUtilDram;
+
+    /** Buffer size of NVM */
+    const unsigned int bufferSizeNvm;
+
+    /** Number of buffer bits in NVM address */
+    const unsigned int pageBitsNvm;
+
+    /** Number of bank bits in NVM address*/
+    const unsigned int bankBitsNvm;
+
+    /** Number of block bits in NVM address */
+    const unsigned int blockBitsNvm;
+
+    /** Number of banks in NVM */
+    const unsigned int nbrOfBanksNvm;
+
+    /** Number of banks to be utilized for a given configuration */
+    const unsigned int nbrOfBanksUtilNvm;
+
+    /** Address mapping to be used */
+    Enums::AddrMap addrMapping;
+
+    /** Number of ranks to be utilized for a given configuration */
+    const unsigned int nbrOfRanksDram;
+
+    /** Number of rank bits in DRAM address*/
+    const unsigned int rankBitsDram;
+
+    /** Number of ranks to be utilized for a given configuration */
+    const unsigned int nbrOfRanksNvm;
+
+    /** Number of rank bits in DRAM address*/
+    const unsigned int rankBitsNvm;
+
+    /** Percent of generated transactions that should go to NVM */
+    const uint8_t nvmPercent;
+
+    /** Remember type of requests to be generated in series */
+    bool isRead;
+
+    /** Remember the interface to be generated in series */
+    bool isNvm;
+
+    /**
+     * Counter to determine the amount of data
+     * manipulated. Used to determine if we should continue
+     * generating requests.
+     */
+    Addr dataManipulated;
+
+    /** Number of sequential DRAM packets to be generated per cpu request */
+    unsigned int numSeqPkts;
+
+    /** Start of address range */
+    Addr startAddr;
+
+    /** End of address range */
+    Addr endAddr;
+
+    /** Blocksize and address increment */
+    Addr blocksize;
+
+    /** Page size of DRAM */
+    unsigned int pageSize;
+
+    /** Number of page bits in DRAM address */
+    unsigned int pageBits;
+
+    /** Number of bank bits in DRAM address*/
+    unsigned int bankBits;
+
+    /** Number of block bits in DRAM address */
+    unsigned int blockBits;
+
+    /** Number of banks in DRAM */
+    unsigned int nbrOfBanks;
+
+    /** Number of banks to be utilized for a given configuration */
+    unsigned int nbrOfBanksUtil;
+
+    /** Number of ranks to be utilized for a given configuration */
+    unsigned int nbrOfRanks;
+
+    /** Number of rank bits in DRAM address*/
+    unsigned int rankBits;
+
+};
+
+#endif
diff --git a/src/cpu/testers/traffic_gen/nvm_gen.cc b/src/cpu/testers/traffic_gen/nvm_gen.cc
new file mode 100644 (file)
index 0000000..fa3efd3
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2020 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 here under.  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.
+ *
+ * Authors: Wendy Elsasser
+ */
+
+#include "cpu/testers/traffic_gen/nvm_gen.hh"
+
+#include <algorithm>
+
+#include "base/random.hh"
+#include "base/trace.hh"
+#include "debug/TrafficGen.hh"
+#include "enums/AddrMap.hh"
+
+NvmGen::NvmGen(SimObject &obj,
+               MasterID master_id, Tick _duration,
+               Addr start_addr, Addr end_addr,
+               Addr _blocksize, Addr cacheline_size,
+               Tick min_period, Tick max_period,
+               uint8_t read_percent, Addr data_limit,
+               unsigned int num_seq_pkts, unsigned int buffer_size,
+               unsigned int nbr_of_banks,
+               unsigned int nbr_of_banks_util,
+               Enums::AddrMap addr_mapping,
+               unsigned int nbr_of_ranks)
+       : RandomGen(obj, master_id, _duration, start_addr, end_addr,
+         _blocksize, cacheline_size, min_period, max_period,
+         read_percent, data_limit),
+         numSeqPkts(num_seq_pkts), countNumSeqPkts(0), addr(0),
+         isRead(true), bufferSize(buffer_size),
+         bufferBits(floorLog2(buffer_size / _blocksize)),
+         bankBits(floorLog2(nbr_of_banks)),
+         blockBits(floorLog2(_blocksize)),
+         nbrOfBanksNVM(nbr_of_banks),
+         nbrOfBanksUtil(nbr_of_banks_util), addrMapping(addr_mapping),
+         rankBits(floorLog2(nbr_of_ranks)),
+         nbrOfRanks(nbr_of_ranks)
+{
+    if (nbr_of_banks_util > nbr_of_banks)
+        fatal("Attempting to use more banks (%d) than "
+              "what is available (%d)\n",
+              nbr_of_banks_util, nbr_of_banks);
+}
+
+PacketPtr
+NvmGen::getNextPacket()
+{
+    // if this is the first of the packets in series to be generated,
+    // start counting again
+    if (countNumSeqPkts == 0) {
+        countNumSeqPkts = numSeqPkts;
+
+        // choose if we generate a read or a write here
+        isRead = readPercent != 0 &&
+            (readPercent == 100 || random_mt.random(0, 100) < readPercent);
+
+        assert((readPercent == 0 && !isRead) ||
+               (readPercent == 100 && isRead) ||
+               readPercent != 100);
+
+        // pick a random bank
+        unsigned int new_bank =
+            random_mt.random<unsigned int>(0, nbrOfBanksUtil - 1);
+
+        // pick a random rank
+        unsigned int new_rank =
+            random_mt.random<unsigned int>(0, nbrOfRanks - 1);
+
+        // Generate the start address of the command series
+        // routine will update addr variable with bank, rank, and col
+        // bits updated for random traffic mode
+        genStartAddr(new_bank, new_rank);
+
+    } else {
+        // increment the column by one
+        if (addrMapping == Enums::RoRaBaCoCh ||
+            addrMapping == Enums::RoRaBaChCo)
+            // Simply increment addr by blocksize to increment
+            // the column by one
+            addr += blocksize;
+
+        else if (addrMapping == Enums::RoCoRaBaCh) {
+            // Explicity increment the column bits
+            unsigned int new_col = ((addr / blocksize /
+                                       nbrOfBanksNVM / nbrOfRanks) %
+                                   (bufferSize / blocksize)) + 1;
+            replaceBits(addr, blockBits + bankBits + rankBits +
+                              bufferBits - 1,
+                        blockBits + bankBits + rankBits, new_col);
+        }
+    }
+
+    DPRINTF(TrafficGen, "NvmGen::getNextPacket: %c to addr %x, "
+            "size %d, countNumSeqPkts: %d, numSeqPkts: %d\n",
+            isRead ? 'r' : 'w', addr, blocksize, countNumSeqPkts, numSeqPkts);
+
+    // create a new request packet
+    PacketPtr pkt = getPacket(addr, blocksize,
+                              isRead ? MemCmd::ReadReq : MemCmd::WriteReq);
+
+    // add the amount of data manipulated to the total
+    dataManipulated += blocksize;
+
+    // subtract the number of packets remained to be generated
+    --countNumSeqPkts;
+
+    // return the generated packet
+    return pkt;
+}
+
+void
+NvmGen::genStartAddr(unsigned int new_bank, unsigned int new_rank)
+{
+    // start by picking a random address in the range
+    addr = random_mt.random<Addr>(startAddr, endAddr - 1);
+
+    // round down to start address of a block, i.e. a NVM burst
+    addr -= addr % blocksize;
+
+    // insert the bank bits at the right spot, and align the
+    // address to achieve the required hit length, this involves
+    // finding the appropriate start address such that all
+    // sequential packets target successive bursts in the same
+    // buffer
+    unsigned int burst_per_buffer = bufferSize / blocksize;
+
+    // pick a random burst address, but ensure that there is room for
+    // numSeqPkts sequential bursts in the same buffer
+    unsigned int new_col =
+        random_mt.random<unsigned int>(0, burst_per_buffer - numSeqPkts);
+
+    if (addrMapping == Enums::RoRaBaCoCh ||
+        addrMapping == Enums::RoRaBaChCo) {
+        // Block bits, then buffer bits, then bank bits, then rank bits
+        replaceBits(addr, blockBits + bufferBits + bankBits - 1,
+                    blockBits + bufferBits, new_bank);
+        replaceBits(addr, blockBits + bufferBits - 1, blockBits, new_col);
+        if (rankBits != 0) {
+            replaceBits(addr, blockBits + bufferBits + bankBits +
+                        rankBits - 1, blockBits + bufferBits + bankBits,
+                        new_rank);
+        }
+    } else if (addrMapping == Enums::RoCoRaBaCh) {
+        // Block bits, then bank bits, then rank bits, then buffer bits
+        replaceBits(addr, blockBits + bankBits - 1, blockBits, new_bank);
+        replaceBits(addr, blockBits + bankBits + rankBits + bufferBits - 1,
+                    blockBits + bankBits + rankBits, new_col);
+        if (rankBits != 0) {
+            replaceBits(addr, blockBits + bankBits + rankBits - 1,
+                        blockBits + bankBits, new_rank);
+        }
+    }
+}
diff --git a/src/cpu/testers/traffic_gen/nvm_gen.hh b/src/cpu/testers/traffic_gen/nvm_gen.hh
new file mode 100644 (file)
index 0000000..c5a89ee
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2020 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 here under.  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.
+ *
+ * Authors: Wendy Elsasser
+ */
+
+/**
+ * @file
+ * Declaration of the NVM generator for issuing variable buffer
+ * hit length requests and bank utilisation.
+ */
+
+#ifndef __CPU_TRAFFIC_GEN_NVM_GEN_HH__
+#define __CPU_TRAFFIC_GEN_NVM_GEN_HH__
+
+#include "base/bitfield.hh"
+#include "base/intmath.hh"
+#include "enums/AddrMap.hh"
+#include "mem/packet.hh"
+#include "random_gen.hh"
+
+/**
+ * NVM specific generator is for issuing request with variable buffer
+ * hit length and bank utilization. Currently assumes a single
+ * channel configuration.
+ */
+class NvmGen : public RandomGen
+{
+
+  public:
+
+    /**
+     * Create a NVM address sequence generator.
+     *
+     * @param obj SimObject owning this sequence generator
+     * @param master_id MasterID related to the memory requests
+     * @param _duration duration of this state before transitioning
+     * @param start_addr Start address
+     * @param end_addr End address
+     * @param _blocksize Size used for transactions injected
+     * @param cacheline_size cache line size in the system
+     * @param min_period Lower limit of random inter-transaction time
+     * @param max_period Upper limit of random inter-transaction time
+     * @param read_percent Percent of transactions that are reads
+     * @param data_limit Upper limit on how much data to read/write
+     * @param num_seq_pkts Number of packets per stride, each of _blocksize
+     * @param page_size Buffer size (bytes) used in the NVM
+     * @param nbr_of_banks Total number of parallel banks in NVM
+     * @param nbr_of_banks_util Number of banks to utilized,
+     *                          for N banks, we will use banks: 0->(N-1)
+     * @param addr_mapping Address mapping to be used,
+     *                     assumes single channel system
+     */
+    NvmGen(SimObject &obj,
+           MasterID master_id, Tick _duration,
+           Addr start_addr, Addr end_addr,
+           Addr _blocksize, Addr cacheline_size,
+           Tick min_period, Tick max_period,
+           uint8_t read_percent, Addr data_limit,
+           unsigned int num_seq_pkts, unsigned int buffer_size,
+           unsigned int nbr_of_banks, unsigned int nbr_of_banks_util,
+           Enums::AddrMap addr_mapping,
+           unsigned int nbr_of_ranks);
+
+    PacketPtr getNextPacket();
+
+    /** Insert bank, rank, and column bits into packed
+     *  address to create address for 1st command in a
+     *  series
+     * @param new_bank Bank number of next packet series
+     * @param new_rank Rank value of next packet series
+    */
+    void genStartAddr(unsigned int new_bank, unsigned int new_rank);
+
+  protected:
+
+    /** Number of sequential NVM packets to be generated per cpu request */
+    const unsigned int numSeqPkts;
+
+    /** Track number of sequential packets generated for a request  */
+    unsigned int countNumSeqPkts;
+
+    /** Address of request */
+    Addr addr;
+
+    /** Remember type of requests to be generated in series */
+    bool isRead;
+
+    /** Buffer size of NVM */
+    const unsigned int bufferSize;
+
+    /** Number of buffer bits in NVM address */
+    const unsigned int bufferBits;
+
+    /** Number of bank bits in NVM address*/
+    const unsigned int bankBits;
+
+    /** Number of block bits in NVM address */
+    const unsigned int blockBits;
+
+    /** Number of banks in NVM */
+    const unsigned int nbrOfBanksNVM;
+
+    /** Number of banks to be utilized for a given configuration */
+    const unsigned int nbrOfBanksUtil;
+
+    /** Address mapping to be used */
+    Enums::AddrMap addrMapping;
+
+    /** Number of rank bits in NVM address*/
+    const unsigned int rankBits;
+
+    /** Number of ranks to be utilized for a given configuration */
+    const unsigned int nbrOfRanks;
+
+};
+
+#endif
index 1ef151914e1c68760826cf2c2c4e8c8c3e3dc326..61f205d463a38b4ac1e8a7764c3269dfdb0e9238 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012-2013, 2016-2019 ARM Limited
+ * Copyright (c) 2012-2013, 2016-2020 ARM Limited
  * All rights reserved
  *
  * The license below extends only to copyright in the software and shall
@@ -182,7 +182,8 @@ TrafficGen::parseConfig()
                     states[id] = createExit(duration);
                     DPRINTF(TrafficGen, "State: %d ExitGen\n", id);
                 } else if (mode == "LINEAR" || mode == "RANDOM" ||
-                           mode == "DRAM"   || mode == "DRAM_ROTATE") {
+                           mode == "DRAM"   || mode == "DRAM_ROTATE" ||
+                           mode == "NVM") {
                     uint32_t read_percent;
                     Addr start_addr;
                     Addr end_addr;
@@ -212,25 +213,26 @@ TrafficGen::parseConfig()
                                                   min_period, max_period,
                                                   read_percent, data_limit);
                         DPRINTF(TrafficGen, "State: %d RandomGen\n", id);
-                    } else if (mode == "DRAM" || mode == "DRAM_ROTATE") {
+                    } else if (mode == "DRAM" || mode == "DRAM_ROTATE" ||
+                               mode == "NVM") {
                         // stride size (bytes) of the request for achieving
                         // required hit length
                         unsigned int stride_size;
                         unsigned int page_size;
-                        unsigned int nbr_of_banks_DRAM;
+                        unsigned int nbr_of_banks;
                         unsigned int nbr_of_banks_util;
                         unsigned _addr_mapping;
                         unsigned int nbr_of_ranks;
 
-                        is >> stride_size >> page_size >> nbr_of_banks_DRAM >>
+                        is >> stride_size >> page_size >> nbr_of_banks >>
                             nbr_of_banks_util >> _addr_mapping >>
                             nbr_of_ranks;
                         Enums::AddrMap addr_mapping =
                             static_cast<Enums::AddrMap>(_addr_mapping);
 
                         if (stride_size > page_size)
-                            warn("DRAM generator stride size (%d) is greater "
-                                 "than page size (%d)  of the memory\n",
+                            warn("Memory generator stride size (%d) is greater"
+                                 " than page size (%d)  of the memory\n",
                                  blocksize, page_size);
 
                         // count the number of sequential packets to
@@ -250,12 +252,12 @@ TrafficGen::parseConfig()
                                                     min_period, max_period,
                                                     read_percent, data_limit,
                                                     num_seq_pkts, page_size,
-                                                    nbr_of_banks_DRAM,
+                                                    nbr_of_banks,
                                                     nbr_of_banks_util,
                                                     addr_mapping,
                                                     nbr_of_ranks);
                             DPRINTF(TrafficGen, "State: %d DramGen\n", id);
-                        } else {
+                        } else if (mode == "DRAM_ROTATE") {
                             // Will rotate to the next rank after rotating
                             // through all banks, for each command type.
                             // In the 50% read case, series will be issued
@@ -270,12 +272,23 @@ TrafficGen::parseConfig()
                                                        read_percent,
                                                        data_limit,
                                                        num_seq_pkts, page_size,
-                                                       nbr_of_banks_DRAM,
+                                                       nbr_of_banks,
                                                        nbr_of_banks_util,
                                                        addr_mapping,
                                                        nbr_of_ranks,
                                                        max_seq_count_per_rank);
                             DPRINTF(TrafficGen, "State: %d DramRotGen\n", id);
+                        } else {
+                            states[id] = createNvm(duration, start_addr,
+                                                   end_addr, blocksize,
+                                                   min_period, max_period,
+                                                   read_percent, data_limit,
+                                                   num_seq_pkts, page_size,
+                                                   nbr_of_banks,
+                                                   nbr_of_banks_util,
+                                                   addr_mapping,
+                                                   nbr_of_ranks);
+                            DPRINTF(TrafficGen, "State: %d NvmGen\n", id);
                         }
                     }
                 } else {
index b7b43dca7f10f52bca57aaa74a4dc7230de796f5..4ef421bdef8a4640ec337b3409341a93166f7d49 100644 (file)
@@ -59,7 +59,10 @@ class DRAMCtrl(QoSMemCtrl):
     port = SlavePort("Slave port")
 
     # Interface to volatile, DRAM media
-    dram = Param.DRAMInterface("DRAM interface")
+    dram = Param.DRAMInterface(NULL, "DRAM interface")
+
+    # Interface to non-volatile media
+    nvm = Param.NVMInterface(NULL, "NVM interface")
 
     # read and write buffer depths are set in the interface
     # the controller will read these values when instantiated
@@ -85,3 +88,5 @@ class DRAMCtrl(QoSMemCtrl):
     # serviced by the memory seeing the sum of the two
     static_frontend_latency = Param.Latency("10ns", "Static frontend latency")
     static_backend_latency = Param.Latency("10ns", "Static backend latency")
+
+    command_window = Param.Latency("10ns", "Static backend latency")
index f571920c201c4d3ed8544e9824dc47f383f3e279..aa415feb45141885ac4860aef3e04fe63139920a 100644 (file)
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-from m5.params import *
-from m5.proxy import *
-
-from m5.objects.AbstractMemory import AbstractMemory
-
-# Enum for the address mapping. With Ch, Ra, Ba, Ro and Co denoting
-# channel, rank, bank, row and column, respectively, and going from
-# MSB to LSB.  Available are RoRaBaChCo and RoRaBaCoCh, that are
-# suitable for an open-page policy, optimising for sequential accesses
-# hitting in the open row. For a closed-page policy, RoCoRaBaCh
-# maximises parallelism.
-class AddrMap(Enum): vals = ['RoRaBaChCo', 'RoRaBaCoCh', 'RoCoRaBaCh']
+from m5.objects.MemInterface import *
 
 # Enum for the page policy, either open, open_adaptive, close, or
 # close_adaptive.
 class PageManage(Enum): vals = ['open', 'open_adaptive', 'close',
                                 'close_adaptive']
 
-class DRAMInterface(AbstractMemory):
+class DRAMInterface(MemInterface):
     type = 'DRAMInterface'
     cxx_header = "mem/dram_ctrl.hh"
 
-    # Allow the interface to set required controller buffer sizes
-    # each entry corresponds to a burst for the specific DRAM
-    # configuration (e.g. x32 with burst length 8 is 32 bytes) and not
-    # the cacheline size or request/packet size
-    write_buffer_size = Param.Unsigned(64, "Number of write queue entries")
-    read_buffer_size = Param.Unsigned(32, "Number of read queue entries")
-
-    # scheduler, address map and page policy
-    addr_mapping = Param.AddrMap('RoRaBaCoCh', "Address mapping policy")
+    # scheduler page policy
     page_policy = Param.PageManage('open_adaptive', "Page management policy")
 
     # enforce a limit on the number of accesses per row
     max_accesses_per_row = Param.Unsigned(16, "Max accesses per row before "
                                           "closing");
 
-    # size of DRAM Chip in Bytes
-    device_size = Param.MemorySize("Size of DRAM chip")
-    # the physical organisation of the DRAM
-    device_bus_width = Param.Unsigned("data bus width in bits for each DRAM "\
-                                      "device/chip")
-    burst_length = Param.Unsigned("Burst lenght (BL) in beats")
-    device_rowbuffer_size = Param.MemorySize("Page (row buffer) size per "\
-                                           "device/chip")
-    devices_per_rank = Param.Unsigned("Number of devices/chips per rank")
-    ranks_per_channel = Param.Unsigned("Number of ranks per channel")
-
     # default to 0 bank groups per rank, indicating bank group architecture
     # is not used
     # update per memory class when bank group architecture is supported
     bank_groups_per_rank = Param.Unsigned(0, "Number of bank groups per rank")
-    banks_per_rank = Param.Unsigned("Number of banks per rank")
 
     # Enable DRAM powerdown states if True. This is False by default due to
     # performance being lower when enabled
@@ -107,9 +76,6 @@ class DRAMInterface(AbstractMemory):
 
     # timing behaviour and constraints - all in nanoseconds
 
-    # the base clock period of the DRAM
-    tCK = Param.Latency("Clock period")
-
     # the amount of time in nanoseconds from issuing an activate command
     # to the data being available in the row buffer for a read/write
     tRCD = Param.Latency("RAS to CAS delay")
@@ -129,18 +95,6 @@ class DRAMInterface(AbstractMemory):
     # minimum time between a read and precharge command
     tRTP = Param.Latency("Read to precharge")
 
-    # time to complete a burst transfer, typically the burst length
-    # divided by two due to the DDR bus, but by making it a parameter
-    # it is easier to also evaluate SDR memories like WideIO.
-    # This parameter has to account for burst length.
-    # Read/Write requests with data size larger than one full burst are broken
-    # down into multiple requests in the controller
-    # tBURST is equivalent to the CAS-to-CAS delay (tCCD)
-    # With bank group architectures, tBURST represents the CAS-to-CAS
-    # delay for bursts to different bank groups (tCCD_S)
-    tBURST = Param.Latency("Burst duration "
-                           "(typically burst length / 2 cycles)")
-
     # tBURST_MAX is the column array cycle delay required before next access,
     # which could be greater than tBURST when the memory access time is greater
     # than tBURST
@@ -170,22 +124,10 @@ class DRAMInterface(AbstractMemory):
     # to be sent. It is 7.8 us for a 64ms refresh requirement
     tREFI = Param.Latency("Refresh command interval")
 
-    # write-to-read, same rank turnaround penalty
-    tWTR = Param.Latency("Write to read, same rank switching time")
-
     # write-to-read, same rank turnaround penalty for same bank group
     tWTR_L = Param.Latency(Self.tWTR, "Write to read, same rank switching "
                            "time, same bank group")
 
-    # read-to-write, same rank turnaround penalty
-    tRTW = Param.Latency("Read to write, same rank switching time")
-
-    # rank-to-rank bus delay penalty
-    # this does not correlate to a memory timing parameter and encompasses:
-    # 1) RD-to-RD, 2) WR-to-WR, 3) RD-to-WR, and 4) WR-to-RD
-    # different rank bus delay
-    tCS = Param.Latency("Rank to rank switching time")
-
     # minimum precharge to precharge delay time
     tPPD = Param.Latency("0ns", "PRE to PRE delay")
 
@@ -221,6 +163,7 @@ class DRAMInterface(AbstractMemory):
     tXSDLL = Param.Latency("0ns", "Self-refresh exit latency DLL")
 
     # number of data beats per clock. with DDR, default is 2, one per edge
+    # used in drampower.cc
     beats_per_clock = Param.Unsigned(2, "Data beats per clock")
 
     data_clock_sync = Param.Bool(False, "Synchronization commands required")
diff --git a/src/mem/MemInterface.py b/src/mem/MemInterface.py
new file mode 100644 (file)
index 0000000..3a8b917
--- /dev/null
@@ -0,0 +1,106 @@
+# Copyright (c) 2012-2020 ARM Limited
+# All rights reserved.
+#
+# The license below extends only to copyright in the software and shall
+# not be construed as granting a license to any other intellectual
+# property including but not limited to intellectual property relating
+# to a hardware implementation of the functionality of the software
+# licensed hereunder.  You may use the software subject to the license
+# terms below provided that you ensure that this notice is replicated
+# unmodified and in its entirety in all distributions of the software,
+# modified or unmodified, in source code or in binary form.
+#
+# Copyright (c) 2013 Amin Farmahini-Farahani
+# Copyright (c) 2015 University of Kaiserslautern
+# Copyright (c) 2015 The University of Bologna
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met: redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer;
+# redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution;
+# neither the name of the copyright holders nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from m5.params import *
+from m5.proxy import *
+
+from m5.objects.AbstractMemory import AbstractMemory
+
+# Enum for the address mapping. With Ch, Ra, Ba, Ro and Co denoting
+# channel, rank, bank, row and column, respectively, and going from
+# MSB to LSB.  Available are RoRaBaChCo and RoRaBaCoCh, that are
+# suitable for an open-page policy, optimising for sequential accesses
+# hitting in the open row. For a closed-page policy, RoCoRaBaCh
+# maximises parallelism.
+class AddrMap(Enum): vals = ['RoRaBaChCo', 'RoRaBaCoCh', 'RoCoRaBaCh']
+
+class MemInterface(AbstractMemory):
+    type = 'MemInterface'
+    abstract = True
+    cxx_header = "mem/dram_ctrl.hh"
+
+    # Allow the interface to set required controller buffer sizes
+    # each entry corresponds to a burst for the specific memory channel
+    # configuration (e.g. x32 with burst length 8 is 32 bytes) and not
+    # the cacheline size or request/packet size
+    write_buffer_size = Param.Unsigned(64, "Number of write queue entries")
+    read_buffer_size = Param.Unsigned(32, "Number of read queue entries")
+
+    # scheduler, address map
+    addr_mapping = Param.AddrMap('RoRaBaCoCh', "Address mapping policy")
+
+    # size of memory device in Bytes
+    device_size = Param.MemorySize("Size of memory device")
+    # the physical organisation of the memory
+    device_bus_width = Param.Unsigned("data bus width in bits for each "\
+                                      "memory device/chip")
+    burst_length = Param.Unsigned("Burst lenght (BL) in beats")
+    device_rowbuffer_size = Param.MemorySize("Page (row buffer) size per "\
+                                           "device/chip")
+    devices_per_rank = Param.Unsigned("Number of devices/chips per rank")
+    ranks_per_channel = Param.Unsigned("Number of ranks per channel")
+    banks_per_rank = Param.Unsigned("Number of banks per rank")
+
+    # timing behaviour and constraints - all in nanoseconds
+
+    # the base clock period of the memory
+    tCK = Param.Latency("Clock period")
+
+    # time to complete a burst transfer, typically the burst length
+    # divided by two due to the DDR bus, but by making it a parameter
+    # it is easier to also evaluate SDR memories like WideIO and new
+    # interfaces, emerging technologies.
+    # This parameter has to account for burst length.
+    # Read/Write requests with data size larger than one full burst are broken
+    # down into multiple requests in the controller
+    tBURST = Param.Latency("Burst duration "
+                           "(typically burst length / 2 cycles)")
+
+    # write-to-read, same rank turnaround penalty
+    tWTR = Param.Latency("Write to read, same rank switching time")
+
+    # read-to-write, same rank turnaround penalty
+    tRTW = Param.Latency("Read to write, same rank switching time")
+
+    # rank-to-rank bus delay penalty
+    # this does not correlate to a memory timing parameter and encompasses:
+    # 1) RD-to-RD, 2) WR-to-WR, 3) RD-to-WR, and 4) WR-to-RD
+    # different rank bus delay
+    tCS = Param.Latency("Rank to rank switching time")
diff --git a/src/mem/NVMInterface.py b/src/mem/NVMInterface.py
new file mode 100644 (file)
index 0000000..f28dd81
--- /dev/null
@@ -0,0 +1,103 @@
+# Copyright (c) 2020 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.
+
+from m5.params import *
+from m5.proxy import *
+from m5.objects.MemInterface import MemInterface
+from m5.objects.DRAMInterface import AddrMap
+
+# The following interface aims to model byte-addressable NVM
+# The most important system-level performance effects of a NVM
+# are modeled without getting into too much detail of the media itself.
+class NVMInterface(MemInterface):
+    type = 'NVMInterface'
+    cxx_header = "mem/dram_ctrl.hh"
+
+    # NVM DIMM could have write buffer to offload writes
+    # define buffer depth, which will limit the number of pending writes
+    max_pending_writes = Param.Unsigned("1", "Max pending write commands")
+
+    # NVM DIMM could have buffer to offload read commands
+    # define buffer depth, which will limit the number of pending reads
+    max_pending_reads = Param.Unsigned("1", "Max pending read commands")
+
+    # timing behaviour and constraints - all in nanoseconds
+
+    # define average latency for NVM media.  Latency defined uniquely
+    # for read and writes as the media is typically not symmetric
+    tREAD = Param.Latency("100ns", "Average NVM read latency")
+    tWRITE = Param.Latency("200ns", "Average NVM write latency")
+    tSEND = Param.Latency("15ns", "Access latency")
+
+    two_cycle_rdwr = Param.Bool(False,
+                     "Two cycles required to send read and write commands")
+
+# NVM delays and device architecture defined to mimic PCM like memory.
+# Can be configured with DDR4_2400 sharing the channel
+class NVM_2400_1x64(NVMInterface):
+    write_buffer_size = 128
+    read_buffer_size = 64
+
+    max_pending_writes = 128
+    max_pending_reads = 64
+
+    device_rowbuffer_size = '256B'
+
+    # 8X capacity compared to DDR4 x4 DIMM with 8Gb devices
+    device_size = '512GB'
+    # Mimic 64-bit media agnostic DIMM interface
+    device_bus_width = 64
+    devices_per_rank = 1
+    ranks_per_channel = 1
+    banks_per_rank = 16
+
+    burst_length = 8
+
+    two_cycle_rdwr = True
+
+    # 1200 MHz
+    tCK = '0.833ns'
+
+    tREAD = '150ns'
+    tWRITE = '500ns';
+    tSEND = '14.16ns';
+    tBURST = '3.332ns';
+
+    # Default all bus turnaround and rank bus delay to 2 cycles
+    # With DDR data bus, clock = 1200 MHz = 1.666 ns
+    tWTR = '1.666ns';
+    tRTW = '1.666ns';
+    tCS = '1.666ns'
+
index ceeed980a54481e63c89ccc13cec8cb84bdd1df9..409116c40ba7c781d3ef6438d2a75698df8f541f 100644 (file)
@@ -47,7 +47,9 @@ SimObject('AbstractMemory.py')
 SimObject('AddrMapper.py')
 SimObject('Bridge.py')
 SimObject('DRAMCtrl.py')
+SimObject('MemInterface.py')
 SimObject('DRAMInterface.py')
+SimObject('NVMInterface.py')
 SimObject('ExternalMaster.py')
 SimObject('ExternalSlave.py')
 SimObject('MemObject.py')
@@ -114,6 +116,7 @@ DebugFlag('CommMonitor')
 DebugFlag('DRAM')
 DebugFlag('DRAMPower')
 DebugFlag('DRAMState')
+DebugFlag('NVM')
 DebugFlag('ExternalPort')
 DebugFlag('HtmMem', 'Hardware Transactional Memory (Mem side)')
 DebugFlag('LLSC')
index 4055505b310796439dda439530309f15daca6882..55451a2e1052fba398db568155fba5de9911ee58 100644 (file)
 #include "debug/DRAMPower.hh"
 #include "debug/DRAMState.hh"
 #include "debug/Drain.hh"
+#include "debug/NVM.hh"
 #include "debug/QOS.hh"
 #include "params/DRAMInterface.hh"
+#include "params/NVMInterface.hh"
 #include "sim/system.hh"
 
 using namespace std;
@@ -59,9 +61,11 @@ DRAMCtrl::DRAMCtrl(const DRAMCtrlParams* p) :
     retryRdReq(false), retryWrReq(false),
     nextReqEvent([this]{ processNextReqEvent(); }, name()),
     respondEvent([this]{ processRespondEvent(); }, name()),
-    dram(p->dram),
-    readBufferSize(dram->readBufferSize),
-    writeBufferSize(dram->writeBufferSize),
+    dram(p->dram), nvm(p->nvm),
+    readBufferSize((dram ? dram->readBufferSize : 0) +
+                   (nvm ? nvm->readBufferSize : 0)),
+    writeBufferSize((dram ? dram->writeBufferSize : 0) +
+                    (nvm ? nvm->writeBufferSize : 0)),
     writeHighThreshold(writeBufferSize * p->write_high_thresh_perc / 100.0),
     writeLowThreshold(writeBufferSize * p->write_low_thresh_perc / 100.0),
     minWritesPerSwitch(p->min_writes_per_switch),
@@ -69,14 +73,22 @@ DRAMCtrl::DRAMCtrl(const DRAMCtrlParams* p) :
     memSchedPolicy(p->mem_sched_policy),
     frontendLatency(p->static_frontend_latency),
     backendLatency(p->static_backend_latency),
+    commandWindow(p->command_window),
     nextBurstAt(0), prevArrival(0),
     nextReqTime(0),
     stats(*this)
 {
+    DPRINTF(DRAM, "Setting up controller\n");
     readQueue.resize(p->qos_priorities);
     writeQueue.resize(p->qos_priorities);
 
-    dram->setCtrl(this);
+    // Hook up interfaces to the controller
+    if (dram)
+        dram->setCtrl(this, commandWindow);
+    if (nvm)
+        nvm->setCtrl(this, commandWindow);
+
+    fatal_if(!dram && !nvm, "Memory controller must have an interface");
 
     // perform a basic check of the write thresholds
     if (p->write_low_thresh_perc >= p->write_high_thresh_perc)
@@ -106,7 +118,8 @@ DRAMCtrl::startup()
         // have to worry about negative values when computing the time for
         // the next request, this will add an insignificant bubble at the
         // start of simulation
-        nextBurstAt = curTick() + dram->tRC();
+        nextBurstAt = curTick() + (dram ? dram->commandOffset() :
+                                          nvm->commandOffset());
     }
 }
 
@@ -118,15 +131,29 @@ DRAMCtrl::recvAtomic(PacketPtr pkt)
     panic_if(pkt->cacheResponding(), "Should not see packets where cache "
              "is responding");
 
+    Tick latency = 0;
     // do the actual memory access and turn the packet into a response
-    dram->access(pkt);
+    if (dram && dram->getAddrRange().contains(pkt->getAddr())) {
+        dram->access(pkt);
 
-    Tick latency = 0;
-    if (pkt->hasData()) {
-        // this value is not supposed to be accurate, just enough to
-        // keep things going, mimic a closed page
-        latency = dram->accessLatency();
+        if (pkt->hasData()) {
+            // this value is not supposed to be accurate, just enough to
+            // keep things going, mimic a closed page
+            latency = dram->accessLatency();
+        }
+    } else if (nvm && nvm->getAddrRange().contains(pkt->getAddr())) {
+        nvm->access(pkt);
+
+        if (pkt->hasData()) {
+            // this value is not supposed to be accurate, just enough to
+            // keep things going, mimic a closed page
+            latency = nvm->accessLatency();
+        }
+    } else {
+        panic("Can't handle address range for packet %s\n",
+              pkt->print());
     }
+
     return latency;
 }
 
@@ -152,8 +179,8 @@ DRAMCtrl::writeQueueFull(unsigned int neededEntries) const
 }
 
 DRAMPacket*
-DRAMInterface::decodePacket(const PacketPtr pkt, Addr dramPktAddr,
-                       unsigned size, bool isRead) const
+MemInterface::decodePacket(const PacketPtr pkt, Addr pkt_addr,
+                       unsigned size, bool is_read, bool is_dram)
 {
     // decode the address based on the address mapping scheme, with
     // Ro, Ra, Co, Ba and Ch denoting row, rank, column, bank and
@@ -161,19 +188,22 @@ DRAMInterface::decodePacket(const PacketPtr pkt, Addr dramPktAddr,
     uint8_t rank;
     uint8_t bank;
     // use a 64-bit unsigned during the computations as the row is
-    // always the top bits, and check before creating the DRAMPacket
+    // always the top bits, and check before creating the packet
     uint64_t row;
 
-    // truncate the address to a DRAM burst, which makes it unique to
-    // a specific column, row, bank, rank and channel
-    Addr addr = dramPktAddr / burstSize;
+    // Get packed address, starting at 0
+    Addr addr = getCtrlAddr(pkt_addr);
+
+    // truncate the address to a memory burst, which makes it unique to
+    // a specific buffer, row, bank, rank and channel
+    addr = addr / burstSize;
 
     // we have removed the lowest order address bits that denote the
     // position within the column
     if (addrMapping == Enums::RoRaBaChCo || addrMapping == Enums::RoRaBaCoCh) {
         // the lowest order bits denote the column to ensure that
         // sequential cache lines occupy the same row
-        addr = addr / columnsPerRowBuffer;
+        addr = addr / burstsPerRowBuffer;
 
         // after the channel bits, get the bank bits to interleave
         // over the banks
@@ -188,11 +218,15 @@ DRAMInterface::decodePacket(const PacketPtr pkt, Addr dramPktAddr,
         // lastly, get the row bits, no need to remove them from addr
         row = addr % rowsPerBank;
     } else if (addrMapping == Enums::RoCoRaBaCh) {
-        // optimise for closed page mode and utilise maximum
-        // parallelism of the DRAM (at the cost of power)
-
-        // take out the lower-order column bits
-        addr = addr / columnsPerStripe;
+        // with emerging technologies, could have small page size with
+        // interleaving granularity greater than row buffer
+        if (burstsPerStripe > burstsPerRowBuffer) {
+            // remove column bits which are a subset of burstsPerStripe
+            addr = addr / burstsPerRowBuffer;
+        } else {
+            // remove lower column bits below channel bits
+            addr = addr / burstsPerStripe;
+        }
 
         // start with the bank bits, as this provides the maximum
         // opportunity for parallelism between requests
@@ -204,7 +238,9 @@ DRAMInterface::decodePacket(const PacketPtr pkt, Addr dramPktAddr,
         addr = addr / ranksPerChannel;
 
         // next, the higher-order column bites
-        addr = addr / (columnsPerRowBuffer / columnsPerStripe);
+        if (burstsPerStripe < burstsPerRowBuffer) {
+            addr = addr / (burstsPerRowBuffer / burstsPerStripe);
+        }
 
         // lastly, get the row bits, no need to remove them from addr
         row = addr % rowsPerBank;
@@ -217,46 +253,42 @@ DRAMInterface::decodePacket(const PacketPtr pkt, Addr dramPktAddr,
     assert(row < Bank::NO_ROW);
 
     DPRINTF(DRAM, "Address: %lld Rank %d Bank %d Row %d\n",
-            dramPktAddr, rank, bank, row);
+            pkt_addr, rank, bank, row);
 
-    if (isRead) {
-        // increment read entries of the rank
-        ++ranks[rank]->readEntries;
-    } else {
-        // increment write entries of the rank
-        ++ranks[rank]->writeEntries;
-    }
-    // create the corresponding DRAM packet with the entry time and
+    // create the corresponding memory packet with the entry time and
     // ready time set to the current tick, the latter will be updated
     // later
     uint16_t bank_id = banksPerRank * rank + bank;
-    return new DRAMPacket(pkt, isRead, rank, bank, row, bank_id, dramPktAddr,
-                          size, ranks[rank]->banks[bank]);
+
+    return new DRAMPacket(pkt, is_read, is_dram, rank, bank, row, bank_id,
+                   pkt_addr, size);
 }
 
 void
-DRAMCtrl::addToReadQueue(PacketPtr pkt, unsigned int pktCount)
+DRAMCtrl::addToReadQueue(PacketPtr pkt, unsigned int pkt_count, bool is_dram)
 {
     // only add to the read queue here. whenever the request is
     // eventually done, set the readyTime, and call schedule()
     assert(!pkt->isWrite());
 
-    assert(pktCount != 0);
+    assert(pkt_count != 0);
 
     // if the request size is larger than burst size, the pkt is split into
-    // multiple DRAM packets
+    // multiple packets
     // Note if the pkt starting address is not aligened to burst size, the
-    // address of first DRAM packet is kept unaliged. Subsequent DRAM packets
+    // address of first packet is kept unaliged. Subsequent packets
     // are aligned to burst size boundaries. This is to ensure we accurately
     // check read packets against packets in write queue.
-    const Addr base_addr = dram->getCtrlAddr(pkt->getAddr());
+    const Addr base_addr = pkt->getAddr();
     Addr addr = base_addr;
     unsigned pktsServicedByWrQ = 0;
     BurstHelper* burst_helper = NULL;
-    uint32_t burstSize = dram->bytesPerBurst();
-    for (int cnt = 0; cnt < pktCount; ++cnt) {
-        unsigned size = std::min((addr | (burstSize - 1)) + 1,
-                                 base_addr + pkt->getSize()) - addr;
+
+    uint32_t burst_size = is_dram ? dram->bytesPerBurst() :
+                                   nvm->bytesPerBurst();
+    for (int cnt = 0; cnt < pkt_count; ++cnt) {
+        unsigned size = std::min((addr | (burst_size - 1)) + 1,
+                        base_addr + pkt->getSize()) - addr;
         stats.readPktSize[ceilLog2(size)]++;
         stats.readBursts++;
         stats.masterReadAccesses[pkt->masterId()]++;
@@ -264,7 +296,7 @@ DRAMCtrl::addToReadQueue(PacketPtr pkt, unsigned int pktCount)
         // First check write buffer to see if the data is already at
         // the controller
         bool foundInWrQ = false;
-        Addr burst_addr = burstAlign(addr);
+        Addr burst_addr = burstAlign(addr, is_dram);
         // if the burst address is not present then there is no need
         // looking any further
         if (isInWriteQueue.find(burst_addr) != isInWriteQueue.end()) {
@@ -282,7 +314,7 @@ DRAMCtrl::addToReadQueue(PacketPtr pkt, unsigned int pktCount)
                                 "Read to addr %lld with size %d serviced by "
                                 "write queue\n",
                                 addr, size);
-                        stats.bytesReadWrQ += burstSize;
+                        stats.bytesReadWrQ += burst_size;
                         break;
                     }
                 }
@@ -294,13 +326,24 @@ DRAMCtrl::addToReadQueue(PacketPtr pkt, unsigned int pktCount)
         if (!foundInWrQ) {
 
             // Make the burst helper for split packets
-            if (pktCount > 1 && burst_helper == NULL) {
+            if (pkt_count > 1 && burst_helper == NULL) {
                 DPRINTF(DRAM, "Read to addr %lld translates to %d "
-                        "dram requests\n", pkt->getAddr(), pktCount);
-                burst_helper = new BurstHelper(pktCount);
+                        "memory requests\n", pkt->getAddr(), pkt_count);
+                burst_helper = new BurstHelper(pkt_count);
             }
 
-            DRAMPacket* dram_pkt = dram->decodePacket(pkt, addr, size, true);
+            DRAMPacket* dram_pkt;
+            if (is_dram) {
+                dram_pkt = dram->decodePacket(pkt, addr, size, true, true);
+                // increment read entries of the rank
+                dram->setupRank(dram_pkt->rank, true);
+            } else {
+                dram_pkt = nvm->decodePacket(pkt, addr, size, true, false);
+                // Increment count to trigger issue of non-deterministic read
+                nvm->setupRank(dram_pkt->rank, true);
+                // Default readyTime to Max; will be reset once read is issued
+                dram_pkt->readyTime = MaxTick;
+            }
             dram_pkt->burstHelper = burst_helper;
 
             assert(!readQueueFull(1));
@@ -318,12 +361,12 @@ DRAMCtrl::addToReadQueue(PacketPtr pkt, unsigned int pktCount)
             stats.avgRdQLen = totalReadQueueSize + respQueue.size();
         }
 
-        // Starting address of next dram pkt (aligned to burst boundary)
-        addr = (addr | (burstSize - 1)) + 1;
+        // Starting address of next memory pkt (aligned to burst boundary)
+        addr = (addr | (burst_size - 1)) + 1;
     }
 
     // If all packets are serviced by write queue, we send the repsonse back
-    if (pktsServicedByWrQ == pktCount) {
+    if (pktsServicedByWrQ == pkt_count) {
         accessAndRespond(pkt, frontendLatency);
         return;
     }
@@ -341,41 +384,48 @@ DRAMCtrl::addToReadQueue(PacketPtr pkt, unsigned int pktCount)
 }
 
 void
-DRAMCtrl::addToWriteQueue(PacketPtr pkt, unsigned int pktCount)
+DRAMCtrl::addToWriteQueue(PacketPtr pkt, unsigned int pkt_count, bool is_dram)
 {
     // only add to the write queue here. whenever the request is
     // eventually done, set the readyTime, and call schedule()
     assert(pkt->isWrite());
 
     // if the request size is larger than burst size, the pkt is split into
-    // multiple DRAM packets
-    const Addr base_addr = dram->getCtrlAddr(pkt->getAddr());
+    // multiple packets
+    const Addr base_addr = pkt->getAddr();
     Addr addr = base_addr;
-    uint32_t burstSize = dram->bytesPerBurst();
-    for (int cnt = 0; cnt < pktCount; ++cnt) {
-        unsigned size = std::min((addr | (burstSize - 1)) + 1,
-                                 base_addr + pkt->getSize()) - addr;
+    uint32_t burst_size = is_dram ? dram->bytesPerBurst() :
+                                   nvm->bytesPerBurst();
+    for (int cnt = 0; cnt < pkt_count; ++cnt) {
+        unsigned size = std::min((addr | (burst_size - 1)) + 1,
+                        base_addr + pkt->getSize()) - addr;
         stats.writePktSize[ceilLog2(size)]++;
         stats.writeBursts++;
         stats.masterWriteAccesses[pkt->masterId()]++;
 
         // see if we can merge with an existing item in the write
         // queue and keep track of whether we have merged or not
-        bool merged = isInWriteQueue.find(burstAlign(addr)) !=
+        bool merged = isInWriteQueue.find(burstAlign(addr, is_dram)) !=
             isInWriteQueue.end();
 
         // if the item was not merged we need to create a new write
         // and enqueue it
         if (!merged) {
-            DRAMPacket* dram_pkt = dram->decodePacket(pkt, addr, size, false);
-
+            DRAMPacket* dram_pkt;
+            if (is_dram) {
+                dram_pkt = dram->decodePacket(pkt, addr, size, false, true);
+                dram->setupRank(dram_pkt->rank, false);
+            } else {
+                dram_pkt = nvm->decodePacket(pkt, addr, size, false, false);
+                nvm->setupRank(dram_pkt->rank, false);
+            }
             assert(totalWriteQueueSize < writeBufferSize);
             stats.wrQLenPdf[totalWriteQueueSize]++;
 
             DPRINTF(DRAM, "Adding to write queue\n");
 
             writeQueue[dram_pkt->qosValue()].push_back(dram_pkt);
-            isInWriteQueue.insert(burstAlign(addr));
+            isInWriteQueue.insert(burstAlign(addr, is_dram));
 
             // log packet
             logRequest(MemCtrl::WRITE, pkt->masterId(), pkt->qosValue(),
@@ -394,8 +444,8 @@ DRAMCtrl::addToWriteQueue(PacketPtr pkt, unsigned int pktCount)
             stats.mergedWrBursts++;
         }
 
-        // Starting address of next dram pkt (aligned to burstSize boundary)
-        addr = (addr | (burstSize - 1)) + 1;
+        // Starting address of next memory pkt (aligned to burst_size boundary)
+        addr = (addr | (burst_size - 1)) + 1;
     }
 
     // we do not wait for the writes to be send to the actual memory,
@@ -457,44 +507,56 @@ DRAMCtrl::recvTimingReq(PacketPtr pkt)
     }
     prevArrival = curTick();
 
+    // What type of media does this packet access?
+    bool is_dram;
+    if (dram && dram->getAddrRange().contains(pkt->getAddr())) {
+        is_dram = true;
+    } else if (nvm && nvm->getAddrRange().contains(pkt->getAddr())) {
+        is_dram = false;
+    } else {
+        panic("Can't handle address range for packet %s\n",
+              pkt->print());
+    }
+
 
-    // Find out how many dram packets a pkt translates to
+    // Find out how many memory packets a pkt translates to
     // If the burst size is equal or larger than the pkt size, then a pkt
-    // translates to only one dram packet. Otherwise, a pkt translates to
-    // multiple dram packets
+    // translates to only one memory packet. Otherwise, a pkt translates to
+    // multiple memory packets
     unsigned size = pkt->getSize();
-    uint32_t burstSize = dram->bytesPerBurst();
-    unsigned offset = pkt->getAddr() & (burstSize - 1);
-    unsigned int dram_pkt_count = divCeil(offset + size, burstSize);
+    uint32_t burst_size = is_dram ? dram->bytesPerBurst() :
+                                  nvm->bytesPerBurst();
+    unsigned offset = pkt->getAddr() & (burst_size - 1);
+    unsigned int pkt_count = divCeil(offset + size, burst_size);
 
     // run the QoS scheduler and assign a QoS priority value to the packet
-    qosSchedule( { &readQueue, &writeQueue }, burstSize, pkt);
+    qosSchedule( { &readQueue, &writeQueue }, burst_size, pkt);
 
     // check local buffers and do not accept if full
     if (pkt->isWrite()) {
         assert(size != 0);
-        if (writeQueueFull(dram_pkt_count)) {
+        if (writeQueueFull(pkt_count)) {
             DPRINTF(DRAM, "Write queue full, not accepting\n");
             // remember that we have to retry this port
             retryWrReq = true;
             stats.numWrRetry++;
             return false;
         } else {
-            addToWriteQueue(pkt, dram_pkt_count);
+            addToWriteQueue(pkt, pkt_count, is_dram);
             stats.writeReqs++;
             stats.bytesWrittenSys += size;
         }
     } else {
         assert(pkt->isRead());
         assert(size != 0);
-        if (readQueueFull(dram_pkt_count)) {
+        if (readQueueFull(pkt_count)) {
             DPRINTF(DRAM, "Read queue full, not accepting\n");
             // remember that we have to retry this port
             retryRdReq = true;
             stats.numRdRetry++;
             return false;
         } else {
-            addToReadQueue(pkt, dram_pkt_count);
+            addToReadQueue(pkt, pkt_count, is_dram);
             stats.readReqs++;
             stats.bytesReadSys += size;
         }
@@ -511,8 +573,10 @@ DRAMCtrl::processRespondEvent()
 
     DRAMPacket* dram_pkt = respQueue.front();
 
-    // media specific checks and functions when read response is complete
-    dram->respondEvent(dram_pkt->rank);
+    if (dram_pkt->isDram()) {
+        // media specific checks and functions when read response is complete
+        dram->respondEvent(dram_pkt->rank);
+    }
 
     if (dram_pkt->burstHelper) {
         // it is a split packet
@@ -543,11 +607,11 @@ DRAMCtrl::processRespondEvent()
         // if there is nothing left in any queue, signal a drain
         if (drainState() == DrainState::Draining &&
             !totalWriteQueueSize && !totalReadQueueSize &&
-            dram->allRanksDrained()) {
+            allIntfDrained()) {
 
             DPRINTF(Drain, "DRAM controller done draining\n");
             signalDrainDone();
-        } else {
+        } else if (dram_pkt->isDram()) {
             // check the refresh state and kick the refresh event loop
             // into action again if banks already closed and just waiting
             // for read to complete
@@ -576,7 +640,7 @@ DRAMCtrl::chooseNext(DRAMPacketQueue& queue, Tick extra_col_delay)
         if (queue.size() == 1) {
             // available rank corresponds to state refresh idle
             DRAMPacket* dram_pkt = *(queue.begin());
-            if (dram->burstReady(dram_pkt->rank)) {
+            if (packetReady(dram_pkt)) {
                 ret = queue.begin();
                 DPRINTF(DRAM, "Single request, going to a free rank\n");
             } else {
@@ -586,7 +650,7 @@ DRAMCtrl::chooseNext(DRAMPacketQueue& queue, Tick extra_col_delay)
             // check if there is a packet going to a free rank
             for (auto i = queue.begin(); i != queue.end(); ++i) {
                 DRAMPacket* dram_pkt = *i;
-                if (dram->burstReady(dram_pkt->rank)) {
+                if (packetReady(dram_pkt)) {
                     ret = i;
                     break;
                 }
@@ -603,8 +667,60 @@ DRAMCtrl::chooseNext(DRAMPacketQueue& queue, Tick extra_col_delay)
 DRAMPacketQueue::iterator
 DRAMCtrl::chooseNextFRFCFS(DRAMPacketQueue& queue, Tick extra_col_delay)
 {
-    // Only determine this if needed
-    vector<uint32_t> earliest_banks(dram->numRanks(), 0);
+    auto selected_pkt_it = queue.end();
+    Tick col_allowed_at = MaxTick;
+
+    // time we need to issue a column command to be seamless
+    const Tick min_col_at = std::max(nextBurstAt + extra_col_delay, curTick());
+
+    // find optimal packet for each interface
+    if (dram && nvm) {
+        // create 2nd set of parameters for NVM
+        auto nvm_pkt_it = queue.end();
+        Tick nvm_col_at = MaxTick;
+
+        // Select DRAM packet by default to give priority if both
+        // can issue at the same time or seamlessly
+        std::tie(selected_pkt_it, col_allowed_at) =
+                 dram->chooseNextFRFCFS(queue, min_col_at);
+        if (selected_pkt_it == queue.end()) {
+            DPRINTF(DRAM, "%s no available DRAM ranks found\n", __func__);
+        }
+
+        std::tie(nvm_pkt_it, nvm_col_at) =
+                 nvm->chooseNextFRFCFS(queue, min_col_at);
+        if (nvm_pkt_it == queue.end()) {
+            DPRINTF(NVM, "%s no available NVM ranks found\n", __func__);
+        }
+
+        // Compare DRAM and NVM and select NVM if it can issue
+        // earlier than the DRAM packet
+        if (col_allowed_at > nvm_col_at) {
+            selected_pkt_it = nvm_pkt_it;
+        }
+    } else if (dram) {
+        std::tie(selected_pkt_it, col_allowed_at) =
+                 dram->chooseNextFRFCFS(queue, min_col_at);
+
+        if (selected_pkt_it == queue.end()) {
+            DPRINTF(DRAM, "%s no available DRAM ranks found\n", __func__);
+        }
+    } else if (nvm) {
+        std::tie(selected_pkt_it, col_allowed_at) =
+                 nvm->chooseNextFRFCFS(queue, min_col_at);
+
+        if (selected_pkt_it == queue.end()) {
+            DPRINTF(NVM, "%s no available NVM ranks found\n", __func__);
+        }
+    }
+
+    return selected_pkt_it;
+}
+
+pair<DRAMPacketQueue::iterator, Tick>
+DRAMInterface::chooseNextFRFCFS(DRAMPacketQueue& queue, Tick min_col_at) const
+{
+    vector<uint32_t> earliest_banks(ranksPerChannel, 0);
 
     // Has minBankPrep been called to populate earliest_banks?
     bool filled_earliest_banks = false;
@@ -626,88 +742,94 @@ DRAMCtrl::chooseNextFRFCFS(DRAMPacketQueue& queue, Tick extra_col_delay)
     // just go for the earliest possible
     bool found_earliest_pkt = false;
 
+    Tick selected_col_at = MaxTick;
     auto selected_pkt_it = queue.end();
 
-    // time we need to issue a column command to be seamless
-    const Tick min_col_at = std::max(nextBurstAt + extra_col_delay, curTick());
-
     for (auto i = queue.begin(); i != queue.end() ; ++i) {
-        DRAMPacket* dram_pkt = *i;
-        const Bank& bank = dram_pkt->bankRef;
-        const Tick col_allowed_at = dram_pkt->isRead() ? bank.rdAllowedAt :
-                                                         bank.wrAllowedAt;
-
-        DPRINTF(DRAM, "%s checking packet in bank %d, row %d\n",
-                __func__, dram_pkt->bankRef.bank, dram_pkt->row);
-
-        // check if rank is not doing a refresh and thus is available, if not,
-        // jump to the next packet
-        if (dram->burstReady(dram_pkt->rank)) {
-
-            DPRINTF(DRAM,
-                    "%s bank %d - Rank %d available\n", __func__,
-                    dram_pkt->bank, dram_pkt->rank);
-
-            // check if it is a row hit
-            if (bank.openRow == dram_pkt->row) {
-                // no additional rank-to-rank or same bank-group
-                // delays, or we switched read/write and might as well
-                // go for the row hit
-                if (col_allowed_at <= min_col_at) {
-                    // FCFS within the hits, giving priority to
-                    // commands that can issue seamlessly, without
-                    // additional delay, such as same rank accesses
-                    // and/or different bank-group accesses
-                    DPRINTF(DRAM, "%s Seamless row buffer hit\n", __func__);
-                    selected_pkt_it = i;
-                    // no need to look through the remaining queue entries
-                    break;
-                } else if (!found_hidden_bank && !found_prepped_pkt) {
-                    // if we did not find a packet to a closed row that can
-                    // issue the bank commands without incurring delay, and
-                    // did not yet find a packet to a prepped row, remember
-                    // the current one
-                    selected_pkt_it = i;
-                    found_prepped_pkt = true;
-                    DPRINTF(DRAM, "%s Prepped row buffer hit\n", __func__);
-                }
-            } else if (!found_earliest_pkt) {
-                // if we have not initialised the bank status, do it
-                // now, and only once per scheduling decisions
-                if (!filled_earliest_banks) {
-                    // determine entries with earliest bank delay
-                    std::tie(earliest_banks, hidden_bank_prep) =
-                        dram->minBankPrep(queue, min_col_at);
-                    filled_earliest_banks = true;
-                }
-
-                // bank is amongst first available banks
-                // minBankPrep will give priority to packets that can
-                // issue seamlessly
-                if (bits(earliest_banks[dram_pkt->rank],
-                         dram_pkt->bank, dram_pkt->bank)) {
-                    found_earliest_pkt = true;
-                    found_hidden_bank = hidden_bank_prep;
-
-                    // give priority to packets that can issue
-                    // bank commands 'behind the scenes'
-                    // any additional delay if any will be due to
-                    // col-to-col command requirements
-                    if (hidden_bank_prep || !found_prepped_pkt)
+        DRAMPacket* pkt = *i;
+
+        // select optimal DRAM packet in Q
+        if (pkt->isDram()) {
+            const Bank& bank = ranks[pkt->rank]->banks[pkt->bank];
+            const Tick col_allowed_at = pkt->isRead() ? bank.rdAllowedAt :
+                                                        bank.wrAllowedAt;
+
+            DPRINTF(DRAM, "%s checking DRAM packet in bank %d, row %d\n",
+                    __func__, pkt->bank, pkt->row);
+
+            // check if rank is not doing a refresh and thus is available,
+            // if not, jump to the next packet
+            if (burstReady(pkt)) {
+
+                DPRINTF(DRAM,
+                        "%s bank %d - Rank %d available\n", __func__,
+                        pkt->bank, pkt->rank);
+
+                // check if it is a row hit
+                if (bank.openRow == pkt->row) {
+                    // no additional rank-to-rank or same bank-group
+                    // delays, or we switched read/write and might as well
+                    // go for the row hit
+                    if (col_allowed_at <= min_col_at) {
+                        // FCFS within the hits, giving priority to
+                        // commands that can issue seamlessly, without
+                        // additional delay, such as same rank accesses
+                        // and/or different bank-group accesses
+                        DPRINTF(DRAM, "%s Seamless buffer hit\n", __func__);
+                        selected_pkt_it = i;
+                        selected_col_at = col_allowed_at;
+                        // no need to look through the remaining queue entries
+                        break;
+                    } else if (!found_hidden_bank && !found_prepped_pkt) {
+                        // if we did not find a packet to a closed row that can
+                        // issue the bank commands without incurring delay, and
+                        // did not yet find a packet to a prepped row, remember
+                        // the current one
                         selected_pkt_it = i;
+                        selected_col_at = col_allowed_at;
+                        found_prepped_pkt = true;
+                        DPRINTF(DRAM, "%s Prepped row buffer hit\n", __func__);
+                    }
+                } else if (!found_earliest_pkt) {
+                    // if we have not initialised the bank status, do it
+                    // now, and only once per scheduling decisions
+                    if (!filled_earliest_banks) {
+                        // determine entries with earliest bank delay
+                        std::tie(earliest_banks, hidden_bank_prep) =
+                            minBankPrep(queue, min_col_at);
+                        filled_earliest_banks = true;
+                    }
+
+                    // bank is amongst first available banks
+                    // minBankPrep will give priority to packets that can
+                    // issue seamlessly
+                    if (bits(earliest_banks[pkt->rank],
+                             pkt->bank, pkt->bank)) {
+                        found_earliest_pkt = true;
+                        found_hidden_bank = hidden_bank_prep;
+
+                        // give priority to packets that can issue
+                        // bank commands 'behind the scenes'
+                        // any additional delay if any will be due to
+                        // col-to-col command requirements
+                        if (hidden_bank_prep || !found_prepped_pkt) {
+                            selected_pkt_it = i;
+                            selected_col_at = col_allowed_at;
+                        }
+                    }
                 }
+            } else {
+                DPRINTF(DRAM, "%s bank %d - Rank %d not available\n", __func__,
+                        pkt->bank, pkt->rank);
             }
-        } else {
-            DPRINTF(DRAM, "%s bank %d - Rank %d not available\n", __func__,
-                    dram_pkt->bank, dram_pkt->rank);
         }
     }
 
     if (selected_pkt_it == queue.end()) {
-        DPRINTF(DRAM, "%s no available ranks found\n", __func__);
+        DPRINTF(DRAM, "%s no available DRAM ranks found\n", __func__);
     }
 
-    return selected_pkt_it;
+    return make_pair(selected_pkt_it, selected_col_at);
 }
 
 void
@@ -718,7 +840,14 @@ DRAMCtrl::accessAndRespond(PacketPtr pkt, Tick static_latency)
     bool needsResponse = pkt->needsResponse();
     // do the actual memory access which also turns the packet into a
     // response
-    dram->access(pkt);
+    if (dram && dram->getAddrRange().contains(pkt->getAddr())) {
+        dram->access(pkt);
+    } else if (nvm && nvm->getAddrRange().contains(pkt->getAddr())) {
+        nvm->access(pkt);
+    } else {
+        panic("Can't handle address range for packet %s\n",
+              pkt->print());
+    }
 
     // turn packet around to go back to requester if response expected
     if (needsResponse) {
@@ -764,12 +893,12 @@ Tick
 DRAMCtrl::getBurstWindow(Tick cmd_tick)
 {
     // get tick aligned to burst window
-    Tick burst_offset = cmd_tick % dram->burstDataDelay();
+    Tick burst_offset = cmd_tick % commandWindow;
     return (cmd_tick - burst_offset);
 }
 
 Tick
-DRAMCtrl::verifySingleCmd(Tick cmd_tick)
+DRAMCtrl::verifySingleCmd(Tick cmd_tick, Tick max_cmds_per_burst)
 {
     // start with assumption that there is no contention on command bus
     Tick cmd_at = cmd_tick;
@@ -779,9 +908,9 @@ DRAMCtrl::verifySingleCmd(Tick cmd_tick)
 
     // verify that we have command bandwidth to issue the command
     // if not, iterate over next window(s) until slot found
-    while (burstTicks.count(burst_tick) >= dram->maxCmdsPerBst()) {
+    while (burstTicks.count(burst_tick) >= max_cmds_per_burst) {
         DPRINTF(DRAM, "Contention found on command bus at %d\n", burst_tick);
-        burst_tick += dram->burstDataDelay();
+        burst_tick += commandWindow;
         cmd_at = burst_tick;
     }
 
@@ -791,7 +920,8 @@ DRAMCtrl::verifySingleCmd(Tick cmd_tick)
 }
 
 Tick
-DRAMCtrl::verifyMultiCmd(Tick cmd_tick, Tick max_multi_cmd_split)
+DRAMCtrl::verifyMultiCmd(Tick cmd_tick, Tick max_cmds_per_burst,
+                         Tick max_multi_cmd_split)
 {
     // start with assumption that there is no contention on command bus
     Tick cmd_at = cmd_tick;
@@ -805,9 +935,9 @@ DRAMCtrl::verifyMultiCmd(Tick cmd_tick, Tick max_multi_cmd_split)
     // Given a maximum latency of max_multi_cmd_split between the commands,
     // find the burst at the maximum latency prior to cmd_at
     Tick burst_offset = 0;
-    Tick first_cmd_offset = cmd_tick % dram->burstDataDelay();
+    Tick first_cmd_offset = cmd_tick % commandWindow;
     while (max_multi_cmd_split > (first_cmd_offset + burst_offset)) {
-        burst_offset += dram->burstDataDelay();
+        burst_offset += commandWindow;
     }
     // get the earliest burst aligned address for first command
     // ensure that the time does not go negative
@@ -823,13 +953,13 @@ DRAMCtrl::verifyMultiCmd(Tick cmd_tick, Tick max_multi_cmd_split)
         auto second_cmd_count = same_burst ? first_cmd_count + 1 :
                                    burstTicks.count(burst_tick);
 
-        first_can_issue = first_cmd_count < dram->maxCmdsPerBst();
-        second_can_issue = second_cmd_count < dram->maxCmdsPerBst();
+        first_can_issue = first_cmd_count < max_cmds_per_burst;
+        second_can_issue = second_cmd_count < max_cmds_per_burst;
 
         if (!second_can_issue) {
             DPRINTF(DRAM, "Contention (cmd2) found on command bus at %d\n",
                     burst_tick);
-            burst_tick += dram->burstDataDelay();
+            burst_tick += commandWindow;
             cmd_at = burst_tick;
         }
 
@@ -842,7 +972,7 @@ DRAMCtrl::verifyMultiCmd(Tick cmd_tick, Tick max_multi_cmd_split)
         if (!first_can_issue || (!second_can_issue && gap_violated)) {
             DPRINTF(DRAM, "Contention (cmd1) found on command bus at %d\n",
                     first_cmd_tick);
-            first_cmd_tick += dram->burstDataDelay();
+            first_cmd_tick += commandWindow;
         }
     }
 
@@ -863,9 +993,9 @@ DRAMInterface::activateBank(Rank& rank_ref, Bank& bank_ref,
     // if not, shift to next burst window
     Tick act_at;
     if (twoCycleActivate)
-        act_at = ctrl->verifyMultiCmd(act_tick, tAAD);
+        act_at = ctrl->verifyMultiCmd(act_tick, maxCommandsPerWindow, tAAD);
     else
-        act_at = ctrl->verifySingleCmd(act_tick);
+        act_at = ctrl->verifySingleCmd(act_tick, maxCommandsPerWindow);
 
     DPRINTF(DRAM, "Activate at tick %d\n", act_at);
 
@@ -983,7 +1113,7 @@ DRAMInterface::prechargeBank(Rank& rank_ref, Bank& bank, Tick pre_tick,
         // Issuing an explicit PRE command
         // Verify that we have command bandwidth to issue the precharge
         // if not, shift to next burst window
-        pre_at = ctrl->verifySingleCmd(pre_tick);
+        pre_at = ctrl->verifySingleCmd(pre_tick, maxCommandsPerWindow);
         // enforce tPPD
         for (int i = 0; i < banksPerRank; i++) {
             rank_ref.banks[i].preAllowedAt = std::max(pre_at + tPPD,
@@ -1025,7 +1155,7 @@ DRAMInterface::prechargeBank(Rank& rank_ref, Bank& bank, Tick pre_tick,
     }
 }
 
-Tick
+pair<Tick, Tick>
 DRAMInterface::doBurstAccess(DRAMPacket* dram_pkt, Tick next_burst_at,
                              const std::vector<DRAMPacketQueue>& queue)
 {
@@ -1046,7 +1176,7 @@ DRAMInterface::doBurstAccess(DRAMPacket* dram_pkt, Tick next_burst_at,
     }
 
     // get the bank
-    Bank& bank_ref = dram_pkt->bankRef;
+    Bank& bank_ref = rank_ref.banks[dram_pkt->bank];
 
     // for the state we need to track if it is a row hit or not
     bool row_hit = true;
@@ -1082,9 +1212,9 @@ DRAMInterface::doBurstAccess(DRAMPacket* dram_pkt, Tick next_burst_at,
     // verify that we have command bandwidth to issue the burst
     // if not, shift to next burst window
     if (dataClockSync && ((cmd_at - rank_ref.lastBurstTick) > clkResyncDelay))
-        cmd_at = ctrl->verifyMultiCmd(cmd_at, tCK);
+        cmd_at = ctrl->verifyMultiCmd(cmd_at, maxCommandsPerWindow, tCK);
     else
-        cmd_at = ctrl->verifySingleCmd(cmd_at);
+        cmd_at = ctrl->verifySingleCmd(cmd_at, maxCommandsPerWindow);
 
     // if we are interleaving bursts, ensure that
     // 1) we don't double interleave on next burst issue
@@ -1131,15 +1261,17 @@ DRAMInterface::doBurstAccess(DRAMPacket* dram_pkt, Tick next_burst_at,
                 } else {
                     // tBURST is default requirement for diff BG timing
                     // Need to also take bus turnaround delays into account
-                    dly_to_rd_cmd = dram_pkt->isRead() ? burst_gap : wrToRdDly;
-                    dly_to_wr_cmd = dram_pkt->isRead() ? rdToWrDly : burst_gap;
+                    dly_to_rd_cmd = dram_pkt->isRead() ? burst_gap :
+                                                       writeToReadDelay();
+                    dly_to_wr_cmd = dram_pkt->isRead() ? readToWriteDelay() :
+                                                       burst_gap;
                 }
             } else {
                 // different rank is by default in a different bank group and
                 // doesn't require longer tCCD or additional RTW, WTR delays
                 // Need to account for rank-to-rank switching
-                dly_to_wr_cmd = rankToRankDly;
-                dly_to_rd_cmd = rankToRankDly;
+                dly_to_wr_cmd = rankToRankDelay();
+                dly_to_rd_cmd = rankToRankDelay();
             }
             ranks[j]->banks[i].rdAllowedAt = std::max(cmd_at + dly_to_rd_cmd,
                                              ranks[j]->banks[i].rdAllowedAt);
@@ -1281,7 +1413,24 @@ DRAMInterface::doBurstAccess(DRAMPacket* dram_pkt, Tick next_burst_at,
 
     }
     // Update bus state to reflect when previous command was issued
-    return (cmd_at + burst_gap);
+    return make_pair(cmd_at, cmd_at + burst_gap);
+}
+
+void
+DRAMInterface::addRankToRankDelay(Tick cmd_at)
+{
+    // update timing for DRAM ranks due to bursts issued
+    // to ranks on other media interfaces
+    for (auto n : ranks) {
+        for (int i = 0; i < banksPerRank; i++) {
+            // different rank by default
+            // Need to only account for rank-to-rank switching
+            n->banks[i].rdAllowedAt = std::max(cmd_at + rankToRankDelay(),
+                                             n->banks[i].rdAllowedAt);
+            n->banks[i].wrAllowedAt = std::max(cmd_at + rankToRankDelay(),
+                                             n->banks[i].wrAllowedAt);
+        }
+    }
 }
 
 bool
@@ -1317,9 +1466,29 @@ DRAMCtrl::doBurstAccess(DRAMPacket* dram_pkt)
     // before adding new entries for next burst
     pruneBurstTick();
 
-    // Update bus state to reflect when previous command was issued
-    std::vector<DRAMPacketQueue>& queue = selQueue(dram_pkt->isRead());
-    nextBurstAt = dram->doBurstAccess(dram_pkt, nextBurstAt, queue);
+    // When was command issued?
+    Tick cmd_at;
+
+    // Issue the next burst and update bus state to reflect
+    // when previous command was issued
+    if (dram_pkt->isDram()) {
+        std::vector<DRAMPacketQueue>& queue = selQueue(dram_pkt->isRead());
+        std::tie(cmd_at, nextBurstAt) =
+                 dram->doBurstAccess(dram_pkt, nextBurstAt, queue);
+
+        // Update timing for NVM ranks if NVM is configured on this channel
+        if (nvm)
+            nvm->addRankToRankDelay(cmd_at);
+
+    } else {
+        std::tie(cmd_at, nextBurstAt) =
+                 nvm->doBurstAccess(dram_pkt, nextBurstAt);
+
+        // Update timing for NVM ranks if NVM is configured on this channel
+        if (dram)
+            dram->addRankToRankDelay(cmd_at);
+
+    }
 
     DPRINTF(DRAM, "Access to %lld, ready at %lld next burst at %lld.\n",
             dram_pkt->addr, dram_pkt->readyTime, nextBurstAt);
@@ -1328,7 +1497,8 @@ DRAMCtrl::doBurstAccess(DRAMPacket* dram_pkt)
     // conservative estimate of when we have to schedule the next
     // request to not introduce any unecessary bubbles. In most cases
     // we will wake up sooner than we have to.
-    nextReqTime = nextBurstAt - dram->tRC();
+    nextReqTime = nextBurstAt - (dram ? dram->commandOffset() :
+                                        nvm->commandOffset());
 
 
     // Update the common bus stats
@@ -1383,9 +1553,33 @@ DRAMCtrl::processNextReqEvent()
     // updates current state
     busState = busStateNext;
 
-    // check ranks for refresh/wakeup - uses busStateNext, so done after turnaround
-    // decisions
-    if (dram->isBusy()) {
+    if (nvm) {
+        for (auto queue = readQueue.rbegin();
+             queue != readQueue.rend(); ++queue) {
+             // select non-deterministic NVM read to issue
+             // assume that we have the command bandwidth to issue this along
+             // with additional RD/WR burst with needed bank operations
+             if (nvm->readsWaitingToIssue()) {
+                 // select non-deterministic NVM read to issue
+                 nvm->chooseRead(*queue);
+             }
+        }
+    }
+
+    // check ranks for refresh/wakeup - uses busStateNext, so done after
+    // turnaround decisions
+    // Default to busy status and update based on interface specifics
+    bool dram_busy = dram ? dram->isBusy() : true;
+    bool nvm_busy = true;
+    bool all_writes_nvm = false;
+    if (nvm) {
+        all_writes_nvm = nvm->numWritesQueued == totalWriteQueueSize;
+        bool read_queue_empty = totalReadQueueSize == 0;
+        nvm_busy = nvm->isBusy(read_queue_empty, all_writes_nvm);
+    }
+    // Default state of unused interface is 'true'
+    // Simply AND the busy signals to determine if system is busy
+    if (dram_busy && nvm_busy) {
         // if all ranks are refreshing wait for them to finish
         // and stall this state machine without taking any further
         // action, and do not schedule a new nextReqEvent
@@ -1414,7 +1608,7 @@ DRAMCtrl::processNextReqEvent()
                 // ensuring all banks are closed and
                 // have exited low power states
                 if (drainState() == DrainState::Draining &&
-                    respQueue.empty() && dram->allRanksDrained()) {
+                    respQueue.empty() && allIntfDrained()) {
 
                     DPRINTF(Drain, "DRAM controller done draining\n");
                     signalDrainDone();
@@ -1443,7 +1637,7 @@ DRAMCtrl::processNextReqEvent()
                 // If we are changing command type, incorporate the minimum
                 // bus turnaround delay which will be rank to rank delay
                 to_read = chooseNext((*queue), switched_cmd_type ?
-                                               dram->rankDelay() : 0);
+                                               minWriteToReadDataGap() : 0);
 
                 if (to_read != queue->end()) {
                     // candidate read found
@@ -1467,7 +1661,9 @@ DRAMCtrl::processNextReqEvent()
             doBurstAccess(dram_pkt);
 
             // sanity check
-            assert(dram_pkt->size <= dram->bytesPerBurst());
+            assert(dram_pkt->size <= (dram_pkt->isDram() ?
+                                      dram->bytesPerBurst() :
+                                      nvm->bytesPerBurst()) );
             assert(dram_pkt->readyTime >= curTick());
 
             // log the response
@@ -1489,7 +1685,10 @@ DRAMCtrl::processNextReqEvent()
             respQueue.push_back(dram_pkt);
 
             // we have so many writes that we have to transition
-            if (totalWriteQueueSize > writeHighThreshold) {
+            // don't transition if the writeRespQueue is full and
+            // there are no other writes that can issue
+            if ((totalWriteQueueSize > writeHighThreshold) &&
+               !(nvm && all_writes_nvm && nvm->writeRespQueueFull())) {
                 switch_to_writes = true;
             }
 
@@ -1522,8 +1721,7 @@ DRAMCtrl::processNextReqEvent()
             // If we are changing command type, incorporate the minimum
             // bus turnaround delay
             to_write = chooseNext((*queue),
-                     switched_cmd_type ? std::min(dram->minRdToWr(),
-                                                  dram->rankDelay()) : 0);
+                     switched_cmd_type ? minReadToWriteDataGap() : 0);
 
             if (to_write != queue->end()) {
                 write_found = true;
@@ -1544,11 +1742,13 @@ DRAMCtrl::processNextReqEvent()
         auto dram_pkt = *to_write;
 
         // sanity check
-        assert(dram_pkt->size <= dram->bytesPerBurst());
+        assert(dram_pkt->size <= (dram_pkt->isDram() ?
+                                  dram->bytesPerBurst() :
+                                  nvm->bytesPerBurst()) );
 
         doBurstAccess(dram_pkt);
 
-        isInWriteQueue.erase(burstAlign(dram_pkt->addr));
+        isInWriteQueue.erase(burstAlign(dram_pkt->addr, dram_pkt->isDram()));
 
         // log the response
         logResponse(MemCtrl::WRITE, dram_pkt->masterId(),
@@ -1565,12 +1765,16 @@ DRAMCtrl::processNextReqEvent()
         // threshold (using the minWritesPerSwitch as the hysteresis) and
         // are not draining, or we have reads waiting and have done enough
         // writes, then switch to reads.
+        // If we are interfacing to NVM and have filled the writeRespQueue,
+        // with only NVM writes in Q, then switch to reads
         bool below_threshold =
             totalWriteQueueSize + minWritesPerSwitch < writeLowThreshold;
 
         if (totalWriteQueueSize == 0 ||
             (below_threshold && drainState() != DrainState::Draining) ||
-            (totalReadQueueSize && writesThisTime >= minWritesPerSwitch)) {
+            (totalReadQueueSize && writesThisTime >= minWritesPerSwitch) ||
+            (totalReadQueueSize && nvm && nvm->writeRespQueueFull() &&
+             all_writes_nvm)) {
 
             // turn the bus back around for reads again
             busStateNext = MemCtrl::READ;
@@ -1596,7 +1800,7 @@ DRAMCtrl::processNextReqEvent()
     }
 }
 
-DRAMInterface::DRAMInterface(const DRAMInterfaceParams* _p)
+MemInterface::MemInterface(const MemInterfaceParams* _p)
     : AbstractMemory(_p),
       addrMapping(_p->addr_mapping),
       burstSize((_p->devices_per_rank * _p->burst_length *
@@ -1605,30 +1809,41 @@ DRAMInterface::DRAMInterface(const DRAMInterfaceParams* _p)
       deviceRowBufferSize(_p->device_rowbuffer_size),
       devicesPerRank(_p->devices_per_rank),
       rowBufferSize(devicesPerRank * deviceRowBufferSize),
-      columnsPerRowBuffer(rowBufferSize / burstSize),
-      columnsPerStripe(range.interleaved() ?
-                       range.granularity() / burstSize : 1),
+      burstsPerRowBuffer(rowBufferSize / burstSize),
+      burstsPerStripe(range.interleaved() ?
+                      range.granularity() / burstSize : 1),
       ranksPerChannel(_p->ranks_per_channel),
+      banksPerRank(_p->banks_per_rank), rowsPerBank(0),
+      tCK(_p->tCK), tCS(_p->tCS), tBURST(_p->tBURST),
+      tRTW(_p->tRTW),
+      tWTR(_p->tWTR)
+{}
+
+void
+MemInterface::setCtrl(DRAMCtrl* _ctrl, unsigned int command_window)
+{
+    ctrl = _ctrl;
+    maxCommandsPerWindow = command_window / tCK;
+}
+
+DRAMInterface::DRAMInterface(const DRAMInterfaceParams* _p)
+    : MemInterface(_p),
       bankGroupsPerRank(_p->bank_groups_per_rank),
       bankGroupArch(_p->bank_groups_per_rank > 0),
-      banksPerRank(_p->banks_per_rank), rowsPerBank(0),
-      tCK(_p->tCK), tCS(_p->tCS), tCL(_p->tCL), tBURST(_p->tBURST),
-      tBURST_MIN(_p->tBURST_MIN), tBURST_MAX(_p->tBURST_MAX), tRTW(_p->tRTW),
+      tCL(_p->tCL),
+      tBURST_MIN(_p->tBURST_MIN), tBURST_MAX(_p->tBURST_MAX),
       tCCD_L_WR(_p->tCCD_L_WR), tCCD_L(_p->tCCD_L), tRCD(_p->tRCD),
       tRP(_p->tRP), tRAS(_p->tRAS), tWR(_p->tWR), tRTP(_p->tRTP),
       tRFC(_p->tRFC), tREFI(_p->tREFI), tRRD(_p->tRRD), tRRD_L(_p->tRRD_L),
       tPPD(_p->tPPD), tAAD(_p->tAAD),
       tXAW(_p->tXAW), tXP(_p->tXP), tXS(_p->tXS),
       clkResyncDelay(tCL + _p->tBURST_MAX),
-      maxCommandsPerBurst(_p->burst_length / _p->beats_per_clock),
       dataClockSync(_p->data_clock_sync),
       burstInterleave(tBURST != tBURST_MIN),
       twoCycleActivate(_p->two_cycle_activate),
       activationLimit(_p->activation_limit),
-      wrToRdDly(tCL + tBURST + _p->tWTR), rdToWrDly(tBURST + tRTW),
       wrToRdDlySameBG(tCL + _p->tBURST_MAX + _p->tWTR_L),
-      rdToWrDlySameBG(tRTW + _p->tBURST_MAX),
-      rankToRankDly(tCS + tBURST),
+      rdToWrDlySameBG(_p->tRTW + _p->tBURST_MAX),
       pageMgmt(_p->page_policy),
       maxAccessesPerRow(_p->max_accesses_per_row),
       timeStampOffset(0), activeRank(0),
@@ -1638,6 +1853,8 @@ DRAMInterface::DRAMInterface(const DRAMInterfaceParams* _p)
       readBufferSize(_p->read_buffer_size),
       writeBufferSize(_p->write_buffer_size)
 {
+    DPRINTF(DRAM, "Setting up DRAM Interface\n");
+
     fatal_if(!isPowerOf2(burstSize), "DRAM burst size %d is not allowed, "
              "must be a power of two\n", burstSize);
 
@@ -1667,8 +1884,8 @@ DRAMInterface::DRAMInterface(const DRAMInterfaceParams* _p)
              "address range assigned (%d Mbytes)\n", deviceCapacity,
              capacity / (1024 * 1024));
 
-    DPRINTF(DRAM, "Row buffer size %d bytes with %d columns per row buffer\n",
-            rowBufferSize, columnsPerRowBuffer);
+    DPRINTF(DRAM, "Row buffer size %d bytes with %d bursts per row buffer\n",
+            rowBufferSize, burstsPerRowBuffer);
 
     rowsPerBank = capacity / (rowBufferSize * banksPerRank * ranksPerChannel);
 
@@ -1736,7 +1953,7 @@ DRAMInterface::init()
             // columns in the DRAM, effectively placing some of the
             // lower-order column bits as the least-significant bits
             // of the address (above the ones denoting the burst size)
-            assert(columnsPerStripe >= 1);
+            assert(burstsPerStripe >= 1);
 
             // channel striping has to be done at a granularity that
             // is equal or larger to a cache line
@@ -1751,7 +1968,7 @@ DRAMInterface::init()
                       "as the row-buffer size\n", name());
             }
             // this is essentially the check above, so just to be sure
-            assert(columnsPerStripe <= columnsPerRowBuffer);
+            assert(burstsPerStripe <= burstsPerRowBuffer);
         }
     }
 }
@@ -1762,10 +1979,10 @@ DRAMInterface::startup()
     if (system()->isTimingMode()) {
         // timestamp offset should be in clock cycles for DRAMPower
         timeStampOffset = divCeil(curTick(), tCK);
-    }
 
-    for (auto r : ranks) {
-        r->startup(curTick() + tREFI - tRP);
+        for (auto r : ranks) {
+            r->startup(curTick() + tREFI - tRP);
+        }
     }
 }
 
@@ -1807,6 +2024,16 @@ DRAMInterface::isBusy()
     return (busy_ranks == ranksPerChannel);
 }
 
+void DRAMInterface::setupRank(const uint8_t rank, const bool is_read)
+{
+    // increment entry count of the rank based on packet type
+    if (is_read) {
+        ++ranks[rank]->readEntries;
+    } else {
+        ++ranks[rank]->writeEntries;
+    }
+}
+
 void
 DRAMInterface::respondEvent(uint8_t rank)
 {
@@ -1926,7 +2153,7 @@ DRAMInterface::minBankPrep(const DRAMPacketQueue& queue,
     // bank in question
     vector<bool> got_waiting(ranksPerChannel * banksPerRank, false);
     for (const auto& p : queue) {
-        if (ranks[p->rank]->inRefIdleState())
+        if (p->isDram() && ranks[p->rank]->inRefIdleState())
             got_waiting[p->bankId] = true;
     }
 
@@ -2798,8 +3025,8 @@ DRAMCtrl::CtrlStats::regStats()
     avgRdQLen.precision(2);
     avgWrQLen.precision(2);
 
-    readPktSize.init(ceilLog2(ctrl.dram->bytesPerBurst()) + 1);
-    writePktSize.init(ceilLog2(ctrl.dram->bytesPerBurst()) + 1);
+    readPktSize.init(ceilLog2(ctrl.system()->cacheLineSize()) + 1);
+    writePktSize.init(ceilLog2(ctrl.system()->cacheLineSize()) + 1);
 
     rdQLenPdf.init(ctrl.readBufferSize);
     wrQLenPdf.init(ctrl.writeBufferSize);
@@ -2967,7 +3194,7 @@ DRAMInterface::DRAMStats::regStats()
 
     avgRdBW = (bytesRead / 1000000) / simSeconds;
     avgWrBW = (bytesWritten / 1000000) / simSeconds;
-    peakBW = (SimClock::Frequency / dram.burstDataDelay()) *
+    peakBW = (SimClock::Frequency / dram.burstDelay()) *
               dram.bytesPerBurst() / 1000000;
 
     busUtil = (avgRdBW + avgWrBW) / peakBW * 100;
@@ -3037,8 +3264,16 @@ DRAMInterface::RankStats::preDumpStats()
 void
 DRAMCtrl::recvFunctional(PacketPtr pkt)
 {
-    // rely on the abstract memory
-    dram->functionalAccess(pkt);
+    if (dram && dram->getAddrRange().contains(pkt->getAddr())) {
+        // rely on the abstract memory
+        dram->functionalAccess(pkt);
+    } else if (nvm && nvm->getAddrRange().contains(pkt->getAddr())) {
+        // rely on the abstract memory
+        nvm->functionalAccess(pkt);
+   } else {
+        panic("Can't handle address range for packet %s\n",
+              pkt->print());
+   }
 }
 
 Port &
@@ -3051,13 +3286,24 @@ DRAMCtrl::getPort(const string &if_name, PortID idx)
     }
 }
 
+bool
+DRAMCtrl::allIntfDrained() const
+{
+   // ensure dram is in power down and refresh IDLE states
+   bool dram_drained = !dram || dram->allRanksDrained();
+   // No outstanding NVM writes
+   // All other queues verified as needed with calling logic
+   bool nvm_drained = !nvm || nvm->allRanksDrained();
+   return (dram_drained && nvm_drained);
+}
+
 DrainState
 DRAMCtrl::drain()
 {
     // if there is anything in any of our internal queues, keep track
     // of that as well
     if (!(!totalWriteQueueSize && !totalReadQueueSize && respQueue.empty() &&
-          dram->allRanksDrained())) {
+          allIntfDrained())) {
 
         DPRINTF(Drain, "DRAM controller not drained, write: %d, read: %d,"
                 " resp: %d\n", totalWriteQueueSize, totalReadQueueSize,
@@ -3069,7 +3315,8 @@ DRAMCtrl::drain()
             schedule(nextReqEvent, curTick());
         }
 
-        dram->drainRanks();
+        if (dram)
+            dram->drainRanks();
 
         return DrainState::Draining;
     } else {
@@ -3088,7 +3335,8 @@ DRAMCtrl::drainResume()
     } else if (isTimingMode && !system()->isTimingMode()) {
         // if we switch from timing mode, stop the refresh events to
         // not cause issues with KVM
-        dram->suspend();
+        if (dram)
+            dram->suspend();
     }
 
     // update the mode
@@ -3104,7 +3352,14 @@ AddrRangeList
 DRAMCtrl::MemoryPort::getAddrRanges() const
 {
     AddrRangeList ranges;
-    ranges.push_back(ctrl.dram->getAddrRange());
+    if (ctrl.dram) {
+        DPRINTF(DRAM, "Pushing DRAM ranges to port\n");
+        ranges.push_back(ctrl.dram->getAddrRange());
+    }
+    if (ctrl.nvm) {
+        DPRINTF(DRAM, "Pushing NVM ranges to port\n");
+        ranges.push_back(ctrl.nvm->getAddrRange());
+    }
     return ranges;
 }
 
@@ -3141,3 +3396,580 @@ DRAMCtrlParams::create()
 {
     return new DRAMCtrl(this);
 }
+
+NVMInterface::NVMInterface(const NVMInterfaceParams* _p)
+    : MemInterface(_p),
+      maxPendingWrites(_p->max_pending_writes),
+      maxPendingReads(_p->max_pending_reads),
+      twoCycleRdWr(_p->two_cycle_rdwr),
+      tREAD(_p->tREAD), tWRITE(_p->tWRITE), tSEND(_p->tSEND),
+      stats(*this),
+      writeRespondEvent([this]{ processWriteRespondEvent(); }, name()),
+      readReadyEvent([this]{ processReadReadyEvent(); }, name()),
+      nextReadAt(0), numPendingReads(0), numReadDataReady(0),
+      numReadsToIssue(0), numWritesQueued(0),
+      readBufferSize(_p->read_buffer_size),
+      writeBufferSize(_p->write_buffer_size)
+{
+    DPRINTF(NVM, "Setting up NVM Interface\n");
+
+    fatal_if(!isPowerOf2(burstSize), "NVM burst size %d is not allowed, "
+             "must be a power of two\n", burstSize);
+
+    // sanity check the ranks since we rely on bit slicing for the
+    // address decoding
+    fatal_if(!isPowerOf2(ranksPerChannel), "NVM rank count of %d is "
+             "not allowed, must be a power of two\n", ranksPerChannel);
+
+    for (int i =0; i < ranksPerChannel; i++) {
+        // Add NVM ranks to the system
+        DPRINTF(NVM, "Creating NVM rank %d \n", i);
+        Rank* rank = new Rank(_p, i, *this);
+        ranks.push_back(rank);
+    }
+
+    uint64_t capacity = ULL(1) << ceilLog2(AbstractMemory::size());
+
+    DPRINTF(NVM, "NVM capacity %lld (%lld) bytes\n", capacity,
+            AbstractMemory::size());
+
+    rowsPerBank = capacity / (rowBufferSize *
+                    banksPerRank * ranksPerChannel);
+
+}
+
+NVMInterface*
+NVMInterfaceParams::create()
+{
+    return new NVMInterface(this);
+}
+
+NVMInterface::Rank::Rank(const NVMInterfaceParams* _p,
+                         int _rank, NVMInterface& _nvm)
+    : EventManager(&_nvm), nvm(_nvm), rank(_rank),
+      banks(_p->banks_per_rank)
+{
+    for (int b = 0; b < _p->banks_per_rank; b++) {
+        banks[b].bank = b;
+        // No bank groups; simply assign to bank number
+        banks[b].bankgr = b;
+    }
+}
+
+void
+NVMInterface::init()
+{
+    AbstractMemory::init();
+}
+
+void NVMInterface::setupRank(const uint8_t rank, const bool is_read)
+{
+    if (is_read) {
+        // increment count to trigger read and track number of reads in Q
+        numReadsToIssue++;
+    } else {
+        // increment count to track number of writes in Q
+        numWritesQueued++;
+    }
+}
+
+pair<DRAMPacketQueue::iterator, Tick>
+NVMInterface::chooseNextFRFCFS(DRAMPacketQueue& queue, Tick min_col_at) const
+{
+    // remember if we found a hit, but one that cannit issue seamlessly
+    bool found_prepped_pkt = false;
+
+    auto selected_pkt_it = queue.end();
+    Tick selected_col_at = MaxTick;
+
+    for (auto i = queue.begin(); i != queue.end() ; ++i) {
+        DRAMPacket* pkt = *i;
+
+        // select optimal NVM packet in Q
+        if (!pkt->isDram()) {
+            const Bank& bank = ranks[pkt->rank]->banks[pkt->bank];
+            const Tick col_allowed_at = pkt->isRead() ? bank.rdAllowedAt :
+                                                        bank.wrAllowedAt;
+
+            // check if rank is not doing a refresh and thus is available,
+            // if not, jump to the next packet
+            if (burstReady(pkt)) {
+                DPRINTF(NVM, "%s bank %d - Rank %d available\n", __func__,
+                        pkt->bank, pkt->rank);
+
+                // no additional rank-to-rank or media delays
+                if (col_allowed_at <= min_col_at) {
+                    // FCFS within entries that can issue without
+                    // additional delay, such as same rank accesses
+                    // or media delay requirements
+                    selected_pkt_it = i;
+                    selected_col_at = col_allowed_at;
+                    // no need to look through the remaining queue entries
+                    DPRINTF(NVM, "%s Seamless buffer hit\n", __func__);
+                    break;
+                } else if (!found_prepped_pkt) {
+                    // packet is to prepped region but cannnot issue
+                    // seamlessly; remember this one and continue
+                    selected_pkt_it = i;
+                    selected_col_at = col_allowed_at;
+                    DPRINTF(NVM, "%s Prepped packet found \n", __func__);
+                    found_prepped_pkt = true;
+                }
+            } else {
+                DPRINTF(NVM, "%s bank %d - Rank %d not available\n", __func__,
+                        pkt->bank, pkt->rank);
+            }
+        }
+    }
+
+    if (selected_pkt_it == queue.end()) {
+        DPRINTF(NVM, "%s no available NVM ranks found\n", __func__);
+    }
+
+    return make_pair(selected_pkt_it, selected_col_at);
+}
+
+void
+NVMInterface::chooseRead(DRAMPacketQueue& queue)
+{
+    Tick cmd_at = std::max(curTick(), nextReadAt);
+
+    // This method does the arbitration between non-deterministic read
+    // requests to NVM. The chosen packet is not removed from the queue
+    // at this time. Removal from the queue will occur when the data is
+    // ready and a separate SEND command is issued to retrieve it via the
+    // chooseNext function in the top-level controller.
+    assert(!queue.empty());
+
+    assert(numReadsToIssue > 0);
+    numReadsToIssue--;
+    // For simplicity, issue non-deterministic reads in order (fcfs)
+    for (auto i = queue.begin(); i != queue.end() ; ++i) {
+        DRAMPacket* pkt = *i;
+
+        // Find 1st NVM read packet that hasn't issued read command
+        if (pkt->readyTime == MaxTick && !pkt->isDram() && pkt->isRead()) {
+           // get the bank
+           Bank& bank_ref = ranks[pkt->rank]->banks[pkt->bank];
+
+            // issueing a read, inc counter and verify we haven't overrun
+            numPendingReads++;
+            assert(numPendingReads <= maxPendingReads);
+
+            // increment the bytes accessed and the accesses per row
+            bank_ref.bytesAccessed += burstSize;
+
+            // Verify command bandiwth to issue
+            // Host can issue read immediately uith buffering closer
+            // to the NVM. The actual execution at the NVM may be delayed
+            // due to busy resources
+            if (twoCycleRdWr) {
+                cmd_at = ctrl->verifyMultiCmd(cmd_at,
+                                              maxCommandsPerWindow, tCK);
+            } else {
+                cmd_at = ctrl->verifySingleCmd(cmd_at,
+                                               maxCommandsPerWindow);
+            }
+
+            // Update delay to next read
+            // Ensures single read command issued per cycle
+            nextReadAt = cmd_at + tCK;
+
+            // If accessing a new location in this bank, update timing
+            // and stats
+            if (bank_ref.openRow != pkt->row) {
+                // update the open bank, re-using row field
+                bank_ref.openRow = pkt->row;
+
+                // sample the bytes accessed to a buffer in this bank
+                // here when we are re-buffering the data
+                stats.bytesPerBank.sample(bank_ref.bytesAccessed);
+                // start counting anew
+                bank_ref.bytesAccessed = 0;
+
+                // holdoff next command to this bank until the read completes
+                // and the data has been successfully buffered
+                // can pipeline accesses to the same bank, sending them
+                // across the interface B2B, but will incur full access
+                // delay between data ready responses to different buffers
+                // in a bank
+                bank_ref.actAllowedAt = std::max(cmd_at,
+                                        bank_ref.actAllowedAt) + tREAD;
+            }
+            // update per packet readyTime to holdoff burst read operation
+            // overloading readyTime, which will be updated again when the
+            // burst is issued
+            pkt->readyTime = std::max(cmd_at, bank_ref.actAllowedAt);
+
+            DPRINTF(NVM, "Issuing NVM Read to bank %d at tick %d. "
+                         "Data ready at %d\n",
+                         bank_ref.bank, cmd_at, pkt->readyTime);
+
+            // Insert into read ready queue. It will be handled after
+            // the media delay has been met
+            if (readReadyQueue.empty()) {
+                assert(!readReadyEvent.scheduled());
+                schedule(readReadyEvent, pkt->readyTime);
+            } else if (readReadyEvent.when() > pkt->readyTime) {
+                // move it sooner in time, to the first read with data
+                reschedule(readReadyEvent, pkt->readyTime);
+            } else {
+                assert(readReadyEvent.scheduled());
+            }
+            readReadyQueue.push_back(pkt->readyTime);
+
+            // found an NVM read to issue - break out
+            break;
+        }
+    }
+}
+
+void
+NVMInterface::processReadReadyEvent()
+{
+    // signal that there is read data ready to be transmitted
+    numReadDataReady++;
+
+    DPRINTF(NVM,
+            "processReadReadyEvent(): Data for an NVM read is ready. "
+            "numReadDataReady is %d\t numPendingReads is %d\n",
+             numReadDataReady, numPendingReads);
+
+    // Find lowest ready time and verify it is equal to curTick
+    // also find the next lowest to schedule next event
+    // Done with this response, erase entry
+    auto ready_it = readReadyQueue.begin();
+    Tick next_ready_at = MaxTick;
+    for (auto i = readReadyQueue.begin(); i != readReadyQueue.end() ; ++i) {
+        if (*ready_it > *i) {
+            next_ready_at = *ready_it;
+            ready_it = i;
+        } else if ((next_ready_at > *i) && (i != ready_it)) {
+            next_ready_at = *i;
+        }
+    }
+
+    // Verify we found the time of this event and remove it
+    assert(*ready_it == curTick());
+    readReadyQueue.erase(ready_it);
+
+    if (!readReadyQueue.empty()) {
+        assert(readReadyQueue.front() >= curTick());
+        assert(!readReadyEvent.scheduled());
+        schedule(readReadyEvent, next_ready_at);
+    }
+
+    // It is possible that a new command kicks things back into
+    // action before reaching this point but need to ensure that we
+    // continue to process new commands as read data becomes ready
+    // This will also trigger a drain if needed
+    if (!ctrl->requestEventScheduled()) {
+        DPRINTF(NVM, "Restart controller scheduler immediately\n");
+        ctrl->restartScheduler(curTick());
+    }
+}
+
+
+bool
+NVMInterface::burstReady(DRAMPacket* pkt) const {
+    bool read_rdy =  pkt->isRead() && (ctrl->inReadBusState(true)) &&
+               (pkt->readyTime <= curTick()) && (numReadDataReady > 0);
+    bool write_rdy =  !pkt->isRead() && !ctrl->inReadBusState(true) &&
+                !writeRespQueueFull();
+    return (read_rdy || write_rdy);
+}
+
+pair<Tick, Tick>
+NVMInterface::doBurstAccess(DRAMPacket* pkt, Tick next_burst_at)
+{
+    DPRINTF(NVM, "NVM Timing access to addr %lld, rank/bank/row %d %d %d\n",
+            pkt->addr, pkt->rank, pkt->bank, pkt->row);
+
+    // get the bank
+    Bank& bank_ref = ranks[pkt->rank]->banks[pkt->bank];
+
+    // respect any constraints on the command
+    const Tick bst_allowed_at = pkt->isRead() ?
+                                bank_ref.rdAllowedAt : bank_ref.wrAllowedAt;
+
+    // we need to wait until the bus is available before we can issue
+    // the command; need minimum of tBURST between commands
+    Tick cmd_at = std::max(bst_allowed_at, curTick());
+
+    // we need to wait until the bus is available before we can issue
+    // the command; need minimum of tBURST between commands
+    cmd_at = std::max(cmd_at, next_burst_at);
+
+    // Verify there is command bandwidth to issue
+    // Read burst (send command) is a simple data access and only requires
+    // one command cycle
+    // Write command may require multiple cycles to enable larger address space
+    if (pkt->isRead() || !twoCycleRdWr) {
+        cmd_at = ctrl->verifySingleCmd(cmd_at, maxCommandsPerWindow);
+    } else {
+        cmd_at = ctrl->verifyMultiCmd(cmd_at, maxCommandsPerWindow, tCK);
+    }
+    // update the packet ready time to reflect when data will be transferred
+    // Use the same bus delays defined for NVM
+    pkt->readyTime = cmd_at + tSEND + tBURST;
+
+    Tick dly_to_rd_cmd;
+    Tick dly_to_wr_cmd;
+    for (auto n : ranks) {
+        for (int i = 0; i < banksPerRank; i++) {
+            // base delay is a function of tBURST and bus turnaround
+            dly_to_rd_cmd = pkt->isRead() ? tBURST : writeToReadDelay();
+            dly_to_wr_cmd = pkt->isRead() ? readToWriteDelay() : tBURST;
+
+            if (pkt->rank != n->rank) {
+                // adjust timing for different ranks
+                // Need to account for rank-to-rank switching with tCS
+                dly_to_wr_cmd = rankToRankDelay();
+                dly_to_rd_cmd = rankToRankDelay();
+            }
+            n->banks[i].rdAllowedAt = std::max(cmd_at + dly_to_rd_cmd,
+                                      n->banks[i].rdAllowedAt);
+
+            n->banks[i].wrAllowedAt = std::max(cmd_at + dly_to_wr_cmd,
+                                      n->banks[i].wrAllowedAt);
+        }
+    }
+
+    DPRINTF(NVM, "NVM Access to %lld, ready at %lld.\n",
+            pkt->addr, pkt->readyTime);
+
+    if (pkt->isRead()) {
+        // completed the read, decrement counters
+        assert(numPendingReads != 0);
+        assert(numReadDataReady != 0);
+
+        numPendingReads--;
+        numReadDataReady--;
+    } else {
+        // Adjust number of NVM writes in Q
+        assert(numWritesQueued > 0);
+        numWritesQueued--;
+
+        // increment the bytes accessed and the accesses per row
+        // only increment for writes as the reads are handled when
+        // the non-deterministic read is issued, before the data transfer
+        bank_ref.bytesAccessed += burstSize;
+
+        // Commands will be issued serially when accessing the same bank
+        // Commands can issue in parallel to different banks
+        if ((bank_ref.bank == pkt->bank) &&
+            (bank_ref.openRow != pkt->row)) {
+           // update the open buffer, re-using row field
+           bank_ref.openRow = pkt->row;
+
+           // sample the bytes accessed to a buffer in this bank
+           // here when we are re-buffering the data
+           stats.bytesPerBank.sample(bank_ref.bytesAccessed);
+           // start counting anew
+           bank_ref.bytesAccessed = 0;
+        }
+
+        // Determine when write will actually complete, assuming it is
+        // scheduled to push to NVM immediately
+        // update actAllowedAt to serialize next command completion that
+        // accesses this bank; must wait until this write completes
+        // Data accesses to the same buffer in this bank
+        // can issue immediately after actAllowedAt expires, without
+        // waiting additional delay of tWRITE. Can revisit this
+        // assumption/simplification in the future.
+        bank_ref.actAllowedAt = std::max(pkt->readyTime,
+                                bank_ref.actAllowedAt) + tWRITE;
+
+        // Need to track number of outstanding writes to
+        // ensure 'buffer' on media controller does not overflow
+        assert(!writeRespQueueFull());
+
+        // Insert into write done queue. It will be handled after
+        // the media delay has been met
+        if (writeRespQueueEmpty()) {
+            assert(!writeRespondEvent.scheduled());
+            schedule(writeRespondEvent, bank_ref.actAllowedAt);
+        } else {
+            assert(writeRespondEvent.scheduled());
+        }
+        writeRespQueue.push_back(bank_ref.actAllowedAt);
+        writeRespQueue.sort();
+        if (writeRespondEvent.when() > bank_ref.actAllowedAt) {
+            DPRINTF(NVM, "Rescheduled respond event from %lld to %11d\n",
+                writeRespondEvent.when(), bank_ref.actAllowedAt);
+            DPRINTF(NVM, "Front of response queue is %11d\n",
+                writeRespQueue.front());
+            reschedule(writeRespondEvent, bank_ref.actAllowedAt);
+        }
+
+    }
+
+    // Update the stats
+    if (pkt->isRead()) {
+        stats.readBursts++;
+        stats.bytesRead += burstSize;
+        stats.perBankRdBursts[pkt->bankId]++;
+        stats.pendingReads.sample(numPendingReads);
+
+        // Update latency stats
+        stats.totMemAccLat += pkt->readyTime - pkt->entryTime;
+        stats.totBusLat += tBURST;
+        stats.totQLat += cmd_at - pkt->entryTime;
+    } else {
+        stats.writeBursts++;
+        stats.bytesWritten += burstSize;
+        stats.perBankWrBursts[pkt->bankId]++;
+    }
+
+    return make_pair(cmd_at, cmd_at + tBURST);
+}
+
+void
+NVMInterface::processWriteRespondEvent()
+{
+    DPRINTF(NVM,
+            "processWriteRespondEvent(): A NVM write reached its readyTime.  "
+            "%d remaining pending NVM writes\n", writeRespQueue.size());
+
+    // Update stat to track histogram of pending writes
+    stats.pendingWrites.sample(writeRespQueue.size());
+
+    // Done with this response, pop entry
+    writeRespQueue.pop_front();
+
+    if (!writeRespQueue.empty()) {
+        assert(writeRespQueue.front() >= curTick());
+        assert(!writeRespondEvent.scheduled());
+        schedule(writeRespondEvent, writeRespQueue.front());
+    }
+
+    // It is possible that a new command kicks things back into
+    // action before reaching this point but need to ensure that we
+    // continue to process new commands as writes complete at the media and
+    // credits become available. This will also trigger a drain if needed
+    if (!ctrl->requestEventScheduled()) {
+        DPRINTF(NVM, "Restart controller scheduler immediately\n");
+        ctrl->restartScheduler(curTick());
+    }
+}
+
+void
+NVMInterface::addRankToRankDelay(Tick cmd_at)
+{
+    // update timing for NVM ranks due to bursts issued
+    // to ranks for other media interfaces
+    for (auto n : ranks) {
+        for (int i = 0; i < banksPerRank; i++) {
+            // different rank by default
+            // Need to only account for rank-to-rank switching
+            n->banks[i].rdAllowedAt = std::max(cmd_at + rankToRankDelay(),
+                                             n->banks[i].rdAllowedAt);
+            n->banks[i].wrAllowedAt = std::max(cmd_at + rankToRankDelay(),
+                                             n->banks[i].wrAllowedAt);
+        }
+    }
+}
+
+bool
+NVMInterface::isBusy(bool read_queue_empty, bool all_writes_nvm)
+{
+     DPRINTF(NVM,"isBusy: numReadDataReady = %d\n", numReadDataReady);
+     // Determine NVM is busy and cannot issue a burst
+     // A read burst cannot issue when data is not ready from the NVM
+     // Also check that we have reads queued to ensure we can change
+     // bus direction to service potential write commands.
+     // A write cannot issue once we've reached MAX pending writes
+     // Only assert busy for the write case when there are also
+     // no reads in Q and the write queue only contains NVM commands
+     // This allows the bus state to switch and service reads
+     return (ctrl->inReadBusState(true) ?
+                 (numReadDataReady == 0) && !read_queue_empty :
+                 writeRespQueueFull() && read_queue_empty &&
+                                         all_writes_nvm);
+}
+
+
+NVMInterface::NVMStats::NVMStats(NVMInterface &_nvm)
+    : Stats::Group(&_nvm),
+    nvm(_nvm),
+
+    ADD_STAT(readBursts, "Number of NVM read bursts"),
+    ADD_STAT(writeBursts, "Number of NVM write bursts"),
+
+    ADD_STAT(perBankRdBursts, "Per bank write bursts"),
+    ADD_STAT(perBankWrBursts, "Per bank write bursts"),
+
+    ADD_STAT(totQLat, "Total ticks spent queuing"),
+    ADD_STAT(totBusLat, "Total ticks spent in databus transfers"),
+    ADD_STAT(totMemAccLat,
+             "Total ticks spent from burst creation until serviced "
+             "by the NVM"),
+    ADD_STAT(avgQLat, "Average queueing delay per NVM burst"),
+    ADD_STAT(avgBusLat, "Average bus latency per NVM burst"),
+    ADD_STAT(avgMemAccLat, "Average memory access latency per NVM burst"),
+
+    ADD_STAT(bytesRead, "Total number of bytes read from DRAM"),
+    ADD_STAT(bytesWritten, "Total number of bytes written to DRAM"),
+    ADD_STAT(avgRdBW, "Average DRAM read bandwidth in MiBytes/s"),
+    ADD_STAT(avgWrBW, "Average DRAM write bandwidth in MiBytes/s"),
+    ADD_STAT(peakBW, "Theoretical peak bandwidth in MiByte/s"),
+    ADD_STAT(busUtil, "NVM Data bus utilization in percentage"),
+    ADD_STAT(busUtilRead, "NVM Data bus read utilization in percentage"),
+    ADD_STAT(busUtilWrite, "NVM Data bus write utilization in percentage"),
+
+    ADD_STAT(pendingReads, "Reads issued to NVM for which data has not been "
+             "transferred"),
+    ADD_STAT(pendingWrites, "Number of outstanding writes to NVM"),
+    ADD_STAT(bytesPerBank, "Bytes read within a bank before loading "
+             "new bank")
+{
+}
+
+void
+NVMInterface::NVMStats::regStats()
+{
+    using namespace Stats;
+
+    perBankRdBursts.init(nvm.ranksPerChannel == 0 ? 1 :
+              nvm.banksPerRank * nvm.ranksPerChannel);
+
+    perBankWrBursts.init(nvm.ranksPerChannel == 0 ? 1 :
+              nvm.banksPerRank * nvm.ranksPerChannel);
+
+    avgQLat.precision(2);
+    avgBusLat.precision(2);
+    avgMemAccLat.precision(2);
+
+    avgRdBW.precision(2);
+    avgWrBW.precision(2);
+    peakBW.precision(2);
+
+    busUtil.precision(2);
+    busUtilRead.precision(2);
+    busUtilWrite.precision(2);
+
+    pendingReads
+        .init(nvm.maxPendingReads)
+        .flags(nozero);
+
+    pendingWrites
+        .init(nvm.maxPendingWrites)
+        .flags(nozero);
+
+    bytesPerBank
+        .init(nvm.rowBufferSize)
+        .flags(nozero);
+
+    avgQLat = totQLat / readBursts;
+    avgBusLat = totBusLat / readBursts;
+    avgMemAccLat = totMemAccLat / readBursts;
+
+    avgRdBW = (bytesRead / 1000000) / simSeconds;
+    avgWrBW = (bytesWritten / 1000000) / simSeconds;
+    peakBW = (SimClock::Frequency / nvm.tBURST) *
+              nvm.burstSize / 1000000;
+
+    busUtil = (avgRdBW + avgWrBW) / peakBW * 100;
+    busUtilRead = avgRdBW / peakBW * 100;
+    busUtilWrite = avgWrBW / peakBW * 100;
+}
index 417e93554c844586f124cf3ceffadd9dc45ee970..7fc499f0d7af23029e5a835fa7b440205ece1c6b 100644 (file)
@@ -49,6 +49,7 @@
 #include <deque>
 #include <string>
 #include <unordered_set>
+#include <utility>
 #include <vector>
 
 #include "base/statistics.hh"
 #include "mem/qos/mem_ctrl.hh"
 #include "mem/qport.hh"
 #include "params/DRAMCtrl.hh"
+#include "params/DRAMInterface.hh"
+#include "params/MemInterface.hh"
+#include "params/NVMInterface.hh"
 #include "sim/eventq.hh"
 
 class DRAMInterfaceParams;
-
-/**
- * A basic class to track the bank state, i.e. what row is
- * currently open (if any), when is the bank free to accept a new
- * column (read/write) command, when can it be precharged, and
- * when can it be activated.
- *
- * The bank also keeps track of how many bytes have been accessed
- * in the open row since it was opened.
- */
-class Bank
-{
-
-  public:
-
-    static const uint32_t NO_ROW = -1;
-
-    uint32_t openRow;
-    uint8_t bank;
-    uint8_t bankgr;
-
-    Tick rdAllowedAt;
-    Tick wrAllowedAt;
-    Tick preAllowedAt;
-    Tick actAllowedAt;
-
-    uint32_t rowAccesses;
-    uint32_t bytesAccessed;
-
-    Bank() :
-        openRow(NO_ROW), bank(0), bankgr(0),
-        rdAllowedAt(0), wrAllowedAt(0), preAllowedAt(0), actAllowedAt(0),
-        rowAccesses(0), bytesAccessed(0)
-    { }
-};
+class NVMInterfaceParams;
 
 /**
  * A burst helper helps organize and manage a packet that is larger than
@@ -142,6 +112,9 @@ class DRAMPacket
 
     const bool read;
 
+    /** Does this packet access DRAM?*/
+    const bool dram;
+
     /** Will be populated by address decoder */
     const uint8_t rank;
     const uint8_t bank;
@@ -173,7 +146,6 @@ class DRAMPacket
      * If not a split packet (common case), this is set to NULL
      */
     BurstHelper* burstHelper;
-    Bank& bankRef;
 
     /**
      * QoS value of the encapsulated packet read at queuing time
@@ -222,15 +194,19 @@ class DRAMPacket
      */
     inline bool isWrite() const { return !read; }
 
+    /**
+     * Return true if its a DRAM access
+     */
+    inline bool isDram() const { return dram; }
 
-    DRAMPacket(PacketPtr _pkt, bool is_read, uint8_t _rank, uint8_t _bank,
-               uint32_t _row, uint16_t bank_id, Addr _addr,
-               unsigned int _size, Bank& bank_ref)
+    DRAMPacket(PacketPtr _pkt, bool is_read, bool is_dram, uint8_t _rank,
+               uint8_t _bank, uint32_t _row, uint16_t bank_id, Addr _addr,
+               unsigned int _size)
         : entryTime(curTick()), readyTime(curTick()), pkt(_pkt),
           _masterId(pkt->masterId()),
-          read(is_read), rank(_rank), bank(_bank), row(_row),
+          read(is_read), dram(is_dram), rank(_rank), bank(_bank), row(_row),
           bankId(bank_id), addr(_addr), size(_size), burstHelper(NULL),
-          bankRef(bank_ref), _qosValue(_pkt->qosValue())
+          _qosValue(_pkt->qosValue())
     { }
 
 };
@@ -239,13 +215,234 @@ class DRAMPacket
 // based on their QoS priority
 typedef std::deque<DRAMPacket*> DRAMPacketQueue;
 
+
+/**
+ * General interface to memory device
+ * Includes functions and parameters shared across media types
+ */
+class MemInterface : public AbstractMemory
+{
+  protected:
+    /**
+     * A basic class to track the bank state, i.e. what row is
+     * currently open (if any), when is the bank free to accept a new
+     * column (read/write) command, when can it be precharged, and
+     * when can it be activated.
+     *
+     * The bank also keeps track of how many bytes have been accessed
+     * in the open row since it was opened.
+     */
+    class Bank
+    {
+
+      public:
+
+        static const uint32_t NO_ROW = -1;
+
+        uint32_t openRow;
+        uint8_t bank;
+        uint8_t bankgr;
+
+        Tick rdAllowedAt;
+        Tick wrAllowedAt;
+        Tick preAllowedAt;
+        Tick actAllowedAt;
+
+        uint32_t rowAccesses;
+        uint32_t bytesAccessed;
+
+        Bank() :
+            openRow(NO_ROW), bank(0), bankgr(0),
+            rdAllowedAt(0), wrAllowedAt(0), preAllowedAt(0), actAllowedAt(0),
+            rowAccesses(0), bytesAccessed(0)
+        { }
+    };
+
+    /**
+     * A pointer to the parent DRAMCtrl instance
+     */
+    DRAMCtrl* ctrl;
+
+    /**
+     * Number of commands that can issue in the defined controller
+     * command window, used to verify command bandwidth
+     */
+    unsigned int maxCommandsPerWindow;
+
+    /**
+     * Memory controller configuration initialized based on parameter
+     * values.
+     */
+    Enums::AddrMap addrMapping;
+
+    /**
+     * General device and channel characteristics
+     * The rowsPerBank is determined based on the capacity, number of
+     * ranks and banks, the burst size, and the row buffer size.
+     */
+    const uint32_t burstSize;
+    const uint32_t deviceSize;
+    const uint32_t deviceRowBufferSize;
+    const uint32_t devicesPerRank;
+    const uint32_t rowBufferSize;
+    const uint32_t burstsPerRowBuffer;
+    const uint32_t burstsPerStripe;
+    const uint32_t ranksPerChannel;
+    const uint32_t banksPerRank;
+    uint32_t rowsPerBank;
+
+    /**
+     * General timing requirements
+     */
+    const Tick M5_CLASS_VAR_USED tCK;
+    const Tick tCS;
+    const Tick tBURST;
+    const Tick tRTW;
+    const Tick tWTR;
+
+    /*
+     * @return delay between write and read commands
+     */
+    virtual Tick writeToReadDelay() const { return tBURST + tWTR; }
+
+    /*
+     * @return delay between write and read commands
+     */
+    Tick readToWriteDelay() const { return tBURST + tRTW; }
+
+    /*
+     * @return delay between accesses to different ranks
+     */
+    Tick rankToRankDelay() const { return tBURST + tCS; }
+
+
+  public:
+    /** Set a pointer to the controller and initialize
+     * interface based on controller parameters
+     * @param _ctrl pointer to the parent controller
+     * @param command_window size of command window used to
+     *                       check command bandwidth
+     */
+    void setCtrl(DRAMCtrl* _ctrl, unsigned int command_window);
+
+    /**
+     * Get an address in a dense range which starts from 0. The input
+     * address is the physical address of the request in an address
+     * space that contains other SimObjects apart from this
+     * controller.
+     *
+     * @param addr The intput address which should be in the addrRange
+     * @return An address in the continues range [0, max)
+     */
+    Addr getCtrlAddr(Addr addr) { return range.getOffset(addr); }
+
+    /**
+     * Setup the rank based on packet received
+     *
+     * @param integer value of rank to be setup. used to index ranks vector
+     * @param are we setting up rank for read or write packet?
+     */
+    virtual void setupRank(const uint8_t rank, const bool is_read) = 0;
+
+    /**
+     * Check drain state of interface
+     *
+     * @return true if all ranks are drained and idle
+     *
+     */
+    virtual bool allRanksDrained() const = 0;
+
+    /**
+     * For FR-FCFS policy, find first command that can issue
+     * Function will be overriden by interface to select based
+     * on media characteristics, used to determine when read
+     * or write can issue.
+     *
+     * @param queue Queued requests to consider
+     * @param min_col_at Minimum tick for 'seamless' issue
+     * @return an iterator to the selected packet, else queue.end()
+     * @return the tick when the packet selected will issue
+     */
+    virtual std::pair<DRAMPacketQueue::iterator, Tick>
+    chooseNextFRFCFS(DRAMPacketQueue& queue, Tick min_col_at) const = 0;
+
+    /*
+     * Function to calulate unloaded latency
+     */
+    virtual Tick accessLatency() const = 0;
+
+    /**
+     * @return number of bytes in a burst for this interface
+     */
+    uint32_t bytesPerBurst() const { return burstSize; }
+
+    /*
+     * @return time to offset next command
+     */
+    virtual Tick commandOffset() const = 0;
+
+    /**
+     * Check if a burst operation can be issued to the interface
+     *
+     * @param Return true if RD/WR can issue
+     */
+    virtual bool burstReady(DRAMPacket* pkt) const = 0;
+
+    /**
+     * Determine the required delay for an access to a different rank
+     *
+     * @return required rank to rank delay
+     */
+    Tick rankDelay() const { return tCS; }
+
+    /**
+     *
+     * @return minimum additional bus turnaround required for read-to-write
+     */
+    Tick minReadToWriteDataGap() const { return std::min(tRTW, tCS); }
+
+    /**
+     *
+     * @return minimum additional bus turnaround required for write-to-read
+     */
+    Tick minWriteToReadDataGap() const { return std::min(tWTR, tCS); }
+
+    /**
+     * Address decoder to figure out physical mapping onto ranks,
+     * banks, and rows. This function is called multiple times on the same
+     * system packet if the pakcet is larger than burst of the memory. The
+     * pkt_addr is used for the offset within the packet.
+     *
+     * @param pkt The packet from the outside world
+     * @param pkt_addr The starting address of the DRAM packet
+     * @param size The size of the DRAM packet in bytes
+     * @param is_read Is the request for a read or a write to memory
+     * @param is_dram Is the request to a DRAM interface
+     * @return A DRAMPacket pointer with the decoded information
+     */
+    DRAMPacket* decodePacket(const PacketPtr pkt, Addr pkt_addr,
+                           unsigned int size, bool is_read, bool is_dram);
+
+    /**
+     *  Add rank to rank delay to bus timing to all banks in all ranks
+     *  when access to an alternate interface is issued
+     *
+     *  param cmd_at Time of current command used as starting point for
+     *               addition of rank-to-rank delay
+     */
+    virtual void addRankToRankDelay(Tick cmd_at) = 0;
+
+    typedef MemInterfaceParams Params;
+    MemInterface(const Params* _p);
+};
+
 /**
  * Interface to DRAM devices with media specific parameters,
  * statistics, and functions.
  * The DRAMInterface includes a class for individual ranks
  * and per rank functions.
  */
-class DRAMInterface : public AbstractMemory
+class DRAMInterface : public MemInterface
 {
   private:
     /**
@@ -662,45 +859,17 @@ class DRAMInterface : public AbstractMemory
     }
 
     /**
-     * A pointer to the parent DRAMCtrl instance
-     */
-    DRAMCtrl* ctrl;
-
-    /**
-     * Memory controller configuration initialized based on parameter
-     * values.
+     * DRAM specific device characteristics
      */
-    Enums::AddrMap addrMapping;
-
-
-    /**
-     * DRAM device and channel characteristics
-     * The rowsPerBank is determined based on the capacity, number of
-     * ranks and banks, the burst size, and the row buffer size.
-     */
-    const uint32_t burstSize;
-    const uint32_t deviceSize;
-    const uint32_t deviceRowBufferSize;
-    const uint32_t devicesPerRank;
-    const uint32_t rowBufferSize;
-    const uint32_t columnsPerRowBuffer;
-    const uint32_t columnsPerStripe;
-    const uint32_t ranksPerChannel;
     const uint32_t bankGroupsPerRank;
     const bool bankGroupArch;
-    const uint32_t banksPerRank;
-    uint32_t rowsPerBank;
 
     /**
-     * DRAM timing requirements
+     * DRAM specific timing requirements
      */
-    const Tick M5_CLASS_VAR_USED tCK;
-    const Tick tCS;
     const Tick tCL;
-    const Tick tBURST;
     const Tick tBURST_MIN;
     const Tick tBURST_MAX;
-    const Tick tRTW;
     const Tick tCCD_L_WR;
     const Tick tCCD_L;
     const Tick tRCD;
@@ -718,16 +887,12 @@ class DRAMInterface : public AbstractMemory
     const Tick tXP;
     const Tick tXS;
     const Tick clkResyncDelay;
-    unsigned int maxCommandsPerBurst;
     const bool dataClockSync;
     const bool burstInterleave;
     const uint8_t twoCycleActivate;
     const uint32_t activationLimit;
-    const Tick wrToRdDly;
-    const Tick rdToWrDly;
     const Tick wrToRdDlySameBG;
     const Tick rdToWrDlySameBG;
-    const Tick rankToRankDly;
 
     Enums::PageManage pageMgmt;
     /**
@@ -843,9 +1008,34 @@ class DRAMInterface : public AbstractMemory
     const uint32_t readBufferSize;
     const uint32_t writeBufferSize;
 
-    /** Setting a pointer to the controller */
-    void setCtrl(DRAMCtrl* _ctrl) { ctrl = _ctrl; }
+    /*
+     * @return delay between write and read commands
+     */
+    Tick writeToReadDelay() const override { return tBURST + tWTR + tCL; }
 
+    /**
+     * Find which are the earliest banks ready to issue an activate
+     * for the enqueued requests. Assumes maximum of 32 banks per rank
+     * Also checks if the bank is already prepped.
+     *
+     * @param queue Queued requests to consider
+     * @param min_col_at time of seamless burst command
+     * @return One-hot encoded mask of bank indices
+     * @return boolean indicating burst can issue seamlessly, with no gaps
+     */
+    std::pair<std::vector<uint32_t>, bool>
+    minBankPrep(const DRAMPacketQueue& queue, Tick min_col_at) const;
+
+    /*
+     * @return time to send a burst of data without gaps
+     */
+    Tick
+    burstDelay() const
+    {
+        return (burstInterleave ? tBURST_MAX / 2 : tBURST);
+    }
+
+  public:
     /**
      * Initialize the DRAM interface and verify parameters
      */
@@ -856,6 +1046,14 @@ class DRAMInterface : public AbstractMemory
      */
     void startup() override;
 
+    /**
+     * Setup the rank based on packet received
+     *
+     * @param integer value of rank to be setup. used to index ranks vector
+     * @param are we setting up rank for read or write packet?
+     */
+    void setupRank(const uint8_t rank, const bool is_read) override;
+
     /**
      * Iterate through dram ranks to exit self-refresh in order to drain
      */
@@ -870,92 +1068,33 @@ class DRAMInterface : public AbstractMemory
      * @return true if all ranks have refreshed, with no commands enqueued
      *
      */
-    bool allRanksDrained() const;
+    bool allRanksDrained() const override;
 
     /**
      * Iterate through DRAM ranks and suspend them
      */
     void suspend();
 
-    /**
-     * Get an address in a dense range which starts from 0. The input
-     * address is the physical address of the request in an address
-     * space that contains other SimObjects apart from this
-     * controller.
-     *
-     * @param addr The intput address which should be in the addrRange
-     * @return An address in the continues range [0, max)
-     */
-    Addr getCtrlAddr(Addr addr) { return range.getOffset(addr); }
-
-    /**
-     * @return number of bytes in a burst for this interface
-     */
-    uint32_t bytesPerBurst() const { return burstSize; }
-
-    /**
-     *
-     * @return number of ranks per channel for this interface
-     */
-    uint32_t numRanks() const { return ranksPerChannel; }
-
     /*
-     * @return time to send a burst of data
+     * @return time to offset next command
      */
-    Tick burstDelay() const { return tBURST; }
-
-    /*
-     * @return time to send a burst of data without gaps
-     */
-    Tick
-    burstDataDelay() const
-    {
-        return (burstInterleave ? tBURST_MAX / 2 : tBURST);
-    }
-
-    /*
-     * @return Maximum number of commands that can issue per burst
-     */
-    Tick maxCmdsPerBst() const { return maxCommandsPerBurst; }
-
-    /**
-     *
-     * @return additional bus turnaround required for read-to-write
-     */
-    Tick minRdToWr() const { return tRTW; }
-
-    /**
-     * Determine the required delay for an access to a different rank
-     *
-     * @return required rank to rank delay
-     */
-    Tick rankDelay() const { return tCS; }
-
-    /*
-     * Function to calulate RAS cycle time for use within and
-     * outside of this class
-     */
-    Tick tRC() const { return (tRP + tRCD); }
+    Tick commandOffset() const override { return (tRP + tRCD); }
 
     /*
      * Function to calulate unloaded, closed bank access latency
      */
-    Tick accessLatency() const { return (tRP + tRCD + tCL); }
+    Tick accessLatency() const override { return (tRP + tRCD + tCL); }
 
     /**
-     * Address decoder to figure out physical mapping onto ranks,
-     * banks, and rows. This function is called multiple times on the same
-     * system packet if the pakcet is larger than burst of the memory. The
-     * dramPktAddr is used for the offset within the packet.
+     * For FR-FCFS policy, find first DRAM command that can issue
      *
-     * @param pkt The packet from the outside world
-     * @param dramPktAddr The starting address of the DRAM packet
-     * @param size The size of the DRAM packet in bytes
-     * @param isRead Is the request for a read or a write to DRAM
-     * @return A DRAMPacket pointer with the decoded information
+     * @param queue Queued requests to consider
+     * @param min_col_at Minimum tick for 'seamless' issue
+     * @return an iterator to the selected packet, else queue.end()
+     * @return the tick when the packet selected will issue
      */
-    DRAMPacket* decodePacket(const PacketPtr pkt, Addr dramPktAddr,
-                           unsigned int size, bool isRead) const;
+    std::pair<DRAMPacketQueue::iterator, Tick>
+    chooseNextFRFCFS(DRAMPacketQueue& queue, Tick min_col_at) const override;
 
     /**
      * Actually do the burst - figure out the latency it
@@ -968,23 +1107,12 @@ class DRAMInterface : public AbstractMemory
      * @param dram_pkt The DRAM packet created from the outside world pkt
      * @param next_burst_at Minimum bus timing requirement from controller
      * @param queue Reference to the read or write queue with the packet
-     * @return tick when burst is issued
+     * @return pair, tick when current burst is issued and
+     *               tick when next burst can issue
      */
-    Tick doBurstAccess(DRAMPacket* dram_pkt, Tick next_burst_at,
-                             const std::vector<DRAMPacketQueue>& queue);
-
-    /**
-     * Find which are the earliest banks ready to issue an activate
-     * for the enqueued requests. Assumes maximum of 32 banks per rank
-     * Also checks if the bank is already prepped.
-     *
-     * @param queue Queued requests to consider
-     * @param min_col_at time of seamless burst command
-     * @return One-hot encoded mask of bank indices
-     * @return boolean indicating burst can issue seamlessly, with no gaps
-     */
-    std::pair<std::vector<uint32_t>, bool>
-    minBankPrep(const DRAMPacketQueue& queue, Tick min_col_at) const;
+    std::pair<Tick, Tick>
+    doBurstAccess(DRAMPacket* dram_pkt, Tick next_burst_at,
+                  const std::vector<DRAMPacketQueue>& queue);
 
     /**
      * Check if a burst operation can be issued to the DRAM
@@ -994,9 +1122,9 @@ class DRAMInterface : public AbstractMemory
      *                    REF IDLE state
      */
     bool
-    burstReady(uint8_t rank) const
+    burstReady(DRAMPacket* pkt) const override
     {
-        return ranks[rank]->inRefIdleState();
+        return ranks[pkt->rank]->inRefIdleState();
     }
 
     /**
@@ -1009,6 +1137,15 @@ class DRAMInterface : public AbstractMemory
      */
     bool isBusy();
 
+    /**
+     *  Add rank to rank delay to bus timing to all DRAM banks in alli ranks
+     *  when access to an alternate interface is issued
+     *
+     *  param cmd_at Time of current command used as starting point for
+     *               addition of rank-to-rank delay
+     */
+    void addRankToRankDelay(Tick cmd_at) override;
+
     /**
      * Complete response process for DRAM when read burst is complete
      * This will update the counters and check if a power down state
@@ -1029,6 +1166,270 @@ class DRAMInterface : public AbstractMemory
     DRAMInterface(const DRAMInterfaceParams* _p);
 };
 
+/**
+ * Interface to NVM devices with media specific parameters,
+ * statistics, and functions.
+ * The NVMInterface includes a class for individual ranks
+ * and per rank functions.
+ */
+class NVMInterface : public MemInterface
+{
+  private:
+    /**
+     * NVM rank class simply includes a vector of banks.
+     */
+    class Rank : public EventManager
+    {
+      private:
+
+        /**
+         * A reference to the parent NVMInterface instance
+         */
+        NVMInterface& nvm;
+
+      public:
+
+        /**
+         * Current Rank index
+         */
+        uint8_t rank;
+
+        /**
+         * Vector of NVM banks. Each rank is made of several banks
+         * that can be accessed in parallel.
+         */
+        std::vector<Bank> banks;
+
+        Rank(const NVMInterfaceParams* _p, int _rank,
+             NVMInterface& _nvm);
+    };
+
+    /**
+     * NVM specific device and channel characteristics
+     */
+    const uint32_t maxPendingWrites;
+    const uint32_t maxPendingReads;
+    const bool twoCycleRdWr;
+
+    /**
+     * NVM specific timing requirements
+     */
+    const Tick tREAD;
+    const Tick tWRITE;
+    const Tick tSEND;
+
+    struct NVMStats : public Stats::Group
+    {
+        NVMStats(NVMInterface &nvm);
+
+        void regStats() override;
+
+        NVMInterface &nvm;
+
+        /** NVM stats */
+        Stats::Scalar readBursts;
+        Stats::Scalar writeBursts;
+
+        Stats::Vector perBankRdBursts;
+        Stats::Vector perBankWrBursts;
+
+        // Latencies summed over all requests
+        Stats::Scalar totQLat;
+        Stats::Scalar totBusLat;
+        Stats::Scalar totMemAccLat;
+
+        // Average latencies per request
+        Stats::Formula avgQLat;
+        Stats::Formula avgBusLat;
+        Stats::Formula avgMemAccLat;
+
+        Stats::Scalar bytesRead;
+        Stats::Scalar bytesWritten;
+
+        // Average bandwidth
+        Stats::Formula avgRdBW;
+        Stats::Formula avgWrBW;
+        Stats::Formula peakBW;
+        Stats::Formula busUtil;
+        Stats::Formula busUtilRead;
+        Stats::Formula busUtilWrite;
+
+        /** NVM stats */
+        Stats::Histogram pendingReads;
+        Stats::Histogram pendingWrites;
+        Stats::Histogram bytesPerBank;
+    };
+
+    NVMStats stats;
+
+    void processWriteRespondEvent();
+    EventFunctionWrapper writeRespondEvent;
+
+    void processReadReadyEvent();
+    EventFunctionWrapper readReadyEvent;
+
+    /**
+      * Vector of nvm ranks
+      */
+    std::vector<Rank*> ranks;
+
+    /**
+     * Holding queue for non-deterministic write commands, which
+     * maintains writes that have been issued but have not completed
+     * Stored seperately mostly to keep the code clean and help with
+     * events scheduling.
+     * This mimics a buffer on the media controller and therefore is
+     * not added to the main write queue for sizing
+     */
+    std::list<Tick> writeRespQueue;
+
+    std::deque<Tick> readReadyQueue;
+
+    /**
+     * Check if the write response queue is empty
+     *
+     * @param Return true if empty
+     */
+    bool writeRespQueueEmpty() const { return writeRespQueue.empty(); }
+
+    /**
+     * Till when must we wait before issuing next read command?
+     */
+    Tick nextReadAt;
+
+    // keep track of reads that have issued for which data is either
+    // not yet ready or has not yet been transferred to the ctrl
+    uint16_t numPendingReads;
+    uint16_t numReadDataReady;
+
+  public:
+    // keep track of the number of reads that have yet to be issued
+    uint16_t numReadsToIssue;
+
+    // number of writes in the writeQueue for the NVM interface
+    uint32_t numWritesQueued;
+
+    /**
+      * Buffer sizes for read and write queues in the controller
+      * These are passed to the controller on instantiation
+      * Defining them here allows for buffers to be resized based
+      * on memory type / configuration.
+      */
+    const uint32_t readBufferSize;
+    const uint32_t writeBufferSize;
+
+    /**
+     * Initialize the NVM interface and verify parameters
+     */
+    void init() override;
+
+    /**
+     * Setup the rank based on packet received
+     *
+     * @param integer value of rank to be setup. used to index ranks vector
+     * @param are we setting up rank for read or write packet?
+     */
+    void setupRank(const uint8_t rank, const bool is_read) override;
+
+    /**
+     * Check drain state of NVM interface
+     *
+     * @return true if write response queue is empty
+     *
+     */
+    bool allRanksDrained() const override { return writeRespQueueEmpty(); }
+
+    /*
+     * @return time to offset next command
+     */
+    Tick commandOffset() const override { return tBURST; }
+
+    /**
+     * Check if a burst operation can be issued to the NVM
+     *
+     * @param Return true if RD/WR can issue
+     *                    for reads, also verfy that ready count
+     *                    has been updated to a non-zero value to
+     *                    account for race conditions between events
+     */
+    bool burstReady(DRAMPacket* pkt) const override;
+
+    /**
+     * This function checks if ranks are busy.
+     * This state is true when either:
+     * 1) There is no command with read data ready to transmit or
+     * 2) The NVM inteface has reached the maximum number of outstanding
+     *    writes commands.
+     * @param read_queue_empty There are no read queued
+     * @param all_writes_nvm   All writes in queue are for NVM interface
+     * @return true of NVM is busy
+     *
+     */
+    bool isBusy(bool read_queue_empty, bool all_writes_nvm);
+    /**
+     * For FR-FCFS policy, find first NVM command that can issue
+     * default to first command to prepped region
+     *
+     * @param queue Queued requests to consider
+     * @param min_col_at Minimum tick for 'seamless' issue
+     * @return an iterator to the selected packet, else queue.end()
+     * @return the tick when the packet selected will issue
+     */
+    std::pair<DRAMPacketQueue::iterator, Tick>
+    chooseNextFRFCFS(DRAMPacketQueue& queue, Tick min_col_at) const override;
+
+    /**
+     *  Add rank to rank delay to bus timing to all NVM banks in alli ranks
+     *  when access to an alternate interface is issued
+     *
+     *  param cmd_at Time of current command used as starting point for
+     *               addition of rank-to-rank delay
+     */
+    void addRankToRankDelay(Tick cmd_at) override;
+
+
+    /**
+     * Select read command to issue asynchronously
+     */
+    void chooseRead(DRAMPacketQueue& queue);
+
+    /*
+     * Function to calulate unloaded access latency
+     */
+    Tick accessLatency() const override { return (tREAD + tSEND); }
+
+    /**
+     * Check if the write response queue has reached defined threshold
+     *
+     * @param Return true if full
+     */
+    bool
+    writeRespQueueFull() const
+    {
+        return writeRespQueue.size() == maxPendingWrites;
+    }
+
+    bool
+    readsWaitingToIssue() const
+    {
+        return ((numReadsToIssue != 0) &&
+                (numPendingReads < maxPendingReads));
+    }
+
+    /**
+     * Actually do the burst and update stats.
+     *
+     * @param pkt The packet created from the outside world pkt
+     * @param next_burst_at Minimum bus timing requirement from controller
+     * @return pair, tick when current burst is issued and
+     *               tick when next burst can issue
+     */
+    std::pair<Tick, Tick>
+    doBurstAccess(DRAMPacket* pkt, Tick next_burst_at);
+
+    NVMInterface(const NVMInterfaceParams* _p);
+};
+
 /**
  * The DRAM controller is a single-channel memory controller capturing
  * the most important timing constraints associated with a
@@ -1056,7 +1457,6 @@ class DRAMInterface : public AbstractMemory
  */
 class DRAMCtrl : public QoS::MemCtrl
 {
-
   private:
 
     // For now, make use of a queued slave port to avoid dealing with
@@ -1115,18 +1515,18 @@ class DRAMCtrl : public QoS::MemCtrl
     /**
      * Check if the read queue has room for more entries
      *
-     * @param pktCount The number of entries needed in the read queue
+     * @param pkt_count The number of entries needed in the read queue
      * @return true if read queue is full, false otherwise
      */
-    bool readQueueFull(unsigned int pktCount) const;
+    bool readQueueFull(unsigned int pkt_count) const;
 
     /**
      * Check if the write queue has room for more entries
      *
-     * @param pktCount The number of entries needed in the write queue
+     * @param pkt_count The number of entries needed in the write queue
      * @return true if write queue is full, false otherwise
      */
-    bool writeQueueFull(unsigned int pktCount) const;
+    bool writeQueueFull(unsigned int pkt_count) const;
 
     /**
      * When a new read comes in, first check if the write q has a
@@ -1138,11 +1538,12 @@ class DRAMCtrl : public QoS::MemCtrl
      * servicing it.
      *
      * @param pkt The request packet from the outside world
-     * @param pktCount The number of DRAM bursts the pkt
+     * @param pkt_count The number of DRAM bursts the pkt
+     * @param is_dram Does this packet access DRAM?
      * translate to. If pkt size is larger then one full burst,
-     * then pktCount is greater than one.
+     * then pkt_count is greater than one.
      */
-    void addToReadQueue(PacketPtr pkt, unsigned int pktCount);
+    void addToReadQueue(PacketPtr pkt, unsigned int pkt_count, bool is_dram);
 
     /**
      * Decode the incoming pkt, create a dram_pkt and push to the
@@ -1151,11 +1552,12 @@ class DRAMCtrl : public QoS::MemCtrl
      * to get full, stop reads, and start draining writes.
      *
      * @param pkt The request packet from the outside world
-     * @param pktCount The number of DRAM bursts the pkt
+     * @param pkt_count The number of DRAM bursts the pkt
+     * @param is_dram Does this packet access DRAM?
      * translate to. If pkt size is larger then one full burst,
-     * then pktCount is greater than one.
+     * then pkt_count is greater than one.
      */
-    void addToWriteQueue(PacketPtr pkt, unsigned int pktCount);
+    void addToWriteQueue(PacketPtr pkt, unsigned int pkt_count, bool is_dram);
 
     /**
      * Actually do the burst based on media specific access function.
@@ -1176,6 +1578,44 @@ class DRAMCtrl : public QoS::MemCtrl
      */
     void accessAndRespond(PacketPtr pkt, Tick static_latency);
 
+    /**
+     * Determine if there is a packet that can issue.
+     *
+     * @param pkt The packet to evaluate
+     */
+    bool
+    packetReady(DRAMPacket* pkt)
+    {
+        return (pkt->isDram() ?
+            dram->burstReady(pkt) : nvm->burstReady(pkt));
+    }
+
+    /**
+     * Calculate the minimum delay used when scheduling a read-to-write
+     * transision.
+     * @param return minimum delay
+     */
+    Tick
+    minReadToWriteDataGap()
+    {
+        Tick dram_min = dram ?  dram->minReadToWriteDataGap() : MaxTick;
+        Tick nvm_min = nvm ?  nvm->minReadToWriteDataGap() : MaxTick;
+        return std::min(dram_min, nvm_min);
+    }
+
+    /**
+     * Calculate the minimum delay used when scheduling a write-to-read
+     * transision.
+     * @param return minimum delay
+     */
+    Tick
+    minWriteToReadDataGap()
+    {
+        Tick dram_min = dram ? dram->minWriteToReadDataGap() : MaxTick;
+        Tick nvm_min = nvm ?  nvm->minWriteToReadDataGap() : MaxTick;
+        return std::min(dram_min, nvm_min);
+    }
+
     /**
      * The memory schduler/arbiter - picks which request needs to
      * go next, based on the specified policy such as FCFS or FR-FCFS
@@ -1218,12 +1658,17 @@ class DRAMCtrl : public QoS::MemCtrl
      * Burst-align an address.
      *
      * @param addr The potentially unaligned address
+     * @param is_dram Does this packet access DRAM?
      *
-     * @return An address aligned to a DRAM burst
+     * @return An address aligned to a memory burst
      */
-    Addr burstAlign(Addr addr) const
+    Addr
+    burstAlign(Addr addr, bool is_dram) const
     {
-        return (addr & ~(Addr(dram->bytesPerBurst() - 1)));
+        if (is_dram)
+            return (addr & ~(Addr(dram->bytesPerBurst() - 1)));
+        else
+            return (addr & ~(Addr(nvm->bytesPerBurst() - 1)));
     }
 
     /**
@@ -1259,10 +1704,15 @@ class DRAMCtrl : public QoS::MemCtrl
     std::unordered_multiset<Tick> burstTicks;
 
     /**
-     * Create pointer to interface of the actual dram media
+     * Create pointer to interface of the actual dram media when connected
      */
     DRAMInterface* const dram;
 
+    /**
+     * Create pointer to interface of the actual nvm media when connected
+     */
+    NVMInterface* const nvm;
+
     /**
      * The following are basic design parameters of the memory
      * controller, and are initialized based on parameter values.
@@ -1297,6 +1747,12 @@ class DRAMCtrl : public QoS::MemCtrl
      */
     const Tick backendLatency;
 
+    /**
+     * Length of a command window, used to check
+     * command bandwidth
+     */
+    const Tick commandWindow;
+
     /**
      * Till when must we wait before issuing next RD/WR burst?
      */
@@ -1386,7 +1842,8 @@ class DRAMCtrl : public QoS::MemCtrl
      * @param is_read The current burst is a read, select read queue
      * @return a reference to the appropriate queue
      */
-    std::vector<DRAMPacketQueue>& selQueue(bool is_read)
+    std::vector<DRAMPacketQueue>&
+    selQueue(bool is_read)
     {
         return (is_read ? readQueue : writeQueue);
     };
@@ -1397,36 +1854,49 @@ class DRAMCtrl : public QoS::MemCtrl
     void pruneBurstTick();
 
   public:
+
     DRAMCtrl(const DRAMCtrlParams* p);
 
+    /**
+     * Ensure that all interfaced have drained commands
+     *
+     * @return bool flag, set once drain complete
+     */
+    bool allIntfDrained() const;
+
     DrainState drain() override;
 
     /**
      * Check for command bus contention for single cycle command.
      * If there is contention, shift command to next burst.
      * Check verifies that the commands issued per burst is less
-     * than a defined max number, maxCommandsPerBurst.
+     * than a defined max number, maxCommandsPerWindow.
      * Therefore, contention per cycle is not verified and instead
      * is done based on a burst window.
      *
      * @param cmd_tick Initial tick of command, to be verified
+     * @param max_cmds_per_burst Number of commands that can issue
+     *                           in a burst window
      * @return tick for command issue without contention
      */
-    Tick verifySingleCmd(Tick cmd_tick);
+    Tick verifySingleCmd(Tick cmd_tick, Tick max_cmds_per_burst);
 
     /**
      * Check for command bus contention for multi-cycle (2 currently)
      * command. If there is contention, shift command(s) to next burst.
      * Check verifies that the commands issued per burst is less
-     * than a defined max number, maxCommandsPerBurst.
+     * than a defined max number, maxCommandsPerWindow.
      * Therefore, contention per cycle is not verified and instead
      * is done based on a burst window.
      *
      * @param cmd_tick Initial tick of command, to be verified
      * @param max_multi_cmd_split Maximum delay between commands
+     * @param max_cmds_per_burst Number of commands that can issue
+     *                           in a burst window
      * @return tick for command issue without contention
      */
-    Tick verifyMultiCmd(Tick cmd_tick, Tick max_multi_cmd_split = 0);
+    Tick verifyMultiCmd(Tick cmd_tick, Tick max_cmds_per_burst,
+                        Tick max_multi_cmd_split = 0);
 
     /**
      * Is there a respondEvent scheduled?