#
# Simple 5 event top level model
#

print_error = lambda msg: False
version = "1.0"
check_event = lambda ev: False

# Constants

PipelineWidth = 4

uops_retired_slots = "UOPS_RETIRED.RETIRE_SLOTS"

def CLKS(EV):
    return EV("CPU_CLK_UNHALTED.THREAD", 1)

def SLOTS(EV):
    return PipelineWidth * CLKS(EV)

# Instructions Per Cycle
def IPC(EV, level):
    return EV("INST_RETIRED.ANY", level) / CLKS(EV)

# Uops Per Instruction
def UPI(EV, level):
    return EV(uops_retired_slots, level) / EV("INST_RETIRED.ANY", level)

# Average Frequency Utilization relative nominal frequency
def TurboUtilization(EV, level):
    return CLKS(EV) / EV("CPU_CLK_UNHALTED.REF_TSC", level)

class FrontendBound:
    name = "Frontend Bound"
    domain = "Slots"
    desc = """
This category reflects slots where the Frontend of the processor undersupplies
its Backend."""
    level = 1
    def compute(self, EV):
        try:
            self.val = EV("IDQ_UOPS_NOT_DELIVERED.CORE", 1) / SLOTS(EV)
            self.thresh = self.val > 0.2
        except ZeroDivisionError:
            self.val = 0
            self.thresh = False
        return self.val

class BadSpeculation:
    name = "Bad Speculation"
    domain = "Slots"
    desc = """
This category reflects slots wasted due to incorrect speculations, which
include slots used to allocate uops that do not eventually get retired and
slots for which allocation was blocked due to recovery from earlier incorrect
speculation. For example, wasted work due to miss-predicted branches is
categorized under the Bad Speculation category"""
    level = 1
    def compute(self, EV):
        try:
            self.val = ( EV("UOPS_ISSUED.ANY", 1) - EV(uops_retired_slots, 1) + PipelineWidth * EV("INT_MISC.RECOVERY_CYCLES", 1) ) / SLOTS(EV)
            self.thresh = self.val > 0.1
        except ZeroDivisionError:
            self.val = 0
            self.thresh = False
        return self.val

class BackendBound:
    name = "Backend Bound"
    domain = "Slots"
    desc = """
This category reflects slots where no uops are being delivered due to a lack
of required resources for accepting more uops in the Backend of the pipeline."""
    level = 1
    def compute(self, EV):
        try:
            self.val = 1 - ( self.FrontendBound.compute(EV) + self.BadSpeculation.compute(EV) + self.Retiring.compute(EV) )
            self.thresh = self.val > 0.2
        except ZeroDivisionError:
            self.val = 0
            self.thresh = False
        return self.val

class Retiring:
    name = "Retiring"
    domain = "Slots"
    desc = """
This category reflects slots utilized by good uops i.e. allocated uops that
eventually get retired."""
    level = 1
    def compute(self, EV):
        try:
            self.val = EV(uops_retired_slots, 1) / SLOTS(EV)
            self.thresh = self.val > 0.7
        except ZeroDivisionError:
            self.val = 0
            self.thresh = False
        return self.val

class Metric_IPC:
    name = "IPC"
    desc = """
Instructions Per Cycle"""
    errcount = 0

    def compute(self, EV):
        try:
            self.val = IPC(EV, 0)
        except ZeroDivisionError:
            print_error("IPC zero division")
            self.errcount += 1
            self.val = 0

class Metric_UPI:
    name = "UPI"
    desc = """
Uops Per Instruction"""
    errcount = 0

    def compute(self, EV):
        try:
            self.val = UPI(EV, 0)
        except ZeroDivisionError:
            print_error("UPI zero division")
            self.errcount += 1
            self.val = 0

class Metric_TurboUtilization:
    name = "TurboUtilization"
    desc = """
Average Frequency Utilization relative nominal frequency"""
    errcount = 0

    def compute(self, EV):
        try:
            self.val = TurboUtilization(EV, 0)
        except ZeroDivisionError:
            print_error("TurboUtilization zero division")
            self.errcount += 1
            self.val = 0

class Setup:
    def __init__(self, r):
        if check_event("UOPS_RETIRED.SLOTS"):
            global uops_retired_slots
            uops_retired_slots = "UOPS_RETIRED.SLOTS"

        o = dict()
        n = FrontendBound() ; r.run(n)
        o["FrontendBound"] = n
        n = BadSpeculation() ; r.run(n)
        o["BadSpeculation"] = n
        n = BackendBound() ; r.run(n)
        o["BackendBound"] = n
        n = Retiring() ; r.run(n)
        o["Retiring"] = n

        o["FrontendBound"].parent = None
        o["BadSpeculation"].parent = None
        o["BackendBound"].parent = None
        o["Retiring"].parent = None
        o["BackendBound"].FrontendBound = o["FrontendBound"]
        o["BackendBound"].BadSpeculation = o["BadSpeculation"]
        o["BackendBound"].Retiring = o["Retiring"]

        o["FrontendBound"].sibling = None
        o["BadSpeculation"].sibling = None
        o["BackendBound"].sibling = None
        o["Retiring"].sibling = None

        o["FrontendBound"].sample = []
        o["BadSpeculation"].sample = []
        o["BackendBound"].sample = []
        o["Retiring"].sample = []

        # user visible metrics

        n = Metric_IPC() ; r.metric(n)
        n = Metric_UPI() ; r.metric(n)
        n = Metric_TurboUtilization() ; r.metric(n)
