# Copyright (c) 2015-2018 Advanced Micro Devices, Inc.
# All rights reserved.
#
# For use for simulation and test purposes only
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. 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.
#
# 3. Neither the name of the copyright holder 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 HOLDER 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.defines import buildEnv
from m5.params import *
from m5.proxy import *
from m5.SimObject import SimObject

from m5.objects.Bridge import Bridge
from m5.objects.ClockedObject import ClockedObject
from m5.objects.Device import DmaDevice
from m5.objects.LdsState import LdsState
from m5.objects.Process import EmulatedDriver

class PrefetchType(Enum): vals = [
    'PF_CU',
    'PF_PHASE',
    'PF_WF',
    'PF_STRIDE',
    'PF_END',
    ]

class GfxVersion(ScopedEnum): vals = [
    'gfx801',
    'gfx803',
    'gfx900',
    'gfx902',
    ]

class PoolManager(SimObject):
    type = 'PoolManager'
    abstract = True
    cxx_class = 'gem5::PoolManager'
    cxx_header = "gpu-compute/pool_manager.hh"

    min_alloc = Param.Int(4, 'min number of VGPRs allocated per WF')
    pool_size = Param.Int(2048, 'number of vector registers per SIMD')

# The simple pool manage only allows one workgroup to
# be executing on a CU at any given time.
class SimplePoolManager(PoolManager):
    type = 'SimplePoolManager'
    cxx_class = 'gem5::SimplePoolManager'
    cxx_header = "gpu-compute/simple_pool_manager.hh"

## This is for allowing multiple workgroups on one CU
class DynPoolManager(PoolManager):
    type = 'DynPoolManager'
    cxx_class = 'gem5::DynPoolManager'
    cxx_header = "gpu-compute/dyn_pool_manager.hh"

class RegisterFile(SimObject):
    type = 'RegisterFile'
    cxx_class = 'gem5::RegisterFile'
    cxx_header = 'gpu-compute/register_file.hh'

    simd_id = Param.Int(-1, 'SIMD ID associated with this Register File')
    num_regs = Param.Int(2048, 'number of registers in this RF')
    wf_size = Param.Int(64, 'Wavefront size (in work items)')

class ScalarRegisterFile(RegisterFile):
    type = 'ScalarRegisterFile'
    cxx_class = 'gem5::ScalarRegisterFile'
    cxx_header = 'gpu-compute/scalar_register_file.hh'

class VectorRegisterFile(RegisterFile):
    type = 'VectorRegisterFile'
    cxx_class = 'gem5::VectorRegisterFile'
    cxx_header = 'gpu-compute/vector_register_file.hh'

class RegisterManager(SimObject):
    type = 'RegisterManager'
    cxx_class = 'gem5::RegisterManager'
    cxx_header = 'gpu-compute/register_manager.hh'

    policy = Param.String("static", "Register Manager Policy")
    vrf_pool_managers = VectorParam.PoolManager('VRF Pool Managers')
    srf_pool_managers = VectorParam.PoolManager('SRF Pool Managers')

class Wavefront(SimObject):
    type = 'Wavefront'
    cxx_class = 'gem5::Wavefront'
    cxx_header = 'gpu-compute/wavefront.hh'

    simdId = Param.Int('SIMD id (0-ComputeUnit.num_SIMDs)')
    wf_slot_id = Param.Int('wavefront id (0-ComputeUnit.max_wfs)')
    wf_size = Param.Int(64, 'Wavefront size (in work items)')
    max_ib_size = Param.Int(13, 'Maximum size (in number of insts) of the '
                            'instruction buffer (IB).')

# Most of the default values here are obtained from the
# AMD Graphics Core Next (GCN) Architecture whitepaper.
class ComputeUnit(ClockedObject):
    type = 'ComputeUnit'
    cxx_class = 'gem5::ComputeUnit'
    cxx_header = 'gpu-compute/compute_unit.hh'

    wavefronts = VectorParam.Wavefront('Number of wavefronts')
    # Wavefront size is 64. This is configurable, however changing
    # this value to anything other than 64 will likely cause errors.
    wf_size = Param.Int(64, 'Wavefront size (in work items)')
    num_barrier_slots = Param.Int(4, 'Number of barrier slots in a CU')
    num_SIMDs = Param.Int(4, 'number of SIMD units per CU')
    num_scalar_cores = Param.Int(1, 'number of Scalar cores per CU')
    num_scalar_mem_pipes = Param.Int(1, 'number of Scalar memory pipelines '\
                                     'per CU')
    simd_width = Param.Int(16, 'width (number of lanes) per SIMD unit')

    operand_network_length = Param.Int(1, 'number of pipe stages of operand '\
                                          'network')

    spbypass_pipe_length = Param.Int(4, 'vector ALU Single Precision bypass '\
                                        'latency')

    dpbypass_pipe_length = Param.Int(4, 'vector ALU Double Precision bypass '\
                                        'latency')
    scalar_pipe_length = Param.Int(1, 'number of pipe stages per scalar ALU')
    issue_period = Param.Int(4, 'number of cycles per issue period')

    vrf_gm_bus_latency = Param.Int(1, 'number of cycles per use of VRF to '\
                                      'GM bus')
    srf_scm_bus_latency = Param.Int(1, 'number of cycles per use of SRF '\
                                       'to Scalar Mem bus')
    vrf_lm_bus_latency = Param.Int(1, 'number of cycles per use of VRF to '\
                                      'LM bus')

    num_global_mem_pipes = Param.Int(1,'number of global memory pipes per CU')
    num_shared_mem_pipes = Param.Int(1,'number of shared memory pipes per CU')
    n_wf = Param.Int(10, 'Number of wavefront slots per SIMD')
    mem_req_latency = Param.Int(50, "Latency for request from the cu to ruby. "\
                                "Represents the pipeline to reach the TCP "\
                                "and specified in GPU clock cycles")
    mem_resp_latency = Param.Int(50, "Latency for responses from ruby to the "\
                                 "cu. Represents the pipeline between the "\
                                 "TCP and cu as well as TCP data array "\
                                 "access. Specified in GPU clock cycles")
    system = Param.System(Parent.any, "system object")
    cu_id = Param.Int('CU id')
    vrf_to_coalescer_bus_width = Param.Int(64, "VRF->Coalescer data bus "\
                                           "width in bytes")
    coalescer_to_vrf_bus_width = Param.Int(64, "Coalescer->VRF data bus "\
                                           "width  in bytes")

    memory_port = VectorRequestPort("Port to the memory system")
    translation_port = VectorRequestPort('Port to the TLB hierarchy')
    sqc_port = RequestPort("Port to the SQC (I-cache")
    sqc_tlb_port = RequestPort("Port to the TLB for the SQC (I-cache)")
    scalar_port = RequestPort("Port to the scalar data cache")
    scalar_tlb_port = RequestPort("Port to the TLB for the scalar data cache")
    gmTokenPort = RequestPort("Port to the GPU coalesecer for sharing tokens")

    perLaneTLB = Param.Bool(False, "enable per-lane TLB")
    prefetch_depth = Param.Int(0, "Number of prefetches triggered at a time"\
                               "(0 turns off prefetching)")
    prefetch_stride = Param.Int(1, "Fixed Prefetch Stride (1 means next-page)")
    prefetch_prev_type = Param.PrefetchType('PF_PHASE', "Prefetch the stride "\
                                            "from last mem req in lane of "\
                                            "CU|Phase|Wavefront")
    execPolicy = Param.String("OLDEST-FIRST", "WF execution selection policy");
    debugSegFault = Param.Bool(False, "enable debugging GPU seg faults")
    functionalTLB = Param.Bool(False, "Assume TLB causes no delay")

    localMemBarrier = Param.Bool(False, "Assume Barriers do not wait on "\
                                        "kernel end")

    countPages = Param.Bool(False, "Generate per-CU file of all pages "\
                            "touched and how many times")
    scalar_mem_queue_size = Param.Int(32, "Number of entries in scalar "\
                                      "memory pipeline's queues")
    global_mem_queue_size = Param.Int(256, "Number of entries in the global "
                                      "memory pipeline's queues")
    local_mem_queue_size = Param.Int(256, "Number of entries in the local "
                                      "memory pipeline's queues")
    max_wave_requests = Param.Int(64, "number of pending vector memory "\
                                      "requests per wavefront")
    max_cu_tokens = Param.Int(4, "Maximum number of tokens, i.e., the number"\
                            " of instructions that can be sent to coalescer")
    ldsBus = Bridge() # the bridge between the CU and its LDS
    ldsPort = RequestPort("The port that goes to the LDS")
    localDataStore = Param.LdsState("the LDS for this CU")

    vector_register_file = VectorParam.VectorRegisterFile("Vector register "\
                                                          "file")

    scalar_register_file = VectorParam.ScalarRegisterFile("Scalar register "\
                                                          "file")
    out_of_order_data_delivery = Param.Bool(False, "enable OoO data delivery"
                                            " in the GM pipeline")
    register_manager = Param.RegisterManager("Register Manager")
    fetch_depth = Param.Int(2, 'number of i-cache lines that may be '
                            'buffered in the fetch unit.')

class Shader(ClockedObject):
    type = 'Shader'
    cxx_class = 'gem5::Shader'
    cxx_header = 'gpu-compute/shader.hh'
    CUs = VectorParam.ComputeUnit('Number of compute units')
    gpu_cmd_proc = Param.GPUCommandProcessor('Command processor for GPU')
    dispatcher = Param.GPUDispatcher('GPU workgroup dispatcher')
    n_wf = Param.Int(10, 'Number of wavefront slots per SIMD')
    impl_kern_launch_acq = Param.Bool(True, """Insert acq packet into
                                         ruby at kernel launch""")
    impl_kern_end_rel = Param.Bool(False, """Insert rel packet into
                                         ruby at kernel end""")
    globalmem = Param.MemorySize('64kB', 'Memory size')
    timing = Param.Bool(False, 'timing memory accesses')

    cpu_pointer = Param.BaseCPU(NULL, "pointer to base CPU")
    translation = Param.Bool(False, "address translation");
    timer_period = Param.Clock('10us', "system timer period")
    idlecu_timeout = Param.Tick(0, "Idle CU watchdog timeout threshold")
    max_valu_insts = Param.Int(0, "Maximum vALU insts before exiting")

class GPUComputeDriver(EmulatedDriver):
    type = 'GPUComputeDriver'
    cxx_class = 'gem5::GPUComputeDriver'
    cxx_header = 'gpu-compute/gpu_compute_driver.hh'
    device = Param.GPUCommandProcessor('GPU controlled by this driver')
    isdGPU = Param.Bool(False, 'Driver is for a dGPU')
    gfxVersion = Param.GfxVersion('gfx801', 'ISA of gpu to model')
    dGPUPoolID = Param.Int(0, 'Pool ID for dGPU.')
    # Default Mtype for caches
    #--     1   1   1   C_RW_S  (Cached-ReadWrite-Shared)
    #--     1   1   0   C_RW_US (Cached-ReadWrite-Unshared)
    #--     1   0   1   C_RO_S  (Cached-ReadOnly-Shared)
    #--     1   0   0   C_RO_US (Cached-ReadOnly-Unshared)
    #--     0   1   x   UC_L2   (Uncached_GL2)
    #--     0   0   x   UC_All  (Uncached_All_Load)
    # default value: 5/C_RO_S (only allow caching in GL2 for read. Shared)
    m_type = Param.Int("Default MTYPE for cache. Valid values between 0-7");

class GPURenderDriver(EmulatedDriver):
    type = 'GPURenderDriver'
    cxx_class = 'gem5::GPURenderDriver'
    cxx_header = 'gpu-compute/gpu_render_driver.hh'

class GPUDispatcher(SimObject):
    type = 'GPUDispatcher'
    cxx_class = 'gem5::GPUDispatcher'
    cxx_header = 'gpu-compute/dispatcher.hh'

class GPUCommandProcessor(DmaDevice):
    type = 'GPUCommandProcessor'
    cxx_class = 'gem5::GPUCommandProcessor'
    cxx_header = 'gpu-compute/gpu_command_processor.hh'
    dispatcher = Param.GPUDispatcher('workgroup dispatcher for the GPU')

    hsapp = Param.HSAPacketProcessor('PP attached to this device')

class StorageClassType(Enum): vals = [
    'SC_SPILL',
    'SC_GLOBAL',
    'SC_GROUP',
    'SC_PRIVATE',
    'SC_READONLY',
    'SC_KERNARG',
    'SC_ARG',
    'SC_NONE',
    ]
