# --------------------------------------------------------------
# This script invokes the gem5 simulator. It is written to be used
# with the Fail* framework. For further information, see
# http://www.m5sim.org/Simulation_Scripts_Explained
# This script is based on $FAIL/simulators/gem5/configs/example/fs.py
#
# Author: Adrian Böckenkamp
#

# --------------------------------------------------------------
# Required Python modules:
import optparse
import sys

import m5 # required by gem5
from m5.objects import * # dito
from m5.util import addToPath, fatal # some helpers

addToPath('../common') # to find the next modules:
from FSConfig import *
import Simulation # for setWorkCountOptions(), run()
import CacheConfig
import Options # for parsing the command line

# --------------------------------------------------------------
# Configuration phase:
parser = optparse.OptionParser() # TODO: functionality useful?
Options.addCommonOptions(parser) # TODO: remove unused options
Options.addFSOptions(parser) # TODO: dito

(options, args) = parser.parse_args()

if args:
    print "Error: script doesn't take any positional arguments"
    sys.exit(1)
print "[Fail*] Welcome to the FULL SYSTEM simulation script for gem5!"

# Sets up the low-level system parameter:
class FailArmSystem(System):
    type = 'ArmSystem'
    load_addr_mask = 0xffffffff
    # 0x35 Implementor is '5' from "M5"
    # 0x0 Variant
    # 0xf Architecture from CPUID scheme
    # 0xc00 Primary part number ("c" or higher implies ARM v7)
    # 0x0 Revision
    midr_regval = Param.UInt32(0x350fc000, "MIDR value")
    multi_proc = Param.Bool(True, "Multiprocessor system?")
    boot_loader = Param.String("", "File that contains the boot loader code if any")
    gic_cpu_addr = Param.Addr(0, "Addres of the GIC CPU interface")
    flags_addr = Param.Addr(0, "Address of the flags register for MP booting")

# Creates gem5-related (Python) system objects (hierarchy):
def makeFailArmSystem(mem_mode, machine_type):
    assert machine_type

    self = FailArmSystem()

    # generic system
    mdesc = SysConfig()

    self.readfile = mdesc.script()
    self.iobus = NoncoherentBus()
    self.membus = MemBus()
    self.membus.badaddr_responder.warn_access = "warn"
    self.bridge = Bridge(delay='50ns', nack_delay='4ns')
    self.bridge.master = self.iobus.slave
    self.bridge.slave = self.membus.master

    self.mem_mode = mem_mode

    if machine_type == "RealView_PBX":
        self.realview = RealViewPBX()
    elif machine_type == "RealView_EB":
        self.realview = RealViewEB()
    elif machine_type == "VExpress_ELT":
        self.realview = VExpress_ELT()
    elif machine_type == "VExpress_EMM":
        self.realview = VExpress_EMM()
        self.load_addr_mask = 0xffffffff
    else:
        print "Unknown Machine Type"
        sys.exit(1)

    # No disk image / IDE controller interface stuff.

    # EOT character on UART will end the simulation
    self.realview.uart.end_on_eot = True
    self.physmem = SimpleMemory(range = AddrRange(Addr(mdesc.mem())),
                                zero = True)
    self.physmem.port = self.membus.master
    self.realview.attachOnChipIO(self.membus, self.bridge)
    self.realview.attachIO(self.iobus)
    self.intrctrl = IntrControl()
    self.terminal = Terminal()
    self.vncserver = VncServer()

    self.system_port = self.membus.slave

    return self

# Driver system CPU is always simple... note this is an assignment of
# a class, not an instance.
DriveCPUClass = AtomicSimpleCPU
drive_mem_mode = 'atomic'

# We use the atomic simple CPU model:
class TestCPUClass(AtomicSimpleCPU): pass
# "TestCPUClass" is our CPU class type (sic!), derived from AtomicSimpleCPU
test_mem_mode = 'atomic'
FutureClass = None

TestCPUClass.clock = '2GHz'
DriveCPUClass.clock = '2GHz'

# Extract #CPUs:
np = options.num_cpus

# This script is only designed to be used with ARM targets. However,
# it should be simple to extend it.
if buildEnv['TARGET_ISA'] != "arm":
	fatal("Incapable of building %s full system (only for ARM)! You may want to try fs.py instead.", buildEnv['TARGET_ISA'])

# Create the gem5 component hierarchy (see above for the definition).
# In particular, it specifies system-specific parameters (e.g., Multi-
# processor system flag, memory bus (type), an UART, ...):
test_sys = makeFailArmSystem(test_mem_mode, options.machine_type)

# Set the path to the kernel. Essentially, this the path to your
# experiment target (e.g., the abo-simple-arm.elf file). It should
# contain the bootload code as well.
if options.kernel is not None:
    print "[Fail*] Using target: " + options.kernel
    test_sys.kernel = options.kernel
else:
	print "[Fail*] No kernel target given, exiting!"
	sys.exit(1)

test_sys.init_param = options.init_param

# Construct the CPUs (np in total) and connect them to the "test system":
test_sys.cpu = [TestCPUClass(cpu_id=i) for i in xrange(np)]

# Create the memory:
mem_size = SysConfig().mem()

# Create the caches:
if options.caches or options.l2cache:
    test_sys.iocache = IOCache(addr_ranges=[test_sys.physmem.range])
    test_sys.iocache.cpu_side = test_sys.iobus.master
    test_sys.iocache.mem_side = test_sys.membus.slave
else:
    test_sys.iobridge = Bridge(delay='50ns', nack_delay='4ns',
                               ranges = [test_sys.physmem.range])
    test_sys.iobridge.slave = test_sys.iobus.master
    test_sys.iobridge.master = test_sys.membus.slave
    test_sys.failfake = FailFakeDevice(pio_addr=0xF00); #DanceOS
    test_sys.iobus.default = test_sys.failfake.pio #DanceOS

# Sanity check
if options.fastmem and (options.caches or options.l2cache):
    fatal("You cannot use fastmem in combination with caches!")

for i in xrange(np):
    if options.fastmem:
        test_sys.cpu[i].fastmem = True
    if options.checker:
        test_sys.cpu[i].addCheckerCpu()

CacheConfig.config_cache(options, test_sys)

# Setup the root node and connect the hierarchy:
root = Root(full_system=True, system=test_sys)

# TODO: What's this for?
if options.timesync:
    root.time_sync_enable = True

# TODO: What's this for?
if options.frame_capture:
    VncServer.frame_capture = True

# --------------------------------------------------------------
# Simulation phase:
Simulation.setWorkCountOptions(test_sys, options) # see Simulation.py
# In essence, "run(...)" calls m5.instantiate() and, finally, m5.simulate().
Simulation.run(options, root, test_sys, FutureClass)
